Merge "Adjust permissions for perfetto profiling dir" into main
diff --git a/.bazelignore b/.bazelignore
new file mode 100644
index 0000000..a35fb26
--- /dev/null
+++ b/.bazelignore
@@ -0,0 +1 @@
+ui
\ No newline at end of file
diff --git a/Android.bp b/Android.bp
index b638356..c46b914 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1318,8 +1318,8 @@
}
// GN: [//protos/perfetto/config:source_set]
-java_library {
- name: "perfetto_config_java_protos",
+filegroup {
+ name: "perfetto_config_filegroup_proto",
srcs: [
"protos/perfetto/common/android_energy_consumer_descriptor.proto",
"protos/perfetto/common/android_log_constants.proto",
@@ -1375,10 +1375,6 @@
"protos/perfetto/config/trace_config.proto",
"protos/perfetto/config/track_event/track_event_config.proto",
],
- proto: {
- type: "lite",
- canonical_path_from_root: false,
- },
}
// GN: //test/cts:perfetto_cts_deps
@@ -2445,6 +2441,7 @@
":perfetto_src_trace_processor_util_build_id",
":perfetto_src_trace_processor_util_bump_allocator",
":perfetto_src_trace_processor_util_descriptors",
+ ":perfetto_src_trace_processor_util_file_buffer",
":perfetto_src_trace_processor_util_glob",
":perfetto_src_trace_processor_util_gzip",
":perfetto_src_trace_processor_util_interned_message_view",
@@ -2457,6 +2454,7 @@
":perfetto_src_trace_processor_util_regex",
":perfetto_src_trace_processor_util_sql_argument",
":perfetto_src_trace_processor_util_stdlib",
+ ":perfetto_src_trace_processor_util_trace_type",
":perfetto_src_trace_processor_util_util",
":perfetto_src_trace_processor_util_zip_reader",
":perfetto_src_traced_probes_android_game_intervention_list_android_game_intervention_list",
@@ -12030,6 +12028,7 @@
"src/trace_processor/importers/common/machine_tracker.cc",
"src/trace_processor/importers/common/mapping_tracker.cc",
"src/trace_processor/importers/common/metadata_tracker.cc",
+ "src/trace_processor/importers/common/process_track_translation_table.cc",
"src/trace_processor/importers/common/process_tracker.cc",
"src/trace_processor/importers/common/sched_event_tracker.cc",
"src/trace_processor/importers/common/slice_tracker.cc",
@@ -12065,6 +12064,7 @@
"src/trace_processor/importers/common/deobfuscation_mapping_table_unittest.cc",
"src/trace_processor/importers/common/event_tracker_unittest.cc",
"src/trace_processor/importers/common/flow_tracker_unittest.cc",
+ "src/trace_processor/importers/common/process_track_translation_table_unittest.cc",
"src/trace_processor/importers/common/process_tracker_unittest.cc",
"src/trace_processor/importers/common/slice_tracker_unittest.cc",
"src/trace_processor/importers/common/slice_translation_table_unittest.cc",
@@ -12236,10 +12236,12 @@
filegroup {
name: "perfetto_src_trace_processor_importers_perf_perf",
srcs: [
- "src/trace_processor/importers/perf/perf_data_parser.cc",
- "src/trace_processor/importers/perf/perf_data_reader.cc",
+ "src/trace_processor/importers/perf/attrs_section_reader.cc",
+ "src/trace_processor/importers/perf/features.cc",
+ "src/trace_processor/importers/perf/mmap_record.cc",
"src/trace_processor/importers/perf/perf_data_tokenizer.cc",
- "src/trace_processor/importers/perf/perf_data_tracker.cc",
+ "src/trace_processor/importers/perf/record_parser.cc",
+ "src/trace_processor/importers/perf/sample.cc",
],
}
@@ -12247,6 +12249,7 @@
filegroup {
name: "perfetto_src_trace_processor_importers_perf_record",
srcs: [
+ "src/trace_processor/importers/perf/perf_counter.cc",
"src/trace_processor/importers/perf/perf_event_attr.cc",
"src/trace_processor/importers/perf/perf_session.cc",
],
@@ -12256,8 +12259,6 @@
filegroup {
name: "perfetto_src_trace_processor_importers_perf_unittests",
srcs: [
- "src/trace_processor/importers/perf/perf_data_reader_unittest.cc",
- "src/trace_processor/importers/perf/perf_data_tracker_unittest.cc",
"src/trace_processor/importers/perf/perf_session_unittest.cc",
"src/trace_processor/importers/perf/reader_unittest.cc",
],
@@ -13182,6 +13183,7 @@
"src/trace_processor/trace_processor_context.cc",
"src/trace_processor/trace_processor_storage.cc",
"src/trace_processor/trace_processor_storage_impl.cc",
+ "src/trace_processor/trace_reader_registry.cc",
"src/trace_processor/virtual_destructors.cc",
],
}
@@ -13456,6 +13458,14 @@
name: "perfetto_src_trace_processor_util_stdlib",
}
+// GN: //src/trace_processor/util:trace_type
+filegroup {
+ name: "perfetto_src_trace_processor_util_trace_type",
+ srcs: [
+ "src/trace_processor/util/trace_type.cc",
+ ],
+}
+
// GN: //src/trace_processor/util:unittests
filegroup {
name: "perfetto_src_trace_processor_util_unittests",
@@ -15061,6 +15071,7 @@
":perfetto_src_trace_processor_util_regex",
":perfetto_src_trace_processor_util_sql_argument",
":perfetto_src_trace_processor_util_stdlib",
+ ":perfetto_src_trace_processor_util_trace_type",
":perfetto_src_trace_processor_util_unittests",
":perfetto_src_trace_processor_util_util",
":perfetto_src_trace_processor_util_zip_reader",
@@ -16030,6 +16041,7 @@
":perfetto_src_trace_processor_util_build_id",
":perfetto_src_trace_processor_util_bump_allocator",
":perfetto_src_trace_processor_util_descriptors",
+ ":perfetto_src_trace_processor_util_file_buffer",
":perfetto_src_trace_processor_util_glob",
":perfetto_src_trace_processor_util_gzip",
":perfetto_src_trace_processor_util_interned_message_view",
@@ -16042,6 +16054,7 @@
":perfetto_src_trace_processor_util_regex",
":perfetto_src_trace_processor_util_sql_argument",
":perfetto_src_trace_processor_util_stdlib",
+ ":perfetto_src_trace_processor_util_trace_type",
":perfetto_src_trace_processor_util_util",
":perfetto_src_trace_processor_util_zip_reader",
"src/trace_processor/trace_processor_shell.cc",
@@ -16216,6 +16229,7 @@
":perfetto_src_trace_processor_importers_fuchsia_fuchsia_record",
":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_proto_minimal",
":perfetto_src_trace_processor_importers_proto_packet_sequence_state_generation_hdr",
":perfetto_src_trace_processor_importers_proto_proto_importer_module",
@@ -16236,6 +16250,7 @@
":perfetto_src_trace_processor_util_proto_to_args_parser",
":perfetto_src_trace_processor_util_protozero_to_text",
":perfetto_src_trace_processor_util_regex",
+ ":perfetto_src_trace_processor_util_trace_type",
":perfetto_src_trace_processor_util_util",
":perfetto_src_trace_redaction_trace_redaction",
"src/trace_redaction/main.cc",
@@ -16412,6 +16427,7 @@
":perfetto_src_trace_processor_util_build_id",
":perfetto_src_trace_processor_util_bump_allocator",
":perfetto_src_trace_processor_util_descriptors",
+ ":perfetto_src_trace_processor_util_file_buffer",
":perfetto_src_trace_processor_util_glob",
":perfetto_src_trace_processor_util_gzip",
":perfetto_src_trace_processor_util_interned_message_view",
@@ -16424,6 +16440,7 @@
":perfetto_src_trace_processor_util_regex",
":perfetto_src_trace_processor_util_sql_argument",
":perfetto_src_trace_processor_util_stdlib",
+ ":perfetto_src_trace_processor_util_trace_type",
":perfetto_src_trace_processor_util_util",
":perfetto_src_trace_processor_util_zip_reader",
":perfetto_src_traceconv_lib",
@@ -17244,6 +17261,38 @@
output_extension: "srcjar",
}
+java_library {
+ name: "perfetto_config_java_protos",
+ srcs: [
+ ":perfetto_config_filegroup_proto",
+ ],
+ static_libs: [
+ "libprotobuf-java-lite",
+ ],
+ proto: {
+ type: "lite",
+ canonical_path_from_root: false,
+ },
+}
+
+java_library {
+ name: "perfetto_config_java_protos_system_server_current",
+ srcs: [
+ ":perfetto_config_filegroup_proto",
+ ],
+ static_libs: [
+ "libprotobuf-java-lite",
+ ],
+ proto: {
+ type: "lite",
+ canonical_path_from_root: false,
+ },
+ sdk_version: "system_server_current",
+ apex_available: [
+ "com.android.profiling",
+ ],
+}
+
prebuilt_etc {
name: "perfetto_persistent_cfg.pbtxt",
filename: "persistent_cfg.pbtxt",
diff --git a/Android.bp.extras b/Android.bp.extras
index 75d3a81..56cfcdd 100644
--- a/Android.bp.extras
+++ b/Android.bp.extras
@@ -198,6 +198,38 @@
output_extension: "srcjar",
}
+java_library {
+ name: "perfetto_config_java_protos",
+ srcs: [
+ ":perfetto_config_filegroup_proto",
+ ],
+ static_libs: [
+ "libprotobuf-java-lite",
+ ],
+ proto: {
+ type: "lite",
+ canonical_path_from_root: false,
+ },
+}
+
+java_library {
+ name: "perfetto_config_java_protos_system_server_current",
+ srcs: [
+ ":perfetto_config_filegroup_proto",
+ ],
+ static_libs: [
+ "libprotobuf-java-lite",
+ ],
+ proto: {
+ type: "lite",
+ canonical_path_from_root: false,
+ },
+ sdk_version: "system_server_current",
+ apex_available: [
+ "com.android.profiling",
+ ],
+}
+
prebuilt_etc {
name: "perfetto_persistent_cfg.pbtxt",
filename: "persistent_cfg.pbtxt",
diff --git a/BUILD b/BUILD
index f4167f8..aa5ed3c 100644
--- a/BUILD
+++ b/BUILD
@@ -272,6 +272,7 @@
":src_trace_processor_util_build_id",
":src_trace_processor_util_bump_allocator",
":src_trace_processor_util_descriptors",
+ ":src_trace_processor_util_file_buffer",
":src_trace_processor_util_glob",
":src_trace_processor_util_gzip",
":src_trace_processor_util_interned_message_view",
@@ -284,6 +285,7 @@
":src_trace_processor_util_regex",
":src_trace_processor_util_sql_argument",
":src_trace_processor_util_stdlib",
+ ":src_trace_processor_util_trace_type",
":src_trace_processor_util_util",
":src_trace_processor_util_zip_reader",
],
@@ -884,6 +886,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",
@@ -897,7 +900,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",
],
)
@@ -1494,6 +1496,8 @@
"src/trace_processor/importers/common/mapping_tracker.h",
"src/trace_processor/importers/common/metadata_tracker.cc",
"src/trace_processor/importers/common/metadata_tracker.h",
+ "src/trace_processor/importers/common/process_track_translation_table.cc",
+ "src/trace_processor/importers/common/process_track_translation_table.h",
"src/trace_processor/importers/common/process_tracker.cc",
"src/trace_processor/importers/common/process_tracker.h",
"src/trace_processor/importers/common/sched_event_state.h",
@@ -1702,16 +1706,19 @@
perfetto_filegroup(
name = "src_trace_processor_importers_perf_perf",
srcs = [
- "src/trace_processor/importers/perf/perf_data_parser.cc",
- "src/trace_processor/importers/perf/perf_data_parser.h",
- "src/trace_processor/importers/perf/perf_data_reader.cc",
- "src/trace_processor/importers/perf/perf_data_reader.h",
+ "src/trace_processor/importers/perf/attrs_section_reader.cc",
+ "src/trace_processor/importers/perf/attrs_section_reader.h",
+ "src/trace_processor/importers/perf/features.cc",
+ "src/trace_processor/importers/perf/features.h",
+ "src/trace_processor/importers/perf/mmap_record.cc",
+ "src/trace_processor/importers/perf/mmap_record.h",
"src/trace_processor/importers/perf/perf_data_tokenizer.cc",
"src/trace_processor/importers/perf/perf_data_tokenizer.h",
- "src/trace_processor/importers/perf/perf_data_tracker.cc",
- "src/trace_processor/importers/perf/perf_data_tracker.h",
"src/trace_processor/importers/perf/perf_file.h",
- "src/trace_processor/importers/perf/reader.h",
+ "src/trace_processor/importers/perf/record_parser.cc",
+ "src/trace_processor/importers/perf/record_parser.h",
+ "src/trace_processor/importers/perf/sample.cc",
+ "src/trace_processor/importers/perf/sample.h",
],
)
@@ -1719,6 +1726,8 @@
perfetto_filegroup(
name = "src_trace_processor_importers_perf_record",
srcs = [
+ "src/trace_processor/importers/perf/perf_counter.cc",
+ "src/trace_processor/importers/perf/perf_counter.h",
"src/trace_processor/importers/perf/perf_event.h",
"src/trace_processor/importers/perf/perf_event_attr.cc",
"src/trace_processor/importers/perf/perf_event_attr.h",
@@ -2902,6 +2911,15 @@
],
)
+# GN target: //src/trace_processor/util:file_buffer
+perfetto_filegroup(
+ name = "src_trace_processor_util_file_buffer",
+ srcs = [
+ "src/trace_processor/util/file_buffer.cc",
+ "src/trace_processor/util/file_buffer.h",
+ ],
+)
+
# GN target: //src/trace_processor/util:glob
perfetto_filegroup(
name = "src_trace_processor_util_glob",
@@ -3011,6 +3029,15 @@
],
)
+# GN target: //src/trace_processor/util:trace_type
+perfetto_filegroup(
+ name = "src_trace_processor_util_trace_type",
+ srcs = [
+ "src/trace_processor/util/trace_type.cc",
+ "src/trace_processor/util/trace_type.h",
+ ],
+)
+
# GN target: //src/trace_processor/util:util
perfetto_filegroup(
name = "src_trace_processor_util_util",
@@ -3092,6 +3119,8 @@
"src/trace_processor/trace_processor_storage.cc",
"src/trace_processor/trace_processor_storage_impl.cc",
"src/trace_processor/trace_processor_storage_impl.h",
+ "src/trace_processor/trace_reader_registry.cc",
+ "src/trace_processor/trace_reader_registry.h",
"src/trace_processor/virtual_destructors.cc",
],
)
@@ -5935,6 +5964,7 @@
":src_trace_processor_util_build_id",
":src_trace_processor_util_bump_allocator",
":src_trace_processor_util_descriptors",
+ ":src_trace_processor_util_file_buffer",
":src_trace_processor_util_glob",
":src_trace_processor_util_gzip",
":src_trace_processor_util_interned_message_view",
@@ -5947,6 +5977,7 @@
":src_trace_processor_util_regex",
":src_trace_processor_util_sql_argument",
":src_trace_processor_util_stdlib",
+ ":src_trace_processor_util_trace_type",
":src_trace_processor_util_util",
":src_trace_processor_util_zip_reader",
],
@@ -6112,6 +6143,7 @@
":src_trace_processor_util_build_id",
":src_trace_processor_util_bump_allocator",
":src_trace_processor_util_descriptors",
+ ":src_trace_processor_util_file_buffer",
":src_trace_processor_util_glob",
":src_trace_processor_util_gzip",
":src_trace_processor_util_interned_message_view",
@@ -6124,6 +6156,7 @@
":src_trace_processor_util_regex",
":src_trace_processor_util_sql_argument",
":src_trace_processor_util_stdlib",
+ ":src_trace_processor_util_trace_type",
":src_trace_processor_util_util",
":src_trace_processor_util_zip_reader",
"src/trace_processor/trace_processor_shell.cc",
@@ -6343,6 +6376,7 @@
":src_trace_processor_util_build_id",
":src_trace_processor_util_bump_allocator",
":src_trace_processor_util_descriptors",
+ ":src_trace_processor_util_file_buffer",
":src_trace_processor_util_glob",
":src_trace_processor_util_gzip",
":src_trace_processor_util_interned_message_view",
@@ -6355,6 +6389,7 @@
":src_trace_processor_util_regex",
":src_trace_processor_util_sql_argument",
":src_trace_processor_util_stdlib",
+ ":src_trace_processor_util_trace_type",
":src_trace_processor_util_util",
":src_trace_processor_util_zip_reader",
":src_traceconv_lib",
diff --git a/CHANGELOG b/CHANGELOG
index d117e7d..70cb79c 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,13 +1,21 @@
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`.
+ * Migrated `sched.utilization` package to `cpu.utilization`.
Trace Processor:
* Added "time to initial display" and "time to full display" metrics to
the Android startup metric.
UI:
*
SDK:
- *
+ * The TRACE_COUNTER macro and CounterTrack constructor no longer accept
+ `const char *` track names. In case your code fails to compile,
+ https://perfetto.dev/docs/instrumentation/track-events#dynamic-event-names
+ explains how to fix the problem.
v45.0 - 2024-05-09:
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 9d84571..0e6a83d 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -1,4 +1,14 @@
{
+ "art-mainline-presubmit": [
+ {
+ "name": "CtsPerfettoTestCases",
+ "options": [
+ {
+ "include-filter": "HeapprofdJavaCtsTest*"
+ }
+ ]
+ }
+ ],
"presubmit": [
{
"name": "CtsPerfettoTestCases"
diff --git a/buildtools/BUILD.gn b/buildtools/BUILD.gn
index ae25789..0518359 100644
--- a/buildtools/BUILD.gn
+++ b/buildtools/BUILD.gn
@@ -164,141 +164,147 @@
}
}
+_protobuf_headers = [
+ "protobuf/src/google/protobuf/any.h",
+ "protobuf/src/google/protobuf/any.pb.h",
+ "protobuf/src/google/protobuf/api.pb.h",
+ "protobuf/src/google/protobuf/arena_impl.h",
+ "protobuf/src/google/protobuf/arena.h",
+ "protobuf/src/google/protobuf/arenastring.h",
+ "protobuf/src/google/protobuf/arenaz_sampler.h",
+ "protobuf/src/google/protobuf/compiler/importer.h",
+ "protobuf/src/google/protobuf/compiler/parser.h",
+ "protobuf/src/google/protobuf/descriptor_database.h",
+ "protobuf/src/google/protobuf/descriptor.h",
+ "protobuf/src/google/protobuf/descriptor.pb.h",
+ "protobuf/src/google/protobuf/duration.pb.h",
+ "protobuf/src/google/protobuf/dynamic_message.h",
+ "protobuf/src/google/protobuf/empty.pb.h",
+ "protobuf/src/google/protobuf/endian.h",
+ "protobuf/src/google/protobuf/explicitly_constructed.h",
+ "protobuf/src/google/protobuf/extension_set_inl.h",
+ "protobuf/src/google/protobuf/extension_set.h",
+ "protobuf/src/google/protobuf/field_access_listener.h",
+ "protobuf/src/google/protobuf/field_mask.pb.h",
+ "protobuf/src/google/protobuf/generated_enum_reflection.h",
+ "protobuf/src/google/protobuf/generated_enum_util.h",
+ "protobuf/src/google/protobuf/generated_message_bases.h",
+ "protobuf/src/google/protobuf/generated_message_reflection.h",
+ "protobuf/src/google/protobuf/generated_message_tctable_decl.h",
+ "protobuf/src/google/protobuf/generated_message_tctable_impl.h",
+ "protobuf/src/google/protobuf/generated_message_util.h",
+ "protobuf/src/google/protobuf/has_bits.h",
+ "protobuf/src/google/protobuf/implicit_weak_message.h",
+ "protobuf/src/google/protobuf/inlined_string_field.h",
+ "protobuf/src/google/protobuf/io/coded_stream.h",
+ "protobuf/src/google/protobuf/io/io_win32.h",
+ "protobuf/src/google/protobuf/io/printer.h",
+ "protobuf/src/google/protobuf/io/strtod.h",
+ "protobuf/src/google/protobuf/io/tokenizer.h",
+ "protobuf/src/google/protobuf/io/zero_copy_stream_impl_lite.h",
+ "protobuf/src/google/protobuf/io/zero_copy_stream_impl.h",
+ "protobuf/src/google/protobuf/io/zero_copy_stream.h",
+ "protobuf/src/google/protobuf/map_entry_lite.h",
+ "protobuf/src/google/protobuf/map_entry.h",
+ "protobuf/src/google/protobuf/map_field_inl.h",
+ "protobuf/src/google/protobuf/map_field_lite.h",
+ "protobuf/src/google/protobuf/map_field.h",
+ "protobuf/src/google/protobuf/map_type_handler.h",
+ "protobuf/src/google/protobuf/map.h",
+ "protobuf/src/google/protobuf/message_lite.h",
+ "protobuf/src/google/protobuf/message.h",
+ "protobuf/src/google/protobuf/metadata_lite.h",
+ "protobuf/src/google/protobuf/metadata.h",
+ "protobuf/src/google/protobuf/parse_context.h",
+ "protobuf/src/google/protobuf/port_def.inc",
+ "protobuf/src/google/protobuf/port_undef.inc",
+ "protobuf/src/google/protobuf/port.h",
+ "protobuf/src/google/protobuf/reflection_internal.h",
+ "protobuf/src/google/protobuf/reflection_ops.h",
+ "protobuf/src/google/protobuf/reflection.h",
+ "protobuf/src/google/protobuf/repeated_field.h",
+ "protobuf/src/google/protobuf/repeated_ptr_field.h",
+ "protobuf/src/google/protobuf/service.h",
+ "protobuf/src/google/protobuf/source_context.pb.h",
+ "protobuf/src/google/protobuf/struct.pb.h",
+ "protobuf/src/google/protobuf/stubs/bytestream.h",
+ "protobuf/src/google/protobuf/stubs/callback.h",
+ "protobuf/src/google/protobuf/stubs/casts.h",
+ "protobuf/src/google/protobuf/stubs/common.h",
+ "protobuf/src/google/protobuf/stubs/hash.h",
+ "protobuf/src/google/protobuf/stubs/logging.h",
+ "protobuf/src/google/protobuf/stubs/macros.h",
+ "protobuf/src/google/protobuf/stubs/map_util.h",
+ "protobuf/src/google/protobuf/stubs/mutex.h",
+ "protobuf/src/google/protobuf/stubs/once.h",
+ "protobuf/src/google/protobuf/stubs/platform_macros.h",
+ "protobuf/src/google/protobuf/stubs/port.h",
+ "protobuf/src/google/protobuf/stubs/status.h",
+ "protobuf/src/google/protobuf/stubs/stl_util.h",
+ "protobuf/src/google/protobuf/stubs/stringpiece.h",
+ "protobuf/src/google/protobuf/stubs/strutil.h",
+ "protobuf/src/google/protobuf/stubs/template_util.h",
+ "protobuf/src/google/protobuf/text_format.h",
+ "protobuf/src/google/protobuf/timestamp.pb.h",
+ "protobuf/src/google/protobuf/type.pb.h",
+ "protobuf/src/google/protobuf/unknown_field_set.h",
+ "protobuf/src/google/protobuf/util/delimited_message_util.h",
+ "protobuf/src/google/protobuf/util/field_comparator.h",
+ "protobuf/src/google/protobuf/util/field_mask_util.h",
+ "protobuf/src/google/protobuf/util/json_util.h",
+ "protobuf/src/google/protobuf/util/message_differencer.h",
+ "protobuf/src/google/protobuf/util/time_util.h",
+ "protobuf/src/google/protobuf/util/type_resolver_util.h",
+ "protobuf/src/google/protobuf/util/type_resolver.h",
+ "protobuf/src/google/protobuf/wire_format_lite.h",
+ "protobuf/src/google/protobuf/wire_format.h",
+ "protobuf/src/google/protobuf/wrappers.pb.h",
+]
+
source_set("protobuf_lite") {
visibility = _buildtools_visibility
sources = [
- "protobuf/src/google/protobuf/any.h",
- "protobuf/src/google/protobuf/any.pb.h",
"protobuf/src/google/protobuf/any_lite.cc",
- "protobuf/src/google/protobuf/api.pb.h",
"protobuf/src/google/protobuf/arena.cc",
- "protobuf/src/google/protobuf/arena.h",
- "protobuf/src/google/protobuf/arena_impl.h",
"protobuf/src/google/protobuf/arenastring.cc",
- "protobuf/src/google/protobuf/arenastring.h",
"protobuf/src/google/protobuf/arenaz_sampler.cc",
- "protobuf/src/google/protobuf/arenaz_sampler.h",
- "protobuf/src/google/protobuf/compiler/importer.h",
- "protobuf/src/google/protobuf/compiler/parser.h",
- "protobuf/src/google/protobuf/descriptor.h",
- "protobuf/src/google/protobuf/descriptor.pb.h",
- "protobuf/src/google/protobuf/descriptor_database.h",
- "protobuf/src/google/protobuf/duration.pb.h",
- "protobuf/src/google/protobuf/dynamic_message.h",
- "protobuf/src/google/protobuf/empty.pb.h",
- "protobuf/src/google/protobuf/explicitly_constructed.h",
"protobuf/src/google/protobuf/extension_set.cc",
- "protobuf/src/google/protobuf/extension_set.h",
- "protobuf/src/google/protobuf/extension_set_inl.h",
- "protobuf/src/google/protobuf/field_access_listener.h",
- "protobuf/src/google/protobuf/field_mask.pb.h",
- "protobuf/src/google/protobuf/generated_enum_reflection.h",
"protobuf/src/google/protobuf/generated_enum_util.cc",
- "protobuf/src/google/protobuf/generated_enum_util.h",
- "protobuf/src/google/protobuf/generated_message_bases.h",
- "protobuf/src/google/protobuf/generated_message_reflection.h",
- "protobuf/src/google/protobuf/generated_message_tctable_decl.h",
- "protobuf/src/google/protobuf/generated_message_tctable_impl.h",
"protobuf/src/google/protobuf/generated_message_tctable_lite.cc",
"protobuf/src/google/protobuf/generated_message_util.cc",
- "protobuf/src/google/protobuf/generated_message_util.h",
- "protobuf/src/google/protobuf/has_bits.h",
"protobuf/src/google/protobuf/implicit_weak_message.cc",
- "protobuf/src/google/protobuf/implicit_weak_message.h",
"protobuf/src/google/protobuf/inlined_string_field.cc",
- "protobuf/src/google/protobuf/inlined_string_field.h",
"protobuf/src/google/protobuf/io/coded_stream.cc",
- "protobuf/src/google/protobuf/io/coded_stream.h",
"protobuf/src/google/protobuf/io/io_win32.cc",
- "protobuf/src/google/protobuf/io/io_win32.h",
- "protobuf/src/google/protobuf/io/printer.h",
"protobuf/src/google/protobuf/io/strtod.cc",
- "protobuf/src/google/protobuf/io/strtod.h",
- "protobuf/src/google/protobuf/io/tokenizer.h",
"protobuf/src/google/protobuf/io/zero_copy_stream.cc",
- "protobuf/src/google/protobuf/io/zero_copy_stream.h",
"protobuf/src/google/protobuf/io/zero_copy_stream_impl.cc",
- "protobuf/src/google/protobuf/io/zero_copy_stream_impl.h",
"protobuf/src/google/protobuf/io/zero_copy_stream_impl_lite.cc",
- "protobuf/src/google/protobuf/io/zero_copy_stream_impl_lite.h",
"protobuf/src/google/protobuf/map.cc",
- "protobuf/src/google/protobuf/map.h",
- "protobuf/src/google/protobuf/map_entry.h",
- "protobuf/src/google/protobuf/map_entry_lite.h",
- "protobuf/src/google/protobuf/map_field.h",
- "protobuf/src/google/protobuf/map_field_inl.h",
- "protobuf/src/google/protobuf/map_field_lite.h",
- "protobuf/src/google/protobuf/map_type_handler.h",
- "protobuf/src/google/protobuf/message.h",
"protobuf/src/google/protobuf/message_lite.cc",
- "protobuf/src/google/protobuf/message_lite.h",
- "protobuf/src/google/protobuf/metadata.h",
- "protobuf/src/google/protobuf/metadata_lite.h",
"protobuf/src/google/protobuf/parse_context.cc",
- "protobuf/src/google/protobuf/parse_context.h",
- "protobuf/src/google/protobuf/port.h",
- "protobuf/src/google/protobuf/port_def.inc",
- "protobuf/src/google/protobuf/port_undef.inc",
- "protobuf/src/google/protobuf/reflection.h",
- "protobuf/src/google/protobuf/reflection_ops.h",
"protobuf/src/google/protobuf/repeated_field.cc",
- "protobuf/src/google/protobuf/repeated_field.h",
"protobuf/src/google/protobuf/repeated_ptr_field.cc",
- "protobuf/src/google/protobuf/repeated_ptr_field.h",
- "protobuf/src/google/protobuf/service.h",
- "protobuf/src/google/protobuf/source_context.pb.h",
"protobuf/src/google/protobuf/string_member_robber.h",
- "protobuf/src/google/protobuf/struct.pb.h",
"protobuf/src/google/protobuf/stubs/bytestream.cc",
- "protobuf/src/google/protobuf/stubs/bytestream.h",
- "protobuf/src/google/protobuf/stubs/callback.h",
- "protobuf/src/google/protobuf/stubs/casts.h",
"protobuf/src/google/protobuf/stubs/common.cc",
- "protobuf/src/google/protobuf/stubs/common.h",
- "protobuf/src/google/protobuf/stubs/hash.h",
"protobuf/src/google/protobuf/stubs/int128.cc",
"protobuf/src/google/protobuf/stubs/int128.h",
- "protobuf/src/google/protobuf/stubs/logging.h",
- "protobuf/src/google/protobuf/stubs/macros.h",
- "protobuf/src/google/protobuf/stubs/map_util.h",
"protobuf/src/google/protobuf/stubs/mathutil.h",
- "protobuf/src/google/protobuf/stubs/mutex.h",
- "protobuf/src/google/protobuf/stubs/once.h",
- "protobuf/src/google/protobuf/stubs/platform_macros.h",
- "protobuf/src/google/protobuf/stubs/port.h",
"protobuf/src/google/protobuf/stubs/status.cc",
- "protobuf/src/google/protobuf/stubs/status.h",
"protobuf/src/google/protobuf/stubs/status_macros.h",
"protobuf/src/google/protobuf/stubs/statusor.cc",
"protobuf/src/google/protobuf/stubs/statusor.h",
- "protobuf/src/google/protobuf/stubs/stl_util.h",
"protobuf/src/google/protobuf/stubs/stringpiece.cc",
- "protobuf/src/google/protobuf/stubs/stringpiece.h",
"protobuf/src/google/protobuf/stubs/stringprintf.cc",
"protobuf/src/google/protobuf/stubs/stringprintf.h",
"protobuf/src/google/protobuf/stubs/structurally_valid.cc",
"protobuf/src/google/protobuf/stubs/strutil.cc",
- "protobuf/src/google/protobuf/stubs/strutil.h",
- "protobuf/src/google/protobuf/stubs/template_util.h",
"protobuf/src/google/protobuf/stubs/time.cc",
"protobuf/src/google/protobuf/stubs/time.h",
- "protobuf/src/google/protobuf/text_format.h",
- "protobuf/src/google/protobuf/timestamp.pb.h",
- "protobuf/src/google/protobuf/type.pb.h",
- "protobuf/src/google/protobuf/unknown_field_set.h",
- "protobuf/src/google/protobuf/util/delimited_message_util.h",
- "protobuf/src/google/protobuf/util/field_comparator.h",
- "protobuf/src/google/protobuf/util/field_mask_util.h",
- "protobuf/src/google/protobuf/util/json_util.h",
- "protobuf/src/google/protobuf/util/message_differencer.h",
- "protobuf/src/google/protobuf/util/time_util.h",
- "protobuf/src/google/protobuf/util/type_resolver.h",
- "protobuf/src/google/protobuf/util/type_resolver_util.h",
- "protobuf/src/google/protobuf/wire_format.h",
"protobuf/src/google/protobuf/wire_format_lite.cc",
- "protobuf/src/google/protobuf/wire_format_lite.h",
- "protobuf/src/google/protobuf/wrappers.pb.h",
]
+ sources += _protobuf_headers
configs -= [ "//gn/standalone:extra_warnings" ]
if (is_win) {
# Protobuf has its own #define WIN32_LEAN_AND_MEAN.
@@ -317,124 +323,39 @@
]
sources = [
"protobuf/src/google/protobuf/any.cc",
- "protobuf/src/google/protobuf/any.h",
"protobuf/src/google/protobuf/any.pb.cc",
- "protobuf/src/google/protobuf/any.pb.h",
"protobuf/src/google/protobuf/api.pb.cc",
- "protobuf/src/google/protobuf/api.pb.h",
- "protobuf/src/google/protobuf/arena.h",
- "protobuf/src/google/protobuf/arena_impl.h",
- "protobuf/src/google/protobuf/arenastring.h",
- "protobuf/src/google/protobuf/arenaz_sampler.h",
"protobuf/src/google/protobuf/compiler/importer.cc",
- "protobuf/src/google/protobuf/compiler/importer.h",
"protobuf/src/google/protobuf/compiler/parser.cc",
- "protobuf/src/google/protobuf/compiler/parser.h",
"protobuf/src/google/protobuf/descriptor.cc",
- "protobuf/src/google/protobuf/descriptor.h",
"protobuf/src/google/protobuf/descriptor.pb.cc",
- "protobuf/src/google/protobuf/descriptor.pb.h",
"protobuf/src/google/protobuf/descriptor_database.cc",
- "protobuf/src/google/protobuf/descriptor_database.h",
"protobuf/src/google/protobuf/duration.pb.cc",
- "protobuf/src/google/protobuf/duration.pb.h",
"protobuf/src/google/protobuf/dynamic_message.cc",
- "protobuf/src/google/protobuf/dynamic_message.h",
"protobuf/src/google/protobuf/empty.pb.cc",
- "protobuf/src/google/protobuf/empty.pb.h",
- "protobuf/src/google/protobuf/explicitly_constructed.h",
- "protobuf/src/google/protobuf/extension_set.h",
"protobuf/src/google/protobuf/extension_set_heavy.cc",
- "protobuf/src/google/protobuf/extension_set_inl.h",
- "protobuf/src/google/protobuf/field_access_listener.h",
"protobuf/src/google/protobuf/field_mask.pb.cc",
- "protobuf/src/google/protobuf/field_mask.pb.h",
- "protobuf/src/google/protobuf/generated_enum_reflection.h",
- "protobuf/src/google/protobuf/generated_enum_util.h",
"protobuf/src/google/protobuf/generated_message_bases.cc",
- "protobuf/src/google/protobuf/generated_message_bases.h",
"protobuf/src/google/protobuf/generated_message_reflection.cc",
- "protobuf/src/google/protobuf/generated_message_reflection.h",
- "protobuf/src/google/protobuf/generated_message_tctable_decl.h",
"protobuf/src/google/protobuf/generated_message_tctable_full.cc",
- "protobuf/src/google/protobuf/generated_message_tctable_impl.h",
- "protobuf/src/google/protobuf/generated_message_util.h",
- "protobuf/src/google/protobuf/has_bits.h",
- "protobuf/src/google/protobuf/implicit_weak_message.h",
- "protobuf/src/google/protobuf/inlined_string_field.h",
- "protobuf/src/google/protobuf/io/coded_stream.h",
"protobuf/src/google/protobuf/io/gzip_stream.cc",
- "protobuf/src/google/protobuf/io/io_win32.h",
"protobuf/src/google/protobuf/io/printer.cc",
- "protobuf/src/google/protobuf/io/printer.h",
- "protobuf/src/google/protobuf/io/strtod.h",
"protobuf/src/google/protobuf/io/tokenizer.cc",
- "protobuf/src/google/protobuf/io/tokenizer.h",
- "protobuf/src/google/protobuf/io/zero_copy_stream.h",
- "protobuf/src/google/protobuf/io/zero_copy_stream_impl.h",
- "protobuf/src/google/protobuf/io/zero_copy_stream_impl_lite.h",
- "protobuf/src/google/protobuf/map.h",
- "protobuf/src/google/protobuf/map_entry.h",
- "protobuf/src/google/protobuf/map_entry_lite.h",
"protobuf/src/google/protobuf/map_field.cc",
- "protobuf/src/google/protobuf/map_field.h",
- "protobuf/src/google/protobuf/map_field_inl.h",
- "protobuf/src/google/protobuf/map_field_lite.h",
- "protobuf/src/google/protobuf/map_type_handler.h",
"protobuf/src/google/protobuf/message.cc",
- "protobuf/src/google/protobuf/message.h",
- "protobuf/src/google/protobuf/message_lite.h",
- "protobuf/src/google/protobuf/metadata.h",
- "protobuf/src/google/protobuf/metadata_lite.h",
- "protobuf/src/google/protobuf/parse_context.h",
- "protobuf/src/google/protobuf/port.h",
- "protobuf/src/google/protobuf/port_def.inc",
- "protobuf/src/google/protobuf/port_undef.inc",
- "protobuf/src/google/protobuf/reflection.h",
- "protobuf/src/google/protobuf/reflection_internal.h",
"protobuf/src/google/protobuf/reflection_ops.cc",
- "protobuf/src/google/protobuf/reflection_ops.h",
- "protobuf/src/google/protobuf/repeated_field.h",
- "protobuf/src/google/protobuf/repeated_ptr_field.h",
"protobuf/src/google/protobuf/service.cc",
- "protobuf/src/google/protobuf/service.h",
"protobuf/src/google/protobuf/source_context.pb.cc",
- "protobuf/src/google/protobuf/source_context.pb.h",
"protobuf/src/google/protobuf/struct.pb.cc",
- "protobuf/src/google/protobuf/struct.pb.h",
- "protobuf/src/google/protobuf/stubs/bytestream.h",
- "protobuf/src/google/protobuf/stubs/callback.h",
- "protobuf/src/google/protobuf/stubs/casts.h",
- "protobuf/src/google/protobuf/stubs/common.h",
- "protobuf/src/google/protobuf/stubs/hash.h",
- "protobuf/src/google/protobuf/stubs/logging.h",
- "protobuf/src/google/protobuf/stubs/macros.h",
- "protobuf/src/google/protobuf/stubs/map_util.h",
- "protobuf/src/google/protobuf/stubs/mutex.h",
- "protobuf/src/google/protobuf/stubs/once.h",
- "protobuf/src/google/protobuf/stubs/platform_macros.h",
- "protobuf/src/google/protobuf/stubs/port.h",
- "protobuf/src/google/protobuf/stubs/status.h",
- "protobuf/src/google/protobuf/stubs/stl_util.h",
- "protobuf/src/google/protobuf/stubs/stringpiece.h",
- "protobuf/src/google/protobuf/stubs/strutil.h",
"protobuf/src/google/protobuf/stubs/substitute.cc",
"protobuf/src/google/protobuf/stubs/substitute.h",
- "protobuf/src/google/protobuf/stubs/template_util.h",
"protobuf/src/google/protobuf/text_format.cc",
- "protobuf/src/google/protobuf/text_format.h",
"protobuf/src/google/protobuf/timestamp.pb.cc",
- "protobuf/src/google/protobuf/timestamp.pb.h",
"protobuf/src/google/protobuf/type.pb.cc",
- "protobuf/src/google/protobuf/type.pb.h",
"protobuf/src/google/protobuf/unknown_field_set.cc",
- "protobuf/src/google/protobuf/unknown_field_set.h",
"protobuf/src/google/protobuf/util/delimited_message_util.cc",
- "protobuf/src/google/protobuf/util/delimited_message_util.h",
"protobuf/src/google/protobuf/util/field_comparator.cc",
- "protobuf/src/google/protobuf/util/field_comparator.h",
"protobuf/src/google/protobuf/util/field_mask_util.cc",
- "protobuf/src/google/protobuf/util/field_mask_util.h",
"protobuf/src/google/protobuf/util/internal/constants.h",
"protobuf/src/google/protobuf/util/internal/datapiece.cc",
"protobuf/src/google/protobuf/util/internal/datapiece.h",
@@ -470,20 +391,13 @@
"protobuf/src/google/protobuf/util/internal/utility.cc",
"protobuf/src/google/protobuf/util/internal/utility.h",
"protobuf/src/google/protobuf/util/json_util.cc",
- "protobuf/src/google/protobuf/util/json_util.h",
"protobuf/src/google/protobuf/util/message_differencer.cc",
- "protobuf/src/google/protobuf/util/message_differencer.h",
"protobuf/src/google/protobuf/util/time_util.cc",
- "protobuf/src/google/protobuf/util/time_util.h",
- "protobuf/src/google/protobuf/util/type_resolver.h",
"protobuf/src/google/protobuf/util/type_resolver_util.cc",
- "protobuf/src/google/protobuf/util/type_resolver_util.h",
"protobuf/src/google/protobuf/wire_format.cc",
- "protobuf/src/google/protobuf/wire_format.h",
- "protobuf/src/google/protobuf/wire_format_lite.h",
"protobuf/src/google/protobuf/wrappers.pb.cc",
- "protobuf/src/google/protobuf/wrappers.pb.h",
]
+ sources += _protobuf_headers
configs -= [ "//gn/standalone:extra_warnings" ]
if (is_win) {
# Protobuf has its own #define WIN32_LEAN_AND_MEAN.
@@ -507,39 +421,40 @@
"protobuf/src/google/protobuf/compiler/code_generator.h",
"protobuf/src/google/protobuf/compiler/command_line_interface.cc",
"protobuf/src/google/protobuf/compiler/command_line_interface.h",
- "protobuf/src/google/protobuf/compiler/cpp/cpp_enum.cc",
- "protobuf/src/google/protobuf/compiler/cpp/cpp_enum.h",
- "protobuf/src/google/protobuf/compiler/cpp/cpp_enum_field.cc",
- "protobuf/src/google/protobuf/compiler/cpp/cpp_enum_field.h",
- "protobuf/src/google/protobuf/compiler/cpp/cpp_extension.cc",
- "protobuf/src/google/protobuf/compiler/cpp/cpp_extension.h",
- "protobuf/src/google/protobuf/compiler/cpp/cpp_field.cc",
- "protobuf/src/google/protobuf/compiler/cpp/cpp_field.h",
- "protobuf/src/google/protobuf/compiler/cpp/cpp_file.cc",
- "protobuf/src/google/protobuf/compiler/cpp/cpp_file.h",
- "protobuf/src/google/protobuf/compiler/cpp/cpp_generator.cc",
"protobuf/src/google/protobuf/compiler/cpp/cpp_generator.h",
- "protobuf/src/google/protobuf/compiler/cpp/cpp_helpers.cc",
- "protobuf/src/google/protobuf/compiler/cpp/cpp_helpers.h",
- "protobuf/src/google/protobuf/compiler/cpp/cpp_map_field.cc",
- "protobuf/src/google/protobuf/compiler/cpp/cpp_map_field.h",
- "protobuf/src/google/protobuf/compiler/cpp/cpp_message.cc",
- "protobuf/src/google/protobuf/compiler/cpp/cpp_message.h",
- "protobuf/src/google/protobuf/compiler/cpp/cpp_message_field.cc",
- "protobuf/src/google/protobuf/compiler/cpp/cpp_message_field.h",
- "protobuf/src/google/protobuf/compiler/cpp/cpp_message_layout_helper.h",
- "protobuf/src/google/protobuf/compiler/cpp/cpp_names.h",
- "protobuf/src/google/protobuf/compiler/cpp/cpp_options.h",
- "protobuf/src/google/protobuf/compiler/cpp/cpp_padding_optimizer.cc",
- "protobuf/src/google/protobuf/compiler/cpp/cpp_padding_optimizer.h",
- "protobuf/src/google/protobuf/compiler/cpp/cpp_parse_function_generator.cc",
- "protobuf/src/google/protobuf/compiler/cpp/cpp_parse_function_generator.h",
- "protobuf/src/google/protobuf/compiler/cpp/cpp_primitive_field.cc",
- "protobuf/src/google/protobuf/compiler/cpp/cpp_primitive_field.h",
- "protobuf/src/google/protobuf/compiler/cpp/cpp_service.cc",
- "protobuf/src/google/protobuf/compiler/cpp/cpp_service.h",
- "protobuf/src/google/protobuf/compiler/cpp/cpp_string_field.cc",
- "protobuf/src/google/protobuf/compiler/cpp/cpp_string_field.h",
+ "protobuf/src/google/protobuf/compiler/cpp/enum.cc",
+ "protobuf/src/google/protobuf/compiler/cpp/enum.h",
+ "protobuf/src/google/protobuf/compiler/cpp/enum_field.cc",
+ "protobuf/src/google/protobuf/compiler/cpp/enum_field.h",
+ "protobuf/src/google/protobuf/compiler/cpp/extension.cc",
+ "protobuf/src/google/protobuf/compiler/cpp/extension.h",
+ "protobuf/src/google/protobuf/compiler/cpp/field.cc",
+ "protobuf/src/google/protobuf/compiler/cpp/field.h",
+ "protobuf/src/google/protobuf/compiler/cpp/file.cc",
+ "protobuf/src/google/protobuf/compiler/cpp/file.h",
+ "protobuf/src/google/protobuf/compiler/cpp/generator.cc",
+ "protobuf/src/google/protobuf/compiler/cpp/generator.h",
+ "protobuf/src/google/protobuf/compiler/cpp/helpers.cc",
+ "protobuf/src/google/protobuf/compiler/cpp/helpers.h",
+ "protobuf/src/google/protobuf/compiler/cpp/map_field.cc",
+ "protobuf/src/google/protobuf/compiler/cpp/map_field.h",
+ "protobuf/src/google/protobuf/compiler/cpp/message.cc",
+ "protobuf/src/google/protobuf/compiler/cpp/message.h",
+ "protobuf/src/google/protobuf/compiler/cpp/message_field.cc",
+ "protobuf/src/google/protobuf/compiler/cpp/message_field.h",
+ "protobuf/src/google/protobuf/compiler/cpp/message_layout_helper.h",
+ "protobuf/src/google/protobuf/compiler/cpp/names.h",
+ "protobuf/src/google/protobuf/compiler/cpp/options.h",
+ "protobuf/src/google/protobuf/compiler/cpp/padding_optimizer.cc",
+ "protobuf/src/google/protobuf/compiler/cpp/padding_optimizer.h",
+ "protobuf/src/google/protobuf/compiler/cpp/parse_function_generator.cc",
+ "protobuf/src/google/protobuf/compiler/cpp/parse_function_generator.h",
+ "protobuf/src/google/protobuf/compiler/cpp/primitive_field.cc",
+ "protobuf/src/google/protobuf/compiler/cpp/primitive_field.h",
+ "protobuf/src/google/protobuf/compiler/cpp/service.cc",
+ "protobuf/src/google/protobuf/compiler/cpp/service.h",
+ "protobuf/src/google/protobuf/compiler/cpp/string_field.cc",
+ "protobuf/src/google/protobuf/compiler/cpp/string_field.h",
"protobuf/src/google/protobuf/compiler/csharp/csharp_doc_comment.cc",
"protobuf/src/google/protobuf/compiler/csharp/csharp_doc_comment.h",
"protobuf/src/google/protobuf/compiler/csharp/csharp_enum.cc",
@@ -574,70 +489,67 @@
"protobuf/src/google/protobuf/compiler/csharp/csharp_source_generator_base.h",
"protobuf/src/google/protobuf/compiler/csharp/csharp_wrapper_field.cc",
"protobuf/src/google/protobuf/compiler/csharp/csharp_wrapper_field.h",
- "protobuf/src/google/protobuf/compiler/java/java_context.cc",
- "protobuf/src/google/protobuf/compiler/java/java_context.h",
- "protobuf/src/google/protobuf/compiler/java/java_doc_comment.cc",
- "protobuf/src/google/protobuf/compiler/java/java_doc_comment.h",
- "protobuf/src/google/protobuf/compiler/java/java_enum.cc",
- "protobuf/src/google/protobuf/compiler/java/java_enum.h",
- "protobuf/src/google/protobuf/compiler/java/java_enum_field.cc",
- "protobuf/src/google/protobuf/compiler/java/java_enum_field.h",
- "protobuf/src/google/protobuf/compiler/java/java_enum_field_lite.cc",
- "protobuf/src/google/protobuf/compiler/java/java_enum_field_lite.h",
- "protobuf/src/google/protobuf/compiler/java/java_enum_lite.cc",
- "protobuf/src/google/protobuf/compiler/java/java_enum_lite.h",
- "protobuf/src/google/protobuf/compiler/java/java_extension.cc",
- "protobuf/src/google/protobuf/compiler/java/java_extension.h",
- "protobuf/src/google/protobuf/compiler/java/java_extension_lite.cc",
- "protobuf/src/google/protobuf/compiler/java/java_extension_lite.h",
- "protobuf/src/google/protobuf/compiler/java/java_field.cc",
- "protobuf/src/google/protobuf/compiler/java/java_field.h",
- "protobuf/src/google/protobuf/compiler/java/java_file.cc",
- "protobuf/src/google/protobuf/compiler/java/java_file.h",
- "protobuf/src/google/protobuf/compiler/java/java_generator.cc",
+ "protobuf/src/google/protobuf/compiler/java/context.cc",
+ "protobuf/src/google/protobuf/compiler/java/context.h",
+ "protobuf/src/google/protobuf/compiler/java/doc_comment.cc",
+ "protobuf/src/google/protobuf/compiler/java/doc_comment.h",
+ "protobuf/src/google/protobuf/compiler/java/enum.cc",
+ "protobuf/src/google/protobuf/compiler/java/enum.h",
+ "protobuf/src/google/protobuf/compiler/java/enum_field.cc",
+ "protobuf/src/google/protobuf/compiler/java/enum_field.h",
+ "protobuf/src/google/protobuf/compiler/java/enum_field_lite.cc",
+ "protobuf/src/google/protobuf/compiler/java/enum_field_lite.h",
+ "protobuf/src/google/protobuf/compiler/java/enum_lite.cc",
+ "protobuf/src/google/protobuf/compiler/java/enum_lite.h",
+ "protobuf/src/google/protobuf/compiler/java/extension.cc",
+ "protobuf/src/google/protobuf/compiler/java/extension.h",
+ "protobuf/src/google/protobuf/compiler/java/extension_lite.cc",
+ "protobuf/src/google/protobuf/compiler/java/extension_lite.h",
+ "protobuf/src/google/protobuf/compiler/java/field.cc",
+ "protobuf/src/google/protobuf/compiler/java/field.h",
+ "protobuf/src/google/protobuf/compiler/java/file.cc",
+ "protobuf/src/google/protobuf/compiler/java/file.h",
+ "protobuf/src/google/protobuf/compiler/java/generator.cc",
+ "protobuf/src/google/protobuf/compiler/java/generator.h",
+ "protobuf/src/google/protobuf/compiler/java/generator_factory.cc",
+ "protobuf/src/google/protobuf/compiler/java/generator_factory.h",
+ "protobuf/src/google/protobuf/compiler/java/helpers.cc",
+ "protobuf/src/google/protobuf/compiler/java/helpers.h",
"protobuf/src/google/protobuf/compiler/java/java_generator.h",
- "protobuf/src/google/protobuf/compiler/java/java_generator_factory.cc",
- "protobuf/src/google/protobuf/compiler/java/java_generator_factory.h",
- "protobuf/src/google/protobuf/compiler/java/java_helpers.cc",
- "protobuf/src/google/protobuf/compiler/java/java_helpers.h",
- "protobuf/src/google/protobuf/compiler/java/java_kotlin_generator.cc",
- "protobuf/src/google/protobuf/compiler/java/java_kotlin_generator.h",
- "protobuf/src/google/protobuf/compiler/java/java_map_field.cc",
- "protobuf/src/google/protobuf/compiler/java/java_map_field.h",
- "protobuf/src/google/protobuf/compiler/java/java_map_field_lite.cc",
- "protobuf/src/google/protobuf/compiler/java/java_map_field_lite.h",
- "protobuf/src/google/protobuf/compiler/java/java_message.cc",
- "protobuf/src/google/protobuf/compiler/java/java_message.h",
- "protobuf/src/google/protobuf/compiler/java/java_message_builder.cc",
- "protobuf/src/google/protobuf/compiler/java/java_message_builder.h",
- "protobuf/src/google/protobuf/compiler/java/java_message_builder_lite.cc",
- "protobuf/src/google/protobuf/compiler/java/java_message_builder_lite.h",
- "protobuf/src/google/protobuf/compiler/java/java_message_field.cc",
- "protobuf/src/google/protobuf/compiler/java/java_message_field.h",
- "protobuf/src/google/protobuf/compiler/java/java_message_field_lite.cc",
- "protobuf/src/google/protobuf/compiler/java/java_message_field_lite.h",
- "protobuf/src/google/protobuf/compiler/java/java_message_lite.cc",
- "protobuf/src/google/protobuf/compiler/java/java_message_lite.h",
- "protobuf/src/google/protobuf/compiler/java/java_name_resolver.cc",
- "protobuf/src/google/protobuf/compiler/java/java_name_resolver.h",
- "protobuf/src/google/protobuf/compiler/java/java_names.h",
- "protobuf/src/google/protobuf/compiler/java/java_options.h",
- "protobuf/src/google/protobuf/compiler/java/java_primitive_field.cc",
- "protobuf/src/google/protobuf/compiler/java/java_primitive_field.h",
- "protobuf/src/google/protobuf/compiler/java/java_primitive_field_lite.cc",
- "protobuf/src/google/protobuf/compiler/java/java_primitive_field_lite.h",
- "protobuf/src/google/protobuf/compiler/java/java_service.cc",
- "protobuf/src/google/protobuf/compiler/java/java_service.h",
- "protobuf/src/google/protobuf/compiler/java/java_shared_code_generator.cc",
- "protobuf/src/google/protobuf/compiler/java/java_shared_code_generator.h",
- "protobuf/src/google/protobuf/compiler/java/java_string_field.cc",
- "protobuf/src/google/protobuf/compiler/java/java_string_field.h",
- "protobuf/src/google/protobuf/compiler/java/java_string_field_lite.cc",
- "protobuf/src/google/protobuf/compiler/java/java_string_field_lite.h",
- "protobuf/src/google/protobuf/compiler/js/js_generator.cc",
- "protobuf/src/google/protobuf/compiler/js/js_generator.h",
- "protobuf/src/google/protobuf/compiler/js/well_known_types_embed.cc",
- "protobuf/src/google/protobuf/compiler/js/well_known_types_embed.h",
+ "protobuf/src/google/protobuf/compiler/java/kotlin_generator.cc",
+ "protobuf/src/google/protobuf/compiler/java/kotlin_generator.h",
+ "protobuf/src/google/protobuf/compiler/java/map_field.cc",
+ "protobuf/src/google/protobuf/compiler/java/map_field.h",
+ "protobuf/src/google/protobuf/compiler/java/map_field_lite.cc",
+ "protobuf/src/google/protobuf/compiler/java/map_field_lite.h",
+ "protobuf/src/google/protobuf/compiler/java/message.cc",
+ "protobuf/src/google/protobuf/compiler/java/message.h",
+ "protobuf/src/google/protobuf/compiler/java/message_builder.cc",
+ "protobuf/src/google/protobuf/compiler/java/message_builder.h",
+ "protobuf/src/google/protobuf/compiler/java/message_builder_lite.cc",
+ "protobuf/src/google/protobuf/compiler/java/message_builder_lite.h",
+ "protobuf/src/google/protobuf/compiler/java/message_field.cc",
+ "protobuf/src/google/protobuf/compiler/java/message_field.h",
+ "protobuf/src/google/protobuf/compiler/java/message_field_lite.cc",
+ "protobuf/src/google/protobuf/compiler/java/message_field_lite.h",
+ "protobuf/src/google/protobuf/compiler/java/message_lite.cc",
+ "protobuf/src/google/protobuf/compiler/java/message_lite.h",
+ "protobuf/src/google/protobuf/compiler/java/name_resolver.cc",
+ "protobuf/src/google/protobuf/compiler/java/name_resolver.h",
+ "protobuf/src/google/protobuf/compiler/java/names.h",
+ "protobuf/src/google/protobuf/compiler/java/options.h",
+ "protobuf/src/google/protobuf/compiler/java/primitive_field.cc",
+ "protobuf/src/google/protobuf/compiler/java/primitive_field.h",
+ "protobuf/src/google/protobuf/compiler/java/primitive_field_lite.cc",
+ "protobuf/src/google/protobuf/compiler/java/primitive_field_lite.h",
+ "protobuf/src/google/protobuf/compiler/java/service.cc",
+ "protobuf/src/google/protobuf/compiler/java/service.h",
+ "protobuf/src/google/protobuf/compiler/java/shared_code_generator.cc",
+ "protobuf/src/google/protobuf/compiler/java/shared_code_generator.h",
+ "protobuf/src/google/protobuf/compiler/java/string_field.cc",
+ "protobuf/src/google/protobuf/compiler/java/string_field.h",
+ "protobuf/src/google/protobuf/compiler/java/string_field_lite.cc",
+ "protobuf/src/google/protobuf/compiler/java/string_field_lite.h",
"protobuf/src/google/protobuf/compiler/objectivec/objectivec_enum.cc",
"protobuf/src/google/protobuf/compiler/objectivec/objectivec_enum.h",
"protobuf/src/google/protobuf/compiler/objectivec/objectivec_enum_field.cc",
@@ -669,12 +581,13 @@
"protobuf/src/google/protobuf/compiler/plugin.h",
"protobuf/src/google/protobuf/compiler/plugin.pb.cc",
"protobuf/src/google/protobuf/compiler/plugin.pb.h",
- "protobuf/src/google/protobuf/compiler/python/python_generator.cc",
+ "protobuf/src/google/protobuf/compiler/python/generator.cc",
+ "protobuf/src/google/protobuf/compiler/python/generator.h",
+ "protobuf/src/google/protobuf/compiler/python/helpers.cc",
+ "protobuf/src/google/protobuf/compiler/python/helpers.h",
+ "protobuf/src/google/protobuf/compiler/python/pyi_generator.cc",
+ "protobuf/src/google/protobuf/compiler/python/pyi_generator.h",
"protobuf/src/google/protobuf/compiler/python/python_generator.h",
- "protobuf/src/google/protobuf/compiler/python/python_helpers.cc",
- "protobuf/src/google/protobuf/compiler/python/python_helpers.h",
- "protobuf/src/google/protobuf/compiler/python/python_pyi_generator.cc",
- "protobuf/src/google/protobuf/compiler/python/python_pyi_generator.h",
"protobuf/src/google/protobuf/compiler/ruby/ruby_generator.cc",
"protobuf/src/google/protobuf/compiler/ruby/ruby_generator.h",
"protobuf/src/google/protobuf/compiler/scc.h",
diff --git a/docs/case-studies/memory.md b/docs/case-studies/memory.md
index 93f67a2..a9508be 100644
--- a/docs/case-studies/memory.md
+++ b/docs/case-studies/memory.md
@@ -407,15 +407,17 @@
![Profile Diamond](/docs/images/profile-diamond.png)
-This will present a flamegraph of the memory attributed to the shortest path
-to a garbage-collection root. In general an object is reachable by many paths,
-we only show the shortest as that reduces the complexity of the data displayed
-and is generally the highest-signal. The rightmost `[merged]` stacks is the
-sum of all objects that are too small to be displayed.
+This will present a set of flamegraph views as explained below.
-![Java Flamegraph](/docs/images/java-heap-graph.png)
+#### "Size" and "Objects" tabs
-The tabs that are available are
+![Java Flamegraph: Size](/docs/images/java-heap-graph.png)
+
+These views show the memory attributed to the shortest path to a
+garbage-collection root. In general an object is reachable by many paths, we
+only show the shortest as that reduces the complexity of the data displayed and
+is generally the highest-signal. The rightmost `[merged]` stacks is the sum of
+all objects that are too small to be displayed.
* **Size**: how many bytes are retained via this path to the GC root.
* **Objects**: how many objects are retained via this path to the GC root.
@@ -432,4 +434,26 @@
We aggregate the paths per class name, so if there are multiple objects of the
same type retained by a `java.lang.Object[]`, we will show one element as its
-child, as you can see in the leftmost stack above.
+child, as you can see in the leftmost stack above. This also applies to the
+dominator tree paths as described below.
+
+#### "Dominated Size" and "Dominated Objects" tabs
+
+![Java Flamegraph: Dominated Size](/docs/images/java-heap-graph-dominated-size.png)
+
+Another way to present the heap graph as a flamegraph (a tree) is to show its
+[dominator tree](/docs/analysis/stdlib-docs.autogen#memory-heap_graph_dominator_tree).
+In a heap graph, an object `a` dominates an object `b` if `b` is reachable from
+the root only via paths that go through `a`. The dominators of an object form a
+chain from the root and the object is exclusvely retained by all objects on this
+chain. For all reachable objects in the graph those chains form a tree, i.e. the
+dominator tree.
+
+We aggregate the tree paths per class name, and each element (tree node)
+represents a set of objects that have the same class name and position in the
+dominator tree.
+
+* **Dominated Size**: how many bytes are exclusively retained by the objects in
+a node.
+* **Dominated Objects**: how many objects are exclusively retained by the
+objects in a node.
diff --git a/docs/images/java-heap-graph-dominated-size.png b/docs/images/java-heap-graph-dominated-size.png
new file mode 100644
index 0000000..94b8186
--- /dev/null
+++ b/docs/images/java-heap-graph-dominated-size.png
Binary files differ
diff --git a/docs/images/java-heap-graph-focus.png b/docs/images/java-heap-graph-focus.png
index cd5d232..c619c41 100644
--- a/docs/images/java-heap-graph-focus.png
+++ b/docs/images/java-heap-graph-focus.png
Binary files differ
diff --git a/docs/images/java-heap-graph.png b/docs/images/java-heap-graph.png
index 829c7fe..730437f 100644
--- a/docs/images/java-heap-graph.png
+++ b/docs/images/java-heap-graph.png
Binary files differ
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/include/perfetto/tracing/string_helpers.h b/include/perfetto/tracing/string_helpers.h
index 0d2819a..03fa9e4 100644
--- a/include/perfetto/tracing/string_helpers.h
+++ b/include/perfetto/tracing/string_helpers.h
@@ -38,6 +38,8 @@
constexpr explicit StaticString(const char* str) : value(str) {}
+ operator bool() const { return !!value; }
+
const char* value;
};
@@ -52,6 +54,9 @@
length = strlen(str);
}
DynamicString(const char* str, size_t len) : value(str), length(len) {}
+ constexpr DynamicString() : value(nullptr), length(0) {}
+
+ operator bool() const { return !!value; }
const char* value;
size_t length;
diff --git a/include/perfetto/tracing/track.h b/include/perfetto/tracing/track.h
index 37f02e5..831c290 100644
--- a/include/perfetto/tracing/track.h
+++ b/include/perfetto/tracing/track.h
@@ -25,6 +25,7 @@
#include "perfetto/tracing/internal/fnv1a.h"
#include "perfetto/tracing/internal/tracing_muxer.h"
#include "perfetto/tracing/platform.h"
+#include "perfetto/tracing/string_helpers.h"
#include "protos/perfetto/trace/trace_packet.pbzero.h"
#include "protos/perfetto/trace/track_event/counter_descriptor.gen.h"
#include "protos/perfetto/trace/track_event/counter_descriptor.pbzero.h"
@@ -201,71 +202,92 @@
perfetto::protos::gen::CounterDescriptor::BuiltinCounterType;
// |name| must outlive this object.
- constexpr explicit CounterTrack(const char* name,
+ constexpr explicit CounterTrack(StaticString name,
Track parent = MakeProcessTrack())
- : Track(internal::Fnv1a(name) ^ kCounterMagic, parent),
- name_(name),
- category_(nullptr) {}
+ : CounterTrack(
+ name,
+ perfetto::protos::pbzero::CounterDescriptor::UNIT_UNSPECIFIED,
+ nullptr,
+ parent) {}
+
+ explicit CounterTrack(DynamicString name, Track parent = MakeProcessTrack())
+ : CounterTrack(
+ name,
+ perfetto::protos::pbzero::CounterDescriptor::UNIT_UNSPECIFIED,
+ nullptr,
+ parent) {}
// |unit_name| is a free-form description of the unit used by this counter. It
// must outlive this object.
- constexpr CounterTrack(const char* name,
+ template <class TrackEventName>
+ constexpr CounterTrack(TrackEventName&& name,
const char* unit_name,
Track parent = MakeProcessTrack())
- : Track(internal::Fnv1a(name) ^ kCounterMagic, parent),
- name_(name),
- category_(nullptr),
- unit_name_(unit_name) {}
+ : CounterTrack(
+ std::forward<TrackEventName>(name),
+ perfetto::protos::pbzero::CounterDescriptor::UNIT_UNSPECIFIED,
+ unit_name,
+ parent) {}
- constexpr CounterTrack(const char* name,
+ template <class TrackEventName>
+ constexpr CounterTrack(TrackEventName&& name,
Unit unit,
Track parent = MakeProcessTrack())
- : Track(internal::Fnv1a(name) ^ kCounterMagic, parent),
- name_(name),
- category_(nullptr),
- unit_(unit) {}
+ : CounterTrack(std::forward<TrackEventName>(name),
+ unit,
+ nullptr,
+ parent) {}
- static constexpr CounterTrack Global(const char* name,
+ template <class TrackEventName>
+ static constexpr CounterTrack Global(TrackEventName&& name,
const char* unit_name) {
- return CounterTrack(name, unit_name, Track());
+ return CounterTrack(std::forward<TrackEventName>(name), unit_name, Track());
}
- static constexpr CounterTrack Global(const char* name, Unit unit) {
- return CounterTrack(name, unit, Track());
+ template <class TrackEventName>
+ static constexpr CounterTrack Global(TrackEventName&& name, Unit unit) {
+ return CounterTrack(std::forward<TrackEventName>(name), unit, Track());
}
- static constexpr CounterTrack Global(const char* name) {
- return Global(name, nullptr);
+ template <class TrackEventName>
+ static constexpr CounterTrack Global(TrackEventName&& name) {
+ return Global(std::forward<TrackEventName>(name), nullptr);
}
constexpr CounterTrack set_unit(Unit unit) const {
- return CounterTrack(uuid, parent_uuid, name_, category_, unit, unit_name_,
- unit_multiplier_, is_incremental_, type_);
+ return CounterTrack(uuid, parent_uuid, static_name_, dynamic_name_,
+ category_, unit, unit_name_, unit_multiplier_,
+ is_incremental_, type_);
}
constexpr CounterTrack set_type(CounterType type) const {
- return CounterTrack(uuid, parent_uuid, name_, category_, unit_, unit_name_,
- unit_multiplier_, is_incremental_, type);
+ return CounterTrack(uuid, parent_uuid, static_name_, dynamic_name_,
+ category_, unit_, unit_name_, unit_multiplier_,
+ is_incremental_, type);
}
constexpr CounterTrack set_unit_name(const char* unit_name) const {
- return CounterTrack(uuid, parent_uuid, name_, category_, unit_, unit_name,
- unit_multiplier_, is_incremental_, type_);
+ return CounterTrack(uuid, parent_uuid, static_name_, dynamic_name_,
+ category_, unit_, unit_name, unit_multiplier_,
+ is_incremental_, type_);
}
constexpr CounterTrack set_unit_multiplier(int64_t unit_multiplier) const {
- return CounterTrack(uuid, parent_uuid, name_, category_, unit_, unit_name_,
- unit_multiplier, is_incremental_, type_);
+ return CounterTrack(uuid, parent_uuid, static_name_, dynamic_name_,
+ category_, unit_, unit_name_, unit_multiplier,
+ is_incremental_, type_);
}
constexpr CounterTrack set_category(const char* category) const {
- return CounterTrack(uuid, parent_uuid, name_, category, unit_, unit_name_,
- unit_multiplier_, is_incremental_, type_);
+ return CounterTrack(uuid, parent_uuid, static_name_, dynamic_name_,
+ category, unit_, unit_name_, unit_multiplier_,
+ is_incremental_, type_);
}
constexpr CounterTrack set_is_incremental(bool is_incremental = true) const {
- return CounterTrack(uuid, parent_uuid, name_, category_, unit_, unit_name_,
- unit_multiplier_, is_incremental, type_);
+ return CounterTrack(uuid, parent_uuid, static_name_, dynamic_name_,
+ category_, unit_, unit_name_, unit_multiplier_,
+ is_incremental, type_);
}
constexpr bool is_incremental() const { return is_incremental_; }
@@ -274,9 +296,29 @@
protos::gen::TrackDescriptor Serialize() const;
private:
+ constexpr CounterTrack(StaticString name,
+ Unit unit,
+ const char* unit_name,
+ Track parent)
+ : Track(internal::Fnv1a(name.value) ^ kCounterMagic, parent),
+ static_name_(name),
+ category_(nullptr),
+ unit_(unit),
+ unit_name_(unit_name) {}
+ CounterTrack(DynamicString name,
+ Unit unit,
+ const char* unit_name,
+ Track parent)
+ : Track(internal::Fnv1a(name.value, name.length) ^ kCounterMagic, parent),
+ static_name_(nullptr),
+ dynamic_name_(name),
+ category_(nullptr),
+ unit_(unit),
+ unit_name_(unit_name) {}
constexpr CounterTrack(uint64_t uuid_,
uint64_t parent_uuid_,
- const char* name,
+ StaticString static_name,
+ DynamicString dynamic_name,
const char* category,
Unit unit,
const char* unit_name,
@@ -284,7 +326,8 @@
bool is_incremental,
CounterType type)
: Track(uuid_, parent_uuid_),
- name_(name),
+ static_name_(static_name),
+ dynamic_name_(dynamic_name),
category_(category),
unit_(unit),
unit_name_(unit_name),
@@ -292,7 +335,8 @@
is_incremental_(is_incremental),
type_(type) {}
- const char* const name_;
+ StaticString static_name_;
+ DynamicString dynamic_name_;
const char* const category_;
Unit unit_ = perfetto::protos::pbzero::CounterDescriptor::UNIT_UNSPECIFIED;
const char* const unit_name_ = nullptr;
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index 960f8c3..20612e4 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -13895,6 +13895,7 @@
optional uint64 smr_pss_anon_kb = 18;
optional uint64 smr_pss_file_kb = 19;
optional uint64 smr_pss_shmem_kb = 20;
+ optional uint64 smr_swap_pss_kb = 23;
// Time spent scheduled in user mode in nanoseconds. Parsed from utime in
// /proc/pid/stat. Recorded if record_process_runtime config option is set.
@@ -14675,7 +14676,7 @@
// |TrackEvent::track_uuid|. It is possible but not necessary to emit a
// TrackDescriptor for this implicit track.
//
-// Next id: 10.
+// Next id: 11.
message TrackDescriptor {
// Unique ID that identifies this track. This ID is global to the whole trace.
// Producers should ensure that it is unlikely to clash with IDs emitted by
@@ -14695,7 +14696,12 @@
// Name of the track. Optional - if unspecified, it may be derived from the
// process/thread name (process/thread tracks), the first event's name (async
// tracks), or counter name (counter tracks).
- optional string name = 2;
+ oneof static_or_dynamic_name {
+ string name = 2;
+ // This field is only set by the SDK when perfetto::StaticString is
+ // provided.
+ string static_name = 10;
+ }
// Associate the track with a process, making it the process-global track.
// There should only be one such track per process (usually for instant
@@ -14743,6 +14749,7 @@
ChromeUserEventTranslationTable chrome_user_event = 2;
ChromePerformanceMarkTranslationTable chrome_performance_mark = 3;
SliceNameTranslationTable slice_name = 4;
+ ProcessTrackNameTranslationTable process_track_name = 5;
}
}
@@ -14767,6 +14774,11 @@
map<string, string> raw_to_deobfuscated_name = 1;
};
+// Raw -> deobfuscated process track name translation rules.
+message ProcessTrackNameTranslationTable {
+ map<string, string> raw_to_deobfuscated_name = 1;
+};
+
// End of protos/perfetto/trace/translation/translation_table.proto
// Begin of protos/perfetto/trace/trigger.proto
diff --git a/protos/perfetto/trace/ps/process_stats.proto b/protos/perfetto/trace/ps/process_stats.proto
index 5175625..f60cb68 100644
--- a/protos/perfetto/trace/ps/process_stats.proto
+++ b/protos/perfetto/trace/ps/process_stats.proto
@@ -83,6 +83,7 @@
optional uint64 smr_pss_anon_kb = 18;
optional uint64 smr_pss_file_kb = 19;
optional uint64 smr_pss_shmem_kb = 20;
+ optional uint64 smr_swap_pss_kb = 23;
// Time spent scheduled in user mode in nanoseconds. Parsed from utime in
// /proc/pid/stat. Recorded if record_process_runtime config option is set.
diff --git a/protos/perfetto/trace/track_event/track_descriptor.proto b/protos/perfetto/trace/track_event/track_descriptor.proto
index d6db233..76890f2 100644
--- a/protos/perfetto/trace/track_event/track_descriptor.proto
+++ b/protos/perfetto/trace/track_event/track_descriptor.proto
@@ -37,7 +37,7 @@
// |TrackEvent::track_uuid|. It is possible but not necessary to emit a
// TrackDescriptor for this implicit track.
//
-// Next id: 10.
+// Next id: 11.
message TrackDescriptor {
// Unique ID that identifies this track. This ID is global to the whole trace.
// Producers should ensure that it is unlikely to clash with IDs emitted by
@@ -57,7 +57,12 @@
// Name of the track. Optional - if unspecified, it may be derived from the
// process/thread name (process/thread tracks), the first event's name (async
// tracks), or counter name (counter tracks).
- optional string name = 2;
+ oneof static_or_dynamic_name {
+ string name = 2;
+ // This field is only set by the SDK when perfetto::StaticString is
+ // provided.
+ string static_name = 10;
+ }
// Associate the track with a process, making it the process-global track.
// There should only be one such track per process (usually for instant
diff --git a/protos/perfetto/trace/translation/translation_table.proto b/protos/perfetto/trace/translation/translation_table.proto
index 06cef31..b934cf7 100644
--- a/protos/perfetto/trace/translation/translation_table.proto
+++ b/protos/perfetto/trace/translation/translation_table.proto
@@ -26,6 +26,7 @@
ChromeUserEventTranslationTable chrome_user_event = 2;
ChromePerformanceMarkTranslationTable chrome_performance_mark = 3;
SliceNameTranslationTable slice_name = 4;
+ ProcessTrackNameTranslationTable process_track_name = 5;
}
}
@@ -49,3 +50,8 @@
message SliceNameTranslationTable {
map<string, string> raw_to_deobfuscated_name = 1;
};
+
+// Raw -> deobfuscated process track name translation rules.
+message ProcessTrackNameTranslationTable {
+ map<string, string> raw_to_deobfuscated_name = 1;
+};
diff --git a/src/trace_processor/BUILD.gn b/src/trace_processor/BUILD.gn
index 84c09ce..b499f0b 100644
--- a/src/trace_processor/BUILD.gn
+++ b/src/trace_processor/BUILD.gn
@@ -109,6 +109,8 @@
"trace_processor_storage.cc",
"trace_processor_storage_impl.cc",
"trace_processor_storage_impl.h",
+ "trace_reader_registry.cc",
+ "trace_reader_registry.h",
"virtual_destructors.cc",
]
deps = [
@@ -132,6 +134,7 @@
"util:descriptors",
"util:gzip",
"util:proto_to_args_parser",
+ "util:trace_type",
]
public_deps = [ "../../include/perfetto/trace_processor:storage" ]
}
@@ -190,6 +193,7 @@
"util:protozero_to_text",
"util:regex",
"util:stdlib",
+ "util:trace_type",
]
public_deps = [
"../../gn:sqlite", # iterator_impl.h includes sqlite3.h.
@@ -247,6 +251,7 @@
"../../gn:default_deps",
"../../gn:gtest_and_gmock",
"../../include/perfetto/trace_processor",
+ "util:trace_type",
]
if (enable_perfetto_trace_processor_json && !is_win) {
diff --git a/src/trace_processor/export_json_unittest.cc b/src/trace_processor/export_json_unittest.cc
index a8f7564..e9e8f2a 100644
--- a/src/trace_processor/export_json_unittest.cc
+++ b/src/trace_processor/export_json_unittest.cc
@@ -29,6 +29,7 @@
#include "src/trace_processor/importers/common/args_tracker.h"
#include "src/trace_processor/importers/common/event_tracker.h"
#include "src/trace_processor/importers/common/metadata_tracker.h"
+#include "src/trace_processor/importers/common/process_track_translation_table.h"
#include "src/trace_processor/importers/common/process_tracker.h"
#include "src/trace_processor/importers/common/track_tracker.h"
#include "src/trace_processor/importers/proto/track_event_tracker.h"
@@ -79,6 +80,8 @@
context_.metadata_tracker.reset(
new MetadataTracker(context_.storage.get()));
context_.process_tracker.reset(new ProcessTracker(&context_));
+ context_.process_track_translation_table.reset(
+ new ProcessTrackTranslationTable(context_.storage.get()));
}
std::string ToJson(ArgumentFilterPredicate argument_filter = nullptr,
diff --git a/src/trace_processor/forwarding_trace_parser.cc b/src/trace_processor/forwarding_trace_parser.cc
index 466ee96..369917d 100644
--- a/src/trace_processor/forwarding_trace_parser.cc
+++ b/src/trace_processor/forwarding_trace_parser.cc
@@ -16,29 +16,24 @@
#include "src/trace_processor/forwarding_trace_parser.h"
+#include <memory>
+#include <optional>
+
#include "perfetto/base/logging.h"
-#include "perfetto/ext/base/string_utils.h"
+#include "perfetto/base/status.h"
+#include "perfetto/ext/base/status_or.h"
+#include "src/trace_processor/importers/common/chunked_trace_reader.h"
#include "src/trace_processor/importers/common/process_tracker.h"
#include "src/trace_processor/importers/proto/proto_trace_reader.h"
#include "src/trace_processor/sorter/trace_sorter.h"
+#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"
namespace perfetto {
namespace trace_processor {
namespace {
-const char kNoZlibErr[] =
- "Cannot open compressed trace. zlib not enabled in the build config";
-
-inline bool isspace(unsigned char c) {
- return ::isspace(c);
-}
-
-std::string RemoveWhitespace(std::string str) {
- str.erase(std::remove_if(str.begin(), str.end(), isspace), str.end());
- return str;
-}
-
TraceSorter::SortingMode ConvertSortingMode(SortingMode sorting_mode) {
switch (sorting_mode) {
case SortingMode::kDefaultHeuristics:
@@ -50,10 +45,30 @@
PERFETTO_FATAL("For GCC");
}
-// Fuchsia traces have a magic number as documented here:
-// https://fuchsia.googlesource.com/fuchsia/+/HEAD/docs/development/tracing/trace-format/README.md#magic-number-record-trace-info-type-0
-constexpr uint64_t kFuchsiaMagicNumber = 0x0016547846040010;
-constexpr char kPerfMagic[] = "PERFILE2";
+std::optional<TraceSorter::SortingMode> GetMinimumSortingMode(
+ TraceType trace_type,
+ const TraceProcessorContext& context) {
+ switch (trace_type) {
+ case kNinjaLogTraceType:
+ case kSystraceTraceType:
+ case kGzipTraceType:
+ case kCtraceTraceType:
+ case kAndroidBugreportTraceType:
+ return std::nullopt;
+
+ case kPerfDataTraceType:
+ return TraceSorter::SortingMode::kDefault;
+
+ case kUnknownTraceType:
+ case kJsonTraceType:
+ case kFuchsiaTraceType:
+ return TraceSorter::SortingMode::kFullSort;
+
+ case kProtoTraceType:
+ return ConvertSortingMode(context.config.sorting_mode);
+ }
+ PERFETTO_FATAL("For GCC");
+}
} // namespace
@@ -62,102 +77,63 @@
ForwardingTraceParser::~ForwardingTraceParser() {}
+base::Status ForwardingTraceParser::Init(const TraceBlobView& blob) {
+ PERFETTO_CHECK(!reader_);
+
+ TraceType trace_type;
+ {
+ auto scoped_trace = context_->storage->TraceExecutionTimeIntoStats(
+ stats::guess_trace_type_duration_ns);
+ trace_type = GuessTraceType(blob.data(), blob.size());
+ context_->trace_type = trace_type;
+ }
+
+ if (trace_type == kUnknownTraceType) {
+ // If renaming this error message don't remove the "(ERR:fmt)" part.
+ // The UI's error_dialog.ts uses it to make the dialog more graceful.
+ return base::ErrStatus("Unknown trace type provided (ERR:fmt)");
+ }
+
+ base::StatusOr<std::unique_ptr<ChunkedTraceReader>> reader_or =
+ context_->reader_registry->CreateTraceReader(trace_type);
+ if (!reader_or.ok()) {
+ return reader_or.status();
+ }
+ reader_ = std::move(*reader_or);
+
+ PERFETTO_DLOG("%s detected", ToString(trace_type));
+ std::optional<TraceSorter::SortingMode> minimum_sorting_mode =
+ GetMinimumSortingMode(trace_type, *context_);
+
+ 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()
+ if (trace_type == kProtoTraceType || trace_type == kSystraceTraceType) {
+ context_->process_tracker->SetPidZeroIsUpidZeroIdleProcess();
+ }
+
+ return base::OkStatus();
+}
+
base::Status ForwardingTraceParser::Parse(TraceBlobView blob) {
// If this is the first Parse() call, guess the trace type and create the
// appropriate parser.
if (!reader_) {
- TraceType trace_type;
- {
- auto scoped_trace = context_->storage->TraceExecutionTimeIntoStats(
- stats::guess_trace_type_duration_ns);
- trace_type = GuessTraceType(blob.data(), blob.size());
- context_->trace_type = trace_type;
- }
- switch (trace_type) {
- case kJsonTraceType: {
- PERFETTO_DLOG("JSON trace detected");
- if (context_->json_trace_tokenizer && context_->json_trace_parser) {
- reader_ = std::move(context_->json_trace_tokenizer);
-
- // JSON traces have no guarantees about the order of events in them.
- context_->sorter.reset(
- new TraceSorter(context_, TraceSorter::SortingMode::kFullSort));
- break;
- }
- return base::ErrStatus("JSON support is disabled");
- }
- case kProtoTraceType: {
- PERFETTO_DLOG("Proto trace detected");
- auto sorting_mode = ConvertSortingMode(context_->config.sorting_mode);
- reader_.reset(new ProtoTraceReader(context_));
- context_->sorter.reset(new TraceSorter(context_, sorting_mode));
- context_->process_tracker->SetPidZeroIsUpidZeroIdleProcess();
- break;
- }
- case kNinjaLogTraceType: {
- PERFETTO_DLOG("Ninja log detected");
- if (context_->ninja_log_parser) {
- reader_ = std::move(context_->ninja_log_parser);
- break;
- }
- return base::ErrStatus("Ninja support is disabled");
- }
- case kFuchsiaTraceType: {
- PERFETTO_DLOG("Fuchsia trace detected");
- if (context_->fuchsia_record_parser &&
- context_->fuchsia_trace_tokenizer) {
- reader_ = std::move(context_->fuchsia_trace_tokenizer);
-
- // Fuschia traces can have massively out of order events.
- context_->sorter.reset(
- new TraceSorter(context_, TraceSorter::SortingMode::kFullSort));
- break;
- }
- return base::ErrStatus("Fuchsia support is disabled");
- }
- case kSystraceTraceType:
- PERFETTO_DLOG("Systrace trace detected");
- context_->process_tracker->SetPidZeroIsUpidZeroIdleProcess();
- if (context_->systrace_trace_parser) {
- reader_ = std::move(context_->systrace_trace_parser);
- break;
- }
- return base::ErrStatus("Systrace support is disabled");
- case kGzipTraceType:
- case kCtraceTraceType:
- if (trace_type == kGzipTraceType) {
- PERFETTO_DLOG("gzip trace detected");
- } else {
- PERFETTO_DLOG("ctrace trace detected");
- }
- if (context_->gzip_trace_parser) {
- reader_ = std::move(context_->gzip_trace_parser);
- break;
- }
- return base::ErrStatus(kNoZlibErr);
- case kAndroidBugreportTraceType:
- PERFETTO_DLOG("Android Bugreport detected");
- if (context_->android_bugreport_parser) {
- reader_ = std::move(context_->android_bugreport_parser);
- break;
- }
- return base::ErrStatus("Android Bugreport support is disabled. %s",
- kNoZlibErr);
- case kPerfDataTraceType:
- PERFETTO_DLOG("perf data detected");
- if (context_->perf_data_trace_tokenizer &&
- context_->perf_record_parser) {
- reader_ = std::move(context_->perf_data_trace_tokenizer);
- context_->sorter.reset(
- new TraceSorter(context_, TraceSorter::SortingMode::kDefault));
- break;
- }
- return base::ErrStatus("perf.data parsing support is disabled.");
- case kUnknownTraceType:
- // If renaming this error message don't remove the "(ERR:fmt)" part.
- // The UI's error_dialog.ts uses it to make the dialog more graceful.
- return base::ErrStatus("Unknown trace type provided (ERR:fmt)");
- }
+ RETURN_IF_ERROR(Init(blob));
}
return reader_->Parse(std::move(blob));
@@ -167,71 +143,5 @@
reader_->NotifyEndOfFile();
}
-TraceType GuessTraceType(const uint8_t* data, size_t size) {
- if (size == 0)
- return kUnknownTraceType;
- std::string start(reinterpret_cast<const char*>(data),
- std::min<size_t>(size, kGuessTraceMaxLookahead));
- if (size >= 8) {
- uint64_t first_word;
- memcpy(&first_word, data, sizeof(first_word));
- if (first_word == kFuchsiaMagicNumber)
- return kFuchsiaTraceType;
- }
- if (base::StartsWith(start, kPerfMagic)) {
- return kPerfDataTraceType;
- }
- std::string start_minus_white_space = RemoveWhitespace(start);
- if (base::StartsWith(start_minus_white_space, "{\""))
- return kJsonTraceType;
- if (base::StartsWith(start_minus_white_space, "[{\""))
- return kJsonTraceType;
-
- // Systrace with header but no leading HTML.
- if (base::Contains(start, "# tracer"))
- return kSystraceTraceType;
-
- // Systrace with leading HTML.
- // Both: <!DOCTYPE html> and <!DOCTYPE HTML> have been observed.
- std::string lower_start = base::ToLower(start);
- if (base::StartsWith(lower_start, "<!doctype html>") ||
- base::StartsWith(lower_start, "<html>"))
- return kSystraceTraceType;
-
- // Traces obtained from atrace -z (compress).
- // They all have the string "TRACE:" followed by 78 9C which is a zlib header
- // for "deflate, default compression, window size=32K" (see b/208691037)
- if (base::Contains(start, "TRACE:\n\x78\x9c"))
- return kCtraceTraceType;
-
- // Traces obtained from atrace without -z (no compression).
- if (base::Contains(start, "TRACE:\n"))
- return kSystraceTraceType;
-
- // Ninja's build log (.ninja_log).
- if (base::StartsWith(start, "# ninja log"))
- return kNinjaLogTraceType;
-
- // Systrace with no header or leading HTML.
- if (base::StartsWith(start, " "))
- return kSystraceTraceType;
-
- // gzip'ed trace containing one of the other formats.
- if (base::StartsWith(start, "\x1f\x8b"))
- return kGzipTraceType;
-
- 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 kUnknownTraceType;
-}
-
} // namespace trace_processor
} // namespace perfetto
diff --git a/src/trace_processor/forwarding_trace_parser.h b/src/trace_processor/forwarding_trace_parser.h
index e63a272..1f32938 100644
--- a/src/trace_processor/forwarding_trace_parser.h
+++ b/src/trace_processor/forwarding_trace_parser.h
@@ -17,16 +17,14 @@
#ifndef SRC_TRACE_PROCESSOR_FORWARDING_TRACE_PARSER_H_
#define SRC_TRACE_PROCESSOR_FORWARDING_TRACE_PARSER_H_
+#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/types/trace_processor_context.h"
-
namespace perfetto {
namespace trace_processor {
-constexpr size_t kGuessTraceMaxLookahead = 64;
-
-TraceType GuessTraceType(const uint8_t* data, size_t size);
+class TraceProcessorContext;
class ForwardingTraceParser : public ChunkedTraceReader {
public:
@@ -38,6 +36,7 @@
void NotifyEndOfFile() override;
private:
+ base::Status Init(const TraceBlobView&);
TraceProcessorContext* const context_;
std::unique_ptr<ChunkedTraceReader> reader_;
};
diff --git a/src/trace_processor/forwarding_trace_parser_unittest.cc b/src/trace_processor/forwarding_trace_parser_unittest.cc
index 74cb631..42d577d 100644
--- a/src/trace_processor/forwarding_trace_parser_unittest.cc
+++ b/src/trace_processor/forwarding_trace_parser_unittest.cc
@@ -16,6 +16,7 @@
#include "src/trace_processor/forwarding_trace_parser.h"
+#include "src/trace_processor/util/trace_type.h"
#include "test/gtest_and_gmock.h"
namespace perfetto {
diff --git a/src/trace_processor/importers/common/BUILD.gn b/src/trace_processor/importers/common/BUILD.gn
index 43dfb23..dc3e11a 100644
--- a/src/trace_processor/importers/common/BUILD.gn
+++ b/src/trace_processor/importers/common/BUILD.gn
@@ -45,6 +45,8 @@
"mapping_tracker.h",
"metadata_tracker.cc",
"metadata_tracker.h",
+ "process_track_translation_table.cc",
+ "process_track_translation_table.h",
"process_tracker.cc",
"process_tracker.h",
"sched_event_state.h",
@@ -87,6 +89,7 @@
"../../util:build_id",
"../../util:profiler_util",
"../fuchsia:fuchsia_record",
+ "../perf:record",
"../systrace:systrace_line",
]
}
@@ -116,6 +119,7 @@
"deobfuscation_mapping_table_unittest.cc",
"event_tracker_unittest.cc",
"flow_tracker_unittest.cc",
+ "process_track_translation_table_unittest.cc",
"process_tracker_unittest.cc",
"slice_tracker_unittest.cc",
"slice_translation_table_unittest.cc",
diff --git a/src/trace_processor/importers/common/async_track_set_tracker_unittest.cc b/src/trace_processor/importers/common/async_track_set_tracker_unittest.cc
index b10aec8..173fd89 100644
--- a/src/trace_processor/importers/common/async_track_set_tracker_unittest.cc
+++ b/src/trace_processor/importers/common/async_track_set_tracker_unittest.cc
@@ -18,6 +18,7 @@
#include "src/trace_processor/importers/common/args_tracker.h"
#include "src/trace_processor/importers/common/global_args_tracker.h"
+#include "src/trace_processor/importers/common/process_track_translation_table.h"
#include "src/trace_processor/importers/common/track_tracker.h"
#include "src/trace_processor/types/trace_processor_context.h"
#include "test/gtest_and_gmock.h"
@@ -34,6 +35,8 @@
context_.args_tracker.reset(new ArgsTracker(&context_));
context_.track_tracker.reset(new TrackTracker(&context_));
context_.async_track_set_tracker.reset(new AsyncTrackSetTracker(&context_));
+ context_.process_track_translation_table.reset(
+ new ProcessTrackTranslationTable(context_.storage.get()));
storage_ = context_.storage.get();
tracker_ = context_.async_track_set_tracker.get();
diff --git a/src/trace_processor/importers/common/mapping_tracker.cc b/src/trace_processor/importers/common/mapping_tracker.cc
index 0dec3e5..965c15f 100644
--- a/src/trace_processor/importers/common/mapping_tracker.cc
+++ b/src/trace_processor/importers/common/mapping_tracker.cc
@@ -164,5 +164,15 @@
});
}
+VirtualMemoryMapping* MappingTracker::GetDummyMapping() {
+ if (!dummy_mapping_) {
+ CreateMappingParams params;
+ params.memory_range =
+ AddressRange::FromStartAndSize(0, std::numeric_limits<uint64_t>::max());
+ dummy_mapping_ = &InternMemoryMapping(params);
+ }
+ return dummy_mapping_;
+}
+
} // namespace trace_processor
} // namespace perfetto
diff --git a/src/trace_processor/importers/common/mapping_tracker.h b/src/trace_processor/importers/common/mapping_tracker.h
index bc45bef..08c767e 100644
--- a/src/trace_processor/importers/common/mapping_tracker.h
+++ b/src/trace_processor/importers/common/mapping_tracker.h
@@ -91,6 +91,10 @@
// Jitted ranges will only be applied to UserMemoryMappings
void AddJitRange(UniquePid upid, AddressRange range, JitCache* jit_cache);
+ // Sometimes we just need a mapping and we are lacking trace data to create a
+ // proper one. Use this mapping in those cases.
+ VirtualMemoryMapping* GetDummyMapping();
+
private:
template <typename MappingImpl>
MappingImpl& AddMapping(std::unique_ptr<MappingImpl> mapping);
@@ -136,6 +140,8 @@
KernelMemoryMapping* kernel_ = nullptr;
base::FlatHashMap<UniquePid, AddressRangeMap<JitCache*>> jit_caches_;
+
+ VirtualMemoryMapping* dummy_mapping_;
};
} // namespace trace_processor
diff --git a/src/trace_processor/importers/common/process_track_translation_table.cc b/src/trace_processor/importers/common/process_track_translation_table.cc
new file mode 100644
index 0000000..cee50c6
--- /dev/null
+++ b/src/trace_processor/importers/common/process_track_translation_table.cc
@@ -0,0 +1,24 @@
+/*
+ * 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/common/process_track_translation_table.h"
+
+namespace perfetto::trace_processor {
+
+ProcessTrackTranslationTable::ProcessTrackTranslationTable(TraceStorage* storage)
+ : storage_(storage) {}
+
+} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/importers/common/process_track_translation_table.h b/src/trace_processor/importers/common/process_track_translation_table.h
new file mode 100644
index 0000000..30f5f56
--- /dev/null
+++ b/src/trace_processor/importers/common/process_track_translation_table.h
@@ -0,0 +1,55 @@
+/*
+ * 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_COMMON_PROCESS_TRACK_TRANSLATION_TABLE_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_PROCESS_TRACK_TRANSLATION_TABLE_H_
+
+#include <cstdint>
+
+#include "perfetto/ext/base/flat_hash_map.h"
+#include "perfetto/ext/base/string_view.h"
+#include "src/trace_processor/storage/trace_storage.h"
+
+namespace perfetto::trace_processor {
+
+// Tracks and stores slice translation rules. It allows Trace Processor
+// to for example deobfuscate slice names.
+class ProcessTrackTranslationTable {
+ public:
+ ProcessTrackTranslationTable(TraceStorage* storage);
+
+ // If the name is not mapped to anything, assumes that no translation is
+ // necessry, and returns the raw_name.
+ StringId TranslateName(StringId raw_name) const {
+ const auto* mapped_name = raw_to_deobfuscated_name_.Find(raw_name);
+ return mapped_name ? *mapped_name : raw_name;
+ }
+
+ void AddNameTranslationRule(base::StringView raw,
+ base::StringView deobfuscated) {
+ const StringId raw_id = storage_->InternString(raw);
+ const StringId deobfuscated_id = storage_->InternString(deobfuscated);
+ raw_to_deobfuscated_name_[raw_id] = deobfuscated_id;
+ }
+
+ private:
+ TraceStorage* storage_;
+ base::FlatHashMap<StringId, StringId> raw_to_deobfuscated_name_;
+};
+
+} // namespace perfetto::trace_processor
+
+#endif // SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_PROCESS_TRACK_TRANSLATION_TABLE_H_
diff --git a/src/trace_processor/importers/common/process_track_translation_table_unittest.cc b/src/trace_processor/importers/common/process_track_translation_table_unittest.cc
new file mode 100644
index 0000000..a947204
--- /dev/null
+++ b/src/trace_processor/importers/common/process_track_translation_table_unittest.cc
@@ -0,0 +1,40 @@
+/*
+ * 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/common/process_track_translation_table.h"
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto::trace_processor {
+namespace {
+
+TEST(ProcessTrackTranslationTable, UnknownName) {
+ TraceStorage storage;
+ ProcessTrackTranslationTable table(&storage);
+ const StringId raw_name = storage.InternString("name1");
+ EXPECT_EQ(raw_name, table.TranslateName(raw_name));
+}
+
+TEST(ProcessTrackTranslationTable, MappedName) {
+ TraceStorage storage;
+ ProcessTrackTranslationTable table(&storage);
+ table.AddNameTranslationRule("raw_name1", "mapped_name1");
+ const StringId raw_name = storage.InternString("raw_name1");
+ const StringId mapped_name = storage.InternString("mapped_name1");
+ EXPECT_EQ(mapped_name, table.TranslateName(raw_name));
+}
+
+} // namespace
+} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/importers/common/trace_parser.h b/src/trace_processor/importers/common/trace_parser.h
index bf90c95..8e41a7d 100644
--- a/src/trace_processor/importers/common/trace_parser.h
+++ b/src/trace_processor/importers/common/trace_parser.h
@@ -20,8 +20,10 @@
#include <stdint.h>
#include <string>
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
+namespace perf_importer {
+struct Record;
+}
class PacketSequenceStateGeneration;
class TraceBlobView;
@@ -59,10 +61,9 @@
class PerfRecordParser {
public:
virtual ~PerfRecordParser();
- virtual void ParsePerfRecord(int64_t, TraceBlobView) = 0;
+ virtual void ParsePerfRecord(int64_t, perf_importer::Record) = 0;
};
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor
#endif // SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_TRACE_PARSER_H_
diff --git a/src/trace_processor/importers/common/track_tracker.cc b/src/trace_processor/importers/common/track_tracker.cc
index 5026391..4db3020 100644
--- a/src/trace_processor/importers/common/track_tracker.cc
+++ b/src/trace_processor/importers/common/track_tracker.cc
@@ -19,7 +19,9 @@
#include <optional>
#include "src/trace_processor/importers/common/args_tracker.h"
+#include "src/trace_processor/importers/common/process_track_translation_table.h"
#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/types/trace_processor_context.h"
namespace perfetto {
namespace trace_processor {
@@ -140,7 +142,7 @@
}
TrackId TrackTracker::InternLegacyChromeAsyncTrack(
- StringId name,
+ StringId raw_name,
uint32_t upid,
int64_t trace_id,
bool trace_id_is_process_scoped,
@@ -151,6 +153,8 @@
tuple.trace_id = trace_id;
tuple.source_scope = source_scope;
+ const StringId name =
+ context_->process_track_translation_table->TranslateName(raw_name);
auto it = chrome_tracks_.find(tuple);
if (it != chrome_tracks_.end()) {
if (name != kNullStringId) {
@@ -194,9 +198,11 @@
return id;
}
-TrackId TrackTracker::CreateProcessAsyncTrack(StringId name,
+TrackId TrackTracker::CreateProcessAsyncTrack(StringId raw_name,
UniquePid upid,
StringId source) {
+ const StringId name =
+ context_->process_track_translation_table->TranslateName(raw_name);
tables::ProcessTrackTable::Row row(name);
row.upid = upid;
row.machine_id = context_->machine_id();
@@ -318,10 +324,12 @@
return track;
}
-TrackId TrackTracker::InternProcessCounterTrack(StringId name,
+TrackId TrackTracker::InternProcessCounterTrack(StringId raw_name,
UniquePid upid,
StringId unit,
StringId description) {
+ const StringId name =
+ context_->process_track_translation_table->TranslateName(raw_name);
auto it = upid_counter_tracks_.find(std::make_pair(name, upid));
if (it != upid_counter_tracks_.end()) {
return it->second;
diff --git a/src/trace_processor/importers/fuchsia/fuchsia_parser_unittest.cc b/src/trace_processor/importers/fuchsia/fuchsia_parser_unittest.cc
index ca02bdc..90a289b 100644
--- a/src/trace_processor/importers/fuchsia/fuchsia_parser_unittest.cc
+++ b/src/trace_processor/importers/fuchsia/fuchsia_parser_unittest.cc
@@ -28,6 +28,7 @@
#include "src/trace_processor/importers/common/event_tracker.h"
#include "src/trace_processor/importers/common/flow_tracker.h"
#include "src/trace_processor/importers/common/metadata_tracker.h"
+#include "src/trace_processor/importers/common/process_track_translation_table.h"
#include "src/trace_processor/importers/common/process_tracker.h"
#include "src/trace_processor/importers/common/slice_tracker.h"
#include "src/trace_processor/importers/common/stack_profile_tracker.h"
@@ -248,6 +249,8 @@
context_.ftrace_sched_tracker.reset(sched_);
process_ = new NiceMock<MockProcessTracker>(&context_);
context_.process_tracker.reset(process_);
+ context_.process_track_translation_table.reset(
+ new ProcessTrackTranslationTable(storage_));
slice_ = new NiceMock<MockSliceTracker>(&context_);
context_.slice_tracker.reset(slice_);
context_.slice_translation_table.reset(new SliceTranslationTable(storage_));
diff --git a/src/trace_processor/importers/perf/BUILD.gn b/src/trace_processor/importers/perf/BUILD.gn
index 376f9b4..7a3f2a3 100644
--- a/src/trace_processor/importers/perf/BUILD.gn
+++ b/src/trace_processor/importers/perf/BUILD.gn
@@ -16,6 +16,8 @@
source_set("record") {
sources = [
+ "perf_counter.cc",
+ "perf_counter.h",
"perf_event.h",
"perf_event_attr.cc",
"perf_event_attr.h",
@@ -26,38 +28,44 @@
]
deps = [
"../../../../gn:default_deps",
+ "../../../../include/perfetto/ext/base:base",
+ "../../../../include/perfetto/trace_processor:trace_processor",
"../../../../protos/perfetto/trace/profiling:zero",
- "../../sorter",
"../../storage",
"../../tables:tables_python",
"../../types",
- "../common",
"../common:parser_types",
]
}
source_set("perf") {
sources = [
- "perf_data_parser.cc",
- "perf_data_parser.h",
- "perf_data_reader.cc",
- "perf_data_reader.h",
+ "attrs_section_reader.cc",
+ "attrs_section_reader.h",
+ "features.cc",
+ "features.h",
+ "mmap_record.cc",
+ "mmap_record.h",
"perf_data_tokenizer.cc",
"perf_data_tokenizer.h",
- "perf_data_tracker.cc",
- "perf_data_tracker.h",
"perf_file.h",
- "reader.h",
+ "record_parser.cc",
+ "record_parser.h",
+ "sample.cc",
+ "sample.h",
]
public_deps = [ ":record" ]
deps = [
"../../../../gn:default_deps",
+ "../../../../protos/perfetto/trace:zero",
"../../../../protos/perfetto/trace/profiling:zero",
"../../sorter",
"../../storage",
"../../tables:tables_python",
"../../types",
- "../common",
- "../common:parser_types",
+ "../../util:build_id",
+ "../../util:file_buffer",
+ "../../util:util",
+ "../common:common",
"../proto:minimal",
]
}
@@ -65,8 +73,6 @@
perfetto_unittest_source_set("unittests") {
testonly = true
sources = [
- "perf_data_reader_unittest.cc",
- "perf_data_tracker_unittest.cc",
"perf_session_unittest.cc",
"reader_unittest.cc",
]
diff --git a/src/trace_processor/importers/perf/attrs_section_reader.cc b/src/trace_processor/importers/perf/attrs_section_reader.cc
new file mode 100644
index 0000000..f19ea3e
--- /dev/null
+++ b/src/trace_processor/importers/perf/attrs_section_reader.cc
@@ -0,0 +1,76 @@
+/*
+ * 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/attrs_section_reader.h"
+
+#include <cinttypes>
+
+#include "perfetto/base/logging.h"
+#include "perfetto/base/status.h"
+#include "perfetto/trace_processor/trace_blob_view.h"
+#include "src/trace_processor/importers/perf/perf_file.h"
+
+namespace perfetto::trace_processor::perf_importer {
+
+// static
+base::StatusOr<AttrsSectionReader> AttrsSectionReader::Create(
+ const PerfFile::Header& header,
+ TraceBlobView section) {
+ PERFETTO_CHECK(section.size() == header.attrs.size);
+
+ if (header.attr_size == 0) {
+ return base::ErrStatus("Invalid attr_size (0) in perf file header.");
+ }
+
+ if (header.attrs.size % header.attr_size != 0) {
+ return base::ErrStatus("Invalid attrs section size %" PRIu64
+ " for attr_size %" PRIu64 " in perf file header.",
+ header.attrs.size, header.attr_size);
+ }
+
+ const size_t num_attr = header.attrs.size / header.attr_size;
+
+ // Each entry is a perf_event_attr followed by a Section, but the size of
+ // the perf_event_attr struct written in the file might not be the same as
+ // sizeof(perf_event_attr) as this struct might grow over time (can be
+ // bigger or smaller).
+ static constexpr size_t kSectionSize = sizeof(PerfFile::Section);
+ if (header.attr_size < kSectionSize) {
+ return base::ErrStatus(
+ "Invalid attr_size in file header. Expected at least %zu, found "
+ "%" PRIu64,
+ kSectionSize, header.attr_size);
+ }
+ const size_t attr_size = header.attr_size - kSectionSize;
+
+ return AttrsSectionReader(std::move(section), num_attr, attr_size);
+}
+
+base::Status AttrsSectionReader::ReadNext(PerfFile::AttrsEntry& entry) {
+ PERFETTO_CHECK(reader_.ReadPerfEventAttr(entry.attr, attr_size_));
+
+ if (entry.attr.size != attr_size_) {
+ return base::ErrStatus(
+ "Invalid attr.size. Expected %zu, but found %" PRIu32, attr_size_,
+ entry.attr.size);
+ }
+
+ PERFETTO_CHECK(reader_.Read(entry.ids));
+ --num_attr_;
+ return base::OkStatus();
+}
+
+} // namespace perfetto::trace_processor::perf_importer
diff --git a/src/trace_processor/importers/perf/attrs_section_reader.h b/src/trace_processor/importers/perf/attrs_section_reader.h
new file mode 100644
index 0000000..a7eaaa3
--- /dev/null
+++ b/src/trace_processor/importers/perf/attrs_section_reader.h
@@ -0,0 +1,56 @@
+/*
+ * 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_ATTRS_SECTION_READER_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_PERF_ATTRS_SECTION_READER_H_
+
+#include "perfetto/ext/base/status_or.h"
+#include "perfetto/trace_processor/trace_blob_view.h"
+#include "src/trace_processor/importers/perf/perf_file.h"
+#include "src/trace_processor/importers/perf/reader.h"
+
+namespace perfetto::trace_processor::perf_importer {
+
+// Helper to read the attrs section of a perf file. Provides an iterator like
+// interface over the perf_event_attr entries.
+class AttrsSectionReader {
+ public:
+ // Creates a new iterator.
+ // `attrs_section` data contained in the attrs section of the perf file.
+ static base::StatusOr<AttrsSectionReader> Create(
+ const PerfFile::Header& header,
+ TraceBlobView attrs_section);
+
+ // Returns true while there are available entries to read via `ReadNext`.
+ bool CanReadNext() const { return num_attr_ != 0; }
+
+ // Reads the next entry. Can onlybe called if `HasMore` returns true.
+ base::Status ReadNext(PerfFile::AttrsEntry& entry);
+
+ private:
+ AttrsSectionReader(TraceBlobView section, size_t num_attr, size_t attr_size)
+ : reader_(std::move(section)),
+ num_attr_(num_attr),
+ attr_size_(attr_size) {}
+
+ Reader reader_;
+ size_t num_attr_;
+ const size_t attr_size_;
+};
+
+} // namespace perfetto::trace_processor::perf_importer
+
+#endif // SRC_TRACE_PROCESSOR_IMPORTERS_PERF_ATTRS_SECTION_READER_H_
diff --git a/src/trace_processor/importers/perf/features.cc b/src/trace_processor/importers/perf/features.cc
new file mode 100644
index 0000000..861a8bf
--- /dev/null
+++ b/src/trace_processor/importers/perf/features.cc
@@ -0,0 +1,258 @@
+/*
+ * 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/features.h"
+
+#include <cstdint>
+#include <utility>
+
+#include "perfetto/base/logging.h"
+#include "perfetto/base/status.h"
+#include "perfetto/ext/base/string_utils.h"
+#include "perfetto/ext/base/string_view.h"
+#include "perfetto/trace_processor/status.h"
+#include "perfetto/trace_processor/trace_blob_view.h"
+#include "src/trace_processor/importers/perf/perf_event.h"
+#include "src/trace_processor/importers/perf/reader.h"
+#include "src/trace_processor/util/status_macros.h"
+
+namespace perfetto::trace_processor::perf_importer::feature {
+namespace {
+
+bool ParseString(Reader& reader, std::string& out) {
+ uint32_t len;
+ base::StringView str;
+ if (!reader.Read(len) || len == 0 || !reader.ReadStringView(str, len)) {
+ return false;
+ }
+
+ if (str.at(len - 1) != '\0') {
+ return false;
+ }
+
+ out = std::string(str.data(), len - 1);
+ return true;
+}
+
+bool ParseBuildId(const perf_event_header& header,
+ TraceBlobView blob,
+ BuildId& out) {
+ Reader reader(std::move(blob));
+ struct {
+ char data[20];
+ uint8_t size;
+ uint8_t reserved[3];
+ } build_id;
+
+ if (!reader.Read(out.pid) || !reader.Read(build_id) ||
+ !reader.ReadStringUntilEndOrNull(out.filename)) {
+ return false;
+ }
+
+ if (header.misc & PERF_RECORD_MISC_EXT_RESERVED) {
+ if (build_id.size > sizeof(build_id.data)) {
+ return false;
+ }
+ } else {
+ // Probably a simpleperf trace. Simpleperf fills build_ids with zeros up
+ // to a length of 20 and leaves the rest uninitialized :( so we can not read
+ // 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;
+ }
+ }
+ out.build_id = std::string(build_id.data, build_id.size);
+ return true;
+}
+
+util::Status ParseEventTypeInfo(std::string value, SimpleperfMetaInfo& out) {
+ for (const auto& line : base::SplitString(value, "\n")) {
+ auto tokens = base::SplitString(line, ",");
+ if (tokens.size() != 3) {
+ return util::ErrStatus("Invalid event_type_info: '%s'", line.c_str());
+ }
+
+ auto type = base::StringToUInt32(tokens[1]);
+ if (!type) {
+ return util::ErrStatus("Could not parse type in event_type_info: '%s'",
+ tokens[1].c_str());
+ }
+ auto config = base::StringToUInt64(tokens[2]);
+ if (!config) {
+ return util::ErrStatus("Could not parse config in event_type_info: '%s'",
+ tokens[2].c_str());
+ }
+
+ out.event_type_info.Insert({*type, *config}, std::move(tokens[0]));
+ }
+
+ return util::OkStatus();
+}
+
+util::Status ParseSimpleperfMetaInfoEntry(
+ std::pair<std::string, std::string> entry,
+ SimpleperfMetaInfo& out) {
+ static constexpr char kEventTypeInfoKey[] = "event_type_info";
+ if (entry.first == kEventTypeInfoKey) {
+ return ParseEventTypeInfo(std::move(entry.second), out);
+ }
+
+ PERFETTO_CHECK(
+ out.entries.Insert(std::move(entry.first), std::move(entry.second))
+ .second);
+ return util::OkStatus();
+}
+
+} // namespace
+
+// static
+util::Status BuildId::Parse(TraceBlobView bytes,
+ std::function<util::Status(BuildId)> cb) {
+ Reader reader(std::move(bytes));
+ while (reader.size_left() != 0) {
+ perf_event_header header;
+ TraceBlobView payload;
+ if (!reader.Read(header)) {
+ return base::ErrStatus(
+ "Failed to parse feature BuildId. Could not read header.");
+ }
+ if (header.size < sizeof(header)) {
+ return base::ErrStatus(
+ "Failed to parse feature BuildId. Invalid size in header.");
+ }
+ if (!reader.ReadBlob(payload, header.size - sizeof(header))) {
+ return base::ErrStatus(
+ "Failed to parse feature BuildId. Could not read payload.");
+ }
+
+ BuildId build_id;
+ if (!ParseBuildId(header, std::move(payload), build_id)) {
+ return base::ErrStatus(
+ "Failed to parse feature BuildId. Could not read entry.");
+ }
+
+ RETURN_IF_ERROR(cb(std::move(build_id)));
+ }
+ return util::OkStatus();
+}
+
+// static
+util::Status SimpleperfMetaInfo::Parse(const TraceBlobView& bytes,
+ SimpleperfMetaInfo& out) {
+ auto* it_end = reinterpret_cast<const char*>(bytes.data() + bytes.size());
+ for (auto* it = reinterpret_cast<const char*>(bytes.data()); it != it_end;) {
+ auto end = std::find(it, it_end, '\0');
+ if (end == it_end) {
+ return util::ErrStatus("Failed to read key from Simpleperf MetaInfo");
+ }
+ std::string key(it, end);
+ it = end;
+ ++it;
+ if (it == it_end) {
+ return util::ErrStatus("Missing value in Simpleperf MetaInfo");
+ }
+ end = std::find(it, it_end, '\0');
+ if (end == it_end) {
+ return util::ErrStatus("Failed to read value from Simpleperf MetaInfo");
+ }
+ std::string value(it, end);
+ it = end;
+ ++it;
+
+ RETURN_IF_ERROR(ParseSimpleperfMetaInfoEntry(
+ std::make_pair(std::move(key), std::move(value)), out));
+ }
+ return util::OkStatus();
+}
+
+// static
+util::Status EventDescription::Parse(
+ TraceBlobView bytes,
+ std::function<util::Status(EventDescription)> cb) {
+ Reader reader(std::move(bytes));
+ uint32_t nr;
+ uint32_t attr_size;
+ if (!reader.Read(nr) || !reader.Read(attr_size)) {
+ return util::ErrStatus("Failed to parse header for PERF_EVENT_DESC");
+ }
+
+ for (; nr != 0; --nr) {
+ EventDescription desc;
+ uint32_t nr_ids;
+ if (!reader.ReadPerfEventAttr(desc.attr, attr_size) ||
+ !reader.Read(nr_ids) || !ParseString(reader, desc.event_string)) {
+ return util::ErrStatus("Failed to parse record for PERF_EVENT_DESC");
+ }
+
+ desc.ids.resize(nr_ids);
+ for (uint64_t& id : desc.ids) {
+ if (!reader.Read(id)) {
+ return util::ErrStatus("Failed to parse ids for PERF_EVENT_DESC");
+ }
+ }
+ RETURN_IF_ERROR(cb(std::move(desc)));
+ }
+ return util::OkStatus();
+}
+
+util::Status ParseSimpleperfFile2(
+ TraceBlobView bytes,
+ std::function<util::Status(TraceBlobView)> cb) {
+ Reader reader(std::move(bytes));
+ while (reader.size_left() != 0) {
+ uint32_t len;
+ if (!reader.Read(len)) {
+ return base::ErrStatus("Failed to parse len in FEATURE_SIMPLEPERF_FILE2");
+ }
+ TraceBlobView payload;
+ if (!reader.ReadBlob(payload, len)) {
+ return base::ErrStatus(
+ "Failed to parse payload in FEATURE_SIMPLEPERF_FILE2");
+ }
+ RETURN_IF_ERROR(cb(std::move(payload)));
+ }
+ return util::OkStatus();
+}
+
+// static
+util::Status HeaderGroupDesc::Parse(TraceBlobView bytes, HeaderGroupDesc& out) {
+ Reader reader(std::move(bytes));
+ uint32_t nr;
+ if (!reader.Read(nr)) {
+ return util::ErrStatus("Failed to parse header for HEADER_GROUP_DESC");
+ }
+
+ HeaderGroupDesc group_desc;
+ group_desc.entries.resize(nr);
+ for (auto& e : group_desc.entries) {
+ if (!ParseString(reader, e.string) || !reader.Read(e.leader_idx) ||
+ !reader.Read(e.nr_members)) {
+ return util::ErrStatus("Failed to parse HEADER_GROUP_DESC entry");
+ }
+ }
+ out = std::move(group_desc);
+ return base::OkStatus();
+}
+
+} // namespace perfetto::trace_processor::perf_importer::feature
diff --git a/src/trace_processor/importers/perf/features.h b/src/trace_processor/importers/perf/features.h
new file mode 100644
index 0000000..2a6c22b
--- /dev/null
+++ b/src/trace_processor/importers/perf/features.h
@@ -0,0 +1,132 @@
+/*
+ * 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_FEATURES_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_PERF_FEATURES_H_
+
+#include <cstdint>
+#include <functional>
+#include <limits>
+#include <string>
+#include <vector>
+
+#include "perfetto/ext/base/flat_hash_map.h"
+#include "perfetto/ext/base/hash.h"
+#include "perfetto/trace_processor/status.h"
+#include "src/trace_processor/importers/perf/perf_event.h"
+
+namespace perfetto ::trace_processor {
+class TraceBlobView;
+
+namespace perf_importer::feature {
+
+enum Id : uint8_t {
+ ID_RESERVED = 0,
+ ID_TRACING_DATA = 1,
+ ID_BUILD_ID = 2,
+ ID_HOSTNAME = 3,
+ ID_OS_RELEASE = 4,
+ ID_VERSION = 5,
+ ID_ARCH = 6,
+ ID_NR_CPUS = 7,
+ ID_CPU_DESC = 8,
+ ID_CPU_ID = 9,
+ ID_TOTAL_MEM = 10,
+ ID_CMD_LINE = 11,
+ ID_EVENT_DESC = 12,
+ ID_CPU_TOPOLOGY = 13,
+ ID_NUMA_TOPOLOGY = 14,
+ ID_BRANCH_STACK = 15,
+ ID_PMU_MAPPINGS = 16,
+ ID_GROUP_DESC = 17,
+ ID_AUX_TRACE = 18,
+ ID_STAT = 19,
+ ID_CACHE = 20,
+ ID_SAMPLE_TIME = 21,
+ ID_SAMPLE_TOPOLOGY = 22,
+ ID_CLOCK_ID = 23,
+ ID_DIR_FORMAT = 24,
+ ID_BPF_PROG_INFO = 25,
+ ID_BPF_BTF = 26,
+ ID_COMPRESSED = 27,
+ ID_CPU_PUM_CAPS = 28,
+ ID_CLOCK_DATA = 29,
+ ID_HYBRID_TOPOLOGY = 30,
+ ID_PMU_CAPS = 31,
+ ID_SIMPLEPERF_FILE = 128,
+ ID_SIMPLEPERF_META_INFO = 129,
+ ID_SIMPLEPERF_FILE2 = 132,
+ ID_MAX = std::numeric_limits<uint8_t>::max(),
+};
+
+struct BuildId {
+ static util::Status Parse(TraceBlobView,
+ std::function<util::Status(BuildId)> cb);
+ int32_t pid;
+ std::string build_id;
+ std::string filename;
+};
+
+struct HeaderGroupDesc {
+ static util::Status Parse(TraceBlobView, HeaderGroupDesc& out);
+ struct Entry {
+ std::string string;
+ uint32_t leader_idx;
+ uint32_t nr_members;
+ };
+ std::vector<Entry> entries;
+};
+
+struct EventDescription {
+ static util::Status Parse(TraceBlobView,
+ std::function<util::Status(EventDescription)> cb);
+ perf_event_attr attr;
+ std::string event_string;
+ std::vector<uint64_t> ids;
+};
+
+struct SimpleperfMetaInfo {
+ static util::Status Parse(const TraceBlobView&, SimpleperfMetaInfo& out);
+ base::FlatHashMap<std::string, std::string> entries;
+ struct EventTypeAndConfig {
+ uint32_t type;
+ uint64_t config;
+ bool operator==(const EventTypeAndConfig& other) {
+ return type == other.type && config == other.config;
+ }
+ bool operator!=(const EventTypeAndConfig& other) {
+ return !(*this == other);
+ }
+ struct Hasher {
+ size_t operator()(const EventTypeAndConfig& o) const {
+ return static_cast<size_t>(base::Hasher::Combine(o.config, o.type));
+ }
+ };
+ };
+ using EventName = std::string;
+ base::FlatHashMap<EventTypeAndConfig, EventName, EventTypeAndConfig::Hasher>
+ event_type_info;
+};
+
+util::Status ParseSimpleperfFile2(
+ TraceBlobView,
+ std::function<util::Status(TraceBlobView)> cb);
+
+} // namespace perf_importer::feature
+
+} // namespace perfetto::trace_processor
+
+#endif // SRC_TRACE_PROCESSOR_IMPORTERS_PERF_FEATURES_H_
diff --git a/src/trace_processor/importers/perf/mmap_record.cc b/src/trace_processor/importers/perf/mmap_record.cc
new file mode 100644
index 0000000..d11fdd5
--- /dev/null
+++ b/src/trace_processor/importers/perf/mmap_record.cc
@@ -0,0 +1,66 @@
+/*
+ * 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/mmap_record.h"
+
+#include <optional>
+
+#include "perfetto/base/status.h"
+#include "src/trace_processor/importers/perf/reader.h"
+#include "src/trace_processor/importers/perf/record.h"
+
+namespace perfetto::trace_processor::perf_importer {
+
+base::Status MmapRecord::Parse(const Record& record) {
+ Reader reader(record.payload.copy());
+ if (!reader.Read(*static_cast<CommonMmapRecordFields*>(this)) ||
+ !reader.ReadCString(filename)) {
+ return base::ErrStatus("Failed to parse MMAP record");
+ }
+ cpu_mode = record.GetCpuMode();
+ return base::OkStatus();
+}
+
+base::Status Mmap2Record::Parse(const Record& record) {
+ Reader reader(record.payload.copy());
+ if (!reader.Read(*static_cast<BaseMmap2Record*>(this)) ||
+ !reader.ReadCString(filename)) {
+ return base::ErrStatus("Failed to parse MMAP record");
+ }
+
+ has_build_id = record.mmap_has_build_id();
+
+ if (has_build_id && build_id.build_id_size >
+ BaseMmap2Record::BuildIdFields::kMaxBuildIdSize) {
+ return base::ErrStatus(
+ "Invalid build_id_size in MMAP2 record. Expected <= %zu but found "
+ "%" PRIu8,
+ BaseMmap2Record::BuildIdFields::kMaxBuildIdSize,
+ build_id.build_id_size);
+ }
+
+ cpu_mode = record.GetCpuMode();
+
+ return base::OkStatus();
+}
+
+std::optional<BuildId> Mmap2Record::GetBuildId() const {
+ return has_build_id ? std::make_optional(BuildId::FromRaw(std::string(
+ build_id.build_id_buf, build_id.build_id_size)))
+ : std::nullopt;
+}
+
+} // namespace perfetto::trace_processor::perf_importer
diff --git a/src/trace_processor/importers/perf/mmap_record.h b/src/trace_processor/importers/perf/mmap_record.h
new file mode 100644
index 0000000..37b3939
--- /dev/null
+++ b/src/trace_processor/importers/perf/mmap_record.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PERF_MMAP_RECORD_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_PERF_MMAP_RECORD_H_
+
+#include <cstdint>
+#include <optional>
+#include <string>
+#include "perfetto/base/status.h"
+#include "protos/perfetto/trace/profiling/profile_packet.pbzero.h"
+#include "src/trace_processor/util/build_id.h"
+
+namespace perfetto::trace_processor::perf_importer {
+
+struct Record;
+
+struct CommonMmapRecordFields {
+ uint32_t pid;
+ uint32_t tid;
+ uint64_t addr;
+ uint64_t len;
+ uint64_t pgoff;
+};
+
+struct MmapRecord : public CommonMmapRecordFields {
+ std::string filename;
+ protos::pbzero::Profiling::CpuMode cpu_mode;
+
+ base::Status Parse(const Record& record);
+};
+
+struct BaseMmap2Record : public CommonMmapRecordFields {
+ struct BuildIdFields {
+ static constexpr size_t kMaxBuildIdSize = 20;
+ uint8_t build_id_size;
+ uint8_t reserved_1;
+ uint16_t reserved_2;
+ char build_id_buf[kMaxBuildIdSize];
+ };
+ struct InodeFields {
+ uint32_t maj;
+ uint32_t min;
+ int64_t ino;
+ uint64_t ino_generation;
+ };
+ static_assert(sizeof(BuildIdFields) == sizeof(InodeFields));
+
+ union {
+ BuildIdFields build_id;
+ InodeFields inode;
+ };
+ uint32_t prot;
+ uint32_t flags;
+};
+
+struct Mmap2Record : public BaseMmap2Record {
+ std::string filename;
+ protos::pbzero::Profiling::CpuMode cpu_mode;
+ bool has_build_id;
+
+ base::Status Parse(const Record& record);
+ std::optional<BuildId> GetBuildId() const;
+};
+
+} // namespace perfetto::trace_processor::perf_importer
+
+#endif // SRC_TRACE_PROCESSOR_IMPORTERS_PERF_MMAP_RECORD_H_
diff --git a/src/trace_processor/importers/perf/perf_counter.cc b/src/trace_processor/importers/perf/perf_counter.cc
new file mode 100644
index 0000000..685e940
--- /dev/null
+++ b/src/trace_processor/importers/perf/perf_counter.cc
@@ -0,0 +1,37 @@
+/*
+ * 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/perf_counter.h"
+
+#include <cstdint>
+
+#include "perfetto/base/logging.h"
+#include "src/trace_processor/tables/counter_tables_py.h"
+
+namespace perfetto::trace_processor::perf_importer {
+
+void PerfCounter::AddDelta(int64_t ts, double delta) {
+ last_count_ += delta;
+ counter_table_.Insert({ts, track_id_, last_count_});
+}
+
+void PerfCounter::AddCount(int64_t ts, double count) {
+ PERFETTO_CHECK(count >= last_count_);
+ last_count_ = count;
+ counter_table_.Insert({ts, track_id_, last_count_});
+}
+
+} // namespace perfetto::trace_processor::perf_importer
diff --git a/src/trace_processor/importers/perf/perf_counter.h b/src/trace_processor/importers/perf/perf_counter.h
new file mode 100644
index 0000000..fb7a28c
--- /dev/null
+++ b/src/trace_processor/importers/perf/perf_counter.h
@@ -0,0 +1,51 @@
+/*
+ * 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_PERF_COUNTER_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_PERF_PERF_COUNTER_H_
+
+#include <cstdint>
+
+#include "src/trace_processor/tables/counter_tables_py.h"
+#include "src/trace_processor/tables/track_tables_py.h"
+
+namespace perfetto::trace_processor::perf_importer {
+
+// Helper class to keep track of perf counters and convert delta values found in
+// perf files to absolute values needed for the perfetto counter table.
+class PerfCounter {
+ public:
+ PerfCounter(tables::CounterTable* counter_table,
+ const tables::PerfCounterTrackTable::ConstRowReference& track)
+ : counter_table_(*counter_table),
+ track_id_(track.id()),
+ is_timebase_(track.is_timebase()) {}
+
+ bool is_timebase() const { return is_timebase_; }
+
+ void AddDelta(int64_t ts, double delta);
+ void AddCount(int64_t ts, double count);
+
+ private:
+ tables::CounterTable& counter_table_;
+ tables::PerfCounterTrackTable::Id track_id_;
+ const bool is_timebase_;
+ double last_count_{0};
+};
+
+} // namespace perfetto::trace_processor::perf_importer
+
+#endif // SRC_TRACE_PROCESSOR_IMPORTERS_PERF_PERF_COUNTER_H_
diff --git a/src/trace_processor/importers/perf/perf_data_parser.cc b/src/trace_processor/importers/perf/perf_data_parser.cc
deleted file mode 100644
index e3dfef3..0000000
--- a/src/trace_processor/importers/perf/perf_data_parser.cc
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "src/trace_processor/importers/perf/perf_data_parser.h"
-
-#include <optional>
-#include <string>
-#include <vector>
-#include "perfetto/base/logging.h"
-#include "perfetto/ext/base/string_utils.h"
-#include "perfetto/trace_processor/trace_blob_view.h"
-#include "src/trace_processor/importers/common/mapping_tracker.h"
-#include "src/trace_processor/importers/common/process_tracker.h"
-#include "src/trace_processor/importers/perf/perf_data_reader.h"
-#include "src/trace_processor/importers/perf/perf_data_tracker.h"
-#include "src/trace_processor/storage/trace_storage.h"
-#include "src/trace_processor/tables/profiler_tables_py.h"
-
-namespace perfetto {
-namespace trace_processor {
-namespace perf_importer {
-
-using FramesTable = tables::StackProfileFrameTable;
-using CallsitesTable = tables::StackProfileCallsiteTable;
-
-PerfDataParser::PerfDataParser(TraceProcessorContext* context)
- : context_(context), tracker_(PerfDataTracker::GetOrCreate(context_)) {}
-
-PerfDataParser::~PerfDataParser() = default;
-
-base::StatusOr<PerfDataTracker::PerfSample> PerfDataParser::ParseSample(
- TraceBlobView tbv) {
- perf_importer::PerfDataReader reader(std::move(tbv));
- return tracker_->ParseSample(reader);
-}
-
-void PerfDataParser::ParsePerfRecord(int64_t ts, TraceBlobView tbv) {
- auto sample_status = ParseSample(std::move(tbv));
- if (!sample_status.ok()) {
- return;
- }
- PerfDataTracker::PerfSample sample = *sample_status;
-
- // The sample has been validated in tokenizer so callchain shouldn't be empty.
- PERFETTO_CHECK(!sample.callchain.empty());
-
- // First instruction pointer in the callchain should be from kernel space, so
- // it shouldn't be available in mappings.
- UniquePid upid = context_->process_tracker->GetOrCreateProcess(*sample.pid);
- if (context_->mapping_tracker->FindUserMappingForAddress(
- upid, sample.callchain.front())) {
- context_->storage->IncrementStats(stats::perf_samples_skipped);
- return;
- }
-
- if (sample.callchain.size() == 1) {
- context_->storage->IncrementStats(stats::perf_samples_skipped);
- return;
- }
-
- std::vector<FramesTable::Row> frame_rows;
- for (uint32_t i = 1; i < sample.callchain.size(); i++) {
- UserMemoryMapping* mapping =
- context_->mapping_tracker->FindUserMappingForAddress(
- upid, sample.callchain[i]);
- if (!mapping) {
- context_->storage->IncrementStats(stats::perf_samples_skipped);
- return;
- }
- FramesTable::Row new_row;
- std::string mock_name =
- base::StackString<1024>(
- "%" PRIu64, sample.callchain[i] - mapping->memory_range().start())
- .ToStdString();
- new_row.name = context_->storage->InternString(mock_name.c_str());
- new_row.mapping = mapping->mapping_id();
- new_row.rel_pc =
- static_cast<int64_t>(mapping->ToRelativePc(sample.callchain[i]));
- frame_rows.push_back(new_row);
- }
-
- // Insert frames. We couldn't do it before as no frames should be added if the
- // mapping couldn't be found for any of them.
- const auto& frames = context_->storage->mutable_stack_profile_frame_table();
- std::vector<FramesTable::Id> frame_ids;
- for (const auto& row : frame_rows) {
- frame_ids.push_back(frames->Insert(row).id);
- }
-
- // Insert callsites.
- const auto& callsites =
- context_->storage->mutable_stack_profile_callsite_table();
-
- std::optional<CallsitesTable::Id> parent_callsite_id;
- for (uint32_t i = 0; i < frame_ids.size(); i++) {
- CallsitesTable::Row callsite_row;
- callsite_row.frame_id = frame_ids[i];
- callsite_row.depth = i;
- callsite_row.parent_id = parent_callsite_id;
- parent_callsite_id = callsites->Insert(callsite_row).id;
- }
-
- // Insert stack sample.
- tables::PerfSampleTable::Row perf_sample_row;
- perf_sample_row.callsite_id = parent_callsite_id;
- perf_sample_row.ts = ts;
- if (sample.cpu) {
- perf_sample_row.cpu = *sample.cpu;
- }
- if (sample.tid) {
- auto utid = context_->process_tracker->GetOrCreateThread(*sample.tid);
- perf_sample_row.utid = utid;
- }
- context_->storage->mutable_perf_sample_table()->Insert(perf_sample_row);
-}
-
-} // namespace perf_importer
-} // namespace trace_processor
-} // namespace perfetto
diff --git a/src/trace_processor/importers/perf/perf_data_parser.h b/src/trace_processor/importers/perf/perf_data_parser.h
deleted file mode 100644
index f2ab0a3..0000000
--- a/src/trace_processor/importers/perf/perf_data_parser.h
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PERF_PERF_DATA_PARSER_H_
-#define SRC_TRACE_PROCESSOR_IMPORTERS_PERF_PERF_DATA_PARSER_H_
-
-#include <stdint.h>
-
-#include "perfetto/base/compiler.h"
-#include "perfetto/trace_processor/trace_blob_view.h"
-#include "src/trace_processor/importers/common/trace_parser.h"
-#include "src/trace_processor/importers/perf/perf_data_tracker.h"
-
-namespace perfetto {
-namespace trace_processor {
-namespace perf_importer {
-
-// Parses samples from perf.data files.
-class PerfDataParser : public PerfRecordParser {
- public:
- explicit PerfDataParser(TraceProcessorContext*);
- ~PerfDataParser() override;
-
- // The data in TraceBlobView has to be a perf.data sample.
- void ParsePerfRecord(int64_t timestamp, TraceBlobView) override;
-
- private:
- base::StatusOr<PerfDataTracker::PerfSample> ParseSample(TraceBlobView);
-
- TraceProcessorContext* context_ = nullptr;
- PerfDataTracker* tracker_ = nullptr;
-};
-
-} // namespace perf_importer
-} // namespace trace_processor
-} // namespace perfetto
-
-#endif // SRC_TRACE_PROCESSOR_IMPORTERS_PERF_PERF_DATA_PARSER_H_
diff --git a/src/trace_processor/importers/perf/perf_data_reader.cc b/src/trace_processor/importers/perf/perf_data_reader.cc
deleted file mode 100644
index e061826..0000000
--- a/src/trace_processor/importers/perf/perf_data_reader.cc
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "src/trace_processor/importers/perf/perf_data_reader.h"
-
-#include <cstddef>
-#include <optional>
-#include <vector>
-#include "perfetto/base/logging.h"
-#include "perfetto/trace_processor/trace_blob_view.h"
-
-namespace perfetto {
-namespace trace_processor {
-namespace perf_importer {
-void PerfDataReader::SkipSlow(size_t bytes_to_skip) {
- size_t bytes_in_buffer = BytesInBuffer();
-
- // Size fits in buffer.
- if (bytes_in_buffer >= bytes_to_skip) {
- buffer_offset_ += bytes_to_skip;
- return;
- }
-
- // Empty the buffer and increase the |blob_offset_|.
- buffer_offset_ = 0;
- buffer_.clear();
- blob_offset_ += bytes_to_skip - bytes_in_buffer;
-}
-
-void PerfDataReader::PeekSlow(uint8_t* obj_data, size_t size) const {
- size_t bytes_in_buffer = BytesInBuffer();
-
- // Read from buffer.
- if (bytes_in_buffer >= size) {
- memcpy(obj_data, buffer_.data() + buffer_offset_, size);
- return;
- }
-
- // Read from blob and buffer.
- memcpy(obj_data, buffer_.data() + buffer_offset_, bytes_in_buffer);
- memcpy(obj_data + bytes_in_buffer, tbv_.data() + blob_offset_,
- size - bytes_in_buffer);
-}
-
-TraceBlobView PerfDataReader::PeekTraceBlobViewSlow(size_t size) const {
- auto blob = TraceBlob::Allocate(size);
- size_t bytes_in_buffer = BytesInBuffer();
-
- // Data is in buffer, so we need to create a new TraceBlob from it.
- if (bytes_in_buffer >= size) {
- memcpy(blob.data(), buffer_.data() + buffer_offset_, size);
- return TraceBlobView(std::move(blob));
- }
-
- // Data is in between blob and buffer and we need to dump data from buffer
- // and blob to a new TraceBlob.
- size_t bytes_from_blob = size - bytes_in_buffer;
- memcpy(blob.data(), buffer_.data() + buffer_offset_, bytes_in_buffer);
- memcpy(blob.data() + bytes_in_buffer, tbv_.data() + blob_offset_,
- bytes_from_blob);
- return TraceBlobView(std::move(blob));
-}
-
-} // namespace perf_importer
-} // namespace trace_processor
-} // namespace perfetto
diff --git a/src/trace_processor/importers/perf/perf_data_reader.h b/src/trace_processor/importers/perf/perf_data_reader.h
deleted file mode 100644
index acf07d1..0000000
--- a/src/trace_processor/importers/perf/perf_data_reader.h
+++ /dev/null
@@ -1,187 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PERF_PERF_DATA_READER_H_
-#define SRC_TRACE_PROCESSOR_IMPORTERS_PERF_PERF_DATA_READER_H_
-
-#include <stdint.h>
-#include <cstddef>
-#include <cstring>
-#include <optional>
-#include <vector>
-
-#include "perfetto/base/logging.h"
-#include "perfetto/trace_processor/trace_blob.h"
-#include "perfetto/trace_processor/trace_blob_view.h"
-
-namespace perfetto {
-namespace trace_processor {
-namespace perf_importer {
-
-// Reader class for tokenizing and parsing. Currently used by perf importer, but
-// it's design is not related to perf. Responsible for hiding away the
-// complexity of reading values from TraceBlobView and glueing the tbvs together
-// in case there is data between many of them.
-class PerfDataReader {
- public:
- PerfDataReader() = default;
- explicit PerfDataReader(TraceBlobView tbv) : tbv_(std::move(tbv)) {}
-
- // Updates old TraceBlobView with new one. If there is data left in the old
- // one, it will be saved in the buffer.
- void Append(TraceBlobView tbv) {
- uint64_t size_before = BytesAvailable();
- buffer_.insert(buffer_.end(), tbv_.data() + blob_offset_,
- tbv_.data() + tbv_.size());
- tbv_ = std::move(tbv);
- blob_offset_ = 0;
-
- // Post condition. Checks whether no data has been lost in the Append.
- PERFETTO_DCHECK(BytesAvailable() == size_before + tbv.size());
- }
-
- // Reads the |obj| and updates |file_offset_| of the reader.
- // NOTE: Assumes count of bytes available is higher than sizeof(T).
- template <typename T>
- void Read(T& obj) {
- Peek(obj);
- Skip<T>();
- }
-
- // Reads the T value for std::optional<T>.
- // NOTE: Assumes count of bytes available is higher than sizeof(T).
- template <typename T>
- void ReadOptional(std::optional<T>& obj) {
- T val;
- Read(val);
- obj = val;
- }
-
- // Reads all of the data in the |vec| and updates |file_offset_| of the
- // reader.
- // NOTE: Assumes count of bytes available is higher than sizeof(T).
- template <typename T>
- void ReadVector(std::vector<T>& vec) {
- PERFETTO_DCHECK(CanReadSize(sizeof(T) * vec.size()));
- for (T& val : vec) {
- Read(val);
- }
- }
-
- // Updates the |file_offset_| by the sizeof(T).
- // NOTE: Assumes count of bytes available is higher than sizeof(T).
- template <typename T>
- void Skip() {
- Skip(sizeof(T));
- }
-
- // Updates the |file_offset_| by the |bytes_to_skip|.
- // NOTE: Assumes count of bytes available is higher than sizeof(T).
- void Skip(uint64_t bytes_to_skip) {
- uint64_t bytes_available_before = BytesAvailable();
- PERFETTO_DCHECK(CanReadSize(bytes_to_skip));
- size_t skip = static_cast<size_t>(bytes_to_skip);
-
- // Incrementing file offset is not related to the way data is split.
- file_offset_ += skip;
- size_t bytes_in_buffer = BytesInBuffer();
-
- // Empty buffer. Increment |blob_offset_|.
- if (PERFETTO_LIKELY(bytes_in_buffer == 0)) {
- buffer_offset_ = 0;
- buffer_.clear();
- blob_offset_ += skip;
- } else {
- SkipSlow(skip);
- }
- PERFETTO_DCHECK(BytesAvailable() == bytes_available_before - skip);
- }
-
- // Peeks the |obj| without updating the |file_offset_| of the reader.
- // NOTE: Assumes count of bytes available is higher than sizeof(T).
- template <typename T>
- void Peek(T& obj) const {
- PERFETTO_DCHECK(CanReadSize(sizeof(T)));
- size_t bytes_available_before = BytesAvailable();
-
- // Read from blob.
- if (PERFETTO_LIKELY(BytesInBuffer() == 0)) {
- memcpy(&obj, tbv_.data() + blob_offset_, sizeof(T));
- } else {
- PeekSlow(reinterpret_cast<uint8_t*>(&obj), sizeof(T));
- }
-
- PERFETTO_DCHECK(BytesAvailable() == bytes_available_before);
- }
-
- // Creates TraceBlobView with data of |data_size| bytes from current offset.
- // NOTE: Assumes count of bytes available is higher than sizeof(T).
- TraceBlobView PeekTraceBlobView(uint64_t data_size) const {
- PERFETTO_DCHECK(CanReadSize(data_size));
- size_t size = static_cast<size_t>(data_size);
- size_t bytes_in_buffer = BytesInBuffer();
-
- // Data is in blob, so it's enough to slice the existing |tbv_|.
- if (PERFETTO_LIKELY(bytes_in_buffer == 0)) {
- return tbv_.slice(tbv_.data() + blob_offset_, size);
- }
- return PeekTraceBlobViewSlow(size);
- }
-
- // Returns if there is enough data to read offsets between |start| and |end|.
- bool CanAccessFileRange(uint64_t start, uint64_t end) const {
- return CanAccessFileOffset(static_cast<size_t>(start)) &&
- CanAccessFileOffset(static_cast<size_t>(end));
- }
-
- // Returns if there is enough data to read |size| bytes.
- bool CanReadSize(uint64_t size) const { return size <= BytesAvailable(); }
-
- uint64_t current_file_offset() const { return file_offset_; }
-
- private:
- void SkipSlow(size_t bytes_to_skip);
-
- void PeekSlow(uint8_t* obj_data, size_t) const;
-
- TraceBlobView PeekTraceBlobViewSlow(size_t) const;
-
- size_t BytesInBuffer() const {
- PERFETTO_DCHECK(buffer_.size() >= buffer_offset_);
- return buffer_.size() - buffer_offset_;
- }
- size_t BytesInBlob() const { return tbv_.size() - blob_offset_; }
- size_t BytesAvailable() const { return BytesInBuffer() + BytesInBlob(); }
-
- bool CanAccessFileOffset(size_t off) const {
- return off >= file_offset_ && off <= file_offset_ + BytesAvailable();
- }
-
- TraceBlobView tbv_;
- std::vector<uint8_t> buffer_;
-
- // Where we are in relation to the current blob.
- size_t blob_offset_ = 0;
- // Where we are in relation to the file.
- size_t file_offset_ = 0;
- // Where we are in relation to the buffer.
- size_t buffer_offset_ = 0;
-};
-} // namespace perf_importer
-} // namespace trace_processor
-} // namespace perfetto
-
-#endif // SRC_TRACE_PROCESSOR_IMPORTERS_PERF_PERF_DATA_READER_H_
diff --git a/src/trace_processor/importers/perf/perf_data_reader_unittest.cc b/src/trace_processor/importers/perf/perf_data_reader_unittest.cc
deleted file mode 100644
index 5bf3081..0000000
--- a/src/trace_processor/importers/perf/perf_data_reader_unittest.cc
+++ /dev/null
@@ -1,234 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "src/trace_processor/importers/perf/perf_data_reader.h"
-
-#include <stddef.h>
-
-#include "perfetto/base/build_config.h"
-#include "test/gtest_and_gmock.h"
-
-namespace perfetto {
-namespace trace_processor {
-namespace perf_importer {
-
-namespace {
-template <typename T>
-TraceBlobView TraceBlobViewFromVector(std::vector<T> nums) {
- size_t data_size = sizeof(T) * nums.size();
- auto blob = TraceBlob::Allocate(data_size);
- memcpy(blob.data(), nums.data(), data_size);
- return TraceBlobView(std::move(blob));
-}
-} // namespace
-
-TEST(PerfDataReaderUnittest, AppendToEmpty) {
- TraceBlobView tbv = TraceBlobViewFromVector(std::vector<uint64_t>{1, 2, 3});
- PerfDataReader reader;
- EXPECT_FALSE(reader.CanReadSize(1));
- reader.Append(std::move(tbv));
- EXPECT_TRUE(reader.CanReadSize(sizeof(uint64_t) * 2));
-}
-
-TEST(PerfDataReaderUnittest, Append) {
- TraceBlobView tbv = TraceBlobViewFromVector(std::vector<uint64_t>{1, 2, 3});
- PerfDataReader reader(std::move(tbv));
-
- EXPECT_TRUE(reader.CanReadSize(sizeof(uint64_t) * 3));
- EXPECT_FALSE(reader.CanReadSize(sizeof(uint64_t) * 3 + 1));
-
- reader.Append(TraceBlobViewFromVector(std::vector<uint64_t>{1, 2}));
- EXPECT_TRUE(reader.CanReadSize(sizeof(uint64_t) * 5));
-}
-
-TEST(PerfDataReaderUnittest, Read) {
- TraceBlobView tbv = TraceBlobViewFromVector(std::vector<uint64_t>{2, 4, 8});
- PerfDataReader reader(std::move(tbv));
- uint64_t val;
- reader.Read(val);
- EXPECT_EQ(val, 2u);
-}
-
-TEST(PerfDataReaderUnittest, ReadFromBuffer) {
- TraceBlobView tbv = TraceBlobViewFromVector(std::vector<uint64_t>{2, 4, 6});
- PerfDataReader reader(std::move(tbv));
- reader.Append(TraceBlobViewFromVector(std::vector<uint64_t>{1, 3}));
-
- // Now the first vector should be in the buffer.
- uint64_t val;
- reader.Read(val);
- EXPECT_EQ(val, 2u);
-}
-
-TEST(PerfDataReaderUnittest, ReadBetweenBufferAndBlob) {
- TraceBlobView tbv = TraceBlobViewFromVector(std::vector<uint64_t>{2, 4});
- PerfDataReader reader(std::move(tbv));
- reader.Append(TraceBlobViewFromVector(std::vector<uint64_t>{1, 3, 5}));
-
- struct Nums {
- uint64_t x;
- uint64_t y;
- uint64_t z;
- };
-
- Nums nums;
- reader.Read(nums);
-
- EXPECT_EQ(nums.x, 2u);
- EXPECT_EQ(nums.y, 4u);
- EXPECT_EQ(nums.z, 1u);
-}
-
-TEST(PerfDataReaderUnittest, ReadOptional) {
- TraceBlobView tbv = TraceBlobViewFromVector(std::vector<uint64_t>{2, 4, 8});
- PerfDataReader reader(std::move(tbv));
- std::optional<uint64_t> val;
- reader.ReadOptional(val);
- EXPECT_EQ(val, 2u);
-}
-
-TEST(PerfDataReaderUnittest, ReadVector) {
- TraceBlobView tbv =
- TraceBlobViewFromVector(std::vector<uint64_t>{2, 4, 8, 16, 32});
- PerfDataReader reader(std::move(tbv));
-
- std::vector<uint64_t> res(3);
- reader.ReadVector(res);
-
- std::vector<uint64_t> valid{2, 4, 8};
- EXPECT_EQ(res, valid);
-}
-
-TEST(PerfDataReaderUnittest, Skip) {
- TraceBlobView tbv = TraceBlobViewFromVector(std::vector<uint64_t>{2, 4, 8});
- PerfDataReader reader(std::move(tbv));
-
- reader.Skip<uint64_t>();
-
- uint64_t val;
- reader.Read(val);
- EXPECT_EQ(val, 4u);
-}
-
-TEST(PerfDataReaderUnittest, SkipInBuffer) {
- TraceBlobView tbv = TraceBlobViewFromVector(std::vector<uint64_t>{2, 4});
- PerfDataReader reader(std::move(tbv));
- reader.Append(TraceBlobViewFromVector(std::vector<uint64_t>{1, 3, 5}));
-
- reader.Skip<uint64_t>();
- EXPECT_EQ(reader.current_file_offset(), sizeof(uint64_t));
-}
-
-TEST(PerfDataReaderUnittest, SkipBetweenBufferAndBlob) {
- TraceBlobView tbv = TraceBlobViewFromVector(std::vector<uint64_t>{2, 4});
- PerfDataReader reader(std::move(tbv));
- reader.Append(TraceBlobViewFromVector(std::vector<uint64_t>{1, 3, 5}));
-
- struct Nums {
- uint64_t x;
- uint64_t y;
- uint64_t z;
- };
-
- reader.Skip<Nums>();
- EXPECT_EQ(reader.current_file_offset(), sizeof(Nums));
-}
-
-TEST(PerfDataReaderUnittest, Peek) {
- TraceBlobView tbv = TraceBlobViewFromVector(std::vector<uint64_t>{2, 4, 8});
- PerfDataReader reader(std::move(tbv));
-
- uint64_t peek_val;
- reader.Peek(peek_val);
-
- uint64_t val;
- reader.Read(val);
- EXPECT_EQ(val, 2u);
-}
-
-TEST(PerfDataReaderUnittest, PeekFromBuffer) {
- TraceBlobView tbv = TraceBlobViewFromVector(std::vector<uint64_t>{2, 4, 6});
- PerfDataReader reader(std::move(tbv));
- reader.Append(TraceBlobViewFromVector(std::vector<uint64_t>{1, 3}));
-
- uint64_t val;
- reader.Peek(val);
- EXPECT_EQ(val, 2u);
-}
-
-TEST(PerfDataReaderUnittest, PeekBetweenBufferAndBlob) {
- TraceBlobView tbv = TraceBlobViewFromVector(std::vector<uint64_t>{2, 4});
- PerfDataReader reader(std::move(tbv));
- reader.Append(TraceBlobViewFromVector(std::vector<uint64_t>{1, 3, 5}));
-
- struct Nums {
- uint64_t x;
- uint64_t y;
- uint64_t z;
- };
-
- Nums nums;
- reader.Peek(nums);
-
- EXPECT_EQ(nums.x, 2u);
- EXPECT_EQ(nums.y, 4u);
- EXPECT_EQ(nums.z, 1u);
-}
-
-TEST(PerfDataReaderUnittest, GetTraceBlobView) {
- TraceBlobView tbv = TraceBlobViewFromVector(std::vector<uint64_t>{2, 4, 8});
- PerfDataReader reader(std::move(tbv));
- EXPECT_TRUE(reader.CanReadSize(sizeof(uint64_t) * 3));
-
- TraceBlobView new_tbv = reader.PeekTraceBlobView(sizeof(uint64_t) * 2);
- PerfDataReader new_reader(std::move(new_tbv));
- EXPECT_TRUE(new_reader.CanReadSize(sizeof(uint64_t) * 2));
- EXPECT_FALSE(new_reader.CanReadSize(sizeof(uint64_t) * 3));
-}
-
-TEST(PerfDataReaderUnittest, GetTraceBlobViewFromBuffer) {
- TraceBlobView tbv = TraceBlobViewFromVector(std::vector<uint64_t>{2, 4});
- PerfDataReader reader(std::move(tbv));
- reader.Append(TraceBlobViewFromVector(std::vector<uint64_t>{1, 3, 5}));
-
- TraceBlobView new_tbv = reader.PeekTraceBlobView(sizeof(uint64_t) * 2);
- PerfDataReader new_reader(std::move(new_tbv));
- EXPECT_TRUE(new_reader.CanReadSize(sizeof(uint64_t) * 2));
- EXPECT_FALSE(new_reader.CanReadSize(sizeof(uint64_t) * 3));
-}
-
-TEST(PerfDataReaderUnittest, GetTraceBlobViewFromBetweenBufferAndBlob) {
- TraceBlobView tbv = TraceBlobViewFromVector(std::vector<uint64_t>{2, 4});
- PerfDataReader reader(std::move(tbv));
- reader.Append(TraceBlobViewFromVector(std::vector<uint64_t>{1, 3, 5}));
-
- TraceBlobView new_tbv = reader.PeekTraceBlobView(sizeof(uint64_t) * 3);
- PerfDataReader new_reader(std::move(new_tbv));
- EXPECT_TRUE(new_reader.CanReadSize(sizeof(uint64_t) * 3));
- EXPECT_FALSE(new_reader.CanReadSize(sizeof(uint64_t) * 4));
-}
-
-TEST(PerfDataReaderUnittest, CanAccessFileRange) {
- TraceBlobView tbv = TraceBlobViewFromVector(std::vector<uint64_t>{2, 4, 8});
- PerfDataReader reader(std::move(tbv));
- EXPECT_TRUE(reader.CanAccessFileRange(2, sizeof(uint64_t) * 3));
- EXPECT_FALSE(reader.CanAccessFileRange(2, sizeof(uint64_t) * 3 + 10));
-}
-
-} // namespace perf_importer
-
-} // namespace trace_processor
-} // namespace perfetto
diff --git a/src/trace_processor/importers/perf/perf_data_tokenizer.cc b/src/trace_processor/importers/perf/perf_data_tokenizer.cc
index 25b54b9..2dcc3e5 100644
--- a/src/trace_processor/importers/perf/perf_data_tokenizer.cc
+++ b/src/trace_processor/importers/perf/perf_data_tokenizer.cc
@@ -16,56 +16,97 @@
#include "src/trace_processor/importers/perf/perf_data_tokenizer.h"
+#include <cstddef>
#include <cstdint>
#include <cstring>
+#include <optional>
+#include <utility>
#include <vector>
+#include "perfetto/base/flat_set.h"
#include "perfetto/base/logging.h"
#include "perfetto/base/status.h"
#include "perfetto/ext/base/status_or.h"
+#include "perfetto/public/compiler.h"
#include "perfetto/trace_processor/trace_blob_view.h"
+#include "protos/perfetto/trace/clock_snapshot.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/perf_data_reader.h"
-#include "src/trace_processor/importers/perf/perf_data_tracker.h"
+#include "src/trace_processor/importers/perf/attrs_section_reader.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"
+#include "src/trace_processor/importers/perf/perf_session.h"
+#include "src/trace_processor/importers/perf/reader.h"
+#include "src/trace_processor/importers/perf/record.h"
+#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/status_macros.h"
-#include "protos/perfetto/trace/profiling/profile_packet.pbzero.h"
-
namespace perfetto {
namespace trace_processor {
namespace perf_importer {
namespace {
-protos::pbzero::Profiling::CpuMode GetCpuMode(const perf_event_header& header) {
- switch (header.misc & kPerfRecordMiscCpumodeMask) {
- case PERF_RECORD_MISC_KERNEL:
- return protos::pbzero::Profiling::MODE_KERNEL;
- case PERF_RECORD_MISC_USER:
- return protos::pbzero::Profiling::MODE_USER;
- case PERF_RECORD_MISC_HYPERVISOR:
- return protos::pbzero::Profiling::MODE_HYPERVISOR;
- case PERF_RECORD_MISC_GUEST_KERNEL:
- return protos::pbzero::Profiling::MODE_GUEST_KERNEL;
- case PERF_RECORD_MISC_GUEST_USER:
- return protos::pbzero::Profiling::MODE_GUEST_USER;
- default:
- return protos::pbzero::Profiling::MODE_UNKNOWN;
+
+void AddIds(uint8_t id_offset,
+ uint64_t flags,
+ base::FlatSet<uint8_t>& feature_ids) {
+ for (size_t i = 0; i < sizeof(flags) * 8; ++i) {
+ if (flags & 1) {
+ feature_ids.insert(id_offset);
+ }
+ flags >>= 1;
+ ++id_offset;
}
}
+
+base::FlatSet<uint8_t> ExtractFeatureIds(const uint64_t& flags,
+ const uint64_t (&flags1)[3]) {
+ base::FlatSet<uint8_t> feature_ids;
+ AddIds(0, flags, feature_ids);
+ AddIds(64, flags1[0], feature_ids);
+ AddIds(128, flags1[1], feature_ids);
+ AddIds(192, flags1[2], feature_ids);
+ return feature_ids;
+}
+
+bool ReadTime(const Record& record, std::optional<uint64_t>& time) {
+ if (!record.attr) {
+ time = std::nullopt;
+ return true;
+ }
+ Reader reader(record.payload.copy());
+ if (record.header.type != PERF_RECORD_SAMPLE) {
+ std::optional<size_t> offset = record.attr->time_offset_from_end();
+ if (!offset.has_value()) {
+ time = std::nullopt;
+ return true;
+ }
+ if (*offset > reader.size_left()) {
+ return false;
+ }
+ return reader.Skip(reader.size_left() - *offset) &&
+ reader.ReadOptional(time);
+ }
+
+ std::optional<size_t> offset = record.attr->time_offset_from_start();
+ if (!offset.has_value()) {
+ time = std::nullopt;
+ return true;
+ }
+ return reader.Skip(*offset) && reader.ReadOptional(time);
+}
+
} // namespace
PerfDataTokenizer::PerfDataTokenizer(TraceProcessorContext* ctx)
- : context_(ctx),
- tracker_(PerfDataTracker::GetOrCreate(context_)),
- reader_() {}
+ : context_(ctx) {}
PerfDataTokenizer::~PerfDataTokenizer() = default;
// A normal perf.data consts of:
// [ header ]
-// [ event ids (one array per attr) ]
// [ attr section ]
// [ data section ]
// [ optional feature sections ]
@@ -75,223 +116,310 @@
// Most file format documentation is outdated or misleading, instead see
// perf_session__do_write_header() in linux/tools/perf/util/header.c.
base::Status PerfDataTokenizer::Parse(TraceBlobView blob) {
- reader_.Append(std::move(blob));
+ buffer_.PushBack(std::move(blob));
- while (parsing_state_ != ParsingState::Records) {
- base::StatusOr<ParsingResult> parsed = ParsingResult::Success;
+ base::StatusOr<ParsingResult> result = ParsingResult::kSuccess;
+ while (result.ok() && result.value() == ParsingResult::kSuccess &&
+ !buffer_.empty()) {
switch (parsing_state_) {
- case ParsingState::Records:
+ case ParsingState::kParseHeader:
+ result = ParseHeader();
break;
- case ParsingState::Header:
- parsed = ParseHeader();
+
+ case ParsingState::kParseAttrs:
+ result = ParseAttrs();
break;
- case ParsingState::AfterHeaderBuffer:
- parsed = ParseAfterHeaderBuffer();
+
+ case ParsingState::kSeekRecords:
+ result = SeekRecords();
break;
- case ParsingState::Attrs:
- parsed = ParseAttrs();
+
+ case ParsingState::kParseRecords:
+ result = ParseRecords();
break;
- case ParsingState::AttrIdsFromBuffer:
- parsed = ParseAttrIdsFromBuffer();
+
+ case ParsingState::kParseFeatures:
+ result = ParseFeatures();
break;
- case ParsingState::AttrIds:
- parsed = ParseAttrIds();
+
+ case ParsingState::kParseFeatureSections:
+ result = ParseFeatureSections();
break;
+
+ case ParsingState::kDone:
+ result = base::ErrStatus("Unexpected data");
}
-
- // There has been an error while parsing.
- RETURN_IF_ERROR(parsed.status());
-
- // There is not enough data to parse so we need to load another blob.
- if (*parsed == ParsingResult::NoSpace)
- return base::OkStatus();
}
-
- while (reader_.current_file_offset() < header_.data.end()) {
- // Make sure |perf_event_header| of the sample is available.
- if (!reader_.CanReadSize(sizeof(perf_event_header))) {
- return base::OkStatus();
- }
-
- perf_event_header ev_header;
- reader_.Peek(ev_header);
- PERFETTO_CHECK(ev_header.size >= sizeof(perf_event_header));
-
- if (!reader_.CanReadSize(ev_header.size)) {
- return base::OkStatus();
- }
-
- reader_.Skip<perf_event_header>();
- uint64_t record_offset = reader_.current_file_offset();
- uint64_t record_size = ev_header.size - sizeof(perf_event_header);
-
- switch (ev_header.type) {
- case PERF_RECORD_SAMPLE: {
- TraceBlobView tbv = reader_.PeekTraceBlobView(record_size);
- auto sample_status = tracker_->ParseSample(reader_);
- if (!sample_status.ok()) {
- continue;
- }
- PerfDataTracker::PerfSample sample = *sample_status;
- if (!ValidateSample(*sample_status)) {
- continue;
- }
- context_->sorter->PushPerfRecord(
- static_cast<int64_t>(*sample_status->ts), std::move(tbv));
- break;
- }
- case PERF_RECORD_MMAP2: {
- PERFETTO_CHECK(ev_header.size >=
- sizeof(PerfDataTracker::Mmap2Record::Numeric));
- auto record = ParseMmap2Record(record_size);
- RETURN_IF_ERROR(record.status());
- record->cpu_mode = GetCpuMode(ev_header);
- tracker_->PushMmap2Record(*record);
- break;
- }
- default:
- break;
- }
-
- reader_.Skip((record_offset + record_size) - reader_.current_file_offset());
- }
-
- return base::OkStatus();
+ return result.status();
}
base::StatusOr<PerfDataTokenizer::ParsingResult>
PerfDataTokenizer::ParseHeader() {
- if (!reader_.CanReadSize(sizeof(PerfHeader))) {
- return ParsingResult::NoSpace;
+ auto tbv = buffer_.SliceOff(0, sizeof(header_));
+ if (!tbv) {
+ return ParsingResult::kMoreDataNeeded;
}
- reader_.Read(header_);
- PERFETTO_CHECK(header_.size == sizeof(PerfHeader));
- if (header_.attr_size !=
- sizeof(perf_event_attr) + sizeof(PerfDataTracker::PerfFileSection)) {
- return base::ErrStatus(
- "Unsupported: perf.data collected with a different ABI version of "
- "perf_event_attr.");
+ PERFETTO_CHECK(Reader(std::move(*tbv)).Read(header_));
+
+ // TODO: Check for endianess (big endian will have letters reversed);
+ if (memcmp(header_.magic, PerfFile::kPerfMagic,
+ sizeof(PerfFile::kPerfMagic)) != 0) {
+ return util::ErrStatus("Invalid magic string");
}
- if (header_.attrs.offset > header_.data.offset) {
- return base::ErrStatus(
- "Can only import files where samples are located after the metadata.");
+ if (header_.size != sizeof(PerfFile::Header)) {
+ return util::ErrStatus("Failed to perf file header size. Expected %" PRIu64
+ ", found %zu",
+ sizeof(PerfFile::Header));
}
- if (header_.size == header_.attrs.offset) {
- parsing_state_ = ParsingState::Attrs;
- } else {
- parsing_state_ = ParsingState::AfterHeaderBuffer;
- }
- return ParsingResult::Success;
-}
+ feature_ids_ = ExtractFeatureIds(header_.flags, header_.flags1);
+ feature_headers_section_ = {header_.data.end(),
+ feature_ids_.size() * sizeof(PerfFile::Section)};
+ context_->clock_tracker->SetTraceTimeClock(
+ protos::pbzero::ClockSnapshot::Clock::MONOTONIC);
-base::StatusOr<PerfDataTokenizer::ParsingResult>
-PerfDataTokenizer::ParseAfterHeaderBuffer() {
- if (!reader_.CanAccessFileRange(header_.size, header_.attrs.offset)) {
- return ParsingResult::NoSpace;
- }
- after_header_buffer_.resize(
- static_cast<size_t>(header_.attrs.offset - header_.size));
- reader_.ReadVector(after_header_buffer_);
- parsing_state_ = ParsingState::Attrs;
- return ParsingResult::Success;
+ PERFETTO_CHECK(buffer_.PopFrontUntil(sizeof(PerfFile::Header)));
+ parsing_state_ = ParsingState::kParseAttrs;
+ return ParsingResult::kSuccess;
}
base::StatusOr<PerfDataTokenizer::ParsingResult>
PerfDataTokenizer::ParseAttrs() {
- if (!reader_.CanAccessFileRange(header_.attrs.offset, header_.attrs.end())) {
- return ParsingResult::NoSpace;
- }
- reader_.Skip(header_.attrs.offset - reader_.current_file_offset());
- PerfDataTracker::PerfFileAttr attr;
- for (uint64_t i = header_.attrs.offset; i < header_.attrs.end();
- i += header_.attr_size) {
- reader_.Read(attr);
- PERFETTO_CHECK(attr.ids.size % sizeof(uint64_t) == 0);
- ids_start_ = std::min(ids_start_, attr.ids.offset);
- ids_end_ = std::max(ids_end_, attr.ids.end());
- attrs_.push_back(attr);
+ std::optional<TraceBlobView> tbv =
+ buffer_.SliceOff(header_.attrs.offset, header_.attrs.size);
+ if (!tbv) {
+ return ParsingResult::kMoreDataNeeded;
}
- if (ids_start_ == header_.size && ids_end_ <= header_.attrs.offset) {
- parsing_state_ = ParsingState::AttrIdsFromBuffer;
- } else {
- parsing_state_ = ParsingState::AttrIds;
+ ASSIGN_OR_RETURN(AttrsSectionReader attr_reader,
+ AttrsSectionReader::Create(header_, std::move(*tbv)));
+
+ PerfSession::Builder builder(
+ context_, context_->perf_sample_tracker->CreatePerfSession());
+ while (attr_reader.CanReadNext()) {
+ PerfFile::AttrsEntry entry;
+ RETURN_IF_ERROR(attr_reader.ReadNext(entry));
+
+ if (entry.ids.size % sizeof(uint64_t) != 0) {
+ return base::ErrStatus("Invalid id section size: %" PRIu64,
+ entry.ids.size);
+ }
+
+ tbv = buffer_.SliceOff(entry.ids.offset, entry.ids.size);
+ if (!tbv) {
+ return ParsingResult::kMoreDataNeeded;
+ }
+
+ std::vector<uint64_t> ids;
+ ids.resize(entry.ids.size / sizeof(uint64_t));
+ PERFETTO_CHECK(Reader(std::move(*tbv)).ReadVector(ids));
+
+ builder.AddAttrAndIds(entry.attr, std::move(ids));
}
- return ParsingResult::Success;
+
+ ASSIGN_OR_RETURN(perf_session_, builder.Build());
+ parsing_state_ = ParsingState::kSeekRecords;
+ return ParsingResult::kSuccess;
}
base::StatusOr<PerfDataTokenizer::ParsingResult>
-PerfDataTokenizer::ParseAttrIds() {
- if (!reader_.CanAccessFileRange(ids_start_, ids_end_)) {
- return ParsingResult::NoSpace;
+PerfDataTokenizer::SeekRecords() {
+ if (!buffer_.PopFrontUntil(header_.data.offset)) {
+ return ParsingResult::kMoreDataNeeded;
}
- for (const auto& attr_file : attrs_) {
- reader_.Skip(attr_file.ids.offset - reader_.current_file_offset());
- std::vector<uint64_t> ids(static_cast<size_t>(attr_file.ids.size) /
- sizeof(uint64_t));
- reader_.ReadVector(ids);
- tracker_->PushAttrAndIds({attr_file.attr, std::move(ids)});
- }
- tracker_->ComputeCommonSampleType();
-
- reader_.Skip(header_.data.offset - reader_.current_file_offset());
- parsing_state_ = ParsingState::Records;
- return ParsingResult::Success;
+ parsing_state_ = ParsingState::kParseRecords;
+ return ParsingResult::kSuccess;
}
base::StatusOr<PerfDataTokenizer::ParsingResult>
-PerfDataTokenizer::ParseAttrIdsFromBuffer() {
- // Each attribute points at an array of event ids. In this case, the ids are
- // in |after_header_buffer_|, i.e. the file contents between the header and
- // the start of the attr section.
- for (const auto& attr_file : attrs_) {
- size_t num_ids = static_cast<size_t>(attr_file.ids.size / sizeof(uint64_t));
- std::vector<uint64_t> ids(num_ids);
- size_t rd_offset = static_cast<size_t>(attr_file.ids.offset - ids_start_);
- size_t rd_size = static_cast<size_t>(attr_file.ids.size);
- PERFETTO_CHECK(rd_offset + rd_size <= after_header_buffer_.size());
- memcpy(ids.data(), after_header_buffer_.data() + rd_offset, rd_size);
+PerfDataTokenizer::ParseRecords() {
+ while (buffer_.file_offset() < header_.data.end()) {
+ Record record;
- tracker_->PushAttrAndIds({attr_file.attr, std::move(ids)});
+ if (auto res = ParseRecord(record);
+ !res.ok() || *res != ParsingResult::kSuccess) {
+ return res;
+ }
+
+ if (!PushRecord(std::move(record))) {
+ context_->storage->IncrementStats(stats::perf_record_skipped);
+ }
}
- after_header_buffer_.clear();
- tracker_->ComputeCommonSampleType();
- reader_.Skip(header_.data.offset - reader_.current_file_offset());
- parsing_state_ = ParsingState::Records;
- return ParsingResult::Success;
+ parsing_state_ = ParsingState::kParseFeatureSections;
+ return ParsingResult::kSuccess;
}
-base::StatusOr<PerfDataTracker::Mmap2Record>
-PerfDataTokenizer::ParseMmap2Record(uint64_t record_size) {
- uint64_t start_offset = reader_.current_file_offset();
- PerfDataTracker::Mmap2Record record;
- reader_.Read(record.num);
- std::vector<char> filename_buffer(
- static_cast<size_t>(record_size) -
- sizeof(PerfDataTracker::Mmap2Record::Numeric));
- reader_.ReadVector(filename_buffer);
- if (filename_buffer.back() != '\0') {
- return base::ErrStatus(
- "Invalid MMAP2 record: filename is not null terminated.");
+base::StatusOr<PerfDataTokenizer::ParsingResult> PerfDataTokenizer::ParseRecord(
+ Record& record) {
+ record.session = perf_session_;
+ std::optional<TraceBlobView> tbv =
+ buffer_.SliceOff(buffer_.file_offset(), sizeof(record.header));
+ if (!tbv) {
+ return ParsingResult::kMoreDataNeeded;
}
- record.filename = std::string(filename_buffer.begin(), filename_buffer.end());
- PERFETTO_CHECK(reader_.current_file_offset() == start_offset + record_size);
- return record;
+ PERFETTO_CHECK(Reader(std::move(*tbv)).Read(record.header));
+
+ if (record.header.size < sizeof(record.header)) {
+ return base::ErrStatus("Invalid record size: %" PRIu16, record.header.size);
+ }
+
+ tbv = buffer_.SliceOff(buffer_.file_offset() + sizeof(record.header),
+ record.header.size - sizeof(record.header));
+ if (!tbv) {
+ return ParsingResult::kMoreDataNeeded;
+ }
+
+ record.payload = std::move(*tbv);
+
+ base::StatusOr<RefPtr<const PerfEventAttr>> attr =
+ perf_session_->FindAttrForRecord(record.header, record.payload);
+ if (!attr.ok()) {
+ return base::ErrStatus("Unable to determine perf_event_attr for record. %s",
+ attr.status().c_message());
+ }
+ record.attr = *attr;
+
+ buffer_.PopFrontBytes(record.header.size);
+ return ParsingResult::kSuccess;
}
-bool PerfDataTokenizer::ValidateSample(
- const PerfDataTracker::PerfSample& sample) {
- if (!sample.cpu.has_value() || !sample.ts.has_value() ||
- sample.callchain.empty() || !sample.pid.has_value()) {
- context_->storage->IncrementStats(stats::perf_samples_skipped);
+base::StatusOr<int64_t> PerfDataTokenizer::ToTraceTimestamp(
+ std::optional<uint64_t> time) {
+ base::StatusOr<int64_t> trace_ts =
+ time.has_value()
+ ? context_->clock_tracker->ToTraceTime(
+ protos::pbzero::ClockSnapshot::Clock::MONOTONIC,
+ static_cast<int64_t>(*time))
+ : std::max(latest_timestamp_, context_->sorter->max_timestamp());
+
+ if (PERFETTO_LIKELY(trace_ts.ok())) {
+ latest_timestamp_ = std::max(latest_timestamp_, *trace_ts);
+ }
+
+ return trace_ts;
+}
+
+bool PerfDataTokenizer::PushRecord(Record record) {
+ std::optional<uint64_t> time;
+ if (!ReadTime(record, time)) {
return false;
}
+
+ base::StatusOr<int64_t> trace_ts = ToTraceTimestamp(time);
+ if (!trace_ts.ok()) {
+ return false;
+ }
+
+ switch (record.header.type) {
+ case PERF_RECORD_AUXTRACE_INFO:
+ case PERF_RECORD_AUXTRACE:
+ case PERF_RECORD_AUX:
+ break;
+ default:
+ context_->sorter->PushPerfRecord(*trace_ts, std::move(record));
+ break;
+ }
+
return true;
}
+base::StatusOr<PerfDataTokenizer::ParsingResult>
+PerfDataTokenizer::ParseFeatureSections() {
+ PERFETTO_CHECK(buffer_.file_offset() == header_.data.end());
+ auto tbv = buffer_.SliceOff(feature_headers_section_.offset,
+ feature_headers_section_.size);
+ if (!tbv) {
+ return ParsingResult::kMoreDataNeeded;
+ }
+
+ Reader reader(std::move(*tbv));
+ for (auto feature_id : feature_ids_) {
+ feature_sections_.emplace_back(std::piecewise_construct,
+ std::forward_as_tuple(feature_id),
+ std::forward_as_tuple());
+ PERFETTO_CHECK(reader.Read(feature_sections_.back().second));
+ }
+
+ std::sort(feature_sections_.begin(), feature_sections_.end(),
+ [](const std::pair<uint8_t, PerfFile::Section>& lhs,
+ const std::pair<uint8_t, PerfFile::Section>& rhs) {
+ return lhs.second.offset > rhs.second.offset;
+ });
+
+ buffer_.PopFrontUntil(feature_headers_section_.end());
+ parsing_state_ = feature_sections_.empty() ? ParsingState::kDone
+ : ParsingState::kParseFeatures;
+ return ParsingResult::kSuccess;
+}
+
+base::StatusOr<PerfDataTokenizer::ParsingResult>
+PerfDataTokenizer::ParseFeatures() {
+ while (!feature_sections_.empty()) {
+ const auto feature_id = feature_sections_.back().first;
+ const auto& section = feature_sections_.back().second;
+ auto tbv = buffer_.SliceOff(section.offset, section.size);
+ if (!tbv) {
+ return ParsingResult::kMoreDataNeeded;
+ }
+
+ RETURN_IF_ERROR(ParseFeature(feature_id, std::move(*tbv)));
+ buffer_.PopFrontUntil(section.end());
+ feature_sections_.pop_back();
+ }
+
+ parsing_state_ = ParsingState::kDone;
+ return ParsingResult::kSuccess;
+}
+
+base::Status PerfDataTokenizer::ParseFeature(uint8_t feature_id,
+ TraceBlobView data) {
+ switch (feature_id) {
+ case feature::ID_EVENT_DESC:
+ return feature::EventDescription::Parse(
+ std::move(data), [&](feature::EventDescription desc) {
+ for (auto id : desc.ids) {
+ perf_session_->SetEventName(id, std::move(desc.event_string));
+ }
+ return base::OkStatus();
+ });
+
+ case feature::ID_BUILD_ID:
+ return feature::BuildId::Parse(
+ std::move(data), [](feature::BuildId) { return base::OkStatus(); });
+
+ case feature::ID_GROUP_DESC: {
+ feature::HeaderGroupDesc group_desc;
+ RETURN_IF_ERROR(
+ feature::HeaderGroupDesc::Parse(std::move(data), group_desc));
+ // TODO(carlscab): Do someting
+ break;
+ }
+
+ case feature::ID_SIMPLEPERF_META_INFO: {
+ feature::SimpleperfMetaInfo meta_info;
+ RETURN_IF_ERROR(
+ feature::SimpleperfMetaInfo::Parse(std::move(data), meta_info));
+ for (auto it = meta_info.event_type_info.GetIterator(); it; ++it) {
+ perf_session_->SetEventName(it.key().type, it.key().config, it.value());
+ }
+ break;
+ }
+ case feature::ID_SIMPLEPERF_FILE2: {
+ RETURN_IF_ERROR(feature::ParseSimpleperfFile2(
+ std::move(data), [&](TraceBlobView) { return util::OkStatus(); }));
+
+ break;
+ }
+ default:
+ context_->storage->IncrementIndexedStats(stats::perf_features_skipped,
+ feature_id);
+ }
+
+ return base::OkStatus();
+}
+
void PerfDataTokenizer::NotifyEndOfFile() {}
} // namespace perf_importer
diff --git a/src/trace_processor/importers/perf/perf_data_tokenizer.h b/src/trace_processor/importers/perf/perf_data_tokenizer.h
index 7a54088..61c1308 100644
--- a/src/trace_processor/importers/perf/perf_data_tokenizer.h
+++ b/src/trace_processor/importers/perf/perf_data_tokenizer.h
@@ -18,45 +18,30 @@
#define SRC_TRACE_PROCESSOR_IMPORTERS_PERF_PERF_DATA_TOKENIZER_H_
#include <stdint.h>
-#include "perfetto/base/status.h"
-#include "perfetto/ext/base/status_or.h"
-#include "perfetto/ext/base/string_utils.h"
-#include "perfetto/trace_processor/trace_blob_view.h"
-#include "src/trace_processor/importers/perf/perf_data_reader.h"
-#include "src/trace_processor/importers/perf/perf_data_tracker.h"
-#include "src/trace_processor/importers/perf/perf_event.h"
-
-#include <limits>
-#include <map>
-#include <string>
+#include <cstdint>
+#include <optional>
#include <vector>
+#include "perfetto/base/flat_set.h"
+#include "perfetto/base/status.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/common/chunked_trace_reader.h"
+#include "src/trace_processor/importers/perf/perf_file.h"
+#include "src/trace_processor/importers/perf/perf_session.h"
+#include "src/trace_processor/util/file_buffer.h"
namespace perfetto {
namespace trace_processor {
+class TraceProcessorContext;
+
namespace perf_importer {
-using Section = PerfDataTracker::PerfFileSection;
+struct Record;
class PerfDataTokenizer : public ChunkedTraceReader {
public:
- struct PerfHeader {
- static constexpr char PERF_MAGIC[] = "PERFILE2";
-
- char magic[8];
- uint64_t size;
- // Size of PerfFileAttr struct and section pointing to ids.
- uint64_t attr_size;
- Section attrs;
- Section data;
- Section event_types;
- uint64_t flags;
- uint64_t flags1[3];
-
- uint64_t num_attrs() const { return attrs.size / attr_size; }
- };
-
explicit PerfDataTokenizer(TraceProcessorContext*);
~PerfDataTokenizer() override;
PerfDataTokenizer(const PerfDataTokenizer&) = delete;
@@ -68,39 +53,46 @@
private:
enum class ParsingState {
- Header = 0,
- AfterHeaderBuffer = 1,
- Attrs = 2,
- AttrIds = 3,
- AttrIdsFromBuffer = 4,
- Records = 5
+ kParseHeader,
+ kParseAttrs,
+ kSeekRecords,
+ kParseRecords,
+ kParseFeatureSections,
+ kParseFeatures,
+ kDone,
};
- enum class ParsingResult { NoSpace = 0, Success = 1 };
+ enum class ParsingResult { kMoreDataNeeded = 0, kSuccess = 1 };
base::StatusOr<ParsingResult> ParseHeader();
- base::StatusOr<ParsingResult> ParseAfterHeaderBuffer();
base::StatusOr<ParsingResult> ParseAttrs();
- base::StatusOr<ParsingResult> ParseAttrIds();
- base::StatusOr<ParsingResult> ParseAttrIdsFromBuffer();
+ base::StatusOr<ParsingResult> SeekRecords();
+ base::StatusOr<ParsingResult> ParseRecords();
+ base::StatusOr<ParsingResult> ParseFeatureSections();
+ base::StatusOr<ParsingResult> ParseFeatures();
- base::StatusOr<PerfDataTracker::Mmap2Record> ParseMmap2Record(
- uint64_t record_size);
+ base::StatusOr<PerfDataTokenizer::ParsingResult> ParseRecord(Record& record);
+ bool PushRecord(Record record);
+ base::Status ParseFeature(uint8_t feature_id, TraceBlobView payload);
- bool ValidateSample(const PerfDataTracker::PerfSample&);
+ base::StatusOr<int64_t> ToTraceTimestamp(std::optional<uint64_t> time);
TraceProcessorContext* context_;
- PerfDataTracker* tracker_;
- ParsingState parsing_state_ = ParsingState::Header;
+ ParsingState parsing_state_ = ParsingState::kParseHeader;
- PerfHeader header_;
+ PerfFile::Header header_;
+ base::FlatSet<uint8_t> feature_ids_;
+ PerfFile::Section feature_headers_section_;
+ // Sections for the features present in the perf file sorted by descending
+ // section offset. This is done so that we can pop from the back as we process
+ // the sections.
+ std::vector<std::pair<uint8_t, PerfFile::Section>> feature_sections_;
- std::vector<PerfDataTracker::PerfFileAttr> attrs_;
- uint64_t ids_start_ = std::numeric_limits<uint64_t>::max();
- uint64_t ids_end_ = 0;
- std::vector<uint8_t> after_header_buffer_;
+ RefPtr<PerfSession> perf_session_;
- perf_importer::PerfDataReader reader_;
+ util::FileBuffer buffer_;
+
+ int64_t latest_timestamp_ = 0;
};
} // namespace perf_importer
diff --git a/src/trace_processor/importers/perf/perf_data_tracker.cc b/src/trace_processor/importers/perf/perf_data_tracker.cc
deleted file mode 100644
index 8b3bc47..0000000
--- a/src/trace_processor/importers/perf/perf_data_tracker.cc
+++ /dev/null
@@ -1,182 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "src/trace_processor/importers/perf/perf_data_tracker.h"
-
-#include <optional>
-
-#include "perfetto/base/status.h"
-#include "src/trace_processor/importers/common/address_range.h"
-#include "src/trace_processor/importers/common/mapping_tracker.h"
-#include "src/trace_processor/importers/common/process_tracker.h"
-#include "src/trace_processor/storage/stats.h"
-#include "src/trace_processor/storage/trace_storage.h"
-
-#include "protos/perfetto/trace/profiling/profile_packet.pbzero.h"
-
-namespace perfetto {
-namespace trace_processor {
-namespace perf_importer {
-namespace {
-
-bool IsInKernel(protos::pbzero::Profiling::CpuMode cpu_mode) {
- switch (cpu_mode) {
- case protos::pbzero::Profiling::MODE_UNKNOWN:
- PERFETTO_CHECK(false);
- case protos::pbzero::Profiling::MODE_GUEST_KERNEL:
- case protos::pbzero::Profiling::MODE_KERNEL:
- return true;
- case protos::pbzero::Profiling::MODE_USER:
- case protos::pbzero::Profiling::MODE_HYPERVISOR:
- case protos::pbzero::Profiling::MODE_GUEST_USER:
- return false;
- }
- PERFETTO_CHECK(false);
-}
-
-CreateMappingParams BuildCreateMappingParams(
- PerfDataTracker::Mmap2Record record) {
- return {AddressRange::FromStartAndSize(record.num.addr, record.num.len),
- record.num.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
- // offset is 0.
- 0,
- // load_bias: This can only be read out of the actual ELF file, which
- // we do not have here, so we set it to 0. When symbolizing we will
- // hopefully have the real load bias and we can compensate there for a
- // possible mismatch.
- 0, record.filename, std::nullopt};
-}
-} // namespace
-
-PerfDataTracker::~PerfDataTracker() = default;
-
-uint64_t PerfDataTracker::ComputeCommonSampleType() {
- if (attrs_.empty()) {
- return 0;
- }
- common_sample_type_ = std::numeric_limits<uint64_t>::max();
- for (const auto& a : attrs_) {
- common_sample_type_ &= a.attr.sample_type;
- }
- return common_sample_type_;
-}
-
-const perf_event_attr* PerfDataTracker::FindAttrWithId(uint64_t id) const {
- for (const auto& attr_and_ids : attrs_) {
- if (auto x =
- std::find(attr_and_ids.ids.begin(), attr_and_ids.ids.end(), id);
- x == attr_and_ids.ids.end()) {
- continue;
- }
- return &attr_and_ids.attr;
- }
- return nullptr;
-}
-
-void PerfDataTracker::PushMmap2Record(Mmap2Record record) {
- if (IsInKernel(record.cpu_mode)) {
- context_->mapping_tracker->CreateKernelMemoryMapping(
- BuildCreateMappingParams(std::move(record)));
- } else {
- UniquePid upid =
- context_->process_tracker->GetOrCreateProcess(record.num.pid);
- context_->mapping_tracker->CreateUserMemoryMapping(
- upid, BuildCreateMappingParams(std::move(record)));
- }
-}
-
-base::StatusOr<PerfDataTracker::PerfSample> PerfDataTracker::ParseSample(
- perfetto::trace_processor::perf_importer::PerfDataReader& reader) {
- uint64_t sample_type = common_sample_type();
- PerfDataTracker::PerfSample sample;
-
- if (sample_type & PERF_SAMPLE_IDENTIFIER) {
- reader.ReadOptional(sample.id);
- if (auto attr = FindAttrWithId(*sample.id); attr) {
- sample_type = attr->sample_type;
- } else {
- return base::ErrStatus("No attr for sample_id");
- }
- }
-
- if (sample_type & PERF_SAMPLE_IP) {
- reader.Skip<uint64_t>();
- }
-
- if (sample_type & PERF_SAMPLE_TID) {
- reader.ReadOptional(sample.pid);
- reader.ReadOptional(sample.tid);
- }
-
- if (sample_type & PERF_SAMPLE_TIME) {
- reader.ReadOptional(sample.ts);
- }
-
- // Ignored. Checked because we need to access later parts of sample.
- if (sample_type & PERF_SAMPLE_ADDR) {
- reader.Skip<uint64_t>();
- }
-
- // The same value as PERF_SAMPLE_IDENTIFIER, so should be ignored.
- if (sample_type & PERF_SAMPLE_ID) {
- reader.Skip<uint64_t>();
- }
-
- // Ignored. Checked because we need to access later parts of sample.
- if (sample_type & PERF_SAMPLE_STREAM_ID) {
- reader.Skip<uint64_t>();
- }
-
- if (sample_type & PERF_SAMPLE_CPU) {
- reader.ReadOptional(sample.cpu);
- // Ignore next uint32_t res.
- reader.Skip<uint32_t>();
- }
-
- // Ignored. Checked because we need to access later parts of sample.
- if (sample_type & PERF_SAMPLE_PERIOD) {
- reader.Skip<uint64_t>();
- }
-
- // Ignored.
- // TODO(mayzner): Implement.
- if (sample_type & PERF_SAMPLE_READ) {
- context_->storage->IncrementStats(stats::perf_samples_skipped);
- return base::ErrStatus("PERF_SAMPLE_READ is not supported");
- }
-
- if (sample_type & PERF_SAMPLE_CALLCHAIN) {
- uint64_t vec_size;
- reader.Read(vec_size);
-
- sample.callchain.resize(static_cast<size_t>(vec_size));
- reader.ReadVector(sample.callchain);
- }
-
- return sample;
-}
-
-PerfDataTracker* PerfDataTracker::GetOrCreate(TraceProcessorContext* context) {
- if (!context->perf_data_tracker) {
- context->perf_data_tracker.reset(new PerfDataTracker(context));
- }
- return static_cast<PerfDataTracker*>(context->perf_data_tracker.get());
-}
-} // namespace perf_importer
-} // namespace trace_processor
-} // namespace perfetto
diff --git a/src/trace_processor/importers/perf/perf_data_tracker.h b/src/trace_processor/importers/perf/perf_data_tracker.h
deleted file mode 100644
index c96b08d..0000000
--- a/src/trace_processor/importers/perf/perf_data_tracker.h
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PERF_PERF_DATA_TRACKER_H_
-#define SRC_TRACE_PROCESSOR_IMPORTERS_PERF_PERF_DATA_TRACKER_H_
-
-#include <cstdint>
-#include <string>
-#include <vector>
-#include "perfetto/base/logging.h"
-#include "perfetto/base/status.h"
-#include "perfetto/ext/base/flat_hash_map.h"
-#include "perfetto/ext/base/status_or.h"
-#include "perfetto/ext/base/string_utils.h"
-#include "protos/perfetto/trace/profiling/profile_packet.pbzero.h"
-#include "src/trace_processor/importers/perf/perf_data_reader.h"
-#include "src/trace_processor/importers/perf/perf_event.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 {
-namespace trace_processor {
-namespace perf_importer {
-
-using MappingTable = tables::StackProfileMappingTable;
-
-class PerfDataTracker : public Destructible {
- public:
- struct PerfFileSection {
- uint64_t offset;
- uint64_t size;
-
- uint64_t end() const { return offset + size; }
- };
- struct PerfFileAttr {
- perf_event_attr attr;
- PerfFileSection ids;
- };
- struct AttrAndIds {
- perf_event_attr attr;
- std::vector<uint64_t> ids;
- };
- struct PerfSample {
- std::optional<uint64_t> id = 0;
- std::optional<uint32_t> pid = 0;
- std::optional<uint32_t> tid = 0;
- std::optional<uint64_t> ts = 0;
- std::optional<uint32_t> cpu = 0;
- std::vector<uint64_t> callchain;
- };
- struct Mmap2Record {
- struct Numeric {
- uint32_t pid;
- uint32_t tid;
- uint64_t addr;
- uint64_t len;
- uint64_t pgoff;
- uint32_t maj;
- uint32_t min;
- uint64_t ino;
- uint64_t ino_generation;
- uint32_t prot;
- uint32_t flags;
- };
- protos::pbzero::Profiling::CpuMode cpu_mode;
- Numeric num;
- std::string filename;
- };
-
- PerfDataTracker(const PerfDataTracker&) = delete;
- PerfDataTracker& operator=(const PerfDataTracker&) = delete;
- explicit PerfDataTracker(TraceProcessorContext* context)
- : context_(context) {}
- ~PerfDataTracker() override;
- static PerfDataTracker* GetOrCreate(TraceProcessorContext* context);
-
- uint64_t ComputeCommonSampleType();
-
- void PushAttrAndIds(AttrAndIds data) { attrs_.push_back(std::move(data)); }
-
- void PushMmap2Record(Mmap2Record record);
-
- uint64_t common_sample_type() { return common_sample_type_; }
-
- base::StatusOr<PerfSample> ParseSample(
- perfetto::trace_processor::perf_importer::PerfDataReader&);
-
- private:
- const perf_event_attr* FindAttrWithId(uint64_t id) const;
- TraceProcessorContext* context_;
- std::vector<AttrAndIds> attrs_;
-
- uint64_t common_sample_type_;
-};
-} // namespace perf_importer
-} // namespace trace_processor
-} // namespace perfetto
-
-#endif // SRC_TRACE_PROCESSOR_IMPORTERS_PERF_PERF_DATA_TRACKER_H_
diff --git a/src/trace_processor/importers/perf/perf_data_tracker_unittest.cc b/src/trace_processor/importers/perf/perf_data_tracker_unittest.cc
deleted file mode 100644
index 923473f..0000000
--- a/src/trace_processor/importers/perf/perf_data_tracker_unittest.cc
+++ /dev/null
@@ -1,243 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "src/trace_processor/importers/perf/perf_data_tracker.h"
-
-#include <stddef.h>
-#include <cstring>
-#include <memory>
-#include <vector>
-
-#include "perfetto/base/build_config.h"
-#include "protos/perfetto/trace/profiling/profile_packet.pbzero.h"
-#include "src/trace_processor/importers/common/address_range.h"
-#include "src/trace_processor/importers/common/mapping_tracker.h"
-#include "src/trace_processor/importers/common/process_tracker.h"
-#include "src/trace_processor/importers/common/stack_profile_tracker.h"
-#include "src/trace_processor/importers/perf/perf_event.h"
-#include "test/gtest_and_gmock.h"
-
-namespace perfetto {
-namespace trace_processor {
-namespace perf_importer {
-namespace {
-
-class PerfDataTrackerUnittest : public testing::Test {
- public:
- PerfDataTrackerUnittest() {
- context_.storage = std::make_unique<TraceStorage>();
- context_.process_tracker = std::make_unique<ProcessTracker>(&context_);
- context_.stack_profile_tracker =
- std::make_unique<StackProfileTracker>(&context_);
- context_.mapping_tracker = std::make_unique<MappingTracker>(&context_);
- }
-
- protected:
- TraceProcessorContext context_;
-};
-
-TEST_F(PerfDataTrackerUnittest, ComputeCommonSampleType) {
- PerfDataTracker* tracker = PerfDataTracker::GetOrCreate(&context_);
-
- PerfDataTracker::AttrAndIds attr_and_ids;
- attr_and_ids.attr.sample_type =
- PERF_SAMPLE_CALLCHAIN | PERF_SAMPLE_CPU | PERF_SAMPLE_TIME;
- tracker->PushAttrAndIds(attr_and_ids);
-
- attr_and_ids.attr.sample_type = PERF_SAMPLE_ADDR | PERF_SAMPLE_CPU;
- tracker->PushAttrAndIds(attr_and_ids);
-
- tracker->ComputeCommonSampleType();
- EXPECT_TRUE(tracker->common_sample_type() & PERF_SAMPLE_CPU);
- EXPECT_FALSE(tracker->common_sample_type() & PERF_SAMPLE_CALLCHAIN);
-}
-
-TEST_F(PerfDataTrackerUnittest, FindMapping) {
- PerfDataTracker* tracker = PerfDataTracker::GetOrCreate(&context_);
-
- PerfDataTracker::Mmap2Record rec;
- rec.cpu_mode = protos::pbzero::Profiling::MODE_USER;
- rec.filename = "file1";
- rec.num.addr = 1000;
- rec.num.len = 100;
- rec.num.pid = 1;
- rec.cpu_mode = protos::pbzero::Profiling::MODE_USER;
- tracker->PushMmap2Record(rec);
-
- rec.num.addr = 2000;
- tracker->PushMmap2Record(rec);
-
- rec.num.addr = 3000;
- tracker->PushMmap2Record(rec);
-
- UserMemoryMapping* mapping =
- context_.mapping_tracker->FindUserMappingForAddress(
- context_.process_tracker->GetOrCreateProcess(1), 2050);
- ASSERT_NE(mapping, nullptr);
- EXPECT_EQ(mapping->memory_range().start(), 2000u);
- EXPECT_EQ(mapping->memory_range().end(), 2100u);
-}
-
-TEST_F(PerfDataTrackerUnittest, FindMappingFalse) {
- PerfDataTracker* tracker = PerfDataTracker::GetOrCreate(&context_);
-
- PerfDataTracker::Mmap2Record rec;
- rec.cpu_mode = protos::pbzero::Profiling::MODE_USER;
- rec.filename = "file1";
- rec.num.addr = 1000;
- rec.num.len = 100;
- rec.num.pid = 1;
- rec.cpu_mode = protos::pbzero::Profiling::MODE_USER;
- tracker->PushMmap2Record(rec);
-
- UserMemoryMapping* mapping =
- context_.mapping_tracker->FindUserMappingForAddress(
- context_.process_tracker->GetOrCreateProcess(2), 2050);
- EXPECT_EQ(mapping, nullptr);
-}
-
-TEST_F(PerfDataTrackerUnittest, ParseSampleTrivial) {
- PerfDataTracker* tracker = PerfDataTracker::GetOrCreate(&context_);
-
- PerfDataTracker::AttrAndIds attr_and_ids;
- attr_and_ids.attr.sample_type = PERF_SAMPLE_TIME;
- tracker->PushAttrAndIds(attr_and_ids);
- tracker->ComputeCommonSampleType();
-
- uint64_t ts = 100;
-
- TraceBlob blob =
- TraceBlob::CopyFrom(static_cast<const void*>(&ts), sizeof(uint64_t));
- PerfDataReader reader(TraceBlobView(std::move(blob)));
-
- auto parsed_sample = tracker->ParseSample(reader);
- EXPECT_TRUE(parsed_sample.ok());
- EXPECT_EQ(parsed_sample->ts, 100u);
-}
-
-TEST_F(PerfDataTrackerUnittest, ParseSampleCallchain) {
- PerfDataTracker* tracker = PerfDataTracker::GetOrCreate(&context_);
-
- PerfDataTracker::AttrAndIds attr_and_ids;
- attr_and_ids.attr.sample_type = PERF_SAMPLE_CALLCHAIN;
- tracker->PushAttrAndIds(attr_and_ids);
- tracker->ComputeCommonSampleType();
-
- struct Sample {
- uint64_t callchain_size; /* if PERF_SAMPLE_CALLCHAIN */
- std::vector<uint64_t> callchain; /* if PERF_SAMPLE_CALLCHAIN */
- };
-
- Sample sample;
- sample.callchain_size = 3;
- sample.callchain = std::vector<uint64_t>{1, 2, 3};
-
- TraceBlob blob = TraceBlob::Allocate(4 * sizeof(uint64_t));
- memcpy(blob.data(), &sample.callchain_size, sizeof(uint64_t));
- memcpy(blob.data() + sizeof(uint64_t), sample.callchain.data(),
- sizeof(uint64_t) * 3);
- PerfDataReader reader(TraceBlobView(std::move(blob)));
-
- auto parsed_sample = tracker->ParseSample(reader);
- EXPECT_TRUE(parsed_sample.ok());
- EXPECT_EQ(parsed_sample->callchain.size(), 3u);
-}
-
-TEST_F(PerfDataTrackerUnittest, ParseSampleWithoutId) {
- PerfDataTracker* tracker = PerfDataTracker::GetOrCreate(&context_);
-
- PerfDataTracker::AttrAndIds attr_and_ids;
- attr_and_ids.attr.sample_type = PERF_SAMPLE_TID | PERF_SAMPLE_TIME |
- PERF_SAMPLE_CPU | PERF_SAMPLE_CALLCHAIN;
- tracker->PushAttrAndIds(attr_and_ids);
- tracker->ComputeCommonSampleType();
-
- struct Sample {
- uint32_t pid; /* if PERF_SAMPLE_TID */
- uint32_t tid; /* if PERF_SAMPLE_TID */
- uint64_t ts; /* if PERF_SAMPLE_TIME */
- uint32_t cpu; /* if PERF_SAMPLE_CPU */
- uint32_t res_ignore; /* if PERF_SAMPLE_CPU */
- uint64_t callchain_size; /* if PERF_SAMPLE_CALLCHAIN */
- };
-
- Sample sample;
- sample.pid = 2;
- sample.ts = 100;
- sample.cpu = 1;
- sample.callchain_size = 3;
- std::vector<uint64_t> callchain{1, 2, 3};
-
- TraceBlob blob = TraceBlob::Allocate(sizeof(Sample) + sizeof(uint64_t) * 3);
- memcpy(blob.data(), &sample, sizeof(Sample));
- memcpy(blob.data() + sizeof(Sample), callchain.data(), sizeof(uint64_t) * 3);
-
- PerfDataReader reader(TraceBlobView(std::move(blob)));
- EXPECT_TRUE(reader.CanReadSize(sizeof(Sample)));
-
- auto parsed_sample = tracker->ParseSample(reader);
- EXPECT_TRUE(parsed_sample.ok());
- EXPECT_EQ(parsed_sample->callchain.size(), 3u);
- EXPECT_EQ(sample.ts, parsed_sample->ts);
-}
-
-TEST_F(PerfDataTrackerUnittest, ParseSampleWithId) {
- PerfDataTracker* tracker = PerfDataTracker::GetOrCreate(&context_);
-
- PerfDataTracker::AttrAndIds attr_and_ids;
- attr_and_ids.attr.sample_type = PERF_SAMPLE_CPU | PERF_SAMPLE_TID |
- PERF_SAMPLE_IDENTIFIER | PERF_SAMPLE_ID |
- PERF_SAMPLE_CALLCHAIN | PERF_SAMPLE_TIME;
- attr_and_ids.ids.push_back(10);
- tracker->PushAttrAndIds(attr_and_ids);
- tracker->ComputeCommonSampleType();
-
- struct Sample {
- uint64_t identifier; /* if PERF_SAMPLE_IDENTIFIER */
- uint32_t pid; /* if PERF_SAMPLE_TID */
- uint32_t tid; /* if PERF_SAMPLE_TID */
- uint64_t ts; /* if PERF_SAMPLE_TIME */
- uint64_t id; /* if PERF_SAMPLE_ID */
- uint32_t cpu; /* if PERF_SAMPLE_CPU */
- uint32_t res_ignore; /* if PERF_SAMPLE_CPU */
- uint64_t callchain_size; /* if PERF_SAMPLE_CALLCHAIN */
- };
-
- Sample sample;
- sample.id = 10;
- sample.identifier = 10;
- sample.cpu = 1;
- sample.pid = 2;
- sample.ts = 100;
- sample.callchain_size = 3;
- std::vector<uint64_t> callchain{1, 2, 3};
-
- TraceBlob blob = TraceBlob::Allocate(sizeof(Sample) + sizeof(uint64_t) * 3);
- memcpy(blob.data(), &sample, sizeof(Sample));
- memcpy(blob.data() + sizeof(Sample), callchain.data(), sizeof(uint64_t) * 3);
-
- PerfDataReader reader(TraceBlobView(std::move(blob)));
-
- auto parsed_sample = tracker->ParseSample(reader);
- EXPECT_TRUE(parsed_sample.ok());
- EXPECT_EQ(parsed_sample->callchain.size(), 3u);
- EXPECT_EQ(100u, parsed_sample->ts);
-}
-
-} // namespace
-} // namespace perf_importer
-} // namespace trace_processor
-} // namespace perfetto
diff --git a/src/trace_processor/importers/perf/perf_event.h b/src/trace_processor/importers/perf/perf_event.h
index 4763e23..58077be 100644
--- a/src/trace_processor/importers/perf/perf_event.h
+++ b/src/trace_processor/importers/perf/perf_event.h
@@ -238,6 +238,7 @@
PERF_RECORD_MISC_GUEST_USER = 5,
PERF_RECORD_MISC_MMAP_BUILD_ID = 1U << 14,
+ PERF_RECORD_MISC_EXT_RESERVED = 1U << 15,
};
enum perf_event_read_format {
@@ -250,7 +251,7 @@
PERF_FORMAT_MAX = 1U << 5, /* non-ABI */
};
-enum perf_callchain_context {
+enum perf_callchain_context : uint64_t {
PERF_CONTEXT_HV = static_cast<uint64_t>(-32),
PERF_CONTEXT_KERNEL = static_cast<uint64_t>(-128),
PERF_CONTEXT_USER = static_cast<uint64_t>(-512),
diff --git a/src/trace_processor/importers/perf/perf_event_attr.cc b/src/trace_processor/importers/perf/perf_event_attr.cc
index 1abf8d0..03fe71f 100644
--- a/src/trace_processor/importers/perf/perf_event_attr.cc
+++ b/src/trace_processor/importers/perf/perf_event_attr.cc
@@ -21,7 +21,11 @@
#include <cstring>
#include <optional>
+#include "perfetto/ext/base/string_view.h"
+#include "src/trace_processor/importers/perf/perf_counter.h"
#include "src/trace_processor/importers/perf/perf_event.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/types/trace_processor_context.h"
namespace perfetto::trace_processor::perf_importer {
@@ -90,11 +94,43 @@
}
} // namespace
-PerfEventAttr::PerfEventAttr(perf_event_attr attr)
- : attr_(std::move(attr)),
+PerfEventAttr::PerfEventAttr(TraceProcessorContext* context,
+ uint32_t perf_session_id,
+ perf_event_attr attr)
+ : context_(context),
+ perf_session_id_(perf_session_id),
+ attr_(std::move(attr)),
time_offset_from_start_(TimeOffsetFromStartOfSampleRecord(attr_)),
time_offset_from_end_(TimeOffsetFromEndOfNonSampleRecord(attr_)),
id_offset_from_start_(IdOffsetFromStartOfSampleRecord(attr_)),
id_offset_from_end_(IdOffsetFromEndOfNonSampleRecord(attr_)) {}
+PerfEventAttr::~PerfEventAttr() = default;
+
+PerfCounter& PerfEventAttr::GetOrCreateCounter(uint32_t cpu) const {
+ auto it = counters_.find(cpu);
+ if (it == counters_.end()) {
+ it = counters_.emplace(cpu, CreateCounter(cpu)).first;
+ }
+ return it->second;
+}
+
+PerfCounter PerfEventAttr::CreateCounter(uint32_t cpu) const {
+ return PerfCounter(
+ context_->storage->mutable_counter_table(),
+ context_->storage->mutable_perf_counter_track_table()
+ ->Insert({/*in_name=*/context_->storage->InternString(
+ base::StringView(event_name_)),
+ /*in_parent_id=*/std::nullopt,
+ /*in_source_arg_set_id=*/std::nullopt,
+ /*in_machine_id=*/std::nullopt,
+ /*in_unit=*/
+ context_->storage->InternString(base::StringView("")),
+ /*in_description=*/
+ context_->storage->InternString(base::StringView("")),
+ /*in_perf_session_id=*/perf_session_id_, /*in_cpu=*/cpu,
+ /*in_is_timebase=*/is_timebase()})
+ .row_reference);
+}
+
} // namespace perfetto::trace_processor::perf_importer
diff --git a/src/trace_processor/importers/perf/perf_event_attr.h b/src/trace_processor/importers/perf/perf_event_attr.h
index 4f219aa..f1af037 100644
--- a/src/trace_processor/importers/perf/perf_event_attr.h
+++ b/src/trace_processor/importers/perf/perf_event_attr.h
@@ -21,17 +21,27 @@
#include <cstddef>
#include <cstdint>
#include <optional>
+#include <unordered_map>
-#include "perfetto/ext/base/string_view.h"
#include "perfetto/trace_processor/ref_counted.h"
+#include "src/trace_processor/importers/perf/perf_counter.h"
#include "src/trace_processor/importers/perf/perf_event.h"
-namespace perfetto::trace_processor::perf_importer {
+namespace perfetto::trace_processor {
+
+class TraceProcessorContext;
+
+namespace perf_importer {
// Wrapper around a `perf_event_attr` object that add some helper methods.
class PerfEventAttr : public RefCounted {
public:
- explicit PerfEventAttr(perf_event_attr attr);
+ PerfEventAttr(TraceProcessorContext* context,
+ uint32_t perf_session_id_,
+ perf_event_attr attr);
+ ~PerfEventAttr();
+ uint32_t type() const { return attr_.type; }
+ uint64_t config() const { return attr_.config; }
uint64_t sample_type() const { return attr_.sample_type; }
uint64_t read_format() const { return attr_.read_format; }
bool sample_id_all() const { return !!attr_.sample_id_all; }
@@ -48,12 +58,6 @@
return attr_.freq ? std::make_optional(attr_.sample_freq) : std::nullopt;
}
- bool is_timebase() const {
- // This is what simpleperf uses for events that are not supposed to sample
- // TODO(b/334978369): Determine if there is a better way to figure this out.
- return attr_.sample_period < (1ull << 62);
- }
-
// Offset from the end of a record's payload to the time filed (if present).
// To be used with non `PERF_RECORD_SAMPLE` records
std::optional<size_t> time_offset_from_end() const {
@@ -81,14 +85,33 @@
return id_offset_from_end_;
}
+ void set_event_name(std::string event_name) {
+ event_name_ = std::move(event_name);
+ }
+
+ PerfCounter& GetOrCreateCounter(uint32_t cpu) const;
+
private:
+ bool is_timebase() const {
+ // This is what simpleperf uses for events that are not supposed to sample
+ // TODO(b/334978369): Determine if there is a better way to figure this out.
+ return attr_.sample_period < (1ull << 62);
+ }
+
+ PerfCounter CreateCounter(uint32_t cpu) const;
+
+ TraceProcessorContext* const context_;
+ uint32_t perf_session_id_;
perf_event_attr attr_;
std::optional<size_t> time_offset_from_start_;
std::optional<size_t> time_offset_from_end_;
std::optional<size_t> id_offset_from_start_;
std::optional<size_t> id_offset_from_end_;
+ mutable std::unordered_map<uint32_t, PerfCounter> counters_;
+ std::string event_name_;
};
-} // namespace perfetto::trace_processor::perf_importer
+} // namespace perf_importer
+} // namespace perfetto::trace_processor
#endif // SRC_TRACE_PROCESSOR_IMPORTERS_PERF_PERF_EVENT_ATTR_H_
diff --git a/src/trace_processor/importers/perf/perf_session.cc b/src/trace_processor/importers/perf/perf_session.cc
index 86d983d..db42ae7 100644
--- a/src/trace_processor/importers/perf/perf_session.cc
+++ b/src/trace_processor/importers/perf/perf_session.cc
@@ -46,11 +46,13 @@
return base::ErrStatus("No perf_event_attr");
}
- const PerfEventAttr base_attr(attr_with_ids_[0].attr);
+ const PerfEventAttr base_attr(context_, perf_session_id_,
+ attr_with_ids_[0].attr);
base::FlatHashMap<uint64_t, RefPtr<PerfEventAttr>> attrs_by_id;
for (const auto& entry : attr_with_ids_) {
- RefPtr<PerfEventAttr> attr(new PerfEventAttr(entry.attr));
+ RefPtr<PerfEventAttr> attr(
+ new PerfEventAttr(context_, perf_session_id_, entry.attr));
if (base_attr.sample_id_all() != attr->sample_id_all()) {
return base::ErrStatus(
"perf_event_attr with different sample_id_all values");
@@ -133,4 +135,22 @@
return RefPtr<const PerfEventAttr>(it->get());
}
+void PerfSession::SetEventName(uint64_t event_id, std::string name) {
+ auto it = attrs_by_id_.Find(event_id);
+ if (!it) {
+ return;
+ }
+ (*it)->set_event_name(std::move(name));
+}
+
+void PerfSession::SetEventName(uint32_t type,
+ uint64_t config,
+ const std::string& name) {
+ for (auto it = attrs_by_id_.GetIterator(); it; ++it) {
+ if (it.value()->type() == type && it.value()->config() == config) {
+ it.value()->set_event_name(name);
+ }
+ }
+}
+
} // 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 abea41b..c2a9e66 100644
--- a/src/trace_processor/importers/perf/perf_session.h
+++ b/src/trace_processor/importers/perf/perf_session.h
@@ -20,7 +20,6 @@
#include <sys/types.h>
#include <cstddef>
#include <cstdint>
-#include <optional>
#include "perfetto/ext/base/flat_hash_map.h"
#include "perfetto/ext/base/status_or.h"
@@ -29,15 +28,19 @@
#include "src/trace_processor/importers/perf/perf_event.h"
#include "src/trace_processor/importers/perf/perf_event_attr.h"
-namespace perfetto::trace_processor::perf_importer {
+namespace perfetto::trace_processor {
+
+class TraceProcessorContext;
+
+namespace perf_importer {
// Helper to deal with perf_event_attr instances in a perf file.
class PerfSession : public RefCounted {
public:
class Builder {
public:
- explicit Builder(uint32_t perf_session_id)
- : perf_session_id_(perf_session_id) {}
+ Builder(TraceProcessorContext* context, uint32_t perf_session_id)
+ : context_(context), perf_session_id_(perf_session_id) {}
base::StatusOr<RefPtr<PerfSession>> Build();
Builder& AddAttrAndIds(perf_event_attr attr, std::vector<uint64_t> ids) {
attr_with_ids_.push_back({std::move(attr), std::move(ids)});
@@ -50,6 +53,7 @@
std::vector<uint64_t> ids;
};
+ TraceProcessorContext* const context_;
uint32_t perf_session_id_;
std::vector<PerfEventAttrWithIds> attr_with_ids_;
};
@@ -62,6 +66,9 @@
const perf_event_header& header,
const TraceBlobView& payload) const;
+ void SetEventName(uint64_t event_id, std::string name);
+ void SetEventName(uint32_t type, uint64_t config, const std::string& name);
+
private:
PerfSession(uint32_t perf_session_id,
base::FlatHashMap<uint64_t, RefPtr<PerfEventAttr>> attrs_by_id,
@@ -83,6 +90,7 @@
bool has_single_perf_event_attr_;
};
-} // namespace perfetto::trace_processor::perf_importer
+} // namespace perf_importer
+} // namespace perfetto::trace_processor
#endif // SRC_TRACE_PROCESSOR_IMPORTERS_PERF_PERF_SESSION_H_
diff --git a/src/trace_processor/importers/perf/perf_session_unittest.cc b/src/trace_processor/importers/perf/perf_session_unittest.cc
index 0ab85f2..fd6bfa3 100644
--- a/src/trace_processor/importers/perf/perf_session_unittest.cc
+++ b/src/trace_processor/importers/perf/perf_session_unittest.cc
@@ -41,12 +41,12 @@
}
TEST(PerfSessionTest, NoAttrBuildFails) {
- PerfSession::Builder builder(0);
+ PerfSession::Builder builder(nullptr, 0);
EXPECT_FALSE(builder.Build().ok());
}
TEST(PerfSessionTest, OneAttrAndNoIdBuildSucceeds) {
- PerfSession::Builder builder(0);
+ PerfSession::Builder builder(nullptr, 0);
perf_event_attr attr;
attr.sample_id_all = false;
attr.sample_type = PERF_SAMPLE_CALLCHAIN | PERF_SAMPLE_CPU | PERF_SAMPLE_TIME;
@@ -61,7 +61,7 @@
}
TEST(PerfSessionTest, MultipleAttrsAndNoIdBuildFails) {
- PerfSession::Builder builder(0);
+ PerfSession::Builder builder(nullptr, 0);
perf_event_attr attr;
attr.sample_id_all = true;
attr.sample_type = PERF_SAMPLE_CALLCHAIN | PERF_SAMPLE_CPU | PERF_SAMPLE_TIME;
@@ -71,7 +71,7 @@
}
TEST(PerfSessionTest, MultipleIdsSameAttrAndNoIdCanExtractAttrFromRecord) {
- PerfSession::Builder builder(0);
+ PerfSession::Builder builder(nullptr, 0);
perf_event_attr attr;
attr.sample_id_all = true;
attr.sample_type = PERF_SAMPLE_IP | PERF_SAMPLE_CPU | PERF_SAMPLE_TIME;
@@ -95,7 +95,7 @@
}
TEST(PerfSessionTest, NoCommonSampleIdAllBuildFails) {
- PerfSession::Builder builder(0);
+ PerfSession::Builder builder(nullptr, 0);
perf_event_attr attr;
attr.sample_id_all = true;
attr.sample_type = PERF_SAMPLE_IDENTIFIER;
@@ -111,7 +111,7 @@
}
TEST(PerfSessionTest, NoCommonOffsetForSampleBuildFails) {
- PerfSession::Builder builder(0);
+ PerfSession::Builder builder(nullptr, 0);
perf_event_attr attr;
attr.sample_id_all = true;
attr.sample_type = PERF_SAMPLE_IP | PERF_SAMPLE_ID;
@@ -122,7 +122,7 @@
}
TEST(PerfSessionTest, NoCommonOffsetForNonSampleBuildFails) {
- PerfSession::Builder builder(0);
+ PerfSession::Builder builder(nullptr, 0);
perf_event_attr attr;
attr.sample_id_all = true;
attr.sample_type = PERF_SAMPLE_ID | PERF_SAMPLE_TID;
@@ -138,7 +138,7 @@
}
TEST(PerfSessionTest, NoCommonOffsetForNonSampleAndNoSampleIdAllBuildSucceeds) {
- PerfSession::Builder builder(0);
+ PerfSession::Builder builder(nullptr, 0);
perf_event_attr attr;
attr.sample_id_all = false;
attr.sample_type = PERF_SAMPLE_IDENTIFIER | PERF_SAMPLE_TID;
@@ -149,7 +149,7 @@
}
TEST(PerfSessionTest, MultiplesessionBuildSucceeds) {
- PerfSession::Builder builder(0);
+ PerfSession::Builder builder(nullptr, 0);
perf_event_attr attr;
attr.sample_id_all = true;
attr.sample_type = PERF_SAMPLE_IP | PERF_SAMPLE_ID;
@@ -159,7 +159,7 @@
}
TEST(PerfSessionTest, FindAttrInRecordWithId) {
- PerfSession::Builder builder(0);
+ PerfSession::Builder builder(nullptr, 0);
perf_event_attr attr;
attr.sample_id_all = true;
attr.sample_type = PERF_SAMPLE_IP | PERF_SAMPLE_ID;
@@ -194,7 +194,7 @@
}
TEST(PerfSessionTest, FindAttrInRecordWithIdentifier) {
- PerfSession::Builder builder(0);
+ PerfSession::Builder builder(nullptr, 0);
perf_event_attr attr;
attr.sample_id_all = true;
attr.sample_type = PERF_SAMPLE_IDENTIFIER | PERF_SAMPLE_IP;
diff --git a/src/trace_processor/importers/perf/reader.h b/src/trace_processor/importers/perf/reader.h
index faf31a2..91a4253 100644
--- a/src/trace_processor/importers/perf/reader.h
+++ b/src/trace_processor/importers/perf/reader.h
@@ -26,7 +26,9 @@
#include <type_traits>
#include <vector>
+#include "perfetto/ext/base/string_view.h"
#include "perfetto/trace_processor/trace_blob_view.h"
+#include "src/trace_processor/importers/perf/perf_event.h"
namespace perfetto::trace_processor::perf_importer {
@@ -45,6 +47,49 @@
// methods are called.
size_t size_left() const { return static_cast<size_t>(end_ - current_); }
+ bool ReadStringView(base::StringView& str, size_t size) {
+ if (size_left() < size) {
+ return false;
+ }
+ str = base::StringView(reinterpret_cast<const char*>(current_), size);
+ current_ += size;
+ return true;
+ }
+
+ bool ReadPerfEventAttr(perf_event_attr& attr, size_t attr_size) {
+ const size_t bytes_to_read = std::min(attr_size, sizeof(attr));
+ const size_t bytes_to_skip = attr_size - bytes_to_read;
+ static_assert(std::has_unique_object_representations_v<perf_event_attr>);
+
+ if (size_left() < bytes_to_read + bytes_to_skip) {
+ return false;
+ }
+
+ memset(&attr, 0, sizeof(attr));
+
+ return Read(&attr, bytes_to_read) && Skip(bytes_to_skip);
+ }
+
+ bool ReadBlob(TraceBlobView& blob, uint32_t size) {
+ if (size_left() < size) {
+ return false;
+ }
+ blob = TraceBlobView(buffer_, static_cast<size_t>(end_ - current_), size);
+ current_ += size;
+ return true;
+ }
+
+ bool ReadStringUntilEndOrNull(std::string& out) {
+ const uint8_t* ptr = current_;
+ while (ptr != end_ && *ptr != 0) {
+ ++ptr;
+ }
+ out = std::string(reinterpret_cast<const char*>(current_),
+ static_cast<size_t>(ptr - current_));
+ current_ = ptr;
+ return true;
+ }
+
template <typename T>
bool Read(T& obj) {
static_assert(std::has_unique_object_representations_v<T>);
diff --git a/src/trace_processor/importers/perf/record_parser.cc b/src/trace_processor/importers/perf/record_parser.cc
new file mode 100644
index 0000000..7371adf
--- /dev/null
+++ b/src/trace_processor/importers/perf/record_parser.cc
@@ -0,0 +1,310 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/trace_processor/importers/perf/record_parser.h"
+
+#include <cstdint>
+#include <optional>
+#include <string>
+#include <vector>
+
+#include "perfetto/base/logging.h"
+#include "perfetto/base/status.h"
+#include "perfetto/ext/base/string_view.h"
+#include "perfetto/public/compiler.h"
+#include "perfetto/trace_processor/ref_counted.h"
+#include "src/trace_processor/importers/common/mapping_tracker.h"
+#include "src/trace_processor/importers/common/process_tracker.h"
+#include "src/trace_processor/importers/common/stack_profile_tracker.h"
+#include "src/trace_processor/importers/perf/perf_counter.h"
+#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/importers/perf/record.h"
+#include "src/trace_processor/importers/perf/sample.h"
+#include "src/trace_processor/importers/proto/perf_sample_tracker.h"
+#include "src/trace_processor/importers/proto/profile_packet_utils.h"
+#include "src/trace_processor/storage/stats.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/tables/profiler_tables_py.h"
+#include "src/trace_processor/util/build_id.h"
+#include "src/trace_processor/util/status_macros.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace perf_importer {
+namespace {
+
+CreateMappingParams BuildCreateMappingParams(
+ const CommonMmapRecordFields& fields,
+ std::string filename,
+ std::optional<BuildId> build_id = std::nullopt) {
+ 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
+ // offset is 0.
+ 0,
+ // load_bias: This can only be read out of the actual ELF file, which
+ // we do not have here, so we set it to 0. When symbolizing we will
+ // hopefully have the real load bias and we can compensate there for a
+ // possible mismatch.
+ 0, std::move(filename), std::move(build_id)};
+}
+
+bool IsInKernel(protos::pbzero::Profiling::CpuMode cpu_mode) {
+ switch (cpu_mode) {
+ case protos::pbzero::Profiling::MODE_UNKNOWN:
+ PERFETTO_FATAL("Unknown CPU mode");
+ case protos::pbzero::Profiling::MODE_GUEST_KERNEL:
+ case protos::pbzero::Profiling::MODE_KERNEL:
+ return true;
+ case protos::pbzero::Profiling::MODE_USER:
+ case protos::pbzero::Profiling::MODE_HYPERVISOR:
+ case protos::pbzero::Profiling::MODE_GUEST_USER:
+ return false;
+ }
+ PERFETTO_FATAL("For GCC.");
+}
+
+} // namespace
+
+using FramesTable = tables::StackProfileFrameTable;
+using CallsitesTable = tables::StackProfileCallsiteTable;
+
+RecordParser::RecordParser(TraceProcessorContext* context)
+ : context_(context) {}
+
+RecordParser::~RecordParser() = default;
+
+void RecordParser::ParsePerfRecord(int64_t ts, Record record) {
+ if (base::Status status = ParseRecord(ts, std::move(record)); !status.ok()) {
+ context_->storage->IncrementStats(record.header.type == PERF_RECORD_SAMPLE
+ ? stats::perf_samples_skipped
+ : stats::perf_record_skipped);
+ }
+}
+
+base::Status RecordParser::ParseRecord(int64_t ts, Record record) {
+ switch (record.header.type) {
+ case PERF_RECORD_COMM:
+ return ParseComm(std::move(record));
+
+ case PERF_RECORD_SAMPLE:
+ return ParseSample(ts, std::move(record));
+
+ case PERF_RECORD_MMAP:
+ return ParseMmap(std::move(record));
+
+ case PERF_RECORD_MMAP2:
+ return ParseMmap2(std::move(record));
+
+ case PERF_RECORD_AUX:
+ case PERF_RECORD_AUXTRACE:
+ case PERF_RECORD_AUXTRACE_INFO:
+ // These should be dealt with at tokenization time
+ PERFETTO_FATAL("Unexpected record type at parsing time: %" PRIu32,
+ record.header.type);
+
+ default:
+ context_->storage->IncrementIndexedStats(
+ stats::perf_unknown_record_type,
+ static_cast<int>(record.header.type));
+ return base::ErrStatus("Unknown PERF_RECORD with type %" PRIu32,
+ record.header.type);
+ }
+}
+
+base::Status RecordParser::ParseSample(int64_t ts, Record record) {
+ Sample sample;
+ RETURN_IF_ERROR(sample.Parse(ts, record));
+
+ if (!sample.period.has_value() && record.attr != nullptr) {
+ sample.period = record.attr->sample_period();
+ }
+
+ return InternSample(std::move(sample));
+}
+
+base::Status RecordParser::InternSample(Sample sample) {
+ if (!sample.time.has_value()) {
+ // We do not really use this TS as this is using the perf clock, but we need
+ // it to be present so that we can compute the trace_ts done during
+ // tokenization. (Actually at tokenization time we do estimate a trace_ts if
+ // no perf ts is present, but for samples we want this to be as accurate as
+ // possible)
+ base::ErrStatus("Can not parse samples with no PERF_SAMPLE_TIME field");
+ }
+
+ if (!sample.pid_tid.has_value()) {
+ base::ErrStatus("Can not parse samples with no PERF_SAMPLE_TID field");
+ }
+
+ if (!sample.cpu.has_value()) {
+ base::ErrStatus("Can not parse samples with no PERF_SAMPLE_CPU field");
+ }
+
+ UniqueTid utid = context_->process_tracker->UpdateThread(sample.pid_tid->tid,
+ sample.pid_tid->pid);
+ const auto upid = *context_->storage->thread_table()
+ .FindById(tables::ThreadTable::Id(utid))
+ ->upid();
+
+ if (sample.callchain.empty() && sample.ip.has_value()) {
+ sample.callchain.push_back(Sample::Frame{sample.cpu_mode, *sample.ip});
+ }
+ std::optional<CallsiteId> callsite_id =
+ InternCallchain(upid, sample.callchain);
+
+ context_->storage->mutable_perf_sample_table()->Insert(
+ {sample.trace_ts, utid, *sample.cpu,
+ context_->storage->InternString(
+ ProfilePacketUtils::StringifyCpuMode(sample.cpu_mode)),
+ callsite_id, std::nullopt, sample.perf_session->perf_session_id()});
+
+ return UpdateCounters(sample);
+}
+
+std::optional<CallsiteId> RecordParser::InternCallchain(
+ UniquePid upid,
+ const std::vector<Sample::Frame>& callchain) {
+ if (callchain.empty()) {
+ return std::nullopt;
+ }
+
+ auto& stack_profile_tracker = *context_->stack_profile_tracker;
+ auto& mapping_tracker = *context_->mapping_tracker;
+
+ std::optional<CallsiteId> parent;
+ uint32_t depth = 0;
+ for (auto it = callchain.rbegin(); it != callchain.rend(); ++it) {
+ VirtualMemoryMapping* mapping;
+ if (IsInKernel(it->cpu_mode)) {
+ mapping = mapping_tracker.FindKernelMappingForAddress(it->ip);
+ } else {
+ mapping = mapping_tracker.FindUserMappingForAddress(upid, it->ip);
+ }
+
+ if (!mapping) {
+ context_->storage->IncrementStats(stats::perf_dummy_mapping_used);
+ // Simpleperf will not create mappings for anonymous executable mappings
+ // which are used by JITted code (e.g. V8 JavaScript).
+ mapping = mapping_tracker.GetDummyMapping();
+ }
+
+ const FrameId frame_id =
+ mapping->InternFrame(mapping->ToRelativePc(it->ip), "");
+
+ parent = stack_profile_tracker.InternCallsite(parent, frame_id, depth);
+ depth++;
+ }
+ return parent;
+}
+
+base::Status RecordParser::ParseComm(Record record) {
+ Reader reader(record.payload.copy());
+ uint32_t pid;
+ uint32_t tid;
+ std::string comm;
+ if (!reader.Read(pid) || !reader.Read(tid) || !reader.ReadCString(comm)) {
+ return base::ErrStatus("Failed to parse PERF_RECORD_COMM");
+ }
+
+ context_->process_tracker->UpdateThread(tid, pid);
+ context_->process_tracker->UpdateThreadName(
+ tid, context_->storage->InternString(base::StringView(comm)),
+ ThreadNamePriority::kFtrace);
+
+ return base::OkStatus();
+}
+
+base::Status RecordParser::ParseMmap(Record record) {
+ MmapRecord mmap;
+ RETURN_IF_ERROR(mmap.Parse(record));
+ if (IsInKernel(record.GetCpuMode())) {
+ context_->mapping_tracker->CreateKernelMemoryMapping(
+ BuildCreateMappingParams(mmap, std::move(mmap.filename)));
+ return base::OkStatus();
+ }
+
+ context_->mapping_tracker->CreateUserMemoryMapping(
+ GetUpid(mmap), BuildCreateMappingParams(mmap, std::move(mmap.filename)));
+
+ return base::OkStatus();
+}
+
+util::Status RecordParser::ParseMmap2(Record record) {
+ Mmap2Record mmap2;
+ RETURN_IF_ERROR(mmap2.Parse(record));
+ if (IsInKernel(record.GetCpuMode())) {
+ context_->mapping_tracker->CreateKernelMemoryMapping(
+ BuildCreateMappingParams(mmap2, std::move(mmap2.filename)));
+ return base::OkStatus();
+ }
+
+ context_->mapping_tracker->CreateUserMemoryMapping(
+ GetUpid(mmap2), BuildCreateMappingParams(mmap2, std::move(mmap2.filename),
+ mmap2.GetBuildId()));
+
+ return base::OkStatus();
+}
+
+UniquePid RecordParser::GetUpid(const CommonMmapRecordFields& fields) const {
+ UniqueTid utid =
+ context_->process_tracker->UpdateThread(fields.tid, fields.pid);
+ auto upid = context_->storage->thread_table()
+ .FindById(tables::ThreadTable::Id(utid))
+ ->upid();
+ PERFETTO_CHECK(upid.has_value());
+ return *upid;
+}
+
+base::Status RecordParser::UpdateCounters(const Sample& sample) {
+ if (!sample.read_groups.empty()) {
+ return UpdateCountersInReadGroups(sample);
+ }
+
+ if (!sample.period.has_value() && !sample.attr->sample_period().has_value()) {
+ return base::ErrStatus("No period for sample");
+ }
+
+ uint64_t period = sample.period.has_value() ? *sample.period
+ : *sample.attr->sample_period();
+ sample.attr->GetOrCreateCounter(*sample.cpu)
+ .AddDelta(sample.trace_ts, static_cast<double>(period));
+ return base::OkStatus();
+}
+
+base::Status RecordParser::UpdateCountersInReadGroups(const Sample& sample) {
+ if (!sample.cpu.has_value()) {
+ return base::ErrStatus("No cpu for sample");
+ }
+
+ for (const auto& entry : sample.read_groups) {
+ RefPtr<const PerfEventAttr> attr =
+ sample.perf_session->FindAttrForEventId(*entry.event_id);
+ if (PERFETTO_UNLIKELY(!attr)) {
+ return base::ErrStatus("No perf_event_attr for id %" PRIu64,
+ *entry.event_id);
+ }
+ attr->GetOrCreateCounter(*sample.cpu)
+ .AddCount(sample.trace_ts, static_cast<double>(entry.value));
+ }
+ return base::OkStatus();
+}
+
+} // namespace perf_importer
+} // namespace trace_processor
+} // namespace perfetto
diff --git a/src/trace_processor/importers/perf/record_parser.h b/src/trace_processor/importers/perf/record_parser.h
new file mode 100644
index 0000000..0a71b49
--- /dev/null
+++ b/src/trace_processor/importers/perf/record_parser.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PERF_RECORD_PARSER_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_PERF_RECORD_PARSER_H_
+
+#include <stdint.h>
+#include <cstdint>
+#include <vector>
+
+#include "perfetto/base/status.h"
+#include "src/trace_processor/importers/common/trace_parser.h"
+#include "src/trace_processor/importers/perf/mmap_record.h"
+#include "src/trace_processor/importers/perf/record.h"
+#include "src/trace_processor/importers/perf/sample.h"
+#include "src/trace_processor/storage/trace_storage.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+class TraceProcessorContext;
+
+namespace perf_importer {
+
+class PerfDataTracker;
+class Reader;
+
+// Parses samples from perf.data files.
+class RecordParser : public PerfRecordParser {
+ public:
+ explicit RecordParser(TraceProcessorContext*);
+ ~RecordParser() override;
+
+ void ParsePerfRecord(int64_t timestamp, Record record) override;
+
+ private:
+ base::Status ParseRecord(int64_t timestamp, Record record);
+ base::Status ParseSample(int64_t ts, Record record);
+ base::Status ParseComm(Record record);
+ base::Status ParseMmap(Record record);
+ base::Status ParseMmap2(Record record);
+
+ base::Status InternSample(Sample sample);
+
+ base::Status UpdateCounters(const Sample& sample);
+ base::Status UpdateCountersInReadGroups(const Sample& sample);
+
+ std::optional<CallsiteId> InternCallchain(
+ UniquePid upid,
+ const std::vector<Sample::Frame>& callchain);
+
+ UniquePid GetUpid(const CommonMmapRecordFields& fields) const;
+
+ TraceProcessorContext* context_ = nullptr;
+};
+
+} // namespace perf_importer
+} // namespace trace_processor
+} // namespace perfetto
+
+#endif // SRC_TRACE_PROCESSOR_IMPORTERS_PERF_RECORD_PARSER_H_
diff --git a/src/trace_processor/importers/perf/sample.cc b/src/trace_processor/importers/perf/sample.cc
new file mode 100644
index 0000000..63ded53
--- /dev/null
+++ b/src/trace_processor/importers/perf/sample.cc
@@ -0,0 +1,252 @@
+/*
+ * 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/sample.h"
+
+#include <cstdint>
+
+#include "perfetto/base/logging.h"
+#include "perfetto/base/status.h"
+#include "perfetto/public/compiler.h"
+#include "src/trace_processor/importers/perf/reader.h"
+#include "src/trace_processor/importers/perf/record.h"
+
+namespace perfetto::trace_processor::perf_importer {
+namespace {
+
+bool ParseSampleReadGroup(Reader& reader,
+ uint64_t read_format,
+ uint64_t num_records,
+ std::vector<Sample::ReadGroup>& out) {
+ out.resize(num_records);
+ for (auto& read : out) {
+ if (PERFETTO_UNLIKELY(!reader.Read(read.value))) {
+ return false;
+ }
+
+ if (read_format & PERF_FORMAT_ID) {
+ if (PERFETTO_UNLIKELY(!reader.ReadOptional(read.event_id))) {
+ return false;
+ }
+ }
+
+ if (read_format & PERF_FORMAT_LOST) {
+ uint64_t lost;
+ if (PERFETTO_UNLIKELY(!reader.Read(lost))) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+bool ParseSampleRead(Reader& reader,
+ uint64_t read_format,
+ std::vector<Sample::ReadGroup>& out) {
+ uint64_t value_or_nr;
+
+ if (PERFETTO_UNLIKELY(!reader.Read(value_or_nr))) {
+ return false;
+ }
+
+ if (read_format & PERF_FORMAT_TOTAL_TIME_ENABLED) {
+ uint64_t total_time_enabled;
+ if (PERFETTO_UNLIKELY(!reader.Read(total_time_enabled))) {
+ return false;
+ }
+ }
+
+ if (read_format & PERF_FORMAT_TOTAL_TIME_RUNNING) {
+ uint64_t total_time_running;
+ if (PERFETTO_UNLIKELY(!reader.Read(total_time_running))) {
+ return false;
+ }
+ }
+
+ if (read_format & PERF_FORMAT_GROUP) {
+ return ParseSampleReadGroup(reader, read_format, value_or_nr, out);
+ }
+
+ std::optional<uint64_t> event_id;
+ if (read_format & PERF_FORMAT_ID) {
+ event_id.emplace(0);
+ if (PERFETTO_UNLIKELY(!reader.ReadOptional(event_id))) {
+ return false;
+ }
+ }
+
+ if (read_format & PERF_FORMAT_LOST) {
+ uint64_t lost;
+ if (PERFETTO_UNLIKELY(!reader.Read(lost))) {
+ return false;
+ }
+ }
+
+ out.push_back({event_id, value_or_nr});
+
+ return true;
+}
+
+protos::pbzero::Profiling::CpuMode PerfCallchainContextToCpuMode(uint64_t ip) {
+ switch (ip) {
+ case PERF_CONTEXT_HV:
+ return protos::pbzero::Profiling::MODE_HYPERVISOR;
+ case PERF_CONTEXT_KERNEL:
+ return protos::pbzero::Profiling::MODE_KERNEL;
+ case PERF_CONTEXT_USER:
+ return protos::pbzero::Profiling::MODE_USER;
+ case PERF_CONTEXT_GUEST_KERNEL:
+ return protos::pbzero::Profiling::MODE_GUEST_KERNEL;
+ case PERF_CONTEXT_GUEST_USER:
+ return protos::pbzero::Profiling::MODE_GUEST_USER;
+ case PERF_CONTEXT_GUEST:
+ default:
+ return protos::pbzero::Profiling::MODE_UNKNOWN;
+ }
+ PERFETTO_FATAL("For GCC");
+}
+
+bool IsPerfContextMark(uint64_t ip) {
+ return ip >= PERF_CONTEXT_MAX;
+}
+
+bool ParseSampleCallchain(Reader& reader,
+ protos::pbzero::Profiling::CpuMode cpu_mode,
+ std::vector<Sample::Frame>& out) {
+ uint64_t nr;
+ if (PERFETTO_UNLIKELY(!reader.Read(nr))) {
+ return false;
+ }
+
+ std::vector<Sample::Frame> frames;
+ frames.reserve(nr);
+ for (; nr != 0; --nr) {
+ uint64_t ip;
+ if (PERFETTO_UNLIKELY(!reader.Read(ip))) {
+ return false;
+ }
+ if (PERFETTO_UNLIKELY(IsPerfContextMark(ip))) {
+ cpu_mode = PerfCallchainContextToCpuMode(ip);
+ continue;
+ }
+ frames.push_back({cpu_mode, ip});
+ }
+
+ out = std::move(frames);
+ return true;
+}
+} // namespace
+
+base::Status Sample::Parse(int64_t in_trace_ts, const Record& record) {
+ PERFETTO_CHECK(record.attr);
+ const uint64_t sample_type = record.attr->sample_type();
+
+ trace_ts = in_trace_ts;
+ cpu_mode = record.GetCpuMode();
+ perf_session = record.session;
+ attr = record.attr;
+
+ Reader reader(record.payload.copy());
+
+ std::optional<uint64_t> identifier;
+ if (sample_type & PERF_SAMPLE_IDENTIFIER) {
+ if (PERFETTO_UNLIKELY(!reader.ReadOptional(identifier))) {
+ return base ::ErrStatus("Not enough data to read PERF_SAMPLE_IDENTIFIER");
+ }
+ }
+
+ if (sample_type & PERF_SAMPLE_IP) {
+ if (PERFETTO_UNLIKELY(!reader.ReadOptional(ip))) {
+ return base ::ErrStatus("Not enough data to read PERF_SAMPLE_IP");
+ }
+ }
+
+ if (sample_type & PERF_SAMPLE_TID) {
+ if (PERFETTO_UNLIKELY(!reader.ReadOptional(pid_tid))) {
+ return base ::ErrStatus("Not enough data to read PERF_SAMPLE_TID");
+ }
+ }
+
+ if (sample_type & PERF_SAMPLE_TIME) {
+ if (PERFETTO_UNLIKELY(!reader.ReadOptional(time))) {
+ return base ::ErrStatus("Not enough data to read PERF_SAMPLE_TIME");
+ }
+ }
+
+ if (sample_type & PERF_SAMPLE_ADDR) {
+ if (PERFETTO_UNLIKELY(!reader.ReadOptional(addr))) {
+ return base ::ErrStatus("Not enough data to read PERF_SAMPLE_ADDR");
+ }
+ }
+
+ if (sample_type & PERF_SAMPLE_ID) {
+ if (PERFETTO_UNLIKELY(!reader.ReadOptional(id))) {
+ return base ::ErrStatus("Not enough data to read PERF_SAMPLE_ID");
+ }
+ }
+
+ if (identifier.has_value()) {
+ if (!id.has_value()) {
+ id = identifier;
+ } else if (PERFETTO_UNLIKELY(*identifier != *id)) {
+ return base::ErrStatus("ID and IDENTIFIER mismatch");
+ }
+ }
+
+ if (sample_type & PERF_SAMPLE_STREAM_ID) {
+ if (PERFETTO_UNLIKELY(!reader.ReadOptional(stream_id))) {
+ return base ::ErrStatus("Not enough data to read PERF_SAMPLE_STREAM_ID");
+ }
+ }
+
+ if (sample_type & PERF_SAMPLE_CPU) {
+ struct {
+ int32_t cpu;
+ int32_t unused;
+ } tmp;
+ if (PERFETTO_UNLIKELY(!reader.Read(tmp))) {
+ return base ::ErrStatus("Not enough data to read PERF_SAMPLE_CPU");
+ }
+ cpu = tmp.cpu;
+ }
+
+ if (sample_type & PERF_SAMPLE_PERIOD) {
+ if (PERFETTO_UNLIKELY(!reader.ReadOptional(period))) {
+ return base ::ErrStatus("Not enough data to read PERF_SAMPLE_PERIOD");
+ }
+ }
+
+ if (sample_type & PERF_SAMPLE_READ) {
+ if (PERFETTO_UNLIKELY(
+ !ParseSampleRead(reader, attr->read_format(), read_groups))) {
+ return base::ErrStatus("Failed to read PERF_SAMPLE_READ field");
+ }
+ if (read_groups.empty()) {
+ return base::ErrStatus("No data in PERF_SAMPLE_READ field");
+ }
+ }
+
+ if (sample_type & PERF_SAMPLE_CALLCHAIN) {
+ if (PERFETTO_UNLIKELY(!ParseSampleCallchain(reader, cpu_mode, callchain))) {
+ return base::ErrStatus("Failed to read PERF_SAMPLE_CALLCHAIN field");
+ }
+ }
+
+ return base::OkStatus();
+}
+
+} // namespace perfetto::trace_processor::perf_importer
diff --git a/src/trace_processor/importers/perf/sample.h b/src/trace_processor/importers/perf/sample.h
new file mode 100644
index 0000000..532b613
--- /dev/null
+++ b/src/trace_processor/importers/perf/sample.h
@@ -0,0 +1,71 @@
+/*
+ * 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_SAMPLE_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_PERF_SAMPLE_H_
+
+#include <cstdint>
+#include <optional>
+#include <vector>
+
+#include "perfetto/base/status.h"
+#include "perfetto/trace_processor/ref_counted.h"
+#include "protos/perfetto/trace/profiling/profile_packet.pbzero.h"
+#include "src/trace_processor/importers/perf/perf_event_attr.h"
+#include "src/trace_processor/importers/perf/perf_session.h"
+
+namespace perfetto::trace_processor::perf_importer {
+
+struct Record;
+
+struct Sample {
+ struct Frame {
+ protos::pbzero::Profiling::CpuMode cpu_mode;
+ uint64_t ip;
+ };
+
+ struct PidTid {
+ uint32_t pid;
+ uint32_t tid;
+ };
+
+ struct ReadGroup {
+ std::optional<uint64_t> event_id;
+ uint64_t value;
+ };
+
+ int64_t trace_ts;
+ protos::pbzero::Profiling::CpuMode cpu_mode;
+ RefPtr<PerfSession> perf_session;
+ RefPtr<const PerfEventAttr> attr;
+
+ std::optional<uint64_t> ip;
+ std::optional<PidTid> pid_tid;
+ std::optional<uint64_t> time;
+ std::optional<uint64_t> addr;
+ std::optional<uint64_t> id;
+ std::optional<uint64_t> stream_id;
+ std::optional<uint32_t> cpu;
+ std::optional<uint64_t> period;
+ std::vector<ReadGroup> read_groups;
+ std::vector<Frame> callchain;
+
+ base::Status Parse(int64_t trace_ts, const Record& record);
+};
+
+} // namespace perfetto::trace_processor::perf_importer
+
+#endif // SRC_TRACE_PROCESSOR_IMPORTERS_PERF_SAMPLE_H_
diff --git a/src/trace_processor/importers/proto/chrome_system_probes_parser.h b/src/trace_processor/importers/proto/chrome_system_probes_parser.h
index ca68814..9c9397b 100644
--- a/src/trace_processor/importers/proto/chrome_system_probes_parser.h
+++ b/src/trace_processor/importers/proto/chrome_system_probes_parser.h
@@ -43,7 +43,7 @@
// Maps a proto field number for memcounters in ProcessStats::Process to
// their StringId. Keep kProcStatsProcessSize equal to 1 + max proto field
// id of ProcessStats::Process. Also update SystemProbesParser.
- static constexpr size_t kProcStatsProcessSize = 23;
+ static constexpr size_t kProcStatsProcessSize = 24;
std::array<StringId, kProcStatsProcessSize> proc_stats_process_names_{};
};
diff --git a/src/trace_processor/importers/proto/network_trace_module_unittest.cc b/src/trace_processor/importers/proto/network_trace_module_unittest.cc
index f174b35..017b7f7 100644
--- a/src/trace_processor/importers/proto/network_trace_module_unittest.cc
+++ b/src/trace_processor/importers/proto/network_trace_module_unittest.cc
@@ -20,6 +20,7 @@
#include "src/trace_processor/importers/common/args_translation_table.h"
#include "src/trace_processor/importers/common/async_track_set_tracker.h"
#include "src/trace_processor/importers/common/global_args_tracker.h"
+#include "src/trace_processor/importers/common/process_track_translation_table.h"
#include "src/trace_processor/importers/common/slice_tracker.h"
#include "src/trace_processor/importers/common/slice_translation_table.h"
#include "src/trace_processor/importers/common/track_tracker.h"
@@ -45,6 +46,8 @@
context_.args_tracker.reset(new ArgsTracker(&context_));
context_.global_args_tracker.reset(new GlobalArgsTracker(storage_));
context_.slice_translation_table.reset(new SliceTranslationTable(storage_));
+ context_.process_track_translation_table.reset(
+ new ProcessTrackTranslationTable(storage_));
context_.args_translation_table.reset(new ArgsTranslationTable(storage_));
context_.async_track_set_tracker.reset(new AsyncTrackSetTracker(&context_));
context_.proto_trace_parser.reset(new ProtoTraceParserImpl(&context_));
diff --git a/src/trace_processor/importers/proto/proto_trace_parser_impl.cc b/src/trace_processor/importers/proto/proto_trace_parser_impl.cc
index 9d318a1..1845073 100644
--- a/src/trace_processor/importers/proto/proto_trace_parser_impl.cc
+++ b/src/trace_processor/importers/proto/proto_trace_parser_impl.cc
@@ -44,7 +44,6 @@
#include "src/trace_processor/types/trace_processor_context.h"
#include "src/trace_processor/types/variadic.h"
-#include "protos/perfetto/common/trace_stats.pbzero.h"
#include "protos/perfetto/config/trace_config.pbzero.h"
#include "protos/perfetto/trace/chrome/chrome_trace_event.pbzero.h"
#include "protos/perfetto/trace/perfetto/perfetto_metatrace.pbzero.h"
@@ -85,9 +84,6 @@
}
}
- if (packet.has_trace_stats())
- ParseTraceStats(packet.trace_stats());
-
if (packet.has_chrome_events()) {
ParseChromeEvents(ts, packet.chrome_events());
}
@@ -160,108 +156,6 @@
context_->args_tracker->Flush();
}
-void ProtoTraceParserImpl::ParseTraceStats(ConstBytes blob) {
- protos::pbzero::TraceStats::Decoder evt(blob.data, blob.size);
- auto* storage = context_->storage.get();
- storage->SetStats(stats::traced_producers_connected,
- static_cast<int64_t>(evt.producers_connected()));
- storage->SetStats(stats::traced_producers_seen,
- static_cast<int64_t>(evt.producers_seen()));
- storage->SetStats(stats::traced_data_sources_registered,
- static_cast<int64_t>(evt.data_sources_registered()));
- storage->SetStats(stats::traced_data_sources_seen,
- static_cast<int64_t>(evt.data_sources_seen()));
- storage->SetStats(stats::traced_tracing_sessions,
- static_cast<int64_t>(evt.tracing_sessions()));
- storage->SetStats(stats::traced_total_buffers,
- static_cast<int64_t>(evt.total_buffers()));
- storage->SetStats(stats::traced_chunks_discarded,
- static_cast<int64_t>(evt.chunks_discarded()));
- storage->SetStats(stats::traced_patches_discarded,
- static_cast<int64_t>(evt.patches_discarded()));
- storage->SetStats(stats::traced_flushes_requested,
- static_cast<int64_t>(evt.flushes_requested()));
- storage->SetStats(stats::traced_flushes_succeeded,
- static_cast<int64_t>(evt.flushes_succeeded()));
- storage->SetStats(stats::traced_flushes_failed,
- static_cast<int64_t>(evt.flushes_failed()));
-
- if (evt.has_filter_stats()) {
- protos::pbzero::TraceStats::FilterStats::Decoder fstat(evt.filter_stats());
- storage->SetStats(stats::filter_errors,
- static_cast<int64_t>(fstat.errors()));
- storage->SetStats(stats::filter_input_bytes,
- static_cast<int64_t>(fstat.input_bytes()));
- storage->SetStats(stats::filter_input_packets,
- static_cast<int64_t>(fstat.input_packets()));
- storage->SetStats(stats::filter_output_bytes,
- static_cast<int64_t>(fstat.output_bytes()));
- storage->SetStats(stats::filter_time_taken_ns,
- static_cast<int64_t>(fstat.time_taken_ns()));
- for (auto [i, it] = std::tuple{0, fstat.bytes_discarded_per_buffer()}; it;
- ++it, ++i) {
- storage->SetIndexedStats(stats::traced_buf_bytes_filtered_out, i,
- static_cast<int64_t>(*it));
- }
- }
-
- switch (evt.final_flush_outcome()) {
- case protos::pbzero::TraceStats::FINAL_FLUSH_SUCCEEDED:
- storage->IncrementStats(stats::traced_final_flush_succeeded, 1);
- break;
- case protos::pbzero::TraceStats::FINAL_FLUSH_FAILED:
- storage->IncrementStats(stats::traced_final_flush_failed, 1);
- break;
- case protos::pbzero::TraceStats::FINAL_FLUSH_UNSPECIFIED:
- break;
- }
-
- int buf_num = 0;
- for (auto it = evt.buffer_stats(); it; ++it, ++buf_num) {
- protos::pbzero::TraceStats::BufferStats::Decoder buf(*it);
- storage->SetIndexedStats(stats::traced_buf_buffer_size, buf_num,
- static_cast<int64_t>(buf.buffer_size()));
- storage->SetIndexedStats(stats::traced_buf_bytes_written, buf_num,
- static_cast<int64_t>(buf.bytes_written()));
- storage->SetIndexedStats(stats::traced_buf_bytes_overwritten, buf_num,
- static_cast<int64_t>(buf.bytes_overwritten()));
- storage->SetIndexedStats(stats::traced_buf_bytes_read, buf_num,
- static_cast<int64_t>(buf.bytes_read()));
- storage->SetIndexedStats(stats::traced_buf_padding_bytes_written, buf_num,
- static_cast<int64_t>(buf.padding_bytes_written()));
- storage->SetIndexedStats(stats::traced_buf_padding_bytes_cleared, buf_num,
- static_cast<int64_t>(buf.padding_bytes_cleared()));
- storage->SetIndexedStats(stats::traced_buf_chunks_written, buf_num,
- static_cast<int64_t>(buf.chunks_written()));
- storage->SetIndexedStats(stats::traced_buf_chunks_rewritten, buf_num,
- static_cast<int64_t>(buf.chunks_rewritten()));
- storage->SetIndexedStats(stats::traced_buf_chunks_overwritten, buf_num,
- static_cast<int64_t>(buf.chunks_overwritten()));
- storage->SetIndexedStats(stats::traced_buf_chunks_discarded, buf_num,
- static_cast<int64_t>(buf.chunks_discarded()));
- storage->SetIndexedStats(stats::traced_buf_chunks_read, buf_num,
- static_cast<int64_t>(buf.chunks_read()));
- storage->SetIndexedStats(
- stats::traced_buf_chunks_committed_out_of_order, buf_num,
- static_cast<int64_t>(buf.chunks_committed_out_of_order()));
- storage->SetIndexedStats(stats::traced_buf_write_wrap_count, buf_num,
- static_cast<int64_t>(buf.write_wrap_count()));
- storage->SetIndexedStats(stats::traced_buf_patches_succeeded, buf_num,
- static_cast<int64_t>(buf.patches_succeeded()));
- storage->SetIndexedStats(stats::traced_buf_patches_failed, buf_num,
- static_cast<int64_t>(buf.patches_failed()));
- storage->SetIndexedStats(stats::traced_buf_readaheads_succeeded, buf_num,
- static_cast<int64_t>(buf.readaheads_succeeded()));
- storage->SetIndexedStats(stats::traced_buf_readaheads_failed, buf_num,
- static_cast<int64_t>(buf.readaheads_failed()));
- storage->SetIndexedStats(stats::traced_buf_abi_violations, buf_num,
- static_cast<int64_t>(buf.abi_violations()));
- storage->SetIndexedStats(
- stats::traced_buf_trace_writer_packet_loss, buf_num,
- static_cast<int64_t>(buf.trace_writer_packet_loss()));
- }
-}
-
void ProtoTraceParserImpl::ParseChromeEvents(int64_t ts, ConstBytes blob) {
TraceStorage* storage = context_->storage.get();
protos::pbzero::ChromeEventBundle::Decoder bundle(blob.data, blob.size);
diff --git a/src/trace_processor/importers/proto/proto_trace_parser_impl.h b/src/trace_processor/importers/proto/proto_trace_parser_impl.h
index e9bce35..2c4dc07 100644
--- a/src/trace_processor/importers/proto/proto_trace_parser_impl.h
+++ b/src/trace_processor/importers/proto/proto_trace_parser_impl.h
@@ -65,7 +65,6 @@
int64_t /*ts*/,
InlineSchedWaking data) override;
- void ParseTraceStats(ConstBytes);
void ParseChromeEvents(int64_t ts, ConstBytes);
void ParseMetatraceEvent(int64_t ts, ConstBytes);
diff --git a/src/trace_processor/importers/proto/proto_trace_parser_impl_unittest.cc b/src/trace_processor/importers/proto/proto_trace_parser_impl_unittest.cc
index 95b16d1..a1a877d 100644
--- a/src/trace_processor/importers/proto/proto_trace_parser_impl_unittest.cc
+++ b/src/trace_processor/importers/proto/proto_trace_parser_impl_unittest.cc
@@ -27,6 +27,7 @@
#include "src/trace_processor/importers/common/flow_tracker.h"
#include "src/trace_processor/importers/common/mapping_tracker.h"
#include "src/trace_processor/importers/common/metadata_tracker.h"
+#include "src/trace_processor/importers/common/process_track_translation_table.h"
#include "src/trace_processor/importers/common/process_tracker.h"
#include "src/trace_processor/importers/common/slice_tracker.h"
#include "src/trace_processor/importers/common/stack_profile_tracker.h"
@@ -265,6 +266,8 @@
context_.ftrace_sched_tracker.reset(sched_);
process_ = new NiceMock<MockProcessTracker>(&context_);
context_.process_tracker.reset(process_);
+ context_.process_track_translation_table.reset(
+ new ProcessTrackTranslationTable(storage_));
slice_ = new NiceMock<MockSliceTracker>(&context_);
context_.slice_tracker.reset(slice_);
context_.slice_translation_table.reset(new SliceTranslationTable(storage_));
diff --git a/src/trace_processor/importers/proto/proto_trace_reader.cc b/src/trace_processor/importers/proto/proto_trace_reader.cc
index 2e50f0c..d958c0b 100644
--- a/src/trace_processor/importers/proto/proto_trace_reader.cc
+++ b/src/trace_processor/importers/proto/proto_trace_reader.cc
@@ -21,6 +21,7 @@
#include "perfetto/base/build_config.h"
#include "perfetto/base/logging.h"
+#include "perfetto/ext/base/flat_hash_map.h"
#include "perfetto/ext/base/string_view.h"
#include "perfetto/ext/base/utils.h"
#include "perfetto/protozero/proto_decoder.h"
@@ -41,6 +42,7 @@
#include "src/trace_processor/util/gzip_utils.h"
#include "protos/perfetto/common/builtin_clock.pbzero.h"
+#include "protos/perfetto/common/trace_stats.pbzero.h"
#include "protos/perfetto/config/trace_config.pbzero.h"
#include "protos/perfetto/trace/clock_snapshot.pbzero.h"
#include "protos/perfetto/trace/extension_descriptor.pbzero.h"
@@ -118,6 +120,16 @@
HandlePreviousPacketDropped(decoder);
}
+ uint32_t sequence_id = decoder.trusted_packet_sequence_id();
+ if (sequence_id) {
+ auto [data_loss, inserted] =
+ packet_sequence_data_loss_.Insert(sequence_id, 0);
+
+ if (!inserted && decoder.previous_packet_dropped()) {
+ *data_loss += 1;
+ }
+ }
+
// It is important that we parse defaults before parsing other fields such as
// the timestamp, since the defaults could affect them.
if (decoder.has_trace_packet_defaults()) {
@@ -131,8 +143,11 @@
}
if (decoder.has_clock_snapshot()) {
- return ParseClockSnapshot(decoder.clock_snapshot(),
- decoder.trusted_packet_sequence_id());
+ return ParseClockSnapshot(decoder.clock_snapshot(), sequence_id);
+ }
+
+ if (decoder.has_trace_stats()) {
+ ParseTraceStats(decoder.trace_stats());
}
if (decoder.has_service_event()) {
@@ -474,6 +489,125 @@
return util::OkStatus();
}
+void ProtoTraceReader::ParseTraceStats(ConstBytes blob) {
+ protos::pbzero::TraceStats::Decoder evt(blob.data, blob.size);
+ auto* storage = context_->storage.get();
+ storage->SetStats(stats::traced_producers_connected,
+ static_cast<int64_t>(evt.producers_connected()));
+ storage->SetStats(stats::traced_producers_seen,
+ static_cast<int64_t>(evt.producers_seen()));
+ storage->SetStats(stats::traced_data_sources_registered,
+ static_cast<int64_t>(evt.data_sources_registered()));
+ storage->SetStats(stats::traced_data_sources_seen,
+ static_cast<int64_t>(evt.data_sources_seen()));
+ storage->SetStats(stats::traced_tracing_sessions,
+ static_cast<int64_t>(evt.tracing_sessions()));
+ storage->SetStats(stats::traced_total_buffers,
+ static_cast<int64_t>(evt.total_buffers()));
+ storage->SetStats(stats::traced_chunks_discarded,
+ static_cast<int64_t>(evt.chunks_discarded()));
+ storage->SetStats(stats::traced_patches_discarded,
+ static_cast<int64_t>(evt.patches_discarded()));
+ storage->SetStats(stats::traced_flushes_requested,
+ static_cast<int64_t>(evt.flushes_requested()));
+ storage->SetStats(stats::traced_flushes_succeeded,
+ static_cast<int64_t>(evt.flushes_succeeded()));
+ storage->SetStats(stats::traced_flushes_failed,
+ static_cast<int64_t>(evt.flushes_failed()));
+
+ if (evt.has_filter_stats()) {
+ protos::pbzero::TraceStats::FilterStats::Decoder fstat(evt.filter_stats());
+ storage->SetStats(stats::filter_errors,
+ static_cast<int64_t>(fstat.errors()));
+ storage->SetStats(stats::filter_input_bytes,
+ static_cast<int64_t>(fstat.input_bytes()));
+ storage->SetStats(stats::filter_input_packets,
+ static_cast<int64_t>(fstat.input_packets()));
+ storage->SetStats(stats::filter_output_bytes,
+ static_cast<int64_t>(fstat.output_bytes()));
+ storage->SetStats(stats::filter_time_taken_ns,
+ static_cast<int64_t>(fstat.time_taken_ns()));
+ for (auto [i, it] = std::tuple{0, fstat.bytes_discarded_per_buffer()}; it;
+ ++it, ++i) {
+ storage->SetIndexedStats(stats::traced_buf_bytes_filtered_out, i,
+ static_cast<int64_t>(*it));
+ }
+ }
+
+ switch (evt.final_flush_outcome()) {
+ case protos::pbzero::TraceStats::FINAL_FLUSH_SUCCEEDED:
+ storage->IncrementStats(stats::traced_final_flush_succeeded, 1);
+ break;
+ case protos::pbzero::TraceStats::FINAL_FLUSH_FAILED:
+ storage->IncrementStats(stats::traced_final_flush_failed, 1);
+ break;
+ case protos::pbzero::TraceStats::FINAL_FLUSH_UNSPECIFIED:
+ break;
+ }
+
+ int buf_num = 0;
+ for (auto it = evt.buffer_stats(); it; ++it, ++buf_num) {
+ protos::pbzero::TraceStats::BufferStats::Decoder buf(*it);
+ storage->SetIndexedStats(stats::traced_buf_buffer_size, buf_num,
+ static_cast<int64_t>(buf.buffer_size()));
+ storage->SetIndexedStats(stats::traced_buf_bytes_written, buf_num,
+ static_cast<int64_t>(buf.bytes_written()));
+ storage->SetIndexedStats(stats::traced_buf_bytes_overwritten, buf_num,
+ static_cast<int64_t>(buf.bytes_overwritten()));
+ storage->SetIndexedStats(stats::traced_buf_bytes_read, buf_num,
+ static_cast<int64_t>(buf.bytes_read()));
+ storage->SetIndexedStats(stats::traced_buf_padding_bytes_written, buf_num,
+ static_cast<int64_t>(buf.padding_bytes_written()));
+ storage->SetIndexedStats(stats::traced_buf_padding_bytes_cleared, buf_num,
+ static_cast<int64_t>(buf.padding_bytes_cleared()));
+ storage->SetIndexedStats(stats::traced_buf_chunks_written, buf_num,
+ static_cast<int64_t>(buf.chunks_written()));
+ storage->SetIndexedStats(stats::traced_buf_chunks_rewritten, buf_num,
+ static_cast<int64_t>(buf.chunks_rewritten()));
+ storage->SetIndexedStats(stats::traced_buf_chunks_overwritten, buf_num,
+ static_cast<int64_t>(buf.chunks_overwritten()));
+ storage->SetIndexedStats(stats::traced_buf_chunks_discarded, buf_num,
+ static_cast<int64_t>(buf.chunks_discarded()));
+ storage->SetIndexedStats(stats::traced_buf_chunks_read, buf_num,
+ static_cast<int64_t>(buf.chunks_read()));
+ storage->SetIndexedStats(
+ stats::traced_buf_chunks_committed_out_of_order, buf_num,
+ static_cast<int64_t>(buf.chunks_committed_out_of_order()));
+ storage->SetIndexedStats(stats::traced_buf_write_wrap_count, buf_num,
+ static_cast<int64_t>(buf.write_wrap_count()));
+ storage->SetIndexedStats(stats::traced_buf_patches_succeeded, buf_num,
+ static_cast<int64_t>(buf.patches_succeeded()));
+ storage->SetIndexedStats(stats::traced_buf_patches_failed, buf_num,
+ static_cast<int64_t>(buf.patches_failed()));
+ storage->SetIndexedStats(stats::traced_buf_readaheads_succeeded, buf_num,
+ static_cast<int64_t>(buf.readaheads_succeeded()));
+ storage->SetIndexedStats(stats::traced_buf_readaheads_failed, buf_num,
+ static_cast<int64_t>(buf.readaheads_failed()));
+ storage->SetIndexedStats(stats::traced_buf_abi_violations, buf_num,
+ static_cast<int64_t>(buf.abi_violations()));
+ storage->SetIndexedStats(
+ stats::traced_buf_trace_writer_packet_loss, buf_num,
+ static_cast<int64_t>(buf.trace_writer_packet_loss()));
+ }
+
+ base::FlatHashMap<int32_t, int64_t> data_loss_per_buffer;
+
+ for (auto it = evt.writer_stats(); it; ++it) {
+ protos::pbzero::TraceStats::WriterStats::Decoder writer(*it);
+ auto* data_loss = packet_sequence_data_loss_.Find(
+ static_cast<uint32_t>(writer.sequence_id()));
+ if (data_loss) {
+ data_loss_per_buffer[static_cast<int32_t>(writer.buffer())] +=
+ static_cast<int64_t>(*data_loss);
+ }
+ }
+
+ for (auto it = data_loss_per_buffer.GetIterator(); it; ++it) {
+ storage->SetIndexedStats(stats::traced_buf_sequence_packet_loss, it.key(),
+ it.value());
+ }
+}
+
void ProtoTraceReader::NotifyEndOfFile() {}
} // namespace trace_processor
diff --git a/src/trace_processor/importers/proto/proto_trace_reader.h b/src/trace_processor/importers/proto/proto_trace_reader.h
index a93ad98..4c4e2ca 100644
--- a/src/trace_processor/importers/proto/proto_trace_reader.h
+++ b/src/trace_processor/importers/proto/proto_trace_reader.h
@@ -78,6 +78,7 @@
void ParseInternedData(const protos::pbzero::TracePacket_Decoder&,
TraceBlobView interned_data);
void ParseTraceConfig(ConstBytes);
+ void ParseTraceStats(ConstBytes);
std::optional<StringId> GetBuiltinClockNameOrNull(int64_t clock_id);
@@ -104,6 +105,8 @@
base::FlatHashMap<uint32_t, PacketSequenceStateBuilder>
packet_sequence_state_builders_;
+ base::FlatHashMap<uint32_t, size_t> packet_sequence_data_loss_;
+
StringId skipped_packet_key_id_;
StringId invalid_incremental_state_key_id_;
};
diff --git a/src/trace_processor/importers/proto/system_probes_parser.cc b/src/trace_processor/importers/proto/system_probes_parser.cc
index 5b878ed..5cc02ff 100644
--- a/src/trace_processor/importers/proto/system_probes_parser.cc
+++ b/src/trace_processor/importers/proto/system_probes_parser.cc
@@ -168,6 +168,8 @@
context->storage->InternString("mem.smaps.pss.file");
proc_stats_process_names_[ProcessStats::Process::kSmrPssShmemKbFieldNumber] =
context->storage->InternString("mem.smaps.pss.shmem");
+ proc_stats_process_names_[ProcessStats::Process::kSmrSwapPssKbFieldNumber] =
+ context->storage->InternString("mem.smaps.swap.pss");
proc_stats_process_names_
[ProcessStats::Process::kRuntimeUserModeFieldNumber] =
context->storage->InternString("runtime.user_ns");
diff --git a/src/trace_processor/importers/proto/system_probes_parser.h b/src/trace_processor/importers/proto/system_probes_parser.h
index a54beb7..472e334 100644
--- a/src/trace_processor/importers/proto/system_probes_parser.h
+++ b/src/trace_processor/importers/proto/system_probes_parser.h
@@ -75,7 +75,7 @@
// their StringId. Keep kProcStatsProcessSize equal to 1 + max proto field
// id of ProcessStats::Process. Also update the value in
// ChromeSystemProbesParser.
- static constexpr size_t kProcStatsProcessSize = 23;
+ static constexpr size_t kProcStatsProcessSize = 24;
std::array<StringId, kProcStatsProcessSize> proc_stats_process_names_{};
// Maps a SysStats::PsiSample::PsiResource type to its StringId.
diff --git a/src/trace_processor/importers/proto/track_event_parser.cc b/src/trace_processor/importers/proto/track_event_parser.cc
index 03c8f21..657e308 100644
--- a/src/trace_processor/importers/proto/track_event_parser.cc
+++ b/src/trace_processor/importers/proto/track_event_parser.cc
@@ -30,6 +30,7 @@
#include "src/trace_processor/importers/common/flow_tracker.h"
#include "src/trace_processor/importers/common/machine_tracker.h"
#include "src/trace_processor/importers/common/process_tracker.h"
+#include "src/trace_processor/importers/common/process_track_translation_table.h"
#include "src/trace_processor/importers/common/track_tracker.h"
#include "src/trace_processor/importers/common/virtual_memory_mapping.h"
#include "src/trace_processor/importers/json/json_utils.h"
@@ -1526,9 +1527,12 @@
}
// Override the name with the most recent name seen (after sorting by ts).
- if (decoder.has_name()) {
+ if (decoder.has_name() || decoder.has_static_name()) {
auto* tracks = context_->storage->mutable_track_table();
- StringId name_id = context_->storage->InternString(decoder.name());
+ const StringId raw_name_id = context_->storage->InternString(
+ decoder.has_name() ? decoder.name() : decoder.static_name());
+ const StringId name_id =
+ context_->process_track_translation_table->TranslateName(raw_name_id);
tracks->mutable_name()->Set(*tracks->id().IndexOf(track_id), name_id);
}
}
diff --git a/src/trace_processor/importers/proto/track_event_tokenizer.cc b/src/trace_processor/importers/proto/track_event_tokenizer.cc
index c836052..238e247 100644
--- a/src/trace_processor/importers/proto/track_event_tokenizer.cc
+++ b/src/trace_processor/importers/proto/track_event_tokenizer.cc
@@ -92,6 +92,8 @@
StringId name_id = kNullStringId;
if (track.has_name())
name_id = context_->storage->InternString(track.name());
+ else if (track.has_static_name())
+ name_id = context_->storage->InternString(track.static_name());
if (packet.has_trusted_pid()) {
context_->process_tracker->UpdateTrustedPid(
diff --git a/src/trace_processor/importers/proto/track_event_tracker.cc b/src/trace_processor/importers/proto/track_event_tracker.cc
index 56e081d..e758b9d 100644
--- a/src/trace_processor/importers/proto/track_event_tracker.cc
+++ b/src/trace_processor/importers/proto/track_event_tracker.cc
@@ -18,6 +18,7 @@
#include "src/trace_processor/importers/common/args_tracker.h"
#include "src/trace_processor/importers/common/args_translation_table.h"
+#include "src/trace_processor/importers/common/process_track_translation_table.h"
#include "src/trace_processor/importers/common/process_tracker.h"
#include "src/trace_processor/importers/common/track_tracker.h"
#include "src/trace_processor/tables/track_tables_py.h"
@@ -201,7 +202,9 @@
reservation_it->second.is_counter) {
return track_id;
}
- tracks->mutable_name()->Set(row, event_name);
+ const StringId track_name =
+ context_->process_track_translation_table->TranslateName(event_name);
+ tracks->mutable_name()->Set(row, track_name);
return track_id;
}
diff --git a/src/trace_processor/importers/proto/translation_table_module.cc b/src/trace_processor/importers/proto/translation_table_module.cc
index 8684df0..f45e353 100644
--- a/src/trace_processor/importers/proto/translation_table_module.cc
+++ b/src/trace_processor/importers/proto/translation_table_module.cc
@@ -16,6 +16,7 @@
#include "src/trace_processor/importers/proto/translation_table_module.h"
#include "src/trace_processor/importers/common/args_translation_table.h"
+#include "src/trace_processor/importers/common/process_track_translation_table.h"
#include "src/trace_processor/importers/common/slice_translation_table.h"
#include "src/trace_processor/importers/proto/packet_sequence_state_generation.h"
@@ -54,6 +55,8 @@
translation_table.chrome_performance_mark());
} else if (translation_table.has_slice_name()) {
ParseSliceNameRules(translation_table.slice_name());
+ } else if (translation_table.has_process_track_name()) {
+ ParseProcessTrackNameRules(translation_table.process_track_name());
}
return ModuleResult::Handled();
}
@@ -113,5 +116,17 @@
}
}
+void TranslationTableModule::ParseProcessTrackNameRules(
+ protozero::ConstBytes bytes) {
+ const auto process_track_name =
+ protos::pbzero::ProcessTrackNameTranslationTable::Decoder(bytes);
+ for (auto it = process_track_name.raw_to_deobfuscated_name(); it; ++it) {
+ protos::pbzero::ProcessTrackNameTranslationTable::
+ RawToDeobfuscatedNameEntry::Decoder entry(*it);
+ context_->process_track_translation_table->AddNameTranslationRule(
+ entry.key(), entry.value());
+ }
+}
+
} // namespace trace_processor
} // namespace perfetto
diff --git a/src/trace_processor/importers/proto/translation_table_module.h b/src/trace_processor/importers/proto/translation_table_module.h
index 2060f41..62d4c72 100644
--- a/src/trace_processor/importers/proto/translation_table_module.h
+++ b/src/trace_processor/importers/proto/translation_table_module.h
@@ -46,6 +46,7 @@
void ParseChromeUserEventRules(protozero::ConstBytes bytes);
void ParseChromePerformanceMarkRules(protozero::ConstBytes bytes);
void ParseSliceNameRules(protozero::ConstBytes bytes);
+ void ParseProcessTrackNameRules(protozero::ConstBytes bytes);
TraceProcessorContext* context_;
};
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/auto/multiuser.sql b/src/trace_processor/perfetto_sql/stdlib/android/auto/multiuser.sql
index ea95dcc..2b408ef 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/auto/multiuser.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/android/auto/multiuser.sql
@@ -64,7 +64,7 @@
FROM android_startups
UNION
SELECT
- slice.ts as event_end_time,
+ slice.ts + slice.dur as event_end_time,
slice.name as event_end_name
FROM slice
WHERE slice.name GLOB "finishUserStopped-10*"
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/process_metadata.sql b/src/trace_processor/perfetto_sql/stdlib/android/process_metadata.sql
index a50b033..a3d47f2 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/process_metadata.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/android/process_metadata.sql
@@ -20,6 +20,47 @@
FROM package_list
GROUP BY 1;
+CREATE PERFETTO FUNCTION _android_package_for_process(
+ uid INT,
+ uid_count INT,
+ process_name STRING
+)
+RETURNS TABLE(
+ package_name STRING,
+ version_code INT,
+ debuggable BOOL
+)
+AS
+WITH min_distance AS (
+ SELECT
+ -- SQLite allows omitting the group-by for the MIN: the other columns
+ -- will match the row with the minimum value.
+ MIN(LENGTH($process_name) - LENGTH(package_name)),
+ package_name,
+ version_code,
+ debuggable
+ FROM package_list
+ WHERE (
+ (
+ $uid = uid
+ AND (
+ -- unique match
+ $uid_count = 1
+ -- or process name is a prefix the package name
+ OR $process_name GLOB package_name || '*'
+ )
+ )
+ OR
+ (
+ -- isolated processes can only be matched based on the name
+ $uid >= 90000 AND $uid < 100000
+ AND STR_SPLIT($process_name, ':', 0) GLOB package_name || '*'
+ )
+ )
+)
+SELECT package_name, version_code, debuggable
+FROM min_distance;
+
-- Data about packages running on the process.
CREATE PERFETTO TABLE android_process_metadata(
-- Process upid.
@@ -57,21 +98,6 @@
plist.debuggable
FROM process
LEFT JOIN _uid_package_count ON process.android_appid = _uid_package_count.uid
-LEFT JOIN package_list plist
- ON (
- (
- process.android_appid = plist.uid
- AND _uid_package_count.uid = plist.uid
- AND (
- -- unique match
- _uid_package_count.cnt = 1
- -- or process name starts with the package name
- OR process.name GLOB plist.package_name || '*')
- )
- OR
- (
- -- isolated processes can only be matched based on the name
- process.android_appid >= 90000 AND process.android_appid < 100000
- AND STR_SPLIT(process.name, ':', 0) = plist.package_name
- )
- );
+LEFT JOIN _android_package_for_process(
+ process.android_appid, _uid_package_count.cnt, process.name
+) AS plist;
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/suspend.sql b/src/trace_processor/perfetto_sql/stdlib/android/suspend.sql
index 0d31b0d..1551ea6 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/suspend.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/android/suspend.sql
@@ -73,7 +73,9 @@
FROM awake_slice
UNION ALL
SELECT ts, dur, 'suspended' AS power_state
-FROM suspend_slice;
+FROM suspend_slice
+ORDER BY ts; -- Order by will cause Perfetto table to index by ts.
+
-- Extracts the duration without counting CPU suspended time from an event.
-- This is the same as converting an event duration from wall clock to monotonic clock.
diff --git a/src/trace_processor/read_trace.cc b/src/trace_processor/read_trace.cc
index 06bd39d..9d5a1f7 100644
--- a/src/trace_processor/read_trace.cc
+++ b/src/trace_processor/read_trace.cc
@@ -25,12 +25,12 @@
#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/gzip/gzip_trace_parser.h"
#include "src/trace_processor/importers/proto/proto_trace_tokenizer.h"
#include "src/trace_processor/read_trace_internal.h"
#include "src/trace_processor/util/gzip_utils.h"
#include "src/trace_processor/util/status_macros.h"
+#include "src/trace_processor/util/trace_type.h"
#include "protos/perfetto/trace/trace.pbzero.h"
#include "protos/perfetto/trace/trace_packet.pbzero.h"
diff --git a/src/trace_processor/sorter/BUILD.gn b/src/trace_processor/sorter/BUILD.gn
index 8d4c9f9..12ccf3985 100644
--- a/src/trace_processor/sorter/BUILD.gn
+++ b/src/trace_processor/sorter/BUILD.gn
@@ -32,6 +32,7 @@
"../importers/common:parser_types",
"../importers/common:trace_parser_hdr",
"../importers/fuchsia:fuchsia_record",
+ "../importers/perf:record",
"../importers/systrace:systrace_line",
"../storage",
"../types",
diff --git a/src/trace_processor/sorter/trace_sorter.cc b/src/trace_processor/sorter/trace_sorter.cc
index a2d9861..9ccea77 100644
--- a/src/trace_processor/sorter/trace_sorter.cc
+++ b/src/trace_processor/sorter/trace_sorter.cc
@@ -15,20 +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)
@@ -116,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;
@@ -127,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)
@@ -188,7 +199,7 @@
switch (static_cast<TimestampedEvent::Type>(event.event_type)) {
case TimestampedEvent::Type::kPerfRecord:
context.perf_record_parser->ParsePerfRecord(
- event.ts, token_buffer_.Extract<TraceBlobView>(id));
+ event.ts, token_buffer_.Extract<perf_importer::Record>(id));
return;
case TimestampedEvent::Type::kTracePacket:
context.proto_trace_parser->ParseTracePacket(
@@ -275,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<TraceBlobView>(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:
@@ -299,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");
@@ -342,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 5ea0e0d..dd81920 100644
--- a/src/trace_processor/sorter/trace_sorter.h
+++ b/src/trace_processor/sorter/trace_sorter.h
@@ -18,26 +18,33 @@
#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"
#include "src/trace_processor/importers/systrace/systrace_line.h"
#include "src/trace_processor/sorter/trace_token_buffer.h"
#include "src/trace_processor/storage/trace_storage.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 {
// 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
@@ -104,7 +111,7 @@
inline void PushPerfRecord(
int64_t timestamp,
- TraceBlobView record,
+ perf_importer::Record record,
std::optional<MachineId> machine_id = std::nullopt) {
TraceTokenBuffer::Id id = token_buffer_.Append(std::move(record));
AppendNonFtraceEvent(timestamp, TimestampedEvent::Type::kPerfRecord, id,
@@ -218,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();
}
@@ -294,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 {
@@ -357,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) {
@@ -400,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()};
}
@@ -447,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/sqlite/db_sqlite_table.cc b/src/trace_processor/sqlite/db_sqlite_table.cc
index 60a8122..cc4f545 100644
--- a/src/trace_processor/sqlite/db_sqlite_table.cc
+++ b/src/trace_processor/sqlite/db_sqlite_table.cc
@@ -571,7 +571,7 @@
// Distinct:
idx_str += "D";
- if (ob_idxes.size() == 1) {
+ if (ob_idxes.size() == 1 && PERFETTO_POPCOUNT(info->colUsed) == 1) {
switch (sqlite3_vtab_distinct(info)) {
case 0:
case 1:
diff --git a/src/trace_processor/sqlite/module_lifecycle_manager.h b/src/trace_processor/sqlite/module_lifecycle_manager.h
index 66054d5..f1f93e8 100644
--- a/src/trace_processor/sqlite/module_lifecycle_manager.h
+++ b/src/trace_processor/sqlite/module_lifecycle_manager.h
@@ -67,6 +67,11 @@
public:
// Per-vtab state. The pointer to this class should be stored in the Vtab.
struct PerVtabState {
+ private:
+ // The below fields should only be accessed by the manager, use GetState to
+ // access the state from outside this class.
+ friend class ModuleStateManager<Module>;
+
ModuleStateManager* manager;
bool disconnected = false;
std::string table_name;
diff --git a/src/trace_processor/storage/stats.h b/src/trace_processor/storage/stats.h
index 9067b09..c8d58ca 100644
--- a/src/trace_processor/storage/stats.h
+++ b/src/trace_processor/storage/stats.h
@@ -155,7 +155,13 @@
F(traced_buf_patches_succeeded, kIndexed, kInfo, kTrace, ""), \
F(traced_buf_readaheads_failed, kIndexed, kInfo, kTrace, ""), \
F(traced_buf_readaheads_succeeded, kIndexed, kInfo, kTrace, ""), \
- F(traced_buf_trace_writer_packet_loss, kIndexed, kDataLoss, kTrace, ""), \
+ F(traced_buf_trace_writer_packet_loss, kIndexed, kDataLoss, kTrace, \
+ "The tracing service observed packet loss for this buffer during this " \
+ "tracing session. This also counts packet loss that happened before " \
+ "the RING_BUFFER start or after the DISCARD buffer end."), \
+ F(traced_buf_sequence_packet_loss, kIndexed, kDataLoss, kAnalysis, \
+ "The number of groups of consecutive packets lost in each sequence for " \
+ "this buffer"), \
F(traced_buf_write_wrap_count, kIndexed, kInfo, kTrace, ""), \
F(traced_chunks_discarded, kSingle, kInfo, kTrace, ""), \
F(traced_data_sources_registered, kSingle, kInfo, kTrace, ""), \
@@ -262,8 +268,13 @@
F(perf_process_shard_count, kIndexed, kInfo, kTrace, ""), \
F(perf_chosen_process_shard, kIndexed, kInfo, kTrace, ""), \
F(perf_guardrail_stop_ts, kIndexed, kDataLoss, kTrace, ""), \
- F(perf_samples_skipped, kSingle, kInfo, kTrace, ""), \
+ F(perf_unknown_record_type, kIndexed, kInfo, kAnalysis, ""), \
+ F(perf_record_skipped, kSingle, kError, kAnalysis, ""), \
+ F(perf_samples_skipped, kSingle, kError, kAnalysis, ""), \
+ F(perf_features_skipped, kIndexed, kInfo, kAnalysis, ""), \
F(perf_samples_skipped_dataloss, kSingle, kDataLoss, kTrace, ""), \
+ F(perf_dummy_mapping_used, kSingle, kInfo, kAnalysis, ""), \
+ F(perf_invalid_event_id, kSingle, kError, kTrace, ""), \
F(memory_snapshot_parser_failure, kSingle, kError, kAnalysis, ""), \
F(thread_time_in_state_out_of_order, kSingle, kError, kAnalysis, ""), \
F(thread_time_in_state_unknown_cpu_freq, \
diff --git a/src/trace_processor/trace_processor_context.cc b/src/trace_processor/trace_processor_context.cc
index 2299a1b..ec20623 100644
--- a/src/trace_processor/trace_processor_context.cc
+++ b/src/trace_processor/trace_processor_context.cc
@@ -31,6 +31,7 @@
#include "src/trace_processor/importers/common/machine_tracker.h"
#include "src/trace_processor/importers/common/mapping_tracker.h"
#include "src/trace_processor/importers/common/metadata_tracker.h"
+#include "src/trace_processor/importers/common/process_track_translation_table.h"
#include "src/trace_processor/importers/common/process_tracker.h"
#include "src/trace_processor/importers/common/sched_event_tracker.h"
#include "src/trace_processor/importers/common/slice_tracker.h"
@@ -45,6 +46,7 @@
#include "src/trace_processor/importers/proto/track_event.descriptor.h"
#include "src/trace_processor/importers/proto/track_event_module.h"
#include "src/trace_processor/sorter/trace_sorter.h"
+#include "src/trace_processor/trace_reader_registry.h"
#include "src/trace_processor/types/destructible.h"
namespace perfetto {
@@ -52,6 +54,7 @@
TraceProcessorContext::TraceProcessorContext(const InitArgs& args)
: config(args.config), storage(args.storage) {
+ reader_registry = std::make_unique<TraceReaderRegistry>(this);
// Init the trackers.
machine_tracker.reset(new MachineTracker(this, args.raw_machine_id));
if (!machine_id()) {
@@ -67,6 +70,8 @@
event_tracker.reset(new EventTracker(this));
sched_event_tracker.reset(new SchedEventTracker(this));
process_tracker.reset(new ProcessTracker(this));
+ process_track_translation_table.reset(
+ new ProcessTrackTranslationTable(storage.get()));
clock_tracker.reset(new ClockTracker(this));
clock_converter.reset(new ClockConverter(this));
mapping_tracker.reset(new MappingTracker(this));
diff --git a/src/trace_processor/trace_processor_impl.cc b/src/trace_processor/trace_processor_impl.cc
index 7bba0e9..fb9b294 100644
--- a/src/trace_processor/trace_processor_impl.cc
+++ b/src/trace_processor/trace_processor_impl.cc
@@ -46,6 +46,7 @@
#include "src/trace_processor/importers/android_bugreport/android_bugreport_parser.h"
#include "src/trace_processor/importers/common/clock_tracker.h"
#include "src/trace_processor/importers/common/metadata_tracker.h"
+#include "src/trace_processor/importers/common/trace_parser.h"
#include "src/trace_processor/importers/fuchsia/fuchsia_trace_parser.h"
#include "src/trace_processor/importers/fuchsia/fuchsia_trace_tokenizer.h"
#include "src/trace_processor/importers/gzip/gzip_trace_parser.h"
@@ -53,8 +54,8 @@
#include "src/trace_processor/importers/json/json_trace_tokenizer.h"
#include "src/trace_processor/importers/json/json_utils.h"
#include "src/trace_processor/importers/ninja/ninja_log_parser.h"
-#include "src/trace_processor/importers/perf/perf_data_parser.h"
#include "src/trace_processor/importers/perf/perf_data_tokenizer.h"
+#include "src/trace_processor/importers/perf/record_parser.h"
#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"
@@ -110,6 +111,7 @@
#include "src/trace_processor/storage/trace_storage.h"
#include "src/trace_processor/tp_metatrace.h"
#include "src/trace_processor/trace_processor_storage_impl.h"
+#include "src/trace_processor/trace_reader_registry.h"
#include "src/trace_processor/types/trace_processor_context.h"
#include "src/trace_processor/types/variadic.h"
#include "src/trace_processor/util/descriptors.h"
@@ -339,27 +341,34 @@
TraceProcessorImpl::TraceProcessorImpl(const Config& cfg)
: TraceProcessorStorageImpl(cfg), config_(cfg) {
- context_.fuchsia_trace_tokenizer =
- std::make_unique<FuchsiaTraceTokenizer>(&context_);
+ context_.reader_registry->RegisterTraceReader<FuchsiaTraceTokenizer>(
+ kFuchsiaTraceType);
context_.fuchsia_record_parser =
std::make_unique<FuchsiaTraceParser>(&context_);
- context_.ninja_log_parser = std::make_unique<NinjaLogParser>(&context_);
- context_.systrace_trace_parser =
- std::make_unique<SystraceTraceParser>(&context_);
- context_.perf_data_trace_tokenizer =
- std::make_unique<perf_importer::PerfDataTokenizer>(&context_);
+
+ context_.reader_registry->RegisterTraceReader<SystraceTraceParser>(
+ kSystraceTraceType);
+ context_.reader_registry->RegisterTraceReader<NinjaLogParser>(
+ kNinjaLogTraceType);
+
+ context_.reader_registry
+ ->RegisterTraceReader<perf_importer::PerfDataTokenizer>(
+ kPerfDataTraceType);
context_.perf_record_parser =
- std::make_unique<perf_importer::PerfDataParser>(&context_);
+ std::make_unique<perf_importer::RecordParser>(&context_);
if (util::IsGzipSupported()) {
- context_.gzip_trace_parser = std::make_unique<GzipTraceParser>(&context_);
- context_.android_bugreport_parser =
- std::make_unique<AndroidBugreportParser>(&context_);
+ context_.reader_registry->RegisterTraceReader<GzipTraceParser>(
+ kGzipTraceType);
+ context_.reader_registry->RegisterTraceReader<GzipTraceParser>(
+ kCtraceTraceType);
+ context_.reader_registry->RegisterTraceReader<AndroidBugreportParser>(
+ kAndroidBugreportTraceType);
}
if (json::IsJsonSupported()) {
- context_.json_trace_tokenizer =
- std::make_unique<JsonTraceTokenizer>(&context_);
+ context_.reader_registry->RegisterTraceReader<JsonTraceTokenizer>(
+ kJsonTraceType);
context_.json_trace_parser =
std::make_unique<JsonTraceParserImpl>(&context_);
}
diff --git a/src/trace_processor/trace_processor_storage_impl.cc b/src/trace_processor/trace_processor_storage_impl.cc
index 3a686e8..243e3da 100644
--- a/src/trace_processor/trace_processor_storage_impl.cc
+++ b/src/trace_processor/trace_processor_storage_impl.cc
@@ -30,6 +30,7 @@
#include "src/trace_processor/importers/common/machine_tracker.h"
#include "src/trace_processor/importers/common/mapping_tracker.h"
#include "src/trace_processor/importers/common/metadata_tracker.h"
+#include "src/trace_processor/importers/common/process_track_translation_table.h"
#include "src/trace_processor/importers/common/process_tracker.h"
#include "src/trace_processor/importers/common/sched_event_tracker.h"
#include "src/trace_processor/importers/common/slice_tracker.h"
@@ -45,6 +46,7 @@
#include "src/trace_processor/importers/proto/proto_trace_reader.h"
#include "src/trace_processor/importers/proto/track_event.descriptor.h"
#include "src/trace_processor/sorter/trace_sorter.h"
+#include "src/trace_processor/trace_reader_registry.h"
#include "src/trace_processor/util/descriptors.h"
namespace perfetto {
@@ -52,6 +54,8 @@
TraceProcessorStorageImpl::TraceProcessorStorageImpl(const Config& cfg)
: context_({cfg, std::make_shared<TraceStorage>(cfg)}) {
+ context_.reader_registry->RegisterTraceReader<ProtoTraceReader>(
+ kProtoTraceType);
context_.proto_trace_parser =
std::make_unique<ProtoTraceParserImpl>(&context_);
RegisterDefaultModules(&context_);
diff --git a/src/trace_processor/trace_reader_registry.cc b/src/trace_processor/trace_reader_registry.cc
new file mode 100644
index 0000000..09c5c7b
--- /dev/null
+++ b/src/trace_processor/trace_reader_registry.cc
@@ -0,0 +1,68 @@
+/*
+ * 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/trace_reader_registry.h"
+#include "perfetto/base/logging.h"
+#include "src/trace_processor/importers/common/chunked_trace_reader.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+#include "src/trace_processor/util/gzip_utils.h"
+#include "src/trace_processor/util/trace_type.h"
+
+namespace perfetto::trace_processor {
+namespace {
+const char kNoZlibErr[] =
+ "Cannot open compressed trace. zlib not enabled in the build config";
+
+bool RequiresZlibSupport(TraceType type) {
+ switch (type) {
+ case kGzipTraceType:
+ case kCtraceTraceType:
+ case kAndroidBugreportTraceType:
+ return true;
+
+ case kNinjaLogTraceType:
+ case kSystraceTraceType:
+ case kPerfDataTraceType:
+ case kUnknownTraceType:
+ case kJsonTraceType:
+ case kFuchsiaTraceType:
+ case kProtoTraceType:
+ return false;
+ }
+ PERFETTO_FATAL("For GCC");
+}
+} // namespace
+
+void TraceReaderRegistry::RegisterFactory(TraceType trace_type,
+ Factory factory) {
+ PERFETTO_CHECK(factories_.Insert(trace_type, std::move(factory)).second);
+}
+
+base::StatusOr<std::unique_ptr<ChunkedTraceReader>>
+TraceReaderRegistry::CreateTraceReader(TraceType type) {
+ if (auto it = factories_.Find(type); it) {
+ return (*it)(context_);
+ }
+
+ if (RequiresZlibSupport(type) && !util::IsGzipSupported()) {
+ return base::ErrStatus("%s support is disabled. %s", ToString(type),
+ kNoZlibErr);
+ }
+
+ return base::ErrStatus("%s support is disabled", ToString(type));
+}
+
+} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/trace_reader_registry.h b/src/trace_processor/trace_reader_registry.h
new file mode 100644
index 0000000..8e9b039
--- /dev/null
+++ b/src/trace_processor/trace_reader_registry.h
@@ -0,0 +1,74 @@
+/*
+ * 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_TRACE_READER_REGISTRY_H_
+#define SRC_TRACE_PROCESSOR_TRACE_READER_REGISTRY_H_
+
+#include <functional>
+#include <memory>
+#include <optional>
+
+#include "perfetto/ext/base/flat_hash_map.h"
+
+#include "perfetto/ext/base/status_or.h"
+#include "src/trace_processor/importers/common/trace_parser.h"
+#include "src/trace_processor/sorter/trace_sorter.h"
+#include "src/trace_processor/util/trace_type.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+class ChunkedTraceReader;
+class TraceProcessorContext;
+
+// Maps `TraceType` values to `ChunkedTraceReader` subclasses.
+// This class is used to create `ChunkedTraceReader` instances for a given
+// `TraceType`.
+class TraceReaderRegistry {
+ public:
+ explicit TraceReaderRegistry(TraceProcessorContext* context)
+ : context_(context) {}
+
+ // Registers a mapping from `TraceType` value to `ChunkedTraceReader`
+ // subclass. Only one such mapping can be registered per `TraceType` value.
+ template <typename Reader>
+ void RegisterTraceReader(TraceType trace_type) {
+ RegisterFactory(trace_type, [](TraceProcessorContext* ctxt) {
+ return std::make_unique<Reader>(ctxt);
+ });
+ }
+
+ // Creates a new `ChunkedTraceReader` instance for the given `type`. Returns
+ // an error if no mapping has been previously registered.
+ base::StatusOr<std::unique_ptr<ChunkedTraceReader>> CreateTraceReader(
+ TraceType type);
+
+ private:
+ using Factory = std::function<std::unique_ptr<ChunkedTraceReader>(
+ TraceProcessorContext*)>;
+ void RegisterFactory(TraceType trace_type, Factory factory);
+
+ TraceProcessorContext* const context_;
+ base::FlatHashMap<TraceType,
+ std::function<std::unique_ptr<ChunkedTraceReader>(
+ TraceProcessorContext*)>>
+ factories_;
+};
+
+} // namespace trace_processor
+} // namespace perfetto
+
+#endif // SRC_TRACE_PROCESSOR_TRACE_READER_REGISTRY_H_
diff --git a/src/trace_processor/types/BUILD.gn b/src/trace_processor/types/BUILD.gn
index 6b66c53..9fbff7a 100644
--- a/src/trace_processor/types/BUILD.gn
+++ b/src/trace_processor/types/BUILD.gn
@@ -32,6 +32,7 @@
"../../../include/perfetto/trace_processor",
"../containers",
"../tables:tables_python",
+ "../util:trace_type",
]
}
diff --git a/src/trace_processor/types/trace_processor_context.h b/src/trace_processor/types/trace_processor_context.h
index 5a91d78..fd72005 100644
--- a/src/trace_processor/types/trace_processor_context.h
+++ b/src/trace_processor/types/trace_processor_context.h
@@ -23,24 +23,11 @@
#include "perfetto/trace_processor/basic_types.h"
#include "src/trace_processor/tables/metadata_tables_py.h"
#include "src/trace_processor/types/destructible.h"
+#include "src/trace_processor/util/trace_type.h"
namespace perfetto {
namespace trace_processor {
-enum TraceType {
- kUnknownTraceType,
- kProtoTraceType,
- kJsonTraceType,
- kFuchsiaTraceType,
- kSystraceTraceType,
- kGzipTraceType,
- kCtraceTraceType,
- kNinjaLogTraceType,
- kAndroidBugreportTraceType,
- kPerfDataTraceType,
-};
-
-class AndroidProbesTracker;
class ArgsTracker;
class ArgsTranslationTable;
class AsyncTrackSetTracker;
@@ -66,12 +53,14 @@
class PerfRecordParser;
class PerfSampleTracker;
class ProcessTracker;
+class ProcessTrackTranslationTable;
class ProtoImporterModule;
class ProtoTraceParser;
class SchedEventTracker;
class SliceTracker;
class SliceTranslationTable;
class StackProfileTracker;
+class TraceReaderRegistry;
class TraceSorter;
class TraceStorage;
class TrackEventModule;
@@ -99,6 +88,8 @@
// |storage| is shared among multiple contexts in multi-machine tracing.
std::shared_ptr<TraceStorage> storage;
+ std::unique_ptr<TraceReaderRegistry> reader_registry;
+
std::unique_ptr<ChunkedTraceReader> chunk_reader;
// The sorter is used to sort trace data by timestamp and is shared among
@@ -118,6 +109,7 @@
std::unique_ptr<SliceTranslationTable> slice_translation_table;
std::unique_ptr<FlowTracker> flow_tracker;
std::unique_ptr<ProcessTracker> process_tracker;
+ std::unique_ptr<ProcessTrackTranslationTable> process_track_translation_table;
std::unique_ptr<EventTracker> event_tracker;
std::unique_ptr<SchedEventTracker> sched_event_tracker;
std::unique_ptr<ClockTracker> clock_tracker;
@@ -153,17 +145,6 @@
std::unique_ptr<Destructible> jit_tracker; // JitTracker
// clang-format on
- // These fields are trace readers which will be called by |forwarding_parser|
- // once the format of the trace is discovered. They are placed here as they
- // are only available in the lib target.
- std::unique_ptr<ChunkedTraceReader> json_trace_tokenizer;
- std::unique_ptr<ChunkedTraceReader> fuchsia_trace_tokenizer;
- std::unique_ptr<ChunkedTraceReader> ninja_log_parser;
- std::unique_ptr<ChunkedTraceReader> android_bugreport_parser;
- std::unique_ptr<ChunkedTraceReader> systrace_trace_parser;
- std::unique_ptr<ChunkedTraceReader> gzip_trace_parser;
- std::unique_ptr<ChunkedTraceReader> perf_data_trace_tokenizer;
-
std::unique_ptr<ProtoTraceParser> proto_trace_parser;
// These fields are trace parsers which will be called by |forwarding_parser|
diff --git a/src/trace_processor/util/BUILD.gn b/src/trace_processor/util/BUILD.gn
index 5ca39a1..6c40069 100644
--- a/src/trace_processor/util/BUILD.gn
+++ b/src/trace_processor/util/BUILD.gn
@@ -266,6 +266,17 @@
]
}
+source_set("trace_type") {
+ sources = [
+ "trace_type.cc",
+ "trace_type.h",
+ ]
+ deps = [
+ "../../../gn:default_deps",
+ "../../../include/perfetto/ext/base",
+ ]
+}
+
source_set("unittests") {
sources = [
"bump_allocator_unittest.cc",
diff --git a/src/trace_processor/util/file_buffer.cc b/src/trace_processor/util/file_buffer.cc
index aa92348..1dbd10b 100644
--- a/src/trace_processor/util/file_buffer.cc
+++ b/src/trace_processor/util/file_buffer.cc
@@ -39,10 +39,11 @@
end_offset_ += size;
}
-bool FileBuffer::PopFrontBytesUntil(const size_t target_offset) {
+bool FileBuffer::PopFrontUntil(const size_t target_offset) {
+ PERFETTO_CHECK(file_offset() <= target_offset);
while (!data_.empty()) {
Entry& entry = data_.front();
- if (target_offset <= entry.file_offset) {
+ if (target_offset == entry.file_offset) {
return true;
}
const size_t bytes_to_pop = target_offset - entry.file_offset;
@@ -110,9 +111,8 @@
auto it = std::upper_bound(
data_.begin(), data_.end(), offset,
[](size_t offset, const Entry& rhs) { return offset < rhs.file_offset; });
- if (it == data_.begin()) {
- return end();
- }
+ // This can only happen if too much data was popped.
+ PERFETTO_CHECK(it != data_.begin());
return std::prev(it);
}
diff --git a/src/trace_processor/util/file_buffer.h b/src/trace_processor/util/file_buffer.h
index 07de20f..35e7384 100644
--- a/src/trace_processor/util/file_buffer.h
+++ b/src/trace_processor/util/file_buffer.h
@@ -39,6 +39,8 @@
// Trivial empty ctor.
FileBuffer() = default;
+ bool empty() const { return data_.empty(); }
+
// Returns the offset to the start of the buffered window of data.
size_t file_offset() const {
return data_.empty() ? end_offset_ : data_.front().file_offset;
@@ -47,10 +49,19 @@
// Adds a `TraceBlobView` at the back.
void PushBack(TraceBlobView view);
- // Shrinks the buffer by dropping bytes from the front of the buffer until the
+ // Shrinks the buffer by dropping data from the front of the buffer until the
// given offset is reached. If not enough data is present as much data as
// possible will be dropped and `false` will be returned.
- bool PopFrontBytesUntil(size_t offset);
+ // ATTENTION: If `offset` < 'file_offset()' (i.e. you try to access data
+ // previously popped) this method will CHECK fail.
+ bool PopFrontUntil(size_t offset);
+
+ // Shrinks the buffer by dropping `bytes` from the front of the buffer. If not
+ // enough data is present as much data as possible will be dropped and `false`
+ // will be returned.
+ bool PopFrontBytes(size_t bytes) {
+ return PopFrontUntil(file_offset() + bytes);
+ }
// Similar to `TraceBlobView::slice_off`, creates a slice with data starting
// at `offset` and of the given `length`. This method might need to allocate a
@@ -58,8 +69,8 @@
// TraceBlobView instances). If not enough data is present `std::nullopt` is
// returned.
//
- // ATTENTION: If `offset` < 'file_offset()' this method will never return a
- // value.
+ // ATTENTION: If `offset` < 'file_offset()' (i.e. you try to access data
+ // previously popped) this method will CHECK fail.
std::optional<TraceBlobView> SliceOff(size_t offset, size_t length) const;
private:
diff --git a/src/trace_processor/util/file_buffer_unittest.cc b/src/trace_processor/util/file_buffer_unittest.cc
index 418cf76..1518fe4 100644
--- a/src/trace_processor/util/file_buffer_unittest.cc
+++ b/src/trace_processor/util/file_buffer_unittest.cc
@@ -16,6 +16,7 @@
#include "src/trace_processor/util/file_buffer.h"
+#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <cstring>
@@ -43,11 +44,9 @@
: expected_data_(expected_data) {}
bool MatchAndExplain(const ArgType& arg,
::testing ::MatchResultListener*) const override {
- if (expected_data_.size() != arg.size()) {
- return false;
- }
- return memcmp(expected_data_.data(), arg.data(), expected_data_.size()) ==
- 0;
+ return std::equal(expected_data_.data(),
+ expected_data_.data() + expected_data_.size(),
+ arg.data(), arg.data() + arg.size());
}
void DescribeTo(::std ::ostream*) const override {}
void DescribeNegationTo(::std ::ostream*) const override {}
@@ -108,7 +107,7 @@
FileBuffer buffer = CreateFileBuffer(Slice(expected_data, kChunkSize));
for (size_t file_offset = 0; file_offset <= kExpectedSize; ++file_offset) {
- EXPECT_TRUE(buffer.PopFrontBytesUntil(file_offset));
+ EXPECT_TRUE(buffer.PopFrontUntil(file_offset));
for (size_t off = file_offset; off <= kExpectedSize; ++off) {
auto expected = expected_data.slice_off(off, kExpectedSize - off);
std::optional<TraceBlobView> tbv = buffer.SliceOff(off, expected.size());
@@ -143,18 +142,16 @@
--expected_size;
++expected_file_offset;
- buffer.PopFrontBytesUntil(expected_file_offset);
+ buffer.PopFrontUntil(expected_file_offset);
EXPECT_THAT(buffer.file_offset(), Eq(expected_file_offset));
- EXPECT_THAT(buffer.SliceOff(expected_file_offset - 1, 1), Eq(std::nullopt));
EXPECT_THAT(buffer.SliceOff(expected_file_offset, expected_size),
Optional(SameDataAs(expected_data.slice_off(
expected_data.size() - expected_size, expected_size))));
expected_size -= kChunkSize;
expected_file_offset += kChunkSize;
- buffer.PopFrontBytesUntil(expected_file_offset);
+ buffer.PopFrontUntil(expected_file_offset);
EXPECT_THAT(buffer.file_offset(), Eq(expected_file_offset));
- EXPECT_THAT(buffer.SliceOff(expected_file_offset - 1, 1), Eq(std::nullopt));
EXPECT_THAT(buffer.SliceOff(expected_file_offset, expected_size),
Optional(SameDataAs(expected_data.slice_off(
expected_data.size() - expected_size, expected_size))));
diff --git a/src/trace_processor/util/trace_type.cc b/src/trace_processor/util/trace_type.cc
new file mode 100644
index 0000000..d265b90
--- /dev/null
+++ b/src/trace_processor/util/trace_type.cc
@@ -0,0 +1,137 @@
+/*
+ * 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/util/trace_type.h"
+
+#include <algorithm>
+#include <cstddef>
+#include <cstdint>
+#include <string>
+
+#include "perfetto/base/logging.h"
+#include "perfetto/ext/base/string_utils.h"
+
+namespace perfetto::trace_processor {
+namespace {
+// Fuchsia traces have a magic number as documented here:
+// https://fuchsia.googlesource.com/fuchsia/+/HEAD/docs/development/tracing/trace-format/README.md#magic-number-record-trace-info-type-0
+constexpr uint64_t kFuchsiaMagicNumber = 0x0016547846040010;
+constexpr char kPerfMagic[] = "PERFILE2";
+
+inline bool isspace(unsigned char c) {
+ return ::isspace(c);
+}
+
+std::string RemoveWhitespace(std::string str) {
+ str.erase(std::remove_if(str.begin(), str.end(), isspace), str.end());
+ return str;
+}
+
+} // namespace
+
+const char* ToString(TraceType trace_type) {
+ switch (trace_type) {
+ case kJsonTraceType:
+ return "JSON trace";
+ case kProtoTraceType:
+ return "proto trace";
+ case kNinjaLogTraceType:
+ return "ninja log";
+ case kFuchsiaTraceType:
+ return "fuchsia trace";
+ case kSystraceTraceType:
+ return "systrace trace";
+ case kGzipTraceType:
+ return "gzip trace";
+ case kCtraceTraceType:
+ return "ctrace trace";
+ case kAndroidBugreportTraceType:
+ return "Android Bugreport";
+ case kPerfDataTraceType:
+ return "perf data";
+ case kUnknownTraceType:
+ return "unknown trace";
+ }
+ PERFETTO_FATAL("For GCC");
+}
+
+TraceType GuessTraceType(const uint8_t* data, size_t size) {
+ if (size == 0)
+ return kUnknownTraceType;
+ std::string start(reinterpret_cast<const char*>(data),
+ std::min<size_t>(size, kGuessTraceMaxLookahead));
+ if (size >= 8) {
+ uint64_t first_word;
+ memcpy(&first_word, data, sizeof(first_word));
+ if (first_word == kFuchsiaMagicNumber)
+ return kFuchsiaTraceType;
+ }
+ if (base::StartsWith(start, kPerfMagic)) {
+ return kPerfDataTraceType;
+ }
+ std::string start_minus_white_space = RemoveWhitespace(start);
+ if (base::StartsWith(start_minus_white_space, "{\""))
+ return kJsonTraceType;
+ if (base::StartsWith(start_minus_white_space, "[{\""))
+ return kJsonTraceType;
+
+ // Systrace with header but no leading HTML.
+ if (base::Contains(start, "# tracer"))
+ return kSystraceTraceType;
+
+ // Systrace with leading HTML.
+ // Both: <!DOCTYPE html> and <!DOCTYPE HTML> have been observed.
+ std::string lower_start = base::ToLower(start);
+ if (base::StartsWith(lower_start, "<!doctype html>") ||
+ base::StartsWith(lower_start, "<html>"))
+ return kSystraceTraceType;
+
+ // Traces obtained from atrace -z (compress).
+ // They all have the string "TRACE:" followed by 78 9C which is a zlib header
+ // for "deflate, default compression, window size=32K" (see b/208691037)
+ if (base::Contains(start, "TRACE:\n\x78\x9c"))
+ return kCtraceTraceType;
+
+ // Traces obtained from atrace without -z (no compression).
+ if (base::Contains(start, "TRACE:\n"))
+ return kSystraceTraceType;
+
+ // Ninja's build log (.ninja_log).
+ if (base::StartsWith(start, "# ninja log"))
+ return kNinjaLogTraceType;
+
+ // Systrace with no header or leading HTML.
+ if (base::StartsWith(start, " "))
+ return kSystraceTraceType;
+
+ // gzip'ed trace containing one of the other formats.
+ if (base::StartsWith(start, "\x1f\x8b"))
+ return kGzipTraceType;
+
+ 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 kUnknownTraceType;
+}
+
+} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/util/trace_type.h b/src/trace_processor/util/trace_type.h
new file mode 100644
index 0000000..fec7a74
--- /dev/null
+++ b/src/trace_processor/util/trace_type.h
@@ -0,0 +1,44 @@
+/*
+ * 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_UTIL_TRACE_TYPE_H_
+#define SRC_TRACE_PROCESSOR_UTIL_TRACE_TYPE_H_
+
+#include <cstddef>
+#include <cstdint>
+
+namespace perfetto::trace_processor {
+
+enum TraceType {
+ kUnknownTraceType,
+ kProtoTraceType,
+ kJsonTraceType,
+ kFuchsiaTraceType,
+ kSystraceTraceType,
+ kGzipTraceType,
+ kCtraceTraceType,
+ kNinjaLogTraceType,
+ kAndroidBugreportTraceType,
+ kPerfDataTraceType,
+};
+
+constexpr size_t kGuessTraceMaxLookahead = 64;
+TraceType GuessTraceType(const uint8_t* data, size_t size);
+const char* ToString(TraceType type);
+
+} // namespace perfetto::trace_processor
+
+#endif // SRC_TRACE_PROCESSOR_UTIL_TRACE_TYPE_H_
diff --git a/src/trace_redaction/main.cc b/src/trace_redaction/main.cc
index eec75fd..7014c0d 100644
--- a/src/trace_redaction/main.cc
+++ b/src/trace_redaction/main.cc
@@ -79,10 +79,11 @@
redactor.emplace_transform<PrunePackageList>();
redactor.emplace_transform<ScrubProcessStats>();
+ auto* comms_harness = redactor.emplace_transform<RedactSchedSwitchHarness>();
+ comms_harness->emplace_transform<ClearComms>();
+
auto* redact_ftrace_events = redactor.emplace_transform<RedactFtraceEvent>();
redact_ftrace_events
- ->emplace_back<RedactSchedSwitch::kFieldId, RedactSchedSwitch>();
- redact_ftrace_events
->emplace_back<RedactTaskNewTask::kFieldId, RedactTaskNewTask>();
redact_ftrace_events
->emplace_back<RedactProcessFree::kFieldId, RedactProcessFree>();
diff --git a/src/trace_redaction/redact_sched_switch.cc b/src/trace_redaction/redact_sched_switch.cc
index 83cb36f..87412c1 100644
--- a/src/trace_redaction/redact_sched_switch.cc
+++ b/src/trace_redaction/redact_sched_switch.cc
@@ -16,6 +16,8 @@
#include "src/trace_redaction/redact_sched_switch.h"
+#include "perfetto/protozero/scattered_heap_buffer.h"
+#include "src/trace_processor/util/status_macros.h"
#include "src/trace_redaction/proto_util.h"
#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h"
@@ -25,19 +27,12 @@
namespace perfetto::trace_redaction {
namespace {
-
-// TODO(vaage): Merge with RedactComm in redact_task_newtask.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 {};
+// 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;
}
-
} // namespace
// Redact sched switch trace events in an ftrace event bundle:
@@ -63,71 +58,170 @@
// 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.
-base::Status RedactSchedSwitch::Redact(
- const Context& context,
- const protos::pbzero::FtraceEventBundle::Decoder&,
- protozero::ProtoDecoder& event,
- protos::pbzero::FtraceEvent* event_message) const {
- if (!context.package_uid.has_value()) {
- return base::ErrStatus("RedactSchedSwitch: missing package uid");
- }
+SchedSwitchTransform::~SchedSwitchTransform() = default;
- if (!context.timeline) {
- return base::ErrStatus("RedactSchedSwitch: missing timeline");
- }
+base::Status RedactSchedSwitchHarness::Transform(const Context& context,
+ std::string* packet) const {
+ protozero::HeapBuffered<protos::pbzero::TracePacket> message;
+ protozero::ProtoDecoder decoder(*packet);
- // 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 =
- event.FindField(protos::pbzero::FtraceEvent::kTimestampFieldNumber);
- if (!timestamp.valid()) {
- return base::OkStatus();
- }
-
- auto sched_switch =
- event.FindField(protos::pbzero::FtraceEvent::kSchedSwitchFieldNumber);
- if (!sched_switch.valid()) {
- return base::ErrStatus(
- "RedactSchedSwitch: was used for unsupported field type");
- }
-
- protozero::ProtoDecoder sched_switch_decoder(sched_switch.as_bytes());
-
- auto prev_pid = sched_switch_decoder.FindField(
- protos::pbzero::SchedSwitchFtraceEvent::kPrevPidFieldNumber);
- auto next_pid = sched_switch_decoder.FindField(
- protos::pbzero::SchedSwitchFtraceEvent::kNextPidFieldNumber);
-
- // There must be a prev pid and a next pid. Otherwise, the event is invalid.
- // Dropping the event is the safest option.
- if (!prev_pid.valid() || !next_pid.valid()) {
- return base::OkStatus();
- }
-
- // Avoid making the message until we know that we have prev and next pids.
- auto sched_switch_message = event_message->set_sched_switch();
-
- for (auto field = sched_switch_decoder.ReadField(); field.valid();
- field = sched_switch_decoder.ReadField()) {
- switch (field.id()) {
- case protos::pbzero::SchedSwitchFtraceEvent::kNextCommFieldNumber:
- sched_switch_message->set_next_comm(
- RedactComm(context, timestamp.as_uint64(), next_pid.as_int32(),
- field.as_string()));
- break;
-
- case protos::pbzero::SchedSwitchFtraceEvent::kPrevCommFieldNumber:
- sched_switch_message->set_prev_comm(
- RedactComm(context, timestamp.as_uint64(), prev_pid.as_int32(),
- field.as_string()));
- break;
-
- default:
- proto_util::AppendField(field, sched_switch_message);
- break;
+ for (auto field = decoder.ReadField(); field.valid();
+ field = decoder.ReadField()) {
+ if (field.id() == protos::pbzero::TracePacket::kFtraceEventsFieldNumber) {
+ RETURN_IF_ERROR(
+ TransformFtraceEvents(context, field, message->set_ftrace_events()));
+ } else {
+ proto_util::AppendField(field, message.get());
}
}
+ packet->assign(message.SerializeAsString());
+
+ return base::OkStatus();
+}
+
+base::Status RedactSchedSwitchHarness::TransformFtraceEvents(
+ const Context& context,
+ protozero::Field ftrace_events,
+ protos::pbzero::FtraceEventBundle* message) const {
+ PERFETTO_DCHECK(ftrace_events.id() ==
+ protos::pbzero::TracePacket::kFtraceEventsFieldNumber);
+
+ protozero::ProtoDecoder decoder(ftrace_events.as_bytes());
+
+ auto cpu =
+ decoder.FindField(protos::pbzero::FtraceEventBundle::kCpuFieldNumber);
+ if (!cpu.valid()) {
+ return base::ErrStatus(
+ "RedactSchedSwitchHarness: missing cpu in ftrace event bundle.");
+ }
+
+ for (auto field = decoder.ReadField(); field.valid();
+ field = decoder.ReadField()) {
+ if (field.id() == protos::pbzero::FtraceEventBundle::kEventFieldNumber) {
+ RETURN_IF_ERROR(TransformFtraceEvent(context, cpu.as_int32(), field,
+ message->add_event()));
+ continue;
+ }
+
+ 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);
+ continue;
+ }
+
+ proto_util::AppendField(field, message);
+ }
+
+ return base::OkStatus();
+}
+
+base::Status RedactSchedSwitchHarness::TransformFtraceEvent(
+ const Context& context,
+ int32_t cpu,
+ protozero::Field ftrace_event,
+ protos::pbzero::FtraceEvent* message) const {
+ PERFETTO_DCHECK(ftrace_event.id() ==
+ protos::pbzero::FtraceEventBundle::kEventFieldNumber);
+
+ protozero::ProtoDecoder decoder(ftrace_event.as_bytes());
+
+ auto ts =
+ decoder.FindField(protos::pbzero::FtraceEvent::kTimestampFieldNumber);
+ if (!ts.valid()) {
+ return base::ErrStatus(
+ "RedactSchedSwitchHarness: missing timestamp in ftrace event.");
+ }
+
+ std::string scratch_str;
+
+ 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);
+ }
+ }
+
+ return base::OkStatus();
+}
+
+base::Status RedactSchedSwitchHarness::TransformFtraceEventSchedSwitch(
+ const Context& context,
+ uint64_t ts,
+ int32_t cpu,
+ protos::pbzero::SchedSwitchFtraceEvent::Decoder& sched_switch,
+ std::string* scratch_str,
+ protos::pbzero::SchedSwitchFtraceEvent* message) const {
+ auto has_fields = {
+ sched_switch.has_prev_comm(), sched_switch.has_prev_pid(),
+ sched_switch.has_prev_prio(), sched_switch.has_prev_state(),
+ sched_switch.has_next_comm(), sched_switch.has_next_pid(),
+ sched_switch.has_next_prio()};
+
+ if (!std::all_of(has_fields.begin(), has_fields.end(), IsTrue)) {
+ return base::ErrStatus(
+ "RedactSchedSwitchHarness: missing required SchedSwitchFtraceEvent "
+ "field.");
+ }
+
+ auto prev_pid = sched_switch.prev_pid();
+ auto prev_comm = sched_switch.prev_comm();
+
+ auto next_pid = sched_switch.next_pid();
+ auto next_comm = sched_switch.next_comm();
+
+ // There are 7 values in a sched switch message. Since 4 of the 7 can be
+ // replaced, it is easier/cleaner to go value-by-value. Go in proto-defined
+ // order.
+
+ 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));
+ }
+
+ message->set_prev_comm(*scratch_str); // FieldNumber = 1
+ message->set_prev_pid(prev_pid); // FieldNumber = 2
+ message->set_prev_prio(sched_switch.prev_prio()); // FieldNumber = 3
+ message->set_prev_state(sched_switch.prev_state()); // FieldNumber = 4
+
+ 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));
+ }
+
+ message->set_next_comm(*scratch_str); // FieldNumber = 5
+ message->set_next_pid(next_pid); // FieldNumber = 6
+ message->set_next_prio(sched_switch.next_prio()); // FieldNumber = 7
+
+ 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 {
+ PERFETTO_DCHECK(pid);
+ PERFETTO_DCHECK(comm);
+
+ if (!context.timeline->PidConnectsToUid(ts, *pid, *context.package_uid)) {
+ comm->clear();
+ }
+
return base::OkStatus();
}
diff --git a/src/trace_redaction/redact_sched_switch.h b/src/trace_redaction/redact_sched_switch.h
index bcfa30d..55f9a75 100644
--- a/src/trace_redaction/redact_sched_switch.h
+++ b/src/trace_redaction/redact_sched_switch.h
@@ -17,23 +17,63 @@
#ifndef SRC_TRACE_REDACTION_REDACT_SCHED_SWITCH_H_
#define SRC_TRACE_REDACTION_REDACT_SCHED_SWITCH_H_
-#include "src/trace_redaction/redact_ftrace_event.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 {
-// Goes through ftrace events and conditonally removes the comm values from
-// sched switch events.
-class RedactSchedSwitch : public FtraceEventRedaction {
+class SchedSwitchTransform {
public:
- static constexpr auto kFieldId =
- protos::pbzero::FtraceEvent::kSchedSwitchFieldNumber;
+ virtual ~SchedSwitchTransform();
+ virtual base::Status Transform(const Context& context,
+ uint64_t ts,
+ int32_t cpu,
+ int32_t* pid,
+ std::string* comm) const = 0;
+};
- base::Status Redact(
+// Goes through all sched switch events are modifies them.
+class RedactSchedSwitchHarness : public TransformPrimitive {
+ public:
+ base::Status Transform(const Context& context,
+ std::string* packet) const override;
+
+ template <class Transform>
+ void emplace_transform() {
+ transforms_.emplace_back(new Transform());
+ }
+
+ private:
+ base::Status TransformFtraceEvents(
const Context& context,
- const protos::pbzero::FtraceEventBundle::Decoder& bundle,
- protozero::ProtoDecoder& event,
- protos::pbzero::FtraceEvent* event_message) const override;
+ protozero::Field ftrace_events,
+ protos::pbzero::FtraceEventBundle* message) const;
+
+ base::Status TransformFtraceEvent(const Context& context,
+ int32_t cpu,
+ protozero::Field ftrace_event,
+ protos::pbzero::FtraceEvent* message) const;
+
+ // scratch_str is a reusable string, allowing comm modifications to be done in
+ // a shared buffer, avoiding allocations when processing ftrace events.
+ base::Status TransformFtraceEventSchedSwitch(
+ const Context& context,
+ uint64_t ts,
+ int32_t cpu,
+ protos::pbzero::SchedSwitchFtraceEvent::Decoder& sched_switch,
+ std::string* scratch_str,
+ protos::pbzero::SchedSwitchFtraceEvent* message) const;
+
+ std::vector<std::unique_ptr<SchedSwitchTransform>> transforms_;
+};
+
+class ClearComms : public SchedSwitchTransform {
+ base::Status Transform(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_integrationtest.cc b/src/trace_redaction/redact_sched_switch_integrationtest.cc
index f14d6e9..247d44b 100644
--- a/src/trace_redaction/redact_sched_switch_integrationtest.cc
+++ b/src/trace_redaction/redact_sched_switch_integrationtest.cc
@@ -16,9 +16,9 @@
#include <cstdint>
#include <string>
+#include <unordered_map>
#include "perfetto/base/status.h"
-#include "perfetto/ext/base/flat_hash_map.h"
#include "src/base/test/status_matchers.h"
#include "src/trace_redaction/collect_timeline_events.h"
#include "src/trace_redaction/find_package_uid.h"
@@ -36,23 +36,6 @@
namespace perfetto::trace_redaction {
-class RedactSchedSwitchIntegrationTest
- : public testing::Test,
- protected TraceRedactionIntegrationFixure {
- protected:
- void SetUp() override {
- trace_redactor()->emplace_collect<FindPackageUid>();
- trace_redactor()->emplace_collect<CollectTimelineEvents>();
-
- auto* ftrace_event_redactions =
- trace_redactor()->emplace_transform<RedactFtraceEvent>();
- ftrace_event_redactions
- ->emplace_back<RedactSchedSwitch::kFieldId, RedactSchedSwitch>();
-
- context()->package_name = "com.Unity.com.unity.multiplayer.samples.coop";
- }
-};
-
// >>> SELECT uid
// >>> FROM package_list
// >>> WHERE package_name='com.Unity.com.unity.multiplayer.samples.coop'
@@ -103,6 +86,35 @@
// | 7950 | UnityGfxDeviceW |
// | 7969 | UnityGfxDeviceW |
// +------+-----------------+
+class RedactSchedSwitchIntegrationTest
+ : public testing::Test,
+ protected TraceRedactionIntegrationFixure {
+ protected:
+ void SetUp() override {
+ trace_redactor()->emplace_collect<FindPackageUid>();
+ trace_redactor()->emplace_collect<CollectTimelineEvents>();
+
+ auto* harness =
+ trace_redactor()->emplace_transform<RedactSchedSwitchHarness>();
+ harness->emplace_transform<ClearComms>();
+
+ context()->package_name = "com.Unity.com.unity.multiplayer.samples.coop";
+ }
+
+ std::unordered_map<int32_t, std::string> expected_names_ = {
+ {7120, "Binder:7105_2"}, {7127, "UnityMain"},
+ {7142, "Job.worker 0"}, {7143, "Job.worker 1"},
+ {7144, "Job.worker 2"}, {7145, "Job.worker 3"},
+ {7146, "Job.worker 4"}, {7147, "Job.worker 5"},
+ {7148, "Job.worker 6"}, {7150, "Background Job."},
+ {7151, "Background Job."}, {7167, "UnityGfxDeviceW"},
+ {7172, "AudioTrack"}, {7174, "FMOD stream thr"},
+ {7180, "Binder:7105_3"}, {7184, "UnityChoreograp"},
+ {7945, "Filter0"}, {7946, "Filter1"},
+ {7947, "Thread-7"}, {7948, "FMOD mixer thre"},
+ {7950, "UnityGfxDeviceW"}, {7969, "UnityGfxDeviceW"},
+ };
+};
TEST_F(RedactSchedSwitchIntegrationTest, ClearsNonTargetSwitchComms) {
auto result = Redact();
@@ -114,30 +126,6 @@
auto redacted = LoadRedacted();
ASSERT_OK(redacted) << redacted.status().c_message();
- base::FlatHashMap<int32_t, std::string> expected_names;
- expected_names.Insert(7120, "Binder:7105_2");
- expected_names.Insert(7127, "UnityMain");
- expected_names.Insert(7142, "Job.worker 0");
- expected_names.Insert(7143, "Job.worker 1");
- expected_names.Insert(7144, "Job.worker 2");
- expected_names.Insert(7145, "Job.worker 3");
- expected_names.Insert(7146, "Job.worker 4");
- expected_names.Insert(7147, "Job.worker 5");
- expected_names.Insert(7148, "Job.worker 6");
- expected_names.Insert(7150, "Background Job.");
- expected_names.Insert(7151, "Background Job.");
- expected_names.Insert(7167, "UnityGfxDeviceW");
- expected_names.Insert(7172, "AudioTrack");
- expected_names.Insert(7174, "FMOD stream thr");
- expected_names.Insert(7180, "Binder:7105_3");
- expected_names.Insert(7184, "UnityChoreograp");
- expected_names.Insert(7945, "Filter0");
- expected_names.Insert(7946, "Filter1");
- expected_names.Insert(7947, "Thread-7");
- expected_names.Insert(7948, "FMOD mixer thre");
- expected_names.Insert(7950, "UnityGfxDeviceW");
- expected_names.Insert(7969, "UnityGfxDeviceW");
-
auto redacted_trace_data = LoadRedacted();
ASSERT_OK(redacted_trace_data) << redacted.status().c_message();
@@ -164,29 +152,29 @@
event_decoder.sched_switch());
ASSERT_TRUE(sched_decoder.has_next_pid());
- ASSERT_TRUE(sched_decoder.has_prev_pid());
-
- auto next_pid = sched_decoder.next_pid();
- auto prev_pid = sched_decoder.prev_pid();
+ ASSERT_TRUE(sched_decoder.has_next_comm());
// If the pid is expected, make sure it has the right now. If it is not
// expected, it should be missing.
- const auto* next_comm = expected_names.Find(next_pid);
- const auto* prev_comm = expected_names.Find(prev_pid);
+ auto next_pid = sched_decoder.next_pid();
+ auto next_comm = expected_names_.find(next_pid);
- EXPECT_TRUE(sched_decoder.has_next_comm());
- EXPECT_TRUE(sched_decoder.has_prev_comm());
-
- if (next_comm) {
- EXPECT_EQ(sched_decoder.next_comm().ToStdString(), *next_comm);
+ if (next_comm == expected_names_.end()) {
+ ASSERT_EQ(sched_decoder.next_comm().size, 0u);
} else {
- EXPECT_EQ(sched_decoder.next_comm().size, 0u);
+ ASSERT_EQ(sched_decoder.next_comm().ToStdString(), next_comm->second);
}
- if (prev_comm) {
- EXPECT_EQ(sched_decoder.prev_comm().ToStdString(), *prev_comm);
+ ASSERT_TRUE(sched_decoder.has_prev_pid());
+ ASSERT_TRUE(sched_decoder.has_prev_comm());
+
+ auto prev_pid = sched_decoder.prev_pid();
+ auto prev_comm = expected_names_.find(prev_pid);
+
+ if (prev_comm == expected_names_.end()) {
+ ASSERT_EQ(sched_decoder.prev_comm().size, 0u);
} else {
- EXPECT_EQ(sched_decoder.prev_comm().size, 0u);
+ ASSERT_EQ(sched_decoder.prev_comm().ToStdString(), prev_comm->second);
}
}
}
diff --git a/src/trace_redaction/redact_sched_switch_unittest.cc b/src/trace_redaction/redact_sched_switch_unittest.cc
index 72b65f4..88280d7 100644
--- a/src/trace_redaction/redact_sched_switch_unittest.cc
+++ b/src/trace_redaction/redact_sched_switch_unittest.cc
@@ -15,7 +15,6 @@
*/
#include "src/trace_redaction/redact_sched_switch.h"
-#include "perfetto/protozero/scattered_heap_buffer.h"
#include "src/base/test/status_matchers.h"
#include "test/gtest_and_gmock.h"
@@ -28,6 +27,7 @@
namespace perfetto::trace_redaction {
namespace {
+
constexpr uint64_t kUidA = 1;
constexpr uint64_t kUidB = 2;
constexpr uint64_t kUidC = 3;
@@ -36,167 +36,130 @@
constexpr int32_t kPidA = 11;
constexpr int32_t kPidB = 12;
-constexpr std::string_view kCommA = "comm-a";
-constexpr std::string_view kCommB = "comm-b";
+constexpr int32_t kCpuA = 0;
+
+constexpr uint64_t kFullStep = 1000;
+constexpr uint64_t kTimeA = 0;
+constexpr uint64_t kTimeB = kFullStep;
+
+constexpr auto kCommA = "comm-a";
+constexpr auto kCommB = "comm-b";
+constexpr auto kCommNone = "";
} // namespace
-// Tests which nested messages and fields are removed.
class RedactSchedSwitchTest : public testing::Test {
protected:
void SetUp() override {
- auto* event = bundle_.add_event();
+ // Create a packet where two pids are swapping back-and-forth.
+ auto* bundle = packet_.mutable_ftrace_events();
+ bundle->set_cpu(kCpuA);
- event->set_timestamp(123456789);
- event->set_pid(kPidA);
+ {
+ auto* event = bundle->add_event();
- auto* sched_switch = event->mutable_sched_switch();
- sched_switch->set_prev_comm(std::string(kCommA));
- sched_switch->set_prev_pid(kPidA);
- sched_switch->set_next_comm(std::string(kCommB));
- sched_switch->set_next_pid(kPidB);
+ event->set_timestamp(kTimeA);
+ event->set_pid(kPidA);
+
+ auto* sched_switch = event->mutable_sched_switch();
+ sched_switch->set_prev_comm(kCommA);
+ sched_switch->set_prev_pid(kPidA);
+ sched_switch->set_prev_prio(0);
+ sched_switch->set_prev_state(0);
+ sched_switch->set_next_comm(kCommB);
+ sched_switch->set_next_pid(kPidB);
+ sched_switch->set_next_prio(0);
+ }
+
+ {
+ auto* event = bundle->add_event();
+
+ event->set_timestamp(kTimeB);
+ event->set_pid(kPidB);
+
+ auto* sched_switch = event->mutable_sched_switch();
+ sched_switch->set_prev_comm(kCommB);
+ sched_switch->set_prev_pid(kPidB);
+ sched_switch->set_prev_prio(0);
+ sched_switch->set_prev_state(0);
+ sched_switch->set_next_comm(kCommA);
+ sched_switch->set_next_pid(kPidA);
+ sched_switch->set_next_prio(0);
+ }
+
+ // 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();
}
- base::Status Redact(const Context& context,
- protos::pbzero::FtraceEvent* event_message) {
- RedactSchedSwitch redact;
-
- auto bundle_str = bundle_.SerializeAsString();
- protos::pbzero::FtraceEventBundle::Decoder bundle_decoder(bundle_str);
-
- auto event_str = bundle_.event().back().SerializeAsString();
- protos::pbzero::FtraceEvent::Decoder event_decoder(event_str);
-
- return redact.Redact(context, bundle_decoder, event_decoder, event_message);
- }
-
- const std::string& event_string() const { return event_string_; }
-
- // This test breaks the rules for task_newtask and the timeline. The
- // timeline will report the task existing before the new task event. This
- // should not happen in the field, but it makes the test more robust.
- std::unique_ptr<ProcessThreadTimeline> CreatePopulatedTimeline() {
- auto timeline = std::make_unique<ProcessThreadTimeline>();
-
- timeline->Append(
- ProcessThreadTimeline::Event::Open(0, kPidA, kNoParent, kUidA));
- timeline->Append(
- ProcessThreadTimeline::Event::Open(0, kPidB, kNoParent, kUidB));
- timeline->Sort();
-
- return timeline;
- }
-
- private:
- std::string event_string_;
-
- std::unique_ptr<ProcessThreadTimeline> timeline_;
-
- protos::gen::FtraceEventBundle bundle_;
+ protos::gen::TracePacket packet_;
+ Context context_;
};
-TEST_F(RedactSchedSwitchTest, RejectMissingPackageUid) {
- RedactSchedSwitch redact;
+// 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) {
+ RedactSchedSwitchHarness redact;
+ redact.emplace_transform<ClearComms>();
- Context context;
- context.timeline = std::make_unique<ProcessThreadTimeline>();
+ context_.package_uid = kUidA;
- protozero::HeapBuffered<protos::pbzero::FtraceEvent> event_message;
- auto result = Redact(context, event_message.get());
- ASSERT_FALSE(result.ok());
+ 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[0].sched_switch().prev_pid(), kPidA);
+ ASSERT_EQ(events[0].sched_switch().prev_comm(), kCommA);
+
+ ASSERT_EQ(events[0].sched_switch().next_pid(), kPidB);
+ ASSERT_EQ(events[0].sched_switch().next_comm(), kCommNone);
+
+ ASSERT_EQ(events[1].sched_switch().prev_pid(), kPidB);
+ ASSERT_EQ(events[1].sched_switch().prev_comm(), kCommNone);
+
+ ASSERT_EQ(events[1].sched_switch().next_pid(), kPidA);
+ ASSERT_EQ(events[1].sched_switch().next_comm(), kCommA);
}
-TEST_F(RedactSchedSwitchTest, RejectMissingTimeline) {
- RedactSchedSwitch redact;
+// 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) {
+ RedactSchedSwitchHarness redact;
+ redact.emplace_transform<ClearComms>();
- Context context;
- context.package_uid = kUidA;
+ context_.package_uid = kUidC;
- protozero::HeapBuffered<protos::pbzero::FtraceEvent> event_message;
- auto result = Redact(context, event_message.get());
- ASSERT_FALSE(result.ok());
-}
+ auto packet_buffer = packet_.SerializeAsString();
-TEST_F(RedactSchedSwitchTest, ReplacePrevAndNextWithEmptyStrings) {
- RedactSchedSwitch redact;
+ ASSERT_OK(redact.Transform(context_, &packet_buffer));
- Context context;
- context.timeline = CreatePopulatedTimeline();
+ protos::gen::TracePacket packet;
+ ASSERT_TRUE(packet.ParseFromString(packet_buffer));
- // Neither pid is connected to the target package (see timeline
- // initialization).
- context.package_uid = kUidC;
+ const auto& bundle = packet.ftrace_events();
+ const auto& events = bundle.event();
- protozero::HeapBuffered<protos::pbzero::FtraceEvent> event_message;
- auto result = Redact(context, event_message.get());
- ASSERT_OK(result) << result.c_message();
+ ASSERT_EQ(events.size(), 2u);
- protos::gen::FtraceEvent event;
- event.ParseFromString(event_message.SerializeAsString());
+ ASSERT_EQ(events[0].sched_switch().prev_comm(), kCommNone);
+ ASSERT_EQ(events[0].sched_switch().next_comm(), kCommNone);
- ASSERT_TRUE(event.has_sched_switch());
-
- // Cleared prev and next comm.
- ASSERT_TRUE(event.sched_switch().has_prev_comm());
- ASSERT_TRUE(event.sched_switch().prev_comm().empty());
-
- ASSERT_TRUE(event.sched_switch().has_next_comm());
- ASSERT_TRUE(event.sched_switch().next_comm().empty());
-}
-
-TEST_F(RedactSchedSwitchTest, ReplacePrevWithEmptyStrings) {
- RedactSchedSwitch redact;
-
- Context context;
- context.timeline = CreatePopulatedTimeline();
-
- // Only next pid is connected to the target package (see timeline
- // initialization).
- context.package_uid = kUidB;
-
- protozero::HeapBuffered<protos::pbzero::FtraceEvent> event_message;
- auto result = Redact(context, event_message.get());
-
- ASSERT_OK(result) << result.c_message();
-
- protos::gen::FtraceEvent event;
- event.ParseFromString(event_message.SerializeAsString());
-
- ASSERT_TRUE(event.has_sched_switch());
-
- // Only cleared the prev comm.
- ASSERT_TRUE(event.sched_switch().has_prev_comm());
- ASSERT_TRUE(event.sched_switch().prev_comm().empty());
-
- ASSERT_TRUE(event.sched_switch().has_next_comm());
- ASSERT_FALSE(event.sched_switch().next_comm().empty());
-}
-
-TEST_F(RedactSchedSwitchTest, ReplaceNextWithEmptyStrings) {
- RedactSchedSwitch redact;
-
- Context context;
- context.timeline = CreatePopulatedTimeline();
-
- // Only prev pid is connected to the target package (see timeline
- // initialization).
- context.package_uid = kUidA;
-
- protozero::HeapBuffered<protos::pbzero::FtraceEvent> event_message;
- auto result = Redact(context, event_message.get());
- ASSERT_OK(result) << result.c_message();
-
- protos::gen::FtraceEvent event;
- event.ParseFromString(event_message.SerializeAsString());
-
- ASSERT_TRUE(event.has_sched_switch());
-
- ASSERT_TRUE(event.sched_switch().has_prev_comm());
- ASSERT_FALSE(event.sched_switch().prev_comm().empty());
-
- // Only cleared the next comm.
- ASSERT_TRUE(event.sched_switch().has_next_comm());
- ASSERT_TRUE(event.sched_switch().next_comm().empty());
+ ASSERT_EQ(events[1].sched_switch().prev_comm(), kCommNone);
+ ASSERT_EQ(events[1].sched_switch().next_comm(), kCommNone);
}
} // namespace perfetto::trace_redaction
diff --git a/src/traceconv/BUILD.gn b/src/traceconv/BUILD.gn
index 8cde4f4..22c02a6 100644
--- a/src/traceconv/BUILD.gn
+++ b/src/traceconv/BUILD.gn
@@ -91,6 +91,7 @@
"../../src/trace_processor/util:descriptors",
"../../src/trace_processor/util:gzip",
"../../src/trace_processor/util:protozero_to_text",
+ "../../src/trace_processor/util:trace_type",
]
sources = [
"deobfuscate_profile.cc",
diff --git a/src/traceconv/trace_to_text.cc b/src/traceconv/trace_to_text.cc
index ef7e03a..3fc82fe 100644
--- a/src/traceconv/trace_to_text.cc
+++ b/src/traceconv/trace_to_text.cc
@@ -26,10 +26,10 @@
#include "protos/perfetto/trace/trace.pbzero.h"
#include "protos/perfetto/trace/trace_packet.pbzero.h"
-#include "src/trace_processor/forwarding_trace_parser.h"
#include "src/trace_processor/util/descriptors.h"
#include "src/trace_processor/util/gzip_utils.h"
#include "src/trace_processor/util/protozero_to_text.h"
+#include "src/trace_processor/util/trace_type.h"
namespace perfetto {
namespace trace_to_text {
diff --git a/src/traced/probes/ps/process_stats_data_source.cc b/src/traced/probes/ps/process_stats_data_source.cc
index 9a27d29..91c72a9 100644
--- a/src/traced/probes/ps/process_stats_data_source.cc
+++ b/src/traced/probes/ps/process_stats_data_source.cc
@@ -720,6 +720,12 @@
GetOrCreateStatsProcess(pid)->set_smr_pss_shmem_kb(counter);
cached.smr_pss_shmem_kb = counter;
}
+ } else if (strcmp(key.data(), "SwapPss") == 0) {
+ auto counter = ToUInt32(value.data());
+ if (counter != cached.smr_swap_pss_kb) {
+ GetOrCreateStatsProcess(pid)->set_smr_swap_pss_kb(counter);
+ cached.smr_swap_pss_kb = counter;
+ }
}
key.clear();
diff --git a/src/traced/probes/ps/process_stats_data_source.h b/src/traced/probes/ps/process_stats_data_source.h
index 9ec28bc..0224dd1 100644
--- a/src/traced/probes/ps/process_stats_data_source.h
+++ b/src/traced/probes/ps/process_stats_data_source.h
@@ -90,6 +90,7 @@
uint32_t smr_pss_anon_kb = std::numeric_limits<uint32_t>::max();
uint32_t smr_pss_file_kb = std::numeric_limits<uint32_t>::max();
uint32_t smr_pss_shmem_kb = std::numeric_limits<uint32_t>::max();
+ uint32_t smr_swap_pss_kb = std::numeric_limits<uint32_t>::max();
uint64_t runtime_user_mode_ns = std::numeric_limits<uint64_t>::max();
uint64_t runtime_kernel_mode_ns = std::numeric_limits<uint64_t>::max();
// file descriptors
diff --git a/src/tracing/test/api_integrationtest.cc b/src/tracing/test/api_integrationtest.cc
index 5e1e277..5072c3d 100644
--- a/src/tracing/test/api_integrationtest.cc
+++ b/src/tracing/test/api_integrationtest.cc
@@ -2485,7 +2485,7 @@
EXPECT_FALSE(found_counter_track_descriptor);
found_counter_track_descriptor = true;
thread_time_counter_uuid = packet.track_descriptor().uuid();
- EXPECT_EQ("thread_time", packet.track_descriptor().name());
+ EXPECT_EQ("thread_time", packet.track_descriptor().static_name());
auto counter = packet.track_descriptor().counter();
EXPECT_EQ(
perfetto::protos::gen::
@@ -5795,8 +5795,10 @@
auto& desc = packet.track_descriptor();
if (!desc.has_counter())
continue;
- counter_names[desc.uuid()] = desc.name();
- EXPECT_EQ((desc.name() != "Framerate3"), desc.counter().is_incremental());
+ counter_names[desc.uuid()] =
+ desc.has_name() ? desc.name() : desc.static_name();
+ EXPECT_EQ((desc.static_name() != "Framerate3"),
+ desc.counter().is_incremental());
}
if (packet.has_track_event()) {
auto event = packet.track_event();
@@ -5869,7 +5871,8 @@
continue;
}
auto desc = packet.track_descriptor();
- counter_names[desc.uuid()] = desc.name();
+ counter_names[desc.uuid()] =
+ desc.has_name() ? desc.name() : desc.static_name();
if (desc.name() == "Framerate") {
EXPECT_EQ("fps", desc.counter().unit_name());
} else if (desc.name() == "Goats teleported") {
diff --git a/src/tracing/track.cc b/src/tracing/track.cc
index 02e6e03..dc02609 100644
--- a/src/tracing/track.cc
+++ b/src/tracing/track.cc
@@ -118,8 +118,13 @@
protos::gen::TrackDescriptor CounterTrack::Serialize() const {
auto desc = Track::Serialize();
- desc.set_name(name_);
auto* counter = desc.mutable_counter();
+ if (static_name_) {
+ desc.set_static_name(static_name_.value);
+ } else {
+ desc.set_name(dynamic_name_.value);
+ }
+
if (category_)
counter->add_categories(category_);
if (unit_ != perfetto::protos::pbzero::CounterDescriptor::UNIT_UNSPECIFIED)
diff --git a/src/tracing/track_event_state_tracker.cc b/src/tracing/track_event_state_tracker.cc
index 1574292..e65d86f 100644
--- a/src/tracing/track_event_state_tracker.cc
+++ b/src/tracing/track_event_state_tracker.cc
@@ -246,7 +246,11 @@
track.index = static_cast<uint32_t>(session_state->tracks.size() + 1);
track.uuid = track_descriptor.uuid();
- track.name = track_descriptor.name().ToStdString();
+ if (track_descriptor.has_name()) {
+ track.name = track_descriptor.name().ToStdString();
+ } else if (track_descriptor.has_static_name()) {
+ track.name = track_descriptor.static_name().ToStdString();
+ }
track.pid = 0;
track.tid = 0;
if (track_descriptor.has_process()) {
diff --git a/test/trace_processor/diff_tests/include_index.py b/test/trace_processor/diff_tests/include_index.py
index 38e3ba2..45c972f 100644
--- a/test/trace_processor/diff_tests/include_index.py
+++ b/test/trace_processor/diff_tests/include_index.py
@@ -75,6 +75,7 @@
from diff_tests.parser.parsing.tests import Parsing
from diff_tests.parser.parsing.tests_debug_annotation import ParsingDebugAnnotation
from diff_tests.parser.parsing.tests_memory_counters import ParsingMemoryCounters
+from diff_tests.parser.parsing.tests_traced_stats import ParsingTracedStats
from diff_tests.parser.parsing.tests_rss_stats import ParsingRssStats
from diff_tests.parser.power.tests_energy_breakdown import PowerEnergyBreakdown
from diff_tests.parser.power.tests_entity_state_residency import EntityStateResidency
@@ -137,6 +138,7 @@
sys.path.pop()
+
def fetch_all_diff_tests(index_path: str) -> List['testing.TestCase']:
parser_tests = [
*AndroidBugreport(index_path, 'parser/android',
@@ -187,11 +189,11 @@
*SmokeJson(index_path, 'parser/smoke', 'SmokeJson').fetch(),
*SmokeSchedEvents(index_path, 'parser/smoke', 'SmokeSchedEvents').fetch(),
*InputMethodClients(index_path, 'parser/android',
- 'InputMethodClients').fetch(),
+ 'InputMethodClients').fetch(),
*InputMethodManagerService(index_path, 'parser/android',
- 'InputMethodManagerService').fetch(),
+ 'InputMethodManagerService').fetch(),
*InputMethodService(index_path, 'parser/android',
- 'InputMethodService').fetch(),
+ 'InputMethodService').fetch(),
*SurfaceFlingerLayers(index_path, 'parser/android',
'SurfaceFlingerLayers').fetch(),
*SurfaceFlingerTransactions(index_path, 'parser/android',
@@ -212,6 +214,8 @@
*ParsingMemoryCounters(index_path, 'parser/parsing',
'ParsingMemoryCounters').fetch(),
*FtraceCrop(index_path, 'parser/ftrace', 'FtraceCrop').fetch(),
+ *ParsingTracedStats(index_path, 'parser/parsing',
+ 'ParsingTracedStats').fetch(),
]
metrics_tests = [
diff --git a/test/trace_processor/diff_tests/metrics/android/android_auto_multiuser.textproto b/test/trace_processor/diff_tests/metrics/android/android_auto_multiuser.textproto
index c33839e..e14c077 100644
--- a/test/trace_processor/diff_tests/metrics/android/android_auto_multiuser.textproto
+++ b/test/trace_processor/diff_tests/metrics/android/android_auto_multiuser.textproto
@@ -11,6 +11,16 @@
uid: 1000010
cmdline: "dummy:2"
}
+ processes {
+ pid: 12
+ uid: 1300010
+ cmdline: "dummy:3"
+ }
+ processes {
+ pid: 20
+ uid: 1000
+ cmdline: "finishUserStopped-10"
+ }
}
}
packet {
@@ -66,6 +76,38 @@
}
}
packet {
+ ftrace_events {
+ cpu: 1
+ event {
+ timestamp: 5000000001
+ pid: 10
+ sched_switch {
+ prev_comm: "dummy:3"
+ prev_pid: 12
+ prev_state: 2
+ next_comm: "dummy:2"
+ next_pid: 11
+ }
+ }
+ }
+}
+packet {
+ ftrace_events {
+ cpu: 1
+ event {
+ timestamp: 5010000000
+ pid: 11
+ sched_switch {
+ prev_comm: "dummy:2"
+ prev_pid: 11
+ prev_state: 2
+ next_comm: "dummy:3"
+ next_pid: 13
+ }
+ }
+ }
+}
+packet {
timestamp: 3000000001
process_stats {
processes {
@@ -102,6 +144,15 @@
}
}
packet {
+ timestamp: 5000000002
+ process_stats {
+ processes {
+ pid: 11
+ vm_rss_kb: 3000
+ }
+ }
+}
+packet {
ftrace_events {
cpu: 1
event {
@@ -124,4 +175,23 @@
}
}
}
-}
\ No newline at end of file
+}
+packet {
+ ftrace_events {
+ cpu: 1
+ event {
+ timestamp: 5000000000
+ pid: 20
+ print {
+ buf: "B|20|finishUserStopped-10-[stopUser]\n"
+ }
+ }
+ event {
+ timestamp: 5100000000
+ pid: 20
+ print {
+ buf: "E|20\n"
+ }
+ }
+ }
+}
diff --git a/test/trace_processor/diff_tests/metrics/android/tests.py b/test/trace_processor/diff_tests/metrics/android/tests.py
index a1c2448..c3667fa 100644
--- a/test/trace_processor/diff_tests/metrics/android/tests.py
+++ b/test/trace_processor/diff_tests/metrics/android/tests.py
@@ -326,9 +326,34 @@
total_memory_usage_kb: 2048
}
}
+ user_switch {
+ user_id: 11
+ start_event: "UserController.startUser-11-fg-start-mode-1"
+ end_event: "finishUserStopped-10-[stopUser]"
+ duration_ms: 2100
+ previous_user_info {
+ user_id: 10
+ total_cpu_time_ms: 19
+ total_memory_usage_kb: 3072
+ }
+ }
}
"""))
+ def test_android_auto_multiuser_timing_table(self):
+ return DiffTestBlueprint(
+ trace=Path("android_auto_multiuser.textproto"),
+ query="""
+ INCLUDE PERFETTO MODULE android.auto.multiuser;
+ SELECT * FROM android_auto_multiuser_timing;
+ """,
+ out=Csv("""
+ "event_start_user_id","event_start_time","event_end_time","event_end_name","event_start_name","duration"
+ "11",3000000000,3999999999,"com.android.car.carlauncher","UserController.startUser-11-fg-start-mode-1",999999999
+ "11",3000000000,5100000000,"finishUserStopped-10-[stopUser]","UserController.startUser-11-fg-start-mode-1",2100000000
+ """)
+ )
+
def test_android_oom_adjuster(self):
return DiffTestBlueprint(
trace=DataPath('android_postboot_unlock.pftrace'),
diff --git a/test/trace_processor/diff_tests/parser/parsing/tests_traced_stats.py b/test/trace_processor/diff_tests/parser/parsing/tests_traced_stats.py
new file mode 100644
index 0000000..af2f362
--- /dev/null
+++ b/test/trace_processor/diff_tests/parser/parsing/tests_traced_stats.py
@@ -0,0 +1,104 @@
+#!/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, TextProto
+from python.generators.diff_tests.testing import DiffTestBlueprint
+from python.generators.diff_tests.testing import TestSuite
+
+
+class ParsingTracedStats(TestSuite):
+ # Check that `previous_packed_dropped: true` maps to
+ # `traced_buf_sequence_packet_loss`.
+ def test_sequence_packet_loss(self):
+ return DiffTestBlueprint(
+ trace=TextProto(r"""
+ packet {
+ trusted_packet_sequence_id: 2
+ previous_packet_dropped: true
+ }
+ packet {
+ trusted_packet_sequence_id: 2
+ }
+ packet {
+ trusted_packet_sequence_id: 2
+ }
+ packet {
+ trusted_packet_sequence_id: 3
+ previous_packet_dropped: true
+ }
+ packet {
+ trusted_packet_sequence_id: 3
+ previous_packet_dropped: true
+ }
+ packet {
+ trusted_packet_sequence_id: 3
+ }
+ packet {
+ trusted_packet_sequence_id: 4
+ previous_packet_dropped: true
+ }
+ packet {
+ trusted_packet_sequence_id: 4
+ previous_packet_dropped: true
+ }
+ packet {
+ trusted_packet_sequence_id: 4
+ }
+ packet {
+ trusted_packet_sequence_id: 5
+ previous_packet_dropped: true
+ }
+ packet {
+ trusted_packet_sequence_id: 5
+ previous_packet_dropped: true
+ }
+ packet {
+ trusted_packet_sequence_id: 5
+ }
+ packet {
+ trusted_uid: 9999
+ trusted_packet_sequence_id: 1
+ trace_stats {
+ writer_stats {
+ sequence_id: 2
+ buffer: 0
+ }
+ writer_stats {
+ sequence_id: 3
+ buffer: 1
+ }
+ writer_stats {
+ sequence_id: 4
+ buffer: 2
+ }
+ writer_stats {
+ sequence_id: 5
+ buffer: 2
+ }
+ }
+ }
+ """),
+ query="""
+ SELECT idx, value
+ FROM stats
+ WHERE name = 'traced_buf_sequence_packet_loss'
+ ORDER BY idx;
+ """,
+ out=Csv("""
+ "idx","value"
+ 0,0
+ 1,1
+ 2,2
+ """))
diff --git a/test/trace_processor/diff_tests/parser/translated_args/process_track_name.textproto b/test/trace_processor/diff_tests/parser/translated_args/process_track_name.textproto
new file mode 100644
index 0000000..22dc8fb
--- /dev/null
+++ b/test/trace_processor/diff_tests/parser/translated_args/process_track_name.textproto
@@ -0,0 +1,100 @@
+packet {
+ translation_table {
+ process_track_name {
+ raw_to_deobfuscated_name { key: "raw_track_name" value: "explicitly_renamed" }
+ raw_to_deobfuscated_name { key: "raw_slice1" value: "should_not_be_renamed" }
+ raw_to_deobfuscated_name { key: "raw_slice2" value: "implicitly_renamed" }
+ raw_to_deobfuscated_name { key: "raw_counter" value: "renamed_counter" }
+ }
+ }
+}
+packet {
+ trusted_packet_sequence_id: 1
+ timestamp: 1000
+ track_descriptor {
+ uuid: 1
+ process: {
+ process_name: "exampleProcess"
+ pid: 1234
+ }
+ }
+}
+# Define a named track
+packet {
+ trusted_packet_sequence_id: 1
+ timestamp: 1001
+ track_descriptor {
+ uuid: 2
+ name: "raw_track_name"
+ parent_uuid: 1
+ }
+}
+# define a track who's name will be implicitly defined by slice names
+packet {
+ trusted_packet_sequence_id: 1
+ timestamp: 1002
+ track_descriptor {
+ uuid: 3
+ parent_uuid: 1
+ }
+}
+# Named track for the counter
+packet {
+ trusted_packet_sequence_id: 1
+ timestamp: 1002
+ track_descriptor {
+ uuid: 4
+ name: "raw_counter"
+ parent_uuid: 1
+ counter {
+ }
+ }
+}
+# Counter Event
+packet {
+ trusted_packet_sequence_id: 1
+ timestamp: 1004
+ track_event {
+ track_uuid: 4
+ type: 4
+ counter_value: 99
+ }
+}
+
+# begin/end pair for explicitly named track
+packet {
+ trusted_packet_sequence_id: 1
+ timestamp: 1500
+ track_event {
+ track_uuid: 2
+ name: "raw_slice1"
+ type: 1
+ }
+}
+packet {
+ trusted_packet_sequence_id: 1
+ timestamp: 2000
+ track_event {
+ track_uuid: 2
+ type: 2
+ }
+}
+
+# begin/end pair for implicitly named slice
+packet {
+ trusted_packet_sequence_id: 1
+ timestamp: 3000
+ track_event {
+ track_uuid: 3
+ name: "raw_slice2"
+ type: 1
+ }
+}
+packet {
+ trusted_packet_sequence_id: 1
+ timestamp: 4000
+ track_event {
+ track_uuid: 3
+ type: 2
+ }
+}
\ No newline at end of file
diff --git a/test/trace_processor/diff_tests/parser/translated_args/tests.py b/test/trace_processor/diff_tests/parser/translated_args/tests.py
index 7e28f38..3bada0d 100644
--- a/test/trace_processor/diff_tests/parser/translated_args/tests.py
+++ b/test/trace_processor/diff_tests/parser/translated_args/tests.py
@@ -121,6 +121,25 @@
"slice_begin"
"""))
+ def test_process_track_name(self):
+ return DiffTestBlueprint(
+ trace=Path('process_track_name.textproto'),
+ query="""
+ SELECT
+ name
+ FROM track
+ WHERE
+ name IS NOT NULL
+ AND type in ('process_track', 'process_counter_track')
+ ORDER BY name;
+ """,
+ out=Csv("""
+ "name"
+ "explicitly_renamed"
+ "implicitly_renamed"
+ "renamed_counter"
+ """))
+
def test_native_symbol_arg(self):
return DiffTestBlueprint(
trace=Path('native_symbol_arg.textproto'),
diff --git a/test/trace_processor/diff_tests/syntax/table_tests.py b/test/trace_processor/diff_tests/syntax/table_tests.py
index 7de63e9..5785b6f 100644
--- a/test/trace_processor/diff_tests/syntax/table_tests.py
+++ b/test/trace_processor/diff_tests/syntax/table_tests.py
@@ -178,6 +178,37 @@
3073,8,4529,8
"""))
+ def test_distinct_multi_column(self):
+ return DiffTestBlueprint(
+ trace=TextProto(''),
+ query="""
+ CREATE PERFETTO TABLE foo AS
+ WITH data(a, b) AS (
+ VALUES
+ -- Needed to defeat any id/sorted detection.
+ (2, 3),
+ (0, 2),
+ (0, 1)
+ )
+ SELECT * FROM data;
+
+ CREATE TABLE bar AS
+ SELECT 1 AS b;
+
+ WITH multi_col_distinct AS (
+ SELECT DISTINCT a FROM foo CROSS JOIN bar USING (b)
+ ), multi_col_group_by AS (
+ SELECT a FROM foo CROSS JOIN bar USING (b) GROUP BY a
+ )
+ SELECT
+ (SELECT COUNT(*) FROM multi_col_distinct) AS cnt_distinct,
+ (SELECT COUNT(*) FROM multi_col_group_by) AS cnt_group_by
+ """,
+ out=Csv("""
+ "cnt_distinct","cnt_group_by"
+ 1,1
+ """))
+
def test_limit(self):
return DiffTestBlueprint(
trace=TextProto(''),
diff --git a/tools/bisect_ui_releases b/tools/bisect_ui_releases
new file mode 100755
index 0000000..ca0679a
--- /dev/null
+++ b/tools/bisect_ui_releases
@@ -0,0 +1,116 @@
+#!/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 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.
+"""
+Runs a bisection on the autopush ui.perfetto.dev builds
+
+Similar to git bisect, but bisects UI releases rather than commits.
+This works only for autopush builds from main, ignores canary and stable
+channels, as they make the history non-linear.
+
+How it works:
+- It first obtains an unordered list of versions from gs://ui.perfetto.dev
+- Then obtains the list of ordered commits from git
+- Intersects the two lists, keeping only git commits that have a corresponding
+ ui autopush release.
+- Proceeds with a guided bisect in the range.
+"""
+
+import argparse
+import sys
+
+from subprocess import check_output
+
+COMMIT_ABBR_LEN = 9 # UI truncates commitish to 9 chars, e.g. v45.0-38b7c2b12.
+
+
+def main():
+ parser = argparse.ArgumentParser()
+ parser.add_argument(
+ '--good',
+ default=None,
+ help='Last good release (e.g. v44.0-257a02990).' +
+ 'Defaults to the first verion available')
+ parser.add_argument(
+ '--bad',
+ default=None,
+ help='First bad release. Defaults to the latest version available')
+ args = parser.parse_args()
+
+ print('Fetching list of UI releases from GCS...')
+ rev_list = check_output(['gsutil.py', 'ls', 'gs://ui.perfetto.dev/']).decode()
+ ui_map = {} # maps '38b7c2b12' -> 'v45.0-38b7c2b12'
+ for line in rev_list.split():
+ version = line.split('/')[3]
+ if '-' not in version:
+ continue
+ ver_hash = version.split('-')[1]
+ ui_map[ver_hash] = version
+ print('Found %d UI versions' % len(ui_map))
+
+ # Get the linear history of all commits.
+ print('Fetching revision history from git...')
+ ui_versions = []
+ git_out = check_output(['git', 'rev-list', '--left-only',
+ 'origin/main']).decode()
+ for line in git_out.split():
+ line = line.strip()
+ rev = line[0:COMMIT_ABBR_LEN]
+ if rev not in ui_map:
+ continue # Not all perfetto commits have a UI autopush build.
+ ui_versions.append(ui_map[rev])
+
+ # git rev-list emits entries in recent -> older versions. Reverse it.
+ ui_versions.reverse()
+
+ # Note that not all the entries in ui_map will be present in ui_versions.
+ # This is because ui_map contains also builds coming from canary and stable
+ # branches, that we ignore here.
+
+ start = ui_versions.index(args.good) if args.good else 0
+ end = ui_versions.index(args.bad) if args.bad else len(ui_versions) - 1
+ while end - start > 1:
+ print('\033c', end='') # clear terminal.
+ print(
+ 'Bisecting from %s (last good) to %s (first bad), %d revisions to go' %
+ (ui_versions[start], ui_versions[end], end - start + 1))
+ mid = (end + start) // 2
+
+ # Print a visual indication of where we are in the bisect.
+ for i in reversed(range(start, end + 1)):
+ sfx = ''
+ if i == start:
+ sfx = ' GOOD --------------'
+ elif i == end:
+ sfx = ' BAD ---------------'
+ elif i == mid:
+ sfx = ' <- version to test'
+ print(ui_versions[i] + sfx)
+
+ user_feedback = input(
+ 'https://ui.perfetto.dev/%s/. Type g for good and b for bad: ' %
+ ui_versions[mid])
+ if user_feedback == 'b':
+ end = mid
+ elif user_feedback == 'g':
+ start = mid
+ else:
+ print('Unrecognised key "%d", try again' % user_feedback)
+
+ print('First bad UI release %s' % ui_versions[end])
+ print('You should now inspect the individual commits via git log good..bad')
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/tools/gen_android_bp b/tools/gen_android_bp
index a0d92ed..2aa0b7f 100755
--- a/tools/gen_android_bp
+++ b/tools/gen_android_bp
@@ -137,7 +137,7 @@
]
},
'config': {
- 'types': ['lite'],
+ 'types': ['filegroup'],
'targets': [
'//protos/perfetto/config:source_set',
]
diff --git a/tools/install-build-deps b/tools/install-build-deps
index 8e1d88f..31972d4 100755
--- a/tools/install-build-deps
+++ b/tools/install-build-deps
@@ -186,7 +186,7 @@
Dependency(
'buildtools/protobuf',
'https://chromium.googlesource.com/external/github.com/protocolbuffers/protobuf.git',
- 'fe271ab76f2ad2b2b28c10443865d2af21e27e0e', # refs/tags/v3.20.3
+ 'f0dc78d7e6e331b8c6bb2d5283e06aa26883ca7c', # refs/tags/v21.12
'all',
'all'),
diff --git a/ui/build.js b/ui/build.js
index 56745df..b675d4f 100644
--- a/ui/build.js
+++ b/ui/build.js
@@ -127,7 +127,7 @@
f: copyUiTestArtifactsAssets,
},
{r: /.*\/dist\/.+\/(?!manifest\.json).*/, f: genServiceWorkerManifestJson},
- {r: /.*\/dist\/.*/, f: notifyLiveServer},
+ {r: /.*\/dist\/.*[.](js|html|css|wasm)$/, f: notifyLiveServer},
];
const tasks = [];
@@ -412,10 +412,7 @@
'protos/perfetto/config/perfetto_config.proto',
'protos/perfetto/ipc/consumer_port.proto',
'protos/perfetto/ipc/wire_protocol.proto',
- 'protos/perfetto/metrics/metrics.proto',
'protos/perfetto/trace/perfetto/perfetto_metatrace.proto',
- 'protos/perfetto/trace/trace.proto',
- 'protos/perfetto/trace/trace_packet.proto',
'protos/perfetto/trace_processor/trace_processor.proto',
];
// Can't put --no-comments here - The comments are load bearing for
diff --git a/ui/src/assets/brand.png b/ui/src/assets/brand.png
index dc6f8b6..63fe6f3 100644
--- a/ui/src/assets/brand.png
+++ b/ui/src/assets/brand.png
Binary files differ
diff --git a/ui/src/assets/favicon.png b/ui/src/assets/favicon.png
index 837b75b..2844520 100644
--- a/ui/src/assets/favicon.png
+++ b/ui/src/assets/favicon.png
Binary files differ
diff --git a/ui/src/assets/logo-128.png b/ui/src/assets/logo-128.png
index 43a2cd1..6d13da5 100644
--- a/ui/src/assets/logo-128.png
+++ b/ui/src/assets/logo-128.png
Binary files differ
diff --git a/ui/src/assets/logo-3d.png b/ui/src/assets/logo-3d.png
index b832ae6..9528c48 100644
--- a/ui/src/assets/logo-3d.png
+++ b/ui/src/assets/logo-3d.png
Binary files differ
diff --git a/ui/src/assets/rec_atrace.png b/ui/src/assets/rec_atrace.png
index d63f2a2..9a6baa2 100644
--- a/ui/src/assets/rec_atrace.png
+++ b/ui/src/assets/rec_atrace.png
Binary files differ
diff --git a/ui/src/assets/rec_battery_counters.png b/ui/src/assets/rec_battery_counters.png
index b73603d..3f1557b 100644
--- a/ui/src/assets/rec_battery_counters.png
+++ b/ui/src/assets/rec_battery_counters.png
Binary files differ
diff --git a/ui/src/assets/rec_board_voltage.png b/ui/src/assets/rec_board_voltage.png
index bc4dd12..d5f5b42 100644
--- a/ui/src/assets/rec_board_voltage.png
+++ b/ui/src/assets/rec_board_voltage.png
Binary files differ
diff --git a/ui/src/assets/rec_cpu_coarse.png b/ui/src/assets/rec_cpu_coarse.png
index b7241bf..9296a19 100644
--- a/ui/src/assets/rec_cpu_coarse.png
+++ b/ui/src/assets/rec_cpu_coarse.png
Binary files differ
diff --git a/ui/src/assets/rec_cpu_fine.png b/ui/src/assets/rec_cpu_fine.png
index 3c8df8b..6d069c2 100644
--- a/ui/src/assets/rec_cpu_fine.png
+++ b/ui/src/assets/rec_cpu_fine.png
Binary files differ
diff --git a/ui/src/assets/rec_cpu_freq.png b/ui/src/assets/rec_cpu_freq.png
index 23d86f8..5bd9e7b 100644
--- a/ui/src/assets/rec_cpu_freq.png
+++ b/ui/src/assets/rec_cpu_freq.png
Binary files differ
diff --git a/ui/src/assets/rec_cpu_voltage.png b/ui/src/assets/rec_cpu_voltage.png
index d82d5f3..7e31de9 100644
--- a/ui/src/assets/rec_cpu_voltage.png
+++ b/ui/src/assets/rec_cpu_voltage.png
Binary files differ
diff --git a/ui/src/assets/rec_frame_timeline.png b/ui/src/assets/rec_frame_timeline.png
index 2c83762..da98c40 100644
--- a/ui/src/assets/rec_frame_timeline.png
+++ b/ui/src/assets/rec_frame_timeline.png
Binary files differ
diff --git a/ui/src/assets/rec_ftrace.png b/ui/src/assets/rec_ftrace.png
index a907f9e..e956d05 100644
--- a/ui/src/assets/rec_ftrace.png
+++ b/ui/src/assets/rec_ftrace.png
Binary files differ
diff --git a/ui/src/assets/rec_gpu_mem_total.png b/ui/src/assets/rec_gpu_mem_total.png
index 4b5a44a..537ce9f 100644
--- a/ui/src/assets/rec_gpu_mem_total.png
+++ b/ui/src/assets/rec_gpu_mem_total.png
Binary files differ
diff --git a/ui/src/assets/rec_java_heap_dump.png b/ui/src/assets/rec_java_heap_dump.png
index 229ebe0..e3ee7c4 100644
--- a/ui/src/assets/rec_java_heap_dump.png
+++ b/ui/src/assets/rec_java_heap_dump.png
Binary files differ
diff --git a/ui/src/assets/rec_lmk.png b/ui/src/assets/rec_lmk.png
index 7324cf9..3a42b2d 100644
--- a/ui/src/assets/rec_lmk.png
+++ b/ui/src/assets/rec_lmk.png
Binary files differ
diff --git a/ui/src/assets/rec_logcat.png b/ui/src/assets/rec_logcat.png
index b3b4905..dda6526 100644
--- a/ui/src/assets/rec_logcat.png
+++ b/ui/src/assets/rec_logcat.png
Binary files differ
diff --git a/ui/src/assets/rec_long_trace.png b/ui/src/assets/rec_long_trace.png
index 23aad0b..f801756 100644
--- a/ui/src/assets/rec_long_trace.png
+++ b/ui/src/assets/rec_long_trace.png
Binary files differ
diff --git a/ui/src/assets/rec_mem_hifreq.png b/ui/src/assets/rec_mem_hifreq.png
index f36ef3e..f2909f8 100644
--- a/ui/src/assets/rec_mem_hifreq.png
+++ b/ui/src/assets/rec_mem_hifreq.png
Binary files differ
diff --git a/ui/src/assets/rec_meminfo.png b/ui/src/assets/rec_meminfo.png
index 4280675..43f968e 100644
--- a/ui/src/assets/rec_meminfo.png
+++ b/ui/src/assets/rec_meminfo.png
Binary files differ
diff --git a/ui/src/assets/rec_native_heap_profiler.png b/ui/src/assets/rec_native_heap_profiler.png
index bb3b010..4915c2b 100644
--- a/ui/src/assets/rec_native_heap_profiler.png
+++ b/ui/src/assets/rec_native_heap_profiler.png
Binary files differ
diff --git a/ui/src/assets/rec_one_shot.png b/ui/src/assets/rec_one_shot.png
index 7539c72..8981075 100644
--- a/ui/src/assets/rec_one_shot.png
+++ b/ui/src/assets/rec_one_shot.png
Binary files differ
diff --git a/ui/src/assets/rec_profiling.png b/ui/src/assets/rec_profiling.png
index 385670c..024d9f5 100644
--- a/ui/src/assets/rec_profiling.png
+++ b/ui/src/assets/rec_profiling.png
Binary files differ
diff --git a/ui/src/assets/rec_ps_stats.png b/ui/src/assets/rec_ps_stats.png
index e37bab1..df02761 100644
--- a/ui/src/assets/rec_ps_stats.png
+++ b/ui/src/assets/rec_ps_stats.png
Binary files differ
diff --git a/ui/src/assets/rec_ring_buf.png b/ui/src/assets/rec_ring_buf.png
index a2490fa..9bbe231 100644
--- a/ui/src/assets/rec_ring_buf.png
+++ b/ui/src/assets/rec_ring_buf.png
Binary files differ
diff --git a/ui/src/assets/rec_syscalls.png b/ui/src/assets/rec_syscalls.png
index 734854a..90b4ad8 100644
--- a/ui/src/assets/rec_syscalls.png
+++ b/ui/src/assets/rec_syscalls.png
Binary files differ
diff --git a/ui/src/assets/rec_vmstat.png b/ui/src/assets/rec_vmstat.png
index 58a4e71..46a1006 100644
--- a/ui/src/assets/rec_vmstat.png
+++ b/ui/src/assets/rec_vmstat.png
Binary files differ
diff --git a/ui/src/assets/scheduling_latency.png b/ui/src/assets/scheduling_latency.png
index 36bcfb8..8b5a2e1 100644
--- a/ui/src/assets/scheduling_latency.png
+++ b/ui/src/assets/scheduling_latency.png
Binary files differ
diff --git a/ui/src/base/utils.ts b/ui/src/base/utils.ts
index 41a201f..d3c5f4b 100644
--- a/ui/src/base/utils.ts
+++ b/ui/src/base/utils.ts
@@ -19,3 +19,8 @@
export function exists<T>(value: T): value is NonNullable<T> {
return value !== undefined && value !== null;
}
+
+// Generic result type - similar to Rust's Result<T, E>
+export type Result<T, E = {}> =
+ | {success: true; result: T}
+ | {success: false; error: E};
diff --git a/ui/src/common/actions.ts b/ui/src/common/actions.ts
index 4cebecb..15d8f6c 100644
--- a/ui/src/common/actions.ts
+++ b/ui/src/common/actions.ts
@@ -253,15 +253,15 @@
// the reducer.
args: {
name: string;
- id: string;
+ key: string;
summaryTrackKey?: string;
collapsed: boolean;
fixedOrdering?: boolean;
},
): void {
- state.trackGroups[args.id] = {
+ state.trackGroups[args.key] = {
name: args.name,
- id: args.id,
+ key: args.key,
collapsed: args.collapsed,
tracks: [],
summaryTrack: args.summaryTrackKey,
@@ -394,12 +394,8 @@
}
},
- toggleTrackGroupCollapsed(
- state: StateDraft,
- args: {trackGroupId: string},
- ): void {
- const id = args.trackGroupId;
- const trackGroup = assertExists(state.trackGroups[id]);
+ toggleTrackGroupCollapsed(state: StateDraft, args: {groupKey: string}): void {
+ const trackGroup = assertExists(state.trackGroups[args.groupKey]);
trackGroup.collapsed = !trackGroup.collapsed;
},
@@ -448,31 +444,6 @@
}
},
- createPermalink(state: StateDraft, args: {isRecordingConfig: boolean}): void {
- state.permalink = {
- requestId: generateNextId(state),
- hash: undefined,
- isRecordingConfig: args.isRecordingConfig,
- };
- },
-
- setPermalink(
- state: StateDraft,
- args: {requestId: string; hash: string},
- ): void {
- // Drop any links for old requests.
- if (state.permalink.requestId !== args.requestId) return;
- state.permalink = args;
- },
-
- loadPermalink(state: StateDraft, args: {hash: string}): void {
- state.permalink = {requestId: generateNextId(state), hash: args.hash};
- },
-
- clearPermalink(state: StateDraft, _: {}): void {
- state.permalink = {};
- },
-
updateStatus(state: StateDraft, args: Status): void {
if (statusTraceEvent) {
traceEventEnd(statusTraceEvent);
@@ -920,7 +891,7 @@
toggleTrackSelection(
state: StateDraft,
- args: {id: string; isTrackGroup: boolean},
+ args: {key: string; isTrackGroup: boolean},
) {
const selection = state.selection;
if (
@@ -930,12 +901,12 @@
return;
}
const areaId = selection.legacySelection.areaId;
- const index = state.areas[areaId].tracks.indexOf(args.id);
+ const index = state.areas[areaId].tracks.indexOf(args.key);
if (index > -1) {
state.areas[areaId].tracks.splice(index, 1);
if (args.isTrackGroup) {
// Also remove all child tracks.
- for (const childTrack of state.trackGroups[args.id].tracks) {
+ for (const childTrack of state.trackGroups[args.key].tracks) {
const childIndex = state.areas[areaId].tracks.indexOf(childTrack);
if (childIndex > -1) {
state.areas[areaId].tracks.splice(childIndex, 1);
@@ -943,10 +914,10 @@
}
}
} else {
- state.areas[areaId].tracks.push(args.id);
+ state.areas[areaId].tracks.push(args.key);
if (args.isTrackGroup) {
// Also add all child tracks.
- for (const childTrack of state.trackGroups[args.id].tracks) {
+ for (const childTrack of state.trackGroups[args.key].tracks) {
if (!state.areas[areaId].tracks.includes(childTrack)) {
state.areas[areaId].tracks.push(childTrack);
}
diff --git a/ui/src/common/actions_unittest.ts b/ui/src/common/actions_unittest.ts
index de497d7..734ac91 100644
--- a/ui/src/common/actions_unittest.ts
+++ b/ui/src/common/actions_unittest.ts
@@ -60,14 +60,14 @@
function fakeTrackGroup(
state: State,
- args: {id: string; summaryTrackId: string},
+ args: {key: string; summaryTrackKey: string},
): State {
return produce(state, (draft) => {
StateActions.addTrackGroup(draft, {
name: 'A group',
- id: args.id,
+ key: args.key,
collapsed: false,
- summaryTrackKey: args.summaryTrackId,
+ summaryTrackKey: args.summaryTrackKey,
});
});
}
@@ -117,7 +117,7 @@
const afterGroup = produce(state, (draft) => {
StateActions.addTrackGroup(draft, {
name: 'A track group',
- id: '123-123-123',
+ key: '123-123-123',
summaryTrackKey: 's',
collapsed: false,
});
@@ -151,19 +151,19 @@
});
});
- const firstTrackId = once.scrollingTracks[0];
- const secondTrackId = once.scrollingTracks[1];
+ const firstTrackKey = once.scrollingTracks[0];
+ const secondTrackKey = once.scrollingTracks[1];
const twice = produce(once, (draft) => {
StateActions.moveTrack(draft, {
- srcId: `${firstTrackId}`,
+ srcId: `${firstTrackKey}`,
op: 'after',
- dstId: `${secondTrackId}`,
+ dstId: `${secondTrackKey}`,
});
});
- expect(twice.scrollingTracks[0]).toBe(secondTrackId);
- expect(twice.scrollingTracks[1]).toBe(firstTrackId);
+ expect(twice.scrollingTracks[0]).toBe(secondTrackKey);
+ expect(twice.scrollingTracks[1]).toBe(firstTrackKey);
});
test('reorder pinned to scrolling', () => {
@@ -326,7 +326,7 @@
test('sortTracksByPriority', () => {
let state = createEmptyState();
- state = fakeTrackGroup(state, {id: 'g', summaryTrackId: 'b'});
+ state = fakeTrackGroup(state, {key: 'g', summaryTrackKey: 'b'});
state = fakeTrack(state, {
key: 'b',
uri: HEAP_PROFILE_TRACK_KIND,
@@ -351,7 +351,7 @@
test('sortTracksByPriorityAndKindAndName', () => {
let state = createEmptyState();
- state = fakeTrackGroup(state, {id: 'g', summaryTrackId: 'b'});
+ state = fakeTrackGroup(state, {key: 'g', summaryTrackKey: 'b'});
state = fakeTrack(state, {
key: 'a',
uri: PROCESS_SCHEDULING_TRACK_KIND,
@@ -416,7 +416,7 @@
test('sortTracksByTidThenName', () => {
let state = createEmptyState();
- state = fakeTrackGroup(state, {id: 'g', summaryTrackId: 'a'});
+ state = fakeTrackGroup(state, {key: 'g', summaryTrackKey: 'a'});
state = fakeTrack(state, {
key: 'a',
uri: SLICE_TRACK_KIND,
diff --git a/ui/src/common/empty_state.ts b/ui/src/common/empty_state.ts
index e3ed2b7..c513d73 100644
--- a/ui/src/common/empty_state.ts
+++ b/ui/src/common/empty_state.ts
@@ -95,7 +95,6 @@
scrollingTracks: [],
areas: {},
queries: {},
- permalink: {},
notes: {},
recordConfig: AUTOLOAD_STARTED_CONFIG_FLAG.get()
diff --git a/ui/src/common/metatracing.ts b/ui/src/common/metatracing.ts
index 756d68d..cdd43b4 100644
--- a/ui/src/common/metatracing.ts
+++ b/ui/src/common/metatracing.ts
@@ -13,12 +13,8 @@
// limitations under the License.
import {featureFlags} from '../core/feature_flags';
-import {
- MetatraceCategories,
- PerfettoMetatrace,
- Trace,
- TracePacket,
-} from '../protos';
+import {MetatraceCategories, PerfettoMetatrace} from '../protos';
+import protobuf from 'protobufjs/minimal';
const METATRACING_BUFFER_SIZE = 100000;
@@ -83,7 +79,7 @@
const traceEvents: TraceEvent[] = [];
function readMetatrace(): Uint8Array {
- const eventToPacket = (e: TraceEvent): TracePacket => {
+ const eventToPacket = (e: TraceEvent): Uint8Array => {
const metatraceEvent = PerfettoMetatrace.create({
eventName: e.eventName,
threadId: e.track,
@@ -97,20 +93,37 @@
}),
);
}
- return TracePacket.create({
- timestamp: e.startNs,
- timestampClockId: 1,
- perfettoMetatrace: metatraceEvent,
- });
+ const PROTO_VARINT_TYPE = 0;
+ const PROTO_LEN_DELIMITED_WIRE_TYPE = 2;
+ const TRACE_PACKET_PROTO_TAG = (1 << 3) | PROTO_LEN_DELIMITED_WIRE_TYPE;
+ const TRACE_PACKET_TIMESTAMP_TAG = (8 << 3) | PROTO_VARINT_TYPE;
+ const TRACE_PACKET_CLOCK_ID_TAG = (58 << 3) | PROTO_VARINT_TYPE;
+ const TRACE_PACKET_METATRACE_TAG =
+ (49 << 3) | PROTO_LEN_DELIMITED_WIRE_TYPE;
+
+ const wri = protobuf.Writer.create();
+ wri.uint32(TRACE_PACKET_PROTO_TAG);
+ wri.fork(); // Start of Trace Packet.
+ wri.uint32(TRACE_PACKET_TIMESTAMP_TAG).int64(e.startNs);
+ wri.uint32(TRACE_PACKET_CLOCK_ID_TAG).int32(1);
+ wri
+ .uint32(TRACE_PACKET_METATRACE_TAG)
+ .bytes(PerfettoMetatrace.encode(metatraceEvent).finish());
+ wri.ldelim();
+ return wri.finish();
};
- const packets: TracePacket[] = [];
+ const packets: Uint8Array[] = [];
for (const event of traceEvents) {
packets.push(eventToPacket(event));
}
- const trace = Trace.create({
- packet: packets,
- });
- return Trace.encode(trace).finish();
+ const totalLength = packets.reduce((acc, arr) => acc + arr.length, 0);
+ const trace = new Uint8Array(totalLength);
+ let offset = 0;
+ for (const packet of packets) {
+ trace.set(packet, offset);
+ offset += packet.length;
+ }
+ return trace;
}
interface TraceEventParams {
diff --git a/ui/src/common/plugins.ts b/ui/src/common/plugins.ts
index 3a5899e..f729597 100644
--- a/ui/src/common/plugins.ts
+++ b/ui/src/common/plugins.ts
@@ -17,7 +17,7 @@
import {Disposable, Trash} from '../base/disposable';
import {Registry} from '../base/registry';
import {Span, duration, time} from '../base/time';
-import {globals} from '../frontend/globals';
+import {TraceContext, globals} from '../frontend/globals';
import {
Command,
DetailsPanel,
@@ -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
@@ -275,10 +276,10 @@
};
return predicate(ref);
})
- .map((group) => group.id);
+ .map((group) => group.key);
- for (const trackGroupId of groupsToExpand) {
- globals.dispatch(Actions.toggleTrackGroupCollapsed({trackGroupId}));
+ for (const groupKey of groupsToExpand) {
+ globals.dispatch(Actions.toggleTrackGroupCollapsed({groupKey}));
}
},
@@ -293,10 +294,10 @@
};
return predicate(ref);
})
- .map((group) => group.id);
+ .map((group) => group.key);
- for (const trackGroupId of groupsToCollapse) {
- globals.dispatch(Actions.toggleTrackGroupCollapsed({trackGroupId}));
+ for (const groupKey of groupsToCollapse) {
+ globals.dispatch(Actions.toggleTrackGroupCollapsed({groupKey}));
}
},
@@ -342,11 +343,16 @@
return globals.store.createSubStore(['plugins', this.pluginId], migrate);
}
- readonly trace = {
- get span(): Span<time, duration> {
- return globals.stateTraceTimeTP();
- },
- };
+ 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/common/queries.ts b/ui/src/common/queries.ts
index a6c461b..227b9cd 100644
--- a/ui/src/common/queries.ts
+++ b/ui/src/common/queries.ts
@@ -40,48 +40,59 @@
params?: QueryRunParams,
): Promise<QueryResponse> {
const startMs = performance.now();
- const queryRes = engine.execute(sqlQuery);
// TODO(primiano): once the controller thread is gone we should pass down
// the result objects directly to the frontend, iterate over the result
// and deal with pagination there. For now we keep the old behavior and
// truncate to 10k rows.
- try {
- await queryRes.waitAllRows();
- } catch {
+ const maybeResult = await engine.tryQuery(sqlQuery);
+
+ if (maybeResult.success) {
+ const queryRes = maybeResult.result;
+ const convertNullsToString = params?.convertNullsToString ?? true;
+
+ const durationMs = performance.now() - startMs;
+ const rows: Row[] = [];
+ const columns = queryRes.columns();
+ let numRows = 0;
+ for (const iter = queryRes.iter({}); iter.valid(); iter.next()) {
+ const row: Row = {};
+ for (const colName of columns) {
+ const value = iter.get(colName);
+ row[colName] = value === null && convertNullsToString ? 'NULL' : value;
+ }
+ rows.push(row);
+ if (++numRows >= MAX_DISPLAY_ROWS) break;
+ }
+
+ const result: QueryResponse = {
+ query: sqlQuery,
+ durationMs,
+ error: queryRes.error(),
+ totalRowCount: queryRes.numRows(),
+ columns,
+ rows,
+ statementCount: queryRes.statementCount(),
+ statementWithOutputCount: queryRes.statementWithOutputCount(),
+ lastStatementSql: queryRes.lastStatementSql(),
+ };
+ return result;
+ } else {
// In the case of a query error we don't want the exception to bubble up
// as a crash. The |queryRes| object will be populated anyways.
// queryRes.error() is used to tell if the query errored or not. If it
// errored, the frontend will show a graceful message instead.
+ return {
+ query: sqlQuery,
+ durationMs: performance.now() - startMs,
+ error: maybeResult.error.message,
+ totalRowCount: 0,
+ columns: [],
+ rows: [],
+ statementCount: 0,
+ statementWithOutputCount: 0,
+ lastStatementSql: '',
+ };
}
-
- const convertNullsToString = params?.convertNullsToString ?? true;
-
- const durationMs = performance.now() - startMs;
- const rows: Row[] = [];
- const columns = queryRes.columns();
- let numRows = 0;
- for (const iter = queryRes.iter({}); iter.valid(); iter.next()) {
- const row: Row = {};
- for (const colName of columns) {
- const value = iter.get(colName);
- row[colName] = value === null && convertNullsToString ? 'NULL' : value;
- }
- rows.push(row);
- if (++numRows >= MAX_DISPLAY_ROWS) break;
- }
-
- const result: QueryResponse = {
- query: sqlQuery,
- durationMs,
- error: queryRes.error(),
- totalRowCount: queryRes.numRows(),
- columns,
- rows,
- statementCount: queryRes.statementCount(),
- statementWithOutputCount: queryRes.statementWithOutputCount(),
- lastStatementSql: queryRes.lastStatementSql(),
- };
- return result;
}
diff --git a/ui/src/common/state.ts b/ui/src/common/state.ts
index fc3018e..94eb429 100644
--- a/ui/src/common/state.ts
+++ b/ui/src/common/state.ts
@@ -151,7 +151,8 @@
// 52. Update track group state - don't make the summary track the first track.
// 53. Remove android log state.
// 54. Remove traceTime.
-export const STATE_VERSION = 54;
+// 55. Rename TrackGroupState.id -> TrackGroupState.key.
+export const STATE_VERSION = 55;
export const SCROLLING_TRACK_GROUP = 'ScrollingTracks';
@@ -288,7 +289,7 @@
}
export interface TrackGroupState {
- id: string;
+ key: string;
name: string;
collapsed: boolean;
tracks: string[]; // Child track ids.
@@ -310,12 +311,6 @@
query: string;
}
-export interface PermalinkConfig {
- requestId?: string; // Set by the frontend to request a new permalink.
- hash?: string; // Set by the controller when the link has been created.
- isRecordingConfig?: boolean; // this permalink request is for a recording config only
-}
-
export interface FrontendLocalState {
visibleState: VisibleState;
}
@@ -476,7 +471,7 @@
newEngineMode: NewEngineMode;
engine?: EngineConfig;
traceUuid?: string;
- trackGroups: ObjectById<TrackGroupState>;
+ trackGroups: ObjectByKey<TrackGroupState>;
tracks: ObjectByKey<TrackState>;
utidToThreadSortKey: UtidToTrackSortKey;
areas: ObjectById<AreaById>;
@@ -486,7 +481,6 @@
debugTrackId?: string;
lastTrackReloadRequest?: number;
queries: ObjectById<QueryConfig>;
- permalink: PermalinkConfig;
notes: ObjectById<Note | AreaNote>;
status: Status;
selection: Selection;
@@ -915,20 +909,19 @@
];
}
-export function getContainingTrackId(
+export function getContainingGroupKey(
state: State,
trackKey: string,
): null | string {
const track = state.tracks[trackKey];
- // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
- if (!track) {
+ if (track === undefined) {
return null;
}
- const parentId = track.trackGroup;
- if (!parentId) {
+ const parentGroupKey = track.trackGroup;
+ if (!parentGroupKey) {
return null;
}
- return parentId;
+ return parentGroupKey;
}
export function getLegacySelection(state: State): LegacySelection | null {
diff --git a/ui/src/common/state_unittest.ts b/ui/src/common/state_unittest.ts
index be66340..ff111de 100644
--- a/ui/src/common/state_unittest.ts
+++ b/ui/src/common/state_unittest.ts
@@ -15,7 +15,7 @@
import {PrimaryTrackSortKey} from '../public';
import {createEmptyState} from './empty_state';
-import {getContainingTrackId, State} from './state';
+import {getContainingGroupKey, State} from './state';
import {deserializeStateObject, serializeStateObject} from './upload_utils';
test('createEmptyState', () => {
@@ -40,9 +40,9 @@
trackGroup: 'containsB',
};
- expect(getContainingTrackId(state, 'z')).toEqual(null);
- expect(getContainingTrackId(state, 'a')).toEqual(null);
- expect(getContainingTrackId(state, 'b')).toEqual('containsB');
+ expect(getContainingGroupKey(state, 'z')).toEqual(null);
+ expect(getContainingGroupKey(state, 'a')).toEqual(null);
+ expect(getContainingGroupKey(state, 'b')).toEqual('containsB');
});
test('state is serializable', () => {
diff --git a/ui/src/controller/app_controller.ts b/ui/src/controller/app_controller.ts
index 4c8ad85..7d4d7cd 100644
--- a/ui/src/controller/app_controller.ts
+++ b/ui/src/controller/app_controller.ts
@@ -16,7 +16,6 @@
import {globals} from '../frontend/globals';
import {Child, Controller, ControllerInitializerAny} from './controller';
-import {PermalinkController} from './permalink_controller';
import {RecordController} from './record_controller';
import {TraceController} from './trace_controller';
@@ -40,9 +39,7 @@
// - An internal promise of a nested controller being resolved and manually
// re-triggering the controllers.
run() {
- const childControllers: ControllerInitializerAny[] = [
- Child('permalink', PermalinkController, {}),
- ];
+ const childControllers: ControllerInitializerAny[] = [];
if (!RECORDING_V2_FLAG.get()) {
childControllers.push(
Child('record', RecordController, {extensionPort: this.extensionPort}),
diff --git a/ui/src/controller/permalink_controller.ts b/ui/src/controller/permalink_controller.ts
deleted file mode 100644
index 1380ebf..0000000
--- a/ui/src/controller/permalink_controller.ts
+++ /dev/null
@@ -1,269 +0,0 @@
-// Copyright (C) 2018 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-import {produce} from 'immer';
-
-import {assertExists} from '../base/logging';
-import {runValidator} from '../base/validators';
-import {Actions} from '../common/actions';
-import {ConversionJobStatus} from '../common/conversion_jobs';
-import {
- createEmptyNonSerializableState,
- createEmptyState,
-} from '../common/empty_state';
-import {EngineConfig, ObjectById, State, STATE_VERSION} from '../common/state';
-import {
- BUCKET_NAME,
- buggyToSha256,
- deserializeStateObject,
- saveState,
- toSha256,
- TraceGcsUploader,
-} from '../common/upload_utils';
-import {globals} from '../frontend/globals';
-import {publishConversionJobStatusUpdate} from '../frontend/publish';
-import {Router} from '../frontend/router';
-
-import {Controller} from './controller';
-import {RecordConfig, recordConfigValidator} from './record_config_types';
-import {showModal} from '../widgets/modal';
-
-interface MultiEngineState {
- currentEngineId?: string;
- engines: ObjectById<EngineConfig>;
-}
-
-function isMultiEngineState(
- state: State | MultiEngineState,
-): state is MultiEngineState {
- if ((state as MultiEngineState).engines !== undefined) {
- return true;
- }
- return false;
-}
-
-export class PermalinkController extends Controller<'main'> {
- private lastRequestId?: string;
- constructor() {
- super('main');
- }
-
- run() {
- if (
- globals.state.permalink.requestId === undefined ||
- globals.state.permalink.requestId === this.lastRequestId
- ) {
- return;
- }
- const requestId = assertExists(globals.state.permalink.requestId);
- this.lastRequestId = requestId;
-
- // if the |hash| is not set, this is a request to create a permalink.
- if (globals.state.permalink.hash === undefined) {
- const isRecordingConfig = assertExists(
- globals.state.permalink.isRecordingConfig,
- );
-
- const jobName = 'create_permalink';
- publishConversionJobStatusUpdate({
- jobName,
- jobStatus: ConversionJobStatus.InProgress,
- });
-
- PermalinkController.createPermalink(isRecordingConfig)
- .then((hash) => {
- globals.dispatch(Actions.setPermalink({requestId, hash}));
- })
- .finally(() => {
- publishConversionJobStatusUpdate({
- jobName,
- jobStatus: ConversionJobStatus.NotRunning,
- });
- });
- return;
- }
-
- // Otherwise, this is a request to load the permalink.
- PermalinkController.loadState(globals.state.permalink.hash).then(
- (stateOrConfig) => {
- if (PermalinkController.isRecordConfig(stateOrConfig)) {
- // This permalink state only contains a RecordConfig. Show the
- // recording page with the config, but keep other state as-is.
- const validConfig = runValidator(
- recordConfigValidator,
- stateOrConfig as unknown,
- ).result;
- globals.dispatch(Actions.setRecordConfig({config: validConfig}));
- Router.navigate('#!/record');
- return;
- }
- globals.dispatch(Actions.setState({newState: stateOrConfig}));
- this.lastRequestId = stateOrConfig.permalink.requestId;
- },
- );
- }
-
- private static upgradeState(state: State): State {
- if (state.engine !== undefined && state.engine.source.type !== 'URL') {
- // All permalink traces should be modified to have a source.type=URL
- // pointing to the uploaded trace. Due to a bug in some older version
- // of the UI (b/327049372), an upload failure can end up with a state that
- // has type=FILE but a null file object. If this happens, invalidate the
- // trace and show a message.
- showModal({
- title: 'Cannot load trace permalink',
- content: m(
- 'div',
- 'The permalink stored on the server is corrupted ' +
- 'and cannot be loaded.',
- ),
- });
- return createEmptyState();
- }
-
- if (state.version !== STATE_VERSION) {
- const newState = createEmptyState();
- // Old permalinks from state versions prior to version 24
- // have multiple engines of which only one is identified as the
- // current engine via currentEngineId. Handle this case:
- if (isMultiEngineState(state)) {
- const engineId = state.currentEngineId;
- if (engineId !== undefined) {
- newState.engine = state.engines[engineId];
- }
- } else {
- newState.engine = state.engine;
- }
-
- if (newState.engine !== undefined) {
- newState.engine.ready = false;
- }
- const message =
- `Unable to parse old state version. Discarding state ` +
- `and loading trace.`;
- console.warn(message);
- PermalinkController.updateStatus(message);
- return newState;
- } else {
- // Loaded state is presumed to be compatible with the State type
- // definition in the app. However, a non-serializable part has to be
- // recreated.
- state.nonSerializableState = createEmptyNonSerializableState();
- }
- return state;
- }
-
- private static isRecordConfig(
- stateOrConfig: State | RecordConfig,
- ): stateOrConfig is RecordConfig {
- const mode = (stateOrConfig as {mode?: string}).mode;
- return (
- mode !== undefined &&
- ['STOP_WHEN_FULL', 'RING_BUFFER', 'LONG_TRACE'].includes(mode)
- );
- }
-
- private static async createPermalink(
- isRecordingConfig: boolean,
- ): Promise<string> {
- let uploadState: State | RecordConfig = globals.state;
-
- if (isRecordingConfig) {
- uploadState = globals.state.recordConfig;
- } else {
- const engine = assertExists(globals.getCurrentEngine());
- let dataToUpload: File | ArrayBuffer | undefined = undefined;
- let traceName = `trace ${engine.id}`;
- if (engine.source.type === 'FILE') {
- dataToUpload = engine.source.file;
- traceName = dataToUpload.name;
- } else if (engine.source.type === 'ARRAY_BUFFER') {
- dataToUpload = engine.source.buffer;
- } else if (engine.source.type !== 'URL') {
- throw new Error(`Cannot share trace ${JSON.stringify(engine.source)}`);
- }
-
- if (dataToUpload !== undefined) {
- PermalinkController.updateStatus(`Uploading ${traceName}`);
- const uploader = new TraceGcsUploader(dataToUpload, () => {
- switch (uploader.state) {
- case 'UPLOADING':
- const statusTxt = `Uploading ${uploader.getEtaString()}`;
- PermalinkController.updateStatus(statusTxt);
- break;
- case 'UPLOADED':
- // Convert state to use URLs and remove permalink.
- const url = uploader.uploadedUrl;
- uploadState = produce(globals.state, (draft) => {
- assertExists(draft.engine).source = {type: 'URL', url};
- draft.permalink = {};
- });
- break;
- case 'ERROR':
- PermalinkController.updateStatus(
- `Upload failed ${uploader.error}`,
- );
- break;
- } // switch (state)
- }); // onProgress
- await uploader.waitForCompletion();
- }
- }
-
- // Upload state.
- PermalinkController.updateStatus(`Creating permalink...`);
- const hash = await saveState(uploadState);
- PermalinkController.updateStatus(`Permalink ready`);
- return hash;
- }
-
- private static async loadState(id: string): Promise<State | RecordConfig> {
- const url = `https://storage.googleapis.com/${BUCKET_NAME}/${id}`;
- const response = await fetch(url);
- if (!response.ok) {
- throw new Error(
- `Could not fetch permalink.\n` +
- `Are you sure the id (${id}) is correct?\n` +
- `URL: ${url}`,
- );
- }
- const text = await response.text();
- const stateHash = await toSha256(text);
- const state = deserializeStateObject<State>(text);
- if (stateHash !== id) {
- // Old permalinks incorrectly dropped some digits from the
- // hexdigest of the SHA256. We don't want to invalidate those
- // links so we also compute the old string and try that here
- // also.
- const buggyStateHash = await buggyToSha256(text);
- if (buggyStateHash !== id) {
- throw new Error(`State hash does not match ${id} vs. ${stateHash}`);
- }
- }
- if (!this.isRecordConfig(state)) {
- return this.upgradeState(state);
- }
- return state;
- }
-
- private static updateStatus(msg: string): void {
- // TODO(hjd): Unify loading updates.
- globals.dispatch(
- Actions.updateStatus({
- msg,
- timestamp: Date.now() / 1000,
- }),
- );
- }
-}
diff --git a/ui/src/controller/search_controller.ts b/ui/src/controller/search_controller.ts
index ba2d354..bd6a3b1 100644
--- a/ui/src/controller/search_controller.ts
+++ b/ui/src/controller/search_controller.ts
@@ -163,7 +163,7 @@
utids.push(it.utid);
}
- const cpus = await this.engine.getCpus();
+ const cpus = globals.traceContext.cpus;
const maxCpu = Math.max(...cpus, -1);
const res = await this.query(`
diff --git a/ui/src/controller/selection_controller.ts b/ui/src/controller/selection_controller.ts
index 1b6dd0f..0a58c43 100644
--- a/ui/src/controller/selection_controller.ts
+++ b/ui/src/controller/selection_controller.ts
@@ -441,7 +441,7 @@
IFNULL(value, 0) as value
FROM counter WHERE ts < ${ts} and track_id = ${trackId}`);
const previousValue = previous.firstRow({value: NUM}).value;
- const endTs = rightTs !== -1n ? rightTs : globals.traceTime.end;
+ const endTs = rightTs !== -1n ? rightTs : globals.traceContext.end;
const delta = value - previousValue;
const duration = endTs - ts;
const trackKey = globals.trackManager.trackKeyByTrackId.get(trackId);
diff --git a/ui/src/controller/trace_controller.ts b/ui/src/controller/trace_controller.ts
index 445bd4a..a3e6a59 100644
--- a/ui/src/controller/trace_controller.ts
+++ b/ui/src/controller/trace_controller.ts
@@ -29,11 +29,11 @@
import {EngineMode, PendingDeeplinkState, ProfileType} from '../common/state';
import {featureFlags, Flag, PERF_SAMPLE_FLAG} from '../core/feature_flags';
import {
- defaultTraceTime,
+ defaultTraceContext,
globals,
QuantizedLoad,
ThreadDesc,
- TraceTime,
+ TraceContext,
} from '../frontend/globals';
import {
clearOverviewData,
@@ -41,7 +41,7 @@
publishMetricError,
publishOverviewData,
publishThreads,
- publishTraceDetails,
+ publishTraceContext,
} from '../frontend/publish';
import {addQueryResultsTab} from '../frontend/query_result_tab';
import {Router} from '../frontend/router';
@@ -452,7 +452,7 @@
const traceUuid = await this.cacheCurrentTrace();
const traceDetails = await getTraceTimeDetails(this.engine);
- publishTraceDetails(traceDetails);
+ publishTraceContext(traceDetails);
const shownJsonWarning =
window.localStorage.getItem(SHOWN_JSON_WARNING_KEY) !== null;
@@ -1100,8 +1100,8 @@
// if we have non-default visible state, update the visible time to it
const previousVisibleState = globals.stateVisibleTime();
const defaultTraceSpan = new TimeSpan(
- defaultTraceTime.start,
- defaultTraceTime.end,
+ defaultTraceContext.start,
+ defaultTraceContext.end,
);
if (
!(
@@ -1122,7 +1122,7 @@
let visibleEnd = traceEnd;
// compare start and end with metadata computed by the trace processor
- const mdTime = await engine.getTracingMetadataTimeBounds();
+ const mdTime = await getTracingMetadataTimeBounds(engine);
// make sure the bounds hold
if (Time.max(visibleStart, mdTime.start) < Time.min(visibleEnd, mdTime.end)) {
visibleStart = Time.max(visibleStart, mdTime.start);
@@ -1145,8 +1145,8 @@
return HighPrecisionTimeSpan.fromTime(visibleStart, visibleEnd);
}
-async function getTraceTimeDetails(engine: EngineBase): Promise<TraceTime> {
- const traceTime = await engine.getTraceTimeBounds();
+async function getTraceTimeDetails(engine: Engine): Promise<TraceContext> {
+ const traceTime = await getTraceTimeBounds(engine);
// Find the first REALTIME or REALTIME_COARSE clock snapshot.
// Prioritize REALTIME over REALTIME_COARSE.
@@ -1216,5 +1216,67 @@
realtimeOffset,
utcOffset,
traceTzOffset,
+ cpus: await getCpus(engine),
+ gpuCount: await getNumberOfGpus(engine),
};
}
+
+async function getTraceTimeBounds(
+ engine: Engine,
+): Promise<Span<time, duration>> {
+ const result = await engine.query(
+ `select start_ts as startTs, end_ts as endTs from trace_bounds`,
+ );
+ const bounds = result.firstRow({
+ startTs: LONG,
+ endTs: LONG,
+ });
+ return new TimeSpan(Time.fromRaw(bounds.startTs), Time.fromRaw(bounds.endTs));
+}
+
+// TODO(hjd): When streaming must invalidate this somehow.
+async function getCpus(engine: Engine): Promise<number[]> {
+ const cpus = [];
+ const queryRes = await engine.query(
+ 'select distinct(cpu) as cpu from sched order by cpu;',
+ );
+ for (const it = queryRes.iter({cpu: NUM}); it.valid(); it.next()) {
+ cpus.push(it.cpu);
+ }
+ return cpus;
+}
+
+async function getNumberOfGpus(engine: Engine): Promise<number> {
+ const result = await engine.query(`
+ select count(distinct(gpu_id)) as gpuCount
+ from gpu_counter_track
+ where name = 'gpufreq';
+ `);
+ return result.firstRow({gpuCount: NUM}).gpuCount;
+}
+
+async function getTracingMetadataTimeBounds(
+ engine: Engine,
+): Promise<Span<time, duration>> {
+ const queryRes = await engine.query(`select
+ name,
+ int_value as intValue
+ from metadata
+ where name = 'tracing_started_ns' or name = 'tracing_disabled_ns'
+ or name = 'all_data_source_started_ns'`);
+ let startBound = Time.MIN;
+ let endBound = Time.MAX;
+ const it = queryRes.iter({name: STR, intValue: LONG_NULL});
+ for (; it.valid(); it.next()) {
+ const columnName = it.name;
+ const timestamp = it.intValue;
+ if (timestamp === null) continue;
+ if (columnName === 'tracing_disabled_ns') {
+ endBound = Time.min(endBound, Time.fromRaw(timestamp));
+ } else {
+ startBound = Time.max(startBound, Time.fromRaw(timestamp));
+ }
+ }
+
+ return new TimeSpan(startBound, endBound);
+}
diff --git a/ui/src/controller/track_decider.ts b/ui/src/controller/track_decider.ts
index e1b907c..a9b186d 100644
--- a/ui/src/controller/track_decider.ts
+++ b/ui/src/controller/track_decider.ts
@@ -118,7 +118,7 @@
}
async addCpuSchedulingTracks(): Promise<void> {
- const cpus = await this.engine.getCpus();
+ const cpus = globals.traceContext.cpus;
const cpuToSize = await this.guessCpuSizes();
for (const cpu of cpus) {
@@ -134,7 +134,7 @@
}
async addCpuFreqTracks(engine: Engine): Promise<void> {
- const cpus = await this.engine.getCpus();
+ const cpus = globals.traceContext.cpus;
for (const cpu of cpus) {
// Only add a cpu freq track if we have
@@ -189,38 +189,38 @@
parentName: STR_NULL,
});
- const parentIdToGroupId = new Map<number, string>();
+ const parentIdToGroupKey = new Map<number, string>();
for (; it.valid(); it.next()) {
const kind = ASYNC_SLICE_TRACK_KIND;
const rawName = it.name === null ? undefined : it.name;
const rawParentName = it.parentName === null ? undefined : it.parentName;
const name = getTrackName({name: rawName, kind});
const parentTrackId = it.parentId;
- let trackGroup = SCROLLING_TRACK_GROUP;
+ let groupKey = SCROLLING_TRACK_GROUP;
if (parentTrackId !== null) {
- const groupId = parentIdToGroupId.get(parentTrackId);
- if (groupId === undefined) {
- trackGroup = uuidv4();
- parentIdToGroupId.set(parentTrackId, trackGroup);
+ const maybeGroupKey = parentIdToGroupKey.get(parentTrackId);
+ if (maybeGroupKey === undefined) {
+ groupKey = uuidv4();
+ parentIdToGroupKey.set(parentTrackId, groupKey);
const parentName = getTrackName({name: rawParentName, kind});
this.addTrackGroupActions.push(
Actions.addTrackGroup({
name: parentName,
- id: trackGroup,
+ key: groupKey,
collapsed: true,
}),
);
} else {
- trackGroup = groupId;
+ groupKey = maybeGroupKey;
}
}
const track: AddTrackArgs = {
uri: `perfetto.AsyncSlices#${rawName}.${it.parentId}`,
trackSortKey: PrimaryTrackSortKey.ASYNC_SLICE_TRACK,
- trackGroup,
+ trackGroup: groupKey,
name,
};
@@ -229,7 +229,7 @@
}
async addGpuFreqTracks(engine: Engine): Promise<void> {
- const numGpus = await this.engine.getNumberOfGpus();
+ const numGpus = globals.traceContext.gpuCount;
for (let gpu = 0; gpu < numGpus; gpu++) {
// Only add a gpu freq track if we have
// gpu freq data.
@@ -315,7 +315,7 @@
return;
}
- const id = uuidv4();
+ const groupUuid = uuidv4();
const summaryTrackKey = uuidv4();
let foundSummary = false;
@@ -328,14 +328,14 @@
track.key = summaryTrackKey;
track.trackGroup = undefined;
} else {
- track.trackGroup = id;
+ track.trackGroup = groupUuid;
}
}
const addGroup = Actions.addTrackGroup({
summaryTrackKey,
name: MEM_DMA_COUNTER_NAME,
- id,
+ key: groupUuid,
collapsed: true,
});
this.addTrackGroupActions.push(addGroup);
@@ -369,7 +369,7 @@
const groupName = group + key;
const addGroup = Actions.addTrackGroup({
name: groupName,
- id: value,
+ key: value,
collapsed: true,
});
this.addTrackGroupActions.push(addGroup);
@@ -408,7 +408,7 @@
const groupName = key;
const addGroup = Actions.addTrackGroup({
name: groupName,
- id: value,
+ key: value,
collapsed: true,
});
this.addTrackGroupActions.push(addGroup);
@@ -441,7 +441,7 @@
if (groupUuid !== undefined) {
const addGroup = Actions.addTrackGroup({
name: groupName,
- id: groupUuid,
+ key: groupUuid,
collapsed: true,
});
this.addTrackGroupActions.push(addGroup);
@@ -483,7 +483,7 @@
if (groupUuid !== undefined) {
const addGroup = Actions.addTrackGroup({
name: groupName,
- id: groupUuid,
+ key: groupUuid,
collapsed: true,
});
this.addTrackGroupActions.push(addGroup);
@@ -511,7 +511,7 @@
if (groupUuid !== undefined) {
const addGroup = Actions.addTrackGroup({
name: groupName,
- id: groupUuid,
+ key: groupUuid,
collapsed: true,
});
this.addTrackGroupActions.push(addGroup);
@@ -537,7 +537,7 @@
summaryTrackKey: string;
}
- const groupNameToIds = new Map<string, GroupIds>();
+ const groupNameToKeys = new Map<string, GroupIds>();
for (; sliceIt.valid(); sliceIt.next()) {
const id = sliceIt.id;
@@ -553,13 +553,13 @@
// If this is the first track encountered for a certain group,
// create an id for the group and use this track as the group's
// summary track.
- const groupIds = groupNameToIds.get(groupName);
- if (groupIds) {
- trackGroupId = groupIds.id;
+ const groupKeys = groupNameToKeys.get(groupName);
+ if (groupKeys) {
+ trackGroupId = groupKeys.id;
} else {
trackGroupId = uuidv4();
summaryTrackKey = uuidv4();
- groupNameToIds.set(groupName, {
+ groupNameToKeys.set(groupName, {
id: trackGroupId,
summaryTrackKey,
});
@@ -575,11 +575,11 @@
});
}
- for (const [groupName, groupIds] of groupNameToIds) {
+ for (const [groupName, groupKeys] of groupNameToKeys) {
const addGroup = Actions.addTrackGroup({
- summaryTrackKey: groupIds.summaryTrackKey,
+ summaryTrackKey: groupKeys.summaryTrackKey,
name: groupName,
- id: groupIds.id,
+ key: groupKeys.id,
collapsed: true,
});
this.addTrackGroupActions.push(addGroup);
@@ -843,7 +843,7 @@
for (const [name, groupUuid] of groupMap) {
const addGroup = Actions.addTrackGroup({
name: name,
- id: groupUuid,
+ key: groupUuid,
collapsed: true,
});
this.addTrackGroupActions.push(addGroup);
@@ -1153,7 +1153,7 @@
const addTrackGroup = Actions.addTrackGroup({
summaryTrackKey,
name: `Kernel threads`,
- id: kthreadGroupUuid,
+ key: kthreadGroupUuid,
collapsed: true,
});
this.addTrackGroupActions.push(addTrackGroup);
@@ -1320,7 +1320,7 @@
const addTrackGroup = Actions.addTrackGroup({
summaryTrackKey,
name,
- id: this.getOrCreateUuid(utid, upid),
+ key: this.getOrCreateUuid(utid, upid),
// Perf profiling tracks remain collapsed, otherwise we would have too
// many expanded process tracks for some perf traces, leading to
// jankyness.
@@ -1373,7 +1373,7 @@
groupUuid = uuidv4();
const addGroup = Actions.addTrackGroup({
name: groupName,
- id: groupUuid,
+ key: groupUuid,
collapsed: true,
fixedOrdering: true,
});
diff --git a/ui/src/core/default_plugins.ts b/ui/src/core/default_plugins.ts
index 15e5d2c..4f5460e 100644
--- a/ui/src/core/default_plugins.ts
+++ b/ui/src/core/default_plugins.ts
@@ -44,7 +44,6 @@
'perfetto.CpuProfile',
'perfetto.CpuSlices',
'perfetto.CriticalUserInteraction',
- 'perfetto.CustomSqlTrack',
'perfetto.DebugSlices',
'perfetto.Flows',
'perfetto.Frames',
@@ -58,4 +57,5 @@
'perfetto.ThreadState',
'perfetto.VisualisedArgs',
'org.kernel.LinuxKernelDevices',
+ 'perfetto.TrackUtils',
];
diff --git a/ui/src/core_plugins/annotation/index.ts b/ui/src/core_plugins/annotation/index.ts
index bda66f3..43ccb9d 100644
--- a/ui/src/core_plugins/annotation/index.ts
+++ b/ui/src/core_plugins/annotation/index.ts
@@ -18,7 +18,8 @@
SLICE_TRACK_KIND,
} from '../chrome_slices/chrome_slice_track';
import {NUM, NUM_NULL, STR} from '../../trace_processor/query_result';
-import {COUNTER_TRACK_KIND, TraceProcessorCounterTrack} from '../counter';
+import {COUNTER_TRACK_KIND} from '../counter';
+import {TraceProcessorCounterTrack} from '../counter/trace_processor_counter_track';
class AnnotationPlugin implements Plugin {
async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
diff --git a/ui/src/core_plugins/async_slices/async_slice_track_v2.ts b/ui/src/core_plugins/async_slices/async_slice_track.ts
similarity index 95%
rename from ui/src/core_plugins/async_slices/async_slice_track_v2.ts
rename to ui/src/core_plugins/async_slices/async_slice_track.ts
index 659a89a..275c418 100644
--- a/ui/src/core_plugins/async_slices/async_slice_track_v2.ts
+++ b/ui/src/core_plugins/async_slices/async_slice_track.ts
@@ -17,7 +17,7 @@
import {NewTrackArgs} from '../../frontend/track';
import {Slice} from '../../public';
-export class AsyncSliceTrackV2 extends NamedSliceTrack {
+export class AsyncSliceTrack extends NamedSliceTrack {
constructor(
args: NewTrackArgs,
maxDepth: number,
diff --git a/ui/src/core_plugins/async_slices/index.ts b/ui/src/core_plugins/async_slices/index.ts
index 644d22f..b01cb38 100644
--- a/ui/src/core_plugins/async_slices/index.ts
+++ b/ui/src/core_plugins/async_slices/index.ts
@@ -16,7 +16,7 @@
import {getTrackName} from '../../public/utils';
import {NUM, NUM_NULL, STR, STR_NULL} from '../../trace_processor/query_result';
-import {AsyncSliceTrackV2} from './async_slice_track_v2';
+import {AsyncSliceTrack} from './async_slice_track';
export const ASYNC_SLICE_TRACK_KIND = 'AsyncSliceTrack';
@@ -71,7 +71,7 @@
trackIds,
kind: ASYNC_SLICE_TRACK_KIND,
trackFactory: ({trackKey}) => {
- return new AsyncSliceTrackV2({engine, trackKey}, maxDepth, trackIds);
+ return new AsyncSliceTrack({engine, trackKey}, maxDepth, trackIds);
},
});
}
@@ -123,7 +123,7 @@
trackIds,
kind: ASYNC_SLICE_TRACK_KIND,
trackFactory: ({trackKey}) => {
- return new AsyncSliceTrackV2(
+ return new AsyncSliceTrack(
{engine: ctx.engine, trackKey},
maxDepth,
trackIds,
@@ -190,7 +190,7 @@
trackIds,
kind: ASYNC_SLICE_TRACK_KIND,
trackFactory: ({trackKey}) => {
- return new AsyncSliceTrackV2({engine, trackKey}, maxDepth, trackIds);
+ return new AsyncSliceTrack({engine, trackKey}, maxDepth, trackIds);
},
});
}
diff --git a/ui/src/core_plugins/chrome_critical_user_interactions/critical_user_interaction_track.ts b/ui/src/core_plugins/chrome_critical_user_interactions/critical_user_interaction_track.ts
new file mode 100644
index 0000000..9268750
--- /dev/null
+++ b/ui/src/core_plugins/chrome_critical_user_interactions/critical_user_interaction_track.ts
@@ -0,0 +1,180 @@
+// 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 {OnSliceClickArgs} from '../../frontend/base_slice_track';
+import {GenericSliceDetailsTab} from '../../frontend/generic_slice_details_tab';
+import {globals} from '../../frontend/globals';
+import {
+ NAMED_ROW,
+ NamedSliceTrackTypes,
+} from '../../frontend/named_slice_track';
+import {NUM, Slice, STR} from '../../public';
+import {
+ CustomSqlDetailsPanelConfig,
+ CustomSqlImportConfig,
+ CustomSqlTableDefConfig,
+ CustomSqlTableSliceTrack,
+} from '../../frontend/tracks/custom_sql_table_slice_track';
+
+import {PageLoadDetailsPanel} from './page_load_details_panel';
+import {StartupDetailsPanel} from './startup_details_panel';
+import {WebContentInteractionPanel} from './web_content_interaction_details_panel';
+
+export const CRITICAL_USER_INTERACTIONS_KIND =
+ 'org.chromium.CriticalUserInteraction.track';
+
+export const CRITICAL_USER_INTERACTIONS_ROW = {
+ ...NAMED_ROW,
+ scopedId: NUM,
+ type: STR,
+};
+export type CriticalUserInteractionRow = typeof CRITICAL_USER_INTERACTIONS_ROW;
+
+export interface CriticalUserInteractionSlice extends Slice {
+ scopedId: number;
+ type: string;
+}
+
+export interface CriticalUserInteractionSliceTrackTypes
+ extends NamedSliceTrackTypes {
+ slice: CriticalUserInteractionSlice;
+ row: CriticalUserInteractionRow;
+}
+
+enum CriticalUserInteractionType {
+ UNKNOWN = 'Unknown',
+ PAGE_LOAD = 'chrome_page_loads',
+ STARTUP = 'chrome_startups',
+ WEB_CONTENT_INTERACTION = 'chrome_web_content_interactions',
+}
+
+function convertToCriticalUserInteractionType(
+ cujType: string,
+): CriticalUserInteractionType {
+ switch (cujType) {
+ case CriticalUserInteractionType.PAGE_LOAD:
+ return CriticalUserInteractionType.PAGE_LOAD;
+ case CriticalUserInteractionType.STARTUP:
+ return CriticalUserInteractionType.STARTUP;
+ case CriticalUserInteractionType.WEB_CONTENT_INTERACTION:
+ return CriticalUserInteractionType.WEB_CONTENT_INTERACTION;
+ default:
+ return CriticalUserInteractionType.UNKNOWN;
+ }
+}
+
+export class CriticalUserInteractionTrack extends CustomSqlTableSliceTrack<CriticalUserInteractionSliceTrackTypes> {
+ static readonly kind = CRITICAL_USER_INTERACTIONS_KIND;
+
+ getSqlDataSource(): CustomSqlTableDefConfig {
+ return {
+ columns: [
+ // The scoped_id is not a unique identifier within the table; generate
+ // a unique id from type and scoped_id on the fly to use for slice
+ // selection.
+ 'hash(type, scoped_id) AS id',
+ 'scoped_id AS scopedId',
+ 'name',
+ 'ts',
+ 'dur',
+ 'type',
+ ],
+ sqlTableName: 'chrome_interactions',
+ };
+ }
+
+ getDetailsPanel(
+ args: OnSliceClickArgs<CriticalUserInteractionSliceTrackTypes['slice']>,
+ ): CustomSqlDetailsPanelConfig {
+ let detailsPanel = {
+ kind: GenericSliceDetailsTab.kind,
+ config: {
+ sqlTableName: this.tableName,
+ title: 'Chrome Interaction',
+ },
+ };
+
+ switch (convertToCriticalUserInteractionType(args.slice.type)) {
+ case CriticalUserInteractionType.PAGE_LOAD:
+ detailsPanel = {
+ kind: PageLoadDetailsPanel.kind,
+ config: {
+ sqlTableName: this.tableName,
+ title: 'Chrome Page Load',
+ },
+ };
+ break;
+ case CriticalUserInteractionType.STARTUP:
+ detailsPanel = {
+ kind: StartupDetailsPanel.kind,
+ config: {
+ sqlTableName: this.tableName,
+ title: 'Chrome Startup',
+ },
+ };
+ break;
+ case CriticalUserInteractionType.WEB_CONTENT_INTERACTION:
+ detailsPanel = {
+ kind: WebContentInteractionPanel.kind,
+ config: {
+ sqlTableName: this.tableName,
+ title: 'Chrome Web Content Interaction',
+ },
+ };
+ break;
+ default:
+ break;
+ }
+ return detailsPanel;
+ }
+
+ onSliceClick(
+ args: OnSliceClickArgs<CriticalUserInteractionSliceTrackTypes['slice']>,
+ ) {
+ const detailsPanelConfig = this.getDetailsPanel(args);
+ globals.makeSelection(
+ Actions.selectGenericSlice({
+ id: args.slice.scopedId,
+ sqlTableName: this.tableName,
+ start: args.slice.ts,
+ duration: args.slice.dur,
+ trackKey: this.trackKey,
+ detailsPanelConfig: {
+ kind: detailsPanelConfig.kind,
+ config: detailsPanelConfig.config,
+ },
+ }),
+ );
+ }
+
+ getSqlImports(): CustomSqlImportConfig {
+ return {
+ modules: ['chrome.interactions'],
+ };
+ }
+
+ getRowSpec(): CriticalUserInteractionSliceTrackTypes['row'] {
+ return CRITICAL_USER_INTERACTIONS_ROW;
+ }
+
+ rowToSlice(
+ row: CriticalUserInteractionSliceTrackTypes['row'],
+ ): CriticalUserInteractionSliceTrackTypes['slice'] {
+ const baseSlice = super.rowToSlice(row);
+ const scopedId = row.scopedId;
+ const type = row.type;
+ return {...baseSlice, scopedId, type};
+ }
+}
diff --git a/ui/src/core_plugins/chrome_critical_user_interactions/index.ts b/ui/src/core_plugins/chrome_critical_user_interactions/index.ts
index faa863b..b8d7050 100644
--- a/ui/src/core_plugins/chrome_critical_user_interactions/index.ts
+++ b/ui/src/core_plugins/chrome_critical_user_interactions/index.ts
@@ -16,186 +16,23 @@
import {Actions} from '../../common/actions';
import {SCROLLING_TRACK_GROUP} from '../../common/state';
-import {OnSliceClickArgs} from '../../frontend/base_slice_track';
-import {
- GenericSliceDetailsTab,
- GenericSliceDetailsTabConfig,
-} from '../../frontend/generic_slice_details_tab';
+import {GenericSliceDetailsTabConfig} from '../../frontend/generic_slice_details_tab';
import {globals} from '../../frontend/globals';
import {
- NAMED_ROW,
- NamedSliceTrackTypes,
-} from '../../frontend/named_slice_track';
-import {
BottomTabToSCSAdapter,
- NUM,
Plugin,
PluginContext,
PluginContextTrace,
PluginDescriptor,
PrimaryTrackSortKey,
- Slice,
- STR,
} from '../../public';
-import {
- CustomSqlDetailsPanelConfig,
- CustomSqlImportConfig,
- CustomSqlTableDefConfig,
- CustomSqlTableSliceTrack,
-} from '../custom_sql_table_slices';
import {PageLoadDetailsPanel} from './page_load_details_panel';
import {StartupDetailsPanel} from './startup_details_panel';
import {WebContentInteractionPanel} from './web_content_interaction_details_panel';
+import {CriticalUserInteractionTrack} from './critical_user_interaction_track';
-export const CRITICAL_USER_INTERACTIONS_KIND =
- 'org.chromium.CriticalUserInteraction.track';
-
-export const CRITICAL_USER_INTERACTIONS_ROW = {
- ...NAMED_ROW,
- scopedId: NUM,
- type: STR,
-};
-export type CriticalUserInteractionRow = typeof CRITICAL_USER_INTERACTIONS_ROW;
-
-export interface CriticalUserInteractionSlice extends Slice {
- scopedId: number;
- type: string;
-}
-
-export interface CriticalUserInteractionSliceTrackTypes
- extends NamedSliceTrackTypes {
- slice: CriticalUserInteractionSlice;
- row: CriticalUserInteractionRow;
-}
-
-enum CriticalUserInteractionType {
- UNKNOWN = 'Unknown',
- PAGE_LOAD = 'chrome_page_loads',
- STARTUP = 'chrome_startups',
- WEB_CONTENT_INTERACTION = 'chrome_web_content_interactions',
-}
-
-function convertToCriticalUserInteractionType(
- cujType: string,
-): CriticalUserInteractionType {
- switch (cujType) {
- case CriticalUserInteractionType.PAGE_LOAD:
- return CriticalUserInteractionType.PAGE_LOAD;
- case CriticalUserInteractionType.STARTUP:
- return CriticalUserInteractionType.STARTUP;
- case CriticalUserInteractionType.WEB_CONTENT_INTERACTION:
- return CriticalUserInteractionType.WEB_CONTENT_INTERACTION;
- default:
- return CriticalUserInteractionType.UNKNOWN;
- }
-}
-
-export class CriticalUserInteractionTrack extends CustomSqlTableSliceTrack<CriticalUserInteractionSliceTrackTypes> {
- static readonly kind = CRITICAL_USER_INTERACTIONS_KIND;
-
- getSqlDataSource(): CustomSqlTableDefConfig {
- return {
- columns: [
- // The scoped_id is not a unique identifier within the table; generate
- // a unique id from type and scoped_id on the fly to use for slice
- // selection.
- 'hash(type, scoped_id) AS id',
- 'scoped_id AS scopedId',
- 'name',
- 'ts',
- 'dur',
- 'type',
- ],
- sqlTableName: 'chrome_interactions',
- };
- }
-
- getDetailsPanel(
- args: OnSliceClickArgs<CriticalUserInteractionSliceTrackTypes['slice']>,
- ): CustomSqlDetailsPanelConfig {
- let detailsPanel = {
- kind: GenericSliceDetailsTab.kind,
- config: {
- sqlTableName: this.tableName,
- title: 'Chrome Interaction',
- },
- };
-
- switch (convertToCriticalUserInteractionType(args.slice.type)) {
- case CriticalUserInteractionType.PAGE_LOAD:
- detailsPanel = {
- kind: PageLoadDetailsPanel.kind,
- config: {
- sqlTableName: this.tableName,
- title: 'Chrome Page Load',
- },
- };
- break;
- case CriticalUserInteractionType.STARTUP:
- detailsPanel = {
- kind: StartupDetailsPanel.kind,
- config: {
- sqlTableName: this.tableName,
- title: 'Chrome Startup',
- },
- };
- break;
- case CriticalUserInteractionType.WEB_CONTENT_INTERACTION:
- detailsPanel = {
- kind: WebContentInteractionPanel.kind,
- config: {
- sqlTableName: this.tableName,
- title: 'Chrome Web Content Interaction',
- },
- };
- break;
- default:
- break;
- }
- return detailsPanel;
- }
-
- onSliceClick(
- args: OnSliceClickArgs<CriticalUserInteractionSliceTrackTypes['slice']>,
- ) {
- const detailsPanelConfig = this.getDetailsPanel(args);
- globals.makeSelection(
- Actions.selectGenericSlice({
- id: args.slice.scopedId,
- sqlTableName: this.tableName,
- start: args.slice.ts,
- duration: args.slice.dur,
- trackKey: this.trackKey,
- detailsPanelConfig: {
- kind: detailsPanelConfig.kind,
- config: detailsPanelConfig.config,
- },
- }),
- );
- }
-
- getSqlImports(): CustomSqlImportConfig {
- return {
- modules: ['chrome.interactions'],
- };
- }
-
- getRowSpec(): CriticalUserInteractionSliceTrackTypes['row'] {
- return CRITICAL_USER_INTERACTIONS_ROW;
- }
-
- rowToSlice(
- row: CriticalUserInteractionSliceTrackTypes['row'],
- ): CriticalUserInteractionSliceTrackTypes['slice'] {
- const baseSlice = super.rowToSlice(row);
- const scopedId = row.scopedId;
- const type = row.type;
- return {...baseSlice, scopedId, type};
- }
-}
-
-export function addCriticalUserInteractionTrack() {
+function addCriticalUserInteractionTrack() {
const trackKey = uuidv4();
globals.dispatchMultiple([
Actions.addTrack({
diff --git a/ui/src/core_plugins/chrome_scroll_jank/common.ts b/ui/src/core_plugins/chrome_scroll_jank/common.ts
index 8c20cc4..523880d 100644
--- a/ui/src/core_plugins/chrome_scroll_jank/common.ts
+++ b/ui/src/core_plugins/chrome_scroll_jank/common.ts
@@ -15,7 +15,7 @@
import {AddTrackArgs} from '../../common/actions';
import {ObjectByKey} from '../../common/state';
import {featureFlags} from '../../core/feature_flags';
-import {CustomSqlDetailsPanelConfig} from '../custom_sql_table_slices';
+import {CustomSqlDetailsPanelConfig} from '../../frontend/tracks/custom_sql_table_slice_track';
export const SCROLL_JANK_GROUP_ID = 'chrome-scroll-jank-track-group';
diff --git a/ui/src/core_plugins/chrome_scroll_jank/event_latency_track.ts b/ui/src/core_plugins/chrome_scroll_jank/event_latency_track.ts
index 6d89daa..98f3dd3 100644
--- a/ui/src/core_plugins/chrome_scroll_jank/event_latency_track.ts
+++ b/ui/src/core_plugins/chrome_scroll_jank/event_latency_track.ts
@@ -20,7 +20,7 @@
CustomSqlDetailsPanelConfig,
CustomSqlTableDefConfig,
CustomSqlTableSliceTrack,
-} from '../custom_sql_table_slices';
+} from '../../frontend/tracks/custom_sql_table_slice_track';
import {EventLatencySliceDetailsPanel} from './event_latency_details_panel';
import {JANK_COLOR} from './jank_colors';
diff --git a/ui/src/core_plugins/chrome_scroll_jank/index.ts b/ui/src/core_plugins/chrome_scroll_jank/index.ts
index 4267f4c..aae51c4 100644
--- a/ui/src/core_plugins/chrome_scroll_jank/index.ts
+++ b/ui/src/core_plugins/chrome_scroll_jank/index.ts
@@ -84,7 +84,7 @@
const addTrackGroup = Actions.addTrackGroup({
name: 'Chrome Scroll Jank',
- id: SCROLL_JANK_GROUP_ID,
+ key: SCROLL_JANK_GROUP_ID,
collapsed: false,
fixedOrdering: true,
});
diff --git a/ui/src/core_plugins/chrome_scroll_jank/scroll_jank_v3_track.ts b/ui/src/core_plugins/chrome_scroll_jank/scroll_jank_v3_track.ts
index f775740..11d6f1f 100644
--- a/ui/src/core_plugins/chrome_scroll_jank/scroll_jank_v3_track.ts
+++ b/ui/src/core_plugins/chrome_scroll_jank/scroll_jank_v3_track.ts
@@ -20,7 +20,7 @@
CustomSqlDetailsPanelConfig,
CustomSqlTableDefConfig,
CustomSqlTableSliceTrack,
-} from '../custom_sql_table_slices';
+} from '../../frontend/tracks/custom_sql_table_slice_track';
import {EventLatencyTrackTypes} from './event_latency_track';
import {JANK_COLOR} from './jank_colors';
diff --git a/ui/src/core_plugins/chrome_scroll_jank/scroll_track.ts b/ui/src/core_plugins/chrome_scroll_jank/scroll_track.ts
index 123d8f9..2d69139 100644
--- a/ui/src/core_plugins/chrome_scroll_jank/scroll_track.ts
+++ b/ui/src/core_plugins/chrome_scroll_jank/scroll_track.ts
@@ -19,7 +19,7 @@
CustomSqlDetailsPanelConfig,
CustomSqlTableDefConfig,
CustomSqlTableSliceTrack,
-} from '../custom_sql_table_slices';
+} from '../../frontend/tracks/custom_sql_table_slice_track';
import {
DecideTracksResult,
SCROLL_JANK_GROUP_ID,
diff --git a/ui/src/core_plugins/counter/index.ts b/ui/src/core_plugins/counter/index.ts
index 55773cd..7ac31d0 100644
--- a/ui/src/core_plugins/counter/index.ts
+++ b/ui/src/core_plugins/counter/index.ts
@@ -14,14 +14,10 @@
import m from 'mithril';
-import {Time} from '../../base/time';
-import {Actions} from '../../common/actions';
import {CounterDetailsPanel} from '../../frontend/counter_panel';
-import {globals} from '../../frontend/globals';
import {
NUM_NULL,
STR_NULL,
- LONG,
LONG_NULL,
NUM,
Plugin,
@@ -31,11 +27,8 @@
STR,
} from '../../public';
import {getTrackName} from '../../public/utils';
-import {
- BaseCounterTrack,
- BaseCounterTrackArgs,
- CounterOptions,
-} from '../../frontend/base_counter_track';
+import {CounterOptions} from '../../frontend/base_counter_track';
+import {TraceProcessorCounterTrack} from './trace_processor_counter_track';
export const COUNTER_TRACK_KIND = 'CounterTrack';
@@ -112,82 +105,6 @@
return options;
}
-interface TraceProcessorCounterTrackArgs extends BaseCounterTrackArgs {
- trackId: number;
- rootTable?: string;
-}
-
-export class TraceProcessorCounterTrack extends BaseCounterTrack {
- private trackId: number;
- private rootTable: string;
-
- constructor(args: TraceProcessorCounterTrackArgs) {
- super(args);
- this.trackId = args.trackId;
- this.rootTable = args.rootTable ?? 'counter';
- }
-
- getSqlSource() {
- return `select ts, value from ${this.rootTable} where track_id = ${this.trackId}`;
- }
-
- onMouseClick({x}: {x: number}): boolean {
- const {visibleTimeScale} = globals.timeline;
- const time = visibleTimeScale.pxToHpTime(x).toTime('floor');
-
- const query = `
- select
- id,
- ts as leftTs,
- (
- select ts
- from ${this.rootTable}
- where
- track_id = ${this.trackId}
- and ts >= ${time}
- order by ts
- limit 1
- ) as rightTs
- from ${this.rootTable}
- where
- track_id = ${this.trackId}
- and ts < ${time}
- order by ts DESC
- limit 1
- `;
-
- this.engine.query(query).then((result) => {
- const it = result.iter({
- id: NUM,
- leftTs: LONG,
- rightTs: LONG_NULL,
- });
- if (!it.valid()) {
- return;
- }
- const trackKey = this.trackKey;
- const id = it.id;
- const leftTs = Time.fromRaw(it.leftTs);
-
- // TODO(stevegolton): Don't try to guess times and durations here, make it
- // obvious to the user that this counter sample has no duration as it's
- // the last one in the series
- const rightTs = Time.fromRaw(it.rightTs ?? leftTs);
-
- globals.makeSelection(
- Actions.selectCounter({
- leftTs,
- rightTs,
- id,
- trackKey,
- }),
- );
- });
-
- return true;
- }
-}
-
class CounterPlugin implements Plugin {
async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
await this.addCounterTracks(ctx);
@@ -421,7 +338,7 @@
private async addGpuFrequencyTracks(ctx: PluginContextTrace) {
const engine = ctx.engine;
- const numGpus = await engine.getNumberOfGpus();
+ const numGpus = ctx.trace.gpuCount;
for (let gpu = 0; gpu < numGpus; gpu++) {
// Only add a gpu freq track if we have
diff --git a/ui/src/core_plugins/counter/trace_processor_counter_track.ts b/ui/src/core_plugins/counter/trace_processor_counter_track.ts
new file mode 100644
index 0000000..a80ac4b
--- /dev/null
+++ b/ui/src/core_plugins/counter/trace_processor_counter_track.ts
@@ -0,0 +1,98 @@
+// 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 {Time} from '../../base/time';
+import {Actions} from '../../common/actions';
+import {globals} from '../../frontend/globals';
+import {LONG, LONG_NULL, NUM} from '../../public';
+import {
+ BaseCounterTrack,
+ BaseCounterTrackArgs,
+} from '../../frontend/base_counter_track';
+
+interface TraceProcessorCounterTrackArgs extends BaseCounterTrackArgs {
+ trackId: number;
+ rootTable?: string;
+}
+
+export class TraceProcessorCounterTrack extends BaseCounterTrack {
+ private trackId: number;
+ private rootTable: string;
+
+ constructor(args: TraceProcessorCounterTrackArgs) {
+ super(args);
+ this.trackId = args.trackId;
+ this.rootTable = args.rootTable ?? 'counter';
+ }
+
+ getSqlSource() {
+ return `select ts, value from ${this.rootTable} where track_id = ${this.trackId}`;
+ }
+
+ onMouseClick({x}: {x: number}): boolean {
+ const {visibleTimeScale} = globals.timeline;
+ const time = visibleTimeScale.pxToHpTime(x).toTime('floor');
+
+ const query = `
+ select
+ id,
+ ts as leftTs,
+ (
+ select ts
+ from ${this.rootTable}
+ where
+ track_id = ${this.trackId}
+ and ts >= ${time}
+ order by ts
+ limit 1
+ ) as rightTs
+ from ${this.rootTable}
+ where
+ track_id = ${this.trackId}
+ and ts < ${time}
+ order by ts DESC
+ limit 1
+ `;
+
+ this.engine.query(query).then((result) => {
+ const it = result.iter({
+ id: NUM,
+ leftTs: LONG,
+ rightTs: LONG_NULL,
+ });
+ if (!it.valid()) {
+ return;
+ }
+ const trackKey = this.trackKey;
+ const id = it.id;
+ const leftTs = Time.fromRaw(it.leftTs);
+
+ // TODO(stevegolton): Don't try to guess times and durations here, make it
+ // obvious to the user that this counter sample has no duration as it's
+ // the last one in the series
+ const rightTs = Time.fromRaw(it.rightTs ?? leftTs);
+
+ globals.makeSelection(
+ Actions.selectCounter({
+ leftTs,
+ rightTs,
+ id,
+ trackKey,
+ }),
+ );
+ });
+
+ return true;
+ }
+}
diff --git a/ui/src/core_plugins/cpu_freq/index.ts b/ui/src/core_plugins/cpu_freq/index.ts
index 07b9b1d..a1d3947 100644
--- a/ui/src/core_plugins/cpu_freq/index.ts
+++ b/ui/src/core_plugins/cpu_freq/index.ts
@@ -73,14 +73,14 @@
async onCreate() {
if (this.config.idleTrackId === undefined) {
- await this.engine.execute(`
+ await this.engine.query(`
create view raw_freq_idle_${this.trackUuid} as
select ts, dur, value as freqValue, -1 as idleValue
from experimental_counter_dur c
where track_id = ${this.config.freqTrackId}
`);
} else {
- await this.engine.execute(`
+ await this.engine.query(`
create view raw_freq_${this.trackUuid} as
select ts, dur, value as freqValue
from experimental_counter_dur c
@@ -99,7 +99,7 @@
`);
}
- await this.engine.execute(`
+ await this.engine.query(`
create virtual table cpu_freq_${this.trackUuid}
using __intrinsic_counter_mipmap((
select ts, freqValue as value
@@ -119,13 +119,15 @@
}
async onDestroy(): Promise<void> {
- if (this.engine.isAlive) {
- await this.engine.query(`drop table cpu_freq_${this.trackUuid}`);
- await this.engine.query(`drop table cpu_idle_${this.trackUuid}`);
- await this.engine.query(`drop table raw_freq_idle_${this.trackUuid}`);
- await this.engine.query(`drop view if exists raw_freq_${this.trackUuid}`);
- await this.engine.query(`drop view if exists raw_idle_${this.trackUuid}`);
- }
+ await this.engine.tryQuery(`drop table cpu_freq_${this.trackUuid}`);
+ await this.engine.tryQuery(`drop table cpu_idle_${this.trackUuid}`);
+ await this.engine.tryQuery(`drop table raw_freq_idle_${this.trackUuid}`);
+ await this.engine.tryQuery(
+ `drop view if exists raw_freq_${this.trackUuid}`,
+ );
+ await this.engine.tryQuery(
+ `drop view if exists raw_idle_${this.trackUuid}`,
+ );
}
async onBoundsChange(
@@ -403,7 +405,7 @@
async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
const {engine} = ctx;
- const cpus = await engine.getCpus();
+ const cpus = ctx.trace.cpus;
const maxCpuFreqResult = await engine.query(`
select ifnull(max(value), 0) as freq
diff --git a/ui/src/core_plugins/cpu_profile/cpu_profile_track.ts b/ui/src/core_plugins/cpu_profile/cpu_profile_track.ts
new file mode 100644
index 0000000..802e341
--- /dev/null
+++ b/ui/src/core_plugins/cpu_profile/cpu_profile_track.ts
@@ -0,0 +1,248 @@
+// 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 {searchSegment} from '../../base/binary_search';
+import {duration, Time, time} from '../../base/time';
+import {getLegacySelection} from '../../common/state';
+import {Actions} from '../../common/actions';
+import {colorForSample} from '../../core/colorizer';
+import {TrackData} from '../../common/track_data';
+import {TimelineFetcher} from '../../common/track_helper';
+import {globals} from '../../frontend/globals';
+import {PanelSize} from '../../frontend/panel';
+import {TimeScale} from '../../frontend/time_scale';
+import {Engine, Track} from '../../public';
+import {LONG, NUM} from '../../trace_processor/query_result';
+
+const BAR_HEIGHT = 3;
+const MARGIN_TOP = 4.5;
+const RECT_HEIGHT = 30.5;
+
+interface Data extends TrackData {
+ ids: Float64Array;
+ tsStarts: BigInt64Array;
+ callsiteId: Uint32Array;
+}
+
+export class CpuProfileTrack implements Track {
+ private centerY = this.getHeight() / 2 + BAR_HEIGHT;
+ private markerWidth = (this.getHeight() - MARGIN_TOP - BAR_HEIGHT) / 2;
+ private hoveredTs: time | undefined = undefined;
+ private fetcher = new TimelineFetcher<Data>(this.onBoundsChange.bind(this));
+ private engine: Engine;
+ private utid: number;
+
+ constructor(engine: Engine, utid: number) {
+ this.engine = engine;
+ this.utid = utid;
+ }
+
+ async onUpdate(): Promise<void> {
+ await this.fetcher.requestDataForCurrentTime();
+ }
+
+ async onBoundsChange(
+ start: time,
+ end: time,
+ resolution: duration,
+ ): Promise<Data> {
+ const query = `select
+ id,
+ ts,
+ callsite_id as callsiteId
+ from cpu_profile_stack_sample
+ where utid = ${this.utid}
+ order by ts`;
+
+ const result = await this.engine.query(query);
+ const numRows = result.numRows();
+ const data: Data = {
+ start,
+ end,
+ resolution,
+ length: numRows,
+ ids: new Float64Array(numRows),
+ tsStarts: new BigInt64Array(numRows),
+ callsiteId: new Uint32Array(numRows),
+ };
+
+ const it = result.iter({id: NUM, ts: LONG, callsiteId: NUM});
+ for (let row = 0; it.valid(); it.next(), ++row) {
+ data.ids[row] = it.id;
+ data.tsStarts[row] = it.ts;
+ data.callsiteId[row] = it.callsiteId;
+ }
+
+ return data;
+ }
+
+ async onDestroy(): Promise<void> {
+ this.fetcher.dispose();
+ }
+
+ getHeight() {
+ return MARGIN_TOP + RECT_HEIGHT - 1;
+ }
+
+ render(ctx: CanvasRenderingContext2D, _size: PanelSize): void {
+ const {visibleTimeScale: timeScale} = globals.timeline;
+ const data = this.fetcher.data;
+
+ if (data === undefined) return;
+
+ for (let i = 0; i < data.tsStarts.length; i++) {
+ const centerX = Time.fromRaw(data.tsStarts[i]);
+ const selection = getLegacySelection(globals.state);
+ const isHovered = this.hoveredTs === centerX;
+ const isSelected =
+ selection !== null &&
+ selection.kind === 'CPU_PROFILE_SAMPLE' &&
+ selection.ts === centerX;
+ const strokeWidth = isSelected ? 3 : 0;
+ this.drawMarker(
+ ctx,
+ timeScale.timeToPx(centerX),
+ this.centerY,
+ isHovered,
+ strokeWidth,
+ data.callsiteId[i],
+ );
+ }
+
+ // Group together identical identical CPU profile samples by connecting them
+ // with an horizontal bar.
+ let clusterStartIndex = 0;
+ while (clusterStartIndex < data.tsStarts.length) {
+ const callsiteId = data.callsiteId[clusterStartIndex];
+
+ // Find the end of the cluster by searching for the next different CPU
+ // sample. The resulting range [clusterStartIndex, clusterEndIndex] is
+ // inclusive and within array bounds.
+ let clusterEndIndex = clusterStartIndex;
+ while (
+ clusterEndIndex + 1 < data.tsStarts.length &&
+ data.callsiteId[clusterEndIndex + 1] === callsiteId
+ ) {
+ clusterEndIndex++;
+ }
+
+ // If there are multiple CPU samples in the cluster, draw a line.
+ if (clusterStartIndex !== clusterEndIndex) {
+ const startX = Time.fromRaw(data.tsStarts[clusterStartIndex]);
+ const endX = Time.fromRaw(data.tsStarts[clusterEndIndex]);
+ const leftPx = timeScale.timeToPx(startX) - this.markerWidth;
+ const rightPx = timeScale.timeToPx(endX) + this.markerWidth;
+ const width = rightPx - leftPx;
+ ctx.fillStyle = colorForSample(callsiteId, false);
+ ctx.fillRect(leftPx, MARGIN_TOP, width, BAR_HEIGHT);
+ }
+
+ // Move to the next cluster.
+ clusterStartIndex = clusterEndIndex + 1;
+ }
+ }
+
+ drawMarker(
+ ctx: CanvasRenderingContext2D,
+ x: number,
+ y: number,
+ isHovered: boolean,
+ strokeWidth: number,
+ callsiteId: number,
+ ): void {
+ ctx.beginPath();
+ ctx.moveTo(x - this.markerWidth, y - this.markerWidth);
+ ctx.lineTo(x, y + this.markerWidth);
+ ctx.lineTo(x + this.markerWidth, y - this.markerWidth);
+ ctx.lineTo(x - this.markerWidth, y - this.markerWidth);
+ ctx.closePath();
+ ctx.fillStyle = colorForSample(callsiteId, isHovered);
+ ctx.fill();
+ if (strokeWidth > 0) {
+ ctx.strokeStyle = colorForSample(callsiteId, false);
+ ctx.lineWidth = strokeWidth;
+ ctx.stroke();
+ }
+ }
+
+ onMouseMove({x, y}: {x: number; y: number}) {
+ const data = this.fetcher.data;
+ if (data === undefined) return;
+ const {visibleTimeScale: timeScale} = globals.timeline;
+ const time = timeScale.pxToHpTime(x);
+ const [left, right] = searchSegment(data.tsStarts, time.toTime());
+ const index = this.findTimestampIndex(left, timeScale, data, x, y, right);
+ this.hoveredTs =
+ index === -1 ? undefined : Time.fromRaw(data.tsStarts[index]);
+ }
+
+ onMouseOut() {
+ this.hoveredTs = undefined;
+ }
+
+ onMouseClick({x, y}: {x: number; y: number}) {
+ const data = this.fetcher.data;
+ if (data === undefined) return false;
+ const {visibleTimeScale: timeScale} = globals.timeline;
+
+ const time = timeScale.pxToHpTime(x);
+ const [left, right] = searchSegment(data.tsStarts, time.toTime());
+
+ const index = this.findTimestampIndex(left, timeScale, data, x, y, right);
+
+ if (index !== -1) {
+ const id = data.ids[index];
+ const ts = Time.fromRaw(data.tsStarts[index]);
+
+ globals.makeSelection(
+ Actions.selectCpuProfileSample({id, utid: this.utid, ts}),
+ );
+ return true;
+ }
+ return false;
+ }
+
+ // If the markers overlap the rightmost one will be selected.
+ findTimestampIndex(
+ left: number,
+ timeScale: TimeScale,
+ data: Data,
+ x: number,
+ y: number,
+ right: number,
+ ): number {
+ let index = -1;
+ if (left !== -1) {
+ const start = Time.fromRaw(data.tsStarts[left]);
+ const centerX = timeScale.timeToPx(start);
+ if (this.isInMarker(x, y, centerX)) {
+ index = left;
+ }
+ }
+ if (right !== -1) {
+ const start = Time.fromRaw(data.tsStarts[right]);
+ const centerX = timeScale.timeToPx(start);
+ if (this.isInMarker(x, y, centerX)) {
+ index = right;
+ }
+ }
+ return index;
+ }
+
+ isInMarker(x: number, y: number, centerX: number) {
+ return (
+ Math.abs(x - centerX) + Math.abs(y - this.centerY) <= this.markerWidth
+ );
+ }
+}
diff --git a/ui/src/core_plugins/cpu_profile/index.ts b/ui/src/core_plugins/cpu_profile/index.ts
index 364225d..9e8fe84 100644
--- a/ui/src/core_plugins/cpu_profile/index.ts
+++ b/ui/src/core_plugins/cpu_profile/index.ts
@@ -12,255 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {searchSegment} from '../../base/binary_search';
-import {duration, Time, time} from '../../base/time';
-import {getLegacySelection} from '../../common/state';
-import {Actions} from '../../common/actions';
-import {colorForSample} from '../../core/colorizer';
-import {TrackData} from '../../common/track_data';
-import {TimelineFetcher} from '../../common/track_helper';
import {CpuProfileDetailsPanel} from '../../frontend/cpu_profile_panel';
-import {globals} from '../../frontend/globals';
-import {PanelSize} from '../../frontend/panel';
-import {TimeScale} from '../../frontend/time_scale';
-import {
- Engine,
- Plugin,
- PluginContextTrace,
- PluginDescriptor,
- Track,
-} from '../../public';
-import {
- LONG,
- NUM,
- NUM_NULL,
- STR_NULL,
-} from '../../trace_processor/query_result';
-
-const BAR_HEIGHT = 3;
-const MARGIN_TOP = 4.5;
-const RECT_HEIGHT = 30.5;
+import {Plugin, PluginContextTrace, PluginDescriptor} from '../../public';
+import {NUM, NUM_NULL, STR_NULL} from '../../trace_processor/query_result';
+import {CpuProfileTrack} from './cpu_profile_track';
export const CPU_PROFILE_TRACK_KIND = 'CpuProfileTrack';
-interface Data extends TrackData {
- ids: Float64Array;
- tsStarts: BigInt64Array;
- callsiteId: Uint32Array;
-}
-
-class CpuProfileTrack implements Track {
- private centerY = this.getHeight() / 2 + BAR_HEIGHT;
- private markerWidth = (this.getHeight() - MARGIN_TOP - BAR_HEIGHT) / 2;
- private hoveredTs: time | undefined = undefined;
- private fetcher = new TimelineFetcher<Data>(this.onBoundsChange.bind(this));
- private engine: Engine;
- private utid: number;
-
- constructor(engine: Engine, utid: number) {
- this.engine = engine;
- this.utid = utid;
- }
-
- async onUpdate(): Promise<void> {
- await this.fetcher.requestDataForCurrentTime();
- }
-
- async onBoundsChange(
- start: time,
- end: time,
- resolution: duration,
- ): Promise<Data> {
- const query = `select
- id,
- ts,
- callsite_id as callsiteId
- from cpu_profile_stack_sample
- where utid = ${this.utid}
- order by ts`;
-
- const result = await this.engine.query(query);
- const numRows = result.numRows();
- const data: Data = {
- start,
- end,
- resolution,
- length: numRows,
- ids: new Float64Array(numRows),
- tsStarts: new BigInt64Array(numRows),
- callsiteId: new Uint32Array(numRows),
- };
-
- const it = result.iter({id: NUM, ts: LONG, callsiteId: NUM});
- for (let row = 0; it.valid(); it.next(), ++row) {
- data.ids[row] = it.id;
- data.tsStarts[row] = it.ts;
- data.callsiteId[row] = it.callsiteId;
- }
-
- return data;
- }
-
- async onDestroy(): Promise<void> {
- this.fetcher.dispose();
- }
-
- getHeight() {
- return MARGIN_TOP + RECT_HEIGHT - 1;
- }
-
- render(ctx: CanvasRenderingContext2D, _size: PanelSize): void {
- const {visibleTimeScale: timeScale} = globals.timeline;
- const data = this.fetcher.data;
-
- if (data === undefined) return;
-
- for (let i = 0; i < data.tsStarts.length; i++) {
- const centerX = Time.fromRaw(data.tsStarts[i]);
- const selection = getLegacySelection(globals.state);
- const isHovered = this.hoveredTs === centerX;
- const isSelected =
- selection !== null &&
- selection.kind === 'CPU_PROFILE_SAMPLE' &&
- selection.ts === centerX;
- const strokeWidth = isSelected ? 3 : 0;
- this.drawMarker(
- ctx,
- timeScale.timeToPx(centerX),
- this.centerY,
- isHovered,
- strokeWidth,
- data.callsiteId[i],
- );
- }
-
- // Group together identical identical CPU profile samples by connecting them
- // with an horizontal bar.
- let clusterStartIndex = 0;
- while (clusterStartIndex < data.tsStarts.length) {
- const callsiteId = data.callsiteId[clusterStartIndex];
-
- // Find the end of the cluster by searching for the next different CPU
- // sample. The resulting range [clusterStartIndex, clusterEndIndex] is
- // inclusive and within array bounds.
- let clusterEndIndex = clusterStartIndex;
- while (
- clusterEndIndex + 1 < data.tsStarts.length &&
- data.callsiteId[clusterEndIndex + 1] === callsiteId
- ) {
- clusterEndIndex++;
- }
-
- // If there are multiple CPU samples in the cluster, draw a line.
- if (clusterStartIndex !== clusterEndIndex) {
- const startX = Time.fromRaw(data.tsStarts[clusterStartIndex]);
- const endX = Time.fromRaw(data.tsStarts[clusterEndIndex]);
- const leftPx = timeScale.timeToPx(startX) - this.markerWidth;
- const rightPx = timeScale.timeToPx(endX) + this.markerWidth;
- const width = rightPx - leftPx;
- ctx.fillStyle = colorForSample(callsiteId, false);
- ctx.fillRect(leftPx, MARGIN_TOP, width, BAR_HEIGHT);
- }
-
- // Move to the next cluster.
- clusterStartIndex = clusterEndIndex + 1;
- }
- }
-
- drawMarker(
- ctx: CanvasRenderingContext2D,
- x: number,
- y: number,
- isHovered: boolean,
- strokeWidth: number,
- callsiteId: number,
- ): void {
- ctx.beginPath();
- ctx.moveTo(x - this.markerWidth, y - this.markerWidth);
- ctx.lineTo(x, y + this.markerWidth);
- ctx.lineTo(x + this.markerWidth, y - this.markerWidth);
- ctx.lineTo(x - this.markerWidth, y - this.markerWidth);
- ctx.closePath();
- ctx.fillStyle = colorForSample(callsiteId, isHovered);
- ctx.fill();
- if (strokeWidth > 0) {
- ctx.strokeStyle = colorForSample(callsiteId, false);
- ctx.lineWidth = strokeWidth;
- ctx.stroke();
- }
- }
-
- onMouseMove({x, y}: {x: number; y: number}) {
- const data = this.fetcher.data;
- if (data === undefined) return;
- const {visibleTimeScale: timeScale} = globals.timeline;
- const time = timeScale.pxToHpTime(x);
- const [left, right] = searchSegment(data.tsStarts, time.toTime());
- const index = this.findTimestampIndex(left, timeScale, data, x, y, right);
- this.hoveredTs =
- index === -1 ? undefined : Time.fromRaw(data.tsStarts[index]);
- }
-
- onMouseOut() {
- this.hoveredTs = undefined;
- }
-
- onMouseClick({x, y}: {x: number; y: number}) {
- const data = this.fetcher.data;
- if (data === undefined) return false;
- const {visibleTimeScale: timeScale} = globals.timeline;
-
- const time = timeScale.pxToHpTime(x);
- const [left, right] = searchSegment(data.tsStarts, time.toTime());
-
- const index = this.findTimestampIndex(left, timeScale, data, x, y, right);
-
- if (index !== -1) {
- const id = data.ids[index];
- const ts = Time.fromRaw(data.tsStarts[index]);
-
- globals.makeSelection(
- Actions.selectCpuProfileSample({id, utid: this.utid, ts}),
- );
- return true;
- }
- return false;
- }
-
- // If the markers overlap the rightmost one will be selected.
- findTimestampIndex(
- left: number,
- timeScale: TimeScale,
- data: Data,
- x: number,
- y: number,
- right: number,
- ): number {
- let index = -1;
- if (left !== -1) {
- const start = Time.fromRaw(data.tsStarts[left]);
- const centerX = timeScale.timeToPx(start);
- if (this.isInMarker(x, y, centerX)) {
- index = left;
- }
- }
- if (right !== -1) {
- const start = Time.fromRaw(data.tsStarts[right]);
- const centerX = timeScale.timeToPx(start);
- if (this.isInMarker(x, y, centerX)) {
- index = right;
- }
- }
- return index;
- }
-
- isInMarker(x: number, y: number, centerX: number) {
- return (
- Math.abs(x - centerX) + Math.abs(y - this.centerY) <= this.markerWidth
- );
- }
-}
-
class CpuProfile implements Plugin {
async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
const result = await ctx.engine.query(`
diff --git a/ui/src/core_plugins/cpu_slices/cpu_slice_track.ts b/ui/src/core_plugins/cpu_slices/cpu_slice_track.ts
new file mode 100644
index 0000000..70acb78
--- /dev/null
+++ b/ui/src/core_plugins/cpu_slices/cpu_slice_track.ts
@@ -0,0 +1,474 @@
+// 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 {BigintMath as BIMath} from '../../base/bigint_math';
+import {search, searchEq, searchSegment} from '../../base/binary_search';
+import {assertExists, assertTrue} from '../../base/logging';
+import {Duration, duration, Time, time} from '../../base/time';
+import {Actions} from '../../common/actions';
+import {getLegacySelection} from '../../common/state';
+import {
+ cropText,
+ drawDoubleHeadedArrow,
+ drawIncompleteSlice,
+ drawTrackHoverTooltip,
+} from '../../common/canvas_utils';
+import {Color} from '../../core/color';
+import {colorForThread} from '../../core/colorizer';
+import {TrackData} from '../../common/track_data';
+import {TimelineFetcher} from '../../common/track_helper';
+import {checkerboardExcept} from '../../frontend/checkerboard';
+import {globals} from '../../frontend/globals';
+import {PanelSize} from '../../frontend/panel';
+import {Engine, Track} from '../../public';
+import {LONG, NUM} from '../../trace_processor/query_result';
+import {uuidv4Sql} from '../../base/uuid';
+
+export interface Data extends TrackData {
+ // Slices are stored in a columnar fashion. All fields have the same length.
+ ids: Float64Array;
+ startQs: BigInt64Array;
+ endQs: BigInt64Array;
+ utids: Uint32Array;
+ flags: Uint8Array;
+ lastRowId: number;
+}
+
+const MARGIN_TOP = 3;
+const RECT_HEIGHT = 24;
+const TRACK_HEIGHT = MARGIN_TOP * 2 + RECT_HEIGHT;
+
+const CPU_SLICE_FLAGS_INCOMPLETE = 1;
+const CPU_SLICE_FLAGS_REALTIME = 2;
+
+export class CpuSliceTrack implements Track {
+ private mousePos?: {x: number; y: number};
+ private utidHoveredInThisTrack = -1;
+ private fetcher = new TimelineFetcher<Data>(this.onBoundsChange.bind(this));
+
+ private lastRowId = -1;
+ private engine: Engine;
+ private cpu: number;
+ private trackKey: string;
+ private trackUuid = uuidv4Sql();
+
+ constructor(engine: Engine, trackKey: string, cpu: number) {
+ this.engine = engine;
+ this.trackKey = trackKey;
+ this.cpu = cpu;
+ }
+
+ async onCreate() {
+ await this.engine.query(`
+ create virtual table cpu_slice_${this.trackUuid}
+ using __intrinsic_slice_mipmap((
+ select
+ id,
+ ts,
+ iif(dur = -1, lead(ts, 1, trace_end()) over (order by ts) - ts, dur),
+ 0 as depth
+ from sched
+ where cpu = ${this.cpu} and utid != 0
+ ));
+ `);
+ const it = await this.engine.query(`
+ select coalesce(max(id), -1) as lastRowId
+ from sched
+ where cpu = ${this.cpu} and utid != 0
+ `);
+ this.lastRowId = it.firstRow({lastRowId: NUM}).lastRowId;
+ }
+
+ async onUpdate() {
+ await this.fetcher.requestDataForCurrentTime();
+ }
+
+ async onBoundsChange(
+ start: time,
+ end: time,
+ resolution: duration,
+ ): Promise<Data> {
+ assertTrue(BIMath.popcount(resolution) === 1, `${resolution} not pow of 2`);
+
+ const queryRes = await this.engine.query(`
+ select
+ (z.ts / ${resolution}) * ${resolution} as tsQ,
+ (((z.ts + z.dur) / ${resolution}) + 1) * ${resolution} as tsEndQ,
+ s.utid,
+ s.id,
+ s.dur = -1 as isIncomplete,
+ ifnull(s.priority < 100, 0) as isRealtime
+ from cpu_slice_${this.trackUuid}(${start}, ${end}, ${resolution}) z
+ cross join sched s using (id)
+ `);
+
+ const numRows = queryRes.numRows();
+ const slices: Data = {
+ start,
+ end,
+ resolution,
+ length: numRows,
+ lastRowId: this.lastRowId,
+ ids: new Float64Array(numRows),
+ startQs: new BigInt64Array(numRows),
+ endQs: new BigInt64Array(numRows),
+ utids: new Uint32Array(numRows),
+ flags: new Uint8Array(numRows),
+ };
+
+ const it = queryRes.iter({
+ tsQ: LONG,
+ tsEndQ: LONG,
+ utid: NUM,
+ id: NUM,
+ isIncomplete: NUM,
+ isRealtime: NUM,
+ });
+ for (let row = 0; it.valid(); it.next(), row++) {
+ slices.startQs[row] = it.tsQ;
+ slices.endQs[row] = it.tsEndQ;
+ slices.utids[row] = it.utid;
+ slices.ids[row] = it.id;
+
+ slices.flags[row] = 0;
+ if (it.isIncomplete) {
+ slices.flags[row] |= CPU_SLICE_FLAGS_INCOMPLETE;
+ }
+ if (it.isRealtime) {
+ slices.flags[row] |= CPU_SLICE_FLAGS_REALTIME;
+ }
+ }
+ return slices;
+ }
+
+ async onDestroy() {
+ await this.engine.tryQuery(
+ `drop table if exists cpu_slice_${this.trackUuid}`,
+ );
+ this.fetcher.dispose();
+ }
+
+ getHeight(): number {
+ return TRACK_HEIGHT;
+ }
+
+ render(ctx: CanvasRenderingContext2D, size: PanelSize): void {
+ // TODO: fonts and colors should come from the CSS and not hardcoded here.
+ const {visibleTimeScale} = globals.timeline;
+ const data = this.fetcher.data;
+
+ if (data === undefined) return; // Can't possibly draw anything.
+
+ // If the cached trace slices don't fully cover the visible time range,
+ // show a gray rectangle with a "Loading..." label.
+ checkerboardExcept(
+ ctx,
+ this.getHeight(),
+ 0,
+ size.width,
+ visibleTimeScale.timeToPx(data.start),
+ visibleTimeScale.timeToPx(data.end),
+ );
+
+ this.renderSlices(ctx, data);
+ }
+
+ renderSlices(ctx: CanvasRenderingContext2D, data: Data): void {
+ const {visibleTimeScale, visibleTimeSpan, visibleWindowTime} =
+ globals.timeline;
+ assertTrue(data.startQs.length === data.endQs.length);
+ assertTrue(data.startQs.length === data.utids.length);
+
+ const visWindowEndPx = visibleTimeScale.hpTimeToPx(visibleWindowTime.end);
+
+ ctx.textAlign = 'center';
+ ctx.font = '12px Roboto Condensed';
+ const charWidth = ctx.measureText('dbpqaouk').width / 8;
+
+ const startTime = visibleTimeSpan.start;
+ const endTime = visibleTimeSpan.end;
+
+ const rawStartIdx = data.endQs.findIndex((end) => end >= startTime);
+ const startIdx = rawStartIdx === -1 ? 0 : rawStartIdx;
+
+ const [, rawEndIdx] = searchSegment(data.startQs, endTime);
+ const endIdx = rawEndIdx === -1 ? data.startQs.length : rawEndIdx;
+
+ for (let i = startIdx; i < endIdx; i++) {
+ const tStart = Time.fromRaw(data.startQs[i]);
+ let tEnd = Time.fromRaw(data.endQs[i]);
+ const utid = data.utids[i];
+
+ // If the last slice is incomplete, it should end with the end of the
+ // window, else it might spill over the window and the end would not be
+ // visible as a zigzag line.
+ if (
+ data.ids[i] === data.lastRowId &&
+ data.flags[i] & CPU_SLICE_FLAGS_INCOMPLETE
+ ) {
+ tEnd = endTime;
+ }
+ const rectStart = visibleTimeScale.timeToPx(tStart);
+ const rectEnd = visibleTimeScale.timeToPx(tEnd);
+ const rectWidth = Math.max(1, rectEnd - rectStart);
+
+ const threadInfo = globals.threads.get(utid);
+ // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
+ const pid = threadInfo && threadInfo.pid ? threadInfo.pid : -1;
+
+ const isHovering = globals.state.hoveredUtid !== -1;
+ const isThreadHovered = globals.state.hoveredUtid === utid;
+ const isProcessHovered = globals.state.hoveredPid === pid;
+ const colorScheme = colorForThread(threadInfo);
+ let color: Color;
+ let textColor: Color;
+ if (isHovering && !isThreadHovered) {
+ if (!isProcessHovered) {
+ color = colorScheme.disabled;
+ textColor = colorScheme.textDisabled;
+ } else {
+ color = colorScheme.variant;
+ textColor = colorScheme.textVariant;
+ }
+ } else {
+ color = colorScheme.base;
+ textColor = colorScheme.textBase;
+ }
+ ctx.fillStyle = color.cssString;
+
+ if (data.flags[i] & CPU_SLICE_FLAGS_INCOMPLETE) {
+ drawIncompleteSlice(ctx, rectStart, MARGIN_TOP, rectWidth, RECT_HEIGHT);
+ } else {
+ ctx.fillRect(rectStart, MARGIN_TOP, rectWidth, RECT_HEIGHT);
+ }
+
+ // Don't render text when we have less than 5px to play with.
+ if (rectWidth < 5) continue;
+
+ // Stylize real-time threads. We don't do it when zoomed out as the
+ // fillRect is expensive.
+ if (data.flags[i] & CPU_SLICE_FLAGS_REALTIME) {
+ ctx.fillStyle = getHatchedPattern(ctx);
+ ctx.fillRect(rectStart, MARGIN_TOP, rectWidth, RECT_HEIGHT);
+ }
+
+ // TODO: consider de-duplicating this code with the copied one from
+ // chrome_slices/frontend.ts.
+ let title = `[utid:${utid}]`;
+ let subTitle = '';
+ if (threadInfo) {
+ /* eslint-disable @typescript-eslint/strict-boolean-expressions */
+ if (threadInfo.pid) {
+ /* eslint-enable */
+ let procName = threadInfo.procName || '';
+ if (procName.startsWith('/')) {
+ // Remove folder paths from name
+ procName = procName.substring(procName.lastIndexOf('/') + 1);
+ }
+ title = `${procName} [${threadInfo.pid}]`;
+ subTitle = `${threadInfo.threadName} [${threadInfo.tid}]`;
+ } else {
+ title = `${threadInfo.threadName} [${threadInfo.tid}]`;
+ }
+ }
+
+ if (data.flags[i] & CPU_SLICE_FLAGS_REALTIME) {
+ subTitle = subTitle + ' (RT)';
+ }
+
+ const right = Math.min(visWindowEndPx, rectEnd);
+ const left = Math.max(rectStart, 0);
+ const visibleWidth = Math.max(right - left, 1);
+ title = cropText(title, charWidth, visibleWidth);
+ subTitle = cropText(subTitle, charWidth, visibleWidth);
+ const rectXCenter = left + visibleWidth / 2;
+ ctx.fillStyle = textColor.cssString;
+ ctx.font = '12px Roboto Condensed';
+ ctx.fillText(title, rectXCenter, MARGIN_TOP + RECT_HEIGHT / 2 - 1);
+ ctx.fillStyle = textColor.setAlpha(0.6).cssString;
+ ctx.font = '10px Roboto Condensed';
+ ctx.fillText(subTitle, rectXCenter, MARGIN_TOP + RECT_HEIGHT / 2 + 9);
+ }
+
+ const selection = getLegacySelection(globals.state);
+ const details = globals.sliceDetails;
+ if (selection !== null && selection.kind === 'SLICE') {
+ const [startIndex, endIndex] = searchEq(data.ids, selection.id);
+ if (startIndex !== endIndex) {
+ const tStart = Time.fromRaw(data.startQs[startIndex]);
+ const tEnd = Time.fromRaw(data.endQs[startIndex]);
+ const utid = data.utids[startIndex];
+ const color = colorForThread(globals.threads.get(utid));
+ const rectStart = visibleTimeScale.timeToPx(tStart);
+ const rectEnd = visibleTimeScale.timeToPx(tEnd);
+ const rectWidth = Math.max(1, rectEnd - rectStart);
+
+ // Draw a rectangle around the slice that is currently selected.
+ ctx.strokeStyle = color.base.setHSL({l: 30}).cssString;
+ ctx.beginPath();
+ ctx.lineWidth = 3;
+ ctx.strokeRect(rectStart, MARGIN_TOP - 1.5, rectWidth, RECT_HEIGHT + 3);
+ ctx.closePath();
+ // Draw arrow from wakeup time of current slice.
+ if (details.wakeupTs) {
+ const wakeupPos = visibleTimeScale.timeToPx(details.wakeupTs);
+ const latencyWidth = rectStart - wakeupPos;
+ drawDoubleHeadedArrow(
+ ctx,
+ wakeupPos,
+ MARGIN_TOP + RECT_HEIGHT,
+ latencyWidth,
+ latencyWidth >= 20,
+ );
+ // Latency time with a white semi-transparent background.
+ const latency = tStart - details.wakeupTs;
+ const displayText = Duration.humanise(latency);
+ const measured = ctx.measureText(displayText);
+ if (latencyWidth >= measured.width + 2) {
+ ctx.fillStyle = 'rgba(255,255,255,0.7)';
+ ctx.fillRect(
+ wakeupPos + latencyWidth / 2 - measured.width / 2 - 1,
+ MARGIN_TOP + RECT_HEIGHT - 12,
+ measured.width + 2,
+ 11,
+ );
+ ctx.textBaseline = 'bottom';
+ ctx.fillStyle = 'black';
+ ctx.fillText(
+ displayText,
+ wakeupPos + latencyWidth / 2,
+ MARGIN_TOP + RECT_HEIGHT - 1,
+ );
+ }
+ }
+ }
+
+ // Draw diamond if the track being drawn is the cpu of the waker.
+ if (this.cpu === details.wakerCpu && details.wakeupTs) {
+ const wakeupPos = Math.floor(
+ visibleTimeScale.timeToPx(details.wakeupTs),
+ );
+ ctx.beginPath();
+ ctx.moveTo(wakeupPos, MARGIN_TOP + RECT_HEIGHT / 2 + 8);
+ ctx.fillStyle = 'black';
+ ctx.lineTo(wakeupPos + 6, MARGIN_TOP + RECT_HEIGHT / 2);
+ ctx.lineTo(wakeupPos, MARGIN_TOP + RECT_HEIGHT / 2 - 8);
+ ctx.lineTo(wakeupPos - 6, MARGIN_TOP + RECT_HEIGHT / 2);
+ ctx.fill();
+ ctx.closePath();
+ }
+ }
+
+ const hoveredThread = globals.threads.get(this.utidHoveredInThisTrack);
+ const maxHeight = this.getHeight();
+ if (hoveredThread !== undefined && this.mousePos !== undefined) {
+ const tidText = `T: ${hoveredThread.threadName}
+ [${hoveredThread.tid}]`;
+ // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
+ if (hoveredThread.pid) {
+ const pidText = `P: ${hoveredThread.procName}
+ [${hoveredThread.pid}]`;
+ drawTrackHoverTooltip(ctx, this.mousePos, maxHeight, pidText, tidText);
+ } else {
+ drawTrackHoverTooltip(ctx, this.mousePos, maxHeight, tidText);
+ }
+ }
+ }
+
+ onMouseMove(pos: {x: number; y: number}) {
+ const data = this.fetcher.data;
+ this.mousePos = pos;
+ if (data === undefined) return;
+ const {visibleTimeScale} = globals.timeline;
+ if (pos.y < MARGIN_TOP || pos.y > MARGIN_TOP + RECT_HEIGHT) {
+ this.utidHoveredInThisTrack = -1;
+ globals.dispatch(Actions.setHoveredUtidAndPid({utid: -1, pid: -1}));
+ return;
+ }
+ const t = visibleTimeScale.pxToHpTime(pos.x);
+ let hoveredUtid = -1;
+
+ for (let i = 0; i < data.startQs.length; i++) {
+ const tStart = Time.fromRaw(data.startQs[i]);
+ const tEnd = Time.fromRaw(data.endQs[i]);
+ const utid = data.utids[i];
+ if (t.gte(tStart) && t.lt(tEnd)) {
+ hoveredUtid = utid;
+ break;
+ }
+ }
+ this.utidHoveredInThisTrack = hoveredUtid;
+ const threadInfo = globals.threads.get(hoveredUtid);
+ // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
+ const hoveredPid = threadInfo ? (threadInfo.pid ? threadInfo.pid : -1) : -1;
+ globals.dispatch(
+ Actions.setHoveredUtidAndPid({utid: hoveredUtid, pid: hoveredPid}),
+ );
+ }
+
+ onMouseOut() {
+ this.utidHoveredInThisTrack = -1;
+ globals.dispatch(Actions.setHoveredUtidAndPid({utid: -1, pid: -1}));
+ this.mousePos = undefined;
+ }
+
+ onMouseClick({x}: {x: number}) {
+ const data = this.fetcher.data;
+ if (data === undefined) return false;
+ const {visibleTimeScale} = globals.timeline;
+ const time = visibleTimeScale.pxToHpTime(x);
+ const index = search(data.startQs, time.toTime());
+ const id = index === -1 ? undefined : data.ids[index];
+ // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
+ if (!id || this.utidHoveredInThisTrack === -1) return false;
+
+ globals.setLegacySelection(
+ {
+ kind: 'SLICE',
+ id,
+ trackKey: this.trackKey,
+ },
+ {
+ clearSearch: true,
+ pendingScrollId: undefined,
+ switchToCurrentSelectionTab: true,
+ },
+ );
+
+ return true;
+ }
+}
+
+// Creates a diagonal hatched pattern to be used for distinguishing slices with
+// real-time priorities. The pattern is created once as an offscreen canvas and
+// is kept cached inside the Context2D of the main canvas, without making
+// assumptions on the lifetime of the main canvas.
+function getHatchedPattern(mainCtx: CanvasRenderingContext2D): CanvasPattern {
+ const mctx = mainCtx as CanvasRenderingContext2D & {
+ sliceHatchedPattern?: CanvasPattern;
+ };
+ if (mctx.sliceHatchedPattern !== undefined) return mctx.sliceHatchedPattern;
+ const canvas = document.createElement('canvas');
+ const SIZE = 8;
+ canvas.width = canvas.height = SIZE;
+ const ctx = assertExists(canvas.getContext('2d'));
+ ctx.strokeStyle = 'rgba(255,255,255,0.3)';
+ ctx.beginPath();
+ ctx.lineWidth = 1;
+ ctx.moveTo(0, SIZE);
+ ctx.lineTo(SIZE, 0);
+ ctx.stroke();
+ mctx.sliceHatchedPattern = assertExists(mctx.createPattern(canvas, 'repeat'));
+ return mctx.sliceHatchedPattern;
+}
diff --git a/ui/src/core_plugins/cpu_slices/index.ts b/ui/src/core_plugins/cpu_slices/index.ts
index 1fc1e67..3f456a3 100644
--- a/ui/src/core_plugins/cpu_slices/index.ts
+++ b/ui/src/core_plugins/cpu_slices/index.ts
@@ -12,458 +12,21 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {BigintMath as BIMath} from '../../base/bigint_math';
-import {search, searchEq, searchSegment} from '../../base/binary_search';
-import {assertExists, assertTrue} from '../../base/logging';
-import {Duration, duration, Time, time} from '../../base/time';
-import {Actions} from '../../common/actions';
-import {getLegacySelection} from '../../common/state';
-import {
- cropText,
- drawDoubleHeadedArrow,
- drawIncompleteSlice,
- drawTrackHoverTooltip,
-} from '../../common/canvas_utils';
-import {Color} from '../../core/color';
-import {colorForThread} from '../../core/colorizer';
-import {TrackData} from '../../common/track_data';
-import {TimelineFetcher} from '../../common/track_helper';
-import {checkerboardExcept} from '../../frontend/checkerboard';
-import {globals} from '../../frontend/globals';
-import {PanelSize} from '../../frontend/panel';
import {SliceDetailsPanel} from '../../frontend/slice_details_panel';
import {
Engine,
Plugin,
PluginContextTrace,
PluginDescriptor,
- Track,
} from '../../public';
-import {LONG, NUM, STR_NULL} from '../../trace_processor/query_result';
-import {uuidv4Sql} from '../../base/uuid';
+import {NUM, STR_NULL} from '../../trace_processor/query_result';
+import {CpuSliceTrack} from './cpu_slice_track';
export const CPU_SLICE_TRACK_KIND = 'CpuSliceTrack';
-export interface Data extends TrackData {
- // Slices are stored in a columnar fashion. All fields have the same length.
- ids: Float64Array;
- startQs: BigInt64Array;
- endQs: BigInt64Array;
- utids: Uint32Array;
- flags: Uint8Array;
- lastRowId: number;
-}
-
-const MARGIN_TOP = 3;
-const RECT_HEIGHT = 24;
-const TRACK_HEIGHT = MARGIN_TOP * 2 + RECT_HEIGHT;
-
-const CPU_SLICE_FLAGS_INCOMPLETE = 1;
-const CPU_SLICE_FLAGS_REALTIME = 2;
-
-class CpuSliceTrack implements Track {
- private mousePos?: {x: number; y: number};
- private utidHoveredInThisTrack = -1;
- private fetcher = new TimelineFetcher<Data>(this.onBoundsChange.bind(this));
-
- private lastRowId = -1;
- private engine: Engine;
- private cpu: number;
- private trackKey: string;
- private trackUuid = uuidv4Sql();
-
- constructor(engine: Engine, trackKey: string, cpu: number) {
- this.engine = engine;
- this.trackKey = trackKey;
- this.cpu = cpu;
- }
-
- async onCreate() {
- await this.engine.query(`
- create virtual table cpu_slice_${this.trackUuid}
- using __intrinsic_slice_mipmap((
- select
- id,
- ts,
- iif(dur = -1, lead(ts, 1, trace_end()) over (order by ts) - ts, dur),
- 0 as depth
- from sched
- where cpu = ${this.cpu} and utid != 0
- ));
- `);
- const it = await this.engine.query(`
- select coalesce(max(id), -1) as lastRowId
- from sched
- where cpu = ${this.cpu} and utid != 0
- `);
- this.lastRowId = it.firstRow({lastRowId: NUM}).lastRowId;
- }
-
- async onUpdate() {
- await this.fetcher.requestDataForCurrentTime();
- }
-
- async onBoundsChange(
- start: time,
- end: time,
- resolution: duration,
- ): Promise<Data> {
- assertTrue(BIMath.popcount(resolution) === 1, `${resolution} not pow of 2`);
-
- const queryRes = await this.engine.query(`
- select
- (z.ts / ${resolution}) * ${resolution} as tsQ,
- (((z.ts + z.dur) / ${resolution}) + 1) * ${resolution} as tsEndQ,
- s.utid,
- s.id,
- s.dur = -1 as isIncomplete,
- ifnull(s.priority < 100, 0) as isRealtime
- from cpu_slice_${this.trackUuid}(${start}, ${end}, ${resolution}) z
- cross join sched s using (id)
- `);
-
- const numRows = queryRes.numRows();
- const slices: Data = {
- start,
- end,
- resolution,
- length: numRows,
- lastRowId: this.lastRowId,
- ids: new Float64Array(numRows),
- startQs: new BigInt64Array(numRows),
- endQs: new BigInt64Array(numRows),
- utids: new Uint32Array(numRows),
- flags: new Uint8Array(numRows),
- };
-
- const it = queryRes.iter({
- tsQ: LONG,
- tsEndQ: LONG,
- utid: NUM,
- id: NUM,
- isIncomplete: NUM,
- isRealtime: NUM,
- });
- for (let row = 0; it.valid(); it.next(), row++) {
- slices.startQs[row] = it.tsQ;
- slices.endQs[row] = it.tsEndQ;
- slices.utids[row] = it.utid;
- slices.ids[row] = it.id;
-
- slices.flags[row] = 0;
- if (it.isIncomplete) {
- slices.flags[row] |= CPU_SLICE_FLAGS_INCOMPLETE;
- }
- if (it.isRealtime) {
- slices.flags[row] |= CPU_SLICE_FLAGS_REALTIME;
- }
- }
- return slices;
- }
-
- async onDestroy() {
- if (this.engine.isAlive) {
- await this.engine.query(
- `drop table if exists cpu_slice_${this.trackUuid}`,
- );
- }
- this.fetcher.dispose();
- }
-
- getHeight(): number {
- return TRACK_HEIGHT;
- }
-
- render(ctx: CanvasRenderingContext2D, size: PanelSize): void {
- // TODO: fonts and colors should come from the CSS and not hardcoded here.
- const {visibleTimeScale} = globals.timeline;
- const data = this.fetcher.data;
-
- if (data === undefined) return; // Can't possibly draw anything.
-
- // If the cached trace slices don't fully cover the visible time range,
- // show a gray rectangle with a "Loading..." label.
- checkerboardExcept(
- ctx,
- this.getHeight(),
- 0,
- size.width,
- visibleTimeScale.timeToPx(data.start),
- visibleTimeScale.timeToPx(data.end),
- );
-
- this.renderSlices(ctx, data);
- }
-
- renderSlices(ctx: CanvasRenderingContext2D, data: Data): void {
- const {visibleTimeScale, visibleTimeSpan, visibleWindowTime} =
- globals.timeline;
- assertTrue(data.startQs.length === data.endQs.length);
- assertTrue(data.startQs.length === data.utids.length);
-
- const visWindowEndPx = visibleTimeScale.hpTimeToPx(visibleWindowTime.end);
-
- ctx.textAlign = 'center';
- ctx.font = '12px Roboto Condensed';
- const charWidth = ctx.measureText('dbpqaouk').width / 8;
-
- const startTime = visibleTimeSpan.start;
- const endTime = visibleTimeSpan.end;
-
- const rawStartIdx = data.endQs.findIndex((end) => end >= startTime);
- const startIdx = rawStartIdx === -1 ? 0 : rawStartIdx;
-
- const [, rawEndIdx] = searchSegment(data.startQs, endTime);
- const endIdx = rawEndIdx === -1 ? data.startQs.length : rawEndIdx;
-
- for (let i = startIdx; i < endIdx; i++) {
- const tStart = Time.fromRaw(data.startQs[i]);
- let tEnd = Time.fromRaw(data.endQs[i]);
- const utid = data.utids[i];
-
- // If the last slice is incomplete, it should end with the end of the
- // window, else it might spill over the window and the end would not be
- // visible as a zigzag line.
- if (
- data.ids[i] === data.lastRowId &&
- data.flags[i] & CPU_SLICE_FLAGS_INCOMPLETE
- ) {
- tEnd = endTime;
- }
- const rectStart = visibleTimeScale.timeToPx(tStart);
- const rectEnd = visibleTimeScale.timeToPx(tEnd);
- const rectWidth = Math.max(1, rectEnd - rectStart);
-
- const threadInfo = globals.threads.get(utid);
- // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
- const pid = threadInfo && threadInfo.pid ? threadInfo.pid : -1;
-
- const isHovering = globals.state.hoveredUtid !== -1;
- const isThreadHovered = globals.state.hoveredUtid === utid;
- const isProcessHovered = globals.state.hoveredPid === pid;
- const colorScheme = colorForThread(threadInfo);
- let color: Color;
- let textColor: Color;
- if (isHovering && !isThreadHovered) {
- if (!isProcessHovered) {
- color = colorScheme.disabled;
- textColor = colorScheme.textDisabled;
- } else {
- color = colorScheme.variant;
- textColor = colorScheme.textVariant;
- }
- } else {
- color = colorScheme.base;
- textColor = colorScheme.textBase;
- }
- ctx.fillStyle = color.cssString;
-
- if (data.flags[i] & CPU_SLICE_FLAGS_INCOMPLETE) {
- drawIncompleteSlice(ctx, rectStart, MARGIN_TOP, rectWidth, RECT_HEIGHT);
- } else {
- ctx.fillRect(rectStart, MARGIN_TOP, rectWidth, RECT_HEIGHT);
- }
-
- // Don't render text when we have less than 5px to play with.
- if (rectWidth < 5) continue;
-
- // Stylize real-time threads. We don't do it when zoomed out as the
- // fillRect is expensive.
- if (data.flags[i] & CPU_SLICE_FLAGS_REALTIME) {
- ctx.fillStyle = getHatchedPattern(ctx);
- ctx.fillRect(rectStart, MARGIN_TOP, rectWidth, RECT_HEIGHT);
- }
-
- // TODO: consider de-duplicating this code with the copied one from
- // chrome_slices/frontend.ts.
- let title = `[utid:${utid}]`;
- let subTitle = '';
- if (threadInfo) {
- /* eslint-disable @typescript-eslint/strict-boolean-expressions */
- if (threadInfo.pid) {
- /* eslint-enable */
- let procName = threadInfo.procName || '';
- if (procName.startsWith('/')) {
- // Remove folder paths from name
- procName = procName.substring(procName.lastIndexOf('/') + 1);
- }
- title = `${procName} [${threadInfo.pid}]`;
- subTitle = `${threadInfo.threadName} [${threadInfo.tid}]`;
- } else {
- title = `${threadInfo.threadName} [${threadInfo.tid}]`;
- }
- }
-
- if (data.flags[i] & CPU_SLICE_FLAGS_REALTIME) {
- subTitle = subTitle + ' (RT)';
- }
-
- const right = Math.min(visWindowEndPx, rectEnd);
- const left = Math.max(rectStart, 0);
- const visibleWidth = Math.max(right - left, 1);
- title = cropText(title, charWidth, visibleWidth);
- subTitle = cropText(subTitle, charWidth, visibleWidth);
- const rectXCenter = left + visibleWidth / 2;
- ctx.fillStyle = textColor.cssString;
- ctx.font = '12px Roboto Condensed';
- ctx.fillText(title, rectXCenter, MARGIN_TOP + RECT_HEIGHT / 2 - 1);
- ctx.fillStyle = textColor.setAlpha(0.6).cssString;
- ctx.font = '10px Roboto Condensed';
- ctx.fillText(subTitle, rectXCenter, MARGIN_TOP + RECT_HEIGHT / 2 + 9);
- }
-
- const selection = getLegacySelection(globals.state);
- const details = globals.sliceDetails;
- if (selection !== null && selection.kind === 'SLICE') {
- const [startIndex, endIndex] = searchEq(data.ids, selection.id);
- if (startIndex !== endIndex) {
- const tStart = Time.fromRaw(data.startQs[startIndex]);
- const tEnd = Time.fromRaw(data.endQs[startIndex]);
- const utid = data.utids[startIndex];
- const color = colorForThread(globals.threads.get(utid));
- const rectStart = visibleTimeScale.timeToPx(tStart);
- const rectEnd = visibleTimeScale.timeToPx(tEnd);
- const rectWidth = Math.max(1, rectEnd - rectStart);
-
- // Draw a rectangle around the slice that is currently selected.
- ctx.strokeStyle = color.base.setHSL({l: 30}).cssString;
- ctx.beginPath();
- ctx.lineWidth = 3;
- ctx.strokeRect(rectStart, MARGIN_TOP - 1.5, rectWidth, RECT_HEIGHT + 3);
- ctx.closePath();
- // Draw arrow from wakeup time of current slice.
- if (details.wakeupTs) {
- const wakeupPos = visibleTimeScale.timeToPx(details.wakeupTs);
- const latencyWidth = rectStart - wakeupPos;
- drawDoubleHeadedArrow(
- ctx,
- wakeupPos,
- MARGIN_TOP + RECT_HEIGHT,
- latencyWidth,
- latencyWidth >= 20,
- );
- // Latency time with a white semi-transparent background.
- const latency = tStart - details.wakeupTs;
- const displayText = Duration.humanise(latency);
- const measured = ctx.measureText(displayText);
- if (latencyWidth >= measured.width + 2) {
- ctx.fillStyle = 'rgba(255,255,255,0.7)';
- ctx.fillRect(
- wakeupPos + latencyWidth / 2 - measured.width / 2 - 1,
- MARGIN_TOP + RECT_HEIGHT - 12,
- measured.width + 2,
- 11,
- );
- ctx.textBaseline = 'bottom';
- ctx.fillStyle = 'black';
- ctx.fillText(
- displayText,
- wakeupPos + latencyWidth / 2,
- MARGIN_TOP + RECT_HEIGHT - 1,
- );
- }
- }
- }
-
- // Draw diamond if the track being drawn is the cpu of the waker.
- if (this.cpu === details.wakerCpu && details.wakeupTs) {
- const wakeupPos = Math.floor(
- visibleTimeScale.timeToPx(details.wakeupTs),
- );
- ctx.beginPath();
- ctx.moveTo(wakeupPos, MARGIN_TOP + RECT_HEIGHT / 2 + 8);
- ctx.fillStyle = 'black';
- ctx.lineTo(wakeupPos + 6, MARGIN_TOP + RECT_HEIGHT / 2);
- ctx.lineTo(wakeupPos, MARGIN_TOP + RECT_HEIGHT / 2 - 8);
- ctx.lineTo(wakeupPos - 6, MARGIN_TOP + RECT_HEIGHT / 2);
- ctx.fill();
- ctx.closePath();
- }
- }
-
- const hoveredThread = globals.threads.get(this.utidHoveredInThisTrack);
- const maxHeight = this.getHeight();
- if (hoveredThread !== undefined && this.mousePos !== undefined) {
- const tidText = `T: ${hoveredThread.threadName}
- [${hoveredThread.tid}]`;
- // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
- if (hoveredThread.pid) {
- const pidText = `P: ${hoveredThread.procName}
- [${hoveredThread.pid}]`;
- drawTrackHoverTooltip(ctx, this.mousePos, maxHeight, pidText, tidText);
- } else {
- drawTrackHoverTooltip(ctx, this.mousePos, maxHeight, tidText);
- }
- }
- }
-
- onMouseMove(pos: {x: number; y: number}) {
- const data = this.fetcher.data;
- this.mousePos = pos;
- if (data === undefined) return;
- const {visibleTimeScale} = globals.timeline;
- if (pos.y < MARGIN_TOP || pos.y > MARGIN_TOP + RECT_HEIGHT) {
- this.utidHoveredInThisTrack = -1;
- globals.dispatch(Actions.setHoveredUtidAndPid({utid: -1, pid: -1}));
- return;
- }
- const t = visibleTimeScale.pxToHpTime(pos.x);
- let hoveredUtid = -1;
-
- for (let i = 0; i < data.startQs.length; i++) {
- const tStart = Time.fromRaw(data.startQs[i]);
- const tEnd = Time.fromRaw(data.endQs[i]);
- const utid = data.utids[i];
- if (t.gte(tStart) && t.lt(tEnd)) {
- hoveredUtid = utid;
- break;
- }
- }
- this.utidHoveredInThisTrack = hoveredUtid;
- const threadInfo = globals.threads.get(hoveredUtid);
- // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
- const hoveredPid = threadInfo ? (threadInfo.pid ? threadInfo.pid : -1) : -1;
- globals.dispatch(
- Actions.setHoveredUtidAndPid({utid: hoveredUtid, pid: hoveredPid}),
- );
- }
-
- onMouseOut() {
- this.utidHoveredInThisTrack = -1;
- globals.dispatch(Actions.setHoveredUtidAndPid({utid: -1, pid: -1}));
- this.mousePos = undefined;
- }
-
- onMouseClick({x}: {x: number}) {
- const data = this.fetcher.data;
- if (data === undefined) return false;
- const {visibleTimeScale} = globals.timeline;
- const time = visibleTimeScale.pxToHpTime(x);
- const index = search(data.startQs, time.toTime());
- const id = index === -1 ? undefined : data.ids[index];
- // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
- if (!id || this.utidHoveredInThisTrack === -1) return false;
-
- globals.setLegacySelection(
- {
- kind: 'SLICE',
- id,
- trackKey: this.trackKey,
- },
- {
- clearSearch: true,
- pendingScrollId: undefined,
- switchToCurrentSelectionTab: true,
- },
- );
-
- return true;
- }
-}
-
class CpuSlices implements Plugin {
async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
- const cpus = await ctx.engine.getCpus();
+ const cpus = ctx.trace.cpus;
const cpuToSize = await this.guessCpuSizes(ctx.engine);
for (const cpu of cpus) {
@@ -516,29 +79,6 @@
}
}
-// Creates a diagonal hatched pattern to be used for distinguishing slices with
-// real-time priorities. The pattern is created once as an offscreen canvas and
-// is kept cached inside the Context2D of the main canvas, without making
-// assumptions on the lifetime of the main canvas.
-function getHatchedPattern(mainCtx: CanvasRenderingContext2D): CanvasPattern {
- const mctx = mainCtx as CanvasRenderingContext2D & {
- sliceHatchedPattern?: CanvasPattern;
- };
- if (mctx.sliceHatchedPattern !== undefined) return mctx.sliceHatchedPattern;
- const canvas = document.createElement('canvas');
- const SIZE = 8;
- canvas.width = canvas.height = SIZE;
- const ctx = assertExists(canvas.getContext('2d'));
- ctx.strokeStyle = 'rgba(255,255,255,0.3)';
- ctx.beginPath();
- ctx.lineWidth = 1;
- ctx.moveTo(0, SIZE);
- ctx.lineTo(SIZE, 0);
- ctx.stroke();
- mctx.sliceHatchedPattern = assertExists(mctx.createPattern(canvas, 'repeat'));
- return mctx.sliceHatchedPattern;
-}
-
export const plugin: PluginDescriptor = {
pluginId: 'perfetto.CpuSlices',
plugin: CpuSlices,
diff --git a/ui/src/core_plugins/debug/counter_track.ts b/ui/src/core_plugins/debug/counter_track.ts
index 56274d3..5c0022d 100644
--- a/ui/src/core_plugins/debug/counter_track.ts
+++ b/ui/src/core_plugins/debug/counter_track.ts
@@ -67,8 +67,6 @@
}
private async dropTrackTable(): Promise<void> {
- if (this.engine.isAlive) {
- this.engine.query(`drop table if exists ${this.sqlTableName}`);
- }
+ this.engine.tryQuery(`drop table if exists ${this.sqlTableName}`);
}
}
diff --git a/ui/src/core_plugins/debug/slice_track.ts b/ui/src/core_plugins/debug/slice_track.ts
index 49e5142..a0734d9 100644
--- a/ui/src/core_plugins/debug/slice_track.ts
+++ b/ui/src/core_plugins/debug/slice_track.ts
@@ -19,7 +19,7 @@
CustomSqlDetailsPanelConfig,
CustomSqlTableDefConfig,
CustomSqlTableSliceTrack,
-} from '../custom_sql_table_slices';
+} from '../../frontend/tracks/custom_sql_table_slice_track';
import {DebugSliceDetailsTab} from './details_tab';
import {
@@ -111,8 +111,6 @@
}
private async destroyTrackTable() {
- if (this.engine.isAlive) {
- await this.engine.query(`DROP TABLE IF EXISTS ${this.sqlTableName}`);
- }
+ await this.engine.tryQuery(`DROP TABLE IF EXISTS ${this.sqlTableName}`);
}
}
diff --git a/ui/src/core_plugins/frames/actual_frames_track_v2.ts b/ui/src/core_plugins/frames/actual_frames_track.ts
similarity index 100%
rename from ui/src/core_plugins/frames/actual_frames_track_v2.ts
rename to ui/src/core_plugins/frames/actual_frames_track.ts
diff --git a/ui/src/core_plugins/frames/expected_frames_track_v2.ts b/ui/src/core_plugins/frames/expected_frames_track.ts
similarity index 100%
rename from ui/src/core_plugins/frames/expected_frames_track_v2.ts
rename to ui/src/core_plugins/frames/expected_frames_track.ts
diff --git a/ui/src/core_plugins/frames/index.ts b/ui/src/core_plugins/frames/index.ts
index 2cc877f..8e67de9 100644
--- a/ui/src/core_plugins/frames/index.ts
+++ b/ui/src/core_plugins/frames/index.ts
@@ -16,8 +16,8 @@
import {getTrackName} from '../../public/utils';
import {NUM, NUM_NULL, STR, STR_NULL} from '../../trace_processor/query_result';
-import {ActualFramesTrack as ActualFramesTrackV2} from './actual_frames_track_v2';
-import {ExpectedFramesTrack as ExpectedFramesTrackV2} from './expected_frames_track_v2';
+import {ActualFramesTrack} from './actual_frames_track';
+import {ExpectedFramesTrack} from './expected_frames_track';
export const EXPECTED_FRAMES_SLICE_TRACK_KIND = 'ExpectedFramesSliceTrack';
export const ACTUAL_FRAMES_SLICE_TRACK_KIND = 'ActualFramesSliceTrack';
@@ -75,12 +75,7 @@
trackIds,
kind: EXPECTED_FRAMES_SLICE_TRACK_KIND,
trackFactory: ({trackKey}) => {
- return new ExpectedFramesTrackV2(
- engine,
- maxDepth,
- trackKey,
- trackIds,
- );
+ return new ExpectedFramesTrack(engine, maxDepth, trackKey, trackIds);
},
});
}
@@ -138,7 +133,7 @@
trackIds,
kind: ACTUAL_FRAMES_SLICE_TRACK_KIND,
trackFactory: ({trackKey}) => {
- return new ActualFramesTrackV2(engine, maxDepth, trackKey, trackIds);
+ return new ActualFramesTrack(engine, maxDepth, trackKey, trackIds);
},
});
}
diff --git a/ui/src/core_plugins/heap_profile/heap_profile_track.ts b/ui/src/core_plugins/heap_profile/heap_profile_track.ts
new file mode 100644
index 0000000..9ecd947
--- /dev/null
+++ b/ui/src/core_plugins/heap_profile/heap_profile_track.ts
@@ -0,0 +1,108 @@
+// 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 {ProfileType, LegacySelection} from '../../common/state';
+import {profileType} from '../../controller/flamegraph_controller';
+import {
+ BASE_ROW,
+ BaseSliceTrack,
+ BaseSliceTrackTypes,
+ OnSliceClickArgs,
+ OnSliceOverArgs,
+} from '../../frontend/base_slice_track';
+import {globals} from '../../frontend/globals';
+import {NewTrackArgs} from '../../frontend/track';
+import {Slice} from '../../public';
+import {STR} from '../../trace_processor/query_result';
+
+export const HEAP_PROFILE_TRACK_KIND = 'HeapProfileTrack';
+
+const HEAP_PROFILE_ROW = {
+ ...BASE_ROW,
+ type: STR,
+};
+type HeapProfileRow = typeof HEAP_PROFILE_ROW;
+interface HeapProfileSlice extends Slice {
+ type: ProfileType;
+}
+
+interface HeapProfileTrackTypes extends BaseSliceTrackTypes {
+ row: HeapProfileRow;
+ slice: HeapProfileSlice;
+}
+
+export class HeapProfileTrack extends BaseSliceTrack<HeapProfileTrackTypes> {
+ private upid: number;
+
+ constructor(args: NewTrackArgs, upid: number) {
+ super(args);
+ this.upid = upid;
+ }
+
+ getSqlSource(): string {
+ return `select
+ *,
+ 0 AS dur,
+ 0 AS depth
+ from (
+ select distinct
+ id,
+ ts,
+ 'heap_profile:' || (select group_concat(distinct heap_name) from heap_profile_allocation where upid = ${this.upid}) AS type
+ from heap_profile_allocation
+ where upid = ${this.upid}
+ union
+ select distinct
+ id,
+ graph_sample_ts AS ts,
+ 'graph' AS type
+ from heap_graph_object
+ where upid = ${this.upid}
+ )`;
+ }
+
+ getRowSpec(): HeapProfileRow {
+ return HEAP_PROFILE_ROW;
+ }
+
+ rowToSlice(row: HeapProfileRow): HeapProfileSlice {
+ const slice = super.rowToSlice(row);
+ let type = row.type;
+ if (type === 'heap_profile:libc.malloc,com.android.art') {
+ type = 'heap_profile:com.android.art,libc.malloc';
+ }
+ slice.type = profileType(type);
+ return slice;
+ }
+
+ onSliceOver(args: OnSliceOverArgs<HeapProfileSlice>) {
+ args.tooltip = [args.slice.type];
+ }
+
+ onSliceClick(args: OnSliceClickArgs<HeapProfileSlice>) {
+ globals.makeSelection(
+ Actions.selectHeapProfile({
+ id: args.slice.id,
+ upid: this.upid,
+ ts: args.slice.ts,
+ type: args.slice.type,
+ }),
+ );
+ }
+
+ protected isSelectionHandled(selection: LegacySelection): boolean {
+ return selection.kind === 'HEAP_PROFILE';
+ }
+}
diff --git a/ui/src/core_plugins/heap_profile/index.ts b/ui/src/core_plugins/heap_profile/index.ts
index 512af1a..c3576f2 100644
--- a/ui/src/core_plugins/heap_profile/index.ts
+++ b/ui/src/core_plugins/heap_profile/index.ts
@@ -12,107 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {Actions} from '../../common/actions';
-import {ProfileType, LegacySelection} from '../../common/state';
-import {profileType} from '../../controller/flamegraph_controller';
-import {
- BASE_ROW,
- BaseSliceTrack,
- BaseSliceTrackTypes,
- OnSliceClickArgs,
- OnSliceOverArgs,
-} from '../../frontend/base_slice_track';
import {FlamegraphDetailsPanel} from '../../frontend/flamegraph_panel';
-import {globals} from '../../frontend/globals';
-import {NewTrackArgs} from '../../frontend/track';
-import {
- Plugin,
- PluginContextTrace,
- PluginDescriptor,
- Slice,
-} from '../../public';
-import {NUM, STR} from '../../trace_processor/query_result';
+import {Plugin, PluginContextTrace, PluginDescriptor} from '../../public';
+import {NUM} from '../../trace_processor/query_result';
+import {HeapProfileTrack} from './heap_profile_track';
export const HEAP_PROFILE_TRACK_KIND = 'HeapProfileTrack';
-const HEAP_PROFILE_ROW = {
- ...BASE_ROW,
- type: STR,
-};
-type HeapProfileRow = typeof HEAP_PROFILE_ROW;
-interface HeapProfileSlice extends Slice {
- type: ProfileType;
-}
-
-interface HeapProfileTrackTypes extends BaseSliceTrackTypes {
- row: HeapProfileRow;
- slice: HeapProfileSlice;
-}
-
-class HeapProfileTrack extends BaseSliceTrack<HeapProfileTrackTypes> {
- private upid: number;
-
- constructor(args: NewTrackArgs, upid: number) {
- super(args);
- this.upid = upid;
- }
-
- getSqlSource(): string {
- return `select
- *,
- 0 AS dur,
- 0 AS depth
- from (
- select distinct
- id,
- ts,
- 'heap_profile:' || (select group_concat(distinct heap_name) from heap_profile_allocation where upid = ${this.upid}) AS type
- from heap_profile_allocation
- where upid = ${this.upid}
- union
- select distinct
- id,
- graph_sample_ts AS ts,
- 'graph' AS type
- from heap_graph_object
- where upid = ${this.upid}
- )`;
- }
-
- getRowSpec(): HeapProfileRow {
- return HEAP_PROFILE_ROW;
- }
-
- rowToSlice(row: HeapProfileRow): HeapProfileSlice {
- const slice = super.rowToSlice(row);
- let type = row.type;
- if (type === 'heap_profile:libc.malloc,com.android.art') {
- type = 'heap_profile:com.android.art,libc.malloc';
- }
- slice.type = profileType(type);
- return slice;
- }
-
- onSliceOver(args: OnSliceOverArgs<HeapProfileSlice>) {
- args.tooltip = [args.slice.type];
- }
-
- onSliceClick(args: OnSliceClickArgs<HeapProfileSlice>) {
- globals.makeSelection(
- Actions.selectHeapProfile({
- id: args.slice.id,
- upid: this.upid,
- ts: args.slice.ts,
- type: args.slice.type,
- }),
- );
- }
-
- protected isSelectionHandled(selection: LegacySelection): boolean {
- return selection.kind === 'HEAP_PROFILE';
- }
-}
-
class HeapProfilePlugin implements Plugin {
async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
const result = await ctx.engine.query(`
diff --git a/ui/src/core_plugins/perf_samples_profile/index.ts b/ui/src/core_plugins/perf_samples_profile/index.ts
index 319fb25..df9b7c0 100644
--- a/ui/src/core_plugins/perf_samples_profile/index.ts
+++ b/ui/src/core_plugins/perf_samples_profile/index.ts
@@ -12,25 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {searchSegment} from '../../base/binary_search';
-import {duration, Time, time} from '../../base/time';
-import {Actions} from '../../common/actions';
-import {ProfileType, getLegacySelection} from '../../common/state';
import {TrackData} from '../../common/track_data';
-import {TimelineFetcher} from '../../common/track_helper';
-import {FLAMEGRAPH_HOVERED_COLOR} from '../../frontend/flamegraph';
import {FlamegraphDetailsPanel} from '../../frontend/flamegraph_panel';
-import {globals} from '../../frontend/globals';
-import {PanelSize} from '../../frontend/panel';
-import {TimeScale} from '../../frontend/time_scale';
-import {
- Engine,
- Plugin,
- PluginContextTrace,
- PluginDescriptor,
- Track,
-} from '../../public';
-import {LONG, NUM} from '../../trace_processor/query_result';
+import {Plugin, PluginContextTrace, PluginDescriptor} from '../../public';
+import {NUM} from '../../trace_processor/query_result';
+import {PerfSamplesProfileTrack} from './perf_samples_profile_track';
export const PERF_SAMPLES_PROFILE_TRACK_KIND = 'PerfSamplesProfileTrack';
@@ -38,211 +24,6 @@
tsStarts: BigInt64Array;
}
-const PERP_SAMPLE_COLOR = 'hsl(224, 45%, 70%)';
-
-// 0.5 Makes the horizontal lines sharp.
-const MARGIN_TOP = 4.5;
-const RECT_HEIGHT = 30.5;
-
-class PerfSamplesProfileTrack implements Track {
- private centerY = this.getHeight() / 2;
- private markerWidth = (this.getHeight() - MARGIN_TOP) / 2;
- private hoveredTs: time | undefined = undefined;
- private fetcher = new TimelineFetcher(this.onBoundsChange.bind(this));
- private upid: number;
- private engine: Engine;
-
- constructor(engine: Engine, upid: number) {
- this.upid = upid;
- this.engine = engine;
- }
-
- async onUpdate(): Promise<void> {
- await this.fetcher.requestDataForCurrentTime();
- }
-
- async onDestroy(): Promise<void> {
- this.fetcher.dispose();
- }
-
- async onBoundsChange(
- start: time,
- end: time,
- resolution: duration,
- ): Promise<Data> {
- if (this.upid === undefined) {
- return {
- start,
- end,
- resolution,
- length: 0,
- tsStarts: new BigInt64Array(),
- };
- }
- const queryRes = await this.engine.query(`
- select ts, upid from perf_sample
- join thread using (utid)
- where upid = ${this.upid}
- and callsite_id is not null
- order by ts`);
- const numRows = queryRes.numRows();
- const data: Data = {
- start,
- end,
- resolution,
- length: numRows,
- tsStarts: new BigInt64Array(numRows),
- };
-
- const it = queryRes.iter({ts: LONG});
- for (let row = 0; it.valid(); it.next(), row++) {
- data.tsStarts[row] = it.ts;
- }
- return data;
- }
-
- getHeight() {
- return MARGIN_TOP + RECT_HEIGHT - 1;
- }
-
- render(ctx: CanvasRenderingContext2D, _size: PanelSize): void {
- const {visibleTimeScale} = globals.timeline;
- const data = this.fetcher.data;
-
- if (data === undefined) return;
-
- for (let i = 0; i < data.tsStarts.length; i++) {
- const centerX = Time.fromRaw(data.tsStarts[i]);
- const selection = getLegacySelection(globals.state);
- const isHovered = this.hoveredTs === centerX;
- const isSelected =
- selection !== null &&
- selection.kind === 'PERF_SAMPLES' &&
- selection.leftTs <= centerX &&
- selection.rightTs >= centerX;
- const strokeWidth = isSelected ? 3 : 0;
- this.drawMarker(
- ctx,
- visibleTimeScale.timeToPx(centerX),
- this.centerY,
- isHovered,
- strokeWidth,
- );
- }
- }
-
- drawMarker(
- ctx: CanvasRenderingContext2D,
- x: number,
- y: number,
- isHovered: boolean,
- strokeWidth: number,
- ): void {
- ctx.beginPath();
- ctx.moveTo(x, y - this.markerWidth);
- ctx.lineTo(x - this.markerWidth, y);
- ctx.lineTo(x, y + this.markerWidth);
- ctx.lineTo(x + this.markerWidth, y);
- ctx.lineTo(x, y - this.markerWidth);
- ctx.closePath();
- ctx.fillStyle = isHovered ? FLAMEGRAPH_HOVERED_COLOR : PERP_SAMPLE_COLOR;
- ctx.fill();
- if (strokeWidth > 0) {
- ctx.strokeStyle = FLAMEGRAPH_HOVERED_COLOR;
- ctx.lineWidth = strokeWidth;
- ctx.stroke();
- }
- }
-
- onMouseMove({x, y}: {x: number; y: number}) {
- const data = this.fetcher.data;
- if (data === undefined) return;
- const {visibleTimeScale} = globals.timeline;
- const time = visibleTimeScale.pxToHpTime(x);
- const [left, right] = searchSegment(data.tsStarts, time.toTime());
- const index = this.findTimestampIndex(
- left,
- visibleTimeScale,
- data,
- x,
- y,
- right,
- );
- this.hoveredTs =
- index === -1 ? undefined : Time.fromRaw(data.tsStarts[index]);
- }
-
- onMouseOut() {
- this.hoveredTs = undefined;
- }
-
- onMouseClick({x, y}: {x: number; y: number}) {
- const data = this.fetcher.data;
- if (data === undefined) return false;
- const {visibleTimeScale} = globals.timeline;
-
- const time = visibleTimeScale.pxToHpTime(x);
- const [left, right] = searchSegment(data.tsStarts, time.toTime());
-
- const index = this.findTimestampIndex(
- left,
- visibleTimeScale,
- data,
- x,
- y,
- right,
- );
-
- if (index !== -1) {
- const ts = Time.fromRaw(data.tsStarts[index]);
- globals.makeSelection(
- Actions.selectPerfSamples({
- id: index,
- upid: this.upid,
- leftTs: ts,
- rightTs: ts,
- type: ProfileType.PERF_SAMPLE,
- }),
- );
- return true;
- }
- return false;
- }
-
- // If the markers overlap the rightmost one will be selected.
- findTimestampIndex(
- left: number,
- timeScale: TimeScale,
- data: Data,
- x: number,
- y: number,
- right: number,
- ): number {
- let index = -1;
- if (left !== -1) {
- const start = Time.fromRaw(data.tsStarts[left]);
- const centerX = timeScale.timeToPx(start);
- if (this.isInMarker(x, y, centerX)) {
- index = left;
- }
- }
- if (right !== -1) {
- const start = Time.fromRaw(data.tsStarts[right]);
- const centerX = timeScale.timeToPx(start);
- if (this.isInMarker(x, y, centerX)) {
- index = right;
- }
- }
- return index;
- }
-
- isInMarker(x: number, y: number, centerX: number) {
- return (
- Math.abs(x - centerX) + Math.abs(y - this.centerY) <= this.markerWidth
- );
- }
-}
-
class PerfSamplesProfilePlugin implements Plugin {
async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
const result = await ctx.engine.query(`
diff --git a/ui/src/core_plugins/perf_samples_profile/perf_samples_profile_track.ts b/ui/src/core_plugins/perf_samples_profile/perf_samples_profile_track.ts
new file mode 100644
index 0000000..aca33de
--- /dev/null
+++ b/ui/src/core_plugins/perf_samples_profile/perf_samples_profile_track.ts
@@ -0,0 +1,237 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import {searchSegment} from '../../base/binary_search';
+import {duration, Time, time} from '../../base/time';
+import {Actions} from '../../common/actions';
+import {ProfileType, getLegacySelection} from '../../common/state';
+import {TrackData} from '../../common/track_data';
+import {TimelineFetcher} from '../../common/track_helper';
+import {FLAMEGRAPH_HOVERED_COLOR} from '../../frontend/flamegraph';
+import {globals} from '../../frontend/globals';
+import {PanelSize} from '../../frontend/panel';
+import {TimeScale} from '../../frontend/time_scale';
+import {Engine, Track} from '../../public';
+import {LONG} from '../../trace_processor/query_result';
+
+export const PERF_SAMPLES_PROFILE_TRACK_KIND = 'PerfSamplesProfileTrack';
+
+export interface Data extends TrackData {
+ tsStarts: BigInt64Array;
+}
+
+const PERP_SAMPLE_COLOR = 'hsl(224, 45%, 70%)';
+
+// 0.5 Makes the horizontal lines sharp.
+const MARGIN_TOP = 4.5;
+const RECT_HEIGHT = 30.5;
+
+export class PerfSamplesProfileTrack implements Track {
+ private centerY = this.getHeight() / 2;
+ private markerWidth = (this.getHeight() - MARGIN_TOP) / 2;
+ private hoveredTs: time | undefined = undefined;
+ private fetcher = new TimelineFetcher(this.onBoundsChange.bind(this));
+ private upid: number;
+ private engine: Engine;
+
+ constructor(engine: Engine, upid: number) {
+ this.upid = upid;
+ this.engine = engine;
+ }
+
+ async onUpdate(): Promise<void> {
+ await this.fetcher.requestDataForCurrentTime();
+ }
+
+ async onDestroy(): Promise<void> {
+ this.fetcher.dispose();
+ }
+
+ async onBoundsChange(
+ start: time,
+ end: time,
+ resolution: duration,
+ ): Promise<Data> {
+ if (this.upid === undefined) {
+ return {
+ start,
+ end,
+ resolution,
+ length: 0,
+ tsStarts: new BigInt64Array(),
+ };
+ }
+ const queryRes = await this.engine.query(`
+ select ts, upid from perf_sample
+ join thread using (utid)
+ where upid = ${this.upid}
+ and callsite_id is not null
+ order by ts`);
+ const numRows = queryRes.numRows();
+ const data: Data = {
+ start,
+ end,
+ resolution,
+ length: numRows,
+ tsStarts: new BigInt64Array(numRows),
+ };
+
+ const it = queryRes.iter({ts: LONG});
+ for (let row = 0; it.valid(); it.next(), row++) {
+ data.tsStarts[row] = it.ts;
+ }
+ return data;
+ }
+
+ getHeight() {
+ return MARGIN_TOP + RECT_HEIGHT - 1;
+ }
+
+ render(ctx: CanvasRenderingContext2D, _size: PanelSize): void {
+ const {visibleTimeScale} = globals.timeline;
+ const data = this.fetcher.data;
+
+ if (data === undefined) return;
+
+ for (let i = 0; i < data.tsStarts.length; i++) {
+ const centerX = Time.fromRaw(data.tsStarts[i]);
+ const selection = getLegacySelection(globals.state);
+ const isHovered = this.hoveredTs === centerX;
+ const isSelected =
+ selection !== null &&
+ selection.kind === 'PERF_SAMPLES' &&
+ selection.leftTs <= centerX &&
+ selection.rightTs >= centerX;
+ const strokeWidth = isSelected ? 3 : 0;
+ this.drawMarker(
+ ctx,
+ visibleTimeScale.timeToPx(centerX),
+ this.centerY,
+ isHovered,
+ strokeWidth,
+ );
+ }
+ }
+
+ drawMarker(
+ ctx: CanvasRenderingContext2D,
+ x: number,
+ y: number,
+ isHovered: boolean,
+ strokeWidth: number,
+ ): void {
+ ctx.beginPath();
+ ctx.moveTo(x, y - this.markerWidth);
+ ctx.lineTo(x - this.markerWidth, y);
+ ctx.lineTo(x, y + this.markerWidth);
+ ctx.lineTo(x + this.markerWidth, y);
+ ctx.lineTo(x, y - this.markerWidth);
+ ctx.closePath();
+ ctx.fillStyle = isHovered ? FLAMEGRAPH_HOVERED_COLOR : PERP_SAMPLE_COLOR;
+ ctx.fill();
+ if (strokeWidth > 0) {
+ ctx.strokeStyle = FLAMEGRAPH_HOVERED_COLOR;
+ ctx.lineWidth = strokeWidth;
+ ctx.stroke();
+ }
+ }
+
+ onMouseMove({x, y}: {x: number; y: number}) {
+ const data = this.fetcher.data;
+ if (data === undefined) return;
+ const {visibleTimeScale} = globals.timeline;
+ const time = visibleTimeScale.pxToHpTime(x);
+ const [left, right] = searchSegment(data.tsStarts, time.toTime());
+ const index = this.findTimestampIndex(
+ left,
+ visibleTimeScale,
+ data,
+ x,
+ y,
+ right,
+ );
+ this.hoveredTs =
+ index === -1 ? undefined : Time.fromRaw(data.tsStarts[index]);
+ }
+
+ onMouseOut() {
+ this.hoveredTs = undefined;
+ }
+
+ onMouseClick({x, y}: {x: number; y: number}) {
+ const data = this.fetcher.data;
+ if (data === undefined) return false;
+ const {visibleTimeScale} = globals.timeline;
+
+ const time = visibleTimeScale.pxToHpTime(x);
+ const [left, right] = searchSegment(data.tsStarts, time.toTime());
+
+ const index = this.findTimestampIndex(
+ left,
+ visibleTimeScale,
+ data,
+ x,
+ y,
+ right,
+ );
+
+ if (index !== -1) {
+ const ts = Time.fromRaw(data.tsStarts[index]);
+ globals.makeSelection(
+ Actions.selectPerfSamples({
+ id: index,
+ upid: this.upid,
+ leftTs: ts,
+ rightTs: ts,
+ type: ProfileType.PERF_SAMPLE,
+ }),
+ );
+ return true;
+ }
+ return false;
+ }
+
+ // If the markers overlap the rightmost one will be selected.
+ findTimestampIndex(
+ left: number,
+ timeScale: TimeScale,
+ data: Data,
+ x: number,
+ y: number,
+ right: number,
+ ): number {
+ let index = -1;
+ if (left !== -1) {
+ const start = Time.fromRaw(data.tsStarts[left]);
+ const centerX = timeScale.timeToPx(start);
+ if (this.isInMarker(x, y, centerX)) {
+ index = left;
+ }
+ }
+ if (right !== -1) {
+ const start = Time.fromRaw(data.tsStarts[right]);
+ const centerX = timeScale.timeToPx(start);
+ if (this.isInMarker(x, y, centerX)) {
+ index = right;
+ }
+ }
+ return index;
+ }
+
+ isInMarker(x: number, y: number, centerX: number) {
+ return (
+ Math.abs(x - centerX) + Math.abs(y - this.centerY) <= this.markerWidth
+ );
+ }
+}
diff --git a/ui/src/core_plugins/process_summary/index.ts b/ui/src/core_plugins/process_summary/index.ts
index f2a7475..da51bb0 100644
--- a/ui/src/core_plugins/process_summary/index.ts
+++ b/ui/src/core_plugins/process_summary/index.ts
@@ -34,6 +34,8 @@
}
private async addProcessTrackGroups(ctx: PluginContextTrace): Promise<void> {
+ const cpuCount = Math.max(...ctx.trace.cpus, -1) + 1;
+
const result = await ctx.engine.query(`
INCLUDE PERFETTO MODULE android.process_metadata;
@@ -106,7 +108,7 @@
isDebuggable,
},
trackFactory: () => {
- return new ProcessSchedulingTrack(ctx.engine, config);
+ return new ProcessSchedulingTrack(ctx.engine, config, cpuCount);
},
});
} else {
diff --git a/ui/src/core_plugins/process_summary/process_scheduling_track.ts b/ui/src/core_plugins/process_summary/process_scheduling_track.ts
index 28b536f..4b26432 100644
--- a/ui/src/core_plugins/process_summary/process_scheduling_track.ts
+++ b/ui/src/core_plugins/process_summary/process_scheduling_track.ts
@@ -56,23 +56,18 @@
private mousePos?: {x: number; y: number};
private utidHoveredInThisTrack = -1;
private fetcher = new TimelineFetcher(this.onBoundsChange.bind(this));
- private maxCpu = 0;
+ private cpuCount: number;
private engine: Engine;
private trackUuid = uuidv4Sql();
private config: Config;
- constructor(engine: Engine, config: Config) {
+ constructor(engine: Engine, config: Config, cpuCount: number) {
this.engine = engine;
this.config = config;
+ this.cpuCount = cpuCount;
}
async onCreate(): Promise<void> {
- const cpus = await this.engine.getCpus();
-
- // A process scheduling track should only exist in a trace that has cpus.
- assertTrue(cpus.length > 0);
- this.maxCpu = Math.max(...cpus) + 1;
-
if (this.config.upid !== null) {
await this.engine.query(`
create virtual table process_scheduling_${this.trackUuid}
@@ -119,11 +114,9 @@
async onDestroy(): Promise<void> {
this.fetcher.dispose();
- if (this.engine.isAlive) {
- await this.engine.query(`
- drop table process_scheduling_${this.trackUuid}
- `);
- }
+ await this.engine.tryQuery(`
+ drop table process_scheduling_${this.trackUuid}
+ `);
}
async onBoundsChange(
@@ -142,7 +135,7 @@
end,
resolution,
length: numRows,
- maxCpu: this.maxCpu,
+ maxCpu: this.cpuCount,
starts: new BigInt64Array(numRows),
ends: new BigInt64Array(numRows),
cpus: new Uint32Array(numRows),
diff --git a/ui/src/core_plugins/process_summary/process_summary_track.ts b/ui/src/core_plugins/process_summary/process_summary_track.ts
index 5fa31de..57abd75 100644
--- a/ui/src/core_plugins/process_summary/process_summary_track.ts
+++ b/ui/src/core_plugins/process_summary/process_summary_track.ts
@@ -171,13 +171,11 @@
}
async onDestroy(): Promise<void> {
- if (this.engine.isAlive) {
- await this.engine.query(
- `drop table if exists ${this.tableName(
- 'window',
- )}; drop table if exists ${this.tableName('span')}`,
- );
- }
+ await this.engine.tryQuery(
+ `drop table if exists ${this.tableName(
+ 'window',
+ )}; drop table if exists ${this.tableName('span')}`,
+ );
this.fetcher.dispose();
}
diff --git a/ui/src/core_plugins/screenshots/index.ts b/ui/src/core_plugins/screenshots/index.ts
index 029f818..f6eaebb 100644
--- a/ui/src/core_plugins/screenshots/index.ts
+++ b/ui/src/core_plugins/screenshots/index.ts
@@ -15,7 +15,6 @@
import {uuidv4} from '../../base/uuid';
import {AddTrackArgs} from '../../common/actions';
import {GenericSliceDetailsTabConfig} from '../../frontend/generic_slice_details_tab';
-import {NamedSliceTrackTypes} from '../../frontend/named_slice_track';
import {
BottomTabToSCSAdapter,
NUM,
@@ -25,34 +24,9 @@
PrimaryTrackSortKey,
} from '../../public';
import {Engine} from '../../trace_processor/engine';
-import {
- CustomSqlDetailsPanelConfig,
- CustomSqlTableDefConfig,
- CustomSqlTableSliceTrack,
-} from '../custom_sql_table_slices';
import {ScreenshotTab} from './screenshot_panel';
-
-class ScreenshotsTrack extends CustomSqlTableSliceTrack<NamedSliceTrackTypes> {
- static readonly kind = 'dev.perfetto.ScreenshotsTrack';
-
- getSqlDataSource(): CustomSqlTableDefConfig {
- return {
- sqlTableName: 'android_screenshots',
- columns: ['*'],
- };
- }
-
- getDetailsPanel(): CustomSqlDetailsPanelConfig {
- return {
- kind: ScreenshotTab.kind,
- config: {
- sqlTableName: this.tableName,
- title: 'Screenshots',
- },
- };
- }
-}
+import {ScreenshotsTrack} from './screenshots_track';
export type DecideTracksResult = {
tracksToAdd: AddTrackArgs[];
diff --git a/ui/src/core_plugins/screenshots/screenshots_track.ts b/ui/src/core_plugins/screenshots/screenshots_track.ts
new file mode 100644
index 0000000..a880b33
--- /dev/null
+++ b/ui/src/core_plugins/screenshots/screenshots_track.ts
@@ -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.
+
+import {
+ CustomSqlDetailsPanelConfig,
+ CustomSqlTableDefConfig,
+ CustomSqlTableSliceTrack,
+} from '../../frontend/tracks/custom_sql_table_slice_track';
+import {NamedSliceTrackTypes} from '../../frontend/named_slice_track';
+import {ScreenshotTab} from './screenshot_panel';
+
+export class ScreenshotsTrack extends CustomSqlTableSliceTrack<NamedSliceTrackTypes> {
+ static readonly kind = 'dev.perfetto.ScreenshotsTrack';
+
+ getSqlDataSource(): CustomSqlTableDefConfig {
+ return {
+ sqlTableName: 'android_screenshots',
+ columns: ['*'],
+ };
+ }
+
+ getDetailsPanel(): CustomSqlDetailsPanelConfig {
+ return {
+ kind: ScreenshotTab.kind,
+ config: {
+ sqlTableName: this.tableName,
+ title: 'Screenshots',
+ },
+ };
+ }
+}
diff --git a/ui/src/core_plugins/thread_state/index.ts b/ui/src/core_plugins/thread_state/index.ts
index 73e2faf..ab0bcc5 100644
--- a/ui/src/core_plugins/thread_state/index.ts
+++ b/ui/src/core_plugins/thread_state/index.ts
@@ -24,7 +24,7 @@
import {getTrackName} from '../../public/utils';
import {NUM, NUM_NULL, STR_NULL} from '../../trace_processor/query_result';
-import {ThreadStateTrack as ThreadStateTrackV2} from './thread_state_v2';
+import {ThreadStateTrack} from './thread_state_track';
export const THREAD_STATE_TRACK_KIND = 'ThreadStateTrack';
@@ -64,7 +64,7 @@
kind: THREAD_STATE_TRACK_KIND,
utid,
trackFactory: ({trackKey}) => {
- return new ThreadStateTrackV2(
+ return new ThreadStateTrack(
{
engine: ctx.engine,
trackKey,
diff --git a/ui/src/core_plugins/thread_state/thread_state_v2.ts b/ui/src/core_plugins/thread_state/thread_state_track.ts
similarity index 100%
rename from ui/src/core_plugins/thread_state/thread_state_v2.ts
rename to ui/src/core_plugins/thread_state/thread_state_track.ts
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/core_plugins/visualised_args/visualized_args_track.ts b/ui/src/core_plugins/visualised_args/visualized_args_track.ts
index 8de85d3..55cf49d 100644
--- a/ui/src/core_plugins/visualised_args/visualized_args_track.ts
+++ b/ui/src/core_plugins/visualised_args/visualized_args_track.ts
@@ -68,9 +68,7 @@
`);
return new DisposableCallback(() => {
- if (this.engine.isAlive) {
- this.engine.query(`drop view ${this.viewName}`);
- }
+ this.engine.tryQuery(`drop view ${this.viewName}`);
});
}
diff --git a/ui/src/frontend/app.ts b/ui/src/frontend/app.ts
index 09b1036..6c90216 100644
--- a/ui/src/frontend/app.ts
+++ b/ui/src/frontend/app.ts
@@ -18,12 +18,10 @@
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 {runQuery} from '../common/queries';
import {
DurationPrecision,
setDurationPrecision,
@@ -31,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';
@@ -62,12 +54,16 @@
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 permalink = globals.state.permalink;
- if (!permalink.requestId || !permalink.hash) return null;
- const url = `${self.location.origin}/#!/?s=${permalink.hash}`;
+ const hash = globals.permalinkHash;
+ if (!hash) return null;
+ const url = `${self.location.origin}/#!/?s=${hash}`;
const linkProps = {title: 'Click to copy the URL', onclick: onClickCopy(url)};
return m('.alert-permalink', [
@@ -75,7 +71,7 @@
m(
'button',
{
- onclick: () => globals.dispatch(Actions.clearPermalink({})),
+ onclick: () => publishPermalinkHash(undefined),
},
m('i.material-icons.disallow-selection', 'close'),
),
@@ -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 {
@@ -322,9 +217,8 @@
const engine = this.getEngine();
if (engine !== undefined && trackUtid != 0) {
- await runQuery(
+ await engine.query(
`INCLUDE PERFETTO MODULE sched.thread_executing_span;`,
- engine,
);
await addDebugSliceTrack(
engine,
@@ -365,9 +259,8 @@
const engine = this.getEngine();
if (engine !== undefined && trackUtid != 0) {
- await runQuery(
+ await engine.query(
`INCLUDE PERFETTO MODULE sched.thread_executing_span_with_slice;`,
- engine,
);
await addDebugSliceTrack(
engine,
@@ -454,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: '/',
},
{
@@ -476,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: () => {
@@ -495,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(),
@@ -620,8 +453,8 @@
if (selection !== null && selection.kind === 'AREA') {
const area = globals.state.areas[selection.areaId];
const coversEntireTimeRange =
- globals.traceTime.start === area.start &&
- globals.traceTime.end === area.end;
+ globals.traceContext.start === area.start &&
+ globals.traceContext.end === area.end;
if (!coversEntireTimeRange) {
// If the current selection is an area which does not cover the
// entire time range, preserve the list of selected tracks and
@@ -636,7 +469,7 @@
// If the current selection is not an area, select all.
tracksToSelect = Object.keys(globals.state.tracks);
}
- const {start, end} = globals.traceTime;
+ const {start, end} = globals.traceContext;
globals.dispatch(
Actions.selectArea({
area: {
@@ -655,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 =
@@ -683,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;
@@ -707,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,
@@ -717,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();
},
});
}
@@ -746,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.
@@ -777,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();
},
});
}
@@ -822,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) => {
@@ -840,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();
},
});
}
@@ -865,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;
}
}
@@ -987,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/base_counter_track.ts b/ui/src/frontend/base_counter_track.ts
index 9a37a6a..d010f95 100644
--- a/ui/src/frontend/base_counter_track.ts
+++ b/ui/src/frontend/base_counter_track.ts
@@ -18,26 +18,18 @@
import {Disposable, NullDisposable} from '../base/disposable';
import {assertTrue, assertUnreachable} from '../base/logging';
import {Time, time} from '../base/time';
+import {uuidv4Sql} from '../base/uuid';
import {drawTrackHoverTooltip} from '../common/canvas_utils';
import {raf} from '../core/raf_scheduler';
+import {CacheKey} from '../core/timeline_cache';
import {Engine, LONG, NUM, Track} from '../public';
import {Button} from '../widgets/button';
-import {MenuItem, MenuDivider, PopupMenu2} from '../widgets/menu';
+import {MenuDivider, MenuItem, PopupMenu2} from '../widgets/menu';
import {checkerboardExcept} from './checkerboard';
import {globals} from './globals';
import {PanelSize} from './panel';
import {NewTrackArgs} from './track';
-import {CacheKey} from '../core/timeline_cache';
-import {featureFlags} from '../core/feature_flags';
-import {uuidv4Sql} from '../base/uuid';
-
-export const COUNTER_DEBUG_MENU_ITEMS = featureFlags.register({
- id: 'counterDebugMenuItems',
- name: 'Counter debug menu items',
- description: 'Extra counter menu items for debugging purposes.',
- defaultValue: false,
-});
function roundAway(n: number): number {
const exp = Math.ceil(Math.log10(Math.max(Math.abs(n), 1)));
@@ -355,65 +347,63 @@
},
}),
- COUNTER_DEBUG_MENU_ITEMS.get() && [
- m(MenuDivider),
- m(
- MenuItem,
- {
- label: `Mode (currently: ${options.yMode})`,
- },
+ m(MenuDivider),
+ m(
+ MenuItem,
+ {
+ label: `Mode (currently: ${options.yMode})`,
+ },
- m(MenuItem, {
- label: 'Value',
- icon:
- options.yMode === 'value'
- ? 'radio_button_checked'
- : 'radio_button_unchecked',
- onclick: () => {
- options.yMode = 'value';
- this.invalidate();
- },
- }),
-
- m(MenuItem, {
- label: 'Delta',
- icon:
- options.yMode === 'delta'
- ? 'radio_button_checked'
- : 'radio_button_unchecked',
- onclick: () => {
- options.yMode = 'delta';
- this.invalidate();
- },
- }),
-
- m(MenuItem, {
- label: 'Rate',
- icon:
- options.yMode === 'rate'
- ? 'radio_button_checked'
- : 'radio_button_unchecked',
- onclick: () => {
- options.yMode = 'rate';
- this.invalidate();
- },
- }),
- ),
m(MenuItem, {
- label: 'Round y-axis scale',
+ label: 'Value',
icon:
- options.yRangeRounding === 'human_readable'
- ? 'check_box'
- : 'check_box_outline_blank',
+ options.yMode === 'value'
+ ? 'radio_button_checked'
+ : 'radio_button_unchecked',
onclick: () => {
- options.yRangeRounding =
- options.yRangeRounding === 'human_readable'
- ? 'strict'
- : 'human_readable';
+ options.yMode = 'value';
this.invalidate();
},
}),
- ],
+
+ m(MenuItem, {
+ label: 'Delta',
+ icon:
+ options.yMode === 'delta'
+ ? 'radio_button_checked'
+ : 'radio_button_unchecked',
+ onclick: () => {
+ options.yMode = 'delta';
+ this.invalidate();
+ },
+ }),
+
+ m(MenuItem, {
+ label: 'Rate',
+ icon:
+ options.yMode === 'rate'
+ ? 'radio_button_checked'
+ : 'radio_button_unchecked',
+ onclick: () => {
+ options.yMode = 'rate';
+ this.invalidate();
+ },
+ }),
+ ),
+ m(MenuItem, {
+ label: 'Round y-axis scale',
+ icon:
+ options.yRangeRounding === 'human_readable'
+ ? 'check_box'
+ : 'check_box_outline_blank',
+ onclick: () => {
+ options.yRangeRounding =
+ options.yRangeRounding === 'human_readable'
+ ? 'strict'
+ : 'human_readable';
+ this.invalidate();
+ },
+ }),
];
}
@@ -698,9 +688,7 @@
this.initState.dispose();
this.initState = undefined;
}
- if (this.engine.isAlive) {
- await this.engine.query(`drop table if exists ${this.getTableName()}`);
- }
+ await this.engine.tryQuery(`drop table if exists ${this.getTableName()}`);
}
// Compute the range of values to display and range label.
diff --git a/ui/src/frontend/base_slice_track.ts b/ui/src/frontend/base_slice_track.ts
index 720ace2..253ed41 100644
--- a/ui/src/frontend/base_slice_track.ts
+++ b/ui/src/frontend/base_slice_track.ts
@@ -666,9 +666,7 @@
this.initState.dispose();
this.initState = undefined;
}
- if (this.engine.isAlive) {
- await this.engine.execute(`drop table ${this.getTableName()}`);
- }
+ await this.engine.tryQuery(`drop table ${this.getTableName()}`);
}
// This method figures out if the visible window is outside the bounds of
diff --git a/ui/src/frontend/chrome_slice_details_tab.ts b/ui/src/frontend/chrome_slice_details_tab.ts
index 91cf54f..dd13a8d 100644
--- a/ui/src/frontend/chrome_slice_details_tab.ts
+++ b/ui/src/frontend/chrome_slice_details_tab.ts
@@ -17,7 +17,6 @@
import {Icons} from '../base/semantic_icons';
import {duration, Time, TimeSpan} from '../base/time';
import {exists} from '../base/utils';
-import {runQuery} from '../common/queries';
import {raf} from '../core/raf_scheduler';
import {Engine} from '../trace_processor/engine';
import {LONG, LONG_NULL, NUM, STR_NULL} from '../trace_processor/query_result';
@@ -104,17 +103,18 @@
run: (slice: SliceDetails) => {
const engine = getEngine();
if (engine === undefined) return;
- runQuery(
- `
+ engine
+ .query(
+ `
INCLUDE PERFETTO MODULE android.binder;
INCLUDE PERFETTO MODULE android.monitor_contention;
`,
- engine,
- ).then(() =>
- addDebugSliceTrack(
- engine,
- {
- sqlSource: `
+ )
+ .then(() =>
+ addDebugSliceTrack(
+ engine,
+ {
+ sqlSource: `
WITH merged AS (
SELECT s.ts, s.dur, tx.aidl_name AS name, 0 AS depth
FROM android_binder_txns tx
@@ -151,14 +151,14 @@
AND short_blocked_method IS NOT NULL
ORDER BY depth
) SELECT ts, dur, name FROM merged`,
- },
- `Binder names (${getProcessNameFromSlice(
- slice,
- )}:${getThreadNameFromSlice(slice)})`,
- {ts: 'ts', dur: 'dur', name: 'name'},
- [],
- ),
- );
+ },
+ `Binder names (${getProcessNameFromSlice(
+ slice,
+ )}:${getThreadNameFromSlice(slice)})`,
+ {ts: 'ts', dur: 'dur', name: 'name'},
+ [],
+ ),
+ );
},
},
];
diff --git a/ui/src/frontend/flow_events_renderer.ts b/ui/src/frontend/flow_events_renderer.ts
index 4ffa6cf..69097ec 100644
--- a/ui/src/frontend/flow_events_renderer.ts
+++ b/ui/src/frontend/flow_events_renderer.ts
@@ -71,8 +71,8 @@
for (const trackId of getTrackIds(track)) {
this.trackIdToTrackPanel.set(trackId, {panel, yStart});
}
- } else if (exists(panel.trackGroupId)) {
- this.groupIdToTrackGroupPanel.set(panel.trackGroupId, {
+ } else if (exists(panel.groupKey)) {
+ this.groupIdToTrackGroupPanel.set(panel.groupKey, {
panel,
yStart,
height,
diff --git a/ui/src/frontend/globals.ts b/ui/src/frontend/globals.ts
index 04cdc17..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;
@@ -221,7 +222,7 @@
pendingScrollId: number | undefined;
}
-export interface TraceTime {
+export interface TraceContext {
readonly start: time;
readonly end: time;
@@ -237,14 +238,22 @@
// Trace TZ is like UTC but keeps into account also the timezone_off_mins
// recorded into the trace, to show timestamps in the device local time.
readonly traceTzOffset: time;
+
+ // The list of CPUs in the trace
+ readonly cpus: number[];
+
+ // The number of gpus in the trace
+ readonly gpuCount: number;
}
-export const defaultTraceTime: TraceTime = {
+export const defaultTraceContext: TraceContext = {
start: Time.ZERO,
end: Time.fromSeconds(10),
realtimeOffset: Time.ZERO,
utcOffset: Time.ZERO,
traceTzOffset: Time.ZERO,
+ cpus: [],
+ gpuCount: 0,
};
/**
@@ -291,12 +300,15 @@
private _selectionManager = new SelectionManager(this._store);
private _hasFtrace: boolean = false;
+ omnibox = new OmniboxManager();
+
scrollToTrackKey?: string | number;
httpRpcState: HttpRpcState = {connected: false};
newVersionAvailable = false;
showPanningHint = false;
+ permalinkHash?: string;
- traceTime = defaultTraceTime;
+ traceContext = defaultTraceContext;
// TODO(hjd): Remove once we no longer need to update UUID on redraw.
private _publishRedraw?: () => void = undefined;
@@ -716,19 +728,19 @@
// Get a timescale that covers the entire trace
getTraceTimeScale(pxSpan: PxSpan): TimeScale {
- const {start, end} = this.traceTime;
+ const {start, end} = this.traceContext;
const traceTime = HighPrecisionTimeSpan.fromTime(start, end);
return TimeScale.fromHPTimeSpan(traceTime, pxSpan);
}
// Get the trace time bounds
stateTraceTime(): Span<HighPrecisionTime> {
- const {start, end} = this.traceTime;
+ const {start, end} = this.traceContext;
return HighPrecisionTimeSpan.fromTime(start, end);
}
stateTraceTimeTP(): Span<time, duration> {
- const {start, end} = this.traceTime;
+ const {start, end} = this.traceContext;
return new TimeSpan(start, end);
}
@@ -762,14 +774,14 @@
switch (fmt) {
case TimestampFormat.Timecode:
case TimestampFormat.Seconds:
- return this.traceTime.start;
+ return this.traceContext.start;
case TimestampFormat.Raw:
case TimestampFormat.RawLocale:
return Time.ZERO;
case TimestampFormat.UTC:
- return this.traceTime.utcOffset;
+ return this.traceContext.utcOffset;
case TimestampFormat.TraceTz:
- return this.traceTime.traceTzOffset;
+ return this.traceContext.traceTzOffset;
default:
const x: never = fmt;
throw new Error(`Unsupported format ${x}`);
@@ -859,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/notes_panel.ts b/ui/src/frontend/notes_panel.ts
index c583e2d..0dc71bd 100644
--- a/ui/src/frontend/notes_panel.ts
+++ b/ui/src/frontend/notes_panel.ts
@@ -57,12 +57,9 @@
export class NotesPanel implements Panel {
readonly kind = 'panel';
readonly selectable = false;
- readonly trackKey = undefined;
hoveredX: null | number = null;
- constructor(readonly key: string) {}
-
render(): m.Children {
const allCollapsed = Object.values(globals.state.trackGroups).every(
(group) => group.collapsed,
diff --git a/ui/src/frontend/omnibox_manager.ts b/ui/src/frontend/omnibox_manager.ts
new file mode 100644
index 0000000..bb09810
--- /dev/null
+++ b/ui/src/frontend/omnibox_manager.ts
@@ -0,0 +1,155 @@
+// 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.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/frontend/overview_timeline_panel.ts b/ui/src/frontend/overview_timeline_panel.ts
index a47434a..4ff95bb 100644
--- a/ui/src/frontend/overview_timeline_panel.ts
+++ b/ui/src/frontend/overview_timeline_panel.ts
@@ -42,7 +42,6 @@
private static HANDLE_SIZE_PX = 5;
readonly kind = 'panel';
readonly selectable = false;
- readonly trackKey = undefined;
private width = 0;
private gesture?: DragGestureHandler;
@@ -51,8 +50,6 @@
private dragStrategy?: DragStrategy;
private readonly boundOnMouseMove = this.onMouseMove.bind(this);
- constructor(readonly key: string) {}
-
// Must explicitly type now; arguments types are no longer auto-inferred.
// https://github.com/Microsoft/TypeScript/issues/1373
onupdate({dom}: m.CVnodeDOM) {
diff --git a/ui/src/frontend/panel_container.ts b/ui/src/frontend/panel_container.ts
index 9214430..5611a19 100644
--- a/ui/src/frontend/panel_container.ts
+++ b/ui/src/frontend/panel_container.ts
@@ -47,22 +47,20 @@
const CANVAS_OVERDRAW_PX = 100;
export interface Panel {
- kind: 'panel';
+ readonly kind: 'panel';
render(): m.Children;
- selectable: boolean;
- key: string;
- trackKey?: string;
- trackGroupId?: string;
+ readonly selectable: boolean;
+ readonly trackKey?: string; // Defined if this panel represents are track
+ readonly groupKey?: string; // Defined if this panel represents a group - i.e. a group summary track
renderCanvas(ctx: CanvasRenderingContext2D, size: PanelSize): void;
getSliceRect?(tStart: time, tDur: time, depth: number): SliceRect | undefined;
}
export interface PanelGroup {
- kind: 'group';
- collapsed: boolean;
- header: Panel;
- childPanels: Panel[];
- trackGroupId: string;
+ readonly kind: 'group';
+ readonly collapsed: boolean;
+ readonly header: Panel;
+ readonly childPanels: Panel[];
}
export type PanelOrGroup = Panel | PanelGroup;
@@ -74,7 +72,7 @@
}
interface PanelInfo {
- id: string; // Can be == '' for singleton panels.
+ trackOrGroupKey: string; // Can be == '' for singleton panels.
panel: Panel;
height: number;
width: number;
@@ -91,7 +89,7 @@
private panelContainerHeight = 0;
// Updated every render cycle in the view() hook
- private panelByKey = new Map<string, Panel>();
+ private panelById = new Map<string, Panel>();
// Updated every render cycle in the oncreate/onupdate hook
private panelInfos: PanelInfo[] = [];
@@ -179,11 +177,11 @@
tracks.push(panel.trackKey);
continue;
}
- if (panel.trackGroupId !== undefined) {
- const trackGroup = globals.state.trackGroups[panel.trackGroupId];
+ if (panel.groupKey !== undefined) {
+ const trackGroup = globals.state.trackGroups[panel.groupKey];
// Only select a track group and all child tracks if it is closed.
if (trackGroup.collapsed) {
- tracks.push(panel.trackGroupId);
+ tracks.push(panel.groupKey);
for (const track of trackGroup.tracks) {
tracks.push(track);
}
@@ -258,37 +256,40 @@
this.trash.dispose();
}
- renderPanel(node: Panel, key: string, extraClass = ''): m.Vnode {
- assertFalse(this.panelByKey.has(key));
- this.panelByKey.set(key, node);
- return m(`.pf-panel${extraClass}`, {key, 'data-key': key}, node.render());
+ renderPanel(node: Panel, panelId: string, extraClass = ''): m.Vnode {
+ assertFalse(this.panelById.has(panelId));
+ this.panelById.set(panelId, node);
+ return m(
+ `.pf-panel${extraClass}`,
+ {'data-panel-id': panelId},
+ node.render(),
+ );
}
// Render a tree of panels into one vnode. Argument `path` is used to build
// `key` attribute for intermediate tree vnodes: otherwise Mithril internals
// will complain about keyed and non-keyed vnodes mixed together.
- renderTree(node: PanelOrGroup, path: string): m.Vnode {
+ renderTree(node: PanelOrGroup, panelId: string): m.Vnode {
if (node.kind === 'group') {
return m(
'div.pf-panel-group',
- {key: path},
this.renderPanel(
node.header,
- `${path}-header`,
+ `${panelId}-header`,
node.collapsed ? '' : '.pf-sticky',
),
...node.childPanels.map((child, index) =>
- this.renderTree(child, `${path}-${index}`),
+ this.renderTree(child, `${panelId}-${index}`),
),
);
}
- return this.renderPanel(node, assertExists(node.key));
+ return this.renderPanel(node, panelId);
}
view({attrs}: m.CVnode<PanelContainerAttrs>) {
- this.panelByKey.clear();
+ this.panelById.clear();
const children = attrs.panels.map((panel, index) =>
- this.renderTree(panel, `track-tree-${index}`),
+ this.renderTree(panel, `${index}`),
);
return m(
@@ -316,14 +317,15 @@
this.panelContainerHeight = domRect.height;
dom.querySelectorAll('.pf-panel').forEach((panelElement) => {
- const key = assertExists(panelElement.getAttribute('data-key'));
- const panel = assertExists(this.panelByKey.get(key));
+ const panelHTMLElement = toHTMLElement(panelElement);
+ const panelId = assertExists(panelHTMLElement.dataset.panelId);
+ const panel = assertExists(this.panelById.get(panelId));
// NOTE: the id can be undefined for singletons like overview timeline.
- const id = panel.trackKey || panel.trackGroupId || '';
+ const key = panel.trackKey || panel.groupKey || '';
const rect = panelElement.getBoundingClientRect();
this.panelInfos.push({
- id,
+ trackOrGroupKey: key,
height: rect.height,
width: rect.width,
clientX: rect.x,
@@ -440,7 +442,7 @@
let selectedTracksMaxY = this.panelContainerTop;
let trackFromCurrentContainerSelected = false;
for (let i = 0; i < this.panelInfos.length; i++) {
- if (area.tracks.includes(this.panelInfos[i].id)) {
+ if (area.tracks.includes(this.panelInfos[i].trackOrGroupKey)) {
trackFromCurrentContainerSelected = true;
selectedTracksMinY = Math.min(
selectedTracksMinY,
diff --git a/ui/src/frontend/permalink.ts b/ui/src/frontend/permalink.ts
new file mode 100644
index 0000000..888fe55
--- /dev/null
+++ b/ui/src/frontend/permalink.ts
@@ -0,0 +1,250 @@
+// 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 {produce} from 'immer';
+import {assertExists} from '../base/logging';
+import {runValidator} from '../base/validators';
+import {Actions} from '../common/actions';
+import {ConversionJobStatus} from '../common/conversion_jobs';
+import {
+ createEmptyNonSerializableState,
+ createEmptyState,
+} from '../common/empty_state';
+import {EngineConfig, ObjectById, STATE_VERSION, State} from '../common/state';
+import {
+ BUCKET_NAME,
+ TraceGcsUploader,
+ buggyToSha256,
+ deserializeStateObject,
+ saveState,
+ toSha256,
+} from '../common/upload_utils';
+import {
+ RecordConfig,
+ recordConfigValidator,
+} from '../controller/record_config_types';
+import {globals} from './globals';
+import {
+ publishConversionJobStatusUpdate,
+ publishPermalinkHash,
+} from './publish';
+import {Router} from './router';
+import {showModal} from '../widgets/modal';
+
+export interface PermalinkOptions {
+ isRecordingConfig?: boolean;
+}
+
+export async function createPermalink(
+ options: PermalinkOptions = {},
+): Promise<void> {
+ const {isRecordingConfig = false} = options;
+ const jobName = 'create_permalink';
+ publishConversionJobStatusUpdate({
+ jobName,
+ jobStatus: ConversionJobStatus.InProgress,
+ });
+
+ try {
+ const hash = await createPermalinkInternal(isRecordingConfig);
+ publishPermalinkHash(hash);
+ } finally {
+ publishConversionJobStatusUpdate({
+ jobName,
+ jobStatus: ConversionJobStatus.NotRunning,
+ });
+ }
+}
+
+async function createPermalinkInternal(
+ isRecordingConfig: boolean,
+): Promise<string> {
+ let uploadState: State | RecordConfig = globals.state;
+
+ if (isRecordingConfig) {
+ uploadState = globals.state.recordConfig;
+ } else {
+ const engine = assertExists(globals.getCurrentEngine());
+ let dataToUpload: File | ArrayBuffer | undefined = undefined;
+ let traceName = `trace ${engine.id}`;
+ if (engine.source.type === 'FILE') {
+ dataToUpload = engine.source.file;
+ traceName = dataToUpload.name;
+ } else if (engine.source.type === 'ARRAY_BUFFER') {
+ dataToUpload = engine.source.buffer;
+ } else if (engine.source.type !== 'URL') {
+ throw new Error(`Cannot share trace ${JSON.stringify(engine.source)}`);
+ }
+
+ if (dataToUpload !== undefined) {
+ updateStatus(`Uploading ${traceName}`);
+ const uploader = new TraceGcsUploader(dataToUpload, () => {
+ switch (uploader.state) {
+ case 'UPLOADING':
+ const statusTxt = `Uploading ${uploader.getEtaString()}`;
+ updateStatus(statusTxt);
+ break;
+ case 'UPLOADED':
+ // Convert state to use URLs and remove permalink.
+ const url = uploader.uploadedUrl;
+ uploadState = produce(globals.state, (draft) => {
+ assertExists(draft.engine).source = {type: 'URL', url};
+ });
+ break;
+ case 'ERROR':
+ updateStatus(`Upload failed ${uploader.error}`);
+ break;
+ } // switch (state)
+ }); // onProgress
+ await uploader.waitForCompletion();
+ }
+ }
+
+ // Upload state.
+ updateStatus(`Creating permalink...`);
+ const hash = await saveState(uploadState);
+ updateStatus(`Permalink ready`);
+ return hash;
+}
+
+function updateStatus(msg: string): void {
+ // TODO(hjd): Unify loading updates.
+ globals.dispatch(
+ Actions.updateStatus({
+ msg,
+ timestamp: Date.now() / 1000,
+ }),
+ );
+}
+
+export async function loadPermalink(hash: string): Promise<void> {
+ // Otherwise, this is a request to load the permalink.
+ const stateOrConfig = await loadState(hash);
+
+ if (isRecordConfig(stateOrConfig)) {
+ // This permalink state only contains a RecordConfig. Show the
+ // recording page with the config, but keep other state as-is.
+ const validConfig = runValidator(
+ recordConfigValidator,
+ stateOrConfig as unknown,
+ ).result;
+ globals.dispatch(Actions.setRecordConfig({config: validConfig}));
+ Router.navigate('#!/record');
+ return;
+ }
+ globals.dispatch(Actions.setState({newState: stateOrConfig}));
+}
+
+async function loadState(id: string): Promise<State | RecordConfig> {
+ const url = `https://storage.googleapis.com/${BUCKET_NAME}/${id}`;
+ const response = await fetch(url);
+ if (!response.ok) {
+ throw new Error(
+ `Could not fetch permalink.\n` +
+ `Are you sure the id (${id}) is correct?\n` +
+ `URL: ${url}`,
+ );
+ }
+ const text = await response.text();
+ const stateHash = await toSha256(text);
+ const state = deserializeStateObject<State>(text);
+ if (stateHash !== id) {
+ // Old permalinks incorrectly dropped some digits from the
+ // hexdigest of the SHA256. We don't want to invalidate those
+ // links so we also compute the old string and try that here
+ // also.
+ const buggyStateHash = await buggyToSha256(text);
+ if (buggyStateHash !== id) {
+ throw new Error(`State hash does not match ${id} vs. ${stateHash}`);
+ }
+ }
+ if (!isRecordConfig(state)) {
+ return upgradeState(state);
+ }
+ return state;
+}
+
+function isRecordConfig(
+ stateOrConfig: State | RecordConfig,
+): stateOrConfig is RecordConfig {
+ const mode = (stateOrConfig as {mode?: string}).mode;
+ return (
+ mode !== undefined &&
+ ['STOP_WHEN_FULL', 'RING_BUFFER', 'LONG_TRACE'].includes(mode)
+ );
+}
+
+function upgradeState(state: State): State {
+ if (state.engine !== undefined && state.engine.source.type !== 'URL') {
+ // All permalink traces should be modified to have a source.type=URL
+ // pointing to the uploaded trace. Due to a bug in some older version
+ // of the UI (b/327049372), an upload failure can end up with a state that
+ // has type=FILE but a null file object. If this happens, invalidate the
+ // trace and show a message.
+ showModal({
+ title: 'Cannot load trace permalink',
+ content: m(
+ 'div',
+ 'The permalink stored on the server is corrupted ' +
+ 'and cannot be loaded.',
+ ),
+ });
+ return createEmptyState();
+ }
+
+ if (state.version !== STATE_VERSION) {
+ const newState = createEmptyState();
+ // Old permalinks from state versions prior to version 24
+ // have multiple engines of which only one is identified as the
+ // current engine via currentEngineId. Handle this case:
+ if (isMultiEngineState(state)) {
+ const engineId = state.currentEngineId;
+ if (engineId !== undefined) {
+ newState.engine = state.engines[engineId];
+ }
+ } else {
+ newState.engine = state.engine;
+ }
+
+ if (newState.engine !== undefined) {
+ newState.engine.ready = false;
+ }
+ const message =
+ `Unable to parse old state version. Discarding state ` +
+ `and loading trace.`;
+ console.warn(message);
+ updateStatus(message);
+ return newState;
+ } else {
+ // Loaded state is presumed to be compatible with the State type
+ // definition in the app. However, a non-serializable part has to be
+ // recreated.
+ state.nonSerializableState = createEmptyNonSerializableState();
+ }
+ return state;
+}
+
+interface MultiEngineState {
+ currentEngineId?: string;
+ engines: ObjectById<EngineConfig>;
+}
+
+function isMultiEngineState(
+ state: State | MultiEngineState,
+): state is MultiEngineState {
+ if ((state as MultiEngineState).engines !== undefined) {
+ return true;
+ }
+ return false;
+}
diff --git a/ui/src/frontend/publish.ts b/ui/src/frontend/publish.ts
index bfb34ca..2fbc6df 100644
--- a/ui/src/frontend/publish.ts
+++ b/ui/src/frontend/publish.ts
@@ -31,7 +31,7 @@
SliceDetails,
ThreadDesc,
ThreadStateDetails,
- TraceTime,
+ TraceContext,
} from './globals';
import {findCurrentSelection} from './keyboard_event_handler';
@@ -96,8 +96,8 @@
globals.publishRedraw();
}
-export function publishTraceDetails(details: TraceTime): void {
- globals.traceTime = details;
+export function publishTraceContext(details: TraceContext): void {
+ globals.traceContext = details;
globals.publishRedraw();
}
@@ -208,3 +208,8 @@
globals.showPanningHint = true;
globals.publishRedraw();
}
+
+export function publishPermalinkHash(hash: string | undefined): void {
+ globals.permalinkHash = hash;
+ globals.publishRedraw();
+}
diff --git a/ui/src/frontend/record_page.ts b/ui/src/frontend/record_page.ts
index ad59ea8..49cc7b3 100644
--- a/ui/src/frontend/record_page.ts
+++ b/ui/src/frontend/record_page.ts
@@ -57,6 +57,7 @@
import {RecordingSectionAttrs} from './recording/recording_sections';
import {RecordingSettings} from './recording/recording_settings';
import {EtwSettings} from './recording/etw_settings';
+import {createPermalink} from './permalink';
export const PERSIST_CONFIG_FLAG = featureFlags.register({
id: 'persistConfigsUI',
@@ -176,9 +177,7 @@
'button.permalinkconfig',
{
onclick: () => {
- globals.dispatch(
- Actions.createPermalink({isRecordingConfig: true}),
- );
+ createPermalink({isRecordingConfig: true});
},
},
'Share recording settings',
diff --git a/ui/src/frontend/record_page_v2.ts b/ui/src/frontend/record_page_v2.ts
index db87cfa..dc1d015 100644
--- a/ui/src/frontend/record_page_v2.ts
+++ b/ui/src/frontend/record_page_v2.ts
@@ -16,7 +16,6 @@
import {Attributes} from 'mithril';
import {assertExists} from '../base/logging';
-import {Actions} from '../common/actions';
import {RecordingConfigUtils} from '../common/recordingV2/recording_config_utils';
import {
ChromeTargetInfo,
@@ -57,6 +56,7 @@
import {RecordingSettings} from './recording/recording_settings';
import {FORCE_RESET_MESSAGE} from './recording/recording_ui_utils';
import {showAddNewTargetModal} from './recording/reset_target_modal';
+import {createPermalink} from './permalink';
const START_RECORDING_MESSAGE = 'Start Recording';
@@ -186,9 +186,7 @@
'button.permalinkconfig',
{
onclick: () => {
- globals.dispatch(
- Actions.createPermalink({isRecordingConfig: true}),
- );
+ createPermalink({isRecordingConfig: true});
},
},
'Share recording settings',
diff --git a/ui/src/frontend/scroll_helper.ts b/ui/src/frontend/scroll_helper.ts
index f643001..cd7e94d 100644
--- a/ui/src/frontend/scroll_helper.ts
+++ b/ui/src/frontend/scroll_helper.ts
@@ -18,7 +18,7 @@
HighPrecisionTime,
HighPrecisionTimeSpan,
} from '../common/high_precision_time';
-import {getContainingTrackId} from '../common/state';
+import {getContainingGroupKey} from '../common/state';
import {globals} from './globals';
@@ -127,12 +127,12 @@
}
let trackGroup = null;
- const trackGroupId = getContainingTrackId(globals.state, trackKeyString);
- if (trackGroupId) {
- trackGroup = document.querySelector('#track_' + trackGroupId);
+ const groupKey = getContainingGroupKey(globals.state, trackKeyString);
+ if (groupKey) {
+ trackGroup = document.querySelector('#track_' + groupKey);
}
- if (!trackGroupId || !trackGroup) {
+ if (!groupKey || !trackGroup) {
console.error(`Can't scroll, track (${trackKeyString}) not found.`);
return;
}
@@ -142,7 +142,7 @@
if (openGroup) {
// After the track exists in the dom, it will be scrolled to.
globals.scrollToTrackKey = trackKey;
- globals.dispatch(Actions.toggleTrackGroupCollapsed({trackGroupId}));
+ globals.dispatch(Actions.toggleTrackGroupCollapsed({groupKey}));
return;
} else {
trackGroup.scrollIntoView({behavior: 'smooth', block: 'nearest'});
diff --git a/ui/src/frontend/simple_counter_track.ts b/ui/src/frontend/simple_counter_track.ts
index 5b21ded..7bf0f0e 100644
--- a/ui/src/frontend/simple_counter_track.ts
+++ b/ui/src/frontend/simple_counter_track.ts
@@ -74,8 +74,6 @@
}
private async dropTrackTable(): Promise<void> {
- if (this.engine.isAlive) {
- await this.engine.query(`drop table if exists ${this.sqlTableName}`);
- }
+ await this.engine.tryQuery(`drop table if exists ${this.sqlTableName}`);
}
}
diff --git a/ui/src/frontend/simple_slice_track.ts b/ui/src/frontend/simple_slice_track.ts
index c292fe6..3eebb83 100644
--- a/ui/src/frontend/simple_slice_track.ts
+++ b/ui/src/frontend/simple_slice_track.ts
@@ -17,7 +17,7 @@
CustomSqlDetailsPanelConfig,
CustomSqlTableDefConfig,
CustomSqlTableSliceTrack,
-} from '../core_plugins/custom_sql_table_slices';
+} from './tracks/custom_sql_table_slice_track';
import {NamedSliceTrackTypes} from './named_slice_track';
import {ARG_PREFIX, SliceColumns, SqlDataSource} from './debug_tracks';
import {uuidv4Sql} from '../base/uuid';
@@ -108,8 +108,6 @@
}
private async destroyTrackTable() {
- if (this.engine.isAlive) {
- await this.engine.query(`DROP TABLE IF EXISTS ${this.sqlTableName}`);
- }
+ await this.engine.tryQuery(`DROP TABLE IF EXISTS ${this.sqlTableName}`);
}
}
diff --git a/ui/src/frontend/thread_state_tab.ts b/ui/src/frontend/thread_state_tab.ts
index fac6b8a..f028b15 100644
--- a/ui/src/frontend/thread_state_tab.ts
+++ b/ui/src/frontend/thread_state_tab.ts
@@ -15,7 +15,6 @@
import m from 'mithril';
import {Time, time} from '../base/time';
-import {runQuery} from '../common/queries';
import {raf} from '../core/raf_scheduler';
import {Anchor} from '../widgets/anchor';
import {Button} from '../widgets/button';
@@ -322,14 +321,13 @@
label: 'Critical path lite',
intent: Intent.Primary,
onclick: () =>
- runQuery(
- `INCLUDE PERFETTO MODULE sched.thread_executing_span;`,
- this.engine,
- ).then(() =>
- addDebugSliceTrack(
- this.engine,
- {
- sqlSource: `
+ this.engine
+ .query(`INCLUDE PERFETTO MODULE sched.thread_executing_span;`)
+ .then(() =>
+ addDebugSliceTrack(
+ this.engine,
+ {
+ sqlSource: `
SELECT
cr.id,
cr.utid,
@@ -347,26 +345,27 @@
JOIN thread USING(utid)
JOIN process USING(upid)
`,
- columns: sliceLiteColumnNames,
- },
- `${this.state?.thread?.name}`,
- sliceLiteColumns,
- sliceLiteColumnNames,
+ columns: sliceLiteColumnNames,
+ },
+ `${this.state?.thread?.name}`,
+ sliceLiteColumns,
+ sliceLiteColumnNames,
+ ),
),
- ),
}),
m(Button, {
label: 'Critical path',
intent: Intent.Primary,
onclick: () =>
- runQuery(
- `INCLUDE PERFETTO MODULE sched.thread_executing_span_with_slice;`,
- this.engine,
- ).then(() =>
- addDebugSliceTrack(
- this.engine,
- {
- sqlSource: `
+ this.engine
+ .query(
+ `INCLUDE PERFETTO MODULE sched.thread_executing_span_with_slice;`,
+ )
+ .then(() =>
+ addDebugSliceTrack(
+ this.engine,
+ {
+ sqlSource: `
SELECT cr.id, cr.utid, cr.ts, cr.dur, cr.name, cr.table_name
FROM
_thread_executing_span_critical_path_stack(
@@ -375,13 +374,13 @@
trace_bounds.end_ts - trace_bounds.start_ts) cr,
trace_bounds WHERE name IS NOT NULL
`,
- columns: sliceColumnNames,
- },
- `${this.state?.thread?.name}`,
- sliceColumns,
- sliceColumnNames,
+ columns: sliceColumnNames,
+ },
+ `${this.state?.thread?.name}`,
+ sliceColumns,
+ sliceColumnNames,
+ ),
),
- ),
}),
];
}
diff --git a/ui/src/frontend/tickmark_panel.ts b/ui/src/frontend/tickmark_panel.ts
index 90f3e4c..632052d 100644
--- a/ui/src/frontend/tickmark_panel.ts
+++ b/ui/src/frontend/tickmark_panel.ts
@@ -31,9 +31,6 @@
export class TickmarkPanel implements Panel {
readonly kind = 'panel';
readonly selectable = false;
- readonly trackKey = undefined;
-
- constructor(readonly key: string) {}
render(): m.Children {
return m('.tickbar');
diff --git a/ui/src/frontend/time_axis_panel.ts b/ui/src/frontend/time_axis_panel.ts
index af1f3df..16c22ed 100644
--- a/ui/src/frontend/time_axis_panel.ts
+++ b/ui/src/frontend/time_axis_panel.ts
@@ -32,9 +32,6 @@
export class TimeAxisPanel implements Panel {
readonly kind = 'panel';
readonly selectable = false;
- readonly trackKey = undefined;
-
- constructor(readonly key: string) {}
render(): m.Children {
return m('.time-axis-panel');
@@ -57,16 +54,16 @@
break;
case TimestampFormat.UTC:
const offsetDate = Time.toDate(
- globals.traceTime.utcOffset,
- globals.traceTime.realtimeOffset,
+ globals.traceContext.utcOffset,
+ globals.traceContext.realtimeOffset,
);
const dateStr = toISODateOnly(offsetDate);
ctx.fillText(`UTC ${dateStr}`, 6, 10);
break;
case TimestampFormat.TraceTz:
const offsetTzDate = Time.toDate(
- globals.traceTime.traceTzOffset,
- globals.traceTime.realtimeOffset,
+ globals.traceContext.traceTzOffset,
+ globals.traceContext.realtimeOffset,
);
const dateTzStr = toISODateOnly(offsetTzDate);
ctx.fillText(dateTzStr, 6, 10);
diff --git a/ui/src/frontend/time_selection_panel.ts b/ui/src/frontend/time_selection_panel.ts
index d77b095..66e401c 100644
--- a/ui/src/frontend/time_selection_panel.ts
+++ b/ui/src/frontend/time_selection_panel.ts
@@ -140,9 +140,6 @@
export class TimeSelectionPanel implements Panel {
readonly kind = 'panel';
readonly selectable = false;
- readonly trackKey = undefined;
-
- constructor(readonly key: string) {}
render(): m.Children {
return m('.time-selection-panel');
diff --git a/ui/src/frontend/trace_attrs.ts b/ui/src/frontend/trace_attrs.ts
index 61f991e..ff80c85 100644
--- a/ui/src/frontend/trace_attrs.ts
+++ b/ui/src/frontend/trace_attrs.ts
@@ -13,8 +13,8 @@
// limitations under the License.
import {assertExists} from '../base/logging';
-import {Actions} from '../common/actions';
import {TraceArrayBufferSource} from '../common/state';
+import {createPermalink} from './permalink';
import {showModal} from '../widgets/modal';
import {onClickCopy} from './clipboard';
@@ -74,7 +74,7 @@
);
if (result) {
globals.logging.logEvent('Trace Actions', 'Create permalink');
- globals.dispatch(Actions.createPermalink({isRecordingConfig: false}));
+ createPermalink();
}
}
diff --git a/ui/src/frontend/trace_info_page.ts b/ui/src/frontend/trace_info_page.ts
index c8d7547..87f270a 100644
--- a/ui/src/frontend/trace_info_page.ts
+++ b/ui/src/frontend/trace_info_page.ts
@@ -14,12 +14,51 @@
import m from 'mithril';
-import {QueryResponse, runQuery} from '../common/queries';
import {raf} from '../core/raf_scheduler';
import {Engine} from '../trace_processor/engine';
import {globals} from './globals';
import {createPage} from './pages';
+import {QueryResult, UNKNOWN} from '../trace_processor/query_result';
+
+function getEngine(name: string): Engine | undefined {
+ const currentEngine = globals.getCurrentEngine();
+ if (currentEngine === undefined) return undefined;
+ const engineId = currentEngine.id;
+ return globals.engines.get(engineId)?.getProxy(name);
+}
+
+/**
+ * Extracts and copies fields from a source object based on the keys present in
+ * a spec object, effectively creating a new object that includes only the
+ * fields that are present in the spec object.
+ *
+ * @template S - A type representing the spec object, a subset of T.
+ * @template T - A type representing the source object, a superset of S.
+ *
+ * @param {T} source - The source object containing the full set of properties.
+ * @param {S} spec - The specification object whose keys determine which fields
+ * should be extracted from the source object.
+ *
+ * @returns {S} A new object containing only the fields from the source object
+ * that are also present in the specification object.
+ *
+ * @example
+ * const fullObject = { foo: 123, bar: '123', baz: true };
+ * const spec = { foo: 0, bar: '' };
+ * const result = pickFields(fullObject, spec);
+ * console.log(result); // Output: { foo: 123, bar: '123' }
+ */
+function pickFields<S extends Record<string, unknown>, T extends S>(
+ source: T,
+ spec: S,
+): S {
+ const result: Record<string, unknown> = {};
+ for (const key of Object.keys(spec)) {
+ result[key] = source[key];
+ }
+ return result as S;
+}
interface StatsSectionAttrs {
title: string;
@@ -29,58 +68,71 @@
queryId: string;
}
-function getEngine(name: string): Engine | undefined {
- const currentEngine = globals.getCurrentEngine();
- if (currentEngine === undefined) return undefined;
- const engineId = currentEngine.id;
- return globals.engines.get(engineId)?.getProxy(name);
-}
+const statsSpec = {
+ name: UNKNOWN,
+ value: UNKNOWN,
+ description: UNKNOWN,
+ idx: UNKNOWN,
+ severity: UNKNOWN,
+ source: UNKNOWN,
+};
+
+type StatsSectionRow = typeof statsSpec;
// Generic class that generate a <section> + <table> from the stats table.
// The caller defines the query constraint, title and styling.
// Used for errors, data losses and debugging sections.
class StatsSection implements m.ClassComponent<StatsSectionAttrs> {
- private queryResponse?: QueryResponse;
+ private data?: StatsSectionRow[];
constructor({attrs}: m.CVnode<StatsSectionAttrs>) {
const engine = getEngine('StatsSection');
if (engine === undefined) {
return;
}
- const query = `select name, value, cast(ifnull(idx, '') as text) as idx,
- description, severity, source from stats
- where ${attrs.sqlConstraints || '1=1'}
- order by name, idx`;
- runQuery(query, engine).then((resp: QueryResponse) => {
- this.queryResponse = resp;
+ const query = `
+ select
+ name,
+ value,
+ cast(ifnull(idx, '') as text) as idx,
+ description,
+ severity,
+ source from stats
+ where ${attrs.sqlConstraints || '1=1'}
+ order by name, idx
+ `;
+
+ engine.query(query).then((resp) => {
+ const data: StatsSectionRow[] = [];
+ const it = resp.iter(statsSpec);
+ for (; it.valid(); it.next()) {
+ data.push(pickFields(it, statsSpec));
+ }
+ this.data = data;
+
raf.scheduleFullRedraw();
});
}
view({attrs}: m.CVnode<StatsSectionAttrs>) {
- const resp = this.queryResponse;
- if (resp === undefined || resp.totalRowCount === 0) {
+ const data = this.data;
+ if (data === undefined || data.length === 0) {
return m('');
}
- if (resp.error) throw new Error(resp.error);
- const tableRows = [];
- for (const row of resp.rows) {
+ const tableRows = data.map((row) => {
const help = [];
- // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
- if (row.description) {
+ if (Boolean(row.description)) {
help.push(m('i.material-icons.contextual-help', 'help_outline'));
}
const idx = row.idx !== '' ? `[${row.idx}]` : '';
- tableRows.push(
- m(
- 'tr',
- m('td.name', {title: row.description}, `${row.name}${idx}`, help),
- m('td', `${row.value}`),
- m('td', `${row.severity} (${row.source})`),
- ),
+ return m(
+ 'tr',
+ m('td.name', {title: row.description}, `${row.name}${idx}`, help),
+ m('td', `${row.value}`),
+ m('td', `${row.severity} (${row.source})`),
);
- }
+ });
return m(
`section${attrs.cssClass}`,
@@ -107,49 +159,63 @@
}
}
+const traceMetadataRowSpec = {name: UNKNOWN, value: UNKNOWN};
+
+type TraceMetadataRow = typeof traceMetadataRowSpec;
+
class TraceMetadata implements m.ClassComponent {
- private queryResponse?: QueryResponse;
+ private data?: TraceMetadataRow[];
constructor() {
const engine = getEngine('StatsSection');
if (engine === undefined) {
return;
}
- const query = `with
- metadata_with_priorities as (select
- name, ifnull(str_value, cast(int_value as text)) as value,
- name in (
- "trace_size_bytes",
- "cr-os-arch",
- "cr-os-name",
- "cr-os-version",
- "cr-physical-memory",
- "cr-product-version",
- "cr-hardware-class"
- ) as priority
- from metadata
- )
- select name, value
- from metadata_with_priorities
- order by priority desc, name`;
- runQuery(query, engine).then((resp: QueryResponse) => {
- this.queryResponse = resp;
+ const query = `
+ with metadata_with_priorities as (
+ select
+ name,
+ ifnull(str_value, cast(int_value as text)) as value,
+ name in (
+ "trace_size_bytes",
+ "cr-os-arch",
+ "cr-os-name",
+ "cr-os-version",
+ "cr-physical-memory",
+ "cr-product-version",
+ "cr-hardware-class"
+ ) as priority
+ from metadata
+ )
+ select
+ name,
+ value
+ from metadata_with_priorities
+ order by
+ priority desc,
+ name
+ `;
+
+ engine.query(query).then((resp: QueryResult) => {
+ const tableRows: TraceMetadataRow[] = [];
+ const it = resp.iter(traceMetadataRowSpec);
+ for (; it.valid(); it.next()) {
+ tableRows.push(pickFields(it, traceMetadataRowSpec));
+ }
+ this.data = tableRows;
raf.scheduleFullRedraw();
});
}
view() {
- const resp = this.queryResponse;
- if (resp === undefined || resp.totalRowCount === 0) {
+ const data = this.data;
+ if (data === undefined || data.length === 0) {
return m('');
}
- const tableRows = [];
- for (const row of resp.rows) {
- tableRows.push(
- m('tr', m('td.name', `${row.name}`), m('td', `${row.value}`)),
- );
- }
+ const tableRows = data.map((row) => {
+ return m('tr', m('td.name', `${row.name}`), m('td', `${row.value}`));
+ });
return m(
'section',
@@ -163,40 +229,68 @@
}
}
+const androidGameInterventionRowSpec = {
+ package_name: UNKNOWN,
+ uid: UNKNOWN,
+ current_mode: UNKNOWN,
+ standard_mode_supported: UNKNOWN,
+ standard_mode_downscale: UNKNOWN,
+ standard_mode_use_angle: UNKNOWN,
+ standard_mode_fps: UNKNOWN,
+ perf_mode_supported: UNKNOWN,
+ perf_mode_downscale: UNKNOWN,
+ perf_mode_use_angle: UNKNOWN,
+ perf_mode_fps: UNKNOWN,
+ battery_mode_supported: UNKNOWN,
+ battery_mode_downscale: UNKNOWN,
+ battery_mode_use_angle: UNKNOWN,
+ battery_mode_fps: UNKNOWN,
+};
+
+type AndroidGameInterventionRow = typeof androidGameInterventionRowSpec;
+
class AndroidGameInterventionList implements m.ClassComponent {
- private queryResponse?: QueryResponse;
+ private data?: AndroidGameInterventionRow[];
constructor() {
const engine = getEngine('StatsSection');
if (engine === undefined) {
return;
}
- const query = `select
- package_name,
- uid,
- current_mode,
- standard_mode_supported,
- standard_mode_downscale,
- standard_mode_use_angle,
- standard_mode_fps,
- perf_mode_supported,
- perf_mode_downscale,
- perf_mode_use_angle,
- perf_mode_fps,
- battery_mode_supported,
- battery_mode_downscale,
- battery_mode_use_angle,
- battery_mode_fps
- from android_game_intervention_list`;
- runQuery(query, engine).then((resp: QueryResponse) => {
- this.queryResponse = resp;
+ const query = `
+ select
+ package_name,
+ uid,
+ current_mode,
+ standard_mode_supported,
+ standard_mode_downscale,
+ standard_mode_use_angle,
+ standard_mode_fps,
+ perf_mode_supported,
+ perf_mode_downscale,
+ perf_mode_use_angle,
+ perf_mode_fps,
+ battery_mode_supported,
+ battery_mode_downscale,
+ battery_mode_use_angle,
+ battery_mode_fps
+ from android_game_intervention_list
+ `;
+
+ engine.query(query).then((resp) => {
+ const data: AndroidGameInterventionRow[] = [];
+ const it = resp.iter(androidGameInterventionRowSpec);
+ for (; it.valid(); it.next()) {
+ data.push(pickFields(it, androidGameInterventionRowSpec));
+ }
+ this.data = data;
raf.scheduleFullRedraw();
});
}
view() {
- const resp = this.queryResponse;
- if (resp === undefined || resp.totalRowCount === 0) {
+ const data = this.data;
+ if (data === undefined || data.length === 0) {
return m('');
}
@@ -204,7 +298,8 @@
let standardInterventions = '';
let perfInterventions = '';
let batteryInterventions = '';
- for (const row of resp.rows) {
+
+ for (const row of data) {
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
if (row.standard_mode_supported) {
standardInterventions = `angle=${row.standard_mode_use_angle},downscale=${row.standard_mode_downscale},fps=${row.standard_mode_fps}`;
@@ -268,46 +363,68 @@
}
}
-class PackageList implements m.ClassComponent {
- private queryResponse?: QueryResponse;
+const packageDataSpec = {
+ packageName: UNKNOWN,
+ versionCode: UNKNOWN,
+ debuggable: UNKNOWN,
+ profileableFromShell: UNKNOWN,
+};
+
+type PackageData = typeof packageDataSpec;
+
+class PackageListSection implements m.ClassComponent {
+ private packageList?: PackageData[];
constructor() {
const engine = getEngine('StatsSection');
if (engine === undefined) {
return;
}
- const query = `select package_name, version_code, debuggable,
- profileable_from_shell from package_list`;
- runQuery(query, engine).then((resp: QueryResponse) => {
- this.queryResponse = resp;
- raf.scheduleFullRedraw();
- });
+ this.loadData(engine);
+ }
+
+ private async loadData(engine: Engine): Promise<void> {
+ const query = `
+ select
+ package_name as packageName,
+ version_code as versionCode,
+ debuggable,
+ profileable_from_shell as profileableFromShell
+ from package_list
+ `;
+
+ const packageList: PackageData[] = [];
+ const result = await engine.query(query);
+ const it = result.iter(packageDataSpec);
+ for (; it.valid(); it.next()) {
+ packageList.push(pickFields(it, packageDataSpec));
+ }
+
+ this.packageList = packageList;
+ raf.scheduleFullRedraw();
}
view() {
- const resp = this.queryResponse;
- if (resp === undefined || resp.totalRowCount === 0) {
- return m('');
+ const packageList = this.packageList;
+ if (packageList === undefined || packageList.length === 0) {
+ return undefined;
}
- const tableRows = [];
- for (const row of resp.rows) {
- tableRows.push(
+ const tableRows = packageList.map((it) => {
+ return m(
+ 'tr',
+ m('td.name', `${it.packageName}`),
+ m('td', `${it.versionCode}`),
+ /* eslint-disable @typescript-eslint/strict-boolean-expressions */
m(
- 'tr',
- m('td.name', `${row.package_name}`),
- m('td', `${row.version_code}`),
- /* eslint-disable @typescript-eslint/strict-boolean-expressions */
- m(
- 'td',
- `${row.debuggable ? 'debuggable' : ''} ${
- row.profileable_from_shell ? 'profileable' : ''
- }`,
- ),
- /* eslint-enable */
+ 'td',
+ `${it.debuggable ? 'debuggable' : ''} ${
+ it.profileableFromShell ? 'profileable' : ''
+ }`,
),
+ /* eslint-enable */
);
- }
+ });
return m(
'section',
@@ -348,7 +465,7 @@
sqlConstraints: `severity = 'data_loss' and value > 0`,
}),
m(TraceMetadata),
- m(PackageList),
+ m(PackageListSection),
m(AndroidGameInterventionList),
m(StatsSection, {
queryId: 'info_all',
diff --git a/ui/src/frontend/trace_url_handler.ts b/ui/src/frontend/trace_url_handler.ts
index a50bed7..0e60724 100644
--- a/ui/src/frontend/trace_url_handler.ts
+++ b/ui/src/frontend/trace_url_handler.ts
@@ -18,6 +18,7 @@
import {tryGetTrace} from '../common/cache_manager';
import {showModal} from '../widgets/modal';
+import {loadPermalink} from './permalink';
import {loadAndroidBugToolInfo} from './android_bug_tool';
import {globals} from './globals';
import {Route, Router} from './router';
@@ -34,7 +35,7 @@
export function maybeOpenTraceFromRoute(route: Route) {
if (route.args.s) {
// /?s=xxxx for permalinks.
- globals.dispatch(Actions.loadPermalink({hash: route.args.s}));
+ loadPermalink(route.args.s);
return;
}
diff --git a/ui/src/frontend/track_group_panel.ts b/ui/src/frontend/track_group_panel.ts
index ec9a43a..10ca865 100644
--- a/ui/src/frontend/track_group_panel.ts
+++ b/ui/src/frontend/track_group_panel.ts
@@ -16,7 +16,7 @@
import {Icons} from '../base/semantic_icons';
import {Actions} from '../common/actions';
-import {getContainingTrackId, getLegacySelection} from '../common/state';
+import {getContainingGroupKey, getLegacySelection} from '../common/state';
import {TrackCacheEntry} from '../common/track_cache';
import {TrackTags} from '../public';
@@ -38,8 +38,7 @@
import {Button} from '../widgets/button';
interface Attrs {
- trackGroupId: string;
- key: string;
+ groupKey: string;
title: string;
collapsed: boolean;
trackFSM?: TrackCacheEntry;
@@ -50,16 +49,14 @@
export class TrackGroupPanel implements Panel {
readonly kind = 'panel';
readonly selectable = true;
- readonly key: string;
- readonly trackGroupId: string;
+ readonly groupKey: string;
constructor(private attrs: Attrs) {
- this.trackGroupId = attrs.trackGroupId;
- this.key = attrs.key;
+ this.groupKey = attrs.groupKey;
}
render(): m.Children {
- const {trackGroupId, title, labels, tags, collapsed, trackFSM} = this.attrs;
+ const {groupKey, title, labels, tags, collapsed, trackFSM} = this.attrs;
let name = title;
if (name[0] === '/') {
@@ -72,25 +69,25 @@
const searchIndex = globals.state.searchIndex;
if (searchIndex !== -1) {
const trackKey = globals.currentSearchResults.trackKeys[searchIndex];
- const parentTrackId = getContainingTrackId(globals.state, trackKey);
- if (parentTrackId === trackGroupId) {
+ const containingGroupKey = getContainingGroupKey(globals.state, trackKey);
+ if (containingGroupKey === groupKey) {
highlightClass = 'flash';
}
}
const selection = getLegacySelection(globals.state);
- const trackGroup = globals.state.trackGroups[trackGroupId];
+ const trackGroup = globals.state.trackGroups[groupKey];
let checkBox = Icons.BlankCheckbox;
if (selection !== null && selection.kind === 'AREA') {
const selectedArea = globals.state.areas[selection.areaId];
if (
- selectedArea.tracks.includes(trackGroupId) &&
+ selectedArea.tracks.includes(groupKey) &&
trackGroup.tracks.every((id) => selectedArea.tracks.includes(id))
) {
checkBox = Icons.Checkbox;
} else if (
- selectedArea.tracks.includes(trackGroupId) ||
+ selectedArea.tracks.includes(groupKey) ||
trackGroup.tracks.some((id) => selectedArea.tracks.includes(id))
) {
checkBox = Icons.IndeterminateCheckbox;
@@ -107,7 +104,7 @@
return m(
`.track-group-panel[collapsed=${collapsed}]`,
{
- id: 'track_' + trackGroupId,
+ id: 'track_' + groupKey,
oncreate: () => this.onupdate(),
onupdate: () => this.onupdate(),
},
@@ -118,7 +115,7 @@
if (e.defaultPrevented) return;
globals.dispatch(
Actions.toggleTrackGroupCollapsed({
- trackGroupId,
+ groupKey,
}),
),
e.stopPropagation();
@@ -143,7 +140,7 @@
onclick: (e: MouseEvent) => {
globals.dispatch(
Actions.toggleTrackSelection({
- id: trackGroupId,
+ key: groupKey,
isTrackGroup: true,
}),
);
@@ -180,7 +177,7 @@
if (!selection || selection.kind !== 'AREA') return;
const selectedArea = globals.state.areas[selection.areaId];
const selectedAreaDuration = selectedArea.end - selectedArea.start;
- if (selectedArea.tracks.includes(this.trackGroupId)) {
+ if (selectedArea.tracks.includes(this.groupKey)) {
ctx.fillStyle = 'rgba(131, 152, 230, 0.3)';
ctx.fillRect(
visibleTimeScale.timeToPx(selectedArea.start) + TRACK_SHELL_WIDTH,
diff --git a/ui/src/frontend/track_panel.ts b/ui/src/frontend/track_panel.ts
index 806b49c..e0aca05 100644
--- a/ui/src/frontend/track_panel.ts
+++ b/ui/src/frontend/track_panel.ts
@@ -199,7 +199,7 @@
onclick: (e: MouseEvent) => {
globals.dispatch(
Actions.toggleTrackSelection({
- id: attrs.trackKey,
+ key: attrs.trackKey,
isTrackGroup: false,
}),
);
@@ -423,10 +423,6 @@
constructor(private readonly attrs: TrackPanelAttrs) {}
- get key(): string {
- return this.attrs.trackKey;
- }
-
get trackKey(): string {
return this.attrs.trackKey;
}
diff --git a/ui/src/core_plugins/custom_sql_table_slices/index.ts b/ui/src/frontend/tracks/custom_sql_table_slice_track.ts
similarity index 82%
rename from ui/src/core_plugins/custom_sql_table_slices/index.ts
rename to ui/src/frontend/tracks/custom_sql_table_slice_track.ts
index 72f2075..cd31610 100644
--- a/ui/src/core_plugins/custom_sql_table_slices/index.ts
+++ b/ui/src/frontend/tracks/custom_sql_table_slice_track.ts
@@ -18,15 +18,11 @@
import {Actions} from '../../common/actions';
import {generateSqlWithInternalLayout} from '../../common/internal_layout_utils';
import {LegacySelection} from '../../common/state';
-import {OnSliceClickArgs} from '../../frontend/base_slice_track';
-import {GenericSliceDetailsTabConfigBase} from '../../frontend/generic_slice_details_tab';
-import {globals} from '../../frontend/globals';
-import {
- NamedSliceTrack,
- NamedSliceTrackTypes,
-} from '../../frontend/named_slice_track';
-import {NewTrackArgs} from '../../frontend/track';
-import {Plugin, PluginDescriptor} from '../../public';
+import {OnSliceClickArgs} from '../base_slice_track';
+import {GenericSliceDetailsTabConfigBase} from '../generic_slice_details_tab';
+import {globals} from '../globals';
+import {NamedSliceTrack, NamedSliceTrackTypes} from '../named_slice_track';
+import {NewTrackArgs} from '../track';
export interface CustomSqlImportConfig {
modules: string[];
@@ -94,10 +90,8 @@
});
await this.engine.query(sql);
return DisposableCallback.from(() => {
- if (this.engine.isAlive) {
- this.engine.query(`DROP VIEW ${this.tableName}`);
- config.dispose?.dispose();
- }
+ this.engine.tryQuery(`DROP VIEW ${this.tableName}`);
+ config.dispose?.dispose();
});
}
@@ -139,10 +133,3 @@
}
}
}
-
-class CustomSqlTrackPlugin implements Plugin {}
-
-export const plugin: PluginDescriptor = {
- pluginId: 'perfetto.CustomSqlTrack',
- plugin: CustomSqlTrackPlugin,
-};
diff --git a/ui/src/frontend/viewer_page.ts b/ui/src/frontend/viewer_page.ts
index b67f2e5..429c391 100644
--- a/ui/src/frontend/viewer_page.ts
+++ b/ui/src/frontend/viewer_page.ts
@@ -82,11 +82,11 @@
// Used to prevent global deselection if a pan/drag select occurred.
private keepCurrentSelection = false;
- private overviewTimelinePanel = new OverviewTimelinePanel('overview');
- private timeAxisPanel = new TimeAxisPanel('timeaxis');
- private timeSelectionPanel = new TimeSelectionPanel('timeselection');
- private notesPanel = new NotesPanel('notes');
- private tickmarkPanel = new TickmarkPanel('searchTickmarks');
+ private overviewTimelinePanel = new OverviewTimelinePanel();
+ private timeAxisPanel = new TimeAxisPanel();
+ private timeSelectionPanel = new TimeSelectionPanel();
+ private notesPanel = new NotesPanel();
+ private tickmarkPanel = new TickmarkPanel();
private readonly PAN_ZOOM_CONTENT_REF = 'pan-and-zoom-content';
@@ -128,7 +128,7 @@
currentY: number,
editing: boolean,
) => {
- const traceTime = globals.traceTime;
+ const traceTime = globals.traceContext;
const {visibleTimeScale} = timeline;
this.keepCurrentSelection = true;
if (editing) {
@@ -231,8 +231,7 @@
if (key) {
const trackBundle = this.resolveTrack(key);
headerPanel = new TrackGroupPanel({
- trackGroupId: group.id,
- key: `trackgroup-${group.id}`,
+ groupKey: group.key,
trackFSM: trackBundle.trackFSM,
labels: trackBundle.labels,
tags: trackBundle.tags,
@@ -241,8 +240,7 @@
});
} else {
headerPanel = new TrackGroupPanel({
- trackGroupId: group.id,
- key: `trackgroup-${group.id}`,
+ groupKey: group.key,
collapsed: group.collapsed,
title: group.name,
});
@@ -268,7 +266,6 @@
collapsed: group.collapsed,
childPanels: childTracks,
header: headerPanel,
- trackGroupId: group.id,
});
}
diff --git a/ui/src/plugins/dev.perfetto.AndroidCujs/index.ts b/ui/src/plugins/dev.perfetto.AndroidCujs/index.ts
index aaef484..b4f20ca 100644
--- a/ui/src/plugins/dev.perfetto.AndroidCujs/index.ts
+++ b/ui/src/plugins/dev.perfetto.AndroidCujs/index.ts
@@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {runQuery} from '../../common/queries';
import {addDebugSliceTrack} from '../../public';
import {Plugin, PluginContextTrace, PluginDescriptor} from '../../public';
@@ -170,7 +169,7 @@
id: 'dev.perfetto.AndroidCujs#PinJankCUJs',
name: 'Add track: Android jank CUJs',
callback: () => {
- runQuery(JANK_CUJ_QUERY_PRECONDITIONS, ctx.engine).then(() => {
+ ctx.engine.query(JANK_CUJ_QUERY_PRECONDITIONS).then(() => {
addDebugSliceTrack(
ctx.engine,
{
@@ -189,9 +188,9 @@
id: 'dev.perfetto.AndroidCujs#ListJankCUJs',
name: 'Run query: Android jank CUJs',
callback: () => {
- runQuery(JANK_CUJ_QUERY_PRECONDITIONS, ctx.engine).then(() =>
- ctx.tabs.openQuery(JANK_CUJ_QUERY, 'Android Jank CUJs'),
- );
+ ctx.engine
+ .query(JANK_CUJ_QUERY_PRECONDITIONS)
+ .then(() => ctx.tabs.openQuery(JANK_CUJ_QUERY, 'Android Jank CUJs'));
},
});
@@ -223,7 +222,7 @@
id: 'dev.perfetto.AndroidCujs#PinBlockingCalls',
name: 'Add track: Android Blocking calls during CUJs',
callback: () => {
- runQuery(JANK_CUJ_QUERY_PRECONDITIONS, ctx.engine).then(() =>
+ ctx.engine.query(JANK_CUJ_QUERY_PRECONDITIONS).then(() =>
addDebugSliceTrack(
ctx.engine,
{
diff --git a/ui/src/plugins/dev.perfetto.AndroidLongBatteryTracing/index.ts b/ui/src/plugins/dev.perfetto.AndroidLongBatteryTracing/index.ts
index 8f360b4..0666986 100644
--- a/ui/src/plugins/dev.perfetto.AndroidLongBatteryTracing/index.ts
+++ b/ui/src/plugins/dev.perfetto.AndroidLongBatteryTracing/index.ts
@@ -490,13 +490,25 @@
const HIGH_CPU = `
drop table if exists high_cpu;
create table high_cpu as
- with base as (
+ with cpu_cycles_args as (
select
- ts,
- EXTRACT_ARG(arg_set_id, 'cpu_cycles_per_uid_cluster.uid') as uid,
- EXTRACT_ARG(arg_set_id, 'cpu_cycles_per_uid_cluster.cluster') as cluster,
- sum(EXTRACT_ARG(arg_set_id, 'cpu_cycles_per_uid_cluster.time_millis')) as time_millis
- from track t join slice s on t.id = s.track_id
+ arg_set_id,
+ min(iif(key = 'cpu_cycles_per_uid_cluster.uid', int_value, null)) as uid,
+ min(iif(key = 'cpu_cycles_per_uid_cluster.cluster', int_value, null)) as cluster,
+ min(iif(key = 'cpu_cycles_per_uid_cluster.time_millis', int_value, null)) as time_millis
+ from args
+ where key in (
+ 'cpu_cycles_per_uid_cluster.uid',
+ 'cpu_cycles_per_uid_cluster.cluster',
+ 'cpu_cycles_per_uid_cluster.time_millis'
+ )
+ group by 1
+ ),
+ base as (
+ select ts, uid, cluster, sum(time_millis) as time_millis
+ from track t
+ join slice s on t.id = s.track_id
+ join cpu_cycles_args using (arg_set_id)
where t.name = 'Statsd Atoms'
and s.name = 'cpu_cycles_per_uid_cluster'
group by 1, 2, 3
@@ -521,8 +533,7 @@
with_ratio as (
select
ts,
- 100.0 * cpu_dur / dur as value,
- dur,
+ iif(dur is null, 0, 100.0 * cpu_dur / dur) as value,
case cluster when 0 then 'little' when 1 then 'mid' when 2 then 'big' else 'cl-' || cluster end as cluster,
case
when uid = 0 then 'AID_ROOT'
@@ -533,17 +544,9 @@
else pl.package_name
end as pkg
from with_windows left join app_package_list pl using(uid)
- where cpu_dur is not null
- ),
- with_zeros as (
- select ts, value, cluster, pkg
- from with_ratio
- union all
- select ts + dur as ts, 0 as value, cluster, pkg
- from with_ratio
)
select ts, sum(value) as value, cluster, pkg
- from with_zeros
+ from with_ratio
group by 1, 3, 4`;
const WAKEUPS = `
diff --git a/ui/src/plugins/dev.perfetto.AndroidPerfTraceCounters/index.ts b/ui/src/plugins/dev.perfetto.AndroidPerfTraceCounters/index.ts
index 2ec9223..b8feb57 100644
--- a/ui/src/plugins/dev.perfetto.AndroidPerfTraceCounters/index.ts
+++ b/ui/src/plugins/dev.perfetto.AndroidPerfTraceCounters/index.ts
@@ -14,7 +14,6 @@
import {Plugin, PluginContextTrace, PluginDescriptor} from '../../public';
import {addDebugSliceTrack} from '../../public';
-import {runQuery} from '../../common/queries';
const PERF_TRACE_COUNTERS_PRECONDITION = `
SELECT
@@ -27,8 +26,8 @@
class AndroidPerfTraceCounters implements Plugin {
async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
- const resp = await runQuery(PERF_TRACE_COUNTERS_PRECONDITION, ctx.engine);
- if (resp.totalRowCount === 0) return;
+ const resp = await ctx.engine.query(PERF_TRACE_COUNTERS_PRECONDITION);
+ if (resp.numRows() === 0) return;
ctx.registerCommand({
id: 'dev.perfetto.AndroidPerfTraceCounters#ThreadRuntimeIPC',
name: 'Add a track to show a thread runtime ipc',
diff --git a/ui/src/plugins/dev.perfetto.TimelineSync/index.ts b/ui/src/plugins/dev.perfetto.TimelineSync/index.ts
index 74f2554..7699bd4 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,22 @@
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;
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 +45,14 @@
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;
// 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 +63,160 @@
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);
}
onDeactivate(_: PluginContext) {
- this.disableTimelineSync();
+ this.disableTimelineSync(this._sessionId);
}
async onTraceLoad(ctx: PluginContextTrace) {
this._ctx = ctx;
+ this._traceLoadTime = Date.now();
+ this.advertise();
}
- 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();
+ children.push(
+ m(
+ 'option',
+ {value: clientId, selected: this._advertisedClients.size === 1},
+ `${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, clients: ClientId[]) {
+ if (sessionId === this._sessionId) return; // Already in this session id.
+ if (!clients.includes(this._clientId)) return; // Not for us.
+ 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 +227,7 @@
}
private scheduleViewportUpdateMessage() {
- if (!this._active) return;
+ if (!this.active) return;
const currentViewport = this.getCurrentViewportBounds();
if (
(!this._lastViewportBounds ||
@@ -116,6 +244,7 @@
this._chan?.postMessage({
perfettoSync: {
cmd: 'MSG_SET_VIEWPORT',
+ sessionId: this._sessionId,
viewportBounds,
},
clientId: this._clientId,
@@ -123,12 +252,33 @@
}
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':
+ this.enableTimelineSync(sync.sessionId, sync.clients);
+ 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 +286,7 @@
requestViewBounds: ViewportBounds,
source: ClientId,
) {
+ if (!this.active) return;
this.cacheSiblingInitialBoundIfNeeded(requestViewBounds, source);
const remappedViewport = this.remapViewportBounds(
requestViewBounds,
@@ -222,8 +373,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 +396,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/plugins/org.kernel.LinuxKernelDevices/index.ts b/ui/src/plugins/org.kernel.LinuxKernelDevices/index.ts
index eca39b4..9f627b8 100644
--- a/ui/src/plugins/org.kernel.LinuxKernelDevices/index.ts
+++ b/ui/src/plugins/org.kernel.LinuxKernelDevices/index.ts
@@ -20,7 +20,7 @@
STR_NULL,
} from '../../public';
import {ASYNC_SLICE_TRACK_KIND} from '../../core_plugins/async_slices';
-import {AsyncSliceTrackV2} from '../../core_plugins/async_slices/async_slice_track_v2';
+import {AsyncSliceTrack} from '../../core_plugins/async_slices/async_slice_track';
// This plugin renders visualizations of runtime power state transitions for
// Linux kernel devices (devices managed by Linux drivers).
@@ -50,7 +50,7 @@
trackIds: [trackId],
kind: ASYNC_SLICE_TRACK_KIND,
trackFactory: ({trackKey}) => {
- return new AsyncSliceTrackV2(
+ return new AsyncSliceTrack(
{
engine: ctx.engine,
trackKey,
diff --git a/ui/src/protos/index.ts b/ui/src/protos/index.ts
index 378fcfb..44270c2 100644
--- a/ui/src/protos/index.ts
+++ b/ui/src/protos/index.ts
@@ -75,9 +75,7 @@
import StatCounters = protos.perfetto.protos.SysStatsConfig.StatCounters;
import StatusResult = protos.perfetto.protos.StatusResult;
import SysStatsConfig = protos.perfetto.protos.SysStatsConfig;
-import Trace = protos.perfetto.protos.Trace;
import TraceConfig = protos.perfetto.protos.TraceConfig;
-import TracePacket = protos.perfetto.protos.TracePacket;
import TraceProcessorApiVersion = protos.perfetto.protos.TraceProcessorApiVersion;
import TraceProcessorRpc = protos.perfetto.protos.TraceProcessorRpc;
import TraceProcessorRpcStream = protos.perfetto.protos.TraceProcessorRpcStream;
@@ -144,9 +142,7 @@
StatCounters,
StatusResult,
SysStatsConfig,
- Trace,
TraceConfig,
- TracePacket,
TraceProcessorApiVersion,
TraceProcessorRpc,
TraceProcessorRpcStream,
diff --git a/ui/src/public/index.ts b/ui/src/public/index.ts
index 7fb2e44..9e540fd 100644
--- a/ui/src/public/index.ts
+++ b/ui/src/public/index.ts
@@ -22,6 +22,8 @@
import {PanelSize} from '../frontend/panel';
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 {
@@ -34,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 {
@@ -433,10 +436,9 @@
// Create a store mounted over the top of this plugin's persistent state.
mountStore<T>(migrate: Migrate<T>): Store<T>;
- trace: {
- // A span representing the start and end time of the trace
- readonly span: Span<time, duration>;
- };
+ trace: TraceContext;
+
+ prompt(text: string, options?: PromptOption[]): Promise<string>;
}
export interface Plugin {
diff --git a/ui/src/trace_processor/engine.ts b/ui/src/trace_processor/engine.ts
index 90901c5..d0f70af 100644
--- a/ui/src/trace_processor/engine.ts
+++ b/ui/src/trace_processor/engine.ts
@@ -14,7 +14,6 @@
import {defer, Deferred} from '../base/deferred';
import {assertExists, assertTrue} from '../base/logging';
-import {duration, Span, Time, time, TimeSpan} from '../base/time';
import {
ComputeMetricArgs,
ComputeMetricResult,
@@ -31,17 +30,14 @@
import {ProtoRingBuffer} from './proto_ring_buffer';
import {
createQueryResult,
- LONG,
- LONG_NULL,
- NUM,
QueryError,
QueryResult,
- STR,
WritableQueryResult,
} from './query_result';
import TPM = TraceProcessorRpc.TraceProcessorMethod;
import {Disposable} from '../base/disposable';
+import {Result} from '../base/utils';
export interface LoadingTracker {
beginLoading(): void;
@@ -67,16 +63,43 @@
}
export interface Engine {
- execute(sqlQuery: string, tag?: string): Promise<QueryResult> & QueryResult;
- query(sqlQuery: string, tag?: string): Promise<QueryResult>;
- getCpus(): Promise<number[]>;
- getNumberOfGpus(): Promise<number>;
- getTracingMetadataTimeBounds(): Promise<Span<time, duration>>;
+ /**
+ * Execute a query against the database, returning a promise that resolves
+ * when the query has completed but rejected when the query fails for whatever
+ * reason. On success, the promise will only resolve once all the resulting
+ * rows have been received.
+ *
+ * The promise will be rejected if the query fails.
+ *
+ * @param sql The query to execute.
+ * @param tag An optional tag used to trace the origin of the query.
+ */
+ query(sql: string, tag?: string): Promise<QueryResult>;
+
+ /**
+ * Execute a query against the database, returning a promise that resolves
+ * when the query has completed or failed. The promise will never get
+ * rejected, it will always successfully resolve. Use the returned wrapper
+ * object to determine whether the query completed successfully.
+ *
+ * The promise will only resolve once all the resulting rows have been
+ * received.
+ *
+ * @param sql The query to execute.
+ * @param tag An optional tag used to trace the origin of the query.
+ */
+ tryQuery(sql: string, tag?: string): Promise<Result<QueryResult, Error>>;
+
+ /**
+ * Execute one or more metric and get the result.
+ *
+ * @param metrics The metrics to run.
+ * @param format The format of the response.
+ */
computeMetric(
metrics: string[],
format: 'json' | 'prototext' | 'proto',
): Promise<string | Uint8Array>;
- readonly isAlive: boolean;
}
// Abstract interface of a trace proccessor.
@@ -92,8 +115,6 @@
// 2. Call onRpcResponseBytes() when response data is received.
export abstract class EngineBase implements Engine {
abstract readonly id: string;
- private _cpus?: number[];
- private _numGpus?: number;
private loadingTracker: LoadingTracker;
private txSeqId = 0;
private rxSeqId = 0;
@@ -106,7 +127,6 @@
private pendingComputeMetrics = new Array<Deferred<string | Uint8Array>>();
private pendingReadMetatrace?: Deferred<DisableAndReadMetatraceResult>;
private _isMetatracingEnabled = false;
- readonly isAlive = false;
constructor(tracker?: LoadingTracker) {
this.loadingTracker = tracker ? tracker : new NullLoadingTracker();
@@ -360,7 +380,10 @@
//
// Optional |tag| (usually a component name) can be provided to allow
// attributing trace processor workload to different UI components.
- execute(sqlQuery: string, tag?: string): Promise<QueryResult> & QueryResult {
+ private streamingQuery(
+ sqlQuery: string,
+ tag?: string,
+ ): Promise<QueryResult> & QueryResult {
const rpc = TraceProcessorRpc.create();
rpc.request = TPM.TPM_QUERY_STREAMING;
rpc.queryArgs = new QueryArgs();
@@ -376,13 +399,13 @@
return result;
}
- // Wraps .execute(), captures errors and re-throws with current stack.
+ // Wraps .streamingQuery(), captures errors and re-throws with current stack.
//
- // Note: This function is less flexible that .execute() as it only returns a
+ // Note: This function is less flexible than .execute() as it only returns a
// promise which must be unwrapped before the QueryResult may be accessed.
async query(sqlQuery: string, tag?: string): Promise<QueryResult> {
try {
- return await this.execute(sqlQuery, tag);
+ return await this.streamingQuery(sqlQuery, tag);
} catch (e) {
// Replace the error's stack trace with the one from here
// Note: It seems only V8 can trace the stack up the promise chain, so its
@@ -394,6 +417,19 @@
}
}
+ async tryQuery(
+ sql: string,
+ tag?: string,
+ ): Promise<Result<QueryResult, Error>> {
+ try {
+ const result = await this.query(sql, tag);
+ return {success: true, result};
+ } catch (error: unknown) {
+ // We know we only throw Error type objects so we can type assert safely
+ return {success: false, error: error as Error};
+ }
+ }
+
isMetatracingEnabled(): boolean {
return this._isMetatracingEnabled;
}
@@ -438,79 +474,6 @@
this.rpcSendRequestBytes(buf);
}
- // TODO(hjd): When streaming must invalidate this somehow.
- async getCpus(): Promise<number[]> {
- if (!this._cpus) {
- const cpus = [];
- const queryRes = await this.query(
- 'select distinct(cpu) as cpu from sched order by cpu;',
- );
- for (const it = queryRes.iter({cpu: NUM}); it.valid(); it.next()) {
- cpus.push(it.cpu);
- }
- this._cpus = cpus;
- }
- return this._cpus;
- }
-
- async getNumberOfGpus(): Promise<number> {
- // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
- if (!this._numGpus) {
- const result = await this.query(`
- select count(distinct(gpu_id)) as gpuCount
- from gpu_counter_track
- where name = 'gpufreq';
- `);
- this._numGpus = result.firstRow({gpuCount: NUM}).gpuCount;
- }
- return this._numGpus;
- }
-
- // TODO: This should live in code that's more specific to chrome, instead of
- // in engine.
- async getNumberOfProcesses(): Promise<number> {
- const result = await this.query('select count(*) as cnt from process;');
- return result.firstRow({cnt: NUM}).cnt;
- }
-
- async getTraceTimeBounds(): Promise<Span<time, duration>> {
- const result = await this.query(
- `select start_ts as startTs, end_ts as endTs from trace_bounds`,
- );
- const bounds = result.firstRow({
- startTs: LONG,
- endTs: LONG,
- });
- return new TimeSpan(
- Time.fromRaw(bounds.startTs),
- Time.fromRaw(bounds.endTs),
- );
- }
-
- async getTracingMetadataTimeBounds(): Promise<Span<time, duration>> {
- const queryRes = await this.query(`select
- name,
- int_value as intValue
- from metadata
- where name = 'tracing_started_ns' or name = 'tracing_disabled_ns'
- or name = 'all_data_source_started_ns'`);
- let startBound = Time.MIN;
- let endBound = Time.MAX;
- const it = queryRes.iter({name: STR, intValue: LONG_NULL});
- for (; it.valid(); it.next()) {
- const columnName = it.name;
- const timestamp = it.intValue;
- if (timestamp === null) continue;
- if (columnName === 'tracing_disabled_ns') {
- endBound = Time.min(endBound, Time.fromRaw(timestamp));
- } else {
- startBound = Time.max(startBound, Time.fromRaw(timestamp));
- }
- }
-
- return new TimeSpan(startBound, endBound);
- }
-
getProxy(tag: string): EngineProxy {
return new EngineProxy(this, tag);
}
@@ -522,58 +485,42 @@
private tag: string;
private _isAlive: boolean;
- get isAlive(): boolean {
- return this._isAlive;
- }
-
constructor(engine: EngineBase, tag: string) {
this.engine = engine;
this.tag = tag;
this._isAlive = true;
}
- execute(query: string, tag?: string): Promise<QueryResult> & QueryResult {
- if (!this.isAlive) {
- throw new Error(`EngineProxy ${this.tag} was disposed.`);
- }
- return this.engine.execute(query, tag || this.tag);
- }
-
async query(query: string, tag?: string): Promise<QueryResult> {
- if (!this.isAlive) {
+ if (!this._isAlive) {
throw new Error(`EngineProxy ${this.tag} was disposed.`);
}
return await this.engine.query(query, tag);
}
+ async tryQuery(
+ query: string,
+ tag?: string,
+ ): Promise<Result<QueryResult, Error>> {
+ if (!this._isAlive) {
+ return {
+ success: false,
+ error: new Error(`EngineProxy ${this.tag} was disposed.`),
+ };
+ }
+ return await this.engine.tryQuery(query, tag);
+ }
+
async computeMetric(
metrics: string[],
format: 'json' | 'prototext' | 'proto',
): Promise<string | Uint8Array> {
- if (!this.isAlive) {
+ if (!this._isAlive) {
return Promise.reject(new Error(`EngineProxy ${this.tag} was disposed.`));
}
return this.engine.computeMetric(metrics, format);
}
- async getCpus(): Promise<number[]> {
- if (!this.isAlive) {
- return Promise.reject(new Error(`EngineProxy ${this.tag} was disposed.`));
- }
- return this.engine.getCpus();
- }
-
- async getNumberOfGpus(): Promise<number> {
- if (!this.isAlive) {
- return Promise.reject(new Error(`EngineProxy ${this.tag} was disposed.`));
- }
- return this.engine.getNumberOfGpus();
- }
-
- async getTracingMetadataTimeBounds(): Promise<Span<time, bigint>> {
- return this.engine.getTracingMetadataTimeBounds();
- }
-
get engineId(): string {
return this.engine.id;
}
diff --git a/ui/src/trace_processor/query_result.ts b/ui/src/trace_processor/query_result.ts
index ac48079..a29f4dc 100644
--- a/ui/src/trace_processor/query_result.ts
+++ b/ui/src/trace_processor/query_result.ts
@@ -57,6 +57,7 @@
import {utf8Decode} from '../base/string_utils';
import {Duration, duration, Time, time} from '../base/time';
+export const UNKNOWN: ColumnType = null;
export const NUM = 0;
export const STR = 'str';
export const NUM_NULL: number | null = 1;
@@ -203,6 +204,8 @@
return 'LONG';
case LONG_NULL:
return 'LONG_NULL';
+ case UNKNOWN:
+ return 'UNKNOWN';
default:
return `INVALID(${t})`;
}
@@ -215,21 +218,25 @@
expected === NUM_NULL ||
expected === STR_NULL ||
expected === BLOB_NULL ||
- expected === LONG_NULL
+ expected === LONG_NULL ||
+ expected === UNKNOWN
);
case CellType.CELL_VARINT:
return (
expected === NUM ||
expected === NUM_NULL ||
expected === LONG ||
- expected === LONG_NULL
+ expected === LONG_NULL ||
+ expected === UNKNOWN
);
case CellType.CELL_FLOAT64:
- return expected === NUM || expected === NUM_NULL;
+ return expected === NUM || expected === NUM_NULL || expected === UNKNOWN;
case CellType.CELL_STRING:
- return expected === STR || expected === STR_NULL;
+ return expected === STR || expected === STR_NULL || expected === UNKNOWN;
case CellType.CELL_BLOB:
- return expected === BLOB || expected === BLOB_NULL;
+ return (
+ expected === BLOB || expected === BLOB_NULL || expected === UNKNOWN
+ );
default:
throw new Error(`Unknown CellType ${actual}`);
}
@@ -285,11 +292,10 @@
// If true all rows have been fetched. Calling iter() will iterate through the
// last row. If false, iter() will return an iterator which might iterate
// through some rows (or none) but will surely not reach the end.
-
isComplete(): boolean;
// Returns a promise that is resolved only when all rows (i.e. all batches)
- // have been fetched. The promise return value is always the object iself.
+ // have been fetched. The promise return value is always the object itself.
waitAllRows(): Promise<QueryResult>;
// Returns a promise that is resolved when either:
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
diff --git a/ui/src/widgets/vega_view.ts b/ui/src/widgets/vega_view.ts
index 39be606..89185c3 100644
--- a/ui/src/widgets/vega_view.ts
+++ b/ui/src/widgets/vega_view.ts
@@ -75,31 +75,32 @@
if (this.engine === undefined) {
return '';
}
- const result = this.engine.execute(uri);
try {
- await result.waitAllRows();
+ const result = await this.engine.query(uri);
+ const columns = result.columns();
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const rows: any[] = [];
+ for (const it = result.iter({}); it.valid(); it.next()) {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const row: any = {};
+ for (const name of columns) {
+ let value = it.get(name);
+ if (typeof value === 'bigint') {
+ value = Number(value);
+ }
+ row[name] = value;
+ }
+ rows.push(row);
+ }
+ return JSON.stringify(rows);
} catch (e) {
if (e instanceof QueryError) {
- console.error(result.error());
+ console.error(e);
return '';
+ } else {
+ throw e;
}
}
- const columns = result.columns();
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- const rows: any[] = [];
- for (const it = result.iter({}); it.valid(); it.next()) {
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- const row: any = {};
- for (const name of columns) {
- let value = it.get(name);
- if (typeof value === 'bigint') {
- value = Number(value);
- }
- row[name] = value;
- }
- rows.push(row);
- }
- return JSON.stringify(rows);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any