Merge "Don't crash when frame names are null" into main
diff --git a/Android.bp b/Android.bp
index bb7050d..9bfdcd7 100644
--- a/Android.bp
+++ b/Android.bp
@@ -494,6 +494,7 @@
":perfetto_include_perfetto_ext_base_base",
":perfetto_include_perfetto_ext_base_version",
":perfetto_include_perfetto_ext_ipc_ipc",
+ ":perfetto_include_perfetto_ext_protozero_protozero",
":perfetto_include_perfetto_ext_traced_sys_stats_counters",
":perfetto_include_perfetto_ext_traced_traced",
":perfetto_include_perfetto_ext_tracing_core_core",
@@ -1358,6 +1359,7 @@
":perfetto_include_perfetto_ext_base_base",
":perfetto_include_perfetto_ext_base_version",
":perfetto_include_perfetto_ext_ipc_ipc",
+ ":perfetto_include_perfetto_ext_protozero_protozero",
":perfetto_include_perfetto_ext_traced_sys_stats_counters",
":perfetto_include_perfetto_ext_traced_traced",
":perfetto_include_perfetto_ext_tracing_core_core",
@@ -1656,6 +1658,7 @@
":perfetto_include_perfetto_ext_base_base",
":perfetto_include_perfetto_ext_base_version",
":perfetto_include_perfetto_ext_ipc_ipc",
+ ":perfetto_include_perfetto_ext_protozero_protozero",
":perfetto_include_perfetto_ext_traced_sys_stats_counters",
":perfetto_include_perfetto_ext_traced_traced",
":perfetto_include_perfetto_ext_tracing_core_core",
@@ -2025,6 +2028,11 @@
name: "perfetto_include_perfetto_ext_ipc_ipc",
}
+// GN: //include/perfetto/ext/protozero:protozero
+filegroup {
+ name: "perfetto_include_perfetto_ext_protozero_protozero",
+}
+
// GN: //include/perfetto/ext/trace_processor:demangle
filegroup {
name: "perfetto_include_perfetto_ext_trace_processor_demangle",
@@ -2155,6 +2163,7 @@
":perfetto_include_perfetto_ext_base_base",
":perfetto_include_perfetto_ext_base_version",
":perfetto_include_perfetto_ext_ipc_ipc",
+ ":perfetto_include_perfetto_ext_protozero_protozero",
":perfetto_include_perfetto_ext_trace_processor_demangle",
":perfetto_include_perfetto_ext_trace_processor_export_json",
":perfetto_include_perfetto_ext_trace_processor_importers_memory_tracker_memory_tracker",
@@ -2369,6 +2378,7 @@
":perfetto_src_trace_processor_storage_storage",
":perfetto_src_trace_processor_tables_tables",
":perfetto_src_trace_processor_types_types",
+ ":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_glob",
@@ -2382,12 +2392,9 @@
":perfetto_src_trace_processor_util_protozero_to_text",
":perfetto_src_trace_processor_util_regex",
":perfetto_src_trace_processor_util_sql_argument",
- ":perfetto_src_trace_processor_util_stack_traces_util",
":perfetto_src_trace_processor_util_stdlib",
":perfetto_src_trace_processor_util_util",
":perfetto_src_trace_processor_util_zip_reader",
- ":perfetto_src_trace_redaction_integrationtests",
- ":perfetto_src_trace_redaction_trace_redaction",
":perfetto_src_traced_probes_android_game_intervention_list_android_game_intervention_list",
":perfetto_src_traced_probes_android_log_android_log",
":perfetto_src_traced_probes_android_system_property_android_system_property",
@@ -9828,6 +9835,7 @@
"src/base/paged_memory.cc",
"src/base/periodic_task.cc",
"src/base/pipe.cc",
+ "src/base/scoped_mmap.cc",
"src/base/status.cc",
"src/base/string_splitter.cc",
"src/base/string_utils.cc",
@@ -9917,6 +9925,7 @@
"src/base/paged_memory_unittest.cc",
"src/base/periodic_task_unittest.cc",
"src/base/scoped_file_unittest.cc",
+ "src/base/scoped_mmap_unittest.cc",
"src/base/small_vector_unittest.cc",
"src/base/status_or_unittest.cc",
"src/base/status_unittest.cc",
@@ -10542,8 +10551,6 @@
"src/profiling/symbolizer/filesystem_posix.cc",
"src/profiling/symbolizer/filesystem_windows.cc",
"src/profiling/symbolizer/local_symbolizer.cc",
- "src/profiling/symbolizer/scoped_read_mmap_posix.cc",
- "src/profiling/symbolizer/scoped_read_mmap_windows.cc",
"src/profiling/symbolizer/subprocess_posix.cc",
"src/profiling/symbolizer/subprocess_windows.cc",
"src/profiling/symbolizer/symbolizer.cc",
@@ -11100,6 +11107,7 @@
"src/trace_processor/importers/common/event_tracker.cc",
"src/trace_processor/importers/common/flow_tracker.cc",
"src/trace_processor/importers/common/global_args_tracker.cc",
+ "src/trace_processor/importers/common/mapping_tracker.cc",
"src/trace_processor/importers/common/metadata_tracker.cc",
"src/trace_processor/importers/common/process_tracker.cc",
"src/trace_processor/importers/common/slice_tracker.cc",
@@ -11108,6 +11116,7 @@
"src/trace_processor/importers/common/system_info_tracker.cc",
"src/trace_processor/importers/common/trace_parser.cc",
"src/trace_processor/importers/common/track_tracker.cc",
+ "src/trace_processor/importers/common/virtual_memory_mapping.cc",
],
}
@@ -12041,6 +12050,7 @@
"src/trace_processor/perfetto_sql/stdlib/intervals/overlap.sql",
"src/trace_processor/perfetto_sql/stdlib/linux/cpu_idle.sql",
"src/trace_processor/perfetto_sql/stdlib/pkvm/hypervisor.sql",
+ "src/trace_processor/perfetto_sql/stdlib/prelude/casts.sql",
"src/trace_processor/perfetto_sql/stdlib/prelude/slices.sql",
"src/trace_processor/perfetto_sql/stdlib/prelude/trace_bounds.sql",
"src/trace_processor/perfetto_sql/stdlib/sched/states.sql",
@@ -12279,6 +12289,7 @@
srcs: [
"src/trace_processor/forwarding_trace_parser_unittest.cc",
"src/trace_processor/ref_counted_unittest.cc",
+ "src/trace_processor/trace_blob_unittest.cc",
],
}
@@ -12305,6 +12316,14 @@
name: "perfetto_src_trace_processor_unittests",
}
+// GN: //src/trace_processor/util:build_id
+filegroup {
+ name: "perfetto_src_trace_processor_util_build_id",
+ srcs: [
+ "src/trace_processor/util/build_id.cc",
+ ],
+}
+
// GN: //src/trace_processor/util:bump_allocator
filegroup {
name: "perfetto_src_trace_processor_util_bump_allocator",
@@ -12405,14 +12424,6 @@
],
}
-// GN: //src/trace_processor/util:stack_traces_util
-filegroup {
- name: "perfetto_src_trace_processor_util_stack_traces_util",
- srcs: [
- "src/trace_processor/util/stack_traces_util.cc",
- ],
-}
-
// GN: //src/trace_processor/util:stdlib
filegroup {
name: "perfetto_src_trace_processor_util_stdlib",
@@ -12450,20 +12461,14 @@
],
}
-// GN: //src/trace_redaction:integrationtests
-filegroup {
- name: "perfetto_src_trace_redaction_integrationtests",
- srcs: [
- "src/trace_redaction/trace_redactor_integrationtest.cc",
- ],
-}
-
// GN: //src/trace_redaction:trace_redaction
filegroup {
name: "perfetto_src_trace_redaction_trace_redaction",
srcs: [
"src/trace_redaction/find_package_uid.cc",
+ "src/trace_redaction/populate_allow_lists.cc",
"src/trace_redaction/prune_package_list.cc",
+ "src/trace_redaction/scrub_trace_packet.cc",
"src/trace_redaction/trace_redaction_framework.cc",
"src/trace_redaction/trace_redactor.cc",
],
@@ -12475,6 +12480,7 @@
srcs: [
"src/trace_redaction/find_package_uid_unittest.cc",
"src/trace_redaction/prune_package_list_unittest.cc",
+ "src/trace_redaction/scrub_trace_packet_unittest.cc",
],
}
@@ -13671,6 +13677,7 @@
":perfetto_include_perfetto_ext_base_threading_threading",
":perfetto_include_perfetto_ext_base_version",
":perfetto_include_perfetto_ext_ipc_ipc",
+ ":perfetto_include_perfetto_ext_protozero_protozero",
":perfetto_include_perfetto_ext_trace_processor_demangle",
":perfetto_include_perfetto_ext_trace_processor_export_json",
":perfetto_include_perfetto_ext_trace_processor_importers_memory_tracker_memory_tracker",
@@ -13944,6 +13951,7 @@
":perfetto_src_trace_processor_types_types",
":perfetto_src_trace_processor_types_unittests",
":perfetto_src_trace_processor_unittests",
+ ":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_glob",
@@ -13957,7 +13965,6 @@
":perfetto_src_trace_processor_util_protozero_to_text",
":perfetto_src_trace_processor_util_regex",
":perfetto_src_trace_processor_util_sql_argument",
- ":perfetto_src_trace_processor_util_stack_traces_util",
":perfetto_src_trace_processor_util_stdlib",
":perfetto_src_trace_processor_util_unittests",
":perfetto_src_trace_processor_util_util",
@@ -14222,6 +14229,7 @@
":perfetto_include_perfetto_ext_base_base",
":perfetto_include_perfetto_ext_base_version",
":perfetto_include_perfetto_ext_ipc_ipc",
+ ":perfetto_include_perfetto_ext_protozero_protozero",
":perfetto_include_perfetto_ext_traced_sys_stats_counters",
":perfetto_include_perfetto_ext_traced_traced",
":perfetto_include_perfetto_ext_tracing_core_core",
@@ -14537,6 +14545,7 @@
":perfetto_include_perfetto_ext_base_base",
":perfetto_include_perfetto_ext_base_http_http",
":perfetto_include_perfetto_ext_base_version",
+ ":perfetto_include_perfetto_ext_protozero_protozero",
":perfetto_include_perfetto_ext_trace_processor_demangle",
":perfetto_include_perfetto_ext_trace_processor_export_json",
":perfetto_include_perfetto_ext_trace_processor_importers_memory_tracker_memory_tracker",
@@ -14645,6 +14654,7 @@
":perfetto_src_trace_processor_storage_storage",
":perfetto_src_trace_processor_tables_tables",
":perfetto_src_trace_processor_types_types",
+ ":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_glob",
@@ -14658,7 +14668,6 @@
":perfetto_src_trace_processor_util_protozero_to_text",
":perfetto_src_trace_processor_util_regex",
":perfetto_src_trace_processor_util_sql_argument",
- ":perfetto_src_trace_processor_util_stack_traces_util",
":perfetto_src_trace_processor_util_stdlib",
":perfetto_src_trace_processor_util_util",
":perfetto_src_trace_processor_util_zip_reader",
@@ -14775,6 +14784,7 @@
":perfetto_include_perfetto_base_base",
":perfetto_include_perfetto_ext_base_base",
":perfetto_include_perfetto_ext_base_version",
+ ":perfetto_include_perfetto_ext_protozero_protozero",
":perfetto_include_perfetto_ext_trace_processor_demangle",
":perfetto_include_perfetto_ext_trace_processor_export_json",
":perfetto_include_perfetto_ext_trace_processor_importers_memory_tracker_memory_tracker",
@@ -14878,6 +14888,7 @@
":perfetto_src_trace_processor_storage_storage",
":perfetto_src_trace_processor_tables_tables",
":perfetto_src_trace_processor_types_types",
+ ":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_glob",
@@ -14891,7 +14902,6 @@
":perfetto_src_trace_processor_util_protozero_to_text",
":perfetto_src_trace_processor_util_regex",
":perfetto_src_trace_processor_util_sql_argument",
- ":perfetto_src_trace_processor_util_stack_traces_util",
":perfetto_src_trace_processor_util_stdlib",
":perfetto_src_trace_processor_util_util",
":perfetto_src_trace_processor_util_zip_reader",
diff --git a/BUILD b/BUILD
index 1e7c962..ff93699 100644
--- a/BUILD
+++ b/BUILD
@@ -266,6 +266,7 @@
":src_trace_processor_tables_tables",
":src_trace_processor_tables_tables_python",
":src_trace_processor_types_types",
+ ":src_trace_processor_util_build_id",
":src_trace_processor_util_bump_allocator",
":src_trace_processor_util_descriptors",
":src_trace_processor_util_glob",
@@ -279,7 +280,6 @@
":src_trace_processor_util_protozero_to_text",
":src_trace_processor_util_regex",
":src_trace_processor_util_sql_argument",
- ":src_trace_processor_util_stack_traces_util",
":src_trace_processor_util_stdlib",
":src_trace_processor_util_util",
":src_trace_processor_util_zip_reader",
@@ -287,6 +287,7 @@
hdrs = [
":include_perfetto_base_base",
":include_perfetto_ext_base_base",
+ ":include_perfetto_ext_protozero_protozero",
":include_perfetto_ext_trace_processor_demangle",
":include_perfetto_ext_trace_processor_export_json",
":include_perfetto_ext_trace_processor_importers_memory_tracker_memory_tracker",
@@ -474,6 +475,7 @@
":include_perfetto_base_base",
":include_perfetto_ext_base_base",
":include_perfetto_ext_ipc_ipc",
+ ":include_perfetto_ext_protozero_protozero",
":include_perfetto_ext_traced_sys_stats_counters",
":include_perfetto_ext_traced_traced",
":include_perfetto_ext_tracing_core_core",
@@ -599,6 +601,7 @@
"include/perfetto/ext/base/pipe.h",
"include/perfetto/ext/base/platform.h",
"include/perfetto/ext/base/scoped_file.h",
+ "include/perfetto/ext/base/scoped_mmap.h",
"include/perfetto/ext/base/small_set.h",
"include/perfetto/ext/base/small_vector.h",
"include/perfetto/ext/base/status_or.h",
@@ -650,6 +653,14 @@
],
)
+# GN target: //include/perfetto/ext/protozero:protozero
+perfetto_filegroup(
+ name = "include_perfetto_ext_protozero_protozero",
+ srcs = [
+ "include/perfetto/ext/protozero/proto_ring_buffer.h",
+ ],
+)
+
# GN target: //include/perfetto/ext/trace_processor/importers/memory_tracker:memory_tracker
perfetto_filegroup(
name = "include_perfetto_ext_trace_processor_importers_memory_tracker_memory_tracker",
@@ -1031,6 +1042,7 @@
"src/base/paged_memory.cc",
"src/base/periodic_task.cc",
"src/base/pipe.cc",
+ "src/base/scoped_mmap.cc",
"src/base/status.cc",
"src/base/string_splitter.cc",
"src/base/string_utils.cc",
@@ -1232,9 +1244,6 @@
"src/profiling/symbolizer/filesystem_windows.cc",
"src/profiling/symbolizer/local_symbolizer.cc",
"src/profiling/symbolizer/local_symbolizer.h",
- "src/profiling/symbolizer/scoped_read_mmap.h",
- "src/profiling/symbolizer/scoped_read_mmap_posix.cc",
- "src/profiling/symbolizer/scoped_read_mmap_windows.cc",
"src/profiling/symbolizer/subprocess.h",
"src/profiling/symbolizer/subprocess_posix.cc",
"src/profiling/symbolizer/subprocess_windows.cc",
@@ -1311,7 +1320,6 @@
name = "src_protozero_proto_ring_buffer",
srcs = [
"src/protozero/proto_ring_buffer.cc",
- "src/protozero/proto_ring_buffer.h",
],
)
@@ -1459,6 +1467,7 @@
"src/trace_processor/importers/common/clock_converter.h",
"src/trace_processor/importers/common/clock_tracker.cc",
"src/trace_processor/importers/common/clock_tracker.h",
+ "src/trace_processor/importers/common/create_mapping_params.h",
"src/trace_processor/importers/common/deobfuscation_mapping_table.cc",
"src/trace_processor/importers/common/deobfuscation_mapping_table.h",
"src/trace_processor/importers/common/event_tracker.cc",
@@ -1467,6 +1476,8 @@
"src/trace_processor/importers/common/flow_tracker.h",
"src/trace_processor/importers/common/global_args_tracker.cc",
"src/trace_processor/importers/common/global_args_tracker.h",
+ "src/trace_processor/importers/common/mapping_tracker.cc",
+ "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_tracker.cc",
@@ -1482,6 +1493,8 @@
"src/trace_processor/importers/common/trace_parser.cc",
"src/trace_processor/importers/common/track_tracker.cc",
"src/trace_processor/importers/common/track_tracker.h",
+ "src/trace_processor/importers/common/virtual_memory_mapping.cc",
+ "src/trace_processor/importers/common/virtual_memory_mapping.h",
],
)
@@ -2469,6 +2482,7 @@
perfetto_filegroup(
name = "src_trace_processor_perfetto_sql_stdlib_prelude_prelude",
srcs = [
+ "src/trace_processor/perfetto_sql/stdlib/prelude/casts.sql",
"src/trace_processor/perfetto_sql/stdlib/prelude/slices.sql",
"src/trace_processor/perfetto_sql/stdlib/prelude/trace_bounds.sql",
],
@@ -2673,6 +2687,15 @@
],
)
+# GN target: //src/trace_processor/util:build_id
+perfetto_filegroup(
+ name = "src_trace_processor_util_build_id",
+ srcs = [
+ "src/trace_processor/util/build_id.cc",
+ "src/trace_processor/util/build_id.h",
+ ],
+)
+
# GN target: //src/trace_processor/util:bump_allocator
perfetto_filegroup(
name = "src_trace_processor_util_bump_allocator",
@@ -2792,15 +2815,6 @@
],
)
-# GN target: //src/trace_processor/util:stack_traces_util
-perfetto_filegroup(
- name = "src_trace_processor_util_stack_traces_util",
- srcs = [
- "src/trace_processor/util/stack_traces_util.cc",
- "src/trace_processor/util/stack_traces_util.h",
- ],
-)
-
# GN target: //src/trace_processor/util:stdlib
perfetto_filegroup(
name = "src_trace_processor_util_stdlib",
@@ -5617,6 +5631,7 @@
":src_trace_processor_tables_tables",
":src_trace_processor_tables_tables_python",
":src_trace_processor_types_types",
+ ":src_trace_processor_util_build_id",
":src_trace_processor_util_bump_allocator",
":src_trace_processor_util_descriptors",
":src_trace_processor_util_glob",
@@ -5630,7 +5645,6 @@
":src_trace_processor_util_protozero_to_text",
":src_trace_processor_util_regex",
":src_trace_processor_util_sql_argument",
- ":src_trace_processor_util_stack_traces_util",
":src_trace_processor_util_stdlib",
":src_trace_processor_util_util",
":src_trace_processor_util_zip_reader",
@@ -5718,6 +5732,7 @@
srcs = [
":include_perfetto_base_base",
":include_perfetto_ext_base_base",
+ ":include_perfetto_ext_protozero_protozero",
":include_perfetto_ext_trace_processor_demangle",
":include_perfetto_ext_trace_processor_export_json",
":include_perfetto_ext_trace_processor_importers_memory_tracker_memory_tracker",
@@ -5787,6 +5802,7 @@
":src_trace_processor_tables_tables",
":src_trace_processor_tables_tables_python",
":src_trace_processor_types_types",
+ ":src_trace_processor_util_build_id",
":src_trace_processor_util_bump_allocator",
":src_trace_processor_util_descriptors",
":src_trace_processor_util_glob",
@@ -5800,7 +5816,6 @@
":src_trace_processor_util_protozero_to_text",
":src_trace_processor_util_regex",
":src_trace_processor_util_sql_argument",
- ":src_trace_processor_util_stack_traces_util",
":src_trace_processor_util_stdlib",
":src_trace_processor_util_util",
":src_trace_processor_util_zip_reader",
@@ -5878,7 +5893,7 @@
":src_profiling_deobfuscator",
":src_profiling_symbolizer_symbolize_database",
":src_profiling_symbolizer_symbolizer",
- ":src_trace_processor_util_stack_traces_util",
+ ":src_trace_processor_util_build_id",
":src_traceconv_pprofbuilder",
":src_traceconv_utils",
],
@@ -5943,6 +5958,7 @@
srcs = [
":include_perfetto_base_base",
":include_perfetto_ext_base_base",
+ ":include_perfetto_ext_protozero_protozero",
":include_perfetto_ext_trace_processor_demangle",
":include_perfetto_ext_trace_processor_export_json",
":include_perfetto_ext_trace_processor_importers_memory_tracker_memory_tracker",
@@ -6009,6 +6025,7 @@
":src_trace_processor_tables_tables",
":src_trace_processor_tables_tables_python",
":src_trace_processor_types_types",
+ ":src_trace_processor_util_build_id",
":src_trace_processor_util_bump_allocator",
":src_trace_processor_util_descriptors",
":src_trace_processor_util_glob",
@@ -6022,7 +6039,6 @@
":src_trace_processor_util_protozero_to_text",
":src_trace_processor_util_regex",
":src_trace_processor_util_sql_argument",
- ":src_trace_processor_util_stack_traces_util",
":src_trace_processor_util_stdlib",
":src_trace_processor_util_util",
":src_trace_processor_util_zip_reader",
diff --git a/buildtools/BUILD.gn b/buildtools/BUILD.gn
index 1d6e039..07c6e21 100644
--- a/buildtools/BUILD.gn
+++ b/buildtools/BUILD.gn
@@ -1424,6 +1424,7 @@
cflags += [
"-Wno-deprecated-declarations",
"-Wno-vla-cxx-extension",
+ "-Wno-c99-designator",
]
}
public_configs = [ ":libunwindstack_config" ]
diff --git a/docs/analysis/batch-trace-processor.md b/docs/analysis/batch-trace-processor.md
index be1408d..4fdb4f9 100644
--- a/docs/analysis/batch-trace-processor.md
+++ b/docs/analysis/batch-trace-processor.md
@@ -127,7 +127,7 @@
By default, batch trace processor only ships with a single resolver which knows
how to lookup filesystem paths: however, custom resolvers can be easily
created and registered. See the documentation on the
-[TraceUriResolver class](https://cs.android.com/android/platform/superproject/+/master:external/perfetto/python/perfetto/trace_uri_resolver/resolver.py;l=56?q=resolver.py)
+[TraceUriResolver class](https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/python/perfetto/trace_uri_resolver/resolver.py;l=56?q=resolver.py)
for information on how to do this.
## Memory usage
diff --git a/docs/contributing/build-instructions.md b/docs/contributing/build-instructions.md
index 4ee7789..39d0294 100644
--- a/docs/contributing/build-instructions.md
+++ b/docs/contributing/build-instructions.md
@@ -92,7 +92,7 @@
Follow these instructions if you are an AOSP contributor.
-The source code lives in [`external/perfetto` in the AOSP tree](https://cs.android.com/android/platform/superproject/+/main:external/perfetto/).
+The source code lives in [`external/perfetto` in the AOSP tree](https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/).
Follow the instructions on https://source.android.com/setup/build/building .
diff --git a/docs/data-sources/atrace.md b/docs/data-sources/atrace.md
index 7d3d325..c481538 100644
--- a/docs/data-sources/atrace.md
+++ b/docs/data-sources/atrace.md
@@ -46,7 +46,7 @@
introduced [Tracing SDK](/docs/instrumentation/tracing-sdk.md). At the moment
the advice is to keep using the existing ATrace API on Android.
-[libcutils]: https://cs.android.com/android/platform/superproject/+/main:system/core/libcutils/include/cutils/trace.h?q=f:trace%20libcutils
+[libcutils]: https://cs.android.com/android/platform/superproject/main/+/main:system/core/libcutils/include/cutils/trace.h?q=f:trace%20libcutils
## UI
diff --git a/docs/data-sources/battery-counters.md b/docs/data-sources/battery-counters.md
index 7d31c74..847ca24 100644
--- a/docs/data-sources/battery-counters.md
+++ b/docs/data-sources/battery-counters.md
@@ -32,7 +32,7 @@
For more details on HW specs and resolution see
[Measuring Device Power](https://source.android.com/devices/tech/power/device).
-[health-hal]: https://cs.android.com/android/platform/superproject/+/main:hardware/interfaces/health/2.0/IHealth.hal?q=IHealth
+[health-hal]: https://cs.android.com/android/platform/superproject/main/+/main:hardware/interfaces/health/2.0/IHealth.hal?q=IHealth
#### Measuring charge while plugged on USB
@@ -133,7 +133,7 @@
Googlers: See [go/power-rails-internal-doc](http://go/power-rails-internal-doc)
for instructions on how to change the default rail selection on Pixel devices.
-[power-hal]: https://cs.android.com/android/platform/superproject/+/main:hardware/interfaces/power/stats/1.0/IPowerStats.hal
+[power-hal]: https://cs.android.com/android/platform/superproject/main/+/main:hardware/interfaces/power/stats/1.0/IPowerStats.hal
Simplified block diagram:
diff --git a/docs/data-sources/frametimeline.md b/docs/data-sources/frametimeline.md
index 434cc81..0931a6d 100644
--- a/docs/data-sources/frametimeline.md
+++ b/docs/data-sources/frametimeline.md
@@ -117,7 +117,7 @@
## Janks explained
The jank types are defined in
-[JankInfo.h](https://cs.android.com/android/platform/superproject/+/main:frameworks/native/libs/gui/include/gui/JankInfo.h?l=22).
+[JankInfo.h](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/native/libs/gui/include/gui/JankInfo.h?l=22).
Since each app is written differently, there is no common way to go into the
internals of the apps and specify what the reason for the jank was. Our goal is
not to do this but rather, provide a quick way to tell if app was janky or if
diff --git a/docs/data-sources/native-heap-profiler.md b/docs/data-sources/native-heap-profiler.md
index 47b3f2a..d3071e1 100644
--- a/docs/data-sources/native-heap-profiler.md
+++ b/docs/data-sources/native-heap-profiler.md
@@ -225,7 +225,7 @@
On userdebug builds, all processes except for a small set of critical
services can be profiled (to find the set of disallowed targets, look for
`never_profile_heap` in [heapprofd.te](
-https://cs.android.com/android/platform/superproject/+/main:system/sepolicy/private/heapprofd.te?q=never_profile_heap).
+https://cs.android.com/android/platform/superproject/main/+/main:system/sepolicy/private/heapprofd.te?q=never_profile_heap).
This restriction can be lifted by disabling SELinux by running
`adb shell su root setenforce 0` or by passing `--disable-selinux` to the
`heap_profile` script.
diff --git a/docs/design-docs/heapprofd-design.md b/docs/design-docs/heapprofd-design.md
index 1b6f34e..e59b9a2 100644
--- a/docs/design-docs/heapprofd-design.md
+++ b/docs/design-docs/heapprofd-design.md
@@ -39,7 +39,7 @@
### Enabling profiling
#### Use case 1: profiling future allocations from a running process
-One of the real-time signals ([`BIONIC_SIGNAL_PROFILER`](https://cs.android.com/android/platform/superproject/+/main:bionic/libc/platform/bionic/reserved_signals.h?q=symbol:BIONIC_SIGNAL_PROFILER)) is reserved in libc as a triggering mechanism. In this scenario:
+One of the real-time signals ([`BIONIC_SIGNAL_PROFILER`](https://cs.android.com/android/platform/superproject/main/+/main:bionic/libc/platform/bionic/reserved_signals.h?q=symbol:BIONIC_SIGNAL_PROFILER)) is reserved in libc as a triggering mechanism. In this scenario:
* heapprofd sends a RT signal to the target process
* Upon receipt of the signal, bionic reacts by installing a temporary malloc hook, which in turn spawns a thread to dynamically load libheapprofd.so in the process context. This means heapprofd will not work for statically linked binaries, as they lack the ability to `dlopen`. We can not spawn the thread directly from the signal handler, as `pthread_create` is not async-safe.
@@ -109,7 +109,7 @@
Remote unwinding also enables us to use _global caching_ (`Elf::SetCachingEnabled(true)`) in libunwindstack. This prevents debug information being used by different processes to be loaded and decompressed multiple times.
-We add an `FDMaps` objects to parse maps from `/proc/self/maps` sent by the target process. We keep `FDMaps` object cached per process that is being profiled. This both saves the overhead of text-parsing `/proc/[pid]/maps` as well as keeps various objects needed for unwinding (e.g. decompressed minidebuginfo). In case an unwind fails with `ERROR_INVALID_MAP` we reparse the maps object. We will do changes to libunwindstack to create a more general version of [`LocalUpdatableMaps`](https://cs.android.com/android/platform/superproject/+/main:system/unwinding/libunwindstack/Maps.cpp?q=symbol:LocalUpdatableMaps) that is also applicable for remote processes.
+We add an `FDMaps` objects to parse maps from `/proc/self/maps` sent by the target process. We keep `FDMaps` object cached per process that is being profiled. This both saves the overhead of text-parsing `/proc/[pid]/maps` as well as keeps various objects needed for unwinding (e.g. decompressed minidebuginfo). In case an unwind fails with `ERROR_INVALID_MAP` we reparse the maps object. We will do changes to libunwindstack to create a more general version of [`LocalUpdatableMaps`](https://cs.android.com/android/platform/superproject/main/+/main:system/unwinding/libunwindstack/Maps.cpp?q=symbol:LocalUpdatableMaps) that is also applicable for remote processes.
#### Advantages of remote unwinding
@@ -221,7 +221,7 @@
The most efficient way of stack unwinding is using frame pointers. This is unreliable on Android as we do not control build parameters for vendor libraries or OEM builds and due to issues on ARM32. Thus, our stack unwinding relies on libunwindstack which uses DWARF information from the library ELF files to determine return addresses. This can significantly slower, with unwinding of a stack taking between 100μs and ~100 ms ([data from simpleperf](https://gist.github.com/fmayer/a3a5a352196f9037f34241f8fb09004d)).
-[libunwindstack](https://cs.android.com/android/platform/superproject/+/main:system/unwinding/libunwindstack/) is Android's replacement for [libunwind](https://www.nongnu.org/libunwind/). It has a modern C++ object-oriented API surface and support for Android specific features allowing it to unwind mixed native and Java applications using information emitted by ART depending on execution mode. It also supports symbolization for native code and all three execution modes or ART.
+[libunwindstack](https://cs.android.com/android/platform/superproject/main/+/main:system/unwinding/libunwindstack/) is Android's replacement for [libunwind](https://www.nongnu.org/libunwind/). It has a modern C++ object-oriented API surface and support for Android specific features allowing it to unwind mixed native and Java applications using information emitted by ART depending on execution mode. It also supports symbolization for native code and all three execution modes or ART.
### Symbolization
Symbolization is the process of determining function name and line number from a code address. For builds by Google, we can get symbolized binaries (i.e. binaries with an ELF section that can be used for symbolization) from go/ab or https://ci.android.com (e.g. https://ci.android.com/builds/submitted/6410994/aosp_cf_x86_phone-userdebug/latest/aosp_cf_x86_phone-symbols-6410994.zip).
@@ -235,11 +235,11 @@
## Related Work
### simpleperf
-Even though [simpleperf](https://cs.android.com/android/platform/superproject/+/main:system/extras/simpleperf/doc/README.md) is a CPU rather than memory profiler, it is similar in nature to the work proposed here in that it supports offline unwinding. The kernel is asked to provide copies of stack traces at regular intervals, which are dumped onto disk. The dumped information is then used to unwind the stacks after the profiling is complete.
+Even though [simpleperf](https://cs.android.com/android/platform/superproject/main/+/main:system/extras/simpleperf/doc/README.md) is a CPU rather than memory profiler, it is similar in nature to the work proposed here in that it supports offline unwinding. The kernel is asked to provide copies of stack traces at regular intervals, which are dumped onto disk. The dumped information is then used to unwind the stacks after the profiling is complete.
### malloc-debug
-[malloc-debug](https://cs.android.com/android/platform/superproject/+/main:bionic/libc/malloc_debug/) instruments bionic's allocation functions to detect common memory problems like buffer overflows, double frees, etc. This is similar to the project described in this document as it uses the same mechanism to instrument the libc allocation functions. Unlike heapprofd, it does not provide the user with heap dumps.
+[malloc-debug](https://cs.android.com/android/platform/superproject/main/+/main:bionic/libc/malloc_debug/) instruments bionic's allocation functions to detect common memory problems like buffer overflows, double frees, etc. This is similar to the project described in this document as it uses the same mechanism to instrument the libc allocation functions. Unlike heapprofd, it does not provide the user with heap dumps.
### Feature Matrix
@@ -259,9 +259,9 @@
For non-zygote processes, we could use [`pthread_atfork(3)`](http://man7.org/linux/man-pages/man3/pthread_atfork.3.html) to establish new connections.
-For zygote processes, [`FileDescriptorInfo::ReopenOrDetach`](https://cs.android.com/android/platform/superproject/+/main:frameworks/base/core/jni/fd_utils.cpp?q=%22void%20FileDescriptorInfo::ReopenOrDetach%22), which is called after `fork(2)`– and thus after the `pthread_atfork` handlers – detaches all sockets, i.e. replaces them with file descriptors to `/dev/null`. If the socket is not contained within [`kPathWhiteList`](https://cs.android.com/android/platform/superproject/+/main:frameworks/base/core/jni/fd_utils.cpp?q=symbol:kPathWhitelist), zygote crashes instead. Thus using only a `pthread_atfork` handler is not feasible, as the connections established within will immediately get disconnected in zygote children.
+For zygote processes, [`FileDescriptorInfo::ReopenOrDetach`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/jni/fd_utils.cpp?q=%22void%20FileDescriptorInfo::ReopenOrDetach%22), which is called after `fork(2)`– and thus after the `pthread_atfork` handlers – detaches all sockets, i.e. replaces them with file descriptors to `/dev/null`. If the socket is not contained within [`kPathWhiteList`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/jni/fd_utils.cpp?q=symbol:kPathWhitelist), zygote crashes instead. Thus using only a `pthread_atfork` handler is not feasible, as the connections established within will immediately get disconnected in zygote children.
-After forking, zygote calls [`PreApplicationInit`](https://cs.android.com/android/platform/superproject/+/main:frameworks/base/core/jni/com_android_internal_os_Zygote.cpp?q=symbol:PreApplicationInit), which is currently used by malloc\_debug to detect whether it is in the root zygote or in a child process by setting `gMallocLeakZygoteChild`. It also calls [Java callbacks](https://cs.android.com/android/platform/superproject/+/main:frameworks/base/core/jni/com_android_internal_os_Zygote.cpp?q=CallStaticVoidMethod.*gCallPostForkChildHooks), but there does not seem to currently exist a way to dynamically register native callbacks.
+After forking, zygote calls [`PreApplicationInit`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/jni/com_android_internal_os_Zygote.cpp?q=symbol:PreApplicationInit), which is currently used by malloc\_debug to detect whether it is in the root zygote or in a child process by setting `gMallocLeakZygoteChild`. It also calls [Java callbacks](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/jni/com_android_internal_os_Zygote.cpp?q=CallStaticVoidMethod.*gCallPostForkChildHooks), but there does not seem to currently exist a way to dynamically register native callbacks.
Naive lazy initialization (i.e. closing the socket in the atfork handler, and then reconnecting on the first call to malloc) is problematic, as the code in zygote between fork and `ReopenOrDetach` might call `malloc`, thus leading to the connection to be established, which then gets closed by `ReopenOrDetach` again.
diff --git a/docs/design-docs/heapprofd-sampling.md b/docs/design-docs/heapprofd-sampling.md
index 085645c..929e1b5 100644
--- a/docs/design-docs/heapprofd-sampling.md
+++ b/docs/design-docs/heapprofd-sampling.md
@@ -172,7 +172,7 @@
using. This resolves the above issues.
[algorithm]:
- https://cs.android.com/android/platform/superproject/+/main:external/perfetto/src/profiling/memory/sampler.h
+ https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/src/profiling/memory/sampler.h
[example visualization]: /docs/images/native-heap-prof.png
[Android Heap Profiler]: /docs/design-docs/heapprofd-design
[mean absolute percentage error]: https://en.wikipedia.org/wiki/Mean_absolute_percentage_error
diff --git a/docs/design-docs/heapprofd-wire-protocol.md b/docs/design-docs/heapprofd-wire-protocol.md
index 9fa94e1..5eeb984 100644
--- a/docs/design-docs/heapprofd-wire-protocol.md
+++ b/docs/design-docs/heapprofd-wire-protocol.md
@@ -6,7 +6,7 @@
_**Last Updated**: 2019-02-11_
## Objective
-Restructure heapprofd to make use of the <code>[SharedRingBuffer](https://cs.android.com/android/platform/superproject/+/main:external/perfetto/src/profiling/memory/shared_ring_buffer.cc)</code>.
+Restructure heapprofd to make use of the <code>[SharedRingBuffer](https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/src/profiling/memory/shared_ring_buffer.cc)</code>.
## Overview
diff --git a/docs/instrumentation/tracing-sdk.md b/docs/instrumentation/tracing-sdk.md
index 9e72732..e0fba60 100644
--- a/docs/instrumentation/tracing-sdk.md
+++ b/docs/instrumentation/tracing-sdk.md
@@ -319,7 +319,7 @@
* On Linux and MacOS and Windows `traced` must be built and run separately. See
the [Linux quickstart](/docs/quickstart/linux-tracing.md) for instructions.
* On Windows the tracing protocol works over TCP/IP (
- [127.0.0.1:32278](https://cs.android.com/android/platform/superproject/+/main:external/perfetto/src/tracing/ipc/default_socket.cc;l=75;drc=4f88a2fdfd3801c109d5e927b8206f9756288b12)
+ [127.0.0.1:32278](https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/src/tracing/ipc/default_socket.cc;l=75;drc=4f88a2fdfd3801c109d5e927b8206f9756288b12)
) + named shmem.
## {#recording} Recording traces through the API
diff --git a/docs/reference/perfetto-cli.md b/docs/reference/perfetto-cli.md
index 531ce05..195ceb1 100644
--- a/docs/reference/perfetto-cli.md
+++ b/docs/reference/perfetto-cli.md
@@ -100,7 +100,7 @@
: Specifies the atrace categories you want to record a trace for.
For example, the following command traces Window Manager using atrace:
`adb shell perfetto --out FILE wm`. To record other categories, see the
- [list of atrace categories](https://cs.android.com/android/platform/superproject/+/main:frameworks/native/cmds/atrace/atrace.cpp).
+ [list of atrace categories](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/native/cmds/atrace/atrace.cpp).
Note: Available categories are Android version dependent.
`FTRACE_GROUP/FTRACE_NAME`
diff --git a/docs/reference/synthetic-track-event.md b/docs/reference/synthetic-track-event.md
index bcc5ae3..31f8ea0 100644
--- a/docs/reference/synthetic-track-event.md
+++ b/docs/reference/synthetic-track-event.md
@@ -12,9 +12,9 @@
representation of protobufs.
The root container of the protobuf-based traces is the
-[Trace](https://cs.android.com/android/platform/superproject/+/master:external/perfetto/protos/perfetto/trace/trace.proto)
+[Trace](https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/protos/perfetto/trace/trace.proto)
message which itself is simply a repeated field of
-[TracePacket](https://cs.android.com/android/platform/superproject/+/master:external/perfetto/protos/perfetto/trace/trace_packet.proto)
+[TracePacket](https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/protos/perfetto/trace/trace_packet.proto)
messages.
## Thread-scoped (sync) slices
diff --git a/gn/perfetto_integrationtests.gni b/gn/perfetto_integrationtests.gni
index 969c977..78469f5 100644
--- a/gn/perfetto_integrationtests.gni
+++ b/gn/perfetto_integrationtests.gni
@@ -50,7 +50,11 @@
[ "src/trace_processor:integrationtests" ]
}
-perfetto_integrationtests_targets += [ "src/trace_redaction:integrationtests" ]
+# This test requires traces that are not available on Android builds.
+if (perfetto_build_standalone && !is_android) {
+ perfetto_integrationtests_targets +=
+ [ "src/trace_redaction:integrationtests" ]
+}
if (enable_perfetto_traced_relay) {
perfetto_integrationtests_targets += [ "src/traced_relay:integrationtests" ]
diff --git a/gn/perfetto_unittests.gni b/gn/perfetto_unittests.gni
index f1d46ba..836ac9a 100644
--- a/gn/perfetto_unittests.gni
+++ b/gn/perfetto_unittests.gni
@@ -86,4 +86,6 @@
perfetto_unittests_targets += [ "src/traced_relay:unittests" ]
}
-perfetto_unittests_targets += [ "src/trace_redaction:unittests" ]
+if (!is_win) {
+ perfetto_unittests_targets += [ "src/trace_redaction:unittests" ]
+}
diff --git a/gn/standalone/BUILD.gn b/gn/standalone/BUILD.gn
index 43c81d0..a7b7a7c 100644
--- a/gn/standalone/BUILD.gn
+++ b/gn/standalone/BUILD.gn
@@ -352,33 +352,14 @@
"-D__ANDROID_API__=${android_api_level}",
"--target=$android_abi_target",
]
- cflags_cc += [
- "-isystem$android_ndk_root/sources/cxx-stl/llvm-libc++/include",
- "-isystem$android_ndk_root/sources/android/support/include",
- "-isystem$android_ndk_root/sources/cxx-stl/llvm-libc++abi/include",
- ]
+ cflags_cc += [ "-isystem$android_compile_sysroot/c++/v1" ]
- # Keep these include paths *after* the above to prevent the wrong include
- # being used for some headers (e.g. math.h).
- # We need to add to each language (cflags_c, cflags_cc etc.) separately as
- # we added to cflags directly, these inludes would end up before the
- # languge and thus the above includes.
- sysroot_cflags = [
- "-isystem$android_compile_sysroot/$android_compile_sysroot_subdir",
- "-isystem$android_compile_sysroot",
- ]
- cflags_c += sysroot_cflags
- cflags_cc += sysroot_cflags
-
+ android_lib_dir = "$android_compile_sysroot/usr/lib/$android_abi_target/$android_api_level"
ldflags += [
"-Wl,-z,nocopyreloc",
- "-gcc-toolchain",
- "$android_toolchain_root",
- "--sysroot=$android_ndk_root/$android_link_sysroot_subdir",
+ "--sysroot=$android_compile_sysroot",
+ "-B${android_lib_dir}",
"--target=$android_abi_target",
- "-Wl,--exclude-libs,libunwind.a",
- "-Wl,--exclude-libs,libgcc.a",
- "-Wl,--exclude-libs,libc++_static.a",
"-Wl,--no-undefined",
"-Wl,-z,noexecstack",
"-Wl,-z,relro",
@@ -386,23 +367,11 @@
"-Wl,--warn-shared-textrel",
"-Wl,--fatal-warnings",
- # New NDKs need setting this to not give "unable to find library -lc++".
- # See https://github.com/android/ndk/issues/951#issuecomment-501017894.
- "-nostdlib++",
+ # From NDK docs: "although the option uses the name "libstdc++" for
+ # historical reasons, this is correct for libc++ as well.
+ "-static-libstdc++",
]
- lib_dirs = [
- "$android_ndk_root/sources/cxx-stl/llvm-libc++/libs/$android_app_abi",
- ]
- libs += [
- "gcc",
- "c++_static",
- "c++abi",
- ]
-
- if (current_cpu == "arm") {
- # New NDKs don't have libandroid_support for arm64.
- libs += [ "android_support" ]
- }
+ lib_dirs = [ android_lib_dir ]
}
}
diff --git a/gn/standalone/android.gni b/gn/standalone/android.gni
index ab9492e..3b82104 100644
--- a/gn/standalone/android.gni
+++ b/gn/standalone/android.gni
@@ -28,40 +28,20 @@
declare_args() {
android_llvm_dir = "$android_ndk_root/toolchains/llvm/prebuilt/$android_host"
- android_clangrt_dir = "$android_llvm_dir/lib64/clang/9.0.9/lib/linux"
- android_compile_sysroot = "$android_ndk_root/sysroot/usr/include"
+ android_clangrt_dir = "$android_llvm_dir/lib/clang/17/lib/linux"
+ android_compile_sysroot = "$android_llvm_dir/sysroot"
if (current_cpu == "x86") {
android_abi_target = "i686-linux-android"
- android_compile_sysroot_subdir = "i686-linux-android"
- android_link_sysroot_subdir =
- "platforms/android-${android_api_level}/arch-x86"
- android_prebuilt_arch = "android-x86"
- android_toolchain_root = "$android_ndk_root/toolchains/x86-${_android_toolchain_version}/prebuilt/$android_host"
android_llvm_arch = "i686"
} else if (current_cpu == "arm") {
android_abi_target = "arm-linux-androideabi"
- android_compile_sysroot_subdir = "arm-linux-androideabi"
- android_link_sysroot_subdir =
- "platforms/android-${android_api_level}/arch-arm"
- android_prebuilt_arch = "android-arm"
- android_toolchain_root = "$android_ndk_root/toolchains/arm-linux-androideabi-${_android_toolchain_version}/prebuilt/$android_host"
android_llvm_arch = "arm"
} else if (current_cpu == "x64") {
android_abi_target = "x86_64-linux-android"
- android_compile_sysroot_subdir = "x86_64-linux-android"
- android_link_sysroot_subdir =
- "platforms/android-${android_api_level}/arch-x86_64"
- android_prebuilt_arch = "android-x86_64"
- android_toolchain_root = "$android_ndk_root/toolchains/x86_64-${_android_toolchain_version}/prebuilt/$android_host"
android_llvm_arch = "x86_64"
} else if (current_cpu == "arm64") {
android_abi_target = "aarch64-linux-android"
- android_compile_sysroot_subdir = "aarch64-linux-android"
- android_link_sysroot_subdir =
- "platforms/android-${android_api_level}/arch-arm64"
- android_prebuilt_arch = "android-arm64"
- android_toolchain_root = "$android_ndk_root/toolchains/aarch64-linux-android-${_android_toolchain_version}/prebuilt/$android_host"
android_llvm_arch = "aarch64"
} else {
assert(false, "Need android libgcc support for this arch.")
diff --git a/gn/standalone/toolchain/BUILD.gn b/gn/standalone/toolchain/BUILD.gn
index 71f2966..fcf2794 100644
--- a/gn/standalone/toolchain/BUILD.gn
+++ b/gn/standalone/toolchain/BUILD.gn
@@ -185,11 +185,11 @@
} else {
target_ar = "ar"
if (is_android) {
- target_ar = "$android_toolchain_root/bin/$android_abi_target-ar"
+ target_ar = "$android_llvm_dir/bin/llvm-ar"
target_cc = "$android_llvm_dir/bin/clang"
target_cxx = "$android_llvm_dir/bin/clang++"
target_linker = "$android_llvm_dir/bin/ld.lld"
- target_strip = "$android_toolchain_root/bin/$android_abi_target-strip"
+ target_strip = "$android_llvm_dir/bin/llvm-strip"
} else {
assert(_target_triplet != "",
"target_triplet must be non-empty when cross-compiling")
@@ -297,11 +297,13 @@
tool("solink") {
soname = "{{target_output_name}}{{output_extension}}"
unstripped_so = "{{root_out_dir}}/$soname"
+ rspfile = "$unstripped_so.rsp"
+ rspfile_content = "{{inputs}}"
rpath = "-Wl,-soname,$soname"
if (is_mac) {
rpath = "-Wl,-install_name,@rpath/$soname"
}
- command = "$cc_wrapper $cxx $ld_arg -shared {{ldflags}} ${external_ldflags} {{inputs}} {{solibs}} {{libs}} $rpath -o $unstripped_so"
+ command = "$cc_wrapper $cxx $ld_arg -shared {{ldflags}} ${external_ldflags} @$rspfile {{solibs}} {{libs}} $rpath -o $unstripped_so"
outputs = [ unstripped_so ]
output_prefix = "lib"
default_output_extension = ".so"
@@ -316,7 +318,9 @@
tool("link") {
unstripped_exe =
"{{root_out_dir}}/{{target_output_name}}{{output_extension}}"
- command = "$cc_wrapper $cxx $ld_arg {{ldflags}} ${external_ldflags} {{inputs}} {{solibs}} {{libs}} -o $unstripped_exe"
+ rspfile = "$unstripped_exe.rsp"
+ rspfile_content = "{{inputs}}"
+ command = "$cc_wrapper $cxx $ld_arg {{ldflags}} ${external_ldflags} @$rspfile {{solibs}} {{libs}} -o $unstripped_exe"
outputs = [ unstripped_exe ]
description = "link $unstripped_exe"
if (strip != "") {
diff --git a/include/perfetto/ext/base/BUILD.gn b/include/perfetto/ext/base/BUILD.gn
index 6e38737..2418346 100644
--- a/include/perfetto/ext/base/BUILD.gn
+++ b/include/perfetto/ext/base/BUILD.gn
@@ -37,6 +37,7 @@
"pipe.h",
"platform.h",
"scoped_file.h",
+ "scoped_mmap.h",
"small_set.h",
"small_vector.h",
"status_or.h",
diff --git a/include/perfetto/ext/base/metatrace_events.h b/include/perfetto/ext/base/metatrace_events.h
index e846ed6..ff87aea 100644
--- a/include/perfetto/ext/base/metatrace_events.h
+++ b/include/perfetto/ext/base/metatrace_events.h
@@ -51,7 +51,7 @@
F(FTRACE_CPU_WAIT_CMD), /*unused*/ \
F(FTRACE_CPU_RUN_CYCLE), /*unused*/ \
F(FTRACE_CPU_FLUSH), \
- F(FTRACE_CPU_DRAIN), /*unused*/ \
+ F(FTRACE_CPU_BUFFER_WATERMARK), \
F(READ_SYS_STATS), \
F(PS_WRITE_ALL_PROCESSES), \
F(PS_ON_PIDS), \
diff --git a/include/perfetto/ext/base/scoped_mmap.h b/include/perfetto/ext/base/scoped_mmap.h
new file mode 100644
index 0000000..1af620b
--- /dev/null
+++ b/include/perfetto/ext/base/scoped_mmap.h
@@ -0,0 +1,75 @@
+/*
+ * 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 INCLUDE_PERFETTO_EXT_BASE_SCOPED_MMAP_H_
+#define INCLUDE_PERFETTO_EXT_BASE_SCOPED_MMAP_H_
+
+#include <cstddef>
+
+#include "perfetto/base/build_config.h"
+#include "perfetto/ext/base/scoped_file.h"
+
+namespace perfetto::base {
+
+// RAII wrapper that holds ownership of an mmap()d area and of a file. Calls
+// unmap() and close() on destruction.
+class ScopedMmap {
+ public:
+ // Creates a memory mapping for the first `length` bytes of `file`.
+ static ScopedMmap FromHandle(base::ScopedPlatformHandle file, size_t length);
+
+ ScopedMmap() {}
+ ~ScopedMmap();
+ ScopedMmap(ScopedMmap&& other) noexcept;
+
+ ScopedMmap& operator=(ScopedMmap&& other) noexcept;
+
+ // Returns a pointer to the mapped memory area. Only valid if `IsValid()` is
+ // true.
+ void* data() const { return ptr_; }
+
+ // Returns true if this object contains a successfully mapped area.
+ bool IsValid() const { return ptr_ != nullptr; }
+
+ // Returns the length of the mapped area.
+ size_t length() const { return length_; }
+
+ // Unmaps the area and closes the file. Returns false if this held a mmap()d
+ // area and unmapping failed. In any case, after this method, `IsValid()` will
+ // return false.
+ bool reset() noexcept;
+
+ private:
+ ScopedMmap(const ScopedMmap&) = delete;
+ ScopedMmap& operator=(const ScopedMmap&) = delete;
+
+ size_t length_ = 0;
+ void* ptr_ = nullptr;
+ ScopedPlatformHandle file_;
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+ ScopedPlatformHandle map_;
+#endif
+};
+
+// Tries to open `fname` and maps its first `length` bytes in memory.
+ScopedMmap ReadMmapFilePart(const char* fname, size_t length);
+
+// Tries to open `fname` and maps the whole file into memory.
+ScopedMmap ReadMmapWholeFile(const char* fname);
+
+} // namespace perfetto::base
+
+#endif // INCLUDE_PERFETTO_EXT_BASE_SCOPED_MMAP_H_
diff --git a/include/perfetto/ext/base/version.h b/include/perfetto/ext/base/version.h
index 212424a..ad78372 100644
--- a/include/perfetto/ext/base/version.h
+++ b/include/perfetto/ext/base/version.h
@@ -20,9 +20,27 @@
namespace perfetto {
namespace base {
-// The returned pointer is a static string is safe to pass around.
+// The returned pointer is a static string and safe to pass around.
+// Returns a human readable string currently of the approximate form:
+// Perfetto v42.1-deadbeef0 (deadbeef03c641e4b4ea9cf38e9b5696670175a9)
+// However you should not depend on the format of this string.
+// It maybe not be possible to determine the version. In which case the
+// string will be of the approximate form:
+// Perfetto v0.0 (unknown)
const char* GetVersionString();
+// The returned pointer is a static string and safe to pass around.
+// Returns the short code used to identity the version:
+// v42.1-deadbeef0
+// It maybe not be possible to determine the version. In which case
+// this returns nullptr.
+// This can be compared with equality to other
+// version codes to detect matched builds (for example to see if
+// trace_processor_shell and the UI were built at the same revision)
+// but you should not attempt to parse it as the format may change
+// without warning.
+const char* GetVersionCode();
+
} // namespace base
} // namespace perfetto
diff --git a/src/trace_processor/perfetto_sql/stdlib/deprecated/BUILD.gn b/include/perfetto/ext/protozero/BUILD.gn
similarity index 77%
rename from src/trace_processor/perfetto_sql/stdlib/deprecated/BUILD.gn
rename to include/perfetto/ext/protozero/BUILD.gn
index 1e92f31..5354e2c 100644
--- a/src/trace_processor/perfetto_sql/stdlib/deprecated/BUILD.gn
+++ b/include/perfetto/ext/protozero/BUILD.gn
@@ -12,8 +12,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import("../../../../../gn/perfetto_sql.gni")
+import("../../../../gn/perfetto.gni")
-perfetto_sql_source_set("deprecated") {
- sources = [ "v42/common" ]
+source_set("protozero") {
+ public_deps = [ "../base" ]
+ deps = [ "../../../../gn:default_deps" ]
+ sources = [ "proto_ring_buffer.h" ]
}
diff --git a/src/protozero/proto_ring_buffer.h b/include/perfetto/ext/protozero/proto_ring_buffer.h
similarity index 96%
rename from src/protozero/proto_ring_buffer.h
rename to include/perfetto/ext/protozero/proto_ring_buffer.h
index 6459426..8db542c 100644
--- a/src/protozero/proto_ring_buffer.h
+++ b/include/perfetto/ext/protozero/proto_ring_buffer.h
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-#ifndef SRC_PROTOZERO_PROTO_RING_BUFFER_H_
-#define SRC_PROTOZERO_PROTO_RING_BUFFER_H_
+#ifndef INCLUDE_PERFETTO_EXT_PROTOZERO_PROTO_RING_BUFFER_H_
+#define INCLUDE_PERFETTO_EXT_PROTOZERO_PROTO_RING_BUFFER_H_
#include <stdint.h>
@@ -150,4 +150,4 @@
} // namespace protozero
-#endif // SRC_PROTOZERO_PROTO_RING_BUFFER_H_
+#endif // INCLUDE_PERFETTO_EXT_PROTOZERO_PROTO_RING_BUFFER_H_
diff --git a/include/perfetto/trace_processor/trace_blob.h b/include/perfetto/trace_processor/trace_blob.h
index 87f327f..33ac858 100644
--- a/include/perfetto/trace_processor/trace_blob.h
+++ b/include/perfetto/trace_processor/trace_blob.h
@@ -65,7 +65,7 @@
~TraceBlob();
// Allow move.
- TraceBlob(TraceBlob&& other) noexcept { *this = std::move(other); }
+ TraceBlob(TraceBlob&& other) noexcept;
TraceBlob& operator=(TraceBlob&&) noexcept;
// Disallow copy.
diff --git a/infra/ci/config.py b/infra/ci/config.py
index 0f0f068..c306457 100755
--- a/infra/ci/config.py
+++ b/infra/ci/config.py
@@ -98,11 +98,6 @@
'PERFETTO_TEST_SCRIPT':
'test/ci/android_tests.sh',
},
- 'android-clang-arm-asan': {
- 'PERFETTO_TEST_GN_ARGS': 'is_debug=false target_os="android" '
- 'target_cpu="arm" is_asan=true',
- 'PERFETTO_TEST_SCRIPT': 'test/ci/android_tests.sh',
- },
'linux-clang-x86_64-libfuzzer': {
'PERFETTO_TEST_GN_ARGS': 'is_debug=false is_fuzzer=true is_asan=true',
'PERFETTO_TEST_SCRIPT': 'test/ci/fuzzer_tests.sh',
diff --git a/infra/ci/controller/Makefile b/infra/ci/controller/Makefile
index 42e6194..337d72e 100644
--- a/infra/ci/controller/Makefile
+++ b/infra/ci/controller/Makefile
@@ -31,7 +31,7 @@
--project ${PROJECT} -v ${GAE_VERSION} -s default -q
lib/.stamp:
- echo "If this fails run `sudo apt install python-pip`"
+ echo "If this fails run sudo apt install python-pip"
python2.7 -m pip install -t lib/ rsa==4.0 oauth2client==4.1.3 httplib2==0.20.4
touch $@
diff --git a/infra/ci/frontend/static/script.js b/infra/ci/frontend/static/script.js
index 207cc83..a2e9dc6 100644
--- a/infra/ci/frontend/static/script.js
+++ b/infra/ci/frontend/static/script.js
@@ -28,7 +28,6 @@
{ id: 'linux-clang-x86_64-bazel', label: 'bazel' },
{ id: 'ui-clang-x86_64-release', label: 'rel' },
{ id: 'android-clang-arm-release', label: 'rel' },
- { id: 'android-clang-arm-asan', label: 'asan' },
];
const STATS_LINK =
@@ -193,7 +192,7 @@
m('td[rowspan=4]', 'Status'),
m('td[rowspan=4]', 'Owner'),
m('td[rowspan=4]', 'Updated'),
- m('td[colspan=11]', 'Bots'),
+ m('td[colspan=10]', 'Bots'),
),
m('tr',
m('td[colspan=9]', 'linux'),
@@ -203,7 +202,7 @@
m('td', 'gcc7'),
m('td[colspan=7]', 'clang'),
m('td[colspan=1]', 'ui'),
- m('td[colspan=2]', 'clang-arm'),
+ m('td[colspan=1]', 'clang-arm'),
),
m('tr#cls_header',
JOB_TYPES.map(job => m(`td#${job.id}`, job.label))
diff --git a/infra/perfetto.dev/src/markdown_render.js b/infra/perfetto.dev/src/markdown_render.js
index f607665..21c4575 100644
--- a/infra/perfetto.dev/src/markdown_render.js
+++ b/infra/perfetto.dev/src/markdown_render.js
@@ -20,7 +20,7 @@
const hljs = require('highlight.js');
const CS_BASE_URL =
- 'https://cs.android.com/android/platform/superproject/+/main:external/perfetto';
+ 'https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto';
const ROOT_DIR = path.dirname(path.dirname(path.dirname(__dirname)));
diff --git a/infra/ui.perfetto.dev/appengine/main.py b/infra/ui.perfetto.dev/appengine/main.py
index 0fac701..acca00d 100644
--- a/infra/ui.perfetto.dev/appengine/main.py
+++ b/infra/ui.perfetto.dev/appengine/main.py
@@ -19,8 +19,9 @@
REQ_HEADERS = [
'Accept',
- 'Accept-Encoding',
- 'Cache-Control',
+ # TODO(primiano): re-enable once the gzip handling outage fixed.
+ # 'Accept-Encoding',
+ # 'Cache-Control',
]
RESP_HEADERS = [
diff --git a/infra/ui.perfetto.dev/appengine/requirements.txt b/infra/ui.perfetto.dev/appengine/requirements.txt
index 1faca3d..9aa5a27 100644
--- a/infra/ui.perfetto.dev/appengine/requirements.txt
+++ b/infra/ui.perfetto.dev/appengine/requirements.txt
@@ -1,2 +1,3 @@
-Flask==1.1.2
+Flask==2.2.5
+Jinja2==3.0.3
requests
diff --git a/protos/perfetto/config/ftrace/ftrace_config.proto b/protos/perfetto/config/ftrace/ftrace_config.proto
index 5edb1dd..4d59f61 100644
--- a/protos/perfetto/config/ftrace/ftrace_config.proto
+++ b/protos/perfetto/config/ftrace/ftrace_config.proto
@@ -18,15 +18,33 @@
package perfetto.protos;
-// Next id: 26.
+// Next id: 28
message FtraceConfig {
+ // Ftrace events to record, example: "sched/sched_switch".
repeated string ftrace_events = 1;
+ // Android-specific event categories:
repeated string atrace_categories = 2;
repeated string atrace_apps = 3;
- // *Per-CPU* buffer size.
+
+ // Size of each per-cpu kernel ftrace ring buffer.
+ // Not guaranteed if there are multiple concurrent tracing sessions, as the
+ // buffers cannot be resized without pausing recording in the kernel.
optional uint32 buffer_size_kb = 10;
+
+ // If set, specifies how often the tracing daemon reads from the kernel ring
+ // buffer. Not guaranteed if there are multiple concurrent tracing sessions.
+ // Leave unset unless you're fine-tuning a local config.
optional uint32 drain_period_ms = 11;
+ // If set, the tracing daemon will read kernel ring buffers as soon as
+ // they're filled past this percentage of occupancy. In other words, a value
+ // of 50 means that a read pass is triggered as soon as any per-cpu buffer is
+ // half-full. Not guaranteed if there are multiple concurrent tracing
+ // sessions.
+ // Currently does nothing on Linux kernels below v6.1.
+ // Introduced in: perfetto v43.
+ optional uint32 drain_buffer_percent = 26;
+
// Configuration for compact encoding of scheduler events. When enabled (and
// recording the relevant ftrace events), specific high-volume events are
// encoded in a denser format than normal.
@@ -194,4 +212,22 @@
// * ftrace_events
// * buffer_size_kb
optional string instance_name = 25;
+
+ // If true, |buffer_size_kb| is interpreted as a lower bound, allowing the
+ // implementation to choose a bigger buffer size.
+ //
+ // Most configs for perfetto v43+ should simply leave both fields unset.
+ //
+ // If you need a config compatible with a range of perfetto builds and you
+ // used to set a non-default buffer_size_kb, consider setting both fields.
+ // Example:
+ // buffer_size_kb: 4096
+ // buffer_size_lower_bound: true
+ // On older builds, the per-cpu buffers will be exactly 4 MB.
+ // On v43+, buffers will be at least 4 MB.
+ // In both cases, neither is guaranteed if there are other concurrent
+ // perfetto ftrace sessions, as the buffers cannot be resized without pausing
+ // the recording in the kernel.
+ // Introduced in: perfetto v43.
+ optional bool buffer_size_lower_bound = 27;
}
diff --git a/protos/perfetto/config/perfetto_config.proto b/protos/perfetto/config/perfetto_config.proto
index 36595af..f158730 100644
--- a/protos/perfetto/config/perfetto_config.proto
+++ b/protos/perfetto/config/perfetto_config.proto
@@ -745,15 +745,33 @@
// Begin of protos/perfetto/config/ftrace/ftrace_config.proto
-// Next id: 26.
+// Next id: 28
message FtraceConfig {
+ // Ftrace events to record, example: "sched/sched_switch".
repeated string ftrace_events = 1;
+ // Android-specific event categories:
repeated string atrace_categories = 2;
repeated string atrace_apps = 3;
- // *Per-CPU* buffer size.
+
+ // Size of each per-cpu kernel ftrace ring buffer.
+ // Not guaranteed if there are multiple concurrent tracing sessions, as the
+ // buffers cannot be resized without pausing recording in the kernel.
optional uint32 buffer_size_kb = 10;
+
+ // If set, specifies how often the tracing daemon reads from the kernel ring
+ // buffer. Not guaranteed if there are multiple concurrent tracing sessions.
+ // Leave unset unless you're fine-tuning a local config.
optional uint32 drain_period_ms = 11;
+ // If set, the tracing daemon will read kernel ring buffers as soon as
+ // they're filled past this percentage of occupancy. In other words, a value
+ // of 50 means that a read pass is triggered as soon as any per-cpu buffer is
+ // half-full. Not guaranteed if there are multiple concurrent tracing
+ // sessions.
+ // Currently does nothing on Linux kernels below v6.1.
+ // Introduced in: perfetto v43.
+ optional uint32 drain_buffer_percent = 26;
+
// Configuration for compact encoding of scheduler events. When enabled (and
// recording the relevant ftrace events), specific high-volume events are
// encoded in a denser format than normal.
@@ -921,6 +939,24 @@
// * ftrace_events
// * buffer_size_kb
optional string instance_name = 25;
+
+ // If true, |buffer_size_kb| is interpreted as a lower bound, allowing the
+ // implementation to choose a bigger buffer size.
+ //
+ // Most configs for perfetto v43+ should simply leave both fields unset.
+ //
+ // If you need a config compatible with a range of perfetto builds and you
+ // used to set a non-default buffer_size_kb, consider setting both fields.
+ // Example:
+ // buffer_size_kb: 4096
+ // buffer_size_lower_bound: true
+ // On older builds, the per-cpu buffers will be exactly 4 MB.
+ // On v43+, buffers will be at least 4 MB.
+ // In both cases, neither is guaranteed if there are other concurrent
+ // perfetto ftrace sessions, as the buffers cannot be resized without pausing
+ // the recording in the kernel.
+ // Introduced in: perfetto v43.
+ optional bool buffer_size_lower_bound = 27;
}
// End of protos/perfetto/config/ftrace/ftrace_config.proto
diff --git a/protos/perfetto/metrics/android/android_boot.proto b/protos/perfetto/metrics/android/android_boot.proto
index 89a56d2..a043efe 100644
--- a/protos/perfetto/metrics/android/android_boot.proto
+++ b/protos/perfetto/metrics/android/android_boot.proto
@@ -42,6 +42,22 @@
optional int64 num_of_processes = 2;
optional double average_start_time = 3;
}
+ message GarbageCollectionAggregation {
+ optional int64 total_gc_count = 1;
+ optional int64 num_of_processes_with_gc = 2;
+ optional int64 num_of_threads_with_gc = 3;
+ optional double avg_gc_duration = 4;
+ optional double avg_running_gc_duration = 5;
+ optional int64 full_gc_count = 6;
+ optional int64 collector_transition_gc_count = 7;
+ optional int64 young_gc_count = 8;
+ optional int64 native_alloc_gc_count = 9;
+ optional int64 explicit_gc_count = 10;
+ optional int64 alloc_gc_count = 11;
+ optional double mb_per_ms_of_gc = 12;
+ }
optional ProcessStartAggregation full_trace_process_start_aggregation = 6;
optional ProcessStartAggregation post_boot_process_start_aggregation = 7;
+ optional GarbageCollectionAggregation full_trace_gc_aggregation = 8;
+ optional GarbageCollectionAggregation post_boot_gc_aggregation = 9;
}
diff --git a/protos/perfetto/metrics/perfetto_merged_metrics.proto b/protos/perfetto/metrics/perfetto_merged_metrics.proto
index c96a4a2..58f9731 100644
--- a/protos/perfetto/metrics/perfetto_merged_metrics.proto
+++ b/protos/perfetto/metrics/perfetto_merged_metrics.proto
@@ -171,8 +171,24 @@
optional int64 num_of_processes = 2;
optional double average_start_time = 3;
}
+ message GarbageCollectionAggregation {
+ optional int64 total_gc_count = 1;
+ optional int64 num_of_processes_with_gc = 2;
+ optional int64 num_of_threads_with_gc = 3;
+ optional double avg_gc_duration = 4;
+ optional double avg_running_gc_duration = 5;
+ optional int64 full_gc_count = 6;
+ optional int64 collector_transition_gc_count = 7;
+ optional int64 young_gc_count = 8;
+ optional int64 native_alloc_gc_count = 9;
+ optional int64 explicit_gc_count = 10;
+ optional int64 alloc_gc_count = 11;
+ optional double mb_per_ms_of_gc = 12;
+ }
optional ProcessStartAggregation full_trace_process_start_aggregation = 6;
optional ProcessStartAggregation post_boot_process_start_aggregation = 7;
+ optional GarbageCollectionAggregation full_trace_gc_aggregation = 8;
+ optional GarbageCollectionAggregation post_boot_gc_aggregation = 9;
}
// End of protos/perfetto/metrics/android/android_boot.proto
diff --git a/protos/perfetto/trace/android/surfaceflinger_layers.proto b/protos/perfetto/trace/android/surfaceflinger_layers.proto
index c7cc0cf..d85dc7f 100644
--- a/protos/perfetto/trace/android/surfaceflinger_layers.proto
+++ b/protos/perfetto/trace/android/surfaceflinger_layers.proto
@@ -101,8 +101,10 @@
// Similar to DEVICE, but the layer position may have been asynchronously set
// through setCursorPosition
HWC_TYPE_CURSOR = 4;
- // Layer was composited by the device via a sideband stream.
+ // Layer was composited by the device via a sideband stream
HWC_TYPE_SIDEBAND = 5;
+ // Layer was composited by hardware optimized for display decoration
+ HWC_TYPE_DISPLAY_DECORATION = 6;
}
// Information about each layer.
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index 3654a9f..490a9e8 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -745,15 +745,33 @@
// Begin of protos/perfetto/config/ftrace/ftrace_config.proto
-// Next id: 26.
+// Next id: 28
message FtraceConfig {
+ // Ftrace events to record, example: "sched/sched_switch".
repeated string ftrace_events = 1;
+ // Android-specific event categories:
repeated string atrace_categories = 2;
repeated string atrace_apps = 3;
- // *Per-CPU* buffer size.
+
+ // Size of each per-cpu kernel ftrace ring buffer.
+ // Not guaranteed if there are multiple concurrent tracing sessions, as the
+ // buffers cannot be resized without pausing recording in the kernel.
optional uint32 buffer_size_kb = 10;
+
+ // If set, specifies how often the tracing daemon reads from the kernel ring
+ // buffer. Not guaranteed if there are multiple concurrent tracing sessions.
+ // Leave unset unless you're fine-tuning a local config.
optional uint32 drain_period_ms = 11;
+ // If set, the tracing daemon will read kernel ring buffers as soon as
+ // they're filled past this percentage of occupancy. In other words, a value
+ // of 50 means that a read pass is triggered as soon as any per-cpu buffer is
+ // half-full. Not guaranteed if there are multiple concurrent tracing
+ // sessions.
+ // Currently does nothing on Linux kernels below v6.1.
+ // Introduced in: perfetto v43.
+ optional uint32 drain_buffer_percent = 26;
+
// Configuration for compact encoding of scheduler events. When enabled (and
// recording the relevant ftrace events), specific high-volume events are
// encoded in a denser format than normal.
@@ -921,6 +939,24 @@
// * ftrace_events
// * buffer_size_kb
optional string instance_name = 25;
+
+ // If true, |buffer_size_kb| is interpreted as a lower bound, allowing the
+ // implementation to choose a bigger buffer size.
+ //
+ // Most configs for perfetto v43+ should simply leave both fields unset.
+ //
+ // If you need a config compatible with a range of perfetto builds and you
+ // used to set a non-default buffer_size_kb, consider setting both fields.
+ // Example:
+ // buffer_size_kb: 4096
+ // buffer_size_lower_bound: true
+ // On older builds, the per-cpu buffers will be exactly 4 MB.
+ // On v43+, buffers will be at least 4 MB.
+ // In both cases, neither is guaranteed if there are other concurrent
+ // perfetto ftrace sessions, as the buffers cannot be resized without pausing
+ // the recording in the kernel.
+ // Introduced in: perfetto v43.
+ optional bool buffer_size_lower_bound = 27;
}
// End of protos/perfetto/config/ftrace/ftrace_config.proto
@@ -5148,8 +5184,10 @@
// Similar to DEVICE, but the layer position may have been asynchronously set
// through setCursorPosition
HWC_TYPE_CURSOR = 4;
- // Layer was composited by the device via a sideband stream.
+ // Layer was composited by the device via a sideband stream
HWC_TYPE_SIDEBAND = 5;
+ // Layer was composited by hardware optimized for display decoration
+ HWC_TYPE_DISPLAY_DECORATION = 6;
}
// Information about each layer.
diff --git a/protos/perfetto/trace_processor/trace_processor.proto b/protos/perfetto/trace_processor/trace_processor.proto
index c96caa0..1e579ca 100644
--- a/protos/perfetto/trace_processor/trace_processor.proto
+++ b/protos/perfetto/trace_processor/trace_processor.proto
@@ -38,9 +38,18 @@
enum TraceProcessorApiVersion {
// This variable has been introduced in v15 and is used to deal with API
- // mismatches between UI and trace_processor_shell --httpd. Increment this
- // every time a new feature that the UI depends on is being introduced (e.g.
- // new tables, new SQL operators, metrics that are required by the UI).
+ // mismatches between UI and trace_processor_shell --httpd.
+ //
+ // Prior to API version 11 this was incremented every time a new
+ // feature that the UI depended on was introduced (e.g. new tables,
+ // new SQL operators, metrics that are required by the UI, etc).
+ // This:
+ // a. Tended to be forgotten
+ // b. Still led to issues when the TP dropped *backwards*
+ // compatibility of a feature (since we checked TP >= UI
+ // TRACE_PROCESSOR_CURRENT_API_VERSION).
+ // Now the UI attempts to redirect the user to the matched version
+ // of the UI if one exists.
// See also StatusResult.api_version (below).
// Changes:
// 7. Introduce GUESS_CPU_SIZE
@@ -48,10 +57,12 @@
// 9. Add get_thread_state_summary_for_interval.
// 10. Add 'slice_is_ancestor' to stdlib.
// 11. Removal of experimental module from stdlib.
- TRACE_PROCESSOR_CURRENT_API_VERSION = 11;
+ // 12. Changed UI to be more aggresive about version matching.
+ // Added version_code.
+ TRACE_PROCESSOR_CURRENT_API_VERSION = 12;
}
-// At lowest level, the wire-format of the RPC procol is a linear sequence of
+// At lowest level, the wire-format of the RPC protocol is a linear sequence of
// TraceProcessorRpc messages on each side of the byte pipe
// Each message is prefixed by a tag (field = 1, type = length delimited) and a
// varint encoding its size (this is so the whole stream can also be read /
@@ -236,6 +247,15 @@
// The API version is incremented every time a change that the UI depends
// on is introduced (e.g. adding a new table that the UI queries).
optional int32 api_version = 3;
+
+ // Typically something like "v42.1-deadbeef0", but could be just
+ // "v42", "v0.0", or unset for binaries built from Bazel or other
+ // build configurations. This can be compared with equality to other
+ // version codes to detect matched builds (for example to see if
+ // trace_processor_shell and the UI were built at the same revision)
+ // but you should not attempt to parse it as the format may change
+ // without warning.
+ optional string version_code = 4;
}
// Input for the /compute_metric endpoint.
diff --git a/protos/third_party/chromium/chrome_track_event.proto b/protos/third_party/chromium/chrome_track_event.proto
index 602102e..a5d6977 100644
--- a/protos/third_party/chromium/chrome_track_event.proto
+++ b/protos/third_party/chromium/chrome_track_event.proto
@@ -87,6 +87,8 @@
message ChromeMessagePumpForUI {
// The MSG defined in winuser.h.
optional uint32 message_id = 1;
+ // The result of the Win32 API call WaitForMultipleObjectsEx().
+ optional uint32 wait_for_object_result = 2;
}
// An enumeration specifying the reason of the RenderFrame deletion.
@@ -1386,6 +1388,14 @@
STEP_BUFFER_SWAP_POST_SUBMIT = 12;
STEP_FINISH_BUFFER_SWAP = 13;
STEP_SWAP_BUFFERS_ACK = 14;
+
+ // Frame submission stages when Exo (Wayland server implementation in Ash
+ // browser process) is involved. Wayland clients (such as Lacros or ARC++)
+ // submit visual contents via Wayland surface commits, with which Exo
+ // constructs compositor frames and forwards to Ash Viz.
+ STEP_EXO_CONSTRUCT_COMPOSITOR_FRAME = 15;
+ STEP_EXO_SUBMIT_COMPOSITOR_FRAME = 16;
+ STEP_EXO_DISCARD_COMPOSITOR_FRAME = 17;
}
enum FrameSkippedReason {
SKIPPED_REASON_UNKNOWN = 0;
@@ -1515,16 +1525,36 @@
// The events getHistorySize().
optional int32 history_size = 1;
// The time of the oldest event (getHistoricalEventTimeNanos(0)).
- optional int64 time_ns = 2;
+ optional int64 oldest_time_ns = 2;
+ // The time of the newest event (getEventTimeNanos(0)).
+ optional int64 latest_time_ns = 5;
// The X coordinate of the event as reported by MotionEvent.getX().
optional float x_pixel = 3;
// The Y coordinate of the event as reported by MotionEvent.getY().
optional float y_pixel = 4;
+ // Determine if the previous forwarded event changed x coordinate.
+ optional bool has_x_movement = 6;
+ // Determine if the previous forwarded event changed y coordinate.
+ optional bool has_y_movement = 7;
+}
+
+// TouchDispositionGestureFilter is a class on android that detects and forwards
+// along gesture events based on touch acks and other information.
+message TouchDispositionGestureFilter {
+ // The number of gesture's inside of a GestureEventDataPacket.
+ optional int32 gesture_count = 1;
+}
+
+message ViewClassName {
+ // The name associated with a View class in browser UI. The class name is set
+ // by the view class itself through metadata macros and contains no data that
+ // can be modified by a user.
+ optional string name = 1;
}
message ChromeTrackEvent {
// Extension range for Chrome: 1000-1999
- // Next ID: 1059
+ // Next ID: 1061
extend TrackEvent {
optional ChromeAppState chrome_app_state = 1000;
@@ -1650,5 +1680,10 @@
optional WebContentInteraction web_content_interaction = 1058;
optional EventForwarder event_forwarder = 1059;
+
+ optional TouchDispositionGestureFilter touch_disposition_gesture_filter =
+ 1060;
+
+ optional ViewClassName view_class_name = 1061;
}
}
diff --git a/python/perfetto/trace_processor/metrics.descriptor b/python/perfetto/trace_processor/metrics.descriptor
index c4832d5..9783b76 100644
--- a/python/perfetto/trace_processor/metrics.descriptor
+++ b/python/perfetto/trace_processor/metrics.descriptor
Binary files differ
diff --git a/python/perfetto/trace_processor/trace_processor.descriptor b/python/perfetto/trace_processor/trace_processor.descriptor
index 261ab0b..ce0bc66 100644
--- a/python/perfetto/trace_processor/trace_processor.descriptor
+++ b/python/perfetto/trace_processor/trace_processor.descriptor
Binary files differ
diff --git a/src/base/BUILD.gn b/src/base/BUILD.gn
index bb2e11b..d8d00a7 100644
--- a/src/base/BUILD.gn
+++ b/src/base/BUILD.gn
@@ -46,6 +46,7 @@
"paged_memory.cc",
"periodic_task.cc",
"pipe.cc",
+ "scoped_mmap.cc",
"status.cc",
"string_splitter.cc",
"string_utils.cc",
@@ -201,6 +202,7 @@
"paged_memory_unittest.cc",
"periodic_task_unittest.cc",
"scoped_file_unittest.cc",
+ "scoped_mmap_unittest.cc",
"small_vector_unittest.cc",
"status_or_unittest.cc",
"status_unittest.cc",
diff --git a/src/base/file_utils.cc b/src/base/file_utils.cc
index 65fcc46..db0105c 100644
--- a/src/base/file_utils.cc
+++ b/src/base/file_utils.cc
@@ -410,8 +410,7 @@
std::optional<size_t> GetFileSize(const std::string& file_path) {
#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
- // This does not use base::OpenFile because to avoid getting an exclusive
- // lock.
+ // This does not use base::OpenFile to avoid getting an exclusive lock.
HANDLE file =
CreateFileA(file_path.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
diff --git a/src/base/scoped_mmap.cc b/src/base/scoped_mmap.cc
new file mode 100644
index 0000000..c12d33b
--- /dev/null
+++ b/src/base/scoped_mmap.cc
@@ -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.
+ */
+
+#include "perfetto/ext/base/scoped_mmap.h"
+
+#include <utility>
+
+#include "perfetto/ext/base/file_utils.h"
+#include "perfetto/ext/base/scoped_file.h"
+
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \
+ PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) || \
+ PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
+#include <sys/mman.h>
+#include <unistd.h>
+#elif PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+#include <Windows.h>
+#endif
+
+namespace perfetto::base {
+namespace {
+
+ScopedPlatformHandle OpenFileForMmap(const char* fname) {
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \
+ PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) || \
+ PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
+ return OpenFile(fname, O_RDONLY);
+#elif PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+ // This does not use base::OpenFile to avoid getting an exclusive lock.
+ return ScopedPlatformHandle(CreateFileA(fname, GENERIC_READ, FILE_SHARE_READ,
+ nullptr, OPEN_EXISTING,
+ FILE_ATTRIBUTE_NORMAL, nullptr));
+#else
+ // mmap is not supported. Do not even open the file.
+ base::ignore_result(fname);
+ return ScopedPlatformHandle();
+#endif
+}
+
+std::optional<size_t> GetPlatformHandleFileSize(PlatformHandle file) {
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \
+ PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) || \
+ PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
+ off_t file_size_offset = lseek(file, 0, SEEK_END);
+ if (file_size_offset <= 0) {
+ return std::nullopt;
+ }
+
+ lseek(file, 0, SEEK_SET);
+
+ size_t file_size = static_cast<size_t>(file_size_offset);
+ if (static_cast<off_t>(file_size) != file_size_offset) {
+ return std::nullopt;
+ }
+ return file_size;
+#elif PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+ LARGE_INTEGER fs;
+ fs.QuadPart = 0;
+ if (!GetFileSizeEx(file, &fs)) {
+ return std::nullopt;
+ }
+
+ size_t file_size = static_cast<size_t>(fs.QuadPart);
+ if (static_cast<decltype(fs.QuadPart)>(file_size) != fs.QuadPart) {
+ return std::nullopt;
+ }
+ return file_size;
+#else
+ // mmap is not supported. This does not matter.
+ base::ignore_result(file);
+ return std::nullopt;
+#endif
+}
+
+} // namespace
+
+ScopedMmap::ScopedMmap(ScopedMmap&& other) noexcept {
+ *this = std::move(other);
+}
+
+ScopedMmap& ScopedMmap::operator=(ScopedMmap&& other) noexcept {
+ reset();
+ std::swap(ptr_, other.ptr_);
+ std::swap(length_, other.length_);
+ std::swap(file_, other.file_);
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+ std::swap(map_, other.map_);
+#endif
+ return *this;
+}
+
+ScopedMmap::~ScopedMmap() {
+ reset();
+}
+
+// static
+ScopedMmap ScopedMmap::FromHandle(base::ScopedPlatformHandle file,
+ size_t length) {
+ ScopedMmap ret;
+ if (!file) {
+ return ret;
+ }
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \
+ PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) || \
+ PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
+ void* ptr = mmap(nullptr, length, PROT_READ, MAP_PRIVATE, *file, 0);
+ if (ptr != MAP_FAILED) {
+ ret.ptr_ = ptr;
+ ret.length_ = length;
+ ret.file_ = std::move(file);
+ }
+#elif PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+ ScopedPlatformHandle map(
+ CreateFileMapping(*file, nullptr, PAGE_READONLY, 0, 0, nullptr));
+ if (!map) {
+ return ret;
+ }
+ void* ptr = MapViewOfFile(*map, FILE_MAP_READ, 0, 0, length);
+ if (ptr != nullptr) {
+ ret.ptr_ = ptr;
+ ret.length_ = length;
+ ret.file_ = std::move(file);
+ ret.map_ = std::move(map);
+ }
+#else
+ base::ignore_result(length);
+#endif
+ return ret;
+}
+
+bool ScopedMmap::reset() noexcept {
+ bool ret = true;
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \
+ PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) || \
+ PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
+ if (ptr_ != nullptr) {
+ ret = munmap(ptr_, length_) == 0;
+ }
+#elif PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+ if (ptr_ != nullptr) {
+ ret = UnmapViewOfFile(ptr_);
+ }
+ map_.reset();
+#endif
+ ptr_ = nullptr;
+ length_ = 0;
+ file_.reset();
+ return ret;
+}
+
+ScopedMmap ReadMmapFilePart(const char* fname, size_t length) {
+ return ScopedMmap::FromHandle(OpenFileForMmap(fname), length);
+}
+
+ScopedMmap ReadMmapWholeFile(const char* fname) {
+ ScopedPlatformHandle file = OpenFileForMmap(fname);
+ if (!file) {
+ return ScopedMmap();
+ }
+ std::optional<size_t> file_size = GetPlatformHandleFileSize(file.get());
+ if (!file_size.has_value()) {
+ return ScopedMmap();
+ }
+ return ScopedMmap::FromHandle(std::move(file), *file_size);
+}
+
+} // namespace perfetto::base
diff --git a/src/base/scoped_mmap_unittest.cc b/src/base/scoped_mmap_unittest.cc
new file mode 100644
index 0000000..2c6b004
--- /dev/null
+++ b/src/base/scoped_mmap_unittest.cc
@@ -0,0 +1,87 @@
+/*
+ * 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 "perfetto/ext/base/scoped_mmap.h"
+
+#include "src/base/test/tmp_dir_tree.h"
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto::base {
+namespace {
+
+class ScopedMmapTest : public ::testing::Test {
+ void SetUp() override {
+#if !PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) && \
+ !PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) && \
+ !PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE) && \
+ !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+ GTEST_SKIP() << "mmap not supported";
+#endif
+ }
+};
+
+TEST_F(ScopedMmapTest, WholeNonExistingFile) {
+ base::TmpDirTree tmp;
+
+ ScopedMmap mapped = ReadMmapWholeFile(tmp.AbsolutePath("f1.txt").c_str());
+
+ EXPECT_FALSE(mapped.IsValid());
+}
+
+TEST_F(ScopedMmapTest, PartNonExistingFile) {
+ base::TmpDirTree tmp;
+
+ ScopedMmap mapped = ReadMmapFilePart(tmp.AbsolutePath("f1.txt").c_str(), 4);
+
+ EXPECT_FALSE(mapped.IsValid());
+}
+
+TEST_F(ScopedMmapTest, WholeOneByteFile) {
+ base::TmpDirTree tmp;
+ tmp.AddFile("f1.txt", "c");
+
+ ScopedMmap mapped = ReadMmapWholeFile(tmp.AbsolutePath("f1.txt").c_str());
+
+ ASSERT_TRUE(mapped.IsValid());
+ ASSERT_NE(mapped.data(), nullptr);
+ ASSERT_EQ(mapped.length(), 1u);
+ EXPECT_EQ(*static_cast<char*>(mapped.data()), 'c');
+}
+
+TEST_F(ScopedMmapTest, PartThreeBytes) {
+ base::TmpDirTree tmp;
+ tmp.AddFile("f1.txt", "ccccc");
+
+ ScopedMmap mapped = ReadMmapFilePart(tmp.AbsolutePath("f1.txt").c_str(), 3);
+
+ ASSERT_TRUE(mapped.IsValid());
+ ASSERT_NE(mapped.data(), nullptr);
+ ASSERT_EQ(mapped.length(), 3u);
+}
+
+TEST_F(ScopedMmapTest, Reset) {
+ base::TmpDirTree tmp;
+ tmp.AddFile("f1.txt", "ccccc");
+ ScopedMmap mapped = ReadMmapWholeFile(tmp.AbsolutePath("f1.txt").c_str());
+ ASSERT_TRUE(mapped.IsValid());
+
+ EXPECT_TRUE(mapped.reset());
+
+ EXPECT_FALSE(mapped.IsValid());
+}
+
+} // namespace
+} // namespace perfetto::base
diff --git a/src/base/version.cc b/src/base/version.cc
index 18569d3..e989534 100644
--- a/src/base/version.cc
+++ b/src/base/version.cc
@@ -23,18 +23,26 @@
#if PERFETTO_BUILDFLAG(PERFETTO_VERSION_GEN)
#include "perfetto_version.gen.h"
#else
-#define PERFETTO_VERSION_STRING() "v0.0"
+#define PERFETTO_VERSION_STRING() nullptr
#define PERFETTO_VERSION_SCM_REVISION() "unknown"
#endif
namespace perfetto {
namespace base {
+const char* GetVersionCode() {
+ return PERFETTO_VERSION_STRING();
+}
+
const char* GetVersionString() {
static const char* version_str = [] {
static constexpr size_t kMaxLen = 256;
+ const char* version_code = PERFETTO_VERSION_STRING();
+ if (version_code == nullptr) {
+ version_code = "v0.0";
+ }
char* version = new char[kMaxLen + 1];
- snprintf(version, kMaxLen, "Perfetto %s (%s)", PERFETTO_VERSION_STRING(),
+ snprintf(version, kMaxLen, "Perfetto %s (%s)", version_code,
PERFETTO_VERSION_SCM_REVISION());
return version;
}();
diff --git a/src/perfetto_cmd/bugreport_path.h b/src/perfetto_cmd/bugreport_path.h
index 5bc7602..71ab3df 100644
--- a/src/perfetto_cmd/bugreport_path.h
+++ b/src/perfetto_cmd/bugreport_path.h
@@ -25,16 +25,21 @@
namespace perfetto {
// Expose for testing
-inline std::string GetBugreportTracePath() {
+
+inline std::string GetBugreportTraceDir() {
#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) && \
PERFETTO_BUILDFLAG(PERFETTO_ANDROID_BUILD)
- return "/data/misc/perfetto-traces/bugreport/systrace.pftrace";
+ return "/data/misc/perfetto-traces/bugreport";
#else
// Only for tests, SaveTraceForBugreport is not used on other OSes.
- return base::GetSysTempDir() + "/bugreport.pftrace";
+ return base::GetSysTempDir();
#endif
}
+inline std::string GetBugreportTracePath() {
+ return GetBugreportTraceDir() + "/systrace.pftrace";
+}
+
} // namespace perfetto
#endif // SRC_PERFETTO_CMD_BUGREPORT_PATH_H_
diff --git a/src/perfetto_cmd/perfetto_cmd.cc b/src/perfetto_cmd/perfetto_cmd.cc
index e356b0d..b1840e9 100644
--- a/src/perfetto_cmd/perfetto_cmd.cc
+++ b/src/perfetto_cmd/perfetto_cmd.cc
@@ -60,6 +60,7 @@
#include "perfetto/ext/base/utils.h"
#include "perfetto/ext/base/uuid.h"
#include "perfetto/ext/base/version.h"
+#include "perfetto/ext/base/waitable_event.h"
#include "perfetto/ext/traced/traced.h"
#include "perfetto/ext/tracing/core/basic_types.h"
#include "perfetto/ext/tracing/core/trace_packet.h"
@@ -86,7 +87,7 @@
std::atomic<perfetto::PerfettoCmd*> g_perfetto_cmd;
const uint32_t kOnTraceDataTimeoutMs = 3000;
-const uint32_t kCloneTimeoutMs = 10000;
+const uint32_t kCloneTimeoutMs = 30000;
class LoggingErrorReporter : public ErrorReporter {
public:
@@ -294,6 +295,7 @@
enum LongOption {
OPT_ALERT_ID = 1000,
OPT_BUGREPORT,
+ OPT_BUGREPORT_ALL,
OPT_CLONE,
OPT_CLONE_SKIP_FILTER,
OPT_CONFIG_ID,
@@ -343,6 +345,7 @@
{"query-raw", no_argument, nullptr, OPT_QUERY_RAW},
{"version", no_argument, nullptr, OPT_VERSION},
{"save-for-bugreport", no_argument, nullptr, OPT_BUGREPORT},
+ {"save-all-for-bugreport", no_argument, nullptr, OPT_BUGREPORT_ALL},
{nullptr, 0, nullptr, 0}};
std::string config_file_name;
@@ -565,6 +568,11 @@
continue;
}
+ if (option == OPT_BUGREPORT_ALL) {
+ clone_all_bugreport_traces_ = true;
+ continue;
+ }
+
PrintUsage(argv[0]);
return 1;
}
@@ -600,8 +608,9 @@
return 1;
}
- if (bugreport_ && (is_attach() || is_detach() || query_service_ ||
- has_config_options || background_wait_)) {
+ if ((bugreport_ || clone_all_bugreport_traces_) &&
+ (is_attach() || is_detach() || query_service_ || has_config_options ||
+ background_wait_)) {
PERFETTO_ELOG("--save-for-bugreport cannot take any other argument");
return 1;
}
@@ -630,7 +639,8 @@
bool parsed = false;
bool cfg_could_be_txt = false;
- const bool will_trace_or_trigger = !is_attach() && !query_service_;
+ const bool will_trace_or_trigger =
+ !is_attach() && !query_service_ && !clone_all_bugreport_traces_;
if (!will_trace_or_trigger) {
if ((!trace_config_raw.empty() || has_config_options)) {
PERFETTO_ELOG("Cannot specify a trace config with this option");
@@ -1029,7 +1039,7 @@
#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
if (!background_ && !is_detach() && !upload_flag_ &&
triggers_to_activate_.empty() && !isatty(STDIN_FILENO) &&
- !isatty(STDERR_FILENO)) {
+ !isatty(STDERR_FILENO) && getenv("TERM")) {
fprintf(stderr,
"Warning: No PTY. CTRL+C won't gracefully stop the trace. If you "
"are running perfetto via adb shell, use the -tt arg (adb shell "
@@ -1074,6 +1084,19 @@
return;
}
+ if (clone_all_bugreport_traces_) {
+ ConsumerEndpoint::QueryServiceStateArgs args;
+ // Reduces the size of the IPC reply skipping data sources and producers.
+ args.sessions_only = true;
+ auto weak_this = weak_factory_.GetWeakPtr();
+ consumer_endpoint_->QueryServiceState(
+ args, [weak_this](bool success, const TracingServiceState& svc_state) {
+ if (weak_this)
+ weak_this->CloneAllBugreportTraces(success, svc_state);
+ });
+ return;
+ }
+
if (is_attach()) {
consumer_endpoint_->Attach(attach_key_);
return;
@@ -1382,7 +1405,7 @@
// This is used with --save-all-for-bugreport, to pause all cloning threads
// so that they first issue the clone and then proceed only after the service
- // have seen all the clone requests.
+ // has seen all the clone requests.
if (on_session_cloned_) {
std::function<void()> on_session_cloned(nullptr);
std::swap(on_session_cloned, on_session_cloned_);
@@ -1593,6 +1616,115 @@
});
}
+void PerfettoCmd::CloneAllBugreportTraces(
+ bool success,
+ const TracingServiceState& service_state) {
+ if (!success)
+ PERFETTO_FATAL("Failed to list active tracing sessions");
+
+ struct SessionToClone {
+ int32_t bugreport_score;
+ TracingSessionID tsid;
+ std::string fname; // Before deduping logic.
+ bool operator<(const SessionToClone& other) const {
+ return bugreport_score > other.bugreport_score; // High score first.
+ }
+ };
+ std::vector<SessionToClone> sessions;
+ for (const auto& session : service_state.tracing_sessions()) {
+ if (session.bugreport_score() <= 0 || !session.is_started())
+ continue;
+ std::string fname;
+ if (!session.bugreport_filename().empty()) {
+ fname = session.bugreport_filename();
+ } else {
+ fname = "systrace.pftrace";
+ }
+ sessions.emplace_back(
+ SessionToClone{session.bugreport_score(), session.id(), fname});
+ } // for(session)
+
+ if (sessions.empty()) {
+ PERFETTO_LOG("No tracing sessions eligible for bugreport were found.");
+ exit(0);
+ }
+
+ // First clone all sessions, synchronize, then read them back into files.
+ // The `sync_fn` below will be executed on each thread inside OnSessionCloned
+ // before proceeding with the readback. The logic below delays the readback
+ // of all threads, until the service has acked all the clone requests.
+ // The tracing service is single-threaded and data readbacks can take several
+ // seconds. This is to minimize the global clone time and avoid that that
+ // several sessions stomp on each other.
+ const size_t num_sessions = sessions.size();
+
+ // sync_point needs to be a shared_ptr to deal with the case where the main
+ // thread runs in the middle of the Notify() and the Wait() and destroys the
+ // WaitableEvent before some thread gets to the Wait().
+ auto sync_point = std::make_shared<base::WaitableEvent>();
+
+ std::function<void()> sync_fn = [sync_point, num_sessions] {
+ sync_point->Notify();
+ sync_point->Wait(num_sessions);
+ };
+
+ // Clone the sessions in order, starting with the highest score first.
+ std::sort(sessions.begin(), sessions.end());
+ for (auto it = sessions.begin(); it != sessions.end(); ++it) {
+ std::string actual_fname = it->fname;
+ size_t dupes = static_cast<size_t>(std::count_if(
+ sessions.begin(), it,
+ [&](const SessionToClone& o) { return o.fname == it->fname; }));
+ if (dupes > 0) {
+ std::string suffix = "_" + std::to_string(dupes);
+ const size_t last_dot = actual_fname.find_last_of('.');
+ if (last_dot != std::string::npos) {
+ actual_fname.replace(last_dot, 1, suffix + ".");
+ } else {
+ actual_fname.append(suffix);
+ }
+ } // if (dupes > 0)
+
+ // Clone the tracing session into the bugreport file.
+ std::string out_path = GetBugreportTraceDir() + "/" + actual_fname;
+ remove(out_path.c_str());
+ PERFETTO_LOG("Cloning tracing session %" PRIu64 " with score %d into %s",
+ it->tsid, it->bugreport_score, out_path.c_str());
+ std::string cmdline;
+ cmdline.reserve(128);
+ ArgsAppend(&cmdline, "perfetto");
+ ArgsAppend(&cmdline, "--clone");
+ ArgsAppend(&cmdline, std::to_string(it->tsid));
+ ArgsAppend(&cmdline, "--clone-for-bugreport");
+ ArgsAppend(&cmdline, "--out");
+ ArgsAppend(&cmdline, out_path);
+ CloneSessionOnThread(it->tsid, cmdline, kNewThreadPerRequest, sync_fn);
+ } // for(sessions)
+
+ PERFETTO_DLOG("Issuing %zu CloneSession requests", num_sessions);
+ sync_point->Wait(num_sessions);
+ PERFETTO_DLOG("All %zu sessions have acked the clone request", num_sessions);
+
+ // After all sessions are done, quit.
+ // Note that there is no risk that thd.PostTask() will interleave with the
+ // sequence of tasks that PerfettoCmd involves, there is no race here.
+ // There are two TaskRunners here, nested into each other:
+ // 1) The "outer" ThreadTaskRunner, created by `thd`. This will see only one
+ // task ever, which is "run perfetto_cmd until completion".
+ // 2) Internally PerfettoCmd creates its own UnixTaskRunner, which creates
+ // a nested TaskRunner that takes control of the execution. This returns
+ // only once TaskRunner::Quit() is called, in its epilogue.
+ auto done_count = std::make_shared<std::atomic<size_t>>(num_sessions);
+ for (auto& thd : snapshot_threads_) {
+ thd.PostTask([done_count] {
+ if (done_count->fetch_sub(1) == 1) {
+ PERFETTO_DLOG("All sessions cloned. quitting");
+ exit(0);
+ }
+ });
+ }
+}
+
void PerfettoCmd::LogUploadEvent(PerfettoStatsdAtom atom) {
if (!statsd_logging_)
return;
diff --git a/src/perfetto_cmd/perfetto_cmd.h b/src/perfetto_cmd/perfetto_cmd.h
index ca8ef78..d526b6f 100644
--- a/src/perfetto_cmd/perfetto_cmd.h
+++ b/src/perfetto_cmd/perfetto_cmd.h
@@ -82,6 +82,7 @@
void FinalizeTraceAndExit();
void PrintUsage(const char* argv0);
void PrintServiceState(bool success, const TracingServiceState&);
+ void CloneAllBugreportTraces(bool success, const TracingServiceState&);
void CloneSessionOnThread(TracingSessionID,
const std::string& cmdline, // \0 separated.
CloneThreadMode,
@@ -162,6 +163,7 @@
bool query_service_ = false;
bool query_service_output_raw_ = false;
bool query_service_long_ = false;
+ bool clone_all_bugreport_traces_ = false;
bool bugreport_ = false;
bool background_ = false;
bool background_wait_ = false;
diff --git a/src/profiling/symbolizer/BUILD.gn b/src/profiling/symbolizer/BUILD.gn
index c81dbb1..2eb2c93 100644
--- a/src/profiling/symbolizer/BUILD.gn
+++ b/src/profiling/symbolizer/BUILD.gn
@@ -29,9 +29,6 @@
"filesystem_windows.cc",
"local_symbolizer.cc",
"local_symbolizer.h",
- "scoped_read_mmap.h",
- "scoped_read_mmap_posix.cc",
- "scoped_read_mmap_windows.cc",
"subprocess.h",
"subprocess_posix.cc",
"subprocess_windows.cc",
@@ -52,7 +49,7 @@
"../../../include/perfetto/trace_processor:trace_processor",
"../../../protos/perfetto/trace:zero",
"../../../protos/perfetto/trace/profiling:zero",
- "../../trace_processor/util:stack_traces_util",
+ "../../trace_processor/util:build_id",
]
sources = [
"symbolize_database.cc",
diff --git a/src/profiling/symbolizer/local_symbolizer.cc b/src/profiling/symbolizer/local_symbolizer.cc
index 9ade8b4..eee5076 100644
--- a/src/profiling/symbolizer/local_symbolizer.cc
+++ b/src/profiling/symbolizer/local_symbolizer.cc
@@ -30,10 +30,10 @@
#include "perfetto/base/logging.h"
#include "perfetto/ext/base/file_utils.h"
#include "perfetto/ext/base/scoped_file.h"
+#include "perfetto/ext/base/scoped_mmap.h"
#include "perfetto/ext/base/string_utils.h"
#include "src/profiling/symbolizer/elf.h"
#include "src/profiling/symbolizer/filesystem.h"
-#include "src/profiling/symbolizer/scoped_read_mmap.h"
namespace perfetto {
namespace profiling {
@@ -222,12 +222,12 @@
static_assert(EI_CLASS > EI_MAG3, "mem[EI_MAG?] accesses are in range.");
if (size <= EI_CLASS)
return std::nullopt;
- ScopedReadMmap map(fname, size);
+ base::ScopedMmap map = base::ReadMmapFilePart(fname, size);
if (!map.IsValid()) {
- PERFETTO_PLOG("mmap");
+ PERFETTO_PLOG("Failed to mmap %s", fname);
return std::nullopt;
}
- char* mem = static_cast<char*>(*map);
+ char* mem = static_cast<char*>(map.data());
if (!IsElf(mem, size))
return std::nullopt;
diff --git a/src/profiling/symbolizer/scoped_read_mmap.h b/src/profiling/symbolizer/scoped_read_mmap.h
deleted file mode 100644
index 69a028a..0000000
--- a/src/profiling/symbolizer/scoped_read_mmap.h
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef SRC_PROFILING_SYMBOLIZER_SCOPED_READ_MMAP_H_
-#define SRC_PROFILING_SYMBOLIZER_SCOPED_READ_MMAP_H_
-
-#include "perfetto/ext/base/scoped_file.h"
-
-namespace perfetto {
-namespace profiling {
-
-class ScopedReadMmap {
- public:
- ScopedReadMmap(const char* fname, size_t length);
- virtual ~ScopedReadMmap();
-
- void* operator*() { return ptr_; }
-
- bool IsValid();
-
- private:
- size_t length_;
- void* ptr_;
-#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
- void* file_ = nullptr;
- void* map_ = nullptr;
-#else
- base::ScopedFile fd_;
-#endif
-};
-
-} // namespace profiling
-} // namespace perfetto
-
-#endif // SRC_PROFILING_SYMBOLIZER_SCOPED_READ_MMAP_H_
diff --git a/src/profiling/symbolizer/scoped_read_mmap_posix.cc b/src/profiling/symbolizer/scoped_read_mmap_posix.cc
deleted file mode 100644
index f6c3276..0000000
--- a/src/profiling/symbolizer/scoped_read_mmap_posix.cc
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "src/profiling/symbolizer/scoped_read_mmap.h"
-
-#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
-
-#include "perfetto/base/logging.h"
-#include "perfetto/ext/base/file_utils.h"
-
-#include <sys/mman.h>
-
-namespace perfetto {
-namespace profiling {
-
-ScopedReadMmap::ScopedReadMmap(const char* fname, size_t length)
- : length_(length), fd_(base::OpenFile(fname, O_RDONLY)) {
- if (!fd_) {
- PERFETTO_PLOG("Failed to open %s", fname);
- return;
- }
- ptr_ = mmap(nullptr, length, PROT_READ, MAP_PRIVATE, *fd_, 0);
-}
-
-ScopedReadMmap::~ScopedReadMmap() {
- if (ptr_ != MAP_FAILED)
- munmap(ptr_, length_);
-}
-
-bool ScopedReadMmap::IsValid() {
- return ptr_ != MAP_FAILED;
-}
-
-} // namespace profiling
-} // namespace perfetto
-
-#endif // !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
diff --git a/src/profiling/symbolizer/scoped_read_mmap_windows.cc b/src/profiling/symbolizer/scoped_read_mmap_windows.cc
deleted file mode 100644
index d56d913..0000000
--- a/src/profiling/symbolizer/scoped_read_mmap_windows.cc
+++ /dev/null
@@ -1,65 +0,0 @@
-
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "src/profiling/symbolizer/scoped_read_mmap.h"
-
-#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
-
-#include <Windows.h>
-
-namespace perfetto {
-namespace profiling {
-
-ScopedReadMmap::ScopedReadMmap(const char* fName, size_t length)
- : length_(length), ptr_(nullptr) {
- file_ = CreateFileA(fName, GENERIC_READ, FILE_SHARE_READ, nullptr,
- OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
- if (file_ == INVALID_HANDLE_VALUE) {
- PERFETTO_DLOG("Failed to open file: %s", fName);
- return;
- }
- map_ = CreateFileMapping(file_, nullptr, PAGE_READONLY, 0, 0, nullptr);
- if (map_ == INVALID_HANDLE_VALUE) {
- PERFETTO_DLOG("Failed to mmap file");
- return;
- }
- ptr_ = MapViewOfFile(map_, FILE_MAP_READ, 0, 0, length_);
- if (ptr_ == nullptr) {
- PERFETTO_DLOG("Failed to map view of file");
- }
-}
-
-ScopedReadMmap::~ScopedReadMmap() {
- if (ptr_ != nullptr) {
- UnmapViewOfFile(ptr_);
- }
- if (map_ != nullptr && map_ != INVALID_HANDLE_VALUE) {
- CloseHandle(map_);
- }
- if (file_ != nullptr && file_ != INVALID_HANDLE_VALUE) {
- CloseHandle(file_);
- }
-}
-
-bool ScopedReadMmap::IsValid() {
- return ptr_ != nullptr;
-}
-
-} // namespace profiling
-} // namespace perfetto
-
-#endif // PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
diff --git a/src/profiling/symbolizer/symbolize_database.cc b/src/profiling/symbolizer/symbolize_database.cc
index 224874b..a008e1a 100644
--- a/src/profiling/symbolizer/symbolize_database.cc
+++ b/src/profiling/symbolizer/symbolize_database.cc
@@ -28,8 +28,7 @@
#include "protos/perfetto/trace/profiling/profile_common.pbzero.h"
#include "protos/perfetto/trace/trace.pbzero.h"
#include "protos/perfetto/trace/trace_packet.pbzero.h"
-
-#include "src/trace_processor/util/stack_traces_util.h"
+#include "src/trace_processor/util/build_id.h"
namespace perfetto {
namespace profiling {
@@ -56,32 +55,6 @@
}
};
-std::string FromHex(const char* str, size_t size) {
- if (size % 2) {
- PERFETTO_DFATAL_OR_ELOG("Failed to parse hex %s", str);
- return "";
- }
- std::string result(size / 2, '\0');
- for (size_t i = 0; i < size; i += 2) {
- char hex_byte[3];
- hex_byte[0] = str[i];
- hex_byte[1] = str[i + 1];
- hex_byte[2] = '\0';
- char* end;
- long int byte = strtol(hex_byte, &end, 16);
- if (*end != '\0') {
- PERFETTO_DFATAL_OR_ELOG("Failed to parse hex %s", str);
- return "";
- }
- result[i / 2] = static_cast<char>(byte);
- }
- return result;
-}
-
-std::string FromHex(const std::string& str) {
- return FromHex(str.c_str(), str.size());
-}
-
std::map<UnsymbolizedMapping, std::vector<uint64_t>> GetUnsymbolizedFrames(
trace_processor::TraceProcessor* tp) {
std::map<UnsymbolizedMapping, std::vector<uint64_t>> res;
@@ -89,17 +62,10 @@
while (it.Next()) {
int64_t load_bias = it.Get(3).AsLong();
PERFETTO_CHECK(load_bias >= 0);
- std::string build_id;
- // TODO(b/148109467): Remove workaround once all active Chrome versions
- // write raw bytes instead of a string as build_id.
- std::string raw_build_id = it.Get(1).AsString();
- if (!trace_processor::util::IsHexModuleId(base::StringView(raw_build_id))) {
- build_id = FromHex(raw_build_id);
- } else {
- build_id = raw_build_id;
- }
- UnsymbolizedMapping unsymbolized_mapping{it.Get(0).AsString(), build_id,
- static_cast<uint64_t>(load_bias)};
+ trace_processor::BuildId build_id =
+ trace_processor::BuildId::FromHex(it.Get(1).AsString());
+ UnsymbolizedMapping unsymbolized_mapping{
+ it.Get(0).AsString(), build_id.raw(), static_cast<uint64_t>(load_bias)};
int64_t rel_pc = it.Get(2).AsLong();
res[unsymbolized_mapping].emplace_back(rel_pc);
}
diff --git a/src/protozero/BUILD.gn b/src/protozero/BUILD.gn
index 0082a8c..4d940bc 100644
--- a/src/protozero/BUILD.gn
+++ b/src/protozero/BUILD.gn
@@ -45,15 +45,13 @@
}
source_set("proto_ring_buffer") {
+ public_deps = [ "../../include/perfetto/ext/protozero" ]
deps = [
":protozero",
"../../gn:default_deps",
"../base",
]
- sources = [
- "proto_ring_buffer.cc",
- "proto_ring_buffer.h",
- ]
+ sources = [ "proto_ring_buffer.cc" ]
}
perfetto_unittest_source_set("unittests") {
diff --git a/src/protozero/proto_ring_buffer.cc b/src/protozero/proto_ring_buffer.cc
index 6f82a42..7b12a1b 100644
--- a/src/protozero/proto_ring_buffer.cc
+++ b/src/protozero/proto_ring_buffer.cc
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#include "src/protozero/proto_ring_buffer.h"
+#include "perfetto/ext/protozero/proto_ring_buffer.h"
#include "perfetto/base/logging.h"
#include "perfetto/ext/base/paged_memory.h"
diff --git a/src/protozero/proto_ring_buffer_unittest.cc b/src/protozero/proto_ring_buffer_unittest.cc
index 30cc3f1..5766d0a 100644
--- a/src/protozero/proto_ring_buffer_unittest.cc
+++ b/src/protozero/proto_ring_buffer_unittest.cc
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#include "src/protozero/proto_ring_buffer.h"
+#include "perfetto/ext/protozero/proto_ring_buffer.h"
#include <stdint.h>
#include <sys/types.h>
diff --git a/src/protozero/test/proto_ring_buffer_benchmark.cc b/src/protozero/test/proto_ring_buffer_benchmark.cc
index b65da3b..0c0f7f9 100644
--- a/src/protozero/test/proto_ring_buffer_benchmark.cc
+++ b/src/protozero/test/proto_ring_buffer_benchmark.cc
@@ -18,8 +18,8 @@
#include <string>
#include "perfetto/ext/base/file_utils.h"
+#include "perfetto/ext/protozero/proto_ring_buffer.h"
#include "src/base/test/utils.h"
-#include "src/protozero/proto_ring_buffer.h"
static void BM_ProtoRingBufferReadLargeChunks(benchmark::State& state) {
std::string trace_data;
diff --git a/src/trace_processor/BUILD.gn b/src/trace_processor/BUILD.gn
index 694b515..e37c9c6 100644
--- a/src/trace_processor/BUILD.gn
+++ b/src/trace_processor/BUILD.gn
@@ -132,7 +132,6 @@
"util:descriptors",
"util:gzip",
"util:proto_to_args_parser",
- "util:stack_traces_util",
]
public_deps = [ "../../include/perfetto/trace_processor:storage" ]
}
@@ -240,6 +239,7 @@
sources = [
"forwarding_trace_parser_unittest.cc",
"ref_counted_unittest.cc",
+ "trace_blob_unittest.cc",
]
deps = [
":storage_minimal",
diff --git a/src/trace_processor/containers/bit_vector.cc b/src/trace_processor/containers/bit_vector.cc
index b566558..43a5279 100644
--- a/src/trace_processor/containers/bit_vector.cc
+++ b/src/trace_processor/containers/bit_vector.cc
@@ -297,6 +297,37 @@
PERFETTO_DCHECK(update.CountSetBits() == CountSetBits());
}
+BitVector BitVector::FromSortedIndexVector(
+ const std::vector<int64_t>& indices) {
+ // The rest of the algorithm depends on |indices| being non empty.
+ if (indices.empty()) {
+ return BitVector();
+ }
+
+ // We are creating the smallest BitVector that can have all of the values from
+ // |indices| set. As we assume that |indices| is sorted, the size would be the
+ // last element + 1 and the last bit of the final BitVector will be set.
+ uint32_t size = static_cast<uint32_t>(indices.back() + 1);
+
+ uint32_t block_count = BlockCount(size);
+ std::vector<uint64_t> words(block_count * Block::kWords);
+ for (const int64_t i : indices) {
+ auto word_idx = static_cast<uint32_t>(i / kBitsInWord);
+ auto in_word_idx = static_cast<uint32_t>(i % kBitsInWord);
+ BitVector::BitWord(&words[word_idx]).Set(in_word_idx);
+ }
+
+ std::vector<uint32_t> counts(block_count);
+ for (uint32_t i = 1; i < counts.size(); ++i) {
+ // The number of set bits in each block is the number of set bits before and
+ // in the previous block.
+ counts[i] = counts[i - 1] +
+ ConstBlock(&words[Block::kWords * (i - 1)]).CountSetBits();
+ }
+
+ return BitVector(words, counts, size);
+}
+
BitVector BitVector::IntersectRange(uint32_t range_start,
uint32_t range_end) const {
// We should skip all bits until the index of first set bit bigger than
diff --git a/src/trace_processor/containers/bit_vector.h b/src/trace_processor/containers/bit_vector.h
index 884e1ca..c7d67c5 100644
--- a/src/trace_processor/containers/bit_vector.h
+++ b/src/trace_processor/containers/bit_vector.h
@@ -350,6 +350,14 @@
return bv;
}
+ // Creates BitVector from a vector of sorted indices. Set bits in the
+ // resulting BitVector are values from the index vector.
+ // Note for callers - the passed index vector has to:
+ // - be sorted
+ // - have first element >= 0
+ // - last value smaller than numeric limit of uint32_t.
+ static BitVector FromSortedIndexVector(const std::vector<int64_t>&);
+
// Creates a BitVector of size `min(range_end, size())` with bits between
// |start| and |end| filled with corresponding bits from |this| BitVector.
BitVector IntersectRange(uint32_t range_start, uint32_t range_end) const;
diff --git a/src/trace_processor/containers/bit_vector_benchmark.cc b/src/trace_processor/containers/bit_vector_benchmark.cc
index fd723a6..55d3307 100644
--- a/src/trace_processor/containers/bit_vector_benchmark.cc
+++ b/src/trace_processor/containers/bit_vector_benchmark.cc
@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+#include <limits>
#include <random>
#include <benchmark/benchmark.h>
@@ -278,3 +279,17 @@
}
}
BENCHMARK(BM_BitVectorSetBitsIterator)->Apply(BitVectorArgs);
+
+static void BM_BitVectorFromIndexVector(benchmark::State& state) {
+ std::vector<int64_t> indices;
+ for (int64_t i = 0; i < 1024l * 1024l; i++) {
+ indices.push_back(i);
+ }
+
+ indices.push_back(std::numeric_limits<uint32_t>::max() >> 5);
+
+ for (auto _ : state) {
+ benchmark::DoNotOptimize(BitVector::FromSortedIndexVector(indices));
+ }
+}
+BENCHMARK(BM_BitVectorFromIndexVector);
diff --git a/src/trace_processor/containers/bit_vector_unittest.cc b/src/trace_processor/containers/bit_vector_unittest.cc
index d803cef..ee7452f 100644
--- a/src/trace_processor/containers/bit_vector_unittest.cc
+++ b/src/trace_processor/containers/bit_vector_unittest.cc
@@ -17,6 +17,7 @@
#include "src/trace_processor/containers/bit_vector.h"
#include <bitset>
+#include <limits>
#include <random>
#include "perfetto/protozero/scattered_heap_buffer.h"
@@ -648,6 +649,38 @@
ASSERT_TRUE(bv.IsSet(8 * 1024));
}
+TEST(BitVectorUnittest, FromIndexVectorEmpty) {
+ std::vector<int64_t> indices{};
+ BitVector bv = BitVector::FromSortedIndexVector(indices);
+
+ ASSERT_EQ(bv.size(), 0u);
+}
+
+TEST(BitVectorUnittest, FromIndexVector) {
+ std::vector<int64_t> indices{0, 100, 200, 2000};
+ BitVector bv = BitVector::FromSortedIndexVector(indices);
+
+ ASSERT_EQ(bv.size(), 2001u);
+ ASSERT_EQ(bv.CountSetBits(), 4u);
+ ASSERT_TRUE(bv.IsSet(0));
+ ASSERT_TRUE(bv.IsSet(100));
+ ASSERT_TRUE(bv.IsSet(200));
+ ASSERT_TRUE(bv.IsSet(2000));
+}
+
+TEST(BitVectorUnittest, FromIndexVectorStressTestLargeValues) {
+ std::vector<int64_t> indices{0, 1 << 2, 1 << 10, 1 << 20, 1 << 30};
+ BitVector bv = BitVector::FromSortedIndexVector(indices);
+
+ ASSERT_EQ(bv.size(), (1 << 30) + 1u);
+ ASSERT_EQ(bv.CountSetBits(), 5u);
+ ASSERT_TRUE(bv.IsSet(0));
+ ASSERT_TRUE(bv.IsSet(1 << 2));
+ ASSERT_TRUE(bv.IsSet(1 << 10));
+ ASSERT_TRUE(bv.IsSet(1 << 20));
+ ASSERT_TRUE(bv.IsSet(1 << 30));
+}
+
TEST(BitVectorUnittest, Not) {
BitVector bv(10);
bv.Set(2);
diff --git a/src/trace_processor/db/column/dense_null_overlay.cc b/src/trace_processor/db/column/dense_null_overlay.cc
index 7dff76f..d9a7fb1 100644
--- a/src/trace_processor/db/column/dense_null_overlay.cc
+++ b/src/trace_processor/db/column/dense_null_overlay.cc
@@ -64,7 +64,7 @@
SearchValidationResult DenseNullOverlay::ChainImpl::ValidateSearchConstraints(
FilterOp op,
SqlValue sql_val) const {
- if (op == FilterOp::kIsNull) {
+ if (op == FilterOp::kIsNull || op == FilterOp::kIsNotNull) {
return SearchValidationResult::kOk;
}
return inner_->ValidateSearchConstraints(op, sql_val);
@@ -91,6 +91,15 @@
case SearchValidationResult::kOk:
break;
}
+ } else if (op == FilterOp::kIsNotNull) {
+ switch (inner_->ValidateSearchConstraints(op, sql_val)) {
+ case SearchValidationResult::kNoData:
+ return RangeOrBitVector(Range());
+ case SearchValidationResult::kAllData:
+ return RangeOrBitVector(non_null_->IntersectRange(in.start, in.end));
+ case SearchValidationResult::kOk:
+ break;
+ }
}
RangeOrBitVector inner_res = inner_->SearchValidated(op, sql_val, in);
@@ -150,6 +159,23 @@
case SearchValidationResult::kOk:
break;
}
+ } else if (op == FilterOp::kIsNotNull) {
+ switch (inner_->ValidateSearchConstraints(op, sql_val)) {
+ case SearchValidationResult::kNoData: {
+ BitVector::Builder non_null_indices(indices.size);
+ for (const uint32_t* it = indices.data;
+ it != indices.data + indices.size; it++) {
+ non_null_indices.Append(non_null_->IsSet(*it));
+ }
+ // There is no need to search in underlying storage. We should just
+ // check if the index is set in |non_null_|.
+ return RangeOrBitVector(std::move(non_null_indices).Build());
+ }
+ case SearchValidationResult::kAllData:
+ return RangeOrBitVector(Range(0, indices.size));
+ case SearchValidationResult::kOk:
+ break;
+ }
}
RangeOrBitVector inner_res =
diff --git a/src/trace_processor/db/column/null_overlay.cc b/src/trace_processor/db/column/null_overlay.cc
index af31ac5..7984656 100644
--- a/src/trace_processor/db/column/null_overlay.cc
+++ b/src/trace_processor/db/column/null_overlay.cc
@@ -113,7 +113,7 @@
SearchValidationResult NullOverlay::ChainImpl::ValidateSearchConstraints(
FilterOp op,
SqlValue sql_val) const {
- if (op == FilterOp::kIsNull) {
+ if (op == FilterOp::kIsNull || op == FilterOp::kIsNotNull) {
return SearchValidationResult::kOk;
}
return inner_->ValidateSearchConstraints(op, sql_val);
@@ -139,6 +139,15 @@
case SearchValidationResult::kOk:
break;
}
+ } else if (op == FilterOp::kIsNotNull) {
+ switch (inner_->ValidateSearchConstraints(op, sql_val)) {
+ case SearchValidationResult::kNoData:
+ return RangeOrBitVector(Range());
+ case SearchValidationResult::kAllData:
+ return RangeOrBitVector(non_null_->IntersectRange(in.start, in.end));
+ case SearchValidationResult::kOk:
+ break;
+ }
}
// Figure out the bounds of the indices in the underlying storage and search
@@ -177,6 +186,23 @@
case SearchValidationResult::kOk:
break;
}
+ } else if (op == FilterOp::kIsNotNull) {
+ switch (inner_->ValidateSearchConstraints(op, sql_val)) {
+ case SearchValidationResult::kNoData: {
+ BitVector::Builder non_null_indices(indices.size);
+ for (const uint32_t* it = indices.data;
+ it != indices.data + indices.size; it++) {
+ non_null_indices.Append(non_null_->IsSet(*it));
+ }
+ // There is no need to search in underlying storage. We should just
+ // check if the index is set in |non_null_|.
+ return RangeOrBitVector(std::move(non_null_indices).Build());
+ }
+ case SearchValidationResult::kAllData:
+ return RangeOrBitVector(Range(0, indices.size));
+ case SearchValidationResult::kOk:
+ break;
+ }
}
BitVector::Builder storage_non_null(indices.size);
diff --git a/src/trace_processor/importers/common/BUILD.gn b/src/trace_processor/importers/common/BUILD.gn
index 823edd8..8b66fb3 100644
--- a/src/trace_processor/importers/common/BUILD.gn
+++ b/src/trace_processor/importers/common/BUILD.gn
@@ -28,6 +28,7 @@
"clock_converter.h",
"clock_tracker.cc",
"clock_tracker.h",
+ "create_mapping_params.h",
"deobfuscation_mapping_table.cc",
"deobfuscation_mapping_table.h",
"event_tracker.cc",
@@ -36,6 +37,8 @@
"flow_tracker.h",
"global_args_tracker.cc",
"global_args_tracker.h",
+ "mapping_tracker.cc",
+ "mapping_tracker.h",
"metadata_tracker.cc",
"metadata_tracker.h",
"process_tracker.cc",
@@ -51,6 +54,8 @@
"trace_parser.cc",
"track_tracker.cc",
"track_tracker.h",
+ "virtual_memory_mapping.cc",
+ "virtual_memory_mapping.h",
]
public_deps = [
":trace_parser_hdr",
@@ -70,8 +75,8 @@
"../../storage",
"../../tables:tables",
"../../types",
+ "../../util:build_id",
"../../util:profiler_util",
- "../../util:stack_traces_util",
"../fuchsia:fuchsia_record",
"../systrace:systrace_line",
]
diff --git a/src/trace_processor/importers/common/address_range.h b/src/trace_processor/importers/common/address_range.h
index 817ed9f..5d7f430 100644
--- a/src/trace_processor/importers/common/address_range.h
+++ b/src/trace_processor/importers/common/address_range.h
@@ -18,8 +18,10 @@
#define SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_ADDRESS_RANGE_H_
#include <algorithm>
+
#include <cstdint>
#include <map>
+#include <set>
#include <tuple>
#include <utility>
@@ -33,6 +35,29 @@
// Note: This means that you can not have a range containing int64_max
class AddressRange {
public:
+ struct CompareByEnd {
+ // Allow heterogeneous lookups (https://abseil.io/tips/144)
+ using is_transparent = void;
+ // Keeps ranges sorted by end address
+ bool operator()(const AddressRange& lhs, const AddressRange& rhs) const {
+ return lhs.end() < rhs.end();
+ }
+
+ // Overload to implement PC lookup via upper_bound.
+ bool operator()(const AddressRange& lhs, uint64_t pc) const {
+ return lhs.end() < pc;
+ }
+
+ // Overload to implement PC lookup via upper_bound.
+ bool operator()(uint64_t pc, const AddressRange& rhs) const {
+ return pc < rhs.end();
+ }
+ };
+
+ static constexpr AddressRange FromStartAndSize(uint64_t start,
+ uint64_t size) {
+ return AddressRange(start, start + size);
+ }
constexpr AddressRange() : AddressRange(0, 0) {}
constexpr AddressRange(uint64_t start, uint64_t end)
@@ -62,7 +87,8 @@
// there exists a point such that Contains(point) would return true for both
// ranges.
constexpr bool Overlaps(const AddressRange& other) const {
- return start_ < other.end_ && other.start_ < end_;
+ return !empty() && !other.empty() && start_ < other.end_ &&
+ other.start_ < end_;
}
// Two ranges are the same is their respective limits are the same, that is A
@@ -91,49 +117,119 @@
uint64_t end_;
};
+// Contains unique collection of addresses. These addresses are kept as
+// sorted collection of non contiguous and non overlapping AddressRange
+// instances. As addresses are added or removed these AddressRange might be
+// merged or spliced as needed to keep the ranges non contiguous and non
+// overlapping.
+class AddressSet {
+ public:
+ // TODO(carlscab): Consider using base::FlatSet. As of now this class is used
+ // so little that it does not really matter.
+ using Impl = std::set<AddressRange, AddressRange::CompareByEnd>;
+
+ using value_type = typename Impl::value_type;
+ using const_iterator = typename Impl::const_iterator;
+ using size_type = typename Impl::size_type;
+
+ const_iterator begin() const { return ranges_.begin(); }
+ const_iterator end() const { return ranges_.end(); }
+
+ // Adds all the addresses in the given range to the set.
+ void Add(AddressRange range) {
+ if (range.empty()) {
+ return;
+ }
+ uint64_t start = range.start();
+ uint64_t end = range.end();
+ // Note lower_bound here as we might need to merge with the range just
+ // before.
+ auto it = ranges_.lower_bound(start);
+
+ PERFETTO_DCHECK(it == ranges_.end() || range.start() <= it->end());
+
+ while (it != ranges_.end() && range.end() >= it->start()) {
+ start = std::min(start, it->start());
+ end = std::max(end, it->end());
+ it = ranges_.erase(it);
+ }
+ ranges_.emplace_hint(it, AddressRange(start, end));
+ }
+
+ // Removes all the addresses in the given range from the set.
+ void Remove(AddressRange range) {
+ if (range.empty()) {
+ return;
+ }
+ auto it = ranges_.upper_bound(range.start());
+ PERFETTO_DCHECK(it == ranges_.end() || range.start() < it->end());
+
+ while (it != ranges_.end() && range.end() > it->start()) {
+ if (range.start() > it->start()) {
+ // range.start() is contained in *it. Split *it at range.start() into
+ // two ranges. Continue the loop at the second of them.
+ PERFETTO_DCHECK(it->Contains(range.start()));
+ auto old = *it;
+ it = ranges_.erase(it);
+ ranges_.emplace_hint(it, old.start(), range.start());
+ it = ranges_.emplace_hint(it, range.start(), old.end());
+ } else if (range.end() < it->end()) {
+ // range.end() is contained in *it. Split *it at range.end() into two
+ // ranges. The first of them needs to be deleted.
+ PERFETTO_DCHECK(it->Contains(range.end()));
+ auto old_end = it->end();
+ it = ranges_.erase(it);
+ ranges_.emplace_hint(it, range.end(), old_end);
+ } else {
+ // range fully contains *it, so it can be removed
+ PERFETTO_DCHECK(range.Contains(*it));
+ it = ranges_.erase(it);
+ }
+ }
+ }
+
+ bool operator==(const AddressSet& o) const { return ranges_ == o.ranges_; }
+ bool operator!=(const AddressSet& o) const { return ranges_ != o.ranges_; }
+
+ private:
+ // Invariants:
+ // * There are no overlapping ranges.
+ // * There are no empty ranges.
+ // * There are no two ranges a, b where a.end == b.start, that is there are
+ // no contiguous mappings.
+ // * Ranges are sorted by end
+ // Thus lookups are O(log N) and point lookups are trivial using upper_bound()
+ Impl ranges_;
+};
+
// Maps AddressRange instances to a given value. These AddressRange instances
// (basically the keys of the map) will never overlap, as insertions of
// overlapping ranges will always fail.
template <typename Value>
class AddressRangeMap {
public:
- struct CompareByEnd {
- // Allow heterogeneous lookups (https://abseil.io/tips/144)
- using is_transparent = void;
- // Keeps ranges sorted by end address
- bool operator()(const AddressRange& lhs, const AddressRange& rhs) const {
- return lhs.end() < rhs.end();
- }
-
- // Overload to implement PC lookup via upper_bound.
- bool operator()(const AddressRange& lhs, uint64_t pc) const {
- return lhs.end() < pc;
- }
-
- // Overload to implement PC lookup via upper_bound.
- bool operator()(uint64_t pc, const AddressRange& rhs) const {
- return pc < rhs.end();
- }
- };
-
- using Impl = std::map<AddressRange, Value, CompareByEnd>;
+ using Impl = std::map<AddressRange, Value, AddressRange::CompareByEnd>;
using value_type = typename Impl::value_type;
using iterator = typename Impl::iterator;
using const_iterator = typename Impl::const_iterator;
using size_type = typename Impl::size_type;
- // Fails if the new range overlaps with any existing one.
+ // Fails if the new range overlaps with any existing one or when inserting an
+ // empty range (as there is effectively no key to map from).
template <typename... Args>
- std::pair<iterator, bool> Emplace(AddressRange range, Args&&... args) {
+ bool Emplace(AddressRange range, Args&&... args) {
+ if (range.empty()) {
+ return false;
+ }
auto it = ranges_.upper_bound(range.start());
if (it != ranges_.end() && range.end() > it->first.start()) {
- return {it, false};
+ return false;
}
- return {ranges_.emplace_hint(
- it, std::piecewise_construct, std::forward_as_tuple(range),
- std::forward_as_tuple(std::forward<Args>(args)...)),
- true};
+ ranges_.emplace_hint(it, std::piecewise_construct,
+ std::forward_as_tuple(range),
+ std::forward_as_tuple(std::forward<Args>(args)...));
+ return true;
}
// Finds the map entry that fully contains the given `range` or `end()` if not
@@ -181,13 +277,15 @@
// Emplaces a new value into the map by first deleting all overlapping
// intervals. It takes an optional (set to nullptr to ignore) callback `cb`
// that will be called for each deleted map entry.
- // ATTENTION: `range` can not be empty. Supporting it would complicate things
- // too much for a not needed use case.
+ // Returns true on success, fails if the new range is empty (as there is
+ // effectively no key to map from).
template <typename Callback, typename... Args>
- void DeleteOverlapsAndEmplace(Callback cb,
+ bool DeleteOverlapsAndEmplace(Callback cb,
AddressRange range,
Args&&... args) {
- PERFETTO_CHECK(!range.empty());
+ if (range.empty()) {
+ return false;
+ }
auto it = ranges_.upper_bound(range.start());
PERFETTO_DCHECK(it == ranges_.end() || range.start() < it->first.end());
@@ -199,21 +297,38 @@
ranges_.emplace_hint(it, std::piecewise_construct,
std::forward_as_tuple(range),
std::forward_as_tuple(std::forward<Args>(args)...));
+ return true;
}
// Same as above but without a callback.
- template <typename Callback, typename... Args>
- void DeleteOverlapsAndEmplace(AddressRange range, Args&&... args) {
+ template <typename... Args>
+ bool DeleteOverlapsAndEmplace(AddressRange range, Args&&... args) {
struct NoOp {
void operator()(std::pair<const AddressRange, Value>&) {}
};
- DeleteOverlapsAndEmplace(NoOp(), range, std::forward<Args>(args)...);
+ return DeleteOverlapsAndEmplace(NoOp(), range, std::forward<Args>(args)...);
+ }
+
+ // Calls `cb` for each entry in the map that overlaps the given `range`. That
+ // is, there is a point so for which `AddressRange::Contains` returns true for
+ // both the entry and the given `range'
+ template <typename Callback>
+ void ForOverlaps(AddressRange range, Callback cb) {
+ if (range.empty()) {
+ return;
+ }
+ for (auto it = ranges_.upper_bound(range.start());
+ it != ranges_.end() && range.end() > it->first.start(); ++it) {
+ cb(*it);
+ }
}
private:
- // Invariant: There are no overlapping ranges.
- // Which makes lookups O(log N). Also, ranges are sorted by end which makes
- // point lookups trivial using upper_bound()
+ // Invariants:
+ // * There are no overlapping ranges.
+ // * There are no empty ranges.
+ // * Ranges are sorted by end
+ // Thus lookups are O(log N) and point lookups are trivial using upper_bound()
Impl ranges_;
};
diff --git a/src/trace_processor/importers/common/address_range_unittest.cc b/src/trace_processor/importers/common/address_range_unittest.cc
index 2ae43ff..35f67ae 100644
--- a/src/trace_processor/importers/common/address_range_unittest.cc
+++ b/src/trace_processor/importers/common/address_range_unittest.cc
@@ -38,6 +38,7 @@
namespace {
+using ::testing::_;
using ::testing::A;
using ::testing::AllOf;
using ::testing::ElementsAre;
@@ -49,6 +50,11 @@
using ::testing::Pointee;
using ::testing::SizeIs;
+MATCHER_P2(IteratorPointsTo, container, matcher, "") {
+ return ExplainMatchResult(Ne(container.end()), arg, result_listener) &&
+ ExplainMatchResult(matcher, *arg, result_listener);
+}
+
auto AppendRangesTo(std::vector<AddressRange>& ranges) {
return [&ranges](std::pair<const AddressRange, int>& e) {
ranges.push_back(e.first);
@@ -115,6 +121,18 @@
EXPECT_THAT(AddressRange(0, 10).IntersectWith(AddressRange()), IsEmpty());
}
+TEST(AddressRange, Overlap) {
+ EXPECT_FALSE(AddressRange(0, 10).Overlaps(AddressRange(5, 5)));
+ EXPECT_FALSE(AddressRange(5, 5).Overlaps(AddressRange(0, 10)));
+ EXPECT_FALSE(AddressRange(0, 10).Overlaps(AddressRange(10, 20)));
+ EXPECT_FALSE(AddressRange(10, 20).Overlaps(AddressRange(0, 10)));
+
+ EXPECT_TRUE(AddressRange(0, 10).Overlaps(AddressRange(9, 10)));
+ EXPECT_TRUE(AddressRange(10, 20).Overlaps(AddressRange(0, 11)));
+ EXPECT_TRUE(AddressRange(0, 10).Overlaps(AddressRange(5, 6)));
+ EXPECT_TRUE(AddressRange(0, 10).Overlaps(AddressRange(5, 20)));
+}
+
TEST(AddressRangeMap, Empty) {
AddressRangeMap<int> empty;
EXPECT_THAT(empty, IsEmpty());
@@ -122,25 +140,43 @@
TEST(AddressRangeMap, EmplaceFailsForOverlaps) {
AddressRangeMap<int> map;
- ASSERT_TRUE(map.Emplace(AddressRange(10, 20)).second);
+ ASSERT_TRUE(map.Emplace(AddressRange(10, 20), 42));
- EXPECT_FALSE(map.Emplace(AddressRange(10, 20)).second);
- EXPECT_FALSE(map.Emplace(AddressRange(11, 19)).second);
- EXPECT_FALSE(map.Emplace(AddressRange(0, 11)).second);
- EXPECT_FALSE(map.Emplace(AddressRange(19, 30)).second);
- EXPECT_THAT(map, SizeIs(1));
+ EXPECT_FALSE(map.Emplace(AddressRange(10, 20)));
+ EXPECT_FALSE(map.Emplace(AddressRange(11, 19)));
+ EXPECT_FALSE(map.Emplace(AddressRange(0, 11)));
+ EXPECT_FALSE(map.Emplace(AddressRange(19, 30)));
+ EXPECT_THAT(map, ElementsAre(Pair(AddressRange(10, 20), 42)));
}
TEST(AddressRangeMap, EmplaceSucceedsForNonOverlaps) {
AddressRangeMap<int> map;
- EXPECT_TRUE(map.Emplace(AddressRange(10, 20)).second);
- EXPECT_TRUE(map.Emplace(AddressRange(0, 10)).second);
- EXPECT_TRUE(map.Emplace(AddressRange(20, 30)).second);
+ EXPECT_TRUE(map.Emplace(AddressRange(10, 20)));
+ EXPECT_TRUE(map.Emplace(AddressRange(0, 10)));
+ EXPECT_TRUE(map.Emplace(AddressRange(20, 30)));
EXPECT_THAT(map, SizeIs(3));
}
+TEST(AddressRangeMap, EmplaceFailsForEmptyRange) {
+ AddressRangeMap<int> map;
+
+ EXPECT_FALSE(map.Emplace(AddressRange(0, 0)));
+ EXPECT_FALSE(map.Emplace(AddressRange(100, 100)));
+
+ EXPECT_THAT(map, IsEmpty());
+}
+
+TEST(AddressRangeMap, DeleteOverlapsAndEmplaceFailsForEmptyRange) {
+ AddressRangeMap<int> map;
+ EXPECT_TRUE(map.Emplace(AddressRange(0, 10), 42));
+ EXPECT_FALSE(map.Emplace(AddressRange(0, 0)));
+ EXPECT_FALSE(map.Emplace(AddressRange(100, 100)));
+
+ EXPECT_THAT(map, ElementsAre(Pair(AddressRange(0, 10), 42)));
+}
+
TEST(AddressRangeMap, FindAddress) {
AddressRangeMap<int> map;
map.Emplace(AddressRange(0, 10), 0);
@@ -174,30 +210,33 @@
TEST(AddressRangeMap, FindRangeThatContains) {
AddressRangeMap<int> map;
- const auto it_1 = map.Emplace(AddressRange(0, 10), 0).first;
- const auto it_2 = map.Emplace(AddressRange(10, 20), 1).first;
- const auto it_3 = map.Emplace(AddressRange(25, 30), 2).first;
- const auto end = map.end();
+ map.Emplace(AddressRange(0, 10), 0);
+ map.Emplace(AddressRange(10, 20), 1);
+ map.Emplace(AddressRange(25, 30), 2);
- EXPECT_THAT(map.FindRangeThatContains({0, 10}), Eq(it_1));
- EXPECT_THAT(map.FindRangeThatContains({0, 1}), Eq(it_1));
- EXPECT_THAT(map.FindRangeThatContains({3, 4}), Eq(it_1));
- EXPECT_THAT(map.FindRangeThatContains({9, 10}), Eq(it_1));
+ auto match_1 = IteratorPointsTo(map, Pair(AddressRange(0, 10), 0));
+ auto match_2 = IteratorPointsTo(map, Pair(AddressRange(10, 20), 1));
+ auto match_3 = IteratorPointsTo(map, Pair(AddressRange(25, 30), 2));
- EXPECT_THAT(map.FindRangeThatContains({10, 11}), Eq(it_2));
- EXPECT_THAT(map.FindRangeThatContains({11, 12}), Eq(it_2));
- EXPECT_THAT(map.FindRangeThatContains({19, 20}), Eq(it_2));
- EXPECT_THAT(map.FindRangeThatContains({10, 20}), Eq(it_2));
+ EXPECT_THAT(map.FindRangeThatContains({0, 10}), match_1);
+ EXPECT_THAT(map.FindRangeThatContains({0, 1}), match_1);
+ EXPECT_THAT(map.FindRangeThatContains({3, 4}), match_1);
+ EXPECT_THAT(map.FindRangeThatContains({9, 10}), match_1);
- EXPECT_THAT(map.FindRangeThatContains({25, 26}), Eq(it_3));
- EXPECT_THAT(map.FindRangeThatContains({26, 27}), Eq(it_3));
- EXPECT_THAT(map.FindRangeThatContains({29, 30}), Eq(it_3));
- EXPECT_THAT(map.FindRangeThatContains({25, 30}), Eq(it_3));
+ EXPECT_THAT(map.FindRangeThatContains({10, 11}), match_2);
+ EXPECT_THAT(map.FindRangeThatContains({11, 12}), match_2);
+ EXPECT_THAT(map.FindRangeThatContains({19, 20}), match_2);
+ EXPECT_THAT(map.FindRangeThatContains({10, 20}), match_2);
- EXPECT_THAT(map.FindRangeThatContains({9, 11}), Eq(end));
- EXPECT_THAT(map.FindRangeThatContains({20, 21}), Eq(end));
- EXPECT_THAT(map.FindRangeThatContains({24, 25}), Eq(end));
- EXPECT_THAT(map.FindRangeThatContains({14, 27}), Eq(end));
+ EXPECT_THAT(map.FindRangeThatContains({25, 26}), match_3);
+ EXPECT_THAT(map.FindRangeThatContains({26, 27}), match_3);
+ EXPECT_THAT(map.FindRangeThatContains({29, 30}), match_3);
+ EXPECT_THAT(map.FindRangeThatContains({25, 30}), match_3);
+
+ EXPECT_THAT(map.FindRangeThatContains({9, 11}), Eq(map.end()));
+ EXPECT_THAT(map.FindRangeThatContains({20, 21}), Eq(map.end()));
+ EXPECT_THAT(map.FindRangeThatContains({24, 25}), Eq(map.end()));
+ EXPECT_THAT(map.FindRangeThatContains({14, 27}), Eq(map.end()));
}
TEST(AddressRangeMap, DeleteOverlapsAndEmplace) {
@@ -285,6 +324,114 @@
EXPECT_THAT(map, ElementsAre(entry(5, 11, 5), entry(25, 30, 2)));
}
+TEST(AddressRangeMap, ForOverlapsEmptyRangeDoesNothing) {
+ AddressRangeMap<int> map;
+ map.Emplace(AddressRange(0, 10), 0);
+ map.Emplace(AddressRange(10, 20), 1);
+ map.Emplace(AddressRange(25, 30), 2);
+
+ MockFunction<void(AddressRangeMap<int>::value_type&)> cb;
+ EXPECT_CALL(cb, Call).Times(0);
+
+ map.ForOverlaps(AddressRange(5, 5), cb.AsStdFunction());
+}
+
+TEST(AddressRangeMap, ForOverlaps) {
+ AddressRangeMap<int> map;
+ map.Emplace(AddressRange(0, 10), 0);
+ map.Emplace(AddressRange(10, 20), 1);
+ map.Emplace(AddressRange(20, 30), 2);
+ map.Emplace(AddressRange(35, 40), 3);
+ map.Emplace(AddressRange(40, 50), 4);
+
+ MockFunction<void(AddressRangeMap<int>::value_type&)> cb;
+ EXPECT_CALL(cb, Call(Pair(AddressRange(10, 20), 1)));
+ EXPECT_CALL(cb, Call(Pair(AddressRange(20, 30), 2)));
+ EXPECT_CALL(cb, Call(Pair(AddressRange(35, 40), 3)));
+
+ map.ForOverlaps(AddressRange(15, 36), cb.AsStdFunction());
+}
+
+TEST(AddressSet, Empty) {
+ AddressSet empty;
+ EXPECT_THAT(empty, ElementsAre());
+}
+
+TEST(AddressSet, EmptyRangesAreNotAdded) {
+ AddressSet empty;
+
+ empty.Add({0, 0});
+ empty.Add({10, 10});
+
+ EXPECT_THAT(empty, ElementsAre());
+}
+
+TEST(AddressSet, NonOverlapingNonContiguousAreNotMerged) {
+ AddressSet set;
+ set.Add({0, 10});
+ set.Add({11, 20});
+
+ EXPECT_THAT(set, ElementsAre(AddressRange(0, 10), AddressRange(11, 20)));
+}
+
+TEST(AddressSet, ContiguousAreMerged) {
+ AddressSet set;
+ set.Add({0, 10});
+ set.Add({30, 40});
+ set.Add({10, 30});
+
+ EXPECT_THAT(set, ElementsAre(AddressRange(0, 40)));
+}
+
+TEST(AddressSet, OverlapsAreMerged) {
+ AddressSet set;
+ set.Add({0, 10});
+ set.Add({30, 40});
+ set.Add({5, 35});
+
+ EXPECT_THAT(set, ElementsAre(AddressRange(0, 40)));
+}
+
+TEST(AddressSet, SpliceRemove) {
+ AddressSet set;
+ set.Add({0, 10});
+ set.Remove({2, 5});
+
+ EXPECT_THAT(set, ElementsAre(AddressRange(0, 2), AddressRange(5, 10)));
+}
+
+TEST(AddressSet, PartialRemove) {
+ AddressSet set;
+ set.Add({0, 10});
+ set.Remove({0, 2});
+ set.Remove({8, 10});
+
+ EXPECT_THAT(set, ElementsAre(AddressRange(2, 8)));
+}
+
+TEST(AddressSet, MultipleRemove) {
+ AddressSet set;
+ set.Add({0, 10});
+ set.Add({12, 15});
+ set.Add({20, 30});
+ set.Remove({5, 25});
+
+ EXPECT_THAT(set, ElementsAre(AddressRange(0, 5), AddressRange(25, 30)));
+}
+
+TEST(AddressSet, RemoveEmptyRangeDoesNothing) {
+ AddressSet set;
+ set.Add({0, 10});
+ set.Add({20, 30});
+
+ set.Remove({0, 0});
+ set.Remove({2, 2});
+ set.Remove({10, 10});
+ set.Remove({11, 11});
+
+ EXPECT_THAT(set, ElementsAre(AddressRange(0, 10), AddressRange(20, 30)));
+}
+
} // namespace
} // namespace trace_processor
} // namespace perfetto
diff --git a/src/trace_processor/importers/common/clock_tracker.h b/src/trace_processor/importers/common/clock_tracker.h
index a812593..117e0e1 100644
--- a/src/trace_processor/importers/common/clock_tracker.h
+++ b/src/trace_processor/importers/common/clock_tracker.h
@@ -29,14 +29,9 @@
#include "perfetto/base/logging.h"
#include "perfetto/ext/base/status_or.h"
-#include "perfetto/ext/base/string_utils.h"
#include "src/trace_processor/importers/common/metadata_tracker.h"
-#include "src/trace_processor/storage/trace_storage.h"
#include "src/trace_processor/types/trace_processor_context.h"
-#include "protos/perfetto/common/builtin_clock.pbzero.h"
-#include "protos/perfetto/trace/clock_snapshot.pbzero.h"
-
namespace perfetto {
namespace trace_processor {
@@ -154,7 +149,7 @@
// Converts a sequence-scoped clock ids to a global clock id that can be
// passed as argument to ClockTracker functions.
- static ClockId SeqenceToGlobalClock(uint32_t seq_id, uint32_t clock_id) {
+ static ClockId SequenceToGlobalClock(uint32_t seq_id, uint32_t clock_id) {
PERFETTO_DCHECK(IsSequenceClock(clock_id));
return (static_cast<int64_t>(seq_id) << 32) | clock_id;
}
diff --git a/src/trace_processor/importers/common/clock_tracker_unittest.cc b/src/trace_processor/importers/common/clock_tracker_unittest.cc
index 717592f..51bd020 100644
--- a/src/trace_processor/importers/common/clock_tracker_unittest.cc
+++ b/src/trace_processor/importers/common/clock_tracker_unittest.cc
@@ -208,10 +208,10 @@
TEST_F(ClockTrackerTest, SequenceScopedClocks) {
ct_.AddSnapshot({{MONOTONIC, 1000}, {BOOTTIME, 100000}});
- ClockTracker::ClockId c64_1 = ct_.SeqenceToGlobalClock(1, 64);
- ClockTracker::ClockId c65_1 = ct_.SeqenceToGlobalClock(1, 65);
- ClockTracker::ClockId c66_1 = ct_.SeqenceToGlobalClock(1, 66);
- ClockTracker::ClockId c66_2 = ct_.SeqenceToGlobalClock(2, 64);
+ ClockTracker::ClockId c64_1 = ct_.SequenceToGlobalClock(1, 64);
+ ClockTracker::ClockId c65_1 = ct_.SequenceToGlobalClock(1, 65);
+ ClockTracker::ClockId c66_1 = ct_.SequenceToGlobalClock(1, 66);
+ ClockTracker::ClockId c66_2 = ct_.SequenceToGlobalClock(2, 64);
ct_.AddSnapshot(
{{MONOTONIC, 10000},
diff --git a/src/trace_processor/importers/common/create_mapping_params.h b/src/trace_processor/importers/common/create_mapping_params.h
new file mode 100644
index 0000000..7aba456
--- /dev/null
+++ b/src/trace_processor/importers/common/create_mapping_params.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_COMMON_CREATE_MAPPING_PARAMS_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_CREATE_MAPPING_PARAMS_H_
+
+#include <cstddef>
+#include <cstdint>
+#include <optional>
+#include <string>
+#include <tuple>
+
+#include "perfetto/ext/base/hash.h"
+#include "src/trace_processor/importers/common/address_range.h"
+#include "src/trace_processor/util/build_id.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+struct CreateMappingParams {
+ AddressRange memory_range;
+ // This is the offset into the file that has been mapped at
+ // memory_range.start()
+ uint64_t exact_offset = 0;
+ // 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.
+ uint64_t start_offset = 0;
+ // This can only be read out of the actual ELF file.
+ uint64_t load_bias = 0;
+ std::string name;
+ std::optional<BuildId> build_id;
+
+ auto ToTuple() const {
+ return std::tie(memory_range, exact_offset, start_offset, load_bias, name,
+ build_id);
+ }
+
+ bool operator==(const CreateMappingParams& o) const {
+ return ToTuple() == o.ToTuple();
+ }
+
+ struct Hasher {
+ size_t operator()(const CreateMappingParams& p) const {
+ base::Hasher h;
+ h.UpdateAll(p.memory_range.start(), p.memory_range.end(), p.exact_offset,
+ p.start_offset, p.load_bias, p.name);
+ if (p.build_id) {
+ h.Update(*p.build_id);
+ }
+ return static_cast<size_t>(h.digest());
+ }
+ };
+};
+
+} // namespace trace_processor
+} // namespace perfetto
+
+#endif // SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_CREATE_MAPPING_PARAMS_H_
diff --git a/src/trace_processor/importers/common/mapping_tracker.cc b/src/trace_processor/importers/common/mapping_tracker.cc
new file mode 100644
index 0000000..13b8274
--- /dev/null
+++ b/src/trace_processor/importers/common/mapping_tracker.cc
@@ -0,0 +1,169 @@
+/*
+ * 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/mapping_tracker.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+#include <utility>
+
+#include "perfetto/ext/base/string_view.h"
+#include "src/trace_processor/importers/common/address_range.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+#include "src/trace_processor/util/build_id.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace {
+
+bool IsKernelModule(base::StringView name) {
+ return !name.StartsWith("[kernel.kallsyms]");
+}
+
+} // namespace
+
+JitDelegate::~JitDelegate() = default;
+
+template <typename MappingImpl>
+MappingImpl& MappingTracker::AddMapping(std::unique_ptr<MappingImpl> mapping) {
+ auto ptr = mapping.get();
+ PERFETTO_CHECK(
+ mappings_by_id_.Insert(ptr->mapping_id(), std::move(mapping)).second);
+
+ mappings_by_name_and_build_id_[NameAndBuildId{base::StringView(ptr->name()),
+ ptr->build_id()}]
+ .push_back(ptr);
+
+ return *ptr;
+}
+
+KernelMemoryMapping& MappingTracker::CreateKernelMemoryMapping(
+ CreateMappingParams params) {
+ // TODO(carlscab): Guess build_id if not provided. Some tools like simpleperf
+ // add a mapping file_name ->build_id that we could use here
+
+ const bool is_module = IsKernelModule(base::StringView(params.name));
+
+ if (!is_module && kernel_ != nullptr) {
+ PERFETTO_CHECK(params.memory_range == kernel_->memory_range());
+ return *kernel_;
+ }
+
+ std::unique_ptr<KernelMemoryMapping> mapping(
+ new KernelMemoryMapping(context_, std::move(params)));
+
+ if (is_module) {
+ // TODO(carlscab): Overlaps not supported (for now?). Should be fine for
+ // kernel.
+ PERFETTO_CHECK(
+ kernel_modules_.Emplace(mapping->memory_range(), mapping.get()));
+ } else {
+ kernel_ = mapping.get();
+ }
+
+ return AddMapping(std::move(mapping));
+}
+
+UserMemoryMapping& MappingTracker::CreateUserMemoryMapping(
+ UniquePid upid,
+ CreateMappingParams params) {
+ // TODO(carlscab): Guess build_id if not provided. Some tools like simpleperf
+ // add a mapping file_name ->build_id that we could use here
+
+ const AddressRange mapping_range = params.memory_range;
+ std::unique_ptr<UserMemoryMapping> mapping(
+ new UserMemoryMapping(context_, upid, std::move(params)));
+ // TODO(carlscab): Overlaps not supported (for now?).
+ PERFETTO_CHECK(user_memory_[upid].Emplace(mapping_range, mapping.get()));
+
+ jit_delegates_[upid].ForOverlaps(
+ mapping_range, [&](std::pair<const AddressRange, JitDelegate*>& entry) {
+ const auto& jit_range = entry.first;
+ JitDelegate* jit_delegate = entry.second;
+ PERFETTO_CHECK(jit_range.Contains(mapping_range));
+ mapping->SetJitDelegate(jit_delegate);
+ });
+
+ return AddMapping(std::move(mapping));
+}
+
+KernelMemoryMapping* MappingTracker::FindKernelMappingForAddress(
+ uint64_t address) const {
+ if (auto it = kernel_modules_.Find(address); it != kernel_modules_.end()) {
+ return it->second;
+ }
+ if (kernel_ && kernel_->memory_range().Contains(address)) {
+ return kernel_;
+ }
+ return nullptr;
+}
+
+UserMemoryMapping* MappingTracker::FindUserMappingForAddress(
+ UniquePid upid,
+ uint64_t address) const {
+ if (auto* vm = user_memory_.Find(upid); vm) {
+ if (auto it = vm->Find(address); it != vm->end()) {
+ return it->second;
+ }
+ }
+
+ if (auto* delegates = jit_delegates_.Find(upid); delegates) {
+ if (auto it = delegates->Find(address); it != delegates->end()) {
+ return it->second->CreateMapping();
+ }
+ }
+
+ return nullptr;
+}
+
+std::vector<VirtualMemoryMapping*> MappingTracker::FindMappings(
+ base::StringView name,
+ const BuildId& build_id) const {
+ if (auto res = mappings_by_name_and_build_id_.Find({name, build_id});
+ res != nullptr) {
+ return *res;
+ }
+ return {};
+}
+
+VirtualMemoryMapping& MappingTracker::InternMemoryMapping(
+ CreateMappingParams params) {
+ if (auto* mapping = interned_mappings_.Find(params); mapping) {
+ return **mapping;
+ }
+
+ std::unique_ptr<VirtualMemoryMapping> mapping(
+ new VirtualMemoryMapping(context_, params));
+ interned_mappings_.Insert(std::move(params), mapping.get());
+ return AddMapping(std::move(mapping));
+}
+
+void MappingTracker::AddJitRange(UniquePid upid,
+ AddressRange jit_range,
+ JitDelegate* delegate) {
+ // TODO(carlscab): Deal with overlaps
+ jit_delegates_[upid].DeleteOverlapsAndEmplace(jit_range, delegate);
+ user_memory_[upid].ForOverlaps(
+ jit_range, [&](std::pair<const AddressRange, UserMemoryMapping*>& entry) {
+ PERFETTO_CHECK(jit_range.Contains(entry.first));
+ entry.second->SetJitDelegate(delegate);
+ });
+}
+
+} // 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
new file mode 100644
index 0000000..95dc355
--- /dev/null
+++ b/src/trace_processor/importers/common/mapping_tracker.h
@@ -0,0 +1,168 @@
+/*
+ * 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_MAPPING_TRACKER_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_MAPPING_TRACKER_H_
+
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+#include <optional>
+#include <vector>
+
+#include "perfetto/ext/base/flat_hash_map.h"
+#include "perfetto/ext/base/hash.h"
+#include "perfetto/ext/base/string_view.h"
+#include "src/trace_processor/importers/common/address_range.h"
+#include "src/trace_processor/importers/common/virtual_memory_mapping.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+#include "src/trace_processor/util/build_id.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+// Api used to forward frame interning requests for frames that fall in a
+// jitted memory region.
+// MappingTracker allows other trackers to register ranges of memory for
+// which they need to control when a new frame is created. Jitted code can
+// move in memory over time, so the same program counter might refer to
+// different functions at different point in time. MappingTracker does
+// not keep track of such moves but instead delegates the creation of jitted
+// frames to a delegate.
+class JitDelegate {
+ public:
+ virtual ~JitDelegate();
+ // Forward frame interning request.
+ // Implementations are free to intern the frame as needed.
+ // Returns frame_id, and whether a new row as created or not.
+ virtual std::pair<FrameId, bool> InternFrame(
+ VirtualMemoryMapping* mapping,
+ uint64_t rel_pc,
+ base::StringView function_name) = 0;
+
+ // Simpleperf does not emit mmap events for jitted ranges (actually for non
+ // file backed executable mappings). So have a way to generate a mapping on
+ // the fly for FindMapping requests in a jitted region with no associated
+ // mapping.
+ virtual UserMemoryMapping* CreateMapping() = 0;
+};
+
+// Keeps track of all aspects relative to memory mappings.
+// This class keeps track of 3 types of mappings: UserMemoryMapping,
+// KernelMemoryMapping and others. The others are used to represent mapping
+// where we do not have enough information to determine what type of
+// mapping (user, kernel) we are dealing with. This is usually the case with
+// data sources that do not provide enough information about the mappings.
+//
+// TODO(carlscab): Hopefully we can slowly get rid of cases where these other
+// mappings are needed. The biggest blocker right now is determining the upid.
+// we could infer this from the actual samples that use said mapping (those
+// usually have a pid attached). So we would need to have a "fake" mapping that
+// actually materializes when we see a sample with a pid.
+//
+// ATTENTION: No overlaps allowed (for now). Eventually the order in which
+// mappings are create will matter as newer mappings will delete old ones.
+// This is how tools like linux perf behave, mmap event have a timestamp
+// associated and there are no "delete events" just new mmap events that
+// overlap (to be deleted) mappings.
+class MappingTracker {
+ public:
+ explicit MappingTracker(TraceProcessorContext* context) : context_(context) {}
+
+ // Create a new kernel space mapping. Returned reference will be valid for the
+ // duration of this instance.
+ KernelMemoryMapping& CreateKernelMemoryMapping(CreateMappingParams params);
+
+ // Create a new user space mapping. Returned reference will be valid for the
+ // duration of this instance.
+ UserMemoryMapping& CreateUserMemoryMapping(UniquePid upid,
+ CreateMappingParams params);
+
+ // Create an "other" mapping. Returned reference will be valid for the
+ // duration of this instance.
+ VirtualMemoryMapping& InternMemoryMapping(CreateMappingParams params);
+
+ // Given an absolute address find the kernel mapping where this address
+ // belongs to. Returns `nullptr` if none is found.
+ KernelMemoryMapping* FindKernelMappingForAddress(uint64_t address) const;
+
+ // Given an absolute address find the user mapping where this address
+ // belongs to. Returns `nullptr` if none is found.
+ UserMemoryMapping* FindUserMappingForAddress(UniquePid upid,
+ uint64_t address) const;
+
+ std::vector<VirtualMemoryMapping*> FindMappings(
+ base::StringView name,
+ const BuildId& build_id) const;
+
+ // Marks a range of memory as containing jitted code.
+ // If the added region overlaps with other existing ranges the latter are all
+ // deleted.
+ // Jitted ranges will only be applied to UserMemoryMappings
+ void AddJitRange(UniquePid upid, AddressRange range, JitDelegate* delegate);
+
+ private:
+ template <typename MappingImpl>
+ MappingImpl& AddMapping(std::unique_ptr<MappingImpl> mapping);
+
+ TraceProcessorContext* const context_;
+ base::FlatHashMap<MappingId, std::unique_ptr<VirtualMemoryMapping>>
+ mappings_by_id_;
+
+ base::FlatHashMap<CreateMappingParams,
+ VirtualMemoryMapping*,
+ CreateMappingParams::Hasher>
+ interned_mappings_;
+
+ struct NameAndBuildId {
+ base::StringView name;
+ std::optional<BuildId> build_id;
+
+ bool operator==(const NameAndBuildId& o) const {
+ return name == o.name && build_id == o.build_id;
+ }
+
+ bool operator!=(const NameAndBuildId& o) const { return !(*this == o); }
+
+ struct Hasher {
+ size_t operator()(const NameAndBuildId& o) const {
+ base::Hasher hasher;
+ hasher.Update(o.name);
+ if (o.build_id) {
+ hasher.Update(*o.build_id);
+ }
+ return static_cast<size_t>(hasher.digest());
+ }
+ };
+ };
+ base::FlatHashMap<NameAndBuildId,
+ std::vector<VirtualMemoryMapping*>,
+ NameAndBuildId::Hasher>
+ mappings_by_name_and_build_id_;
+
+ base::FlatHashMap<UniquePid, AddressRangeMap<UserMemoryMapping*>>
+ user_memory_;
+ AddressRangeMap<KernelMemoryMapping*> kernel_modules_;
+ KernelMemoryMapping* kernel_ = nullptr;
+
+ base::FlatHashMap<UniquePid, AddressRangeMap<JitDelegate*>> jit_delegates_;
+};
+
+} // namespace trace_processor
+} // namespace perfetto
+
+#endif // SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_MAPPING_TRACKER_H_
diff --git a/src/trace_processor/importers/common/stack_profile_tracker.cc b/src/trace_processor/importers/common/stack_profile_tracker.cc
index ad57523..799dc29 100644
--- a/src/trace_processor/importers/common/stack_profile_tracker.cc
+++ b/src/trace_processor/importers/common/stack_profile_tracker.cc
@@ -16,35 +16,18 @@
#include "src/trace_processor/importers/common/stack_profile_tracker.h"
-#include "perfetto/ext/base/string_utils.h"
+#include <cstddef>
+#include <cstdint>
+
#include "perfetto/ext/base/string_view.h"
#include "src/trace_processor/storage/trace_storage.h"
#include "src/trace_processor/tables/profiler_tables_py.h"
#include "src/trace_processor/types/trace_processor_context.h"
#include "src/trace_processor/util/profiler_util.h"
-#include "src/trace_processor/util/stack_traces_util.h"
namespace perfetto {
namespace trace_processor {
-namespace {
-std::string CleanBuildId(base::StringView build_id) {
- if (build_id.empty()) {
- return build_id.ToStdString();
- }
- // If the build_id is 33 characters long, we assume it's a Breakpad debug
- // identifier which is already in Hex and doesn't need conversion.
- // TODO(b/148109467): Remove workaround once all active Chrome versions
- // write raw bytes instead of a string as build_id.
- if (util::IsHexModuleId(build_id)) {
- return build_id.ToStdString();
- }
-
- return base::ToHex(build_id.data(), build_id.size());
-}
-
-} // namespace
-
std::vector<FrameId> StackProfileTracker::JavaFramesForName(
NameInPackage name) const {
if (const auto* frames = java_frames_for_name_.Find(name); frames) {
@@ -53,50 +36,6 @@
return {};
}
-std::vector<MappingId> StackProfileTracker::FindMappingRow(
- StringId name,
- StringId build_id) const {
- if (const auto* mappings =
- mappings_by_name_and_build_id_.Find(std::make_pair(name, build_id));
- mappings) {
- return *mappings;
- }
- return {};
-}
-
-std::vector<FrameId> StackProfileTracker::FindFrameIds(MappingId mapping_id,
- uint64_t rel_pc) const {
- if (const auto* frames =
- frame_by_mapping_and_rel_pc_.Find(std::make_pair(mapping_id, rel_pc));
- frames) {
- return *frames;
- }
- return {};
-}
-
-MappingId StackProfileTracker::InternMapping(
- const CreateMappingParams& params) {
- tables::StackProfileMappingTable::Row row;
- row.build_id = InternBuildId(params.build_id);
- row.exact_offset = static_cast<int64_t>(params.exact_offset);
- row.start_offset = static_cast<int64_t>(params.start_offset);
- row.start = static_cast<int64_t>(params.start);
- row.end = static_cast<int64_t>(params.end);
- row.load_bias = static_cast<int64_t>(params.load_bias);
- row.name = context_->storage->InternString(params.name);
-
- if (MappingId* id = mapping_unique_row_index_.Find(row); id) {
- return *id;
- }
-
- MappingId mapping_id =
- context_->storage->mutable_stack_profile_mapping_table()->Insert(row).id;
- mapping_unique_row_index_.Insert(row, mapping_id);
- mappings_by_name_and_build_id_[{row.name, row.build_id}].push_back(
- mapping_id);
- return mapping_id;
-}
-
CallsiteId StackProfileTracker::InternCallsite(
std::optional<CallsiteId> parent_callsite_id,
FrameId frame_id,
@@ -113,22 +52,12 @@
return callsite_id;
}
-FrameId StackProfileTracker::InternFrame(MappingId mapping_id,
- uint64_t rel_pc,
- base::StringView function_name) {
- tables::StackProfileFrameTable::Row row;
- row.mapping = mapping_id;
- row.rel_pc = static_cast<int64_t>(rel_pc);
- row.name = context_->storage->InternString(function_name);
-
- if (FrameId* id = frame_unique_row_index_.Find(row); id) {
- return *id;
- }
-
- FrameId frame_id =
- context_->storage->mutable_stack_profile_frame_table()->Insert(row).id;
- frame_unique_row_index_.Insert(row, frame_id);
- frame_by_mapping_and_rel_pc_[{mapping_id, rel_pc}].push_back(frame_id);
+void StackProfileTracker::OnFrameCreated(FrameId frame_id) {
+ auto frame =
+ *context_->storage->stack_profile_frame_table().FindById(frame_id);
+ const MappingId mapping_id = frame.mapping();
+ const StringId name_id = frame.name();
+ const auto function_name = context_->storage->GetString(name_id);
if (function_name.find('.') != base::StringView::npos) {
// Java frames always contain a '.'
@@ -139,21 +68,14 @@
std::optional<std::string> package =
PackageFromLocation(context_->storage.get(), mapping_name);
if (package) {
- NameInPackage nip{row.name, context_->storage->InternString(
- base::StringView(*package))};
+ NameInPackage nip{
+ name_id, context_->storage->InternString(base::StringView(*package))};
java_frames_for_name_[nip].push_back(frame_id);
} else if (mapping_name.find("/memfd:") == 0) {
- NameInPackage nip{row.name, context_->storage->InternString("memfd")};
+ NameInPackage nip{name_id, context_->storage->InternString("memfd")};
java_frames_for_name_[nip].push_back(frame_id);
}
}
-
- return frame_id;
-}
-
-StringId StackProfileTracker::InternBuildId(base::StringView build_id) {
- return context_->storage->InternString(
- base::StringView(CleanBuildId(build_id)));
}
} // namespace trace_processor
diff --git a/src/trace_processor/importers/common/stack_profile_tracker.h b/src/trace_processor/importers/common/stack_profile_tracker.h
index a1067b8..b018f74 100644
--- a/src/trace_processor/importers/common/stack_profile_tracker.h
+++ b/src/trace_processor/importers/common/stack_profile_tracker.h
@@ -20,12 +20,11 @@
#include <cstdint>
#include <optional>
#include <tuple>
-#include <utility>
#include <vector>
#include "perfetto/ext/base/flat_hash_map.h"
#include "perfetto/ext/base/hash.h"
-#include "perfetto/ext/base/string_view.h"
+
#include "src/trace_processor/storage/trace_storage.h"
#include "src/trace_processor/tables/profiler_tables_py.h"
@@ -52,64 +51,39 @@
class StackProfileTracker {
public:
- struct CreateMappingParams {
- base::StringView build_id;
- uint64_t exact_offset;
- uint64_t start_offset;
- uint64_t start;
- uint64_t end;
- uint64_t load_bias;
- base::StringView name;
- };
-
explicit StackProfileTracker(TraceProcessorContext* context)
: context_(context) {}
std::vector<FrameId> JavaFramesForName(NameInPackage name) const;
- std::vector<MappingId> FindMappingRow(StringId name, StringId build_id) const;
- std::vector<FrameId> FindFrameIds(MappingId mapping_id,
- uint64_t rel_pc) const;
- MappingId InternMapping(const CreateMappingParams& params);
CallsiteId InternCallsite(std::optional<CallsiteId> parent_callsite_id,
FrameId frame_id,
uint32_t depth);
- FrameId InternFrame(MappingId mapping_id,
- uint64_t rel_pc,
- base::StringView function_name);
+
+ void OnFrameCreated(FrameId frame_id);
private:
- StringId InternBuildId(base::StringView build_id);
-
TraceProcessorContext* const context_;
- base::FlatHashMap<tables::StackProfileMappingTable::Row, MappingId>
- mapping_unique_row_index_;
base::FlatHashMap<tables::StackProfileCallsiteTable::Row, CallsiteId>
callsite_unique_row_index_;
- base::FlatHashMap<tables::StackProfileFrameTable::Row, FrameId>
- frame_unique_row_index_;
- struct MappingHasher {
- size_t operator()(const std::pair<StringId, StringId>& o) const {
- return static_cast<size_t>(
- base::Hasher::Combine(o.first.raw_id(), o.second.raw_id()));
- }
- };
- base::FlatHashMap<std::pair<StringId, StringId>,
- std::vector<MappingId>,
- MappingHasher>
- mappings_by_name_and_build_id_;
+ struct FrameKey {
+ MappingId mapping_id;
+ uint64_t rel_pc;
- struct FrameHasher {
- size_t operator()(const std::pair<MappingId, uint64_t>& o) const {
- return static_cast<size_t>(
- base::Hasher::Combine(o.first.value, o.second));
+ bool operator==(const FrameKey& o) const {
+ return mapping_id == o.mapping_id && rel_pc == o.rel_pc;
}
+
+ bool operator!=(const FrameKey& o) const { return !(*this == o); }
+
+ struct Hasher {
+ size_t operator()(const FrameKey& o) const {
+ return static_cast<size_t>(
+ base::Hasher::Combine(o.mapping_id.value, o.rel_pc));
+ }
+ };
};
- base::FlatHashMap<std::pair<MappingId, uint64_t>,
- std::vector<FrameId>,
- FrameHasher>
- frame_by_mapping_and_rel_pc_;
base::FlatHashMap<NameInPackage, std::vector<FrameId>, NameInPackage::Hasher>
java_frames_for_name_;
diff --git a/src/trace_processor/importers/common/virtual_memory_mapping.cc b/src/trace_processor/importers/common/virtual_memory_mapping.cc
new file mode 100644
index 0000000..60166f59
--- /dev/null
+++ b/src/trace_processor/importers/common/virtual_memory_mapping.cc
@@ -0,0 +1,123 @@
+/*
+ * 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/virtual_memory_mapping.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+#include <optional>
+#include <string>
+#include <utility>
+
+#include "perfetto/ext/base/string_view.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/stack_profile_tracker.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/tables/profiler_tables_py.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+#include "src/trace_processor/util/build_id.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace {
+
+MappingId CreateMapping(TraceProcessorContext* context,
+ const CreateMappingParams& params) {
+ StringId build_id = context->storage->InternString(base::StringView(
+ params.build_id ? params.build_id->ToHex() : std::string()));
+ MappingId mapping_id =
+ context->storage->mutable_stack_profile_mapping_table()
+ ->Insert(
+ {build_id, static_cast<int64_t>(params.exact_offset),
+ static_cast<int64_t>(params.start_offset),
+ static_cast<int64_t>(params.memory_range.start()),
+ static_cast<int64_t>(params.memory_range.end()),
+ static_cast<int64_t>(params.load_bias),
+ context->storage->InternString(base::StringView(params.name))})
+ .id;
+
+ return mapping_id;
+}
+
+} // namespace
+
+VirtualMemoryMapping::VirtualMemoryMapping(TraceProcessorContext* context,
+ CreateMappingParams params)
+ : context_(context),
+ mapping_id_(CreateMapping(context, params)),
+ memory_range_(params.memory_range),
+ offset_(params.exact_offset),
+ load_bias_(params.load_bias),
+ name_(std::move(params.name)),
+ build_id_(std::move(params.build_id)) {}
+
+VirtualMemoryMapping::~VirtualMemoryMapping() = default;
+
+KernelMemoryMapping::KernelMemoryMapping(TraceProcessorContext* context,
+ CreateMappingParams params)
+ : VirtualMemoryMapping(context, std::move(params)) {}
+
+KernelMemoryMapping::~KernelMemoryMapping() = default;
+
+UserMemoryMapping::UserMemoryMapping(TraceProcessorContext* context,
+ UniquePid upid,
+ CreateMappingParams params)
+ : VirtualMemoryMapping(context, std::move(params)), upid_(upid) {}
+
+UserMemoryMapping::~UserMemoryMapping() = default;
+
+FrameId VirtualMemoryMapping::InternFrame(uint64_t rel_pc,
+ base::StringView function_name) {
+ auto [frame_id, was_inserted] =
+ jit_delegate_ ? jit_delegate_->InternFrame(this, rel_pc, function_name)
+ : InternFrameImpl(rel_pc, function_name);
+ if (was_inserted) {
+ frames_by_rel_pc_[rel_pc].push_back(frame_id);
+ context_->stack_profile_tracker->OnFrameCreated(frame_id);
+ }
+ return frame_id;
+}
+
+std::vector<FrameId> VirtualMemoryMapping::FindFrameIds(uint64_t rel_pc) const {
+ if (auto* res = frames_by_rel_pc_.Find(rel_pc); res != nullptr) {
+ return *res;
+ }
+ return {};
+}
+
+std::pair<FrameId, bool> VirtualMemoryMapping::InternFrameImpl(
+ uint64_t rel_pc,
+ base::StringView function_name) {
+ const FrameKey frame_key{rel_pc,
+ context_->storage->InternString(function_name)};
+ if (FrameId* id = interned_frames_.Find(frame_key); id) {
+ return {*id, false};
+ }
+
+ const FrameId frame_id =
+ context_->storage->mutable_stack_profile_frame_table()
+ ->Insert(
+ {frame_key.name_id, mapping_id_, static_cast<int64_t>(rel_pc)})
+ .id;
+ interned_frames_.Insert(frame_key, frame_id);
+
+ return {frame_id, true};
+}
+
+} // namespace trace_processor
+} // namespace perfetto
diff --git a/src/trace_processor/importers/common/virtual_memory_mapping.h b/src/trace_processor/importers/common/virtual_memory_mapping.h
new file mode 100644
index 0000000..7b8ef58
--- /dev/null
+++ b/src/trace_processor/importers/common/virtual_memory_mapping.h
@@ -0,0 +1,152 @@
+
+/*
+ * 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_VIRTUAL_MEMORY_MAPPING_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_VIRTUAL_MEMORY_MAPPING_H_
+
+#include <cstddef>
+#include <cstdint>
+#include <optional>
+#include <string>
+#include <vector>
+
+#include "perfetto/ext/base/flat_hash_map.h"
+#include "perfetto/ext/base/hash.h"
+#include "perfetto/ext/base/string_view.h"
+#include "src/trace_processor/importers/common/address_range.h"
+#include "src/trace_processor/importers/common/create_mapping_params.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+#include "src/trace_processor/util/build_id.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+// TODO(carlscab): Reconsider whether jit is the best abstraction here. All we
+// really care is about mapping a `rel_pc` to a symbol (aka symbolization) and
+// whether is this is constant.
+class JitDelegate;
+
+// Represents a mapping in virtual memory.
+class VirtualMemoryMapping {
+ public:
+ virtual ~VirtualMemoryMapping();
+ // Range of virtual memory this mapping covers.
+ AddressRange memory_range() const { return memory_range_; }
+ MappingId mapping_id() const { return mapping_id_; }
+ // This name could be the path of the underlying file mapped into memory.
+ const std::string& name() const { return name_; }
+ // For file mappings, this is the offset into the file for the first byte in
+ // the mapping
+ uint64_t offset() const { return offset_; }
+ // If the mapped file is an executable or shared library this will return the
+ // load bias, if known. Returns 0 otherwise.
+ uint64_t load_bias() const { return load_bias_; }
+ // If the mapped file is an executable or shared library this will return its
+ // build id, if known.
+ const std::optional<BuildId>& build_id() const { return build_id_; }
+
+ // Whether this maps to a region that holds jitted code.
+ bool is_jitted() const { return jit_delegate_ != nullptr; }
+
+ // Converts an absolute address into a relative one.
+ uint64_t ToRelativePc(uint64_t address) const {
+ return address - memory_range_.start() + offset_ + load_bias_;
+ }
+
+ // Creates a frame for the given `rel_pc`. Note that if the mapping
+ // `is_jitted()` same `rel_pc` values can return different mappings (as jitted
+ // functions can be created and deleted over time.) So for such mappings the
+ // returned `FrameId` should not be cached.
+ FrameId InternFrame(uint64_t rel_pc, base::StringView function_name);
+
+ // Returns all frames ever created in this mapping for the given `rel_pc`.
+ std::vector<FrameId> FindFrameIds(uint64_t rel_pc) const;
+
+ protected:
+ VirtualMemoryMapping(TraceProcessorContext* context,
+ CreateMappingParams params);
+
+ private:
+ friend class MappingTracker;
+
+ std::pair<FrameId, bool> InternFrameImpl(uint64_t rel_pc,
+ base::StringView function_name);
+
+ void SetJitDelegate(JitDelegate* jit_delegate) {
+ jit_delegate_ = jit_delegate;
+ }
+
+ TraceProcessorContext* const context_;
+ const MappingId mapping_id_;
+ const AddressRange memory_range_;
+ const uint64_t offset_;
+ const uint64_t load_bias_;
+ const std::string name_;
+ std::optional<BuildId> const build_id_;
+ JitDelegate* jit_delegate_ = nullptr;
+
+ struct FrameKey {
+ uint64_t rel_pc;
+ // It doesn't seem to make too much sense to key on name, as for the same
+ // mapping and same rel_pc the name should always be the same. But who knows
+ // how producers behave.
+ StringId name_id;
+
+ bool operator==(const FrameKey& o) const {
+ return rel_pc == o.rel_pc && name_id == o.name_id;
+ }
+
+ struct Hasher {
+ size_t operator()(const FrameKey& k) const {
+ return static_cast<size_t>(
+ base::Hasher::Combine(k.rel_pc, k.name_id.raw_id()));
+ }
+ };
+ };
+ base::FlatHashMap<FrameKey, FrameId, FrameKey::Hasher> interned_frames_;
+ base::FlatHashMap<uint64_t, std::vector<FrameId>> frames_by_rel_pc_;
+};
+
+class KernelMemoryMapping : public VirtualMemoryMapping {
+ public:
+ ~KernelMemoryMapping() override;
+
+ private:
+ friend class MappingTracker;
+ KernelMemoryMapping(TraceProcessorContext* context,
+ CreateMappingParams params);
+};
+
+class UserMemoryMapping : public VirtualMemoryMapping {
+ public:
+ ~UserMemoryMapping() override;
+ UniquePid upid() const { return upid_; }
+
+ private:
+ friend class MappingTracker;
+ UserMemoryMapping(TraceProcessorContext* context,
+ UniquePid upid,
+ CreateMappingParams params);
+
+ const UniquePid upid_;
+};
+
+} // namespace trace_processor
+} // namespace perfetto
+
+#endif // SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_VIRTUAL_MEMORY_MAPPING_H_
diff --git a/src/trace_processor/importers/ftrace/ftrace_tokenizer.cc b/src/trace_processor/importers/ftrace/ftrace_tokenizer.cc
index 429cfce..53ba885 100644
--- a/src/trace_processor/importers/ftrace/ftrace_tokenizer.cc
+++ b/src/trace_processor/importers/ftrace/ftrace_tokenizer.cc
@@ -133,7 +133,7 @@
clock_id = BuiltinClock::BUILTIN_CLOCK_BOOTTIME;
break;
case FtraceClock::FTRACE_CLOCK_GLOBAL:
- clock_id = ClockTracker::SeqenceToGlobalClock(
+ clock_id = ClockTracker::SequenceToGlobalClock(
packet_sequence_id, kFtraceGlobalClockIdForOldKernels);
break;
case FtraceClock::FTRACE_CLOCK_MONO_RAW:
@@ -376,7 +376,7 @@
return;
latest_ftrace_clock_snapshot_ts_ = ftrace_ts;
- ClockTracker::ClockId global_id = ClockTracker::SeqenceToGlobalClock(
+ ClockTracker::ClockId global_id = ClockTracker::SequenceToGlobalClock(
packet_sequence_id, kFtraceGlobalClockIdForOldKernels);
context_->clock_tracker->AddSnapshot(
{ClockTracker::ClockTimestamp(global_id, ftrace_ts),
diff --git a/src/trace_processor/importers/perf/BUILD.gn b/src/trace_processor/importers/perf/BUILD.gn
index 387468f..960cd27 100644
--- a/src/trace_processor/importers/perf/BUILD.gn
+++ b/src/trace_processor/importers/perf/BUILD.gn
@@ -28,6 +28,7 @@
]
deps = [
"../../../../gn:default_deps",
+ "../../../../protos/perfetto/trace/profiling:zero",
"../../importers/common",
"../../importers/common:parser_types",
"../../sorter",
@@ -47,6 +48,8 @@
":perf",
"../../../../gn:default_deps",
"../../../../gn:gtest_and_gmock",
+ "../../../../protos/perfetto/trace/profiling:zero",
"../../../base",
+ "../../importers/common",
]
}
diff --git a/src/trace_processor/importers/perf/perf_data_parser.cc b/src/trace_processor/importers/perf/perf_data_parser.cc
index 11a5a13..bb79209 100644
--- a/src/trace_processor/importers/perf/perf_data_parser.cc
+++ b/src/trace_processor/importers/perf/perf_data_parser.cc
@@ -22,6 +22,7 @@
#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"
@@ -58,7 +59,8 @@
// First instruction pointer in the callchain should be from kernel space, so
// it shouldn't be available in mappings.
- if (tracker_->FindMapping(*sample.pid, sample.callchain.front()).ok()) {
+ if (context_->mapping_tracker->FindUserMappingForAddress(
+ *sample.pid, sample.callchain.front())) {
context_->storage->IncrementStats(stats::perf_samples_skipped);
return;
}
@@ -70,19 +72,22 @@
std::vector<FramesTable::Row> frame_rows;
for (uint32_t i = 1; i < sample.callchain.size(); i++) {
- auto mapping = tracker_->FindMapping(*sample.pid, sample.callchain[i]);
- if (!mapping.ok()) {
+ UserMemoryMapping* mapping =
+ context_->mapping_tracker->FindUserMappingForAddress(
+ *sample.pid, 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->start)
+ 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->id;
- new_row.rel_pc = static_cast<int64_t>(sample.callchain[i] - mapping->start);
+ 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);
}
diff --git a/src/trace_processor/importers/perf/perf_data_tokenizer.cc b/src/trace_processor/importers/perf/perf_data_tokenizer.cc
index be1ec02..334148a 100644
--- a/src/trace_processor/importers/perf/perf_data_tokenizer.cc
+++ b/src/trace_processor/importers/perf/perf_data_tokenizer.cc
@@ -15,6 +15,7 @@
*/
#include "src/trace_processor/importers/perf/perf_data_tokenizer.h"
+
#include <cstdint>
#include <cstring>
#include <vector>
@@ -31,9 +32,29 @@
#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;
+ }
+}
+} // namespace
PerfDataTokenizer::PerfDataTokenizer(TraceProcessorContext* ctx)
: context_(ctx),
@@ -124,6 +145,7 @@
sizeof(PerfDataTracker::Mmap2Record::Numeric));
auto record = ParseMmap2Record(record_size);
RETURN_IF_ERROR(record.status());
+ record->cpu_mode = GetCpuMode(ev_header);
tracker_->PushMmap2Record(*record);
break;
}
diff --git a/src/trace_processor/importers/perf/perf_data_tracker.cc b/src/trace_processor/importers/perf/perf_data_tracker.cc
index 0c9b209..c670258 100644
--- a/src/trace_processor/importers/perf/perf_data_tracker.cc
+++ b/src/trace_processor/importers/perf/perf_data_tracker.cc
@@ -15,12 +15,53 @@
*/
#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;
@@ -48,31 +89,15 @@
}
void PerfDataTracker::PushMmap2Record(Mmap2Record record) {
- const auto mappings =
- context_->storage->mutable_stack_profile_mapping_table();
- MappingTable::Row row;
- row.start = static_cast<int64_t>(record.num.addr);
- row.end = static_cast<int64_t>(record.num.addr + record.num.len);
- row.name = context_->storage->InternString(record.filename.c_str());
- MappingTable::Id id = mappings->Insert(row).id;
- MmapRange mmap2_range{record.num.addr, record.num.addr + record.num.len, id};
- mmap2_ranges_[record.num.pid].push_back(mmap2_range);
-}
-
-base::StatusOr<PerfDataTracker::MmapRange> PerfDataTracker::FindMapping(
- uint32_t pid,
- uint64_t ips) {
- auto vec = mmap2_ranges_.Find(pid);
- if (!vec) {
- return base::ErrStatus("Sample pid not found in mappings.");
+ 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)));
}
-
- for (const auto& range : *vec) {
- if (ips >= range.start && ips < range.end) {
- return range;
- }
- }
- return base::ErrStatus("No mapping for callstack frame instruction pointer");
}
base::StatusOr<PerfDataTracker::PerfSample> PerfDataTracker::ParseSample(
diff --git a/src/trace_processor/importers/perf/perf_data_tracker.h b/src/trace_processor/importers/perf/perf_data_tracker.h
index 0ab99aa..11258ed 100644
--- a/src/trace_processor/importers/perf/perf_data_tracker.h
+++ b/src/trace_processor/importers/perf/perf_data_tracker.h
@@ -25,6 +25,7 @@
#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"
@@ -76,14 +77,10 @@
uint32_t prot;
uint32_t flags;
};
+ protos::pbzero::Profiling::CpuMode cpu_mode;
Numeric num;
std::string filename;
};
- struct MmapRange {
- uint64_t start;
- uint64_t end;
- MappingTable::Id id;
- };
PerfDataTracker(const PerfDataTracker&) = delete;
PerfDataTracker& operator=(const PerfDataTracker&) = delete;
@@ -103,14 +100,11 @@
base::StatusOr<PerfSample> ParseSample(
perfetto::trace_processor::perf_importer::Reader&);
- base::StatusOr<MmapRange> FindMapping(uint32_t pid, uint64_t ips);
-
private:
const perf_event_attr* FindAttrWithId(uint64_t id) const;
TraceProcessorContext* context_;
std::vector<AttrAndIds> attrs_;
- base::FlatHashMap</*pid=*/uint32_t, std::vector<MmapRange>> mmap2_ranges_;
uint64_t common_sample_type_;
};
} // namespace perf_importer
diff --git a/src/trace_processor/importers/perf/perf_data_tracker_unittest.cc b/src/trace_processor/importers/perf/perf_data_tracker_unittest.cc
index 6c59be6..3cbdc80 100644
--- a/src/trace_processor/importers/perf/perf_data_tracker_unittest.cc
+++ b/src/trace_processor/importers/perf/perf_data_tracker_unittest.cc
@@ -22,16 +22,35 @@
#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 {
-TEST(PerfDataTrackerUnittest, ComputeCommonSampleType) {
- TraceProcessorContext context;
- PerfDataTracker* tracker = PerfDataTracker::GetOrCreate(&context);
+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 =
@@ -46,16 +65,15 @@
EXPECT_FALSE(tracker->common_sample_type() & PERF_SAMPLE_CALLCHAIN);
}
-TEST(PerfDataTrackerUnittest, FindMapping) {
- TraceProcessorContext context;
- context.storage = std::make_unique<TraceStorage>();
- PerfDataTracker* tracker = PerfDataTracker::GetOrCreate(&context);
+TEST_F(PerfDataTrackerUnittest, FindMapping) {
+ PerfDataTracker* tracker = PerfDataTracker::GetOrCreate(&context_);
PerfDataTracker::Mmap2Record rec;
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;
@@ -64,32 +82,33 @@
rec.num.addr = 3000;
tracker->PushMmap2Record(rec);
- auto res_status = tracker->FindMapping(1, 2050);
- EXPECT_TRUE(res_status.ok());
- EXPECT_EQ(res_status->start, 2000u);
- EXPECT_EQ(res_status->end, 2100u);
+ 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(PerfDataTrackerUnittest, FindMappingFalse) {
- TraceProcessorContext context;
- context.storage = std::make_unique<TraceStorage>();
- PerfDataTracker* tracker = PerfDataTracker::GetOrCreate(&context);
+TEST_F(PerfDataTrackerUnittest, FindMappingFalse) {
+ PerfDataTracker* tracker = PerfDataTracker::GetOrCreate(&context_);
PerfDataTracker::Mmap2Record rec;
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);
- auto res_status = tracker->FindMapping(2, 2050);
- EXPECT_FALSE(res_status.ok());
+ UserMemoryMapping* mapping =
+ context_.mapping_tracker->FindUserMappingForAddress(
+ context_.process_tracker->GetOrCreateProcess(2), 2050);
+ EXPECT_EQ(mapping, nullptr);
}
-TEST(PerfDataTrackerUnittest, ParseSampleTrivial) {
- TraceProcessorContext context;
- context.storage = std::make_unique<TraceStorage>();
- PerfDataTracker* tracker = PerfDataTracker::GetOrCreate(&context);
+TEST_F(PerfDataTrackerUnittest, ParseSampleTrivial) {
+ PerfDataTracker* tracker = PerfDataTracker::GetOrCreate(&context_);
PerfDataTracker::AttrAndIds attr_and_ids;
attr_and_ids.attr.sample_type = PERF_SAMPLE_TIME;
@@ -107,10 +126,8 @@
EXPECT_EQ(parsed_sample->ts, 100u);
}
-TEST(PerfDataTrackerUnittest, ParseSampleCallchain) {
- TraceProcessorContext context;
- context.storage = std::make_unique<TraceStorage>();
- PerfDataTracker* tracker = PerfDataTracker::GetOrCreate(&context);
+TEST_F(PerfDataTrackerUnittest, ParseSampleCallchain) {
+ PerfDataTracker* tracker = PerfDataTracker::GetOrCreate(&context_);
PerfDataTracker::AttrAndIds attr_and_ids;
attr_and_ids.attr.sample_type = PERF_SAMPLE_CALLCHAIN;
@@ -137,10 +154,8 @@
EXPECT_EQ(parsed_sample->callchain.size(), 3u);
}
-TEST(PerfDataTrackerUnittest, ParseSampleWithoutId) {
- TraceProcessorContext context;
- context.storage = std::make_unique<TraceStorage>();
- PerfDataTracker* tracker = PerfDataTracker::GetOrCreate(&context);
+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 |
@@ -177,10 +192,8 @@
EXPECT_EQ(sample.ts, parsed_sample->ts);
}
-TEST(PerfDataTrackerUnittest, ParseSampleWithId) {
- TraceProcessorContext context;
- context.storage = std::make_unique<TraceStorage>();
- PerfDataTracker* tracker = PerfDataTracker::GetOrCreate(&context);
+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 |
@@ -222,6 +235,7 @@
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 53f5f05..c60709f 100644
--- a/src/trace_processor/importers/perf/perf_event.h
+++ b/src/trace_processor/importers/perf/perf_event.h
@@ -223,4 +223,15 @@
PERF_SAMPLE_MAX = 1U << 25, /* non-ABI */
};
+constexpr auto kPerfRecordMiscCpumodeMask = 0x7;
+
+enum perf_record_misc {
+ PERF_RECORD_MISC_CPUMODE_UNKNOWN = 0,
+ PERF_RECORD_MISC_KERNEL = 1,
+ PERF_RECORD_MISC_USER = 2,
+ PERF_RECORD_MISC_HYPERVISOR = 3,
+ PERF_RECORD_MISC_GUEST_KERNEL = 4,
+ PERF_RECORD_MISC_GUEST_USER = 5,
+};
+
#endif // SRC_TRACE_PROCESSOR_IMPORTERS_PERF_PERF_EVENT_H_
diff --git a/src/trace_processor/importers/proto/BUILD.gn b/src/trace_processor/importers/proto/BUILD.gn
index 2eaa5fe..2e542d0 100644
--- a/src/trace_processor/importers/proto/BUILD.gn
+++ b/src/trace_processor/importers/proto/BUILD.gn
@@ -91,9 +91,9 @@
"../../storage",
"../../tables",
"../../types",
+ "../../util:build_id",
"../../util:gzip",
"../../util:profiler_util",
- "../../util:stack_traces_util",
"../common",
"../common:parser_types",
"../ftrace:minimal",
@@ -181,7 +181,6 @@
"../../util:profiler_util",
"../../util:proto_profiler",
"../../util:proto_to_args_parser",
- "../../util:stack_traces_util",
"../common",
"../common:parser_types",
"../etw:full",
@@ -276,7 +275,6 @@
"../../types",
"../../util:descriptors",
"../../util:profiler_util",
- "../../util:stack_traces_util",
"../common",
"../ftrace:full",
]
diff --git a/src/trace_processor/importers/proto/heap_graph_tracker.cc b/src/trace_processor/importers/proto/heap_graph_tracker.cc
index 2e95889..dd94ffb 100644
--- a/src/trace_processor/importers/proto/heap_graph_tracker.cc
+++ b/src/trace_processor/importers/proto/heap_graph_tracker.cc
@@ -139,6 +139,14 @@
return static_cast<int64_t>(static_cast<uint64_t>(nar_size) & ~kIsMalloced);
}
+// A given object can be a heap root in different ways. Ensure analysis is
+// consistent.
+constexpr std::array<protos::pbzero::HeapGraphRoot::Type, 3>
+ kRootTypePrecedence = {
+ protos::pbzero::HeapGraphRoot::ROOT_STICKY_CLASS,
+ protos::pbzero::HeapGraphRoot::ROOT_JNI_GLOBAL,
+ protos::pbzero::HeapGraphRoot::ROOT_JNI_LOCAL,
+};
} // namespace
std::optional<base::StringView> GetStaticClassTypeName(base::StringView type) {
@@ -595,12 +603,10 @@
ObjectTable::RowReference row_ref =
ptr->ToRowReference(storage_->mutable_heap_graph_object_table());
- auto it_and_success = roots_[std::make_pair(sequence_state.current_upid,
- sequence_state.current_ts)]
- .emplace(*ptr);
- if (it_and_success.second) {
- MarkRoot(row_ref, InternRootTypeString(root.root_type));
- }
+ roots_[std::make_pair(sequence_state.current_upid,
+ sequence_state.current_ts)]
+ .emplace(*ptr);
+ MarkRoot(row_ref, InternRootTypeString(root.root_type));
}
}
@@ -786,8 +792,25 @@
return children;
}
+size_t HeapGraphTracker::RankRoot(StringId type) {
+ size_t idx = 0;
+ for (; idx < kRootTypePrecedence.size(); ++idx) {
+ if (type == InternRootTypeString(kRootTypePrecedence[idx])) {
+ break;
+ }
+ }
+ return idx;
+}
+
void HeapGraphTracker::MarkRoot(ObjectTable::RowReference row_ref,
StringId type) {
+ // Already marked as a root
+ if (row_ref.root_type()) {
+ if (RankRoot(type) < RankRoot(*row_ref.root_type())) {
+ row_ref.set_root_type(type);
+ }
+ return;
+ }
row_ref.set_root_type(type);
// DFS to mark reachability for all children
diff --git a/src/trace_processor/importers/proto/heap_graph_tracker.h b/src/trace_processor/importers/proto/heap_graph_tracker.h
index d2dd222..c25d0a1 100644
--- a/src/trace_processor/importers/proto/heap_graph_tracker.h
+++ b/src/trace_processor/importers/proto/heap_graph_tracker.h
@@ -229,6 +229,7 @@
base::FlatSet<tables::HeapGraphObjectTable::Id> GetChildren(
tables::HeapGraphObjectTable::RowReference);
void MarkRoot(tables::HeapGraphObjectTable::RowReference, StringId type);
+ size_t RankRoot(StringId type);
void UpdateShortestPaths(tables::HeapGraphObjectTable::RowReference row_ref);
void FindPathFromRoot(tables::HeapGraphObjectTable::RowReference,
PathFromRoot* path);
diff --git a/src/trace_processor/importers/proto/profile_module.cc b/src/trace_processor/importers/proto/profile_module.cc
index 61596d3..b80cae7 100644
--- a/src/trace_processor/importers/proto/profile_module.cc
+++ b/src/trace_processor/importers/proto/profile_module.cc
@@ -24,6 +24,7 @@
#include "src/trace_processor/importers/common/clock_tracker.h"
#include "src/trace_processor/importers/common/deobfuscation_mapping_table.h"
#include "src/trace_processor/importers/common/event_tracker.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/proto/packet_sequence_state.h"
@@ -36,8 +37,8 @@
#include "src/trace_processor/storage/trace_storage.h"
#include "src/trace_processor/tables/profiler_tables_py.h"
#include "src/trace_processor/types/trace_processor_context.h"
+#include "src/trace_processor/util/build_id.h"
#include "src/trace_processor/util/profiler_util.h"
-#include "src/trace_processor/util/stack_traces_util.h"
#include "protos/perfetto/common/builtin_clock.pbzero.h"
#include "protos/perfetto/common/perf_events.pbzero.h"
@@ -428,19 +429,11 @@
void ProfileModule::ParseModuleSymbols(ConstBytes blob) {
protos::pbzero::ModuleSymbols::Decoder module_symbols(blob.data, blob.size);
- StringId build_id;
- // TODO(b/148109467): Remove workaround once all active Chrome versions
- // write raw bytes instead of a string as build_id.
- if (util::IsHexModuleId(module_symbols.build_id())) {
- build_id = context_->storage->InternString(module_symbols.build_id());
- } else {
- build_id = context_->storage->InternString(base::StringView(base::ToHex(
- module_symbols.build_id().data, module_symbols.build_id().size)));
- }
+ BuildId build_id = BuildId::FromRaw(module_symbols.build_id());
- auto mapping_ids = context_->stack_profile_tracker->FindMappingRow(
- context_->storage->InternString(module_symbols.path()), build_id);
- if (mapping_ids.empty()) {
+ auto mappings =
+ context_->mapping_tracker->FindMappings(module_symbols.path(), build_id);
+ if (mappings.empty()) {
context_->storage->IncrementStats(stats::stackprofile_invalid_mapping_id);
return;
}
@@ -467,12 +460,11 @@
continue;
}
bool frame_found = false;
- for (MappingId mapping_id : mapping_ids) {
+ for (VirtualMemoryMapping* mapping : mappings) {
context_->args_translation_table->AddNativeSymbolTranslationRule(
- mapping_id, address_symbols.address(), last_location);
+ mapping->mapping_id(), address_symbols.address(), last_location);
std::vector<FrameId> frame_ids =
- context_->stack_profile_tracker->FindFrameIds(
- mapping_id, address_symbols.address());
+ mapping->FindFrameIds(address_symbols.address());
for (const FrameId frame_id : frame_ids) {
auto* frames = context_->storage->mutable_stack_profile_frame_table();
diff --git a/src/trace_processor/importers/proto/profile_packet_sequence_state.cc b/src/trace_processor/importers/proto/profile_packet_sequence_state.cc
index 9d889ef..31841fc 100644
--- a/src/trace_processor/importers/proto/profile_packet_sequence_state.cc
+++ b/src/trace_processor/importers/proto/profile_packet_sequence_state.cc
@@ -19,6 +19,8 @@
#include "perfetto/base/flat_set.h"
#include "perfetto/base/logging.h"
#include "perfetto/ext/base/string_view.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/proto/packet_sequence_state.h"
@@ -28,6 +30,7 @@
#include "src/trace_processor/storage/stats.h"
#include "src/trace_processor/storage/trace_storage.h"
#include "src/trace_processor/types/trace_processor_context.h"
+#include "src/trace_processor/util/build_id.h"
namespace perfetto {
namespace trace_processor {
@@ -68,17 +71,16 @@
void ProfilePacketSequenceState::AddMapping(SourceMappingId id,
const SourceMapping& mapping) {
- StackProfileTracker::CreateMappingParams params;
+ CreateMappingParams params;
if (std::string* str = strings_.Find(mapping.build_id); str) {
- params.build_id = base::StringView(*str);
+ params.build_id = BuildId::FromRaw(*str);
} else {
context_->storage->IncrementStats(stats::stackprofile_invalid_string_id);
return;
}
params.exact_offset = mapping.exact_offset;
params.start_offset = mapping.start_offset;
- params.start = mapping.start;
- params.end = mapping.end;
+ params.memory_range = AddressRange(mapping.start, mapping.end);
params.load_bias = mapping.load_bias;
std::vector<base::StringView> path_components;
@@ -93,16 +95,16 @@
break;
}
}
- std::string path = ProfilePacketUtils::MakeMappingName(path_components);
- params.name = base::StringView(path);
- MappingId mapping_id = context_->stack_profile_tracker->InternMapping(params);
- mappings_.Insert(id, mapping_id);
+
+ params.name = ProfilePacketUtils::MakeMappingName(path_components);
+ mappings_.Insert(
+ id, &context_->mapping_tracker->InternMemoryMapping(std::move(params)));
}
void ProfilePacketSequenceState::AddFrame(SourceFrameId id,
const SourceFrame& frame) {
- MappingId* mapping_id = mappings_.Find(frame.mapping_id);
- if (!mapping_id) {
+ VirtualMemoryMapping** mapping = mappings_.Find(frame.mapping_id);
+ if (!mapping) {
context_->storage->IncrementStats(stats::stackprofile_invalid_mapping_id);
return;
}
@@ -113,9 +115,8 @@
return;
}
- FrameId frame_id = context_->stack_profile_tracker->InternFrame(
- *mapping_id, frame.rel_pc, base::StringView(*function_name));
-
+ FrameId frame_id =
+ (*mapping)->InternFrame(frame.rel_pc, base::StringView(*function_name));
frames_.Insert(id, frame_id);
}
diff --git a/src/trace_processor/importers/proto/profile_packet_sequence_state.h b/src/trace_processor/importers/proto/profile_packet_sequence_state.h
index 678aab2..99661da 100644
--- a/src/trace_processor/importers/proto/profile_packet_sequence_state.h
+++ b/src/trace_processor/importers/proto/profile_packet_sequence_state.h
@@ -23,7 +23,6 @@
#include "perfetto/ext/base/hash.h"
#include "perfetto/ext/base/string_view.h"
-#include "protos/perfetto/trace/profiling/profile_common.pbzero.h"
#include "src/trace_processor/importers/proto/packet_sequence_state_generation.h"
#include "src/trace_processor/importers/proto/stack_profile_sequence_state.h"
#include "src/trace_processor/storage/trace_storage.h"
@@ -31,6 +30,8 @@
namespace perfetto {
namespace trace_processor {
+class VirtualMemoryMapping;
+
// Keeps sequence specific state for profile packets.
class ProfilePacketSequenceState final
: public PacketSequenceStateGeneration::InternedDataTracker {
@@ -126,7 +127,7 @@
TraceProcessorContext* const context_;
base::FlatHashMap<SourceStringId, std::string> strings_;
- base::FlatHashMap<SourceMappingId, MappingId> mappings_;
+ base::FlatHashMap<SourceMappingId, VirtualMemoryMapping*> mappings_;
base::FlatHashMap<SourceFrameId, FrameId> frames_;
base::FlatHashMap<SourceCallstackId, CallsiteId> callstacks_;
diff --git a/src/trace_processor/importers/proto/profile_packet_sequence_state_unittest.cc b/src/trace_processor/importers/proto/profile_packet_sequence_state_unittest.cc
index ab947fa..c9cd6e3 100644
--- a/src/trace_processor/importers/proto/profile_packet_sequence_state_unittest.cc
+++ b/src/trace_processor/importers/proto/profile_packet_sequence_state_unittest.cc
@@ -18,8 +18,9 @@
#include <memory>
-#include "src/trace_processor/importers/proto/packet_sequence_state.h"
+#include "src/trace_processor/importers/common/mapping_tracker.h"
#include "src/trace_processor/importers/common/stack_profile_tracker.h"
+#include "src/trace_processor/importers/proto/packet_sequence_state.h"
#include "src/trace_processor/types/trace_processor_context.h"
#include "test/gtest_and_gmock.h"
@@ -58,6 +59,7 @@
public:
HeapProfileTrackerDupTest() {
context.storage.reset(new TraceStorage());
+ context.mapping_tracker.reset(new MappingTracker(&context));
context.stack_profile_tracker.reset(new StackProfileTracker(&context));
packet_sequence_state.reset(new PacketSequenceState(&context));
@@ -196,6 +198,7 @@
TEST(HeapProfileTrackerTest, SourceMappingPath) {
TraceProcessorContext context;
context.storage.reset(new TraceStorage());
+ context.mapping_tracker.reset(new MappingTracker(&context));
context.stack_profile_tracker.reset(new StackProfileTracker(&context));
PacketSequenceState pss(&context);
ProfilePacketSequenceState& ppss =
@@ -229,6 +232,7 @@
TEST(HeapProfileTrackerTest, Functional) {
TraceProcessorContext context;
context.storage.reset(new TraceStorage());
+ context.mapping_tracker.reset(new MappingTracker(&context));
context.stack_profile_tracker.reset(new StackProfileTracker(&context));
PacketSequenceState pss(&context);
diff --git a/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc b/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc
index 148bf60..dc05d36 100644
--- a/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc
+++ b/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc
@@ -25,6 +25,7 @@
#include "src/trace_processor/importers/common/clock_tracker.h"
#include "src/trace_processor/importers/common/event_tracker.h"
#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_tracker.h"
#include "src/trace_processor/importers/common/slice_tracker.h"
@@ -252,6 +253,7 @@
context_.track_tracker.reset(new TrackTracker(&context_));
context_.global_args_tracker.reset(
new GlobalArgsTracker(context_.storage.get()));
+ context_.mapping_tracker.reset(new MappingTracker(&context_));
context_.stack_profile_tracker.reset(new StackProfileTracker(&context_));
context_.args_tracker.reset(new ArgsTracker(&context_));
context_.args_translation_table.reset(new ArgsTranslationTable(storage_));
diff --git a/src/trace_processor/importers/proto/proto_trace_reader.cc b/src/trace_processor/importers/proto/proto_trace_reader.cc
index 6d68464..12e044b 100644
--- a/src/trace_processor/importers/proto/proto_trace_reader.cc
+++ b/src/trace_processor/importers/proto/proto_trace_reader.cc
@@ -191,7 +191,7 @@
timestamp_clock_id);
}
converted_clock_id =
- ClockTracker::SeqenceToGlobalClock(seq_id, timestamp_clock_id);
+ ClockTracker::SequenceToGlobalClock(seq_id, timestamp_clock_id);
}
auto trace_ts =
context_->clock_tracker->ToTraceTime(converted_clock_id, timestamp);
@@ -352,7 +352,7 @@
"(%" PRIu64 ") but the TracePacket sequence_id is zero",
clock_id);
}
- clock_id = ClockTracker::SeqenceToGlobalClock(seq_id, clk.clock_id());
+ clock_id = ClockTracker::SequenceToGlobalClock(seq_id, clk.clock_id());
}
int64_t unit_multiplier_ns =
clk.unit_multiplier_ns()
diff --git a/src/trace_processor/importers/proto/stack_profile_sequence_state.cc b/src/trace_processor/importers/proto/stack_profile_sequence_state.cc
index e503a09..a469f92 100644
--- a/src/trace_processor/importers/proto/stack_profile_sequence_state.cc
+++ b/src/trace_processor/importers/proto/stack_profile_sequence_state.cc
@@ -23,6 +23,8 @@
#include "perfetto/ext/base/string_view.h"
#include "protos/perfetto/trace/interned_data/interned_data.pbzero.h"
#include "protos/perfetto/trace/profiling/profile_common.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/stack_profile_tracker.h"
#include "src/trace_processor/importers/proto/packet_sequence_state.h"
#include "src/trace_processor/importers/proto/packet_sequence_state_generation.h"
@@ -30,6 +32,7 @@
#include "src/trace_processor/storage/stats.h"
#include "src/trace_processor/storage/trace_storage.h"
#include "src/trace_processor/types/trace_processor_context.h"
+#include "src/trace_processor/util/build_id.h"
namespace perfetto {
namespace trace_processor {
@@ -48,21 +51,23 @@
std::optional<MappingId> StackProfileSequenceState::FindOrInsertMapping(
uint64_t iid) {
- if (MappingId* id = cached_mappings_.Find(iid); id) {
- return *id;
+ if (VirtualMemoryMapping* mapping = FindOrInsertMappingImpl(iid); mapping) {
+ return mapping->mapping_id();
+ }
+ return std::nullopt;
+}
+
+VirtualMemoryMapping* StackProfileSequenceState::FindOrInsertMappingImpl(
+ uint64_t iid) {
+ if (auto ptr = cached_mappings_.Find(iid); ptr) {
+ return *ptr;
}
auto* decoder =
LookupInternedMessage<protos::pbzero::InternedData::kMappingsFieldNumber,
protos::pbzero::Mapping>(iid);
if (!decoder) {
context_->storage->IncrementStats(stats::stackprofile_invalid_mapping_id);
- return std::nullopt;
- }
-
- std::optional<base::StringView> build_id =
- LookupInternedBuildId(decoder->build_id());
- if (!build_id) {
- return std::nullopt;
+ return nullptr;
}
std::vector<base::StringView> path_components;
@@ -75,19 +80,26 @@
}
path_components.push_back(*str);
}
- std::string path = ProfilePacketUtils::MakeMappingName(path_components);
- StackProfileTracker::CreateMappingParams params;
- params.build_id = *build_id;
+ CreateMappingParams params;
+ std::optional<base::StringView> build_id =
+ LookupInternedBuildId(decoder->build_id());
+ if (!build_id) {
+ return nullptr;
+ }
+ params.build_id = BuildId::FromRaw(*build_id);
+
+ params.memory_range = AddressRange(decoder->start(), decoder->end());
params.exact_offset = decoder->exact_offset();
params.start_offset = decoder->start_offset();
- params.start = decoder->start();
- params.end = decoder->end();
params.load_bias = decoder->load_bias();
- params.name = base::StringView(path);
- MappingId mapping_id = context_->stack_profile_tracker->InternMapping(params);
- cached_mappings_.Insert(iid, mapping_id);
- return mapping_id;
+ params.name = ProfilePacketUtils::MakeMappingName(path_components);
+
+ VirtualMemoryMapping& mapping =
+ context_->mapping_tracker->InternMemoryMapping(std::move(params));
+
+ cached_mappings_.Insert(iid, &mapping);
+ return &mapping;
}
std::optional<base::StringView>
@@ -110,11 +122,6 @@
std::optional<base::StringView>
StackProfileSequenceState::LookupInternedMappingPath(uint64_t iid) {
- // This should really be an error (value not set) or at the very least return
- // a null string, but for backward compatibility use an empty string instead.
- if (iid == 0) {
- return "";
- }
auto* decoder = LookupInternedMessage<
protos::pbzero::InternedData::kMappingPathsFieldNumber,
protos::pbzero::InternedString>(iid);
@@ -158,7 +165,7 @@
cached_callstacks_.Insert(iid, *parent_callsite_id);
- return *parent_callsite_id;
+ return parent_callsite_id;
}
std::optional<FrameId> StackProfileSequenceState::FindOrInsertFrame(
@@ -174,9 +181,9 @@
return std::nullopt;
}
- std::optional<MappingId> mapping_id =
- FindOrInsertMapping(decoder->mapping_id());
- if (!mapping_id) {
+ VirtualMemoryMapping* mapping =
+ FindOrInsertMappingImpl(decoder->mapping_id());
+ if (!mapping) {
return std::nullopt;
}
@@ -190,9 +197,7 @@
function_name = *func;
}
- FrameId frame_id = context_->stack_profile_tracker->InternFrame(
- *mapping_id, decoder->rel_pc(), function_name);
-
+ FrameId frame_id = mapping->InternFrame(decoder->rel_pc(), function_name);
cached_frames_.Insert(iid, frame_id);
return frame_id;
diff --git a/src/trace_processor/importers/proto/stack_profile_sequence_state.h b/src/trace_processor/importers/proto/stack_profile_sequence_state.h
index 7a7879a..82de785 100644
--- a/src/trace_processor/importers/proto/stack_profile_sequence_state.h
+++ b/src/trace_processor/importers/proto/stack_profile_sequence_state.h
@@ -30,6 +30,7 @@
namespace trace_processor {
class TraceProcessorContext;
+class VirtualMemoryMapping;
class StackProfileSequenceState final
: public PacketSequenceStateGeneration::InternedDataTracker {
@@ -44,13 +45,15 @@
std::optional<CallsiteId> FindOrInsertCallstack(uint64_t iid);
private:
+ // Returns `nullptr`if non could be found.
+ VirtualMemoryMapping* FindOrInsertMappingImpl(uint64_t iid);
std::optional<base::StringView> LookupInternedBuildId(uint64_t iid);
std::optional<base::StringView> LookupInternedMappingPath(uint64_t iid);
std::optional<base::StringView> LookupInternedFunctionName(uint64_t iid);
std::optional<FrameId> FindOrInsertFrame(uint64_t iid);
TraceProcessorContext* const context_;
- base::FlatHashMap<uint64_t, MappingId> cached_mappings_;
+ base::FlatHashMap<uint64_t, VirtualMemoryMapping*> cached_mappings_;
base::FlatHashMap<uint64_t, CallsiteId> cached_callstacks_;
base::FlatHashMap<uint64_t, FrameId> cached_frames_;
};
diff --git a/src/trace_processor/metrics/sql/android/android_boot.sql b/src/trace_processor/metrics/sql/android/android_boot.sql
index a81f28a..34d1c40 100644
--- a/src/trace_processor/metrics/sql/android/android_boot.sql
+++ b/src/trace_processor/metrics/sql/android/android_boot.sql
@@ -16,6 +16,7 @@
INCLUDE PERFETTO MODULE android.process_metadata;
INCLUDE PERFETTO MODULE android.app_process_starts;
+INCLUDE PERFETTO MODULE android.garbage_collection;
CREATE OR REPLACE PERFETTO FUNCTION get_durations(process_name STRING)
RETURNS TABLE(uint_sleep_dur LONG, total_dur LONG) AS
@@ -56,7 +57,7 @@
'full_trace_process_start_aggregation', (
SELECT NULL_IF_EMPTY(AndroidBootMetric_ProcessStartAggregation(
'total_start_sum', (SELECT SUM(total_dur) FROM android_app_process_starts),
- 'num_of_processes', (SELECT COUNT(process_name) FROM android_app_process_starts GROUP BY process_name),
+ 'num_of_processes', (SELECT COUNT(*) FROM android_app_process_starts),
'average_start_time', (SELECT AVG(total_dur) FROM android_app_process_starts)))
FROM android_app_process_starts),
'post_boot_process_start_aggregation', (
@@ -66,11 +67,10 @@
FROM thread_slice WHERE name GLOB "*android.intent.action.USER_UNLOCKED*"
ORDER BY ts ASC LIMIT 1 )
),
- 'num_of_processes', (SELECT COUNT(process_name) FROM android_app_process_starts
+ 'num_of_processes', (SELECT COUNT(*) FROM android_app_process_starts
WHERE proc_start_ts > (SELECT COALESCE(MIN(ts), 0) FROM thread_slice
WHERE name GLOB "*android.intent.action.USER_UNLOCKED*" ORDER BY ts
ASC LIMIT 1 )
- GROUP BY process_name
),
'average_start_time', (SELECT AVG(total_dur) FROM android_app_process_starts
WHERE proc_start_ts > (SELECT COALESCE(MIN(ts), 0) FROM thread_slice
@@ -78,5 +78,107 @@
ASC LIMIT 1 )
)
))
+ ),
+ 'full_trace_gc_aggregation', (
+ SELECT NULL_IF_EMPTY(AndroidBootMetric_GarbageCollectionAggregation(
+ 'total_gc_count', (SELECT COUNT(*) FROM android_garbage_collection_events
+ ),
+ 'num_of_processes_with_gc', (SELECT COUNT(process_name) FROM android_garbage_collection_events
+ ),
+ 'num_of_threads_with_gc', (SELECT SUM(cnt) FROM (SELECT COUNT(*) AS cnt
+ FROM android_garbage_collection_events
+ GROUP by thread_name, process_name)
+ ),
+ 'avg_gc_duration', (SELECT AVG(gc_dur) FROM android_garbage_collection_events),
+ 'avg_running_gc_duration', (SELECT AVG(gc_running_dur) FROM android_garbage_collection_events),
+ 'full_gc_count', (SELECT COUNT(*) FROM android_garbage_collection_events
+ WHERE gc_type = "full"
+ ),
+ 'collector_transition_gc_count', (SELECT COUNT(*) FROM android_garbage_collection_events
+ WHERE gc_type = "collector_transition"
+ ),
+ 'young_gc_count', (SELECT COUNT(*) FROM android_garbage_collection_events
+ WHERE gc_type = "young"
+ ),
+ 'native_alloc_gc_count', (SELECT COUNT(*) FROM android_garbage_collection_events
+ WHERE gc_type = "native_alloc"
+ ),
+ 'explicit_gc_count', (SELECT COUNT(*) FROM android_garbage_collection_events
+ WHERE gc_type = "explicit_gc"
+ ),
+ 'alloc_gc_count', (SELECT COUNT(*) FROM android_garbage_collection_events
+ WHERE gc_type = "alloc_gc"
+ ),
+ 'mb_per_ms_of_gc', (SELECT SUM(reclaimed_mb)/SUM(gc_running_dur/1e6) AS mb_per_ms_dur
+ FROM android_garbage_collection_events
+ )
+ ))
+ ),
+ 'post_boot_gc_aggregation', (
+ SELECT NULL_IF_EMPTY(AndroidBootMetric_GarbageCollectionAggregation(
+ 'total_gc_count', (SELECT COUNT(*) FROM android_garbage_collection_events
+ WHERE gc_ts > (SELECT COALESCE(MIN(ts), 0)
+ FROM thread_slice WHERE name GLOB "*android.intent.action.USER_UNLOCKED*"
+ ORDER BY ts ASC LIMIT 1 )
+ ),
+ 'num_of_processes_with_gc', (SELECT COUNT(process_name) FROM android_garbage_collection_events
+ WHERE gc_ts > (SELECT COALESCE(MIN(ts), 0)
+ FROM thread_slice WHERE name GLOB "*android.intent.action.USER_UNLOCKED*"
+ ORDER BY ts ASC LIMIT 1 )
+ ),
+ 'num_of_threads_with_gc', (SELECT SUM(cnt) FROM (SELECT COUNT(*) AS cnt
+ FROM android_garbage_collection_events
+ WHERE gc_ts > (SELECT COALESCE(MIN(ts), 0) FROM thread_slice
+ WHERE name GLOB "*android.intent.action.USER_UNLOCKED*" ORDER BY ts
+ ASC LIMIT 1 )
+ GROUP by thread_name, process_name)
+ ),
+ 'avg_gc_duration', (SELECT AVG(gc_dur) FROM android_garbage_collection_events
+ WHERE gc_ts > (SELECT COALESCE(MIN(ts), 0) FROM thread_slice
+ WHERE name GLOB "*android.intent.action.USER_UNLOCKED*" ORDER BY ts
+ ASC LIMIT 1 )
+ ),
+ 'avg_running_gc_duration', (SELECT AVG(gc_running_dur) FROM android_garbage_collection_events
+ WHERE gc_ts > (SELECT COALESCE(MIN(ts), 0) FROM thread_slice
+ WHERE name GLOB "*android.intent.action.USER_UNLOCKED*" ORDER BY ts
+ ASC LIMIT 1 )
+ ),
+ 'full_gc_count', (SELECT COUNT(*) FROM android_garbage_collection_events
+ WHERE gc_type = "full" AND gc_ts > (SELECT COALESCE(MIN(ts), 0)
+ FROM thread_slice WHERE name GLOB "*android.intent.action.USER_UNLOCKED*"
+ ORDER BY ts ASC LIMIT 1 )
+ ),
+ 'collector_transition_gc_count', (SELECT COUNT(*) FROM android_garbage_collection_events
+ WHERE gc_type = "collector_transition" AND gc_ts > (SELECT COALESCE(MIN(ts), 0)
+ FROM thread_slice WHERE name GLOB "*android.intent.action.USER_UNLOCKED*"
+ ORDER BY ts ASC LIMIT 1 )
+ ),
+ 'young_gc_count', (SELECT COUNT(*) FROM android_garbage_collection_events
+ WHERE gc_type = "young" AND gc_ts > (SELECT COALESCE(MIN(ts), 0)
+ FROM thread_slice WHERE name GLOB "*android.intent.action.USER_UNLOCKED*"
+ ORDER BY ts ASC LIMIT 1 )
+ ),
+ 'native_alloc_gc_count', (SELECT COUNT(*) FROM android_garbage_collection_events
+ WHERE gc_type = "native_alloc" AND gc_ts > (SELECT COALESCE(MIN(ts), 0)
+ FROM thread_slice WHERE name GLOB "*android.intent.action.USER_UNLOCKED*"
+ ORDER BY ts ASC LIMIT 1 )
+ ),
+ 'explicit_gc_count', (SELECT COUNT(*) FROM android_garbage_collection_events
+ WHERE gc_type = "explicit_gc" AND gc_ts > (SELECT COALESCE(MIN(ts), 0)
+ FROM thread_slice WHERE name GLOB "*android.intent.action.USER_UNLOCKED*"
+ ORDER BY ts ASC LIMIT 1 )
+ ),
+ 'alloc_gc_count', (SELECT COUNT(*) FROM android_garbage_collection_events
+ WHERE gc_type = "alloc_gc" AND gc_ts > (SELECT COALESCE(MIN(ts), 0)
+ FROM thread_slice WHERE name GLOB "*android.intent.action.USER_UNLOCKED*"
+ ORDER BY ts ASC LIMIT 1 )
+ ),
+ 'mb_per_ms_of_gc', (SELECT SUM(reclaimed_mb)/SUM(gc_running_dur/1e6) AS mb_per_ms_dur
+ FROM android_garbage_collection_events
+ WHERE gc_ts > (SELECT COALESCE(MIN(ts), 0) FROM thread_slice
+ WHERE name GLOB "*android.intent.action.USER_UNLOCKED*" ORDER BY ts
+ ASC LIMIT 1 )
+ )
+ ))
)
);
diff --git a/src/trace_processor/perfetto_sql/engine/BUILD.gn b/src/trace_processor/perfetto_sql/engine/BUILD.gn
index 122d576..6a3baf1 100644
--- a/src/trace_processor/perfetto_sql/engine/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/engine/BUILD.gn
@@ -35,7 +35,10 @@
"../..:metatrace",
"../../../../gn:default_deps",
"../../../../gn:sqlite",
+ "../../../../include/perfetto/trace_processor:basic_types",
+ "../../../../protos/perfetto/trace_processor:zero",
"../../../base",
+ "../../containers",
"../../db",
"../../perfetto_sql/intrinsics/functions:interface",
"../../perfetto_sql/intrinsics/table_functions:interface",
diff --git a/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.cc b/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.cc
index e29933b..86d96bc 100644
--- a/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.cc
+++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.cc
@@ -16,12 +16,16 @@
#include "src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h"
+#include <algorithm>
+#include <array>
#include <cctype>
#include <cstddef>
#include <cstdint>
+#include <cstring>
#include <memory>
#include <optional>
#include <string>
+#include <string_view>
#include <utility>
#include <variant>
#include <vector>
@@ -31,6 +35,7 @@
#include "perfetto/ext/base/status_or.h"
#include "perfetto/ext/base/string_utils.h"
#include "perfetto/ext/base/string_view.h"
+#include "src/trace_processor/containers/string_pool.h"
#include "src/trace_processor/db/runtime_table.h"
#include "src/trace_processor/db/table.h"
#include "src/trace_processor/perfetto_sql/engine/created_function.h"
@@ -40,13 +45,18 @@
#include "src/trace_processor/perfetto_sql/engine/runtime_table_function.h"
#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/static_table_function.h"
#include "src/trace_processor/sqlite/db_sqlite_table.h"
+#include "src/trace_processor/sqlite/query_cache.h"
#include "src/trace_processor/sqlite/scoped_db.h"
#include "src/trace_processor/sqlite/sql_source.h"
#include "src/trace_processor/sqlite/sqlite_engine.h"
#include "src/trace_processor/sqlite/sqlite_table.h"
#include "src/trace_processor/tp_metatrace.h"
+#include "src/trace_processor/util/sql_argument.h"
+#include "src/trace_processor/util/sql_modules.h"
#include "src/trace_processor/util/status_macros.h"
+#include "protos/perfetto/trace_processor/metatrace_categories.pbzero.h"
+
// Implementation details
// ----------------------
//
@@ -140,18 +150,18 @@
});
bool IsTokenAllowedInMacro(const std::string& view) {
- for (const char* allowed_token : kTokensAllowedInMacro) {
- if (base::ToLower(view) == base::ToLower(allowed_token)) {
- return true;
- }
- }
- return false;
+ std::string lower = base::ToLower(view);
+ return std::any_of(kTokensAllowedInMacro.begin(), kTokensAllowedInMacro.end(),
+ [&lower](const std::string& allowed_token) {
+ return lower == base::ToLower(allowed_token);
+ });
}
std::string GetTokenNamesAllowedInMacro() {
std::vector<std::string> result;
+ result.reserve(kTokensAllowedInMacro.size());
for (const char* token : kTokensAllowedInMacro) {
- result.push_back(token);
+ result.emplace_back(token);
}
return base::Join(result, ", ");
}
@@ -177,7 +187,7 @@
auto context = std::make_unique<DbSqliteTable::Context>(
query_cache_.get(),
[this](const std::string& name) {
- auto table = runtime_tables_.Find(name);
+ auto* table = runtime_tables_.Find(name);
PERFETTO_CHECK(table);
return table->get();
},
@@ -270,10 +280,9 @@
std::optional<SqlSource> source;
if (auto* cf = std::get_if<PerfettoSqlParser::CreateFunction>(
&parser.statement())) {
- auto source_or = ExecuteCreateFunction(*cf, parser);
- RETURN_IF_ERROR(
- AddTracebackIfNeeded(source_or.status(), parser.statement_sql()));
- source = std::move(source_or.value());
+ RETURN_IF_ERROR(AddTracebackIfNeeded(ExecuteCreateFunction(*cf),
+ parser.statement_sql()));
+ source = RewriteToDummySql(parser.statement_sql());
} else if (auto* cst = std::get_if<PerfettoSqlParser::CreateTable>(
&parser.statement())) {
RETURN_IF_ERROR(AddTracebackIfNeeded(ExecuteCreateTable(*cst),
@@ -368,7 +377,7 @@
base::Status PerfettoSqlEngine::RegisterRuntimeFunction(
bool replace,
const FunctionPrototype& prototype,
- std::string return_type_str,
+ const std::string& return_type_str,
SqlSource sql) {
// Parse the return type into a enum format.
auto opt_return_type =
@@ -403,8 +412,8 @@
std::move(created_fn_ctx)));
runtime_function_count_++;
}
- return CreatedFunction::Prepare(ctx, std::move(prototype),
- std::move(*opt_return_type), std::move(sql));
+ return CreatedFunction::Prepare(ctx, prototype, *opt_return_type,
+ std::move(sql));
}
base::Status PerfettoSqlEngine::ExecuteCreateTable(
@@ -468,6 +477,13 @@
}
ASSIGN_OR_RETURN(auto table, std::move(builder).Build(rows));
+ // TODO(lalitm): unfortunately, in the (very unlikely) event that there is a
+ // sqlite3_interrupt call between the DROP and CREATE, we can end up with the
+ // non-atomic query execution. Fixing this is extremely difficult as it
+ // involves telling SQLite that we want the drop/create to be atomic.
+ //
+ // We would need to do with the transaction API but given we have no usage of
+ // this until now, investigating that needs some proper work.
if (runtime_tables_.Find(create_table.name)) {
if (!create_table.replace) {
return base::ErrStatus("CREATE PERFETTO TABLE: table '%s' already exists",
@@ -483,9 +499,15 @@
runtime_tables_.Insert(create_table.name, std::move(table));
base::StackString<1024> create("CREATE VIRTUAL TABLE %s USING runtime_table",
create_table.name.c_str());
- return Execute(
- SqlSource::FromTraceProcessorImplementation(create.ToStdString()))
- .status();
+ auto status =
+ Execute(SqlSource::FromTraceProcessorImplementation(create.ToStdString()))
+ .status();
+ if (!status.ok()) {
+ // If the registration of the table with SQLite failed, erase the state
+ // we hold.
+ PERFETTO_CHECK(runtime_tables_.Erase(create_table.name));
+ }
+ return status;
}
base::Status PerfettoSqlEngine::ExecuteCreateView(
@@ -520,8 +542,8 @@
base::Status PerfettoSqlEngine::EnableSqlFunctionMemoization(
const std::string& name) {
constexpr size_t kSupportedArgCount = 1;
- CreatedFunction::Context* ctx = static_cast<CreatedFunction::Context*>(
- sqlite_engine()->GetFunctionContext(name.c_str(), kSupportedArgCount));
+ auto* ctx = static_cast<CreatedFunction::Context*>(
+ sqlite_engine()->GetFunctionContext(name, kSupportedArgCount));
if (!ctx) {
return base::ErrStatus(
"EXPERIMENTAL_MEMOIZE: Function %s(INT) does not exist", name.c_str());
@@ -544,7 +566,7 @@
}
std::string module_name = sql_modules::GetModuleName(key);
- auto module = FindModule(module_name);
+ auto* module = FindModule(module_name);
if (!module) {
return base::ErrStatus("INCLUDE: Unknown module name provided - %s",
key.c_str());
@@ -600,25 +622,26 @@
return base::OkStatus();
}
-base::StatusOr<SqlSource> PerfettoSqlEngine::ExecuteCreateFunction(
- const PerfettoSqlParser::CreateFunction& cf,
- const PerfettoSqlParser& parser) {
+base::Status PerfettoSqlEngine::ExecuteCreateFunction(
+ const PerfettoSqlParser::CreateFunction& cf) {
if (!cf.is_table) {
- RETURN_IF_ERROR(
- RegisterRuntimeFunction(cf.replace, cf.prototype, cf.returns, cf.sql));
- return RewriteToDummySql(parser.statement_sql());
+ return RegisterRuntimeFunction(cf.replace, cf.prototype, cf.returns,
+ cf.sql);
}
- RuntimeTableFunction::State state{cf.sql, cf.prototype, {}, std::nullopt};
+ std::unique_ptr<RuntimeTableFunction::State> state(
+ new RuntimeTableFunction::State{cf.sql, cf.prototype, {}, std::nullopt});
// Parse the return type into a enum format.
- base::Status status =
- sql_argument::ParseArgumentDefinitions(cf.returns, state.return_values);
- if (!status.ok()) {
- return base::ErrStatus(
- "CREATE PERFETTO FUNCTION[prototype=%s, return=%s]: unknown return "
- "type specified",
- state.prototype.ToString().c_str(), cf.returns.c_str());
+ {
+ base::Status status = sql_argument::ParseArgumentDefinitions(
+ cf.returns, state->return_values);
+ if (!status.ok()) {
+ return base::ErrStatus(
+ "CREATE PERFETTO FUNCTION[prototype=%s, return=%s]: unknown return "
+ "type specified",
+ state->prototype.ToString().c_str(), cf.returns.c_str());
+ }
}
// Verify that the provided SQL prepares to a statement correctly.
@@ -638,7 +661,7 @@
return base::ErrStatus(
"%s: \"Nameless\" SQL parameters cannot be used in the SQL "
"statements of view functions.",
- state.prototype.function_name.c_str());
+ state->prototype.function_name.c_str());
}
if (!base::StringView(name).StartsWith("$")) {
@@ -646,71 +669,81 @@
"%s: invalid parameter name %s used in the SQL definition of "
"the view function: all parameters must be prefixed with '$' not "
"':' or '@'.",
- state.prototype.function_name.c_str(), name);
+ state->prototype.function_name.c_str(), name);
}
- auto it = std::find_if(state.prototype.arguments.begin(),
- state.prototype.arguments.end(),
+ auto it = std::find_if(state->prototype.arguments.begin(),
+ state->prototype.arguments.end(),
[name](const sql_argument::ArgumentDefinition& arg) {
return arg.dollar_name() == name;
});
- if (it == state.prototype.arguments.end()) {
+ if (it == state->prototype.arguments.end()) {
return base::ErrStatus(
"%s: parameter %s does not appear in the list of arguments in the "
"prototype of the view function.",
- state.prototype.function_name.c_str(), name);
+ state->prototype.function_name.c_str(), name);
}
}
// Verify that the prepared statement column count matches the return
// count.
- uint32_t col_count =
+ auto col_count =
static_cast<uint32_t>(sqlite3_column_count(stmt.sqlite_stmt()));
- if (col_count != state.return_values.size()) {
+ if (col_count != state->return_values.size()) {
return base::ErrStatus(
"%s: number of return values %u does not match SQL statement column "
"count %zu.",
- state.prototype.function_name.c_str(), col_count,
- state.return_values.size());
+ state->prototype.function_name.c_str(), col_count,
+ state->return_values.size());
}
// Verify that the return names matches the prepared statment column names.
for (uint32_t i = 0; i < col_count; ++i) {
const char* name =
sqlite3_column_name(stmt.sqlite_stmt(), static_cast<int>(i));
- if (name != state.return_values[i].name()) {
+ if (name != state->return_values[i].name()) {
return base::ErrStatus(
"%s: column %s at index %u does not match return value name %s.",
- state.prototype.function_name.c_str(), name, i,
- state.return_values[i].name().c_str());
+ state->prototype.function_name.c_str(), name, i,
+ state->return_values[i].name().c_str());
}
}
- state.reusable_stmt = std::move(stmt);
+ state->reusable_stmt = std::move(stmt);
- std::string fn_name = state.prototype.function_name;
- std::string lower_name = base::ToLower(state.prototype.function_name);
+ // TODO(lalitm): this suffers the same non-atomic DROP/CREATE problem as
+ // CREATE PERFETTO TABLE implementation above: see the comment there for
+ // more info on this.
+ std::string fn_name = state->prototype.function_name;
+ std::string lower_name = base::ToLower(state->prototype.function_name);
if (runtime_table_fn_states_.Find(lower_name)) {
if (!cf.replace) {
return base::ErrStatus("Table function named %s already exists",
- state.prototype.function_name.c_str());
+ state->prototype.function_name.c_str());
}
// This will cause |OnTableFunctionDestroyed| below to be executed.
base::StackString<1024> drop("DROP TABLE %s",
- state.prototype.function_name.c_str());
+ state->prototype.function_name.c_str());
auto res = Execute(
SqlSource::FromTraceProcessorImplementation(drop.ToStdString()));
RETURN_IF_ERROR(res.status());
}
- auto it_and_inserted = runtime_table_fn_states_.Insert(
- lower_name,
- std::make_unique<RuntimeTableFunction::State>(std::move(state)));
+ auto it_and_inserted =
+ runtime_table_fn_states_.Insert(lower_name, std::move(state));
PERFETTO_CHECK(it_and_inserted.second);
base::StackString<1024> create(
"CREATE VIRTUAL TABLE %s USING runtime_table_function", fn_name.c_str());
- return cf.sql.RewriteAllIgnoreExisting(
- SqlSource::FromTraceProcessorImplementation(create.ToStdString()));
+ auto status = Execute(cf.sql.RewriteAllIgnoreExisting(
+ SqlSource::FromTraceProcessorImplementation(
+ create.ToStdString())))
+ .status();
+ if (!status.ok()) {
+ // If the registration of the table with SQLite failed, erase the state
+ // we hold.
+ PERFETTO_CHECK(runtime_table_fn_states_.Erase(lower_name));
+ }
+ return status;
}
base::Status PerfettoSqlEngine::ExecuteCreateMacro(
@@ -736,6 +769,7 @@
}
std::vector<std::string> args;
+ args.reserve(create_macro.args.size());
for (const auto& arg : create_macro.args) {
args.push_back(arg.first.sql());
}
@@ -745,7 +779,7 @@
std::move(args),
create_macro.sql,
};
- if (auto it = macros_.Find(create_macro.name.sql()); it) {
+ if (auto* it = macros_.Find(create_macro.name.sql()); it) {
if (!create_macro.replace) {
// TODO(lalitm): add a link to create macro documentation.
return base::ErrStatus("%sMacro already exists",
@@ -762,7 +796,7 @@
RuntimeTableFunction::State* PerfettoSqlEngine::GetRuntimeTableFunctionState(
const std::string& name) const {
- auto it = runtime_table_fn_states_.Find(base::ToLower(name));
+ auto* it = runtime_table_fn_states_.Find(base::ToLower(name));
PERFETTO_CHECK(it);
return it->get();
}
@@ -775,8 +809,8 @@
base::StatusOr<std::vector<std::string>>
PerfettoSqlEngine::GetColumnNamesFromSelectStatement(
const SqliteEngine::PreparedStatement& stmt,
- const char* tag) const {
- uint32_t columns =
+ const char* tag) {
+ auto columns =
static_cast<uint32_t>(sqlite3_column_count(stmt.sqlite_stmt()));
std::vector<std::string> column_names;
for (uint32_t i = 0; i < columns; ++i) {
@@ -804,7 +838,7 @@
base::Status PerfettoSqlEngine::ValidateColumnNames(
const std::vector<std::string>& column_names,
const std::vector<sql_argument::ArgumentDefinition>& schema,
- const char* tag) const {
+ const char* tag) {
// If the user has not provided a schema, we have nothing to validate.
if (schema.empty()) {
return base::OkStatus();
@@ -860,13 +894,13 @@
const RuntimeTable* PerfettoSqlEngine::GetRuntimeTableOrNull(
std::string_view name) const {
- auto table_ptr = runtime_tables_.Find(name.data());
+ auto* table_ptr = runtime_tables_.Find(name.data());
return table_ptr ? table_ptr->get() : nullptr;
}
const Table* PerfettoSqlEngine::GetStaticTableOrNull(
std::string_view name) const {
- auto table_ptr = static_tables_.Find(name.data());
+ auto* table_ptr = static_tables_.Find(name.data());
return table_ptr ? *table_ptr : nullptr;
}
diff --git a/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h b/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h
index 60772ec..69e6b58 100644
--- a/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h
+++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h
@@ -19,24 +19,30 @@
#include <cstdint>
#include <memory>
-#include <optional>
#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
#include "perfetto/base/logging.h"
#include "perfetto/base/status.h"
#include "perfetto/ext/base/flat_hash_map.h"
#include "perfetto/ext/base/status_or.h"
+#include "perfetto/trace_processor/basic_types.h"
+#include "src/trace_processor/containers/string_pool.h"
#include "src/trace_processor/db/runtime_table.h"
#include "src/trace_processor/db/table.h"
+#include "src/trace_processor/perfetto_sql/engine/function_util.h"
#include "src/trace_processor/perfetto_sql/engine/perfetto_sql_parser.h"
#include "src/trace_processor/perfetto_sql/engine/perfetto_sql_preprocessor.h"
#include "src/trace_processor/perfetto_sql/engine/runtime_table_function.h"
#include "src/trace_processor/perfetto_sql/intrinsics/functions/sql_function.h"
#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/static_table_function.h"
-#include "src/trace_processor/sqlite/scoped_db.h"
+#include "src/trace_processor/sqlite/query_cache.h"
#include "src/trace_processor/sqlite/sql_source.h"
#include "src/trace_processor/sqlite/sqlite_engine.h"
#include "src/trace_processor/sqlite/sqlite_utils.h"
+#include "src/trace_processor/util/sql_argument.h"
#include "src/trace_processor/util/sql_modules.h"
namespace perfetto::trace_processor {
@@ -111,7 +117,7 @@
// of |return_type| and is implemented by executing the SQL statement |sql|.
base::Status RegisterRuntimeFunction(bool replace,
const FunctionPrototype& prototype,
- std::string return_type,
+ const std::string& return_type,
SqlSource sql);
// Enables memoization for the given SQL function.
@@ -176,9 +182,7 @@
const Table* GetStaticTableOrNull(std::string_view) const;
private:
- base::StatusOr<SqlSource> ExecuteCreateFunction(
- const PerfettoSqlParser::CreateFunction&,
- const PerfettoSqlParser& parser);
+ base::Status ExecuteCreateFunction(const PerfettoSqlParser::CreateFunction&);
base::Status ExecuteInclude(const PerfettoSqlParser::Include&,
const PerfettoSqlParser& parser);
@@ -200,16 +204,16 @@
// Get the column names from a statement.
// |operator_name| is used in the error message if the statement is invalid.
- base::StatusOr<std::vector<std::string>> GetColumnNamesFromSelectStatement(
- const SqliteEngine::PreparedStatement& stmt,
- const char* tag) const;
+ static base::StatusOr<std::vector<std::string>>
+ GetColumnNamesFromSelectStatement(const SqliteEngine::PreparedStatement& stmt,
+ const char* tag);
// Validates that the column names in |column_names| match the |schema|.
// |operator_name| is used in the error message if the statement is invalid.
- base::Status ValidateColumnNames(
+ static base::Status ValidateColumnNames(
const std::vector<std::string>& column_names,
const std::vector<sql_argument::ArgumentDefinition>& schema,
- const char* operator_name) const;
+ const char* operator_name);
// Given a module and a key, include the correct file(s) from the module.
// The key can contain a wildcard to include all files in the module with the
@@ -255,7 +259,7 @@
template <typename Function>
void WrapSqlFunction(sqlite3_context* ctx, int argc, sqlite3_value** argv) {
using Context = typename Function::Context;
- Context* ud = static_cast<Context*>(sqlite3_user_data(ctx));
+ auto* ud = static_cast<Context*>(sqlite3_user_data(ctx));
ScopedCleanup<Function> scoped_cleanup{ud};
SqlValue value{};
@@ -314,14 +318,14 @@
base::Status PerfettoSqlEngine::RegisterStaticFunction(
const char* name,
int argc,
- std::unique_ptr<typename Function::Context> user_data,
+ std::unique_ptr<typename Function::Context> ctx,
bool deterministic) {
// Metric proto builder functions can be reregistered: don't double count when
// this happens.
if (!engine_->GetFunctionContext(name, argc)) {
static_function_count_++;
}
- return RegisterFunctionWithSqlite<Function>(name, argc, std::move(user_data),
+ return RegisterFunctionWithSqlite<Function>(name, argc, std::move(ctx),
deterministic);
}
@@ -329,14 +333,14 @@
base::Status PerfettoSqlEngine::RegisterFunctionWithSqlite(
const char* name,
int argc,
- std::unique_ptr<typename Function::Context> user_data,
+ std::unique_ptr<typename Function::Context> ctx,
bool deterministic) {
auto ctx_destructor = [](void* ptr) {
delete static_cast<typename Function::Context*>(ptr);
};
return engine_->RegisterFunction(
name, argc, perfetto_sql_internal::WrapSqlFunction<Function>,
- user_data.release(), ctx_destructor, deterministic);
+ ctx.release(), ctx_destructor, deterministic);
}
} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/perfetto_sql/intrinsics/table_functions/dominator_tree.cc b/src/trace_processor/perfetto_sql/intrinsics/table_functions/dominator_tree.cc
index 15d13ef..071982f 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/table_functions/dominator_tree.cc
+++ b/src/trace_processor/perfetto_sql/intrinsics/table_functions/dominator_tree.cc
@@ -287,13 +287,10 @@
NodeState& w_parent_state = GetStateForNode(w_parent);
for (Node v : w_parent_state.self_as_semi_dominator) {
- NodeState& v_state = GetStateForNode(v);
-
Node u = forest.GetMinSemiDominatorToAncestor(v, *this);
- NodeState& u_state = GetStateForNode(u);
-
- u_state.dominator =
- u_state.semi_dominator < v_state.semi_dominator ? u : w_parent;
+ NodeState& v_state = GetStateForNode(v);
+ v_state.dominator =
+ GetSemiDominator(u) < v_state.semi_dominator ? u : w_parent;
}
w_parent_state.self_as_semi_dominator.clear();
}
diff --git a/src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/thread_states.sql b/src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/thread_states.sql
index 952fb1d..7590ffc 100644
--- a/src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/thread_states.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/thread_states.sql
@@ -28,20 +28,9 @@
id INT)
-- Human-readable name for the thread state.
RETURNS STRING AS
-WITH data AS (
- SELECT
- _translate_thread_state_name(state) AS state,
- (CASE io_wait
- WHEN 1 THEN ' (IO)'
- WHEN 0 THEN ' (non-IO)'
- ELSE ''
- END) AS io_wait
- FROM thread_state
- WHERE id = $id
-)
-SELECT
- printf('%s%s', state, io_wait)
-FROM data;
+SELECT sched_state_io_to_human_readable_string(state, io_wait)
+FROM thread_state
+WHERE id = $id;
-- Returns an aggregation of thread states (by state and cpu) for a given
-- interval of time for a given thread.
@@ -87,14 +76,13 @@
SELECT * FROM first_state_starting_before
)
SELECT
- full_name.sched_state_full_name as state,
+ sched_state_io_to_human_readable_string(state, io_wait) as state,
state as raw_state,
cpu_guess_core_type(cpu) as cpu_type,
cpu,
blocked_function,
sum(spans_overlapping_dur($ts, $dur, ts, dur)) as dur
FROM thread_state
-JOIN sched_state_full_name!(thread_state, state, io_wait) full_name USING (id)
JOIN relevant_states USING (id)
GROUP BY state, raw_state, cpu_type, cpu, blocked_function
ORDER BY dur desc;
\ No newline at end of file
diff --git a/src/trace_processor/perfetto_sql/stdlib/prelude/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/prelude/BUILD.gn
index 03f9cd1..499c2e6 100644
--- a/src/trace_processor/perfetto_sql/stdlib/prelude/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/stdlib/prelude/BUILD.gn
@@ -16,6 +16,7 @@
perfetto_sql_source_set("prelude") {
sources = [
+ "casts.sql",
"slices.sql",
"trace_bounds.sql",
]
diff --git a/src/trace_processor/perfetto_sql/stdlib/prelude/casts.sql b/src/trace_processor/perfetto_sql/stdlib/prelude/casts.sql
new file mode 100644
index 0000000..73c9486
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/prelude/casts.sql
@@ -0,0 +1,35 @@
+--
+-- Copyright 2023 The Android Open Source Project
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- https://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+
+-- Casts |value| to INT.
+CREATE PERFETTO MACRO cast_int(
+ -- Query or subquery that will be cast.
+ value Expr
+) RETURNS Expr AS
+CAST($value AS INT);
+
+-- Casts |value| to DOUBLE.
+CREATE PERFETTO MACRO cast_double(
+ -- Query or subquery that will be cast.
+ value Expr
+) RETURNS Expr AS
+CAST($value AS REAL);
+
+-- Casts |value| to STRING.
+CREATE PERFETTO MACRO cast_string(
+ -- Query or subquery that will be cast.
+ value Expr
+) RETURNS Expr AS
+CAST($value AS TEXT);
\ No newline at end of file
diff --git a/src/trace_processor/perfetto_sql/stdlib/sched/states.sql b/src/trace_processor/perfetto_sql/stdlib/sched/states.sql
index 03f3d9c..b991692 100644
--- a/src/trace_processor/perfetto_sql/stdlib/sched/states.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/sched/states.sql
@@ -17,8 +17,7 @@
-- cases which thread_state.ts handles (as complex strings manipulations in
-- SQL are pretty painful), but they are pretty niche.
--- Translates the thread state name from a single-letter shorthard to
--- a human-readable name.
+-- Translates a single-letter scheduling state to a human-readable string.
CREATE PERFETTO FUNCTION sched_state_to_human_readable_string(
-- An individual character string representing the scheduling state of the
-- kernel thread at the end of the slice.
@@ -50,41 +49,24 @@
ELSE $short_name
END;
--- Creates a table with humanly readable sched state names with IO waits.
--- Translates the individual characters in the string to the following: R
--- (runnable), S (awaiting a wakeup), D (in an uninterruptible sleep), T
--- (suspended), t (being traced), X (exiting), P (parked), W (waking), I
--- (idle), N (not contributing to the load average), K (wakeable on fatal
--- signals) and Z (zombie, awaiting cleanup). Adds the IO wait (IO/non
--- IO/nothing) based on the value in the `io_wait_column`.
-CREATE PERFETTO MACRO sched_state_full_name(
- -- Table with columns required for translation and `id` column for joins.
- states_table TableOrSubquery,
- -- Column in `states_table` with single character version of sched state
- -- string.
- sched_name_column ColumnName,
- -- Column in `states_table` with 0 for IO and 1 for non-IO states. Can be
- -- a dummy (no real values), and no value from there would be added to the
- -- resulting strings.
- io_wait_column ColumnName
+-- Translates a single-letter scheduling state and IO wait information to
+-- a human-readable string.
+CREATE PERFETTO FUNCTION sched_state_io_to_human_readable_string(
+ -- An individual character string representing the scheduling state of the
+ -- kernel thread at the end of the slice.
+ sched_state STRING,
+ -- A (posssibly NULL) boolean indicating, if the device was in uninterruptible
+ -- sleep, if it was an IO sleep.
+ io_wait BOOL
)
--- Table with the schema (id UINT32, ts UINT64, sched_state_full_name STRING).
-RETURNS TableOrSubquery AS
-(
- WITH data AS
- (
- SELECT
- id,
- sched_state_to_human_readable_string($sched_name_column) AS sched_state_name,
- (CASE $io_wait_column
- WHEN 1 THEN ' (IO)'
- WHEN 0 THEN ' (non-IO)'
- ELSE ''
- END) AS io_wait
- FROM $states_table
- )
- SELECT
- id,
- printf('%s%s', sched_state_name, io_wait) AS sched_state_full_name
- FROM data
+-- A human readable string with information about the scheduling state and IO wait.
+RETURNS STRING AS
+SELECT printf(
+ '%s%s',
+ sched_state_to_human_readable_string($sched_state),
+ CASE $io_wait
+ WHEN 1 THEN ' (IO)'
+ WHEN 0 THEN ' (non-IO)'
+ ELSE ''
+ END
);
diff --git a/src/trace_processor/rpc/rpc.cc b/src/trace_processor/rpc/rpc.cc
index 68e0732..58fb245 100644
--- a/src/trace_processor/rpc/rpc.cc
+++ b/src/trace_processor/rpc/rpc.cc
@@ -24,11 +24,11 @@
#include "perfetto/base/time.h"
#include "perfetto/ext/base/utils.h"
#include "perfetto/ext/base/version.h"
+#include "perfetto/ext/protozero/proto_ring_buffer.h"
#include "perfetto/ext/trace_processor/rpc/query_result_serializer.h"
#include "perfetto/protozero/scattered_heap_buffer.h"
#include "perfetto/protozero/scattered_stream_writer.h"
#include "perfetto/trace_processor/trace_processor.h"
-#include "src/protozero/proto_ring_buffer.h"
#include "src/trace_processor/tp_metatrace.h"
#include "protos/perfetto/trace_processor/trace_processor.pbzero.h"
@@ -491,6 +491,10 @@
protozero::HeapBuffered<protos::pbzero::StatusResult> status;
status->set_loaded_trace_name(trace_processor_->GetCurrentTraceName());
status->set_human_readable_version(base::GetVersionString());
+ const char* version_code = base::GetVersionCode();
+ if (version_code) {
+ status->set_version_code(version_code);
+ }
status->set_api_version(protos::pbzero::TRACE_PROCESSOR_CURRENT_API_VERSION);
return status.SerializeAsArray();
}
diff --git a/src/trace_processor/rpc/rpc.h b/src/trace_processor/rpc/rpc.h
index c2ef645..c10d4bf 100644
--- a/src/trace_processor/rpc/rpc.h
+++ b/src/trace_processor/rpc/rpc.h
@@ -24,9 +24,9 @@
#include <stddef.h>
#include <stdint.h>
+#include "perfetto/ext/protozero/proto_ring_buffer.h"
#include "perfetto/trace_processor/basic_types.h"
#include "perfetto/trace_processor/status.h"
-#include "src/protozero/proto_ring_buffer.h"
namespace perfetto {
diff --git a/src/trace_processor/sqlite/db_sqlite_table.cc b/src/trace_processor/sqlite/db_sqlite_table.cc
index 4d38bd6..8d18365 100644
--- a/src/trace_processor/sqlite/db_sqlite_table.cc
+++ b/src/trace_processor/sqlite/db_sqlite_table.cc
@@ -23,6 +23,7 @@
#include <cstdint>
#include <functional>
#include <iterator>
+#include <limits>
#include <memory>
#include <optional>
#include <string>
@@ -51,9 +52,11 @@
#include "protos/perfetto/trace_processor/metatrace_categories.pbzero.h"
namespace perfetto::trace_processor {
-
namespace {
+static constexpr uint32_t kInvalidArgumentIndex =
+ std::numeric_limits<uint32_t>::max();
+
std::optional<FilterOp> SqliteOpToFilterOp(int sqlite_op) {
switch (sqlite_op) {
case SQLITE_INDEX_CONSTRAINT_EQ:
@@ -144,6 +147,41 @@
base::SmallVector<char, 2048> buffer_;
};
+base::Status ValidateTableFunctionArguments(const std::string& name,
+ const Table::Schema& schema,
+ const QueryConstraints& qc) {
+ for (uint32_t i = 0; i < schema.columns.size(); ++i) {
+ if (!schema.columns[i].is_hidden) {
+ continue;
+ }
+ auto pred = [i](const QueryConstraints::Constraint& c) {
+ return i == static_cast<uint32_t>(c.column);
+ };
+ auto it =
+ std::find_if(qc.constraints().begin(), qc.constraints().end(), pred);
+ if (it == qc.constraints().end()) {
+ return base::ErrStatus(
+ "Failed to find constraint on column '%u' in function %s", i,
+ name.c_str());
+ }
+
+ // Arguments should always use equality constraints.
+ if (it->op != SQLITE_INDEX_CONSTRAINT_EQ) {
+ return base::ErrStatus(
+ "Only equality constraints supported on column '%u'", i);
+ }
+
+ // Disallow multiple constraints on an argument column.
+ auto count = std::count_if(it + 1, qc.constraints().end(), pred);
+ if (count > 0) {
+ return base::ErrStatus(
+ "Found multiple constraints on column '%u' in function %s", i,
+ name.c_str());
+ }
+ }
+ return base::OkStatus();
+}
+
} // namespace
DbSqliteTable::DbSqliteTable(sqlite3*, Context* context) : context_(context) {}
@@ -203,7 +241,7 @@
BestIndex(schema_, runtime_table_->row_count(), qc, info);
break;
case TableComputation::kTableFunction:
- base::Status status = ValidateTableFunctionArguments(schema_, qc);
+ base::Status status = ValidateTableFunctionArguments(name(), schema_, qc);
if (!status.ok()) {
// TODO(lalitm): instead of returning SQLITE_CONSTRAINT which shows the
// user a very cryptic error message, consider instead SQLITE_OK but
@@ -399,34 +437,6 @@
return QueryCost{final_cost, current_row_count};
}
-base::Status DbSqliteTable::ValidateTableFunctionArguments(
- const Table::Schema& schema,
- const QueryConstraints& qc) {
- for (uint32_t i = 0; i < schema.columns.size(); ++i) {
- const auto& col = schema.columns[i];
- if (!col.is_hidden) {
- continue;
- }
- auto pred = [i](const QueryConstraints::Constraint& c) {
- return i == static_cast<uint32_t>(c.column);
- };
- auto it =
- std::find_if(qc.constraints().begin(), qc.constraints().end(), pred);
- if (it == qc.constraints().end()) {
- return base::ErrStatus("Failed to find constraint on column '%u'", i);
- }
- if (it->op != SQLITE_INDEX_CONSTRAINT_EQ) {
- return base::ErrStatus(
- "Only equality constraints supported on column '%u'", i);
- }
- auto count = std::count_if(it + 1, qc.constraints().end(), pred);
- if (count > 0) {
- return base::ErrStatus("Found multiple constraints on column '%u'", i);
- }
- }
- return base::OkStatus();
-}
-
std::unique_ptr<SqliteTable::BaseCursor> DbSqliteTable::CreateCursor() {
return std::make_unique<Cursor>(this, context_->cache);
}
@@ -434,7 +444,29 @@
DbSqliteTable::Cursor::Cursor(DbSqliteTable* sqlite_table, QueryCache* cache)
: SqliteTable::BaseCursor(sqlite_table),
db_sqlite_table_(sqlite_table),
- cache_(cache) {}
+ cache_(cache) {
+ switch (db_sqlite_table_->context_->computation) {
+ case TableComputation::kStatic:
+ upstream_table_ = db_sqlite_table_->context_->static_table;
+ argument_index_per_column_.resize(sqlite_table->schema_.columns.size(),
+ kInvalidArgumentIndex);
+ break;
+ case TableComputation::kRuntime:
+ upstream_table_ = db_sqlite_table_->runtime_table_;
+ argument_index_per_column_.resize(sqlite_table->schema_.columns.size(),
+ kInvalidArgumentIndex);
+ break;
+ case TableComputation::kTableFunction: {
+ uint32_t argument_index = 0;
+ for (const auto& col : sqlite_table->schema_.columns) {
+ argument_index_per_column_.emplace_back(
+ col.is_hidden ? argument_index++ : kInvalidArgumentIndex);
+ }
+ table_function_arguments_.resize(argument_index);
+ break;
+ }
+ }
+}
DbSqliteTable::Cursor::~Cursor() = default;
void DbSqliteTable::Cursor::TryCacheCreateSortedTable(
@@ -496,56 +528,13 @@
// before the table's destructor.
iterator_ = std::nullopt;
- // We reuse this vector to reduce memory allocations on nested subqueries.
- constraints_.resize(qc.constraints().size());
- uint32_t constraints_pos = 0;
- for (size_t i = 0; i < qc.constraints().size(); ++i) {
- const auto& cs = qc.constraints()[i];
- auto col = static_cast<uint32_t>(cs.column);
-
- // If we get a std::nullopt FilterOp, that means we should allow SQLite
- // to handle the constraint.
- std::optional<FilterOp> opt_op = SqliteOpToFilterOp(cs.op);
- if (!opt_op)
- continue;
-
- SqlValue value = SqliteValueToSqlValue(argv[i]);
- if constexpr (regex::IsRegexSupported()) {
- if (*opt_op == FilterOp::kRegex) {
- if (value.type != SqlValue::kString)
- return base::ErrStatus("Value has to be a string");
-
- if (auto regex_status = regex::Regex::Create(value.AsString());
- !regex_status.ok())
- return regex_status.status();
- }
- }
- constraints_[constraints_pos++] = Constraint{col, *opt_op, value};
- }
- constraints_.resize(constraints_pos);
-
- // We reuse this vector to reduce memory allocations on nested subqueries.
- orders_.resize(qc.order_by().size());
- for (size_t i = 0; i < qc.order_by().size(); ++i) {
- const auto& ob = qc.order_by()[i];
- auto col = static_cast<uint32_t>(ob.iColumn);
- orders_[i] = Order{col, static_cast<bool>(ob.desc)};
- }
+ RETURN_IF_ERROR(PopulateConstraintsAndArguments(qc, argv));
+ PopulateOrderBys(qc);
// Setup the upstream table based on the computation state.
switch (db_sqlite_table_->context_->computation) {
case TableComputation::kStatic:
- // If we have a static table, just set the upstream table to be the static
- // table.
- upstream_table_ = db_sqlite_table_->context_->static_table;
-
- // Tries to create a sorted cached table which can be used to speed up
- // filters below.
- TryCacheCreateSortedTable(qc, history);
- break;
case TableComputation::kRuntime:
- upstream_table_ = db_sqlite_table_->runtime_table_;
-
// Tries to create a sorted cached table which can be used to speed up
// filters below.
TryCacheCreateSortedTable(qc, history);
@@ -555,8 +544,6 @@
"TABLE_FUNCTION_CALL", [this](metatrace::Record* r) {
r->AddArg("Name", db_sqlite_table_->name());
});
- RETURN_IF_ERROR(ExtractTableFunctionArguments(
- db_sqlite_table_->schema_, constraints_, table_function_arguments_));
base::StatusOr<std::unique_ptr<Table>> table =
db_sqlite_table_->context_->static_table_function->ComputeTable(
table_function_arguments_);
@@ -572,79 +559,7 @@
PERFETTO_TP_TRACE(
metatrace::Category::QUERY_DETAILED, "DB_TABLE_FILTER_AND_SORT",
- [this](metatrace::Record* r) {
- r->AddArg("Table", db_sqlite_table_->name());
- for (const Constraint& c : constraints_) {
- SafeStringWriter writer;
- writer.AppendString(
- db_sqlite_table_->schema_.columns[c.col_idx].name);
-
- writer.AppendString(" ");
- switch (c.op) {
- case FilterOp::kEq:
- writer.AppendString("=");
- break;
- case FilterOp::kGe:
- writer.AppendString(">=");
- break;
- case FilterOp::kGt:
- writer.AppendString(">");
- break;
- case FilterOp::kLe:
- writer.AppendString("<=");
- break;
- case FilterOp::kLt:
- writer.AppendString("<");
- break;
- case FilterOp::kNe:
- writer.AppendString("!=");
- break;
- case FilterOp::kIsNull:
- writer.AppendString("IS");
- break;
- case FilterOp::kIsNotNull:
- writer.AppendString("IS NOT");
- break;
- case FilterOp::kGlob:
- writer.AppendString("GLOB");
- break;
- case FilterOp::kRegex:
- writer.AppendString("REGEXP");
- break;
- }
- writer.AppendString(" ");
-
- switch (c.value.type) {
- case SqlValue::kString:
- writer.AppendString(c.value.AsString());
- break;
- case SqlValue::kBytes:
- writer.AppendString("<bytes>");
- break;
- case SqlValue::kNull:
- writer.AppendString("<null>");
- break;
- case SqlValue::kDouble: {
- writer.AppendString(std::to_string(c.value.AsDouble()));
- break;
- }
- case SqlValue::kLong: {
- writer.AppendString(std::to_string(c.value.AsLong()));
- break;
- }
- }
- r->AddArg("Constraint", writer.GetStringView());
- }
-
- for (const auto& o : orders_) {
- SafeStringWriter writer;
- writer.AppendString(
- db_sqlite_table_->schema_.columns[o.col_idx].name);
- if (o.desc)
- writer.AppendString(" desc");
- r->AddArg("Order by", writer.GetStringView());
- }
- });
+ [this](metatrace::Record* r) { FilterAndSortMetatrace(r); });
RowMap filter_map = SourceTable()->QueryToRowMap(constraints_, orders_);
if (filter_map.IsRange() && filter_map.size() <= 1) {
@@ -669,46 +584,127 @@
return base::OkStatus();
}
-base::Status DbSqliteTable::Cursor::ExtractTableFunctionArguments(
- const Table::Schema& schema,
- std::vector<Constraint>& constraints,
- std::vector<SqlValue>& function_arguments) {
- // Ensure that the vector is empty as we will add to it below.
- function_arguments.clear();
+base::Status DbSqliteTable::Cursor::PopulateConstraintsAndArguments(
+ const QueryConstraints& qc,
+ sqlite3_value** argv) {
+ // We reuse this vector to reduce memory allocations on nested subqueries.
+ constraints_.resize(qc.constraints().size());
+ uint32_t constraints_pos = 0;
+ for (size_t i = 0; i < qc.constraints().size(); ++i) {
+ const auto& cs = qc.constraints()[i];
+ auto col = static_cast<uint32_t>(cs.column);
- // It's important that we iterate in schema order as this will match the
- // function argument order.
- for (uint32_t i = 0; i < schema.columns.size(); ++i) {
- const auto& col = schema.columns[i];
- if (!col.is_hidden) {
+ // If we get a std::nullopt FilterOp, that means we should allow SQLite
+ // to handle the constraint.
+ std::optional<FilterOp> opt_op = SqliteOpToFilterOp(cs.op);
+ if (!opt_op)
continue;
+
+ SqlValue value = SqliteValueToSqlValue(argv[i]);
+ if constexpr (regex::IsRegexSupported()) {
+ if (*opt_op == FilterOp::kRegex) {
+ if (value.type != SqlValue::kString)
+ return base::ErrStatus("Value has to be a string");
+
+ if (auto regex_status = regex::Regex::Create(value.AsString());
+ !regex_status.ok())
+ return regex_status.status();
+ }
}
- // ValidateTableFunctionArguments should ensure we only have one constraint
- // but double check this is the case.
- PERFETTO_DCHECK(std::count_if(constraints.begin(), constraints.end(),
- [i](const Constraint& c) {
- return i == c.col_idx;
- }) == 1);
- // ValidateTableFunctionArguments should ensure that we do not get here
- // without a valid equality constraint.
- auto it = std::find_if(constraints.begin(), constraints.end(),
- [i](const Constraint& c) { return i == c.col_idx; });
- PERFETTO_CHECK(it != constraints.end());
- PERFETTO_CHECK(it->op == FilterOp::kEq);
-
- // Add the argument to the arguments.
- function_arguments.push_back(it->value);
+ uint32_t argument_index = argument_index_per_column_[col];
+ if (argument_index == kInvalidArgumentIndex) {
+ constraints_[constraints_pos++] = Constraint{col, *opt_op, value};
+ } else {
+ table_function_arguments_[argument_index] = value;
+ }
}
- // Remove all the argument constraints from the main vector.
- constraints.erase(std::remove_if(constraints.begin(), constraints.end(),
- [&schema](const Constraint& c) {
- return schema.columns[c.col_idx].is_hidden;
- }),
- constraints.end());
+ constraints_.resize(constraints_pos);
return base::OkStatus();
}
+void DbSqliteTable::Cursor::PopulateOrderBys(const QueryConstraints& qc) {
+ // We reuse this vector to reduce memory allocations on nested subqueries.
+ orders_.resize(qc.order_by().size());
+ for (size_t i = 0; i < qc.order_by().size(); ++i) {
+ const auto& ob = qc.order_by()[i];
+ auto col = static_cast<uint32_t>(ob.iColumn);
+ orders_[i] = Order{col, static_cast<bool>(ob.desc)};
+ }
+}
+
+void DbSqliteTable::Cursor::FilterAndSortMetatrace(metatrace::Record* r) {
+ r->AddArg("Table", db_sqlite_table_->name());
+ for (const Constraint& c : constraints_) {
+ SafeStringWriter writer;
+ writer.AppendString(db_sqlite_table_->schema_.columns[c.col_idx].name);
+
+ writer.AppendString(" ");
+ switch (c.op) {
+ case FilterOp::kEq:
+ writer.AppendString("=");
+ break;
+ case FilterOp::kGe:
+ writer.AppendString(">=");
+ break;
+ case FilterOp::kGt:
+ writer.AppendString(">");
+ break;
+ case FilterOp::kLe:
+ writer.AppendString("<=");
+ break;
+ case FilterOp::kLt:
+ writer.AppendString("<");
+ break;
+ case FilterOp::kNe:
+ writer.AppendString("!=");
+ break;
+ case FilterOp::kIsNull:
+ writer.AppendString("IS");
+ break;
+ case FilterOp::kIsNotNull:
+ writer.AppendString("IS NOT");
+ break;
+ case FilterOp::kGlob:
+ writer.AppendString("GLOB");
+ break;
+ case FilterOp::kRegex:
+ writer.AppendString("REGEXP");
+ break;
+ }
+ writer.AppendString(" ");
+
+ switch (c.value.type) {
+ case SqlValue::kString:
+ writer.AppendString(c.value.AsString());
+ break;
+ case SqlValue::kBytes:
+ writer.AppendString("<bytes>");
+ break;
+ case SqlValue::kNull:
+ writer.AppendString("<null>");
+ break;
+ case SqlValue::kDouble: {
+ writer.AppendString(std::to_string(c.value.AsDouble()));
+ break;
+ }
+ case SqlValue::kLong: {
+ writer.AppendString(std::to_string(c.value.AsLong()));
+ break;
+ }
+ }
+ r->AddArg("Constraint", writer.GetStringView());
+ }
+
+ for (const auto& o : orders_) {
+ SafeStringWriter writer;
+ writer.AppendString(db_sqlite_table_->schema_.columns[o.col_idx].name);
+ if (o.desc)
+ writer.AppendString(" desc");
+ r->AddArg("Order by", writer.GetStringView());
+ }
+}
+
DbSqliteTableContext::DbSqliteTableContext(QueryCache* query_cache,
const Table* table,
Table::Schema schema)
diff --git a/src/trace_processor/sqlite/db_sqlite_table.h b/src/trace_processor/sqlite/db_sqlite_table.h
index 5f4a3d7..45bc124 100644
--- a/src/trace_processor/sqlite/db_sqlite_table.h
+++ b/src/trace_processor/sqlite/db_sqlite_table.h
@@ -19,6 +19,7 @@
#include <cstdint>
#include <functional>
+#include <limits>
#include <memory>
#include <optional>
#include <string>
@@ -37,6 +38,7 @@
#include "src/trace_processor/sqlite/query_constraints.h"
#include "src/trace_processor/sqlite/sqlite_table.h"
#include "src/trace_processor/sqlite/sqlite_utils.h"
+#include "src/trace_processor/tp_metatrace.h"
namespace perfetto {
namespace trace_processor {
@@ -92,8 +94,11 @@
Cursor(DbSqliteTable*, QueryCache*);
~Cursor() final;
- Cursor(Cursor&&) noexcept = default;
- Cursor& operator=(Cursor&&) = default;
+ Cursor(const Cursor&) = delete;
+ Cursor& operator=(const Cursor&) = delete;
+
+ Cursor(Cursor&&) noexcept = delete;
+ Cursor& operator=(Cursor&&) = delete;
// Implementation of SqliteTable::Cursor.
base::Status Filter(const QueryConstraints& qc,
@@ -143,16 +148,16 @@
return sorted_cache_table_ ? &*sorted_cache_table_ : upstream_table_;
}
- static base::Status ExtractTableFunctionArguments(
- const Table::Schema&,
- std::vector<Constraint>& constraints,
- std::vector<SqlValue>& function_arguments);
+ base::Status PopulateConstraintsAndArguments(const QueryConstraints& qc,
+ sqlite3_value** argv);
- Cursor(const Cursor&) = delete;
- Cursor& operator=(const Cursor&) = delete;
+ void PopulateOrderBys(const QueryConstraints& qc);
+
+ void FilterAndSortMetatrace(metatrace::Record* record);
DbSqliteTable* db_sqlite_table_ = nullptr;
QueryCache* cache_ = nullptr;
+ std::vector<uint32_t> argument_index_per_column_;
const Table* upstream_table_ = nullptr;
@@ -213,9 +218,6 @@
const QueryConstraints& qc);
private:
- static base::Status ValidateTableFunctionArguments(const Table::Schema&,
- const QueryConstraints&);
-
Context* context_ = nullptr;
// Only valid after Init has completed.
diff --git a/src/trace_processor/sqlite/sqlite_engine.cc b/src/trace_processor/sqlite/sqlite_engine.cc
index 25cc651..9c5da35 100644
--- a/src/trace_processor/sqlite/sqlite_engine.cc
+++ b/src/trace_processor/sqlite/sqlite_engine.cc
@@ -139,14 +139,14 @@
}
}
- // Reset the database itself.
- db_.reset();
-
- // SQLite is not guaranteed to pick saved tables back up when destroyed as
- // from it's perspective, it has called xDisconnect. Make sure to do that
- // ourselves.
+ // SQLite will not pick saved tables back up when destroyed as, from it's
+ // perspective, it has called xDisconnect. Make sure to do that ourselves.
saved_tables_.Clear();
+ // Reset the database itself. We need to do this after clearing the saved
+ // tables as the saved tables could hold onto prepared statements.
+ db_.reset();
+
// The above operations should have cleared all the tables.
if (PERFETTO_UNLIKELY(sqlite_tables_.size() != 0)) {
std::vector<std::string> tables;
diff --git a/src/trace_processor/storage/stats.h b/src/trace_processor/storage/stats.h
index ec72287..7fb6ce0 100644
--- a/src/trace_processor/storage/stats.h
+++ b/src/trace_processor/storage/stats.h
@@ -145,9 +145,9 @@
F(traced_buf_padding_bytes_written, kIndexed, kInfo, kTrace, ""), \
F(traced_buf_patches_failed, kIndexed, kDataLoss, kTrace, \
"The tracing service potentially lost data from one of the data sources "\
- "writing into the given target_buffer. This entry can be ignored" \
- "if you're using DISCARD buffers and traced_buf_chunks_discarded is \
- nonzero, meaning that the buffer was filled."), \
+ "writing into the given target_buffer. This entry can be ignored " \
+ "if you're using DISCARD buffers and traced_buf_chunks_discarded is " \
+ "nonzero, meaning that the buffer was filled."), \
F(traced_buf_patches_succeeded, kIndexed, kInfo, kTrace, ""), \
F(traced_buf_readaheads_failed, kIndexed, kInfo, kTrace, ""), \
F(traced_buf_readaheads_succeeded, kIndexed, kInfo, kTrace, ""), \
diff --git a/src/trace_processor/trace_blob.cc b/src/trace_processor/trace_blob.cc
index a77017b..9855ae7 100644
--- a/src/trace_processor/trace_blob.cc
+++ b/src/trace_processor/trace_blob.cc
@@ -25,6 +25,7 @@
#include "perfetto/base/logging.h"
#include "perfetto/ext/base/utils.h"
#include "perfetto/trace_processor/basic_types.h"
+#include "perfetto/trace_processor/ref_counted.h"
#if TRACE_PROCESSOR_HAS_MMAP()
#include <sys/mman.h>
@@ -89,20 +90,25 @@
size_ = 0;
}
-TraceBlob& TraceBlob::operator=(TraceBlob&& other) noexcept {
- if (this == &other)
- return *this;
+TraceBlob::TraceBlob(TraceBlob&& other) noexcept
+ : RefCounted(std::move(other)) {
static_assert(sizeof(*this) == base::AlignUp<sizeof(void*)>(
sizeof(data_) + sizeof(size_) +
sizeof(ownership_) + sizeof(RefCounted)),
- "TraceBlob move operator needs updating");
+ "TraceBlob move constructor needs updating");
data_ = other.data_;
size_ = other.size_;
ownership_ = other.ownership_;
other.data_ = nullptr;
other.size_ = 0;
other.ownership_ = Ownership::kNull;
- RefCounted::operator=(std::move(other));
+}
+
+TraceBlob& TraceBlob::operator=(TraceBlob&& other) noexcept {
+ if (this == &other)
+ return *this;
+ this->~TraceBlob();
+ new (this) TraceBlob(std::move(other));
return *this;
}
diff --git a/src/trace_processor/util/stack_traces_util.cc b/src/trace_processor/trace_blob_unittest.cc
similarity index 68%
rename from src/trace_processor/util/stack_traces_util.cc
rename to src/trace_processor/trace_blob_unittest.cc
index a255560..5063445 100644
--- a/src/trace_processor/util/stack_traces_util.cc
+++ b/src/trace_processor/trace_blob_unittest.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * 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.
@@ -14,17 +14,21 @@
* limitations under the License.
*/
-#include "src/trace_processor/util/stack_traces_util.h"
-#include "perfetto/ext/base/string_view.h"
+#include "perfetto/trace_processor/trace_blob.h"
+
+#include "test/gtest_and_gmock.h"
namespace perfetto {
namespace trace_processor {
-namespace util {
+namespace {
-bool IsHexModuleId(base::StringView module) {
- return module.size() == 33;
+TEST(TraceBlob, MoveAssignment) {
+ TraceBlob b1 = TraceBlob::Allocate(16);
+ TraceBlob b2 = TraceBlob::Allocate(16);
+
+ b1 = std::move(b2);
}
-} // namespace util
+} // namespace
} // namespace trace_processor
} // namespace perfetto
diff --git a/src/trace_processor/trace_processor_context.cc b/src/trace_processor/trace_processor_context.cc
index c7cea38..f4f6c48 100644
--- a/src/trace_processor/trace_processor_context.cc
+++ b/src/trace_processor/trace_processor_context.cc
@@ -27,6 +27,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/global_args_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_tracker.h"
#include "src/trace_processor/importers/common/slice_tracker.h"
diff --git a/src/trace_processor/trace_processor_storage_impl.cc b/src/trace_processor/trace_processor_storage_impl.cc
index 05b88c0..4b47d52 100644
--- a/src/trace_processor/trace_processor_storage_impl.cc
+++ b/src/trace_processor/trace_processor_storage_impl.cc
@@ -26,6 +26,7 @@
#include "src/trace_processor/importers/common/clock_tracker.h"
#include "src/trace_processor/importers/common/event_tracker.h"
#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_tracker.h"
#include "src/trace_processor/importers/common/slice_tracker.h"
@@ -62,6 +63,7 @@
context_.process_tracker.reset(new ProcessTracker(&context_));
context_.clock_tracker.reset(new ClockTracker(&context_));
context_.clock_converter.reset(new ClockConverter(&context_));
+ context_.mapping_tracker.reset(new MappingTracker(&context_));
context_.perf_sample_tracker.reset(new PerfSampleTracker(&context_));
context_.stack_profile_tracker.reset(new StackProfileTracker(&context_));
context_.metadata_tracker.reset(new MetadataTracker(context_.storage.get()));
diff --git a/src/trace_processor/types/trace_processor_context.h b/src/trace_processor/types/trace_processor_context.h
index ad4e148..769ca0e 100644
--- a/src/trace_processor/types/trace_processor_context.h
+++ b/src/trace_processor/types/trace_processor_context.h
@@ -55,6 +55,7 @@
class StackProfileTracker;
class HeapGraphTracker;
class PerfSampleTracker;
+class MappingTracker;
class MetadataTracker;
class PacketAnalyzer;
class ProtoImporterModule;
@@ -100,6 +101,7 @@
std::unique_ptr<EventTracker> event_tracker;
std::unique_ptr<ClockTracker> clock_tracker;
std::unique_ptr<ClockConverter> clock_converter;
+ std::unique_ptr<MappingTracker> mapping_tracker;
std::unique_ptr<PerfSampleTracker> perf_sample_tracker;
std::unique_ptr<StackProfileTracker> stack_profile_tracker;
std::unique_ptr<MetadataTracker> metadata_tracker;
diff --git a/src/trace_processor/util/BUILD.gn b/src/trace_processor/util/BUILD.gn
index 8420a06..13aefe5 100644
--- a/src/trace_processor/util/BUILD.gn
+++ b/src/trace_processor/util/BUILD.gn
@@ -61,6 +61,17 @@
}
}
+source_set("build_id") {
+ sources = [
+ "build_id.cc",
+ "build_id.h",
+ ]
+ deps = [
+ "../../../gn:default_deps",
+ "../../../include/perfetto/ext/base:base",
+ ]
+}
+
source_set("profiler_util") {
sources = [
"profiler_util.cc",
@@ -74,18 +85,6 @@
]
}
-source_set("stack_traces_util") {
- sources = [
- "stack_traces_util.cc",
- "stack_traces_util.h",
- ]
- deps = [
- "../../../gn:default_deps",
- "../../../include/perfetto/ext/base:base",
- "../../../protos/perfetto/trace/profiling:zero",
- ]
-}
-
source_set("protozero_to_text") {
sources = [
"protozero_to_text.cc",
diff --git a/src/trace_processor/util/build_id.cc b/src/trace_processor/util/build_id.cc
new file mode 100644
index 0000000..889291d
--- /dev/null
+++ b/src/trace_processor/util/build_id.cc
@@ -0,0 +1,128 @@
+/*
+ * 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/util/build_id.h"
+
+#include <cctype>
+#include <cstddef>
+#include <string>
+
+#include "perfetto/base/logging.h"
+#include "perfetto/ext/base/string_utils.h"
+#include "perfetto/ext/base/string_view.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace {
+uint8_t HexToBinary(char c) {
+ switch (c) {
+ case '0':
+ return 0;
+ case '1':
+ return 1;
+ case '2':
+ return 2;
+ case '3':
+ return 3;
+ case '4':
+ return 4;
+ case '5':
+ return 5;
+ case '6':
+ return 6;
+ case '7':
+ return 7;
+ case '8':
+ return 8;
+ case '9':
+ return 9;
+ case 'a':
+ case 'A':
+ return 10;
+ case 'b':
+ case 'B':
+ return 11;
+ case 'c':
+ case 'C':
+ return 12;
+ case 'd':
+ case 'D':
+ return 13;
+ case 'e':
+ case 'E':
+ return 14;
+ case 'f':
+ case 'F':
+ return 15;
+ default:
+ PERFETTO_CHECK(false);
+ }
+}
+
+std::string HexToBinary(base::StringView hex) {
+ std::string res;
+ res.reserve((hex.size() + 1) / 2);
+ auto it = hex.begin();
+
+ if (hex.size() % 2 != 0) {
+ res.push_back(static_cast<char>(HexToBinary(*it)));
+ ++it;
+ }
+
+ while (it != hex.end()) {
+ int v = (HexToBinary(*it++) << 4);
+ v += HexToBinary(*it++);
+ res.push_back(static_cast<char>(v));
+ }
+ return res;
+}
+
+bool IsHex(base::StringView module) {
+ for (char c : module) {
+ if (!std::isxdigit(c)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+// Returns whether this string is of a hex chrome module or not to decide
+// whether to convert the module to/from hex.
+// TODO(b/148109467): Remove workaround once all active Chrome versions
+// write raw bytes instead of a string as build_id.
+bool IsHexModuleId(base::StringView module) {
+ return module.size() == 33 && IsHex(module);
+}
+
+} // namespace
+
+// static
+BuildId BuildId::FromHex(base::StringView data) {
+ if (IsHexModuleId(data)) {
+ return BuildId(data.ToStdString());
+ }
+ return BuildId(HexToBinary(data));
+}
+
+std::string BuildId::ToHex() const {
+ if (IsHexModuleId(base::StringView(raw_))) {
+ return raw_;
+ }
+ return base::ToHex(raw_.data(), raw_.size());
+}
+
+} // namespace trace_processor
+} // namespace perfetto
diff --git a/src/trace_processor/util/build_id.h b/src/trace_processor/util/build_id.h
new file mode 100644
index 0000000..64ec9ac
--- /dev/null
+++ b/src/trace_processor/util/build_id.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2019 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_BUILD_ID_H_
+#define SRC_TRACE_PROCESSOR_UTIL_BUILD_ID_H_
+
+#include <string>
+#include <utility>
+
+#include "perfetto/ext/base/hash.h"
+#include "perfetto/ext/base/string_view.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+// Represents the unique identifier of an executable, shared library, or module.
+// For example for ELF files this is the id stored in the .note.gnu.build-id
+// section. Sometimes a breakpad module id is used.
+// This class abstracts away the details of where this id comes from and how it
+// is converted to a StringId which is the representation used by tables in
+// trace_processor.
+class BuildId {
+ public:
+ // Allow hashing with base::Hash.
+ static constexpr bool kHashable = true;
+ size_t size() const { return raw_.size(); }
+ const char* data() const { return raw_.data(); }
+
+ static BuildId FromHex(base::StringView data);
+
+ static BuildId FromRaw(base::StringView data) {
+ return BuildId(data.ToStdString());
+ }
+ static BuildId FromRaw(std::string data) { return BuildId(std::move(data)); }
+ static BuildId FromRaw(const uint8_t* data, size_t size) {
+ return BuildId(std::string(reinterpret_cast<const char*>(data), size));
+ }
+
+ BuildId(const BuildId&) = default;
+ BuildId(BuildId&&) = default;
+
+ BuildId& operator=(const BuildId&) = default;
+ BuildId& operator=(BuildId&&) = default;
+
+ bool operator==(const BuildId& o) const { return raw_ == o.raw_; }
+
+ bool operator!=(const BuildId& o) const { return !(*this == o); }
+
+ bool operator<(const BuildId& o) const { return raw_ < o.raw_; }
+
+ std::string ToHex() const;
+
+ const std::string& raw() const { return raw_; }
+
+ private:
+ explicit BuildId(std::string data) : raw_(std::move(data)) {}
+ std::string raw_;
+};
+
+} // namespace trace_processor
+} // namespace perfetto
+
+template <>
+struct std::hash<perfetto::trace_processor::BuildId> {
+ std::size_t operator()(
+ const perfetto::trace_processor::BuildId& o) const noexcept {
+ return perfetto::base::Hasher::Combine(o);
+ }
+};
+
+#endif // SRC_TRACE_PROCESSOR_UTIL_BUILD_ID_H_
diff --git a/src/trace_processor/util/stack_traces_util.h b/src/trace_processor/util/stack_traces_util.h
deleted file mode 100644
index 1171786..0000000
--- a/src/trace_processor/util/stack_traces_util.h
+++ /dev/null
@@ -1,36 +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_UTIL_STACK_TRACES_UTIL_H_
-#define SRC_TRACE_PROCESSOR_UTIL_STACK_TRACES_UTIL_H_
-
-#include "perfetto/ext/base/string_view.h"
-
-namespace perfetto {
-namespace trace_processor {
-namespace util {
-
-// Returns whether this string is of a hex chrome module or not to decide
-// whether to convert the module to/from hex.
-// TODO(b/148109467): Remove workaround once all active Chrome versions
-// write raw bytes instead of a string as build_id.
-bool IsHexModuleId(base::StringView module);
-
-} // namespace util
-} // namespace trace_processor
-} // namespace perfetto
-
-#endif // SRC_TRACE_PROCESSOR_UTIL_STACK_TRACES_UTIL_H_
diff --git a/src/trace_redaction/BUILD.gn b/src/trace_redaction/BUILD.gn
index d052722..cff9588 100644
--- a/src/trace_redaction/BUILD.gn
+++ b/src/trace_redaction/BUILD.gn
@@ -18,6 +18,8 @@
sources = [
"find_package_uid.cc",
"find_package_uid.h",
+ "populate_allow_lists.cc",
+ "populate_allow_lists.h",
"prune_package_list.cc",
"prune_package_list.h",
"scrub_trace_packet.cc",
@@ -67,6 +69,7 @@
"../../gn:default_deps",
"../../gn:gtest_and_gmock",
"../../protos/perfetto/trace:non_minimal_cpp",
+ "../../protos/perfetto/trace:zero",
"../../protos/perfetto/trace/android:cpp",
"../../protos/perfetto/trace/ps:cpp",
"../../protos/perfetto/trace/ps:zero",
diff --git a/src/trace_redaction/populate_allow_lists.cc b/src/trace_redaction/populate_allow_lists.cc
new file mode 100644
index 0000000..b14b1f6
--- /dev/null
+++ b/src/trace_redaction/populate_allow_lists.cc
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/trace_redaction/populate_allow_lists.h"
+
+#include "perfetto/base/status.h"
+#include "src/trace_redaction/trace_redaction_framework.h"
+
+#include "protos/perfetto/trace/trace_packet.pbzero.h"
+
+namespace perfetto::trace_redaction {
+
+base::Status PopulateAllowlists::Build(Context* context) const {
+ if (!context->trace_packet_allow_list.empty()) {
+ return base::ErrStatus("Trace packet allow-list should be empty.");
+ }
+
+ context->trace_packet_allow_list = {
+ protos::pbzero::TracePacket::kProcessTreeFieldNumber,
+ protos::pbzero::TracePacket::kProcessStatsFieldNumber,
+ protos::pbzero::TracePacket::kClockSnapshotFieldNumber,
+ protos::pbzero::TracePacket::kSysStatsFieldNumber,
+ protos::pbzero::TracePacket::kTraceConfigFieldNumber,
+ protos::pbzero::TracePacket::kTraceStatsFieldNumber,
+ protos::pbzero::TracePacket::kSystemInfoFieldNumber,
+ protos::pbzero::TracePacket::kTriggerFieldNumber,
+ protos::pbzero::TracePacket::kCpuInfoFieldNumber,
+ protos::pbzero::TracePacket::kServiceEventFieldNumber,
+ protos::pbzero::TracePacket::kInitialDisplayStateFieldNumber,
+ protos::pbzero::TracePacket::kFrameTimelineEventFieldNumber,
+ protos::pbzero::TracePacket::kAndroidSystemPropertyFieldNumber,
+ protos::pbzero::TracePacket::kSynchronizationMarkerFieldNumber,
+ protos::pbzero::TracePacket::kFtraceEventsFieldNumber,
+ };
+
+ return base::OkStatus();
+}
+
+} // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/populate_allow_lists.h b/src/trace_redaction/populate_allow_lists.h
new file mode 100644
index 0000000..5a05d42
--- /dev/null
+++ b/src/trace_redaction/populate_allow_lists.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_TRACE_REDACTION_POPULATE_ALLOW_LISTS_H_
+#define SRC_TRACE_REDACTION_POPULATE_ALLOW_LISTS_H_
+
+#include "perfetto/base/status.h"
+#include "src/trace_redaction/trace_redaction_framework.h"
+
+namespace perfetto::trace_redaction {
+
+// Populates the different allow-lists needed to remove data from the trace.
+// Configuration data in the context can be used to change the contents of the
+// lists.
+class PopulateAllowlists final : public BuildPrimitive {
+ public:
+ base::Status Build(Context* context) const override;
+};
+
+} // namespace perfetto::trace_redaction
+
+#endif // SRC_TRACE_REDACTION_POPULATE_ALLOW_LISTS_H_
diff --git a/src/trace_redaction/scrub_trace_packet.cc b/src/trace_redaction/scrub_trace_packet.cc
index a847b68..e4f03cf 100644
--- a/src/trace_redaction/scrub_trace_packet.cc
+++ b/src/trace_redaction/scrub_trace_packet.cc
@@ -34,15 +34,25 @@
//
// All together, this implementation can be considered linear in relation to the
// trace size.
-base::Status ScrubTracePacket::Transform(const Context&,
+base::Status ScrubTracePacket::Transform(const Context& context,
std::string* packet) const {
+ if (packet == nullptr || packet->empty()) {
+ return base::ErrStatus("Cannot scrub null or empty trace packet.");
+ }
+
+ const auto& allow_list = context.trace_packet_allow_list;
+
+ if (allow_list.empty()) {
+ return base::ErrStatus("Cannot scrub trace packets, missing allow-list.");
+ }
+
protozero::ProtoDecoder d(*packet);
// A packet should only have one data type (proto oneof), but there are other
// values in the packet (e.g. timestamp). If one field is in the allowlist,
// then allow the whole trace packet.
for (auto f = d.ReadField(); f.valid(); f = d.ReadField()) {
- if (allow_list_.count(f.id()) != 0) {
+ if (allow_list.count(f.id()) != 0) {
return base::OkStatus();
}
}
diff --git a/src/trace_redaction/scrub_trace_packet.h b/src/trace_redaction/scrub_trace_packet.h
index 0b95f7f..06710d3 100644
--- a/src/trace_redaction/scrub_trace_packet.h
+++ b/src/trace_redaction/scrub_trace_packet.h
@@ -17,7 +17,6 @@
#ifndef SRC_TRACE_REDACTION_SCRUB_TRACE_PACKET_H_
#define SRC_TRACE_REDACTION_SCRUB_TRACE_PACKET_H_
-#include "include/perfetto/base/flat_set.h"
#include "src/trace_redaction/trace_redaction_framework.h"
#include "protos/perfetto/trace/trace_packet.pbzero.h"
@@ -30,27 +29,6 @@
public:
base::Status Transform(const Context& context,
std::string* packet) const override;
-
- private:
- // TODO(vaage): Most the allow-list into the context and populate it with a
- // build primitive. This will allow for a configurable list.
- base::FlatSet<uint32_t> allow_list_ = {
- protos::pbzero::TracePacket::kProcessTreeFieldNumber,
- protos::pbzero::TracePacket::kProcessStatsFieldNumber,
- protos::pbzero::TracePacket::kClockSnapshotFieldNumber,
- protos::pbzero::TracePacket::kSysStatsFieldNumber,
- protos::pbzero::TracePacket::kTraceConfigFieldNumber,
- protos::pbzero::TracePacket::kTraceStatsFieldNumber,
- protos::pbzero::TracePacket::kSystemInfoFieldNumber,
- protos::pbzero::TracePacket::kTriggerFieldNumber,
- protos::pbzero::TracePacket::kCpuInfoFieldNumber,
- protos::pbzero::TracePacket::kServiceEventFieldNumber,
- protos::pbzero::TracePacket::kInitialDisplayStateFieldNumber,
- protos::pbzero::TracePacket::kFrameTimelineEventFieldNumber,
- protos::pbzero::TracePacket::kAndroidSystemPropertyFieldNumber,
- protos::pbzero::TracePacket::kSynchronizationMarkerFieldNumber,
- protos::pbzero::TracePacket::kFtraceEventsFieldNumber,
- };
};
} // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/scrub_trace_packet_unittest.cc b/src/trace_redaction/scrub_trace_packet_unittest.cc
index 3c73b4d..82b99d4 100644
--- a/src/trace_redaction/scrub_trace_packet_unittest.cc
+++ b/src/trace_redaction/scrub_trace_packet_unittest.cc
@@ -14,17 +14,50 @@
* limitations under the License.
*/
-#include "src/trace_redaction/scrub_trace_packet.h"
-
#include <string>
#include "src/base/test/status_matchers.h"
+#include "src/trace_redaction/scrub_trace_packet.h"
#include "test/gtest_and_gmock.h"
#include "protos/perfetto/trace/ps/process_tree.gen.h"
+#include "protos/perfetto/trace/trace_packet.pbzero.h"
namespace perfetto::trace_redaction {
+TEST(ScrubTracePacketTest, ReturnErrorForNullPacket) {
+ // Have something in the allow-list to avoid that error.
+ Context context;
+ context.trace_packet_allow_list.insert(
+ protos::pbzero::TracePacket::kProcessTreeFieldNumber);
+
+ ScrubTracePacket scrub;
+ ASSERT_FALSE(scrub.Transform(context, nullptr).ok());
+}
+
+TEST(ScrubTracePacketTest, ReturnErrorForEmptyPacket) {
+ // Have something in the allow-list to avoid that error.
+ Context context;
+ context.trace_packet_allow_list.insert(
+ protos::pbzero::TracePacket::kProcessTreeFieldNumber);
+
+ std::string packet_str = "";
+
+ ScrubTracePacket scrub;
+ ASSERT_FALSE(scrub.Transform(context, &packet_str).ok());
+}
+
+TEST(ScrubTracePacketTest, ReturnErrorForEmptyAllowList) {
+ // The context will have no allow-list entries. ScrubTracePacket should fail.
+ Context context;
+
+ protos::gen::TracePacket packet;
+ std::string packet_str = packet.SerializeAsString();
+
+ ScrubTracePacket scrub;
+ ASSERT_FALSE(scrub.Transform(context, &packet_str).ok());
+}
+
// The whole packet should be dropped (cleared) when it has a data type not
// included in the allow-list.
TEST(ScrubTracePacketTest, DropsOutsiderPacketType) {
@@ -35,10 +68,14 @@
std::string packet_str = packet.SerializeAsString();
ASSERT_GT(packet_str.size(), 0u);
- Context ignore; // The context is not important for this primitive.
+ // Populate the allow-list with something that doesn't match the data in the
+ // packet.
+ Context context;
+ context.trace_packet_allow_list.insert(
+ protos::pbzero::TracePacket::kProcessTreeFieldNumber);
ScrubTracePacket scrub;
- ASSERT_OK(scrub.Transform(ignore, &packet_str));
+ ASSERT_OK(scrub.Transform(context, &packet_str));
ASSERT_TRUE(packet_str.empty());
}
@@ -53,10 +90,12 @@
std::string packet_str = packet.SerializeAsString();
ASSERT_GT(packet_str.size(), 0u);
- Context ignore; // The context is not important for this primitive.
+ Context context;
+ context.trace_packet_allow_list.insert(
+ protos::pbzero::TracePacket::kProcessTreeFieldNumber);
ScrubTracePacket scrub;
- ASSERT_OK(scrub.Transform(ignore, &packet_str));
+ ASSERT_OK(scrub.Transform(context, &packet_str));
ASSERT_TRUE(packet_str.empty());
}
@@ -79,10 +118,12 @@
// be).
std::string mutable_packet_str(original_packet_str);
- Context ignore; // The context is not important for this primitive.
+ Context context;
+ context.trace_packet_allow_list.insert(
+ protos::pbzero::TracePacket::kProcessTreeFieldNumber);
ScrubTracePacket scrub;
- ASSERT_OK(scrub.Transform(ignore, &mutable_packet_str));
+ ASSERT_OK(scrub.Transform(context, &mutable_packet_str));
// The transform shouldn't have changed the string, so the string before and
// after should match.
diff --git a/src/trace_redaction/trace_redaction_framework.h b/src/trace_redaction/trace_redaction_framework.h
index c9e7fb4..b481961 100644
--- a/src/trace_redaction/trace_redaction_framework.h
+++ b/src/trace_redaction/trace_redaction_framework.h
@@ -17,10 +17,14 @@
#ifndef SRC_TRACE_REDACTION_TRACE_REDACTION_FRAMEWORK_H_
#define SRC_TRACE_REDACTION_TRACE_REDACTION_FRAMEWORK_H_
+#include <cstdint>
+#include <optional>
#include <string>
#include <vector>
+#include "perfetto/base/flat_set.h"
#include "perfetto/ext/base/status_or.h"
+
#include "protos/perfetto/trace/trace_packet.gen.h"
#include "protos/perfetto/trace/trace_packet.pbzero.h"
@@ -89,6 +93,31 @@
// uid: 1010113
// }
std::optional<uint64_t> package_uid;
+
+ // Trace packets contain a "one of" entry called "data". This field can be
+ // thought of as the message. A track packet with have other fields along
+ // side "data" (e.g. "timestamp"). These fields can be thought of as metadata.
+ //
+ // A message should be removed if:
+ //
+ // ...we know it contains too much sensitive information
+ //
+ // ...we know it contains sensitive information and we know how to remove
+ // the sensitive information, but don't have the resources to do it
+ // right now
+ //
+ // ...we know it provide little value
+ //
+ // "trace_packet_allow_list" contains the field ids of trace packets we want
+ // to pass onto later transformations. Examples are:
+ //
+ // - protos::pbzero::TracePacket::kProcessTreeFieldNumber
+ // - protos::pbzero::TracePacket::kProcessStatsFieldNumber
+ // - protos::pbzero::TracePacket::kClockSnapshotFieldNumber
+ //
+ // Because "data" is a "one of", if no field in "trace_packet_allow_list" can
+ // be found, it packet should be removed.
+ base::FlatSet<uint32_t> trace_packet_allow_list;
};
// Responsible for extracting low-level data from the trace and storing it in
diff --git a/src/trace_redaction/trace_redactor_integrationtest.cc b/src/trace_redaction/trace_redactor_integrationtest.cc
index c30f2f8..8e10095 100644
--- a/src/trace_redaction/trace_redactor_integrationtest.cc
+++ b/src/trace_redaction/trace_redactor_integrationtest.cc
@@ -34,14 +34,12 @@
using TracePacket = protos::pbzero::TracePacket;
constexpr std::string_view kTracePath =
- "test/data/trace_redaction_jank_high_cpu.pftrace";
+ "test/data/trace-redaction-general.pftrace";
-// "com.google.android.settings.intelligence" will have one package, but two
-// processes will reference it. When doing so, they will use two different
-// uids (multiples of 1,000,000).
constexpr std::string_view kPackageName =
- "com.google.android.settings.intelligence";
-constexpr uint64_t kPackageUid = 10118;
+ "com.Unity.com.unity.multiplayer.samples.coop";
+
+constexpr uint64_t kPackageUid = 10252;
class TraceRedactorIntegrationTest : public testing::Test {
public:
@@ -58,6 +56,24 @@
const std::string& dest_trace() const { return dest_trace_->path(); }
+ std::vector<protozero::ConstBytes> GetPackageInfos(
+ const Trace::Decoder& trace) const {
+ std::vector<protozero::ConstBytes> infos;
+
+ for (auto packet_it = trace.packet(); packet_it; ++packet_it) {
+ TracePacket::Decoder packet_decoder(*packet_it);
+ if (packet_decoder.has_packages_list()) {
+ PackagesList::Decoder list_it(packet_decoder.packages_list());
+ for (auto info_it = list_it.packages(); info_it; ++info_it) {
+ PackageInfo::Decoder info(*info_it);
+ infos.push_back(*info_it);
+ }
+ }
+ }
+
+ return infos;
+ }
+
private:
std::string src_trace_;
std::unique_ptr<base::TempFile> dest_trace_;
@@ -78,33 +94,27 @@
std::string redacted_buffer;
ASSERT_TRUE(base::ReadFile(dest_trace(), &redacted_buffer));
- // Collect package info from the trace.
- std::vector<protozero::ConstBytes> infos;
+ Trace::Decoder redacted_trace(redacted_buffer);
+ std::vector<protozero::ConstBytes> infos = GetPackageInfos(redacted_trace);
- Trace::Decoder trace_decoder(redacted_buffer);
+ // It is possible for two packages_list to appear in the trace. The
+ // find_package_uid will stop after the first one is found. Package uids are
+ // appear as n * 1,000,000 where n is some integer. It is also possible for
+ // two packages_list to contain copies of each other - for example
+ // "com.Unity.com.unity.multiplayer.samples.coop" appears in both
+ // packages_list.
+ ASSERT_GE(infos.size(), 1u);
- for (auto packet_it = trace_decoder.packet(); packet_it; ++packet_it) {
- TracePacket::Decoder packet_decoder(*packet_it);
+ for (const auto& info_buffer : infos) {
+ PackageInfo::Decoder info(info_buffer);
- if (packet_decoder.has_packages_list()) {
- PackagesList::Decoder list_it(packet_decoder.packages_list());
+ ASSERT_TRUE(info.has_name());
+ ASSERT_EQ(info.name().ToStdString(), kPackageName);
- for (auto info_it = list_it.packages(); info_it; ++info_it) {
- infos.push_back(*info_it);
- }
- }
+ ASSERT_TRUE(info.has_uid());
+ ASSERT_EQ(NormalizeUid(info.uid()), NormalizeUid(kPackageUid));
}
- ASSERT_EQ(infos.size(), 1u);
-
- PackageInfo::Decoder info(infos[0]);
-
- ASSERT_TRUE(info.has_name());
- ASSERT_EQ(info.name().ToStdString(), kPackageName);
-
- ASSERT_TRUE(info.has_uid());
- ASSERT_EQ(NormalizeUid(info.uid()), NormalizeUid(kPackageUid));
-
ASSERT_TRUE(context.package_uid.has_value());
ASSERT_EQ(NormalizeUid(context.package_uid.value()),
NormalizeUid(kPackageUid));
diff --git a/src/traceconv/trace_to_text.cc b/src/traceconv/trace_to_text.cc
index 1deac65..ef7e03a 100644
--- a/src/traceconv/trace_to_text.cc
+++ b/src/traceconv/trace_to_text.cc
@@ -19,7 +19,7 @@
#include "perfetto/base/logging.h"
#include "perfetto/ext/base/file_utils.h"
#include "perfetto/ext/base/scoped_file.h"
-#include "src/protozero/proto_ring_buffer.h"
+#include "perfetto/ext/protozero/proto_ring_buffer.h"
#include "src/traceconv/trace.descriptor.h"
#include "src/traceconv/utils.h"
diff --git a/src/traced/probes/ftrace/cpu_reader.cc b/src/traced/probes/ftrace/cpu_reader.cc
index 2f1f121..c161203 100644
--- a/src/traced/probes/ftrace/cpu_reader.cc
+++ b/src/traced/probes/ftrace/cpu_reader.cc
@@ -23,11 +23,8 @@
#include <optional>
#include <utility>
-#include "perfetto/base/build_config.h"
#include "perfetto/base/logging.h"
#include "perfetto/ext/base/metatrace.h"
-#include "perfetto/ext/base/string_splitter.h"
-#include "perfetto/ext/base/string_utils.h"
#include "perfetto/ext/base/utils.h"
#include "perfetto/ext/tracing/core/trace_writer.h"
#include "src/kallsyms/kernel_symbol_map.h"
diff --git a/src/traced/probes/ftrace/cpu_reader.h b/src/traced/probes/ftrace/cpu_reader.h
index 08c0d95..9282901 100644
--- a/src/traced/probes/ftrace/cpu_reader.h
+++ b/src/traced/probes/ftrace/cpu_reader.h
@@ -87,9 +87,6 @@
// of this many pages. In other words, we'll read up to
// |kFtraceDataBufSizePages| into memory, parse them, and then repeat if we
// still haven't caught up to the writer.
- // TODO(rsavitski): consider making buffering & parsing page counts
- // independent, should be a single counter in the cpu_reader, similar to
- // lost_events case.
static constexpr size_t kFtraceDataBufSizePages = 32;
uint8_t* ftrace_data_buf() const {
@@ -181,6 +178,12 @@
const FtraceClockSnapshot* ftrace_clock_snapshot);
~CpuReader();
+ // move-only
+ CpuReader(const CpuReader&) = delete;
+ CpuReader& operator=(const CpuReader&) = delete;
+ CpuReader(CpuReader&&) = default;
+ CpuReader& operator=(CpuReader&&) = default;
+
// Reads and parses all ftrace data for this cpu (in batches), until we catch
// up to the writer, or hit |max_pages|. Returns number of pages read.
size_t ReadCycle(ParsingBuffers* parsing_bufs,
@@ -375,10 +378,10 @@
const FtraceClockSnapshot* ftrace_clock_snapshot,
protos::pbzero::FtraceClock ftrace_clock);
- private:
- CpuReader(const CpuReader&) = delete;
- CpuReader& operator=(const CpuReader&) = delete;
+ // For FtraceController, which manages poll callbacks on per-cpu buffer fds.
+ int RawBufferFd() const { return trace_fd_.get(); }
+ private:
// Reads at most |max_pages| of ftrace data, parses it, and writes it
// into |started_data_sources|. Returns number of pages read.
// See comment on ftrace_controller.cc:kMaxParsingWorkingSetPages for
@@ -390,12 +393,12 @@
CompactSchedBuffer* compact_sched_buf,
const std::set<FtraceDataSource*>& started_data_sources);
- const size_t cpu_;
- const ProtoTranslationTable* const table_;
- LazyKernelSymbolizer* const symbolizer_;
+ size_t cpu_;
+ const ProtoTranslationTable* table_;
+ LazyKernelSymbolizer* symbolizer_;
base::ScopedFile trace_fd_;
protos::pbzero::FtraceClock ftrace_clock_{};
- const FtraceClockSnapshot* const ftrace_clock_snapshot_;
+ const FtraceClockSnapshot* ftrace_clock_snapshot_;
};
} // namespace perfetto
diff --git a/src/traced/probes/ftrace/cpu_reader_unittest.cc b/src/traced/probes/ftrace/cpu_reader_unittest.cc
index 39461da..2d4671c 100644
--- a/src/traced/probes/ftrace/cpu_reader_unittest.cc
+++ b/src/traced/probes/ftrace/cpu_reader_unittest.cc
@@ -81,7 +81,7 @@
{},
{},
false /*symbolize_ksyms*/,
- false /*preserve_ftrace_buffer*/,
+ 50u,
{}};
}
diff --git a/src/traced/probes/ftrace/ftrace_config_muxer.cc b/src/traced/probes/ftrace/ftrace_config_muxer.cc
index e91cdcb..6a55f8c 100644
--- a/src/traced/probes/ftrace/ftrace_config_muxer.cc
+++ b/src/traced/probes/ftrace/ftrace_config_muxer.cc
@@ -16,18 +16,20 @@
#include "src/traced/probes/ftrace/ftrace_config_muxer.h"
-#include <stdint.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
+#include <cstdint>
#include <algorithm>
#include <iterator>
+#include <limits>
#include "perfetto/base/compiler.h"
#include "perfetto/ext/base/utils.h"
#include "src/traced/probes/ftrace/atrace_wrapper.h"
#include "src/traced/probes/ftrace/compact_sched.h"
+#include "src/traced/probes/ftrace/ftrace_config_utils.h"
#include "src/traced/probes/ftrace/ftrace_stats.h"
#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h"
@@ -35,8 +37,14 @@
namespace perfetto {
namespace {
-constexpr int kDefaultPerCpuBufferSizeKb = 2 * 1024; // 2mb
-constexpr int kMaxPerCpuBufferSizeKb = 64 * 1024; // 64mb
+constexpr uint64_t kDefaultLowRamPerCpuBufferSizeKb = 2 * (1ULL << 10); // 2mb
+constexpr uint64_t kDefaultHighRamPerCpuBufferSizeKb = 8 * (1ULL << 10); // 8mb
+constexpr uint64_t kMaxPerCpuBufferSizeKb = 64 * (1ULL << 10); // 64mb
+
+// Threshold for physical ram size used when deciding on default kernel buffer
+// sizes. We want to detect 8 GB, but the size reported through sysconf is
+// usually lower.
+constexpr uint64_t kHighMemBytes = 7 * (1ULL << 30); // 7gb
// A fake "syscall id" that indicates all syscalls should be recorded. This
// allows us to distinguish between the case where `syscall_events` is empty
@@ -560,27 +568,6 @@
return true;
}
-// Post-conditions:
-// 1. result >= 1 (should have at least one page per CPU)
-// 2. result * 4 < kMaxTotalBufferSizeKb
-// 3. If input is 0 output is a good default number.
-size_t ComputeCpuBufferSizeInPages(size_t requested_buffer_size_kb) {
- if (requested_buffer_size_kb == 0)
- requested_buffer_size_kb = kDefaultPerCpuBufferSizeKb;
- if (requested_buffer_size_kb > kMaxPerCpuBufferSizeKb) {
- PERFETTO_ELOG(
- "The requested ftrace buf size (%zu KB) is too big, capping to %d KB",
- requested_buffer_size_kb, kMaxPerCpuBufferSizeKb);
- requested_buffer_size_kb = kMaxPerCpuBufferSizeKb;
- }
-
- size_t pages = requested_buffer_size_kb / (base::GetSysPageSize() / 1024);
- if (pages == 0)
- return 1;
-
- return pages;
-}
-
FtraceConfigMuxer::FtraceConfigMuxer(
FtraceProcfs* ftrace,
ProtoTranslationTable* table,
@@ -592,7 +579,7 @@
syscalls_(std::move(syscalls)),
current_state_(),
ds_configs_(),
- vendor_events_(vendor_events),
+ vendor_events_(std::move(vendor_events)),
secondary_instance_(secondary_instance) {}
FtraceConfigMuxer::~FtraceConfigMuxer() = default;
@@ -745,7 +732,7 @@
request, filter.IsEventEnabled(compact_format.sched_switch.event_id),
compact_format);
if (errors && !compact_format.format_valid) {
- errors->failed_ftrace_events.push_back(
+ errors->failed_ftrace_events.emplace_back(
"perfetto/compact_sched (unexpected sched event format)");
}
@@ -755,7 +742,7 @@
FtracePrintFilterConfig::Create(request.print_filter(), table_);
if (!ftrace_print_filter.has_value()) {
if (errors) {
- errors->failed_ftrace_events.push_back(
+ errors->failed_ftrace_events.emplace_back(
"ftrace/print (unexpected format for filtering)");
}
}
@@ -765,12 +752,11 @@
std::vector<std::string> categories(request.atrace_categories());
ds_configs_.emplace(
std::piecewise_construct, std::forward_as_tuple(id),
- std::forward_as_tuple(std::move(filter), std::move(syscall_filter),
- compact_sched, std::move(ftrace_print_filter),
- std::move(apps), std::move(categories),
- request.symbolize_ksyms(),
- request.preserve_ftrace_buffer(),
- GetSyscallsReturningFds(syscalls_)));
+ std::forward_as_tuple(
+ std::move(filter), std::move(syscall_filter), compact_sched,
+ std::move(ftrace_print_filter), std::move(apps),
+ std::move(categories), request.symbolize_ksyms(),
+ request.drain_buffer_percent(), GetSyscallsReturningFds(syscalls_)));
return true;
}
@@ -780,16 +766,25 @@
return false;
}
- // Enable tracing_on to activate ftrace ring buffer before activate the first
- // config.
- if (active_configs_.empty()) {
+ bool first_config = active_configs_.empty();
+ active_configs_.insert(id);
+
+ // Pick the lowest buffer_percent across the new set of active configs.
+ if (!UpdateBufferPercent()) {
+ PERFETTO_ELOG(
+ "Invalid FtraceConfig.drain_buffer_percent or "
+ "/sys/kernel/tracing/buffer_percent file permissions.");
+ // carry on, non-critical error
+ }
+
+ // Enable kernel event writer.
+ if (first_config) {
if (!ftrace_->SetTracingOn(true)) {
PERFETTO_ELOG("Failed to enable ftrace.");
+ active_configs_.erase(id);
return false;
}
}
-
- active_configs_.insert(id);
return true;
}
@@ -849,12 +844,16 @@
}
}
+ // Update buffer_percent to the minimum of the remaining configs.
+ UpdateBufferPercent();
+
// Even if we don't have any other active configs, we might still have idle
// configs around. Tear down the rest of the ftrace config only if all
// configs are removed.
if (ds_configs_.empty()) {
if (ftrace_->SetCpuBufferSizeInPages(1))
current_state_.cpu_buffer_size_pages = 1;
+ ftrace_->SetBufferPercent(50);
ftrace_->DisableAllEvents();
ftrace_->ClearTrace();
ftrace_->SetTracingOn(current_state_.saved_tracing_on);
@@ -945,15 +944,67 @@
}
void FtraceConfigMuxer::SetupBufferSize(const FtraceConfig& request) {
- size_t pages = ComputeCpuBufferSizeInPages(request.buffer_size_kb());
+ int64_t phys_ram_pages = sysconf(_SC_PHYS_PAGES);
+ size_t pages = ComputeCpuBufferSizeInPages(request.buffer_size_kb(),
+ request.buffer_size_lower_bound(),
+ phys_ram_pages);
ftrace_->SetCpuBufferSizeInPages(pages);
current_state_.cpu_buffer_size_pages = pages;
}
+// Post-conditions:
+// 1. result >= 1 (should have at least one page per CPU)
+// 2. result < kMaxTotalBufferSizeKb / (page_size / 1024)
+// 3. If input is 0 output is a good default number
+size_t ComputeCpuBufferSizeInPages(size_t requested_buffer_size_kb,
+ bool buffer_size_lower_bound,
+ int64_t sysconf_phys_pages) {
+ uint32_t page_sz = base::GetSysPageSize();
+ uint64_t default_size_kb =
+ (sysconf_phys_pages > 0 &&
+ (static_cast<uint64_t>(sysconf_phys_pages) >= (kHighMemBytes / page_sz)))
+ ? kDefaultHighRamPerCpuBufferSizeKb
+ : kDefaultLowRamPerCpuBufferSizeKb;
+
+ size_t actual_size_kb = requested_buffer_size_kb;
+ if ((requested_buffer_size_kb == 0) ||
+ (buffer_size_lower_bound && default_size_kb > requested_buffer_size_kb)) {
+ actual_size_kb = default_size_kb;
+ }
+
+ if (actual_size_kb > kMaxPerCpuBufferSizeKb) {
+ PERFETTO_ELOG(
+ "The requested ftrace buf size (%zu KB) is too big, capping to %" PRIu64
+ " KB",
+ actual_size_kb, kMaxPerCpuBufferSizeKb);
+ actual_size_kb = kMaxPerCpuBufferSizeKb;
+ }
+
+ size_t pages = actual_size_kb / (page_sz / 1024);
+ return pages ? pages : 1;
+}
+
size_t FtraceConfigMuxer::GetPerCpuBufferSizePages() {
return current_state_.cpu_buffer_size_pages;
}
+// If new_cfg_id is set, consider it in addition to already active configs
+// as we're trying to activate it.
+bool FtraceConfigMuxer::UpdateBufferPercent() {
+ uint32_t kUnsetPercent = std::numeric_limits<uint32_t>::max();
+ uint32_t min_percent = kUnsetPercent;
+ for (auto cfg_id : active_configs_) {
+ auto ds_it = ds_configs_.find(cfg_id);
+ if (ds_it != ds_configs_.end() && ds_it->second.buffer_percent > 0) {
+ min_percent = std::min(min_percent, ds_it->second.buffer_percent);
+ }
+ }
+ if (min_percent == kUnsetPercent)
+ return true;
+ // Let the kernel ignore values >100.
+ return ftrace_->SetBufferPercent(min_percent);
+}
+
void FtraceConfigMuxer::UpdateAtrace(const FtraceConfig& request,
std::string* atrace_errors) {
// We want to avoid poisoning current_state_.atrace_{categories, apps}
diff --git a/src/traced/probes/ftrace/ftrace_config_muxer.h b/src/traced/probes/ftrace/ftrace_config_muxer.h
index 67176b2..e11efb0 100644
--- a/src/traced/probes/ftrace/ftrace_config_muxer.h
+++ b/src/traced/probes/ftrace/ftrace_config_muxer.h
@@ -41,24 +41,24 @@
// State held by the muxer per data source, used to parse ftrace according to
// that data source's config.
struct FtraceDataSourceConfig {
- FtraceDataSourceConfig(EventFilter _event_filter,
- EventFilter _syscall_filter,
- CompactSchedConfig _compact_sched,
- std::optional<FtracePrintFilterConfig> _print_filter,
- std::vector<std::string> _atrace_apps,
- std::vector<std::string> _atrace_categories,
- bool _symbolize_ksyms,
- bool _preserve_ftrace_buffer,
- base::FlatSet<int64_t> _syscalls_returning_fd)
- : event_filter(std::move(_event_filter)),
- syscall_filter(std::move(_syscall_filter)),
- compact_sched(_compact_sched),
- print_filter(std::move(_print_filter)),
- atrace_apps(std::move(_atrace_apps)),
- atrace_categories(std::move(_atrace_categories)),
- symbolize_ksyms(_symbolize_ksyms),
- preserve_ftrace_buffer(_preserve_ftrace_buffer),
- syscalls_returning_fd(std::move(_syscalls_returning_fd)) {}
+ FtraceDataSourceConfig(EventFilter event_filter_in,
+ EventFilter syscall_filter_in,
+ CompactSchedConfig compact_sched_in,
+ std::optional<FtracePrintFilterConfig> print_filter_in,
+ std::vector<std::string> atrace_apps_in,
+ std::vector<std::string> atrace_categories_in,
+ bool symbolize_ksyms_in,
+ uint32_t buffer_percent_in,
+ base::FlatSet<int64_t> syscalls_returning_fd_in)
+ : event_filter(std::move(event_filter_in)),
+ syscall_filter(std::move(syscall_filter_in)),
+ compact_sched(compact_sched_in),
+ print_filter(std::move(print_filter_in)),
+ atrace_apps(std::move(atrace_apps_in)),
+ atrace_categories(std::move(atrace_categories_in)),
+ symbolize_ksyms(symbolize_ksyms_in),
+ buffer_percent(buffer_percent_in),
+ syscalls_returning_fd(std::move(syscalls_returning_fd_in)) {}
// The event filter allows to quickly check if a certain ftrace event with id
// x is enabled for this data source.
EventFilter event_filter;
@@ -81,8 +81,8 @@
// When enabled will turn on the kallsyms symbolizer in CpuReader.
const bool symbolize_ksyms;
- // Does not clear previous traces.
- const bool preserve_ftrace_buffer;
+ // FtraceConfig.drain_buffer_percent for poll-based reads. Zero if unset.
+ const uint32_t buffer_percent;
// List of syscalls monitored to return a new filedescriptor upon success
base::FlatSet<int64_t> syscalls_returning_fd;
@@ -196,6 +196,7 @@
void SetupClock(const FtraceConfig& request);
void SetupBufferSize(const FtraceConfig& request);
+ bool UpdateBufferPercent();
void UpdateAtrace(const FtraceConfig& request, std::string* atrace_errors);
void DisableAtrace();
@@ -249,7 +250,9 @@
bool secondary_instance_;
};
-size_t ComputeCpuBufferSizeInPages(size_t requested_buffer_size_kb);
+size_t ComputeCpuBufferSizeInPages(size_t requested_buffer_size_kb,
+ bool buffer_size_lower_bound,
+ int64_t sysconf_phys_pages);
} // namespace perfetto
diff --git a/src/traced/probes/ftrace/ftrace_config_muxer_unittest.cc b/src/traced/probes/ftrace/ftrace_config_muxer_unittest.cc
index 92acbf3..d5d7f05 100644
--- a/src/traced/probes/ftrace/ftrace_config_muxer_unittest.cc
+++ b/src/traced/probes/ftrace/ftrace_config_muxer_unittest.cc
@@ -235,22 +235,48 @@
};
TEST_F(FtraceConfigMuxerTest, ComputeCpuBufferSizeInPages) {
- auto Pages = [](uint32_t kb) { return kb * 1024 / base::GetSysPageSize(); };
- const size_t kMaxBufSizeInPages = Pages(64 * 1024);
+ constexpr auto test = ComputeCpuBufferSizeInPages;
+ auto KbToPages = [](uint64_t kb) {
+ return kb * 1024 / base::GetSysPageSize();
+ };
+ auto kMaxBufSizePages = KbToPages(64 * 1024);
+ int64_t kNoRamInfo = 0;
+ bool kExactSize = false;
+ bool kLowerBoundSize = true;
+ int64_t kLowRamPages =
+ static_cast<int64_t>(KbToPages(3 * (1ULL << 20) + 512 * (1ULL << 10)));
+ int64_t kHighRamPages =
+ static_cast<int64_t>(KbToPages(7 * (1ULL << 20) + 512 * (1ULL << 10)));
- // No buffer size given: good default (2mb).
- EXPECT_EQ(ComputeCpuBufferSizeInPages(0), Pages(2048));
+ // No buffer size given: good default.
+ EXPECT_EQ(test(0, kExactSize, kNoRamInfo), KbToPages(2048));
+ // Default depends on device ram size.
+ EXPECT_EQ(test(0, kExactSize, kLowRamPages), KbToPages(2048));
+ EXPECT_EQ(test(0, kExactSize, kHighRamPages), KbToPages(8192));
+
+ // buffer_size_lower_bound lets us choose a higher default than given.
+ // default > requested:
+ EXPECT_EQ(test(4096, kExactSize, kHighRamPages), KbToPages(4096));
+ EXPECT_EQ(test(4096, kLowerBoundSize, kHighRamPages), KbToPages(8192));
+ // requested > default:
+ EXPECT_EQ(test(4096, kExactSize, kLowRamPages), KbToPages(4096));
+ EXPECT_EQ(test(4096, kLowerBoundSize, kLowRamPages), KbToPages(4096));
+ // requested > default:
+ EXPECT_EQ(test(16384, kExactSize, kHighRamPages), KbToPages(16384));
+ EXPECT_EQ(test(16384, kLowerBoundSize, kHighRamPages), KbToPages(16384));
// Buffer size given way too big: good default.
- EXPECT_EQ(ComputeCpuBufferSizeInPages(10 * 1024 * 1024), kMaxBufSizeInPages);
- EXPECT_EQ(ComputeCpuBufferSizeInPages(512 * 1024), kMaxBufSizeInPages);
+ EXPECT_EQ(test(10 * (1ULL << 20), kExactSize, kNoRamInfo), kMaxBufSizePages);
+ EXPECT_EQ(test(512 * 1024, kExactSize, kNoRamInfo), kMaxBufSizePages);
// Your size ends up with less than 1 page per cpu -> 1 page.
- EXPECT_EQ(ComputeCpuBufferSizeInPages(3), 1u);
-
+ EXPECT_EQ(test(3, kExactSize, kNoRamInfo), 1u);
// You picked a good size -> your size rounded to nearest page.
- EXPECT_EQ(ComputeCpuBufferSizeInPages(42),
- 42 * 1024 / base::GetSysPageSize());
+ EXPECT_EQ(test(42, kExactSize, kNoRamInfo), KbToPages(42));
+
+ // Sysconf returning an error is ok.
+ EXPECT_EQ(test(0, kExactSize, -1), KbToPages(2048));
+ EXPECT_EQ(test(4096, kExactSize, -1), KbToPages(4096));
}
TEST_F(FtraceConfigMuxerTest, GenericSyscallFiltering) {
@@ -620,6 +646,8 @@
ASSERT_TRUE(testing::Mock::VerifyAndClearExpectations(&ftrace));
EXPECT_CALL(ftrace, NumberOfCpus()).Times(AnyNumber());
+ EXPECT_CALL(ftrace, WriteToFile("/root/buffer_percent", _))
+ .WillRepeatedly(Return(true));
EXPECT_CALL(ftrace,
WriteToFile("/root/events/sched/sched_switch/enable", "0"));
@@ -1090,6 +1118,8 @@
FtraceConfig config =
CreateFtraceConfig({"sched/sched_switch", "cgroup/cgroup_mkdir"});
FtraceConfigMuxer model(&ftrace, table_.get(), GetSyscallTable(), {});
+ EXPECT_CALL(ftrace, WriteToFile("/root/buffer_percent", _))
+ .WillRepeatedly(Return(true));
EXPECT_CALL(ftrace, ReadFileIntoString("/root/current_tracer"))
.WillOnce(Return("nop"));
diff --git a/src/traced/probes/ftrace/ftrace_controller.cc b/src/traced/probes/ftrace/ftrace_controller.cc
index abf9381..ad924fa 100644
--- a/src/traced/probes/ftrace/ftrace_controller.cc
+++ b/src/traced/probes/ftrace/ftrace_controller.cc
@@ -17,20 +17,28 @@
#include "src/traced/probes/ftrace/ftrace_controller.h"
#include <fcntl.h>
-#include <stdint.h>
+#include <poll.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
+#include <sys/utsname.h>
#include <sys/wait.h>
#include <unistd.h>
+#include <cstdint>
+#include <limits>
+#include <memory>
+#include <optional>
#include <string>
+#include <tuple>
+#include <utility>
#include "perfetto/base/build_config.h"
#include "perfetto/base/logging.h"
#include "perfetto/base/time.h"
#include "perfetto/ext/base/file_utils.h"
#include "perfetto/ext/base/metatrace.h"
+#include "perfetto/ext/base/scoped_file.h"
#include "perfetto/ext/base/string_utils.h"
#include "perfetto/ext/tracing/core/trace_writer.h"
#include "src/kallsyms/kernel_symbol_map.h"
@@ -39,7 +47,9 @@
#include "src/traced/probes/ftrace/cpu_reader.h"
#include "src/traced/probes/ftrace/cpu_stats_parser.h"
#include "src/traced/probes/ftrace/event_info.h"
+#include "src/traced/probes/ftrace/event_info_constants.h"
#include "src/traced/probes/ftrace/ftrace_config_muxer.h"
+#include "src/traced/probes/ftrace/ftrace_config_utils.h"
#include "src/traced/probes/ftrace/ftrace_data_source.h"
#include "src/traced/probes/ftrace/ftrace_metadata.h"
#include "src/traced/probes/ftrace/ftrace_procfs.h"
@@ -50,28 +60,18 @@
namespace perfetto {
namespace {
-constexpr int kDefaultDrainPeriodMs = 100;
-constexpr int kMinDrainPeriodMs = 1;
-constexpr int kMaxDrainPeriodMs = 1000 * 60;
+constexpr uint32_t kDefaultTickPeriodMs = 100;
+constexpr uint32_t kPollBackingTickPeriodMs = 1000;
+constexpr uint32_t kMinTickPeriodMs = 1;
+constexpr uint32_t kMaxTickPeriodMs = 1000 * 60;
+constexpr int kPollRequiredMajorVersion = 6;
+constexpr int kPollRequiredMinorVersion = 1;
// Read at most this many pages of data per cpu per read task. If we hit this
// limit on at least one cpu, we stop and repost the read task, letting other
// tasks get some cpu time before continuing reading.
constexpr size_t kMaxPagesPerCpuPerReadTick = 256; // 1 MB per cpu
-uint32_t ClampDrainPeriodMs(uint32_t drain_period_ms) {
- if (drain_period_ms == 0) {
- return kDefaultDrainPeriodMs;
- }
- if (drain_period_ms < kMinDrainPeriodMs ||
- kMaxDrainPeriodMs < drain_period_ms) {
- PERFETTO_LOG("drain_period_ms was %u should be between %u and %u",
- drain_period_ms, kMinDrainPeriodMs, kMaxDrainPeriodMs);
- return kDefaultDrainPeriodMs;
- }
- return drain_period_ms;
-}
-
bool WriteToFile(const char* path, const char* str) {
auto fd = base::OpenFile(path, O_WRONLY);
if (!fd)
@@ -121,6 +121,26 @@
#endif
}
+struct AndroidGkiVersion {
+ uint64_t version = 0;
+ uint64_t patch_level = 0;
+ uint64_t sub_level = 0;
+ uint64_t release = 0;
+ uint64_t kmi_gen = 0;
+};
+
+#define ANDROID_GKI_UNAME_FMT \
+ "%" PRIu64 ".%" PRIu64 ".%" PRIu64 "-android%" PRIu64 "-%" PRIu64
+
+std::optional<AndroidGkiVersion> ParseAndroidGkiVersion(const char* s) {
+ AndroidGkiVersion v = {};
+ if (sscanf(s, ANDROID_GKI_UNAME_FMT, &v.version, &v.patch_level, &v.sub_level,
+ &v.release, &v.kmi_gen) != 5) {
+ return std::nullopt;
+ }
+ return v;
+}
+
} // namespace
// Method of last resort to reset ftrace state.
@@ -187,7 +207,7 @@
}
PERFETTO_DCHECK(data_sources_.empty());
PERFETTO_DCHECK(primary_.started_data_sources.empty());
- PERFETTO_DCHECK(primary_.per_cpu.empty());
+ PERFETTO_DCHECK(primary_.cpu_readers.empty());
PERFETTO_DCHECK(secondary_instances_.empty());
}
@@ -195,28 +215,41 @@
return static_cast<uint64_t>(base::GetWallTimeMs().count());
}
-void FtraceController::StartIfNeeded(FtraceInstanceState* instance) {
- using FtraceClock = protos::pbzero::FtraceClock;
- if (instance->started_data_sources.size() > 1)
+template <typename F>
+void FtraceController::ForEachInstance(F fn) {
+ fn(&primary_);
+ for (auto& kv : secondary_instances_) {
+ fn(kv.second.get());
+ }
+}
+
+void FtraceController::StartIfNeeded(FtraceInstanceState* instance,
+ const std::string& instance_name) {
+ if (buffer_watermark_support_ == PollSupport::kUntested) {
+ buffer_watermark_support_ = VerifyKernelSupportForBufferWatermark();
+ }
+
+ // If instance is already active, then at most we need to update the buffer
+ // poll callbacks. The periodic |ReadTick| will pick up any updates to the
+ // period the next time it executes.
+ if (instance->started_data_sources.size() > 1) {
+ UpdateBufferWatermarkWatches(instance, instance_name);
return;
+ }
// Lazily allocate the memory used for reading & parsing ftrace. In the case
// of multiple ftrace instances, this might already be valid.
parsing_mem_.AllocateIfNeeded();
- PERFETTO_DCHECK(instance->per_cpu.empty());
- size_t num_cpus = instance->ftrace_procfs->NumberOfCpus();
const auto ftrace_clock = instance->ftrace_config_muxer->ftrace_clock();
- instance->per_cpu.clear();
- instance->per_cpu.reserve(num_cpus);
- size_t period_page_quota =
- instance->ftrace_config_muxer->GetPerCpuBufferSizePages();
+ size_t num_cpus = instance->ftrace_procfs->NumberOfCpus();
+ PERFETTO_CHECK(instance->cpu_readers.empty());
+ instance->cpu_readers.reserve(num_cpus);
for (size_t cpu = 0; cpu < num_cpus; cpu++) {
- auto reader = std::make_unique<CpuReader>(
+ instance->cpu_readers.emplace_back(
cpu, instance->ftrace_procfs->OpenPipeForCpu(cpu),
instance->table.get(), &symbolizer_, ftrace_clock,
&ftrace_clock_snapshot_);
- instance->per_cpu.emplace_back(std::move(reader), period_page_quota);
}
// Special case for primary instance: if not using the boot clock, take
@@ -224,23 +257,26 @@
// conversion back to boot. This is primarily for old kernels that predate
// boot support, and therefore default to "global" clock.
if (instance == &primary_ &&
- ftrace_clock != FtraceClock::FTRACE_CLOCK_UNSPECIFIED) {
+ ftrace_clock != protos::pbzero::FtraceClock::FTRACE_CLOCK_UNSPECIFIED) {
cpu_zero_stats_fd_ = primary_.ftrace_procfs->OpenCpuStats(0 /* cpu */);
MaybeSnapshotFtraceClock();
}
+ // Set up poll callbacks for the buffers if requested by at least one DS.
+ UpdateBufferWatermarkWatches(instance, instance_name);
+
// Start a new repeating read task (even if there is already one posted due
// to a different ftrace instance). Any old tasks will stop due to generation
// checks.
- auto generation = ++generation_;
- auto drain_period_ms = GetDrainPeriodMs();
+ auto generation = ++tick_generation_;
+ auto tick_period_ms = GetTickPeriodMs();
auto weak_this = weak_factory_.GetWeakPtr();
task_runner_->PostDelayedTask(
[weak_this, generation] {
if (weak_this)
weak_this->ReadTick(generation);
},
- drain_period_ms - (NowMs() % drain_period_ms));
+ tick_period_ms - (NowMs() % tick_period_ms));
}
// We handle the ftrace buffers in a repeating task (ReadTick). On a given tick,
@@ -257,31 +293,21 @@
// want to yield to the event loop, re-enqueueing a continuation task at the end
// of the immediate queue (letting other enqueued tasks to run before
// continuing). Therefore we introduce |kMaxPagesPerCpuPerReadTick|.
-//
-// There is also a possibility that the ftrace bandwidth is particularly high.
-// We do not want to continue trying to catch up to the event stream (via
-// continuation tasks) without bound, as we want to limit our cpu% usage. We
-// assume that given a config saying "per-cpu kernel ftrace buffer is N pages,
-// and drain every T milliseconds", we should not read more than N pages per
-// drain period. Therefore we introduce |per_cpu.period_page_quota|. If the
-// consumer wants to handle a high bandwidth of ftrace events, they should set
-// the config values appropriately.
void FtraceController::ReadTick(int generation) {
metatrace::ScopedEvent evt(metatrace::TAG_FTRACE,
metatrace::FTRACE_READ_TICK);
- if (generation != generation_ || GetStartedDataSourcesCount() == 0) {
+ if (generation != tick_generation_ || GetStartedDataSourcesCount() == 0) {
return;
}
+ MaybeSnapshotFtraceClock();
- // Read all cpu buffers with remaining per-period quota.
- bool all_cpus_done = ReadTickForInstance(&primary_);
- for (auto& kv : secondary_instances_) {
- all_cpus_done &= ReadTickForInstance(kv.second.get());
- }
-
+ // Read all per-cpu buffers.
+ bool all_cpus_done = ReadPassForInstance(&primary_);
+ ForEachInstance([&](FtraceInstanceState* instance) {
+ all_cpus_done &= ReadPassForInstance(instance);
+ });
observer_->OnFtraceDataWrittenIntoDataSourceBuffers();
- // More work to do in this period.
auto weak_this = weak_factory_.GetWeakPtr();
if (!all_cpus_done) {
PERFETTO_DLOG("Reposting immediate ReadTick as there's more work.");
@@ -290,107 +316,192 @@
weak_this->ReadTick(generation);
});
} else {
- // Done until next drain period.
- size_t period_page_quota =
- primary_.ftrace_config_muxer->GetPerCpuBufferSizePages();
- for (auto& per_cpu : primary_.per_cpu)
- per_cpu.period_page_quota = period_page_quota;
-
- for (auto& it : secondary_instances_) {
- FtraceInstanceState* instance = it.second.get();
- size_t quota = instance->ftrace_config_muxer->GetPerCpuBufferSizePages();
- for (auto& per_cpu : instance->per_cpu) {
- per_cpu.period_page_quota = quota;
- }
- }
-
- // Snapshot the clock so the data in the next period will be clock synced as
- // well.
- MaybeSnapshotFtraceClock();
-
- auto drain_period_ms = GetDrainPeriodMs();
+ // Done until next period.
+ auto tick_period_ms = GetTickPeriodMs();
task_runner_->PostDelayedTask(
[weak_this, generation] {
if (weak_this)
weak_this->ReadTick(generation);
},
- drain_period_ms - (NowMs() % drain_period_ms));
+ tick_period_ms - (NowMs() % tick_period_ms));
}
+
+#if PERFETTO_DCHECK_IS_ON()
+ // OnFtraceDataWrittenIntoDataSourceBuffers() is supposed to clear
+ // all metadata, including the |kernel_addrs| map for symbolization.
+ ForEachInstance([&](FtraceInstanceState* instance) {
+ for (FtraceDataSource* ds : instance->started_data_sources) {
+ FtraceMetadata* ftrace_metadata = ds->mutable_metadata();
+ PERFETTO_DCHECK(ftrace_metadata->kernel_addrs.empty());
+ PERFETTO_DCHECK(ftrace_metadata->last_kernel_addr_index_written == 0);
+ }
+ });
+#endif
}
-bool FtraceController::ReadTickForInstance(FtraceInstanceState* instance) {
+bool FtraceController::ReadPassForInstance(FtraceInstanceState* instance) {
if (instance->started_data_sources.empty())
return true;
-#if PERFETTO_DCHECK_IS_ON()
- // The OnFtraceDataWrittenIntoDataSourceBuffers() below is supposed to clear
- // all metadata, including the |kernel_addrs| map for symbolization.
- for (FtraceDataSource* ds : instance->started_data_sources) {
- FtraceMetadata* ftrace_metadata = ds->mutable_metadata();
- PERFETTO_DCHECK(ftrace_metadata->kernel_addrs.empty());
- PERFETTO_DCHECK(ftrace_metadata->last_kernel_addr_index_written == 0);
- }
-#endif
-
bool all_cpus_done = true;
- for (size_t i = 0; i < instance->per_cpu.size(); i++) {
- size_t orig_quota = instance->per_cpu[i].period_page_quota;
- if (orig_quota == 0)
- continue;
-
- size_t max_pages = std::min(orig_quota, kMaxPagesPerCpuPerReadTick);
- CpuReader& cpu_reader = *instance->per_cpu[i].reader;
- size_t pages_read = cpu_reader.ReadCycle(&parsing_mem_, max_pages,
- instance->started_data_sources);
-
- size_t new_quota = (pages_read >= orig_quota) ? 0 : orig_quota - pages_read;
- instance->per_cpu[i].period_page_quota = new_quota;
-
- // Reader got stopped by the cap on the number of pages (to not do too much
- // work on the shared thread at once), but can read more in this drain
- // period. Repost the ReadTick (on the immediate queue) to iterate over all
- // cpus again. In other words, we will keep reposting work for all cpus as
- // long as at least one of them hits the read page cap each tick. If all
- // readers catch up to the event stream (pages_read < max_pages), or exceed
- // their quota, we will stop for the given period.
+ for (size_t i = 0; i < instance->cpu_readers.size(); i++) {
+ size_t max_pages = kMaxPagesPerCpuPerReadTick;
+ size_t pages_read = instance->cpu_readers[i].ReadCycle(
+ &parsing_mem_, max_pages, instance->started_data_sources);
PERFETTO_DCHECK(pages_read <= max_pages);
- if (pages_read == max_pages && new_quota > 0) {
+ if (pages_read == max_pages) {
all_cpus_done = false;
}
}
return all_cpus_done;
}
-uint32_t FtraceController::GetDrainPeriodMs() {
+uint32_t FtraceController::GetTickPeriodMs() {
if (data_sources_.empty())
- return kDefaultDrainPeriodMs;
- uint32_t min_drain_period_ms = kMaxDrainPeriodMs + 1;
- for (const FtraceDataSource* data_source : data_sources_) {
- if (data_source->config().drain_period_ms() < min_drain_period_ms)
- min_drain_period_ms = data_source->config().drain_period_ms();
+ return kDefaultTickPeriodMs;
+ uint32_t kUnsetPeriod = std::numeric_limits<uint32_t>::max();
+ uint32_t min_period_ms = kUnsetPeriod;
+ bool using_poll = true;
+ ForEachInstance([&](FtraceInstanceState* instance) {
+ using_poll &= instance->buffer_watches_posted;
+ for (FtraceDataSource* ds : instance->started_data_sources) {
+ if (ds->config().has_drain_period_ms()) {
+ min_period_ms = std::min(min_period_ms, ds->config().drain_period_ms());
+ }
+ }
+ });
+
+ // None of the active data sources requested an explicit tick period.
+ // The historical default is 100ms, but if we know that all instances are also
+ // using buffer watermark polling, we can raise it. We don't disable the tick
+ // entirely as it spreads the read work more evenly, and ensures procfs
+ // scrapes of seen TIDs are not too stale.
+ if (min_period_ms == kUnsetPeriod) {
+ return using_poll ? kPollBackingTickPeriodMs : kDefaultTickPeriodMs;
}
- return ClampDrainPeriodMs(min_drain_period_ms);
+
+ if (min_period_ms < kMinTickPeriodMs || min_period_ms > kMaxTickPeriodMs) {
+ PERFETTO_LOG(
+ "drain_period_ms was %u should be between %u and %u. "
+ "Falling back onto a default.",
+ min_period_ms, kMinTickPeriodMs, kMaxTickPeriodMs);
+ return kDefaultTickPeriodMs;
+ }
+ return min_period_ms;
+}
+
+void FtraceController::UpdateBufferWatermarkWatches(
+ FtraceInstanceState* instance,
+ const std::string& instance_name) {
+ PERFETTO_DCHECK(buffer_watermark_support_ != PollSupport::kUntested);
+ if (buffer_watermark_support_ == PollSupport::kUnsupported)
+ return;
+
+ bool requested_poll = false;
+ for (const FtraceDataSource* ds : instance->started_data_sources) {
+ requested_poll |= ds->config().has_drain_buffer_percent();
+ }
+
+ if (!requested_poll || instance->buffer_watches_posted)
+ return;
+
+ auto weak_this = weak_factory_.GetWeakPtr();
+ for (size_t i = 0; i < instance->cpu_readers.size(); i++) {
+ int fd = instance->cpu_readers[i].RawBufferFd();
+ task_runner_->AddFileDescriptorWatch(fd, [weak_this, instance_name, i] {
+ if (weak_this)
+ weak_this->OnBufferPastWatermark(instance_name, i,
+ /*repoll_watermark=*/true);
+ });
+ }
+ instance->buffer_watches_posted = true;
+}
+
+void FtraceController::RemoveBufferWatermarkWatches(
+ FtraceInstanceState* instance) {
+ if (!instance->buffer_watches_posted)
+ return;
+
+ for (size_t i = 0; i < instance->cpu_readers.size(); i++) {
+ int fd = instance->cpu_readers[i].RawBufferFd();
+ task_runner_->RemoveFileDescriptorWatch(fd);
+ }
+ instance->buffer_watches_posted = false;
+}
+
+// TODO(rsavitski): consider calling OnFtraceData only if we're not reposting
+// a continuation. It's a tradeoff between procfs scrape freshness and urgency
+// to drain ftrace kernel buffers.
+void FtraceController::OnBufferPastWatermark(std::string instance_name,
+ size_t cpu,
+ bool repoll_watermark) {
+ metatrace::ScopedEvent evt(metatrace::TAG_FTRACE,
+ metatrace::FTRACE_CPU_BUFFER_WATERMARK);
+
+ // Instance might have been stopped before this callback runs.
+ FtraceInstanceState* instance = GetInstance(instance_name);
+ if (!instance || cpu >= instance->cpu_readers.size())
+ return;
+
+ // Repoll all per-cpu buffers with zero timeout to confirm that at least
+ // one is still past the watermark. This might not be true if a different
+ // callback / readtick / flush did a read pass before this callback reached
+ // the front of the task runner queue.
+ if (repoll_watermark) {
+ size_t num_cpus = instance->cpu_readers.size();
+ std::vector<struct pollfd> pollfds(num_cpus);
+ for (size_t i = 0; i < num_cpus; i++) {
+ pollfds[i].fd = instance->cpu_readers[i].RawBufferFd();
+ pollfds[i].events = POLLIN;
+ }
+ int r = PERFETTO_EINTR(poll(pollfds.data(), num_cpus, 0));
+ if (r < 0) {
+ PERFETTO_DPLOG("poll failed");
+ return;
+ } else if (r == 0) { // no buffers below the watermark -> we're done.
+ return;
+ }
+ // Count the number of readable fds, as some poll results might be POLLERR,
+ // as seen in cases with offlined cores. It's still fine to attempt reading
+ // from those buffers as CpuReader will handle the ENODEV.
+ bool has_readable_fd = false;
+ for (size_t i = 0; i < num_cpus; i++) {
+ has_readable_fd |= (pollfds[i].revents & POLLIN);
+ }
+ if (!has_readable_fd) {
+ return;
+ }
+ }
+
+ MaybeSnapshotFtraceClock();
+ bool all_cpus_done = ReadPassForInstance(instance);
+ observer_->OnFtraceDataWrittenIntoDataSourceBuffers();
+ if (!all_cpus_done) {
+ // More data to be read, but we want to let other task_runner tasks to run.
+ // Repost a continuation task.
+ auto weak_this = weak_factory_.GetWeakPtr();
+ task_runner_->PostTask([weak_this, instance_name, cpu] {
+ if (weak_this)
+ weak_this->OnBufferPastWatermark(instance_name, cpu,
+ /*repoll_watermark=*/false);
+ });
+ }
}
void FtraceController::Flush(FlushRequestID flush_id) {
metatrace::ScopedEvent evt(metatrace::TAG_FTRACE,
metatrace::FTRACE_CPU_FLUSH);
- FlushForInstance(&primary_);
- for (auto& it : secondary_instances_) {
- FlushForInstance(it.second.get());
- }
-
+ ForEachInstance([&](FtraceInstanceState* instance) { // for clang-format
+ FlushForInstance(instance);
+ });
observer_->OnFtraceDataWrittenIntoDataSourceBuffers();
- for (FtraceDataSource* data_source : primary_.started_data_sources) {
- data_source->OnFtraceFlushComplete(flush_id);
- }
- for (auto& kv : secondary_instances_) {
- for (FtraceDataSource* data_source : kv.second->started_data_sources) {
- data_source->OnFtraceFlushComplete(flush_id);
+ ForEachInstance([&](FtraceInstanceState* instance) {
+ for (FtraceDataSource* ds : instance->started_data_sources) {
+ ds->OnFtraceFlushComplete(flush_id);
}
- }
+ });
}
void FtraceController::FlushForInstance(FtraceInstanceState* instance) {
@@ -400,11 +511,10 @@
// Read all cpus in one go, limiting the per-cpu read amount to make sure we
// don't get stuck chasing the writer if there's a very high bandwidth of
// events.
- size_t per_cpubuf_size_pages =
- instance->ftrace_config_muxer->GetPerCpuBufferSizePages();
- for (size_t i = 0; i < instance->per_cpu.size(); i++) {
- instance->per_cpu[i].reader->ReadCycle(&parsing_mem_, per_cpubuf_size_pages,
- instance->started_data_sources);
+ size_t max_pages = instance->ftrace_config_muxer->GetPerCpuBufferSizePages();
+ for (size_t i = 0; i < instance->cpu_readers.size(); i++) {
+ instance->cpu_readers[i].ReadCycle(&parsing_mem_, max_pages,
+ instance->started_data_sources);
}
}
@@ -415,7 +525,8 @@
if (!instance->started_data_sources.empty())
return;
- instance->per_cpu.clear();
+ RemoveBufferWatermarkWatches(instance);
+ instance->cpu_readers.clear();
if (instance == &primary_) {
cpu_zero_stats_fd_.reset();
}
@@ -471,15 +582,14 @@
FtraceConfigId config_id = data_source->config_id();
PERFETTO_CHECK(config_id);
-
- FtraceInstanceState* instance =
- GetOrCreateInstance(data_source->config().instance_name());
+ const std::string& instance_name = data_source->config().instance_name();
+ FtraceInstanceState* instance = GetOrCreateInstance(instance_name);
PERFETTO_CHECK(instance);
if (!instance->ftrace_config_muxer->ActivateConfig(config_id))
return false;
instance->started_data_sources.insert(data_source);
- StartIfNeeded(instance);
+ StartIfNeeded(instance, instance_name);
// Parse kernel symbols if required by the config. This can be an expensive
// operation (cpu-bound for 500ms+), so delay the StartDataSource
@@ -549,11 +659,77 @@
ReadFtraceNowTs(cpu_zero_stats_fd_).value_or(0);
}
-size_t FtraceController::GetStartedDataSourcesCount() const {
- size_t cnt = primary_.started_data_sources.size();
- for (auto& it : secondary_instances_) {
- cnt += it.second->started_data_sources.size();
+FtraceController::PollSupport
+FtraceController::VerifyKernelSupportForBufferWatermark() {
+ struct utsname uts = {};
+ if (uname(&uts) < 0 || strcmp(uts.sysname, "Linux") != 0)
+ return PollSupport::kUnsupported;
+ if (!PollSupportedOnKernelVersion(uts.release))
+ return PollSupport::kUnsupported;
+
+ // buffer_percent exists and is writable
+ auto* tracefs = primary_.ftrace_procfs.get();
+ uint32_t current = tracefs->ReadBufferPercent();
+ if (!tracefs->SetBufferPercent(current ? current : 50)) {
+ return PollSupport::kUnsupported;
}
+
+ // Polling on per_cpu/cpu0/trace_pipe_raw doesn't return errors.
+ base::ScopedFile fd = tracefs->OpenPipeForCpu(0);
+ struct pollfd pollset = {};
+ pollset.fd = fd.get();
+ pollset.events = POLLIN;
+ int r = PERFETTO_EINTR(poll(&pollset, 1, 0));
+ if (r < 0 || (r > 0 && (pollset.revents & POLLERR))) {
+ return PollSupport::kUnsupported;
+ }
+ return PollSupport::kSupported;
+}
+
+// Check kernel version since the poll implementation has historical bugs.
+// We're looking for at least 6.1 for the following:
+// 42fb0a1e84ff tracing/ring-buffer: Have polling block on watermark
+// Otherwise the poll will wake us up as soon as a single byte is in the
+// buffer. A more conservative check would look for 6.6 for an extra fix that
+// reduces excessive kernel-space wakeups:
+// 1e0cb399c765 ring-buffer: Update "shortest_full" in polling
+// However that doesn't break functionality, so we'll still use poll if
+// requested by the config.
+// static
+bool FtraceController::PollSupportedOnKernelVersion(const char* uts_release) {
+ int major = 0, minor = 0;
+ if (sscanf(uts_release, "%d.%d", &major, &minor) != 2) {
+ return false;
+ }
+ if (major < kPollRequiredMajorVersion ||
+ (major == kPollRequiredMajorVersion &&
+ minor < kPollRequiredMinorVersion)) {
+ // Android: opportunistically detect a few select GKI kernels that are known
+ // to have the fixes. Note: 6.1 and 6.6 GKIs are already covered by the
+ // outer check.
+ std::optional<AndroidGkiVersion> gki = ParseAndroidGkiVersion(uts_release);
+ if (!gki.has_value())
+ return false;
+ // android13-5.10.197 or higher sublevel:
+ // ef47f25e98de ring-buffer: Update "shortest_full" in polling
+ // android13-5.15.133 and
+ // android14-5.15.133 or higher sublevel:
+ // b5d00cd7db66 ring-buffer: Update "shortest_full" in polling
+ bool gki_patched =
+ (gki->release == 13 && gki->version == 5 && gki->patch_level == 10 &&
+ gki->sub_level >= 197) ||
+ ((gki->release == 13 || gki->release == 14) && gki->version == 5 &&
+ gki->patch_level == 15 && gki->sub_level >= 133);
+ return gki_patched;
+ }
+ return true;
+}
+
+size_t FtraceController::GetStartedDataSourcesCount() {
+ size_t cnt = 0;
+ ForEachInstance([&](FtraceInstanceState* instance) {
+ cnt += instance->started_data_sources.size();
+ });
return cnt;
}
@@ -609,7 +785,6 @@
PERFETTO_FATAL("Bug in ftrace instance lifetimes");
}
-// TODO(rsavitski): dedupe with FtraceController::Create.
std::unique_ptr<FtraceController::FtraceInstanceState>
FtraceController::CreateSecondaryInstance(const std::string& instance_name) {
std::optional<std::string> instance_path = AbsolutePathForInstance(
diff --git a/src/traced/probes/ftrace/ftrace_controller.h b/src/traced/probes/ftrace/ftrace_controller.h
index a10e37f..1e7c575 100644
--- a/src/traced/probes/ftrace/ftrace_controller.h
+++ b/src/traced/probes/ftrace/ftrace_controller.h
@@ -26,8 +26,6 @@
#include <string>
#include "perfetto/base/task_runner.h"
-#include "perfetto/ext/base/paged_memory.h"
-#include "perfetto/ext/base/utils.h"
#include "perfetto/ext/base/weak_ptr.h"
#include "perfetto/ext/tracing/core/basic_types.h"
#include "src/kallsyms/lazy_kernel_symbolizer.h"
@@ -93,6 +91,9 @@
const std::string& tracefs_root,
const std::string& raw_cfg_name);
+ // public for testing
+ static bool PollSupportedOnKernelVersion(const char* uts_release);
+
protected:
// Everything protected/virtual for testing:
@@ -103,13 +104,6 @@
Observer*);
struct FtraceInstanceState {
- struct PerCpuState {
- PerCpuState(std::unique_ptr<CpuReader> _reader, size_t _period_page_quota)
- : reader(std::move(_reader)), period_page_quota(_period_page_quota) {}
- std::unique_ptr<CpuReader> reader;
- size_t period_page_quota = 0;
- };
-
FtraceInstanceState(std::unique_ptr<FtraceProcfs>,
std::unique_ptr<ProtoTranslationTable>,
std::unique_ptr<FtraceConfigMuxer>);
@@ -117,47 +111,62 @@
std::unique_ptr<FtraceProcfs> ftrace_procfs;
std::unique_ptr<ProtoTranslationTable> table;
std::unique_ptr<FtraceConfigMuxer> ftrace_config_muxer;
- std::vector<PerCpuState> per_cpu; // empty if no started data sources
+ std::vector<CpuReader> cpu_readers; // empty if no started data sources
std::set<FtraceDataSource*> started_data_sources;
+ bool buffer_watches_posted = false;
};
FtraceInstanceState* GetInstance(const std::string& instance_name);
-
- virtual uint64_t NowMs() const;
-
// TODO(rsavitski): figure out a better testing shim.
virtual std::unique_ptr<FtraceInstanceState> CreateSecondaryInstance(
const std::string& instance_name);
+ virtual uint64_t NowMs() const;
+
private:
friend class TestFtraceController;
+ enum class PollSupport { kUntested, kSupported, kUnsupported };
FtraceController(const FtraceController&) = delete;
FtraceController& operator=(const FtraceController&) = delete;
- // Periodic task that reads all per-cpu ftrace buffers.
+ // Periodic task that reads all per-cpu ftrace buffers. Global across tracefs
+ // instances.
void ReadTick(int generation);
- bool ReadTickForInstance(FtraceInstanceState* instance);
- uint32_t GetDrainPeriodMs();
+ bool ReadPassForInstance(FtraceInstanceState* instance);
+ uint32_t GetTickPeriodMs();
+ // Optional: additional reads based on buffer capacity. Per tracefs instance.
+ void UpdateBufferWatermarkWatches(FtraceInstanceState* instance,
+ const std::string& instance_name);
+ void OnBufferPastWatermark(std::string instance_name,
+ size_t cpu,
+ bool repoll_watermark);
+ void RemoveBufferWatermarkWatches(FtraceInstanceState* instance);
+ PollSupport VerifyKernelSupportForBufferWatermark();
void FlushForInstance(FtraceInstanceState* instance);
- void StartIfNeeded(FtraceInstanceState* instance);
+ void StartIfNeeded(FtraceInstanceState* instance,
+ const std::string& instance_name);
void StopIfNeeded(FtraceInstanceState* instance);
FtraceInstanceState* GetOrCreateInstance(const std::string& instance_name);
void DestroyIfUnusedSeconaryInstance(FtraceInstanceState* instance);
- size_t GetStartedDataSourcesCount() const;
+ size_t GetStartedDataSourcesCount();
void MaybeSnapshotFtraceClock(); // valid only for primary_ tracefs instance
+ template <typename F /* void(FtraceInstanceState*) */>
+ void ForEachInstance(F fn);
+
base::TaskRunner* const task_runner_;
Observer* const observer_;
CpuReader::ParsingBuffers parsing_mem_;
LazyKernelSymbolizer symbolizer_;
FtraceConfigId next_cfg_id_ = 1;
- int generation_ = 0;
+ int tick_generation_ = 0;
bool retain_ksyms_on_stop_ = false;
+ PollSupport buffer_watermark_support_ = PollSupport::kUntested;
std::set<FtraceDataSource*> data_sources_;
// Default tracefs instance (normally /sys/kernel/tracing) is valid for as
// long as the controller is valid.
diff --git a/src/traced/probes/ftrace/ftrace_controller_unittest.cc b/src/traced/probes/ftrace/ftrace_controller_unittest.cc
index debdbfa..28ca2d4 100644
--- a/src/traced/probes/ftrace/ftrace_controller_unittest.cc
+++ b/src/traced/probes/ftrace/ftrace_controller_unittest.cc
@@ -150,6 +150,11 @@
.WillByDefault(Invoke(this, &MockFtraceProcfs::ReadCurrentTracer));
EXPECT_CALL(*this, ReadFileIntoString(root + "current_tracer"))
.Times(AnyNumber());
+
+ ON_CALL(*this, ReadFileIntoString(root + "buffer_percent"))
+ .WillByDefault(Return("50\n"));
+ EXPECT_CALL(*this, ReadFileIntoString(root + "buffer_percent"))
+ .Times(AnyNumber());
}
bool WriteTracingOn(const std::string& /*path*/, const std::string& value) {
@@ -216,7 +221,7 @@
MockTaskRunner* runner() { return runner_.get(); }
MockFtraceProcfs* procfs() { return primary_procfs_; }
- uint32_t drain_period_ms() { return GetDrainPeriodMs(); }
+ uint32_t tick_period_ms() { return GetTickPeriodMs(); }
std::unique_ptr<FtraceDataSource> AddFakeDataSource(const FtraceConfig& cfg) {
std::unique_ptr<FtraceDataSource> data_source(new FtraceDataSource(
@@ -339,8 +344,10 @@
// a single recurring read task will be posted as part of starting the data
// source.
Mock::VerifyAndClearExpectations(controller->runner());
- EXPECT_CALL(*controller->runner(), PostDelayedTask(_, _)).Times(1);
+ EXPECT_CALL(*controller->procfs(), WriteToFile("/root/buffer_percent", _))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(*controller->runner(), PostDelayedTask(_, _)).Times(1);
EXPECT_CALL(*controller->procfs(), WriteToFile("/root/tracing_on", "1"));
ASSERT_TRUE(controller->StartDataSource(data_source.get()));
@@ -390,8 +397,10 @@
// a single recurring read task will be posted as part of starting the data
// sources.
Mock::VerifyAndClearExpectations(controller->runner());
- EXPECT_CALL(*controller->runner(), PostDelayedTask(_, _)).Times(1);
+ EXPECT_CALL(*controller->procfs(), WriteToFile("/root/buffer_percent", _))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(*controller->runner(), PostDelayedTask(_, _)).Times(1);
EXPECT_CALL(*controller->procfs(), WriteToFile("/root/tracing_on", "1"));
ASSERT_TRUE(controller->StartDataSource(data_sourceA.get()));
ASSERT_TRUE(controller->StartDataSource(data_sourceB.get()));
@@ -433,6 +442,8 @@
.WillRepeatedly(Return(true));
EXPECT_CALL(*controller->procfs(), WriteToFile("/root/buffer_size_kb", _));
EXPECT_CALL(*controller->procfs(), WriteToFile(kFooEnablePath, "1"));
+ EXPECT_CALL(*controller->procfs(), WriteToFile("/root/buffer_percent", _))
+ .WillRepeatedly(Return(true));
auto data_source = controller->AddFakeDataSource(config);
EXPECT_CALL(*controller->procfs(), WriteToFile("/root/tracing_on", "1"));
@@ -468,9 +479,11 @@
.Times(AnyNumber());
{
- // No buffer size -> good default.
- EXPECT_CALL(*controller->procfs(),
- WriteToFile("/root/buffer_size_kb", "2048"));
+ // No buffer size -> good default (exact value depends on the ram size of
+ // the machine running this test).
+ EXPECT_CALL(
+ *controller->procfs(),
+ WriteToFile("/root/buffer_size_kb", testing::AnyOf("2048", "8192")));
FtraceConfig config = CreateFtraceConfig({"group/foo"});
auto data_source = controller->AddFakeDataSource(config);
ASSERT_TRUE(controller->StartDataSource(data_source.get()));
@@ -526,6 +539,18 @@
auto data_source = controller->AddFakeDataSource(config);
ASSERT_TRUE(controller->StartDataSource(data_source.get()));
}
+
+ {
+ // buffer_size_lower_bound -> default size no less than given.
+ EXPECT_CALL(
+ *controller->procfs(),
+ WriteToFile("/root/buffer_size_kb", testing::AnyOf("4096", "8192")));
+ FtraceConfig config = CreateFtraceConfig({"group/foo"});
+ config.set_buffer_size_kb(4096);
+ config.set_buffer_size_lower_bound(true);
+ auto data_source = controller->AddFakeDataSource(config);
+ ASSERT_TRUE(controller->StartDataSource(data_source.get()));
+ }
}
TEST(FtraceControllerTest, PeriodicDrainConfig) {
@@ -539,7 +564,8 @@
// No period -> good default.
FtraceConfig config = CreateFtraceConfig({"group/foo"});
auto data_source = controller->AddFakeDataSource(config);
- EXPECT_EQ(100u, controller->drain_period_ms());
+ controller->StartDataSource(data_source.get());
+ EXPECT_EQ(100u, controller->tick_period_ms());
}
{
@@ -547,7 +573,8 @@
FtraceConfig config = CreateFtraceConfig({"group/foo"});
config.set_drain_period_ms(0);
auto data_source = controller->AddFakeDataSource(config);
- EXPECT_EQ(100u, controller->drain_period_ms());
+ controller->StartDataSource(data_source.get());
+ EXPECT_EQ(100u, controller->tick_period_ms());
}
{
@@ -555,7 +582,8 @@
FtraceConfig config = CreateFtraceConfig({"group/foo"});
config.set_drain_period_ms(1000 * 60 * 60);
auto data_source = controller->AddFakeDataSource(config);
- EXPECT_EQ(100u, controller->drain_period_ms());
+ controller->StartDataSource(data_source.get());
+ EXPECT_EQ(100u, controller->tick_period_ms());
}
{
@@ -563,7 +591,8 @@
FtraceConfig config = CreateFtraceConfig({"group/foo"});
config.set_drain_period_ms(200);
auto data_source = controller->AddFakeDataSource(config);
- EXPECT_EQ(200u, controller->drain_period_ms());
+ controller->StartDataSource(data_source.get());
+ EXPECT_EQ(200u, controller->tick_period_ms());
}
}
@@ -644,8 +673,12 @@
config.set_instance_name("secondary");
// Primary instance won't be touched throughout the entire test.
- EXPECT_CALL(*controller->procfs(), WriteToFile(_, _)).Times(0);
+ // Exception: allow testing for kernel support of buffer_percent.
EXPECT_CALL(*controller->procfs(), ClearFile(_)).Times(0);
+ EXPECT_CALL(*controller->procfs(), WriteToFile(_, _)).Times(0);
+ EXPECT_CALL(*controller->procfs(), WriteToFile("/root/buffer_percent", _))
+ .Times(AnyNumber())
+ .WillRepeatedly(Return(true));
// AddDataSource will initialise the tracefs instance, enable the event
// through the muxer, but not yet enable tracing_on.
@@ -746,6 +779,8 @@
EXPECT_CALL(*controller->procfs(), WriteToFile("/root/tracing_on", "1"));
EXPECT_CALL(*controller->GetInstanceMockProcfs("secondary"),
WriteToFile("/root/instances/secondary/tracing_on", "1"));
+ EXPECT_CALL(*controller->procfs(), WriteToFile("/root/buffer_percent", _))
+ .WillRepeatedly(Return(true));
EXPECT_CALL(*controller->runner(), PostDelayedTask(_, _)).Times(2);
ASSERT_TRUE(controller->StartDataSource(primary_ds.get()));
@@ -791,4 +826,31 @@
EXPECT_EQ(*path, "/root/hyp/");
}
+TEST(FtraceControllerTest, PollSupportedOnKernelVersion) {
+ auto test = [](auto s) {
+ return FtraceController::PollSupportedOnKernelVersion(s);
+ };
+ // Linux 6.1 or above are ok
+ EXPECT_TRUE(test("6.5.13-1-amd64"));
+ EXPECT_TRUE(test("6.1.0-1-amd64"));
+ EXPECT_TRUE(test("6.1.25-android14-11-g"));
+ // before 6.1
+ EXPECT_FALSE(test("5.15.200-1-amd"));
+
+ // Android: check allowlisted GKI versions
+
+ // sublevel matters:
+ EXPECT_TRUE(test("5.10.198-android13-4-0"));
+ EXPECT_FALSE(test("5.10.189-android13-4-0"));
+ // sublevel matters:
+ EXPECT_TRUE(test("5.15.137-android14-8-suffix"));
+ EXPECT_FALSE(test("5.15.130-android14-8-suffix"));
+ // sublevel matters:
+ EXPECT_TRUE(test("5.15.137-android13-8-0"));
+ EXPECT_FALSE(test("5.15.129-android13-8-0"));
+ // android12 instead of android13 (clarification: this is part of the kernel
+ // version, and is unrelated to the system image version).
+ EXPECT_FALSE(test("5.10.198-android12-4-0"));
+}
+
} // namespace perfetto
diff --git a/src/traced/probes/ftrace/ftrace_metadata.h b/src/traced/probes/ftrace/ftrace_metadata.h
index 0a741b1..cede119 100644
--- a/src/traced/probes/ftrace/ftrace_metadata.h
+++ b/src/traced/probes/ftrace/ftrace_metadata.h
@@ -22,7 +22,6 @@
#include <unistd.h>
#include <bitset>
-#include <unordered_map>
#include "perfetto/base/flat_set.h"
#include "perfetto/base/logging.h"
diff --git a/src/traced/probes/ftrace/ftrace_procfs.cc b/src/traced/probes/ftrace/ftrace_procfs.cc
index f092468..5576627 100644
--- a/src/traced/probes/ftrace/ftrace_procfs.cc
+++ b/src/traced/probes/ftrace/ftrace_procfs.cc
@@ -21,8 +21,6 @@
#include <sys/types.h>
#include <unistd.h>
-#include <fstream>
-#include <sstream>
#include <string>
#include "perfetto/base/logging.h"
@@ -415,10 +413,6 @@
}
bool FtraceProcfs::SetCpuBufferSizeInPages(size_t pages) {
- if (pages * base::GetSysPageSize() > 1 * 1024 * 1024 * 1024) {
- PERFETTO_ELOG("Tried to set the per CPU buffer size to more than 1gb.");
- return false;
- }
std::string path = root_ + "buffer_size_kb";
return WriteNumberToFile(path, pages * (base::GetSysPageSize() / 1024ul));
}
@@ -515,6 +509,19 @@
return names;
}
+uint32_t FtraceProcfs::ReadBufferPercent() {
+ std::string path = root_ + "buffer_percent";
+ std::string raw = ReadFileIntoString(path);
+ std::optional<uint32_t> percent =
+ base::StringToUInt32(base::StripSuffix(raw, "\n"));
+ return percent.has_value() ? *percent : 0;
+}
+
+bool FtraceProcfs::SetBufferPercent(uint32_t percent) {
+ std::string path = root_ + "buffer_percent";
+ return WriteNumberToFile(path, percent);
+}
+
bool FtraceProcfs::WriteNumberToFile(const std::string& path, size_t value) {
// 2^65 requires 20 digits to write.
char buf[21];
diff --git a/src/traced/probes/ftrace/ftrace_procfs.h b/src/traced/probes/ftrace/ftrace_procfs.h
index 1e56573..42b1f83 100644
--- a/src/traced/probes/ftrace/ftrace_procfs.h
+++ b/src/traced/probes/ftrace/ftrace_procfs.h
@@ -155,6 +155,9 @@
// Get all the available clocks.
std::set<std::string> AvailableClocks();
+ uint32_t ReadBufferPercent();
+ bool SetBufferPercent(uint32_t percent);
+
// Get all the enabled events.
virtual std::vector<std::string> ReadEnabledEvents();
diff --git a/src/traced/probes/probes_producer.cc b/src/traced/probes/probes_producer.cc
index 0aff1db..ab75b35 100644
--- a/src/traced/probes/probes_producer.cc
+++ b/src/traced/probes/probes_producer.cc
@@ -18,8 +18,6 @@
#include <stdio.h>
#include <sys/stat.h>
-#include <algorithm>
-#include <queue>
#include <string>
#include "perfetto/base/logging.h"
@@ -51,12 +49,6 @@
#include "src/traced/probes/sys_stats/sys_stats_data_source.h"
#include "src/traced/probes/system_info/system_info_data_source.h"
-#include "protos/perfetto/config/ftrace/ftrace_config.gen.h"
-#include "protos/perfetto/trace/filesystem/inode_file_map.pbzero.h"
-#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h"
-#include "protos/perfetto/trace/ftrace/ftrace_stats.pbzero.h"
-#include "protos/perfetto/trace/trace_packet.pbzero.h"
-
namespace perfetto {
namespace {
diff --git a/src/traced/probes/statsd_client/statsd_binder_data_source.h b/src/traced/probes/statsd_client/statsd_binder_data_source.h
index 6268eb7..19aa5f5 100644
--- a/src/traced/probes/statsd_client/statsd_binder_data_source.h
+++ b/src/traced/probes/statsd_client/statsd_binder_data_source.h
@@ -22,10 +22,10 @@
#include "perfetto/base/task_runner.h"
#include "perfetto/ext/base/utils.h"
#include "perfetto/ext/base/weak_ptr.h"
+#include "perfetto/ext/protozero/proto_ring_buffer.h"
#include "perfetto/ext/tracing/core/basic_types.h"
#include "perfetto/ext/tracing/core/trace_writer.h"
#include "perfetto/tracing/core/forward_decls.h"
-#include "src/protozero/proto_ring_buffer.h"
#include "src/traced/probes/probes_data_source.h"
namespace perfetto {
diff --git a/src/traced_relay/relay_service.cc b/src/traced_relay/relay_service.cc
index f8bd4a0..e079b28 100644
--- a/src/traced_relay/relay_service.cc
+++ b/src/traced_relay/relay_service.cc
@@ -61,7 +61,7 @@
PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
set_peer_identity->set_pid(pid);
#else
- base::IgnoreResult(pid);
+ base::ignore_result(pid);
#endif
set_peer_identity->set_uid(uid);
set_peer_identity->set_machine_id_hint(machine_id_hint);
diff --git a/test/BUILD.gn b/test/BUILD.gn
index d129b2f..2a0a4bc 100644
--- a/test/BUILD.gn
+++ b/test/BUILD.gn
@@ -39,10 +39,10 @@
"../src/base:base",
"../src/base:test_support",
"../src/perfetto_cmd:bugreport_path",
+ "../src/protozero/filtering:bytecode_generator",
]
if (enable_perfetto_traced_probes) {
deps += [
- "../src/protozero/filtering:bytecode_generator",
"../src/traced/probes/ftrace",
"../src/traced/probes/ftrace:ftrace_procfs",
]
diff --git a/test/cmdline_integrationtest.cc b/test/cmdline_integrationtest.cc
index 74d405b..3ac6f22 100644
--- a/test/cmdline_integrationtest.cc
+++ b/test/cmdline_integrationtest.cc
@@ -25,9 +25,11 @@
#include "perfetto/ext/base/utils.h"
#include "perfetto/ext/traced/traced.h"
#include "perfetto/protozero/scattered_heap_buffer.h"
+#include "perfetto/tracing/core/tracing_service_state.h"
#include "src/base/test/test_task_runner.h"
#include "src/base/test/utils.h"
#include "src/perfetto_cmd/bugreport_path.h"
+#include "src/protozero/filtering/filter_bytecode_generator.h"
#include "test/gtest_and_gmock.h"
#include "test/test_helper.h"
@@ -69,15 +71,31 @@
}
// For the SaveForBugreport* tests.
-TraceConfig CreateTraceConfigForBugreportTest() {
+TraceConfig CreateTraceConfigForBugreportTest(int score = 1,
+ bool add_filter = false,
+ uint32_t msg_count = 3,
+ uint32_t msg_size = 10) {
TraceConfig trace_config;
- trace_config.add_buffers()->set_size_kb(4096);
+ trace_config.add_buffers()->set_size_kb(32768);
trace_config.set_duration_ms(60000); // Will never hit this.
- trace_config.set_bugreport_score(10);
+ trace_config.set_bugreport_score(score);
+
+ if (add_filter) {
+ // Add a trace filter which disallows the trace config echo-back.
+ protozero::FilterBytecodeGenerator filt;
+ filt.AddNestedField(1 /* root trace.packet*/, 1);
+ filt.EndMessage();
+ // Add a random unrelated field to keep the generator happy.
+ filt.AddSimpleField(protos::pbzero::TracePacket::kTraceUuidFieldNumber);
+ filt.EndMessage();
+ trace_config.mutable_trace_filter()->set_bytecode_v2(filt.Serialize());
+ }
+
auto* ds_config = trace_config.add_data_sources()->mutable_config();
ds_config->set_name("android.perfetto.FakeProducer");
- ds_config->mutable_for_testing()->set_message_count(3);
- ds_config->mutable_for_testing()->set_message_size(10);
+
+ ds_config->mutable_for_testing()->set_message_count(msg_count);
+ ds_config->mutable_for_testing()->set_message_size(msg_size);
return trace_config;
}
@@ -95,17 +113,7 @@
test_helper_.StartServiceIfRequired();
}
- FakeProducer* ConnectFakeProducer() {
- return test_helper_.ConnectFakeProducer();
- }
-
- std::function<void()> WrapTask(const std::function<void()>& function) {
- return test_helper_.WrapTask(function);
- }
-
- void WaitForProducerSetup() { test_helper_.WaitForProducerSetup(); }
-
- void WaitForProducerEnabled() { test_helper_.WaitForProducerEnabled(); }
+ TestHelper& test_helper() { return test_helper_; }
// Creates a process that represents the perfetto binary that will
// start when Run() is called. |args| will be passed as part of
@@ -154,7 +162,7 @@
// Start the service and connect a simple fake producer.
StartServiceIfRequiredNoNewExecsAfterThis();
- auto* fake_producer = ConnectFakeProducer();
+ auto* fake_producer = test_helper().ConnectFakeProducer();
ASSERT_TRUE(fake_producer);
std::thread background_trace([&perfetto_proc]() {
@@ -163,16 +171,22 @@
});
// Wait for the producer to start, and then write out packets.
- WaitForProducerEnabled();
+ test_helper().WaitForProducerEnabled();
auto on_data_written = task_runner_.CreateCheckpoint("data_written");
- fake_producer->ProduceEventBatch(WrapTask(on_data_written));
+ fake_producer->ProduceEventBatch(test_helper().WrapTask(on_data_written));
task_runner_.RunUntilCheckpoint("data_written");
ASSERT_EQ(0, perfetto_br_proc.Run(&stderr_)) << "stderr: " << stderr_;
perfetto_proc.SendSigterm();
background_trace.join();
- auto check_trace_contents = [](std::string trace_path) {
+ uint32_t expected_packets = 0;
+ for (auto& ds : trace_config.data_sources()) {
+ if (ds.config().has_for_testing())
+ expected_packets = ds.config().for_testing().message_count();
+ }
+
+ auto check_trace_contents = [expected_packets](std::string trace_path) {
// Read the trace written in the fixed location
// (/data/misc/perfetto-traces/ on Android, /tmp/ on Linux/Mac) and make
// sure it has the right contents.
@@ -181,10 +195,10 @@
ASSERT_FALSE(trace_str.empty());
protos::gen::Trace trace;
ASSERT_TRUE(trace.ParseFromString(trace_str));
- int test_packets = 0;
+ uint32_t test_packets = 0;
for (const auto& p : trace.packet())
test_packets += p.has_for_testing() ? 1 : 0;
- ASSERT_EQ(test_packets, 3) << trace_path;
+ ASSERT_EQ(test_packets, expected_packets) << trace_path;
};
// Verify that both the original trace and the cloned bugreport contain
@@ -377,7 +391,7 @@
// Start the service and connect a simple fake producer.
StartServiceIfRequiredNoNewExecsAfterThis();
- auto* fake_producer = ConnectFakeProducer();
+ auto* fake_producer = test_helper().ConnectFakeProducer();
EXPECT_TRUE(fake_producer);
// Start a background thread that will deliver the config now that we've
@@ -387,13 +401,13 @@
EXPECT_EQ(0, perfetto_proc.Run(&stderr_str)) << stderr_str;
});
- WaitForProducerSetup();
+ test_helper().WaitForProducerSetup();
EXPECT_EQ(0, trigger_proc.Run(&stderr_));
// Wait for the producer to start, and then write out 11 packets.
- WaitForProducerEnabled();
+ test_helper().WaitForProducerEnabled();
auto on_data_written = task_runner_.CreateCheckpoint("data_written");
- fake_producer->ProduceEventBatch(WrapTask(on_data_written));
+ fake_producer->ProduceEventBatch(test_helper().WrapTask(on_data_written));
task_runner_.RunUntilCheckpoint("data_written");
background_trace.join();
@@ -472,7 +486,7 @@
// Start the service and connect a simple fake producer.
StartServiceIfRequiredNoNewExecsAfterThis();
- auto* fake_producer = ConnectFakeProducer();
+ auto* fake_producer = test_helper().ConnectFakeProducer();
EXPECT_TRUE(fake_producer);
// Start a background thread that will deliver the config now that we've
@@ -482,11 +496,11 @@
EXPECT_EQ(0, perfetto_proc.Run(&stderr_str)) << stderr_str;
});
- WaitForProducerEnabled();
+ test_helper().WaitForProducerEnabled();
// Wait for the producer to start, and then write out 11 packets, before the
// trace actually starts (the trigger is seen).
auto on_data_written = task_runner_.CreateCheckpoint("data_written_1");
- fake_producer->ProduceEventBatch(WrapTask(on_data_written));
+ fake_producer->ProduceEventBatch(test_helper().WrapTask(on_data_written));
task_runner_.RunUntilCheckpoint("data_written_1");
EXPECT_EQ(0, trigger_proc.Run(&stderr_)) << "stderr: " << stderr_;
@@ -574,7 +588,7 @@
trace_config.SerializeAsString());
StartServiceIfRequiredNoNewExecsAfterThis();
- auto* fake_producer = ConnectFakeProducer();
+ auto* fake_producer = test_helper().ConnectFakeProducer();
EXPECT_TRUE(fake_producer);
std::string stderr_str;
@@ -644,7 +658,7 @@
// Start the service and connect a simple fake producer.
StartServiceIfRequiredNoNewExecsAfterThis();
- auto* fake_producer = ConnectFakeProducer();
+ auto* fake_producer = test_helper().ConnectFakeProducer();
EXPECT_TRUE(fake_producer);
std::thread background_trace([&perfetto_proc]() {
@@ -652,11 +666,11 @@
EXPECT_EQ(0, perfetto_proc.Run(&stderr_str)) << stderr_str;
});
- WaitForProducerEnabled();
+ test_helper().WaitForProducerEnabled();
// Wait for the producer to start, and then write out 11 packets, before the
// trace actually starts (the trigger is seen).
auto on_data_written = task_runner_.CreateCheckpoint("data_written_1");
- fake_producer->ProduceEventBatch(WrapTask(on_data_written));
+ fake_producer->ProduceEventBatch(test_helper().WrapTask(on_data_written));
task_runner_.RunUntilCheckpoint("data_written_1");
EXPECT_EQ(0, perfetto_proc_2.Run(&stderr_)) << "stderr: " << stderr_;
@@ -739,7 +753,7 @@
// Start the service and connect a simple fake producer.
StartServiceIfRequiredNoNewExecsAfterThis();
- auto* fake_producer = ConnectFakeProducer();
+ auto* fake_producer = test_helper().ConnectFakeProducer();
EXPECT_TRUE(fake_producer);
std::string trace_str;
@@ -809,7 +823,7 @@
// Start the service and connect a simple fake producer.
StartServiceIfRequiredNoNewExecsAfterThis();
- auto* fake_producer = ConnectFakeProducer();
+ auto* fake_producer = test_helper().ConnectFakeProducer();
EXPECT_TRUE(fake_producer);
std::thread background_trace([&perfetto_proc]() {
@@ -817,11 +831,11 @@
EXPECT_EQ(0, perfetto_proc.Run(&stderr_str)) << stderr_str;
});
- WaitForProducerEnabled();
+ test_helper().WaitForProducerEnabled();
// Wait for the producer to start, and then write out 11 packets, before the
// trace actually starts (the trigger is seen).
auto on_data_written = task_runner_.CreateCheckpoint("data_written_1");
- fake_producer->ProduceEventBatch(WrapTask(on_data_written));
+ fake_producer->ProduceEventBatch(test_helper().WrapTask(on_data_written));
task_runner_.RunUntilCheckpoint("data_written_1");
EXPECT_EQ(0, perfetto_proc_2.Run(&stderr_)) << "stderr: " << stderr_;
@@ -888,7 +902,7 @@
// Start the service and connect a simple fake producer.
StartServiceIfRequiredNoNewExecsAfterThis();
- auto* fake_producer = ConnectFakeProducer();
+ auto* fake_producer = test_helper().ConnectFakeProducer();
EXPECT_TRUE(fake_producer);
std::thread background_trace([&perfetto_proc]() {
@@ -896,11 +910,11 @@
EXPECT_EQ(0, perfetto_proc.Run(&stderr_str)) << stderr_str;
});
- WaitForProducerEnabled();
+ test_helper().WaitForProducerEnabled();
// Wait for the producer to start, and then write out 11 packets, before the
// trace actually starts (the trigger is seen).
auto on_data_written = task_runner_.CreateCheckpoint("data_written_1");
- fake_producer->ProduceEventBatch(WrapTask(on_data_written));
+ fake_producer->ProduceEventBatch(test_helper().WrapTask(on_data_written));
task_runner_.RunUntilCheckpoint("data_written_1");
EXPECT_EQ(0, trigger_proc.Run(&stderr_)) << "stderr: " << stderr_;
@@ -977,4 +991,175 @@
RunBugreportTest(std::move(trace_config), /*check_original_trace=*/false);
}
+TEST_F(PerfettoCmdlineTest, SaveAllForBugreport_NoTraces) {
+ auto save_all_cmd = ExecPerfetto({"--save-all-for-bugreport"});
+ StartServiceIfRequiredNoNewExecsAfterThis();
+ EXPECT_EQ(0, save_all_cmd.Run(&stderr_));
+ EXPECT_THAT(stderr_, HasSubstr("No tracing sessions eligible"));
+}
+
+TEST_F(PerfettoCmdlineTest, SaveAllForBugreport_FourTraces) {
+ struct TraceProc {
+ explicit TraceProc(TraceConfig c) : cfg(std::move(c)) {}
+
+ TraceConfig cfg;
+ std::optional<Exec> proc;
+ std::thread thd;
+ };
+
+ auto remove_br_files = [] {
+ remove((GetBugreportTraceDir() + "/systrace.pftrace").c_str());
+ remove((GetBugreportTraceDir() + "/custom_name.pftrace").c_str());
+ remove((GetBugreportTraceDir() + "/custom_name_1.pftrace").c_str());
+ remove((GetBugreportTraceDir() + "/systrace_1.pftrace").c_str());
+ };
+
+ remove_br_files(); // Remove both before and after ending the test.
+ auto remove_on_exit = base::OnScopeExit(remove_br_files);
+
+ auto session_prefix = "bugreport_test_" +
+ std::to_string(base::GetWallTimeNs().count() % 1000000);
+
+ // Create four tracing sessions with different bugreport scores.
+ // Two of them will have the default "systrace.pftrace" name.
+ std::vector<TraceProc> traces;
+ const bool add_filt = true;
+ traces.emplace_back(CreateTraceConfigForBugreportTest(/*score=*/1, add_filt));
+ traces.back().cfg.set_unique_session_name(session_prefix + "_1");
+
+ traces.emplace_back(CreateTraceConfigForBugreportTest(/*score=*/2, add_filt));
+ traces.back().cfg.set_bugreport_filename("custom_name.pftrace");
+ traces.back().cfg.set_unique_session_name(session_prefix + "_2");
+
+ traces.emplace_back(CreateTraceConfigForBugreportTest(/*score=*/3, add_filt));
+ traces.back().cfg.set_bugreport_filename("custom_name.pftrace");
+ traces.back().cfg.set_unique_session_name(session_prefix + "_3");
+
+ traces.emplace_back(CreateTraceConfigForBugreportTest(/*score=*/4, add_filt));
+ traces.back().cfg.set_unique_session_name(session_prefix + "_4");
+
+ for (auto& trace : traces) {
+ std::string cfg = trace.cfg.SerializeAsString();
+ trace.proc = ExecPerfetto({"-o", base::kDevNull, "-c", "-"}, cfg);
+ }
+
+ Exec perfetto_br_proc = ExecPerfetto({"--save-all-for-bugreport"});
+
+ StartServiceIfRequiredNoNewExecsAfterThis();
+
+ for (auto& trace : traces) {
+ trace.thd = std::thread([&trace] {
+ std::string stderr_str;
+ ASSERT_EQ(0, trace.proc->Run(&stderr_str)) << stderr_str;
+ PERFETTO_DLOG("perfetto-cmd output:\n%s", stderr_str.c_str());
+ });
+ }
+
+ // Wait that all tracing sessions are started.
+ // Note that in CTS mode, the Android test infra will start other tracing
+ // sessions for performance reasons. We can't just wait to see 4 sessions,
+ // we need to actually check the unique session name.
+ test_helper().ConnectConsumer();
+ test_helper().WaitForConsumerConnect();
+ for (;;) {
+ auto state = test_helper().QueryServiceStateAndWait();
+ const auto& sessions = state.tracing_sessions();
+ if (std::count_if(sessions.begin(), sessions.end(),
+ [&](const TracingServiceState::TracingSession& s) {
+ return base::StartsWith(s.unique_session_name(),
+ session_prefix);
+ }) >= 4) {
+ break;
+ }
+ base::SleepMicroseconds(100 * 1000);
+ }
+
+ EXPECT_EQ(0, perfetto_br_proc.Run(&stderr_)) << stderr_;
+ PERFETTO_DLOG("perfetto --save-all-for-bugreport output:\n-----\n%s\n-----\n",
+ stderr_.c_str());
+
+ // Stop all the four ongoing traces, which by now got cloned.
+ for (auto& trace : traces) {
+ trace.proc->SendSigterm();
+ trace.thd.join();
+ }
+
+ auto check_trace = [&](std::string fname, int expected_score) {
+ std::string fpath = GetBugreportTraceDir() + "/" + fname;
+ ASSERT_TRUE(base::FileExists(fpath)) << fpath;
+ std::string trace_str;
+ base::ReadFile(fpath, &trace_str);
+ protos::gen::Trace trace;
+ ASSERT_TRUE(trace.ParseFromString(trace_str)) << fpath;
+ EXPECT_THAT(
+ trace.packet(),
+ Contains(Property(&protos::gen::TracePacket::trace_config,
+ Property(&protos::gen::TraceConfig::bugreport_score,
+ Eq(expected_score)))));
+ };
+
+ check_trace("systrace.pftrace", /*expected_score=*/4);
+ check_trace("custom_name.pftrace", /*expected_score=*/3);
+ check_trace("custom_name_1.pftrace", /*expected_score=*/2);
+ check_trace("systrace_1.pftrace", /*expected_score=*/1);
+}
+
+TEST_F(PerfettoCmdlineTest, SaveAllForBugreport_LargeTrace) {
+ auto remove_br_files = [] {
+ remove((GetBugreportTraceDir() + "/systrace.pftrace").c_str());
+ };
+
+ remove_br_files(); // Remove both before and after ending the test.
+ auto remove_on_exit = base::OnScopeExit(remove_br_files);
+
+ const uint32_t kMsgCount = 10000;
+ TraceConfig cfg = CreateTraceConfigForBugreportTest(
+ /*score=*/1, /*add_filter=*/false, kMsgCount, /*msg_size=*/1024);
+ std::string cfg_str = cfg.SerializeAsString();
+ Exec trace_proc = ExecPerfetto({"-o", base::kDevNull, "-c", "-"}, cfg_str);
+ Exec perfetto_br_proc = ExecPerfetto({"--save-all-for-bugreport"});
+
+ StartServiceIfRequiredNoNewExecsAfterThis();
+
+ auto* fake_producer = test_helper().ConnectFakeProducer();
+ EXPECT_TRUE(fake_producer);
+
+ std::thread thd([&trace_proc] {
+ std::string stderr_str;
+ ASSERT_EQ(0, trace_proc.Run(&stderr_str)) << stderr_str;
+ PERFETTO_DLOG("perfetto-cmd output:\n%s", stderr_str.c_str());
+ });
+
+ // Wait that the tracing session is started.
+ test_helper().ConnectConsumer();
+ test_helper().WaitForConsumerConnect();
+ while (test_helper().QueryServiceStateAndWait().num_sessions_started() == 0) {
+ base::SleepMicroseconds(100 * 1000);
+ }
+ test_helper().SyncAndWaitProducer();
+
+ auto on_data_written = task_runner_.CreateCheckpoint("data_written");
+ fake_producer->ProduceEventBatch(test_helper().WrapTask(on_data_written));
+ task_runner_.RunUntilCheckpoint("data_written");
+
+ EXPECT_EQ(0, perfetto_br_proc.Run(&stderr_)) << stderr_;
+ PERFETTO_DLOG("perfetto --save-all-for-bugreport output:\n-----\n%s\n-----\n",
+ stderr_.c_str());
+
+ // Stop the ongoing trace, which by now got cloned.
+ trace_proc.SendSigterm();
+ thd.join();
+
+ std::string fpath = GetBugreportTraceDir() + "/systrace.pftrace";
+ ASSERT_TRUE(base::FileExists(fpath)) << fpath;
+ std::string trace_str;
+ base::ReadFile(fpath, &trace_str);
+ protos::gen::Trace trace;
+ ASSERT_TRUE(trace.ParseFromString(trace_str)) << fpath;
+ ssize_t num_test_packets = std::count_if(
+ trace.packet().begin(), trace.packet().end(),
+ [](const protos::gen::TracePacket& tp) { return tp.has_for_testing(); });
+ EXPECT_EQ(num_test_packets, static_cast<ssize_t>(kMsgCount));
+}
+
} // namespace perfetto
diff --git a/test/data/trace-redaction-general.pftrace.sha256 b/test/data/trace-redaction-general.pftrace.sha256
new file mode 100644
index 0000000..9e7d405
--- /dev/null
+++ b/test/data/trace-redaction-general.pftrace.sha256
@@ -0,0 +1 @@
+dd5f14e36a23158cf740e146cbdd57816056c70ed73b6038a5f317dab28477fe
\ No newline at end of file
diff --git a/test/data/trace_redaction_jank_high_cpu.pftrace.sha256 b/test/data/trace_redaction_jank_high_cpu.pftrace.sha256
deleted file mode 100644
index 93dd891..0000000
--- a/test/data/trace_redaction_jank_high_cpu.pftrace.sha256
+++ /dev/null
@@ -1 +0,0 @@
-5ea2ff312b5061bf23c2da43380295ab1b207902ffa01462a35d665c56bb4e97
\ No newline at end of file
diff --git a/test/trace_processor/diff_tests/metrics/android/android_boot.out b/test/trace_processor/diff_tests/metrics/android/android_boot.out
new file mode 100644
index 0000000..3d5c172
--- /dev/null
+++ b/test/trace_processor/diff_tests/metrics/android/android_boot.out
@@ -0,0 +1,59 @@
+android_boot {
+ system_server_durations {
+ total_dur: 90219646678
+ uninterruptible_sleep_dur: 618417159
+ }
+ systemui_durations {
+ total_dur: 48481027953
+ uninterruptible_sleep_dur: 796263
+ }
+ launcher_durations {
+ total_dur: 23595248987
+ uninterruptible_sleep_dur: 257290255
+ }
+ gms_durations {
+ total_dur: 27804143410
+ uninterruptible_sleep_dur: 101685087
+ }
+ launcher_breakdown {
+ cold_start_dur: 403543498
+ }
+ full_trace_process_start_aggregation {
+ total_start_sum: 10678297679
+ num_of_processes: 29
+ average_start_time: 368217161.3448276
+ }
+ post_boot_process_start_aggregation {
+ total_start_sum: 6112984648
+ num_of_processes: 21
+ average_start_time: 291094507.04761904
+ }
+ full_trace_gc_aggregation {
+ total_gc_count: 4
+ num_of_processes_with_gc: 4
+ num_of_threads_with_gc: 4
+ avg_gc_duration: 260516077.75
+ avg_running_gc_duration: 3902628.5
+ full_gc_count: 4
+ collector_transition_gc_count: 0
+ young_gc_count: 0
+ native_alloc_gc_count: 0
+ explicit_gc_count: 0
+ alloc_gc_count: 0
+ mb_per_ms_of_gc: 0.8829305684617433
+ }
+ post_boot_gc_aggregation {
+ total_gc_count: 4
+ num_of_processes_with_gc: 4
+ num_of_threads_with_gc: 4
+ avg_gc_duration: 260516077.75
+ avg_running_gc_duration: 3902628.5
+ full_gc_count: 4
+ collector_transition_gc_count: 0
+ young_gc_count: 0
+ native_alloc_gc_count: 0
+ explicit_gc_count: 0
+ alloc_gc_count: 0
+ mb_per_ms_of_gc: 0.8829305684617433
+ }
+}
\ No newline at end of file
diff --git a/test/trace_processor/diff_tests/metrics/android/tests.py b/test/trace_processor/diff_tests/metrics/android/tests.py
index 4f15081..cb0c0a2 100644
--- a/test/trace_processor/diff_tests/metrics/android/tests.py
+++ b/test/trace_processor/diff_tests/metrics/android/tests.py
@@ -190,39 +190,7 @@
return DiffTestBlueprint(
trace=DataPath('android_postboot_unlock.pftrace'),
query=Metric('android_boot'),
- out=TextProto(r"""
- android_boot {
- system_server_durations {
- total_dur: 90219646678
- uninterruptible_sleep_dur: 618417159
- }
- systemui_durations {
- total_dur: 48481027953
- uninterruptible_sleep_dur: 796263
- }
- launcher_durations {
- total_dur: 23595248987
- uninterruptible_sleep_dur: 257290255
- }
- gms_durations {
- total_dur: 27804143410
- uninterruptible_sleep_dur: 101685087
- }
- launcher_breakdown {
- cold_start_dur: 403543498
- }
- full_trace_process_start_aggregation {
- total_start_sum: 10678297679
- num_of_processes: 1
- average_start_time: 368217161.3448276
- }
- post_boot_process_start_aggregation {
- total_start_sum: 6112984648
- num_of_processes: 1
- average_start_time: 291094507.04761904
- }
- }
- """))
+ out=Path('android_boot.out'))
def test_ad_services_metric(self):
return DiffTestBlueprint(
diff --git a/test/trace_processor/diff_tests/parser/profiling/heap_graph_flamegraph_system-server-heap-graph.out b/test/trace_processor/diff_tests/parser/profiling/heap_graph_flamegraph_system-server-heap-graph.out
index 1aa70e7..98cd438 100644
--- a/test/trace_processor/diff_tests/parser/profiling/heap_graph_flamegraph_system-server-heap-graph.out
+++ b/test/trace_processor/diff_tests/parser/profiling/heap_graph_flamegraph_system-server-heap-graph.out
@@ -1,5 +1,5 @@
"id","depth","name","map_name","count","cumulative_count","size","cumulative_size","parent_id"
-0,0,"java.lang.Class<java.lang.Object[]> [ROOT_JNI_GLOBAL]","JAVA",1,3,224,292,"[NULL]"
+0,0,"java.lang.Class<java.lang.Object[]> [ROOT_STICKY_CLASS]","JAVA",1,3,224,292,"[NULL]"
1,1,"java.lang.Object[]","JAVA",1,1,28,28,0
2,1,"java.lang.String","JAVA",1,1,40,40,0
3,0,"java.lang.String [ROOT_INTERNED_STRING]","JAVA",41197,41197,1845640,1845640,"[NULL]"
diff --git a/tools/install-build-deps b/tools/install-build-deps
index b27e8ea..02149a1 100755
--- a/tools/install-build-deps
+++ b/tools/install-build-deps
@@ -320,13 +320,13 @@
# Android NDK
Dependency(
'buildtools/ndk.zip',
- 'https://dl.google.com/android/repository/android-ndk-r21e-darwin-x86_64.zip',
- '437278103a3db12632c05b1be5c41bbb8522791a67e415cc54411a65366f499d',
+ 'https://dl.google.com/android/repository/android-ndk-r26c-darwin.zip',
+ '312756dfcbdbf389d35d651e17ca98683bd36cb83cc7bf7ad51cac5c06bd064b',
'darwin', 'all'),
Dependency(
'buildtools/ndk.zip',
- 'https://dl.google.com/android/repository/android-ndk-r21e-linux-x86_64.zip',
- 'ad7ce5467e18d40050dc51b8e7affc3e635c85bd8c59be62de32352328ed467e',
+ 'https://dl.google.com/android/repository/android-ndk-r26c-linux.zip',
+ '6d6e659834d28bb24ba7ae66148ad05115ebbad7dabed1af9b3265674774fcf6',
'linux', 'x64'),
]
@@ -509,10 +509,16 @@
def ExtractZipfilePreservePermissions(zf, info, path):
- zf.extract(info.filename, path=path)
target_path = os.path.join(path, info.filename)
+ mode = info.external_attr >> 16
+ S_IFLNK = 0o120000 # symbolic link
+ if (mode & S_IFLNK) == S_IFLNK:
+ dst = zf.read(info).decode()
+ os.symlink(dst, target_path)
+ return
+ zf.extract(info.filename, path=path)
min_acls = 0o755 if info.filename.endswith('/') else 0o644
- os.chmod(target_path, (info.external_attr >> 16) | min_acls)
+ os.chmod(target_path, mode | min_acls)
def IsGitRepoCheckoutOutAtRevision(path, revision):
diff --git a/tools/symbolize-ui-crash b/tools/symbolize-ui-crash
index 8da6bf3..b6747b4 100755
--- a/tools/symbolize-ui-crash
+++ b/tools/symbolize-ui-crash
@@ -71,14 +71,23 @@
# Look for the GIT commitish appended in crash reports. This is not required
# for resolving the sourcemaps but helps generating better links.
- matches = re.findall(r'([a-f0-9]{40})\sUA', txt)
- git_rev = matches[0] if matches else 'HEAD'
+ matches = re.findall(r'https://ui.perfetto.dev/(.*-)([a-f0-9]{6,})\n', txt)
+ if not matches:
+ logging.fatal('Could not determine the version.'
+ 'The crash report should have a line like: '
+ '"UI: https://ui.perfetto.dev/v12.3-abcdef"')
+ return 1
- matches = re.findall(r'((\bhttp.+?\.js):(\d+):(\d+))', txt)
+ dir_name = matches[0][0] + matches[0][1]
+ git_rev = matches[0][1]
+ base_url = 'https://commondatastorage.googleapis.com/ui.perfetto.dev/' + dir_name + '/'
+ matches = re.findall(r'(\((.+[.]js):(\d+):(\d+)\))', txt)
maps_by_url = {}
sym_lines = ''
for entry in matches:
whole_token, script_url, line, col = entry
+ if '/' not in script_url:
+ script_url = base_url + script_url
map_url = script_url + '.map'
if map_url in maps_by_url:
srcmap = maps_by_url[map_url]
diff --git a/ui/release/build_all_channels.py b/ui/release/build_all_channels.py
index 4a61e5e..5549421 100755
--- a/ui/release/build_all_channels.py
+++ b/ui/release/build_all_channels.py
@@ -26,6 +26,7 @@
import sys
from os.path import dirname
+
pjoin = os.path.join
BUCKET_NAME = 'ui.perfetto.dev'
@@ -127,10 +128,10 @@
print('===================================================================')
print('Uploading to gs://%s' % BUCKET_NAME)
print('===================================================================')
- cp_cmd = [
- 'gsutil', '-m', '-h', 'Cache-Control:public, max-age=3600', 'cp', '-j',
- 'html,js,css,wasm'
- ]
+ # TODO(primiano): re-enable caching once the gzip-related outage is restored.
+ # cache_hdr = 'Cache-Control:public, max-age=3600'
+ cache_hdr = 'Cache-Control:no-cache'
+ cp_cmd = ['gsutil', '-m', '-h', cache_hdr, 'cp', '-j', 'html,js,css,wasm,map']
for name in os.listdir(merged_dist_dir):
path = pjoin(merged_dist_dir, name)
if os.path.isdir(path):
diff --git a/ui/src/assets/modal.scss b/ui/src/assets/modal.scss
index f7ed990..25e7706 100644
--- a/ui/src/assets/modal.scss
+++ b/ui/src/assets/modal.scss
@@ -127,7 +127,7 @@
background-color: #e6e6e6;
color: rgba(0, 0, 0, 0.8);
border: 2px solid transparent;
- border-radius: 4px;
+ border-radius: $pf-border-radius;
cursor: pointer;
text-transform: none;
overflow: visible;
diff --git a/ui/src/assets/perfetto.scss b/ui/src/assets/perfetto.scss
index 02ac412..0cf20b8 100644
--- a/ui/src/assets/perfetto.scss
+++ b/ui/src/assets/perfetto.scss
@@ -53,3 +53,4 @@
@import "widgets/vega_view";
@import "widgets/hotkey";
@import "widgets/text_paragraph";
+@import "widgets/treetable";
diff --git a/ui/src/assets/widgets/button.scss b/ui/src/assets/widgets/button.scss
index a73c5b2..1c7ef99 100644
--- a/ui/src/assets/widgets/button.scss
+++ b/ui/src/assets/widgets/button.scss
@@ -109,3 +109,9 @@
}
}
}
+
+.pf-button-bar {
+ display: flex;
+ flex-direction: row;
+ gap: 2px;
+}
diff --git a/ui/src/assets/widgets/treetable.scss b/ui/src/assets/widgets/treetable.scss
new file mode 100644
index 0000000..e87c54a
--- /dev/null
+++ b/ui/src/assets/widgets/treetable.scss
@@ -0,0 +1,64 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+@import "theme";
+
+$chevron-svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='8' width='8'%3E%3Cline x1='2' y1='0' x2='6' y2='4' stroke='black'/%3E%3Cline x1='6' y1='4' x2='2' y2='8' stroke='black'/%3E%3C/svg%3E");
+
+.pf-treetable {
+ font-family: $pf-font;
+ border-collapse: collapse;
+ text-align: left;
+ th {
+ text-align: left;
+ padding: 2px 8px;
+ border-bottom: solid 1px grey;
+ font-weight: bolder;
+ }
+ td {
+ padding: 2px 8px;
+ text-align: left;
+ padding-left: calc(var(--indentation-level) * 12px + 4px);
+ .pf-treetable-gutter {
+ display: inline;
+ &::before {
+ content: " ";
+ display: inline-block;
+ position: relative;
+ width: 12px;
+ }
+ }
+ }
+ td.pf-treetable-maincol {
+ font-weight: bolder;
+ }
+ td.pf-treetable-node {
+ .pf-treetable-gutter {
+ &::before {
+ content: $chevron-svg;
+ display: inline-block;
+ cursor: pointer;
+ width: 12px;
+ rotate: 90deg;
+ }
+ }
+ &.pf-collapsed {
+ .pf-treetable-gutter {
+ &::before {
+ rotate: 0deg;
+ }
+ }
+ }
+ }
+}
diff --git a/ui/src/base/time.ts b/ui/src/base/time.ts
index cc32288..5bd598e 100644
--- a/ui/src/base/time.ts
+++ b/ui/src/base/time.ts
@@ -209,7 +209,7 @@
// 123,123,123,123,123 -> 34h 12m
// 1,000,000,023 -> 1 s
// 1,230,000,023 -> 1.2 s
- static humanise(dur: duration) {
+ static humanise(dur: duration): string {
const sec = Duration.toSeconds(dur);
const units = ['s', 'ms', 'us', 'ns'];
const sign = Math.sign(sec);
diff --git a/ui/src/common/actions.ts b/ui/src/common/actions.ts
index c42d0e3..2c6dcfd 100644
--- a/ui/src/common/actions.ts
+++ b/ui/src/common/actions.ts
@@ -16,13 +16,11 @@
import {assertExists, assertTrue, assertUnreachable} from '../base/logging';
import {duration, time} from '../base/time';
-import {exists} from '../base/utils';
import {RecordConfig} from '../controller/record_config_types';
import {
GenericSliceDetailsTabConfig,
GenericSliceDetailsTabConfigBase,
} from '../frontend/generic_slice_details_tab';
-import {globals} from '../frontend/globals';
import {
Aggregation,
AggregationFunction,
@@ -70,7 +68,6 @@
ThreadTrackSortKey,
TraceTime,
TrackSortKey,
- TrackState,
UtidToTrackSortKey,
VisibleState,
} from './state';
@@ -204,30 +201,6 @@
state.traceUuid = args.traceUuid;
},
- fillUiTrackIdByTraceTrackId(
- state: StateDraft, trackState: TrackState, trackKey: string) {
- const setTrackKey = (trackId: number, trackKey: string) => {
- if (state.trackKeyByTrackId[trackId] !== undefined &&
- state.trackKeyByTrackId[trackId] !== trackKey) {
- throw new Error(`Trying to map track id ${trackId} to UI track ${
- trackKey}, already mapped to ${state.trackKeyByTrackId[trackId]}`);
- }
- state.trackKeyByTrackId[trackId] = trackKey;
- };
-
- const {uri} = trackState;
- if (exists(uri)) {
- // If track is a new "plugin" type track (i.e. it has a uri), resolve the
- // track ids from through the pluginManager.
- const trackInfo = globals.trackManager.resolveTrackInfo(uri);
- if (trackInfo?.trackIds) {
- for (const trackId of trackInfo.trackIds) {
- setTrackKey(trackId, trackKey);
- }
- }
- }
- },
-
addTracks(state: StateDraft, args: {tracks: AddTrackArgs[]}) {
args.tracks.forEach((track) => {
const trackKey =
@@ -242,7 +215,6 @@
uri: track.uri,
params: track.params,
};
- this.fillUiTrackIdByTraceTrackId(state, track as TrackState, trackKey);
if (track.trackGroup === SCROLLING_TRACK_GROUP) {
state.scrollingTracks.push(trackKey);
} else if (track.trackGroup !== undefined) {
diff --git a/ui/src/common/empty_state.ts b/ui/src/common/empty_state.ts
index 9704dc9..417fc87 100644
--- a/ui/src/common/empty_state.ts
+++ b/ui/src/common/empty_state.ts
@@ -93,7 +93,6 @@
newEngineMode: 'USE_HTTP_RPC_IF_AVAILABLE',
traceTime: {...defaultTraceTime},
tracks: {},
- trackKeyByTrackId: {},
utidToThreadSortKey: {},
aggregatePreferences: {},
trackGroups: {},
diff --git a/ui/src/common/registry.ts b/ui/src/common/registry.ts
index f20ad5c..4edd5a5 100644
--- a/ui/src/common/registry.ts
+++ b/ui/src/common/registry.ts
@@ -61,6 +61,10 @@
return registrant;
}
+ tryGet(kind: string): T|undefined {
+ return this.registry.get(kind);
+ }
+
// Support iteration: for (const foo of fooRegistry.values()) { ... }
* values() {
yield* this.registry.values();
diff --git a/ui/src/common/state.ts b/ui/src/common/state.ts
index 379104a..ffd8f3a 100644
--- a/ui/src/common/state.ts
+++ b/ui/src/common/state.ts
@@ -133,7 +133,8 @@
// 43. Remove visibleTracks.
// 44. Add TabsV2 state.
// 45. Remove v1 tracks.
-export const STATE_VERSION = 45;
+// 46. Remove trackKeyByTrackId.
+export const STATE_VERSION = 46;
export const SCROLLING_TRACK_GROUP = 'ScrollingTracks';
@@ -548,7 +549,6 @@
traceUuid?: string;
trackGroups: ObjectById<TrackGroupState>;
tracks: ObjectByKey<TrackState>;
- trackKeyByTrackId: {[key: number]: string;};
utidToThreadSortKey: UtidToTrackSortKey;
areas: ObjectById<AreaById>;
aggregatePreferences: ObjectById<AggregationState>;
diff --git a/ui/src/common/track_cache.ts b/ui/src/common/track_cache.ts
index 90c004e..613ba3d 100644
--- a/ui/src/common/track_cache.ts
+++ b/ui/src/common/track_cache.ts
@@ -14,6 +14,7 @@
import {Disposable, DisposableCallback} from '../base/disposable';
import {PanelSize} from '../frontend/panel';
+import {exists} from '../base/utils';
import {Store} from '../frontend/store';
import {
Migrate,
@@ -24,7 +25,7 @@
} from '../public';
import {Registry} from './registry';
-import {State} from './state';
+import {ObjectByKey, State, TrackState} from './state';
export interface TrackCacheEntry {
track: Track;
@@ -57,47 +58,55 @@
// Third cycle
// flushTracks() <-- 'foo' is destroyed.
export class TrackManager {
- private readonly registry = new Registry<TrackDescriptor>(({uri}) => uri);
- private readonly potentialTracks = new Set<TrackRef>();
- private safeCache = new Map<string, TrackCacheEntry>();
- private recycleBin = new Map<string, TrackCacheEntry>();
+ private _trackKeyByTrackId = new Map<number, string>();
+ private newTracks = new Map<string, TrackCacheEntry>();
+ private currentTracks = new Map<string, TrackCacheEntry>();
+ private trackRegistry = new Registry<TrackDescriptor>(({uri}) => uri);
+ private defaultTracks = new Set<TrackRef>();
+
private store: Store<State>;
+ private trackState?: ObjectByKey<TrackState>;
constructor(store: Store<State>) {
this.store = store;
}
+ get trackKeyByTrackId() {
+ this.updateTrackKeyByTrackIdMap();
+ return this._trackKeyByTrackId;
+ }
+
registerTrack(trackDesc: TrackDescriptor): Disposable {
- return this.registry.register(trackDesc);
+ return this.trackRegistry.register(trackDesc);
}
addPotentialTrack(track: TrackRef): Disposable {
- this.potentialTracks.add(track);
+ this.defaultTracks.add(track);
return new DisposableCallback(() => {
- this.potentialTracks.delete(track);
+ this.defaultTracks.delete(track);
});
}
findPotentialTracks(): TrackRef[] {
- return Array.from(this.potentialTracks);
+ return Array.from(this.defaultTracks);
}
getAllTracks(): TrackDescriptor[] {
- return Array.from(this.registry.values());
+ return Array.from(this.trackRegistry.values());
}
// Look up track into for a given track's URI.
// Returns |undefined| if no track can be found.
resolveTrackInfo(uri: string): TrackDescriptor|undefined {
- return this.registry.get(uri);
+ return this.trackRegistry.tryGet(uri);
}
// Creates a new track using |uri| and |params| or retrieves a cached track if
// |key| exists in the cache.
resolveTrack(key: string, trackDesc: TrackDescriptor, params?: unknown):
TrackCacheEntry {
- // Search for a cached version of this track in either of the caches.
- const cached = this.recycleBin.get(key) ?? this.safeCache.get(key);
+ // Search for a cached version of this track,
+ const cached = this.currentTracks.get(key);
// Ensure the cached track has the same factory type as the resolved track.
// If this has changed, the track should be re-created.
@@ -107,8 +116,7 @@
// Move this track from the recycle bin to the safe cache, which means
// it's safe from disposal for this cycle.
- this.safeCache.set(key, cached);
- this.recycleBin.delete(key);
+ this.newTracks.set(key, cached);
return cached;
} else {
@@ -125,24 +133,48 @@
const entry = new TrackFSM(track, trackDesc, trackContext);
// Push track into the safe cache.
- this.safeCache.set(key, entry);
+ this.newTracks.set(key, entry);
return entry;
}
}
- // Destroys all tracks in the recycle bin and moves all safe tracks into
- // the recycle bin.
+ // Destroys all current tracks not present in the new cache.
flushOldTracks() {
- for (const entry of this.recycleBin.values()) {
- entry.destroy();
+ for (const [key, entry] of this.currentTracks.entries()) {
+ if (!this.newTracks.has(key)) {
+ entry.destroy();
+ }
}
- this.recycleBin = this.safeCache;
- this.safeCache = new Map<string, TrackCacheEntry>();
+ this.currentTracks = this.newTracks;
+ this.newTracks = new Map<string, TrackCacheEntry>();
+ }
+
+ private updateTrackKeyByTrackIdMap() {
+ if (this.trackState === this.store.state.tracks) {
+ return;
+ }
+
+ const trackKeyByTrackId = new Map<number, string>();
+
+ const trackList = Object.entries(this.store.state.tracks);
+ trackList.forEach(([key, {uri}]) => {
+ const desc = this.trackRegistry.get(uri);
+ for (const trackId of desc?.trackIds ?? []) {
+ const existingKey = trackKeyByTrackId.get(trackId);
+ if (exists(existingKey)) {
+ throw new Error(`Trying to map track id ${trackId} to UI track ${key}, already mapped to ${existingKey}`);
+ }
+ trackKeyByTrackId.set(trackId, key);
+ }
+ });
+
+ this._trackKeyByTrackId = trackKeyByTrackId;
+ this.trackState = this.store.state.tracks;
}
}
-enum TrackState {
+enum TrackFSMState {
Creating = 'creating',
Ready = 'ready',
UpdatePending = 'update_pending',
@@ -157,41 +189,41 @@
* hooks are called synchronously and in the correct order.
*/
class TrackFSM implements TrackCacheEntry {
- private state: TrackState;
+ private state: TrackFSMState;
private error?: Error;
constructor(
public track: Track, public desc: TrackDescriptor, ctx: TrackContext) {
- this.state = TrackState.Creating;
+ this.state = TrackFSMState.Creating;
const result = this.track.onCreate?.(ctx);
Promise.resolve(result)
.then(() => this.onTrackCreated())
.catch((e) => {
this.error = e;
- this.state = TrackState.Error;
+ this.state = TrackFSMState.Error;
});
}
update(): void {
switch (this.state) {
- case TrackState.Creating:
- case TrackState.Updating:
- this.state = TrackState.UpdatePending;
+ case TrackFSMState.Creating:
+ case TrackFSMState.Updating:
+ this.state = TrackFSMState.UpdatePending;
break;
- case TrackState.Ready:
+ case TrackFSMState.Ready:
const result = this.track.onUpdate?.();
Promise.resolve(result)
.then(() => this.onTrackUpdated())
.catch((e) => {
this.error = e;
- this.state = TrackState.Error;
+ this.state = TrackFSMState.Error;
});
- this.state = TrackState.Updating;
+ this.state = TrackFSMState.Updating;
break;
- case TrackState.UpdatePending:
+ case TrackFSMState.UpdatePending:
// Update already pending... do nothing!
break;
- case TrackState.Error:
+ case TrackFSMState.Error:
break;
default:
throw new Error('Invalid state transition');
@@ -200,21 +232,21 @@
destroy(): void {
switch (this.state) {
- case TrackState.Ready:
+ case TrackFSMState.Ready:
// Don't bother awaiting this as the track can no longer be used.
Promise.resolve(this.track.onDestroy?.())
.catch(() => {
// Track crashed while being destroyed
// There's not a lot we can do here - just swallow the error
});
- this.state = TrackState.Destroyed;
+ this.state = TrackFSMState.Destroyed;
break;
- case TrackState.Creating:
- case TrackState.Updating:
- case TrackState.UpdatePending:
- this.state = TrackState.DestroyPending;
+ case TrackFSMState.Creating:
+ case TrackFSMState.Updating:
+ case TrackFSMState.UpdatePending:
+ this.state = TrackFSMState.DestroyPending;
break;
- case TrackState.Error:
+ case TrackFSMState.Error:
break;
default:
throw new Error('Invalid state transition');
@@ -223,25 +255,25 @@
private onTrackCreated() {
switch (this.state) {
- case TrackState.DestroyPending:
+ case TrackFSMState.DestroyPending:
// Don't bother awaiting this as the track can no longer be used.
this.track.onDestroy?.();
- this.state = TrackState.Destroyed;
+ this.state = TrackFSMState.Destroyed;
break;
- case TrackState.UpdatePending:
+ case TrackFSMState.UpdatePending:
const result = this.track.onUpdate?.();
Promise.resolve(result)
.then(() => this.onTrackUpdated())
.catch((e) => {
this.error = e;
- this.state = TrackState.Error;
+ this.state = TrackFSMState.Error;
});
- this.state = TrackState.Updating;
+ this.state = TrackFSMState.Updating;
break;
- case TrackState.Creating:
- this.state = TrackState.Ready;
+ case TrackFSMState.Creating:
+ this.state = TrackFSMState.Ready;
break;
- case TrackState.Error:
+ case TrackFSMState.Error:
break;
default:
throw new Error('Invalid state transition');
@@ -250,25 +282,25 @@
private onTrackUpdated() {
switch (this.state) {
- case TrackState.DestroyPending:
+ case TrackFSMState.DestroyPending:
// Don't bother awaiting this as the track can no longer be used.
this.track.onDestroy?.();
- this.state = TrackState.Destroyed;
+ this.state = TrackFSMState.Destroyed;
break;
- case TrackState.UpdatePending:
+ case TrackFSMState.UpdatePending:
const result = this.track.onUpdate?.();
Promise.resolve(result)
.then(() => this.onTrackUpdated())
.catch((e) => {
this.error = e;
- this.state = TrackState.Error;
+ this.state = TrackFSMState.Error;
});
- this.state = TrackState.Updating;
+ this.state = TrackFSMState.Updating;
break;
- case TrackState.Updating:
- this.state = TrackState.Ready;
+ case TrackFSMState.Updating:
+ this.state = TrackFSMState.Ready;
break;
- case TrackState.Error:
+ case TrackFSMState.Error:
break;
default:
throw new Error('Invalid state transition');
@@ -279,12 +311,12 @@
try {
this.track.render(ctx, size);
} catch {
- this.state = TrackState.Error;
+ this.state = TrackFSMState.Error;
}
}
getError(): Error | undefined {
- if (this.state === TrackState.Error) {
+ if (this.state === TrackFSMState.Error) {
return this.error;
} else {
return undefined;
diff --git a/ui/src/controller/flow_events_controller.ts b/ui/src/controller/flow_events_controller.ts
index 5af5935..1a7143d 100644
--- a/ui/src/controller/flow_events_controller.ts
+++ b/ui/src/controller/flow_events_controller.ts
@@ -201,7 +201,7 @@
const uiTrackIdToInfo = new Map<string, null|Info>();
const trackIdToInfo = new Map<number, null|Info>();
- const trackIdToUiTrackId = globals.state.trackKeyByTrackId;
+ const trackIdToUiTrackId = globals.trackManager.trackKeyByTrackId;
const tracks = globals.state.tracks;
const getInfo = (trackId: number): null|Info => {
@@ -210,7 +210,7 @@
return info;
}
- const uiTrackId = trackIdToUiTrackId[trackId];
+ const uiTrackId = trackIdToUiTrackId.get(trackId);
if (uiTrackId === undefined) {
trackIdToInfo.set(trackId, null);
return null;
diff --git a/ui/src/controller/permalink_controller.ts b/ui/src/controller/permalink_controller.ts
index a309be3..06ac307 100644
--- a/ui/src/controller/permalink_controller.ts
+++ b/ui/src/controller/permalink_controller.ts
@@ -37,6 +37,7 @@
import {Controller} from './controller';
import {RecordConfig, recordConfigValidator} from './record_config_types';
+import {showModal} from '../widgets/modal';
interface MultiEngineState {
currentEngineId?: string;
@@ -108,6 +109,20 @@
}
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
@@ -125,7 +140,6 @@
if (newState.engine !== undefined) {
newState.engine.ready = false;
}
-
const message = `Unable to parse old state version. Discarding state ` +
`and loading trace.`;
console.warn(message);
diff --git a/ui/src/controller/pivot_table_controller.ts b/ui/src/controller/pivot_table_controller.ts
index 230a151..864c402 100644
--- a/ui/src/controller/pivot_table_controller.ts
+++ b/ui/src/controller/pivot_table_controller.ts
@@ -15,7 +15,6 @@
*/
import {Actions} from '../common/actions';
-import {DEFAULT_CHANNEL, getCurrentChannel} from '../common/channels';
import {
AreaSelection,
PivotTableQuery,
@@ -39,8 +38,7 @@
id: 'pivotTable',
name: 'Pivot tables V2',
description: 'Second version of pivot table',
- // Enabled in canary and autopush by default.
- defaultValue: getCurrentChannel() !== DEFAULT_CHANNEL,
+ defaultValue: true,
});
function expectNumber(value: ColumnType): number {
diff --git a/ui/src/controller/search_controller.ts b/ui/src/controller/search_controller.ts
index 89b1973..ab6856c 100644
--- a/ui/src/controller/search_controller.ts
+++ b/ui/src/controller/search_controller.ts
@@ -278,7 +278,7 @@
if (it.source === 'cpu') {
trackId = cpuToTrackId.get(it.sourceId);
} else if (it.source === 'track') {
- trackId = globals.state.trackKeyByTrackId[it.sourceId];
+ trackId = globals.trackManager.trackKeyByTrackId.get(it.sourceId);
} else if (it.source === 'log') {
const logTracks =
Object.values(globals.state.tracks).filter((track) => {
diff --git a/ui/src/controller/selection_controller.ts b/ui/src/controller/selection_controller.ts
index d2bf5ba..6dc58ac 100644
--- a/ui/src/controller/selection_controller.ts
+++ b/ui/src/controller/selection_controller.ts
@@ -422,7 +422,7 @@
const endTs = rightTs !== -1n ? rightTs : globals.state.traceTime.end;
const delta = value - previousValue;
const duration = endTs - ts;
- const trackKey = globals.state.trackKeyByTrackId[trackId];
+ const trackKey = globals.trackManager.trackKeyByTrackId.get(trackId);
const name = trackKey ? globals.state.tracks[trackKey].name : undefined;
return {startTime: ts, value, delta, duration, name};
}
diff --git a/ui/src/controller/trace_controller.ts b/ui/src/controller/trace_controller.ts
index ad92b2f..40a8f09 100644
--- a/ui/src/controller/trace_controller.ts
+++ b/ui/src/controller/trace_controller.ts
@@ -739,7 +739,7 @@
});
const id = row.traceProcessorTrackId;
- const trackKey = globals.state.trackKeyByTrackId[id];
+ const trackKey = globals.trackManager.trackKeyByTrackId.get(id);
if (trackKey === undefined) {
return;
}
diff --git a/ui/src/frontend/aggregation_tab.ts b/ui/src/frontend/aggregation_tab.ts
index 0288b91..4c96c21 100644
--- a/ui/src/frontend/aggregation_tab.ts
+++ b/ui/src/frontend/aggregation_tab.ts
@@ -16,182 +16,133 @@
import {Disposable, Trash} from '../base/disposable';
import {AggregationPanel} from './aggregation_panel';
import {globals} from './globals';
-import {Area, AreaSelection} from '../common/state';
-import {Anchor} from '../widgets/anchor';
-import {Actions} from '../common/actions';
import {isEmptyData} from '../common/aggregation_data';
import {DetailsShell} from '../widgets/details_shell';
-import {Section} from '../widgets/section';
-import {GridLayout} from '../widgets/grid_layout';
-import {Icons} from '../base/semantic_icons';
-import {Tree, TreeNode} from '../widgets/tree';
-import {Timestamp} from './widgets/timestamp';
-import {PIVOT_TABLE_REDUX_FLAG} from '../controller/pivot_table_controller';
+import {Button, ButtonBar} from '../widgets/button';
+import {raf} from '../core/raf_scheduler';
+import {EmptyState} from '../widgets/empty_state';
+import {FlowEventsAreaSelectedPanel} from './flow_events_panel';
+import {PivotTable} from './pivot_table';
-interface AreaDetailsPanelAttrs {
- selection: AreaSelection;
-}
+interface View {
+ key: string;
+ name: string;
+ content: m.Children;
+};
-class AreaDetailsPanel implements m.ClassComponent<AreaDetailsPanelAttrs> {
- view(vnode: m.Vnode<AreaDetailsPanelAttrs>): m.Children {
- const {
- selection,
- } = vnode.attrs;
+class AreaDetailsPanel implements m.ClassComponent {
+ private currentTab: string|undefined = undefined;
- const areaId = selection.areaId;
- const area = globals.state.areas[areaId];
+ private getCurrentView(): string|undefined {
+ const types = this.getViews()
+ .map(({key}) => key);
+
+ if (types.length === 0) {
+ return undefined;
+ }
+
+ if (this.currentTab === undefined) {
+ return types[0];
+ }
+
+ if (!types.includes(this.currentTab)) {
+ return types[0];
+ }
+
+ return this.currentTab;
+ }
+
+ private getViews(): View[] {
+ const views = [];
+
+ for (const [key, value] of globals.aggregateDataStore.entries()) {
+ if (!isEmptyData(value)) {
+ views.push({
+ key: value.tabName,
+ name: value.tabName,
+ content: m(AggregationPanel, {kind: key, key, data: value}),
+ });
+ }
+ }
+
+ const pivotTableState = globals.state.nonSerializableState.pivotTable;
+ if (pivotTableState.selectionArea !== undefined) {
+ views.push({
+ key: 'pivot_table',
+ name: 'Pivot Table',
+ content: m(PivotTable, {
+ selectionArea:
+ pivotTableState.selectionArea,
+ }),
+ });
+ }
+
+ // Add this after all aggregation panels, to make it appear after 'Slices'
+ if (globals.selectedFlows.length > 0) {
+ views.push({
+ key: 'selected_flows',
+ name: 'Flow Events',
+ content: m(FlowEventsAreaSelectedPanel),
+ });
+ }
+
+ return views;
+ }
+
+ view(_: m.Vnode): m.Children {
+ const views = this.getViews();
+ const currentViewKey = this.getCurrentView();
+
+ const aggregationButtons = views.map(({key, name}) => {
+ return m(Button,
+ {
+ onclick: () => {
+ this.currentTab = key;
+ raf.scheduleFullRedraw();
+ },
+ key,
+ label: name,
+ active: currentViewKey === key,
+ minimal: true,
+ },
+ );
+ });
+
+ if (currentViewKey === undefined) {
+ return this.renderEmptyState();
+ }
+
+ const content = views.find(({key}) => key === currentViewKey)?.content;
+ if (content === undefined) {
+ return this.renderEmptyState();
+ }
return m(DetailsShell,
{
title: 'Area Selection',
+ description: m(ButtonBar, aggregationButtons),
},
- m(GridLayout,
- this.renderDetailsSection(area),
- this.renderLinksSection(),
- ),
+ content,
);
}
- private renderDetailsSection(area: Area) {
- return m(Section,
- {
- title: 'Details',
- },
- m(Tree,
- m(TreeNode, {left: 'Start', right: m(Timestamp, {ts: area.start})}),
- m(TreeNode, {left: 'End', right: m(Timestamp, {ts: area.end})}),
- m(TreeNode, {left: 'Track Count', right: area.tracks.length}),
- ),
- );
+ private renderEmptyState(): m.Children {
+ return m(EmptyState, {
+ className: 'pf-noselection',
+ title: 'Unsupported area selection',
+ },
+ 'No details available for this area selection');
}
-
- private renderLinksSection() {
- const linkNodes: m.Children = [];
-
- globals.aggregateDataStore.forEach((value, type) => {
- if (!isEmptyData(value)) {
- const anchor = m(Anchor,
- {
- icon: Icons.ChangeTab,
- onclick: () => {
- globals.dispatch(Actions.showTab({uri: uriForAggType(type)}));
- },
- },
- value.tabName,
- );
- const node = m(TreeNode, {left: anchor});
- linkNodes.push(node);
- }
- });
-
- linkNodes.push(m(TreeNode, {
- left: m(
- Anchor,
- {
- icon: Icons.ChangeTab,
- onclick: () => {
- globals.dispatch(
- Actions.showTab({uri: 'perfetto.Flows#FlowEvents'}));
- },
- },
- 'Flow Events'),
- }));
-
- if (PIVOT_TABLE_REDUX_FLAG.get()) {
- linkNodes.push(m(TreeNode, {
- left: m(
- Anchor,
- {
- icon: Icons.ChangeTab,
- onclick: () => {
- globals.dispatch(
- Actions.showTab({uri: 'perfetto.PivotTable#PivotTable'}));
- },
- },
- 'Pivot Table'),
- }));
- }
-
- if (linkNodes.length === 0) return undefined;
-
- return m(Section,
- {
- title: 'Relevant Aggregations',
- },
- m(Tree, linkNodes),
- );
- }
-}
-
-function uriForAggType(type: string): string {
- return `aggregationTab#${type}`;
}
export class AggregationsTabs implements Disposable {
- private tabs = [
- {
- type: 'cpu_aggregation',
- title: 'CPU by thread',
- },
- {
- type: 'thread_state_aggregation',
- title: 'Thread States',
- },
- {
- type: 'cpu_by_process_aggregation',
- title: 'CPU by process',
- },
- {
- type: 'slice_aggregation',
- title: 'Slices',
- },
- {
- type: 'counter_aggregation',
- title: 'Counters',
- },
- {
- type: 'frame_aggregation',
- title: 'Frames',
- },
- ];
-
private trash = new Trash();
constructor() {
- for (const {type, title} of this.tabs) {
- const uri = uriForAggType(type);
- const unregister = globals.tabManager.registerTab({
- uri,
- isEphemeral: false,
- content: {
- hasContent: () => {
- const data = globals.aggregateDataStore.get(type);
- const hasData = Boolean(data && !isEmptyData(data));
- return hasData;
- },
- getTitle: () => title,
- render: () => {
- const data = globals.aggregateDataStore.get(type);
- return m(AggregationPanel, {kind: type, data});
- },
- },
- });
- this.trash.add(unregister);
-
- const unregisterCmd = globals.commandManager.registerCommand({
- id: uri,
- name: `Show ${title} Aggregation Tab`,
- callback: () => {
- globals.dispatch(Actions.showTab({uri}));
- },
- });
- this.trash.add(unregisterCmd);
- }
-
const unregister = globals.tabManager.registerDetailsPanel({
render(selection) {
if (selection.kind === 'AREA') {
- return m(AreaDetailsPanel, {selection});
+ return m(AreaDetailsPanel);
} else {
return undefined;
}
diff --git a/ui/src/frontend/chrome_slice_details_tab.ts b/ui/src/frontend/chrome_slice_details_tab.ts
index 920c8c8..cf245eb 100644
--- a/ui/src/frontend/chrome_slice_details_tab.ts
+++ b/ui/src/frontend/chrome_slice_details_tab.ts
@@ -204,6 +204,7 @@
name: it.name ?? 'null',
ts: Time.fromRaw(it.ts),
dur: it.dur,
+ depth: 0,
trackId: it.trackId,
threadDur: it.threadDur ?? undefined,
category: it.cat ?? undefined,
diff --git a/ui/src/frontend/flow_events_panel.ts b/ui/src/frontend/flow_events_panel.ts
index 22e420f..db0b841 100644
--- a/ui/src/frontend/flow_events_panel.ts
+++ b/ui/src/frontend/flow_events_panel.ts
@@ -56,7 +56,7 @@
}
const flowClickHandler = (sliceId: number, trackId: number) => {
- const trackKey = globals.state.trackKeyByTrackId[trackId];
+ const trackKey = globals.trackManager.trackKeyByTrackId.get(trackId);
if (trackKey) {
globals.makeSelection(
Actions.selectChromeSlice({id: sliceId, trackKey, table: 'slice'}),
diff --git a/ui/src/frontend/flow_events_renderer.ts b/ui/src/frontend/flow_events_renderer.ts
index fc3083d..c64f05e 100644
--- a/ui/src/frontend/flow_events_renderer.ts
+++ b/ui/src/frontend/flow_events_renderer.ts
@@ -80,7 +80,7 @@
export class FlowEventsRenderer {
private getTrackGroupIdByTrackId(trackId: number): string|undefined {
- const trackKey = globals.state.trackKeyByTrackId[trackId];
+ const trackKey = globals.trackManager.trackKeyByTrackId.get(trackId);
return trackKey ? globals.state.tracks[trackKey].trackGroup : undefined;
}
diff --git a/ui/src/frontend/keyboard_event_handler.ts b/ui/src/frontend/keyboard_event_handler.ts
index 571eef5..bf031ef 100644
--- a/ui/src/frontend/keyboard_event_handler.ts
+++ b/ui/src/frontend/keyboard_event_handler.ts
@@ -172,7 +172,8 @@
for (const flow of globals.connectedFlows) {
if (flow.id === flowId) {
const flowPoint = (direction === 'Backward' ? flow.begin : flow.end);
- const trackKey = globals.state.trackKeyByTrackId[flowPoint.trackId];
+ const trackKeyByTrackId = globals.trackManager.trackKeyByTrackId;
+ const trackKey = trackKeyByTrackId.get(flowPoint.trackId);
if (trackKey) {
globals.makeSelection(Actions.selectChromeSlice({
id: flowPoint.sliceId,
diff --git a/ui/src/frontend/query_table.ts b/ui/src/frontend/query_table.ts
index 38a4aaf..7956990 100644
--- a/ui/src/frontend/query_table.ts
+++ b/ui/src/frontend/query_table.ts
@@ -144,7 +144,7 @@
const sliceStart = Time.fromRaw(BigInt(row.ts));
// row.dur can be negative. Clamp to 1ns.
const sliceDur = BigintMath.max(BigInt(row.dur), 1n);
- const trackKey = globals.state.trackKeyByTrackId[trackId];
+ const trackKey = globals.trackManager.trackKeyByTrackId.get(trackId);
if (trackKey !== undefined) {
reveal(trackKey, sliceStart, Time.add(sliceStart, sliceDur), true);
const sliceId = getSliceId(row);
diff --git a/ui/src/frontend/router.ts b/ui/src/frontend/router.ts
index 29028b0..c6c316b 100644
--- a/ui/src/frontend/router.ts
+++ b/ui/src/frontend/router.ts
@@ -308,4 +308,40 @@
throw new Error('History rewriting livelock');
}
}
+
+ static getUrlForVersion(versionCode: string): string {
+ const url = `${window.location.origin}/${versionCode}/`;
+ return url;
+ }
+
+ static async isVersionAvailable(versionCode: string):
+ Promise<string|undefined> {
+ if (versionCode === '') {
+ return undefined;
+ }
+ const controller = new AbortController();
+ const timeoutId = setTimeout(() => controller.abort(), 1000);
+ const url = Router.getUrlForVersion(versionCode);
+ let r;
+ try {
+ r = await fetch(url, {signal: controller.signal});
+ } catch (e) {
+ console.error(`No UI version for ${versionCode} at ${url}. This is an error if ${versionCode} is a released Perfetto version`);
+ return undefined;
+ } finally {
+ clearTimeout(timeoutId);
+ }
+ if (!r.ok) {
+ return undefined;
+ }
+ return url;
+ }
+
+ static navigateToVersion(versionCode: string): void {
+ const url = Router.getUrlForVersion(versionCode);
+ if (url === undefined) {
+ throw new Error(`No URL known for UI version ${versionCode}.`);
+ }
+ window.location.replace(url);
+ }
}
diff --git a/ui/src/frontend/rpc_http_dialog.ts b/ui/src/frontend/rpc_http_dialog.ts
index f29c846..97c186e 100644
--- a/ui/src/frontend/rpc_http_dialog.ts
+++ b/ui/src/frontend/rpc_http_dialog.ts
@@ -20,6 +20,7 @@
import {StatusResult, TraceProcessorApiVersion} from '../protos';
import {HttpRpcEngine} from '../trace_processor/http_rpc_engine';
import {showModal} from '../widgets/modal';
+import {Router} from './router';
import {globals} from './globals';
import {publishHttpRpcState} from './publish';
@@ -27,9 +28,9 @@
const CURRENT_API_VERSION =
TraceProcessorApiVersion.TRACE_PROCESSOR_CURRENT_API_VERSION;
-const PROMPT = () => `Trace Processor Native Accelerator detected on ` +
-`${HttpRpcEngine.hostAndPort} with:
-$loadedTraceName
+function getPromptMessage(tpStatus: StatusResult): string {
+ return `Trace Processor Native Accelerator detected on ${HttpRpcEngine.hostAndPort} with:
+${tpStatus.loadedTraceName}
YES, use loaded trace:
Will load from the current state of Trace Processor. If you did run
@@ -46,13 +47,11 @@
Using the native accelerator has some minor caveats:
- Only one tab can be using the accelerator.
- Sharing, downloading and conversion-to-legacy aren't supported.
-- You may encounter UI errors if the Trace Processor version you are using is
-too old. Get the latest version from get.perfetto.dev/trace_processor.
`;
+}
-
-const MSG_TOO_OLD = () => `The Trace Processor instance on ` +
-`${HttpRpcEngine.hostAndPort} is too old.
+function getIncompatibleRpcMessage(tpStatus: StatusResult): string {
+ return `The Trace Processor instance on ${HttpRpcEngine.hostAndPort} is too old.
This UI requires TraceProcessor features that are not present in the
Trace Processor native accelerator you are currently running.
@@ -64,14 +63,88 @@
chmod +x ./trace_processor
./trace_processor --httpd
-UI version: ${VERSION}
-TraceProcessor RPC API required: ${CURRENT_API_VERSION} or higher
+UI version code: ${VERSION}
+UI RPC API: ${CURRENT_API_VERSION}
-TraceProcessor version: $tpVersion
-RPC API: $tpApi
+Trace processor version: ${tpStatus.humanReadableVersion}
+Trace processor version code: ${tpStatus.versionCode}
+Trace processor RPC API: ${tpStatus.apiVersion}
`;
+}
-let forceUseOldVersion = false;
+function getVersionMismatchMessage(tpStatus: StatusResult): string {
+ return `The trace processor instance on ${HttpRpcEngine.hostAndPort} is a different build from the UI.
+
+This may cause problems. Where possible it is better to use the matched version of the UI.
+You can do this by clicking the button below.
+
+UI version code: ${VERSION}
+UI RPC API: ${CURRENT_API_VERSION}
+
+Trace processor version: ${tpStatus.humanReadableVersion}
+Trace processor version code: ${tpStatus.versionCode}
+Trace processor RPC API: ${tpStatus.apiVersion}
+`;
+}
+
+// The flow is fairly complicated:
+// +-----------------------------------+
+// | User loads the UI |
+// +-----------------+-----------------+
+// |
+// +-----------------+-----------------+
+// | Is trace_processor present at |
+// | HttpRpcEngine.hostAndPort? |
+// +--------------------------+--------+
+// |No |Yes
+// | +--------------+-------------------------------+
+// | | Does version code of UI and TP match? |
+// | +--------------+----------------------------+--+
+// | |No |Yes
+// | | |
+// | | |
+// | +-------------+-------------+ |
+// | |Is a build of the UI at the| |
+// | |TP version code existant | |
+// | |and reachable? | |
+// | +---+----------------+------+ |
+// | | No | Yes |
+// | | | |
+// | | +--------+-------+ |
+// | | |Dialog: Mismatch| |
+// | | |Load matched UI +-------------------------------+
+// | | |Continue +-+ | |
+// | | +----------------+ | | |
+// | | | | |
+// | +------+--------------------------+----+ | |
+// | |TP RPC version >= UI RPC version | | |
+// | +----+-------------------+-------------+ | |
+// | | No |Yes | |
+// | +----+--------------+ | | |
+// | |Dialog: Bad RPC | | | |
+// | +---+Use built-in WASM | | | |
+// | | |Continue anyway +----| | |
+// | | +-------------------+ | +-----------+-----------+ |
+// | | +--------+TP has preloaded trace?| |
+// | | +-+---------------+-----+ |
+// | | |No |Yes |
+// | | | +---------------------+ |
+// | | | | Dialog: Preloaded? | |
+// | | +--+ YES, use loaded trace |
+// | | +--------| YES, but reset state| |
+// | | +---------------------------------------| NO, Use builtin Wasm| |
+// | | | | | +---------------------+ |
+// | | | | | |
+// | | | Reset TP | |
+// | | | | | |
+// | | | | | |
+// Show the UI Show the UI Link to
+// (WASM mode) (RPC mode) matched UI
+
+// There are three options in the end:
+// - Show the UI (WASM mode)
+// - Show the UI (RPC mode)
+// - Redirect to a matched version of the UI
// Try to connect to the external Trace Processor HTTP RPC accelerator (if
// available, often it isn't). If connected it will populate the
@@ -83,73 +156,184 @@
export async function CheckHttpRpcConnection(): Promise<void> {
const state = await HttpRpcEngine.checkConnection();
publishHttpRpcState(state);
- if (!state.connected) return;
+ if (!state.connected) {
+ // No RPC = exit immediately to the WASM UI.
+ return;
+ }
const tpStatus = assertExists(state.status);
- if (tpStatus.apiVersion < CURRENT_API_VERSION) {
- await showDialogTraceProcessorTooOld(tpStatus);
- if (!forceUseOldVersion) return;
+ function forceWasm() {
+ globals.dispatch(Actions.setNewEngineMode({mode: 'FORCE_BUILTIN_WASM'}));
}
+ // Check short version:
+ if (tpStatus.versionCode !== '' && tpStatus.versionCode !== VERSION) {
+ const url = await Router.isVersionAvailable(tpStatus.versionCode);
+ if (url !== undefined) {
+ // If matched UI available show a dialog asking the user to
+ // switch.
+ const result = await showDialogVersionMismatch(tpStatus, url);
+ switch (result) {
+ case MismatchedVersionDialog.Dismissed:
+ case MismatchedVersionDialog.UseMatchingUi:
+ Router.navigateToVersion(tpStatus.versionCode);
+ return;
+ case MismatchedVersionDialog.UseMismatchedRpc:
+ break;
+ case MismatchedVersionDialog.UseWasm:
+ forceWasm();
+ return;
+ default:
+ const x: never = result;
+ throw new Error(`Unsupported result ${x}`);
+ }
+ }
+ }
+
+ // Check the RPC version:
+ if (tpStatus.apiVersion < CURRENT_API_VERSION) {
+ const result = await showDialogIncompatibleRPC(tpStatus);
+ switch (result) {
+ case IncompatibleRpcDialogResult.Dismissed:
+ case IncompatibleRpcDialogResult.UseWasm:
+ forceWasm();
+ return;
+ case IncompatibleRpcDialogResult.UseIncompatibleRpc:
+ break;
+ default:
+ const x: never = result;
+ throw new Error(`Unsupported result ${x}`);
+ }
+ }
+
+ // Check if pre-loaded:
if (tpStatus.loadedTraceName) {
// If a trace is already loaded in the trace processor (e.g., the user
// launched trace_processor_shell -D trace_file.pftrace), prompt the user to
// initialize the UI with the already-loaded trace.
- return showDialogToUsePreloadedTrace(tpStatus);
+ const result = await showDialogToUsePreloadedTrace(tpStatus);
+ switch (result) {
+ case PreloadedDialogResult.Dismissed:
+ case PreloadedDialogResult.UseRpcWithPreloadedTrace:
+ globals.dispatch(Actions.openTraceFromHttpRpc({}));
+ return;
+ case PreloadedDialogResult.UseRpc:
+ // Resetting state is the default.
+ return;
+ case PreloadedDialogResult.UseWasm:
+ forceWasm();
+ return;
+ default:
+ const x: never = result;
+ throw new Error(`Unsupported result ${x}`);
+ }
}
}
-async function showDialogTraceProcessorTooOld(tpStatus: StatusResult) {
- return showModal({
- title: 'Your Trace Processor binary is outdated',
- content:
- m('.modal-pre',
- MSG_TOO_OLD().replace('$tpVersion', tpStatus.humanReadableVersion)
- .replace('$tpApi', `${tpStatus.apiVersion}`)),
+enum MismatchedVersionDialog {
+ UseMatchingUi = 'useMatchingUi',
+ UseWasm = 'useWasm',
+ UseMismatchedRpc = 'useMismatchedRpc',
+ Dismissed = 'dismissed',
+}
+
+async function showDialogVersionMismatch(tpStatus: StatusResult,
+ url: string):
+ Promise<MismatchedVersionDialog> {
+ let result = MismatchedVersionDialog.Dismissed;
+ await showModal({
+ title: 'Version mismatch',
+ content: m('.modal-pre', getVersionMismatchMessage(tpStatus)),
+ buttons: [
+ {
+ primary: true,
+ text: `Open ${url}`,
+ action: () => {
+ result = MismatchedVersionDialog.UseMatchingUi;
+ },
+ },
+ {
+ text: 'Use builtin Wasm',
+ action: () => {
+ result = MismatchedVersionDialog.UseWasm;
+ },
+ },
+ {
+ text: 'Use mismatched version regardless (might crash)',
+ action: () => {
+ result = MismatchedVersionDialog.UseMismatchedRpc;
+ },
+ },
+ ],
+ });
+ return result;
+}
+
+enum IncompatibleRpcDialogResult {
+ UseWasm = 'useWasm',
+ UseIncompatibleRpc = 'useIncompatibleRpc',
+ Dismissed = 'dismissed',
+}
+
+async function showDialogIncompatibleRPC(tpStatus: StatusResult):
+ Promise<IncompatibleRpcDialogResult> {
+ let result = IncompatibleRpcDialogResult.Dismissed;
+ await showModal({
+ title: 'Incompatible RPC version',
+ content: m('.modal-pre', getIncompatibleRpcMessage(tpStatus)),
buttons: [
{
text: 'Use builtin Wasm',
primary: true,
action: () => {
- globals.dispatch(
- Actions.setNewEngineMode({mode: 'FORCE_BUILTIN_WASM'}));
+ result = IncompatibleRpcDialogResult.UseWasm;
},
},
{
- text: 'Use old version regardless (might crash)',
- primary: false,
+ text: 'Use old version regardless (will crash)',
action: () => {
- forceUseOldVersion = true;
+ result = IncompatibleRpcDialogResult.UseIncompatibleRpc;
},
},
],
});
+ return result;
}
-async function showDialogToUsePreloadedTrace(tpStatus: StatusResult) {
- return showModal({
- title: 'Use Trace Processor Native Acceleration?',
- content:
- m('.modal-pre',
- PROMPT().replace('$loadedTraceName', tpStatus.loadedTraceName)),
+enum PreloadedDialogResult {
+ UseRpcWithPreloadedTrace = 'useRpcWithPreloadedTrace',
+ UseRpc = 'useRpc',
+ UseWasm = 'useWasm',
+ Dismissed = 'dismissed',
+}
+
+async function showDialogToUsePreloadedTrace(tpStatus: StatusResult):
+ Promise<PreloadedDialogResult> {
+ let result = PreloadedDialogResult.Dismissed;
+ await showModal({
+ title: 'Use trace processor native acceleration?',
+ content: m('.modal-pre', getPromptMessage(tpStatus)),
buttons: [
{
text: 'YES, use loaded trace',
primary: true,
action: () => {
- globals.dispatch(Actions.openTraceFromHttpRpc({}));
+ result = PreloadedDialogResult.UseRpcWithPreloadedTrace;
},
},
{
text: 'YES, but reset state',
+ action: () => {
+ result = PreloadedDialogResult.UseRpc;
+ },
},
{
- text: 'NO, Use builtin Wasm',
+ text: 'NO, Use builtin WASM',
action: () => {
- globals.dispatch(
- Actions.setNewEngineMode({mode: 'FORCE_BUILTIN_WASM'}));
+ result = PreloadedDialogResult.UseWasm;
},
},
],
});
+ return result;
}
diff --git a/ui/src/frontend/slice_args.ts b/ui/src/frontend/slice_args.ts
index 341fbe1..4921dd0 100644
--- a/ui/src/frontend/slice_args.ts
+++ b/ui/src/frontend/slice_args.ts
@@ -36,6 +36,7 @@
import {Arg} from './sql/args';
import {addSqlTableTab} from './sql_table/tab';
import {SqlTables} from './sql_table/well_known_tables';
+import {assertExists} from '../base/logging';
// Renders slice arguments (key/value pairs) as a subtree.
export function renderArguments(engine: EngineProxy, args: Arg[]): m.Children {
@@ -158,8 +159,8 @@
const it = result.iter({'trackId': NUM, 'maxDepth': NUM});
const addedTrackKeys: string[] = [];
for (; it.valid(); it.next()) {
- const track =
- globals.state.tracks[globals.state.trackKeyByTrackId[it.trackId]];
+ const trackKey = globals.trackManager.trackKeyByTrackId.get(it.trackId);
+ const track = globals.state.tracks[assertExists(trackKey)];
const utid = (track.trackSortKey as {utid?: number}).utid;
const key = uuidv4();
addedTrackKeys.push(key);
diff --git a/ui/src/frontend/sql/slice.ts b/ui/src/frontend/sql/slice.ts
index 9f0f48d..5418d6a 100644
--- a/ui/src/frontend/sql/slice.ts
+++ b/ui/src/frontend/sql/slice.ts
@@ -20,13 +20,7 @@
import {exists} from '../../base/utils';
import {Actions} from '../../common/actions';
import {EngineProxy} from '../../trace_processor/engine';
-import {
- LONG,
- LONG_NULL,
- NUM,
- STR,
- STR_NULL,
-} from '../../trace_processor/query_result';
+import {LONG, LONG_NULL, NUM, NUM_NULL, STR, STR_NULL} from '../../trace_processor/query_result';
import {Anchor} from '../../widgets/anchor';
import {globals} from '../globals';
import {focusHorizontalRange, verticalScrollToTrack} from '../scroll_helper';
@@ -49,13 +43,16 @@
import {Arg, getArgs} from './args';
+// Basic information about a slice.
export interface SliceDetails {
id: SliceSqlId;
name: string;
ts: time;
absTime?: string;
dur: duration;
+ parentId?: SliceSqlId;
trackId: number;
+ depth: number;
thread?: ThreadInfo;
process?: ProcessInfo;
threadTs?: time;
@@ -106,7 +103,7 @@
return result;
}
-async function getSliceFromConstraints(
+export async function getSliceFromConstraints(
engine: EngineProxy, constraints: SQLConstraints): Promise<SliceDetails[]> {
const query = await engine.query(`
SELECT
@@ -115,6 +112,8 @@
ts,
dur,
track_id as trackId,
+ depth,
+ parent_id as parentId,
thread_dur as threadDur,
thread_ts as threadTs,
category,
@@ -128,6 +127,8 @@
ts: LONG,
dur: LONG,
trackId: NUM,
+ depth: NUM,
+ parentId: NUM_NULL,
threadDur: LONG_NULL,
threadTs: LONG_NULL,
category: STR_NULL,
@@ -151,6 +152,8 @@
ts: Time.fromRaw(it.ts),
dur: it.dur,
trackId: it.trackId,
+ depth: it.depth,
+ parentId: asSliceSqlId(it.parentId ?? undefined),
thread,
process,
threadDur: it.threadDur ?? undefined,
@@ -198,8 +201,8 @@
{
icon: Icons.UpdateSelection,
onclick: () => {
- const trackKey =
- globals.state.trackKeyByTrackId[vnode.attrs.sqlTrackId];
+ const trackKeyByTrackId = globals.trackManager.trackKeyByTrackId;
+ const trackKey = trackKeyByTrackId.get(vnode.attrs.sqlTrackId);
if (trackKey === undefined) return;
verticalScrollToTrack(trackKey, true);
// Clamp duration to 1 - i.e. for instant events
@@ -225,3 +228,46 @@
sqlTrackId: slice.trackId,
});
}
+
+// A slice tree node, combining the information about the given slice with
+// information about its descendants.
+export interface SliceTreeNode extends SliceDetails {
+ children: SliceTreeNode[];
+ parent?: SliceTreeNode;
+}
+
+// Get all descendants for a given slice in a tree form.
+export async function getDescendantSliceTree(
+ engine: EngineProxy, id: SliceSqlId): Promise<SliceTreeNode|undefined> {
+ const slice = await getSlice(engine, id);
+ if (slice === undefined) {
+ return undefined;
+ }
+ const descendants = await getSliceFromConstraints(engine, {
+ filters: [
+ `track_id=${slice.trackId}`,
+ `depth >= ${slice.depth}`,
+ `ts >= ${slice.ts}`,
+ // TODO(altimin): consider making `dur` undefined here instead of -1.
+ slice.dur >= 0 ? `ts <= (${slice.ts} + ${slice.dur})` : undefined,
+ ],
+ orderBy: ['ts', 'depth'],
+ });
+ const slices: {[key: SliceSqlId]: SliceTreeNode} =
+ Object.fromEntries(descendants.map(
+ (slice) =>
+ [slice.id,
+ {
+ children: [],
+ ...slice,
+ },
+ ]));
+ for (const [_, slice] of Object.entries(slices)) {
+ if (slice.parentId !== undefined) {
+ const parent = slices[slice.parentId];
+ slice.parent = parent;
+ parent.children.push(slice);
+ }
+ }
+ return slices[id];
+}
diff --git a/ui/src/frontend/tab_panel.ts b/ui/src/frontend/tab_panel.ts
index 130a494..dff5daf 100644
--- a/ui/src/frontend/tab_panel.ts
+++ b/ui/src/frontend/tab_panel.ts
@@ -44,13 +44,10 @@
const resolvedTabs = tabMan.resolveTabs(tabList);
const tabs = resolvedTabs.map(({uri, tab: tabDesc}): TabWithContent => {
if (tabDesc) {
- const titleStr = tabDesc.content.getTitle();
return {
key: uri,
hasCloseButton: true,
- title: (tabDesc.content.hasContent?.() ?? true) ?
- titleStr :
- m('.pf-nocontent', titleStr),
+ title: tabDesc.content.getTitle(),
content: tabDesc.content.render(),
};
} else {
@@ -140,10 +137,8 @@
};
}
- const detailsPanels = globals.tabManager.detailsPanels;
-
// Get the first "truthy" details panel
- const panel = detailsPanels
+ const panel = globals.tabManager.detailsPanels
.map((dp) => {
return {
content: dp.render(cs),
diff --git a/ui/src/frontend/widgets/treetable.ts b/ui/src/frontend/widgets/treetable.ts
new file mode 100644
index 0000000..cd581b3
--- /dev/null
+++ b/ui/src/frontend/widgets/treetable.ts
@@ -0,0 +1,110 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import m from 'mithril';
+
+import {classNames} from '../../base/classnames';
+import {raf} from '../../core/raf_scheduler';
+
+interface ColumnDescriptor<T> {
+ name: string;
+ getData: (row: T) => string;
+}
+
+export interface TreeTableAttrs<T> {
+ columns: ColumnDescriptor<T>[];
+ getChildren: (row: T) => T[] | undefined;
+ rows: T[];
+}
+
+export class TreeTable<T> implements m.ClassComponent<TreeTableAttrs<T>> {
+ private collapsedPaths = new Set<string>();
+
+ view({attrs}: m.Vnode<TreeTableAttrs<T>, this>): void|m.Children {
+ const {columns, rows} = attrs;
+ const headers = columns.map(({name}) => m('th', name));
+ const renderedRows = this.renderRows(rows, 0, attrs, []);
+ return m(
+ 'table.pf-treetable', m('thead', m('tr', headers)),
+ m('tbody', renderedRows));
+ }
+
+ private renderRows(
+ rows: T[], indentLevel: number, attrs: TreeTableAttrs<T>,
+ path: string[]): m.Children {
+ const {columns, getChildren} = attrs;
+ const renderedRows: m.Children = [];
+ for (const row of rows) {
+ const childRows = getChildren(row);
+ const key = this.keyForRow(row, attrs);
+ const thisPath = path.concat([key]);
+ const hasChildren = childRows && childRows.length > 0;
+ const cols = columns.map(({getData}, index) => {
+ const classes = classNames(
+ hasChildren && 'pf-treetable-node',
+ this.isCollapsed(thisPath) && 'pf-collapsed',
+ );
+ if (index === 0) {
+ const style = {
+ '--indentation-level': indentLevel,
+ };
+ return m(
+ 'td', {style, class: classNames(classes, 'pf-treetable-maincol')},
+ m('.pf-treetable-gutter', {
+ onclick: () => {
+ if (this.isCollapsed(thisPath)) {
+ this.expandPath(thisPath);
+ } else {
+ this.collapsePath(thisPath);
+ }
+ raf.scheduleFullRedraw();
+ },
+ }),
+ getData(row));
+ } else {
+ const style = {
+ '--indentation-level': 0,
+ };
+ return m('td', {style}, getData(row));
+ }
+ });
+ renderedRows.push(m('tr', cols));
+ if (childRows && !this.isCollapsed(thisPath)) {
+ renderedRows.push(
+ this.renderRows(childRows, indentLevel + 1, attrs, thisPath));
+ }
+ }
+ return renderedRows;
+ }
+
+ collapsePath(path: string[]) {
+ const pathStr = path.join('/');
+ this.collapsedPaths.add(pathStr);
+ }
+
+ expandPath(path: string[]) {
+ const pathStr = path.join('/');
+ this.collapsedPaths.delete(pathStr);
+ }
+
+ isCollapsed(path: string[]) {
+ const pathStr = path.join('/');
+ return this.collapsedPaths.has(pathStr);
+ }
+
+ keyForRow(row: T, attrs: TreeTableAttrs<T>): string {
+ const {columns} = attrs;
+ return columns[0].getData(row);
+ }
+}
diff --git a/ui/src/frontend/widgets_page.ts b/ui/src/frontend/widgets_page.ts
index a148856..f6749bd 100644
--- a/ui/src/frontend/widgets_page.ts
+++ b/ui/src/frontend/widgets_page.ts
@@ -30,11 +30,7 @@
import {Icon} from '../widgets/icon';
import {Menu, MenuDivider, MenuItem, PopupMenu2} from '../widgets/menu';
import {showModal} from '../widgets/modal';
-import {
- MultiSelect,
- MultiSelectDiff,
- PopupMultiSelect,
-} from '../widgets/multiselect';
+import {MultiSelect, MultiSelectDiff, PopupMultiSelect} from '../widgets/multiselect';
import {Popup, PopupPosition} from '../widgets/popup';
import {Portal} from '../widgets/portal';
import {FilterableSelect, Select} from '../widgets/select';
@@ -48,6 +44,7 @@
import {createPage} from './pages';
import {PopupMenuButton} from './popup_menu';
import {TableShowcase} from './tables/table_showcase';
+import {TreeTable, TreeTableAttrs} from './widgets/treetable';
const DATA_ENGLISH_LETTER_FREQUENCY = {
table: [
@@ -480,6 +477,72 @@
}
}
+interface File {
+ name: string;
+ size: string;
+ date: string;
+ children?: File[];
+}
+
+const files: File[] = [
+ {
+ name: 'foo',
+ size: '10MB',
+ date: '2023-04-02',
+ },
+ {
+ name: 'bar',
+ size: '123KB',
+ date: '2023-04-08',
+ children: [
+ {
+ name: 'baz',
+ size: '4KB',
+ date: '2023-05-07',
+ },
+ {
+ name: 'qux',
+ size: '18KB',
+ date: '2023-05-28',
+ children: [
+ {
+ name: 'quux',
+ size: '4KB',
+ date: '2023-05-07',
+ },
+ {
+ name: 'corge',
+ size: '18KB',
+ date: '2023-05-28',
+ children: [
+ {
+ name: 'grault',
+ size: '4KB',
+ date: '2023-05-07',
+ },
+ {
+ name: 'garply',
+ size: '18KB',
+ date: '2023-05-28',
+ },
+ {
+ name: 'waldo',
+ size: '87KB',
+ date: '2023-05-02',
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ {
+ name: 'fred',
+ size: '8KB',
+ date: '2022-12-27',
+ },
+];
+
export const WidgetsPage = createPage({
view() {
return m(
@@ -534,8 +597,7 @@
m(WidgetShowcase, {
label: 'Select',
renderWidget: (opts) =>
- m(Select,
- opts,
+ m(Select, opts,
[
m('option', {value: 'foo', label: 'Foo'}),
m('option', {value: 'bar', label: 'Bar'}),
@@ -547,25 +609,24 @@
}),
m(WidgetShowcase, {
label: 'Filterable Select',
- renderWidget: () =>
- m(FilterableSelect, {
- values: ['foo', 'bar', 'baz'],
- onSelected: () => {},
- }),
+ renderWidget: () => m(FilterableSelect, {
+ values: ['foo', 'bar', 'baz'],
+ onSelected: () => {},
+ }),
}),
- m(WidgetShowcase, {
- label: 'Empty State',
- renderWidget: ({header, content}) =>
- m(EmptyState,
- {
+ m(WidgetShowcase,
+ {
+ label: 'Empty State',
+ renderWidget: ({header, content}) =>
+ m(EmptyState, {
title: header && 'No search results found...',
},
content && m(Button, {label: 'Try again'})),
- initialOpts: {
- header: true,
- content: true,
- },
- }),
+ initialOpts: {
+ header: true,
+ content: true,
+ },
+ }),
m(WidgetShowcase, {
label: 'Anchor',
renderWidget: ({icon}) => m(
@@ -581,11 +642,12 @@
icon: true,
},
}),
- m(WidgetShowcase,
- {
- label: 'Table',
- renderWidget: () => m(TableShowcase), initialOpts: {}, wide: true,
- }),
+ m(WidgetShowcase, {
+ label: 'Table',
+ renderWidget: () => m(TableShowcase),
+ initialOpts: {},
+ wide: true,
+ }),
m(WidgetShowcase, {
label: 'Portal',
description: `A portal is a div rendered out of normal flow
@@ -599,7 +661,8 @@
}),
m(WidgetShowcase, {
label: 'Popup',
- description: `A popup is a nicely styled portal element whose position is
+ description:
+ `A popup is a nicely styled portal element whose position is
dynamically updated to appear to float alongside a specific element on
the page, even as the element is moved and scrolled around.`,
renderWidget: (opts) => m(
@@ -621,7 +684,8 @@
}),
m(WidgetShowcase, {
label: 'Controlled Popup',
- description: `The open/close state of a controlled popup is passed in via
+ description:
+ `The open/close state of a controlled popup is passed in via
the 'isOpen' attribute. This means we can get open or close the popup
from wherever we like. E.g. from a button inside the popup.
Keeping this state external also means we can modify other parts of the
@@ -803,85 +867,91 @@
}),
m(WidgetShowcase, {
label: 'Tree',
- renderWidget: (opts) => m(
- Tree,
- opts,
- m(TreeNode, {left: 'Name', right: 'my_event', icon: 'badge'}),
- m(TreeNode, {left: 'CPU', right: '2', icon: 'memory'}),
- m(TreeNode,
- {left: 'Start time', right: '1s 435ms', icon: 'schedule'}),
- m(TreeNode, {left: 'Duration', right: '86ms', icon: 'timer'}),
- m(TreeNode, {
- left: 'SQL',
- right: m(
- PopupMenu2,
- {
- popupPosition: PopupPosition.RightStart,
- trigger: m(Anchor, {
- icon: Icons.ContextMenu,
- }, 'SELECT * FROM raw WHERE id = 123'),
- },
- m(MenuItem, {
- label: 'Copy SQL Query',
- icon: 'content_copy',
- }),
- m(MenuItem, {
- label: 'Execute Query in new tab',
- icon: 'open_in_new',
- }),
- ),
- }),
- m(TreeNode, {
- icon: 'account_tree',
- left: 'Process',
- right: m(Anchor, {icon: 'open_in_new'}, '/bin/foo[789]'),
- }),
- m(TreeNode, {
- left: 'Thread',
- right: m(Anchor, {icon: 'open_in_new'}, 'my_thread[456]'),
- }),
+ description: `Hierarchical tree with left and right values aligned to
+ a grid.`,
+ renderWidget: (opts) =>
m(
- TreeNode,
- {
- left: 'Args',
- summary: 'foo: string, baz: string, quux: string[4]',
- },
- m(TreeNode, {left: 'foo', right: 'bar'}),
- m(TreeNode, {left: 'baz', right: 'qux'}),
+ Tree,
+ opts,
+ m(TreeNode, {left: 'Name', right: 'my_event', icon: 'badge'}),
+ m(TreeNode, {left: 'CPU', right: '2', icon: 'memory'}),
+ m(TreeNode,
+ {left: 'Start time', right: '1s 435ms', icon: 'schedule'}),
+ m(TreeNode, {left: 'Duration', right: '86ms', icon: 'timer'}),
+ m(TreeNode,
+ {
+ left: 'SQL',
+ right:
+ m(
+ PopupMenu2,
+ {
+ popupPosition: PopupPosition.RightStart,
+ trigger:
+ m(Anchor, {
+ icon: Icons.ContextMenu,
+ },
+ 'SELECT * FROM raw WHERE id = 123'),
+ },
+ m(MenuItem, {
+ label: 'Copy SQL Query',
+ icon: 'content_copy',
+ }),
+ m(MenuItem, {
+ label: 'Execute Query in new tab',
+ icon: 'open_in_new',
+ }),
+ ),
+ }),
+ m(TreeNode, {
+ icon: 'account_tree',
+ left: 'Process',
+ right: m(Anchor, {icon: 'open_in_new'}, '/bin/foo[789]'),
+ }),
+ m(TreeNode, {
+ left: 'Thread',
+ right: m(Anchor, {icon: 'open_in_new'}, 'my_thread[456]'),
+ }),
m(
TreeNode,
- {left: 'quux', summary: 'string[4]'},
- m(TreeNode, {left: '[0]', right: 'corge'}),
- m(TreeNode, {left: '[1]', right: 'grault'}),
- m(TreeNode, {left: '[2]', right: 'garply'}),
- m(TreeNode, {left: '[3]', right: 'waldo'}),
+ {
+ left: 'Args',
+ summary: 'foo: string, baz: string, quux: string[4]',
+ },
+ m(TreeNode, {left: 'foo', right: 'bar'}),
+ m(TreeNode, {left: 'baz', right: 'qux'}),
+ m(
+ TreeNode,
+ {left: 'quux', summary: 'string[4]'},
+ m(TreeNode, {left: '[0]', right: 'corge'}),
+ m(TreeNode, {left: '[1]', right: 'grault'}),
+ m(TreeNode, {left: '[2]', right: 'garply'}),
+ m(TreeNode, {left: '[3]', right: 'waldo'}),
+ ),
),
+ m(LazyTreeNode, {
+ left: 'Lazy',
+ icon: 'bedtime',
+ fetchData: async () => {
+ await new Promise((r) => setTimeout(r, 1000));
+ return () => m(TreeNode, {left: 'foo'});
+ },
+ }),
+ m(LazyTreeNode, {
+ left: 'Dynamic',
+ unloadOnCollapse: true,
+ icon: 'bedtime',
+ fetchData: async () => {
+ await new Promise((r) => setTimeout(r, 1000));
+ return () => m(TreeNode, {left: 'foo'});
+ },
+ }),
),
- m(LazyTreeNode, {
- left: 'Lazy',
- icon: 'bedtime',
- fetchData: async () => {
- await new Promise((r) => setTimeout(r, 1000));
- return () => m(TreeNode, {left: 'foo'});
- },
- }),
- m(LazyTreeNode, {
- left: 'Dynamic',
- unloadOnCollapse: true,
- icon: 'bedtime',
- fetchData: async () => {
- await new Promise((r) => setTimeout(r, 1000));
- return () => m(TreeNode, {left: 'foo'});
- },
- }),
- ),
wide: true,
}),
- m(
- WidgetShowcase, {
- label: 'Form',
- renderWidget: () => renderForm('form'),
- }),
+ m(WidgetShowcase, {
+ label: 'Form',
+ renderWidget: () => renderForm('form'),
+ }),
m(WidgetShowcase, {
label: 'Nested Popups',
renderWidget: () => m(
@@ -889,7 +959,8 @@
{
trigger: m(Button, {label: 'Open the popup'}),
},
- m(PopupMenu2,
+ m(
+ PopupMenu2,
{
trigger: m(Button, {label: 'Select an option'}),
},
@@ -902,20 +973,19 @@
}),
),
}),
- m(
- WidgetShowcase, {
- label: 'Callout',
- renderWidget: () => m(
- Callout,
- {
- icon: 'info',
- },
- 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. ' +
- 'Nulla rhoncus tempor neque, sed malesuada eros dapibus vel. ' +
- 'Aliquam in ligula vitae tortor porttitor laoreet iaculis ' +
- 'finibus est.',
- ),
- }),
+ m(WidgetShowcase, {
+ label: 'Callout',
+ renderWidget: () => m(
+ Callout,
+ {
+ icon: 'info',
+ },
+ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. ' +
+ 'Nulla rhoncus tempor neque, sed malesuada eros dapibus vel. ' +
+ 'Aliquam in ligula vitae tortor porttitor laoreet iaculis ' +
+ 'finibus est.',
+ ),
+ }),
m(WidgetShowcase, {
label: 'Editor',
renderWidget: () => m(Editor),
@@ -938,94 +1008,108 @@
},
}),
- m(
- WidgetShowcase, {
- label: 'Form within PopupMenu2',
- description: `A form placed inside a popup menu works just fine,
+ m(WidgetShowcase, {
+ label: 'Form within PopupMenu2',
+ description: `A form placed inside a popup menu works just fine,
and the cancel/submit buttons also dismiss the popup. A bit more
margin is added around it too, which improves the look and feel.`,
- renderWidget: () => m(
- PopupMenu2,
+ renderWidget: () => m(
+ PopupMenu2,
+ {
+ trigger: m(Button, {label: 'Popup!'}),
+ },
+ m(
+ MenuItem,
{
- trigger: m(Button, {label: 'Popup!'}),
+ label: 'Open form...',
},
- m(MenuItem,
- {
- label: 'Open form...',
- },
- renderForm('popup-form'),
- ),
+ renderForm('popup-form'),
),
- }),
- m(
- WidgetShowcase, {
- label: 'Hotkey',
- renderWidget: (opts) => {
- if (opts.platform === 'auto') {
- return m(HotkeyGlyphs, {hotkey: opts.hotkey as Hotkey});
- } else {
- const platform = opts.platform as Platform;
- return m(HotkeyGlyphs, {
- hotkey: opts.hotkey as Hotkey,
- spoof: platform,
- });
- }
- },
- initialOpts: {
- hotkey: 'Mod+Shift+P',
- platform: new EnumOption('auto', ['auto', 'Mac', 'PC']),
- },
- }),
- m(
- WidgetShowcase, {
- label: 'Text Paragraph',
- description: `A basic formatted text paragraph with wrapping. If
+ ),
+ }),
+ m(WidgetShowcase, {
+ label: 'Hotkey',
+ renderWidget: (opts) => {
+ if (opts.platform === 'auto') {
+ return m(HotkeyGlyphs, {hotkey: opts.hotkey as Hotkey});
+ } else {
+ const platform = opts.platform as Platform;
+ return m(HotkeyGlyphs, {
+ hotkey: opts.hotkey as Hotkey,
+ spoof: platform,
+ });
+ }
+ },
+ initialOpts: {
+ hotkey: 'Mod+Shift+P',
+ platform: new EnumOption('auto', ['auto', 'Mac', 'PC']),
+ },
+ }),
+ m(WidgetShowcase, {
+ label: 'Text Paragraph',
+ description: `A basic formatted text paragraph with wrapping. If
it is desirable to preserve the original text format/line breaks,
set the compressSpace attribute to false.`,
- renderWidget: (opts) => {
- return m(TextParagraph, {
+ renderWidget: (opts) => {
+ return m(TextParagraph, {
+ text: `Lorem ipsum dolor sit amet, consectetur adipiscing
+ elit. Nulla rhoncus tempor neque, sed malesuada eros
+ dapibus vel. Aliquam in ligula vitae tortor porttitor
+ laoreet iaculis finibus est.`,
+ compressSpace: opts.compressSpace,
+ });
+ },
+ initialOpts: {
+ compressSpace: true,
+ },
+ }),
+ m(WidgetShowcase, {
+ label: 'Multi Paragraph Text',
+ description: `A wrapper for multiple paragraph widgets.`,
+ renderWidget: () => {
+ return m(
+ MultiParagraphText,
+ m(TextParagraph, {
text: `Lorem ipsum dolor sit amet, consectetur adipiscing
elit. Nulla rhoncus tempor neque, sed malesuada eros
dapibus vel. Aliquam in ligula vitae tortor porttitor
laoreet iaculis finibus est.`,
- compressSpace: opts.compressSpace,
- });
- },
- initialOpts: {
- compressSpace: true,
- },
- }),
- m(
- WidgetShowcase, {
- label: 'Multi Paragraph Text',
- description: `A wrapper for multiple paragraph widgets.`,
- renderWidget: () => {
- return m(MultiParagraphText,
- m(TextParagraph, {
- text: `Lorem ipsum dolor sit amet, consectetur adipiscing
- elit. Nulla rhoncus tempor neque, sed malesuada eros
- dapibus vel. Aliquam in ligula vitae tortor porttitor
- laoreet iaculis finibus est.`,
- compressSpace: true,
- }), m(TextParagraph, {
- text: `Sed ut perspiciatis unde omnis iste natus error sit
+ compressSpace: true,
+ }),
+ m(TextParagraph, {
+ text: `Sed ut perspiciatis unde omnis iste natus error sit
voluptatem accusantium doloremque laudantium, totam rem
aperiam, eaque ipsa quae ab illo inventore veritatis et
quasi architecto beatae vitae dicta sunt explicabo.
Nemo enim ipsam voluptatem quia voluptas sit aspernatur
aut odit aut fugit, sed quia consequuntur magni dolores
eos qui ratione voluptatem sequi nesciunt.`,
- compressSpace: true,
- }),
- );
- },
- }),
- m(
- WidgetShowcase, {
- label: 'Modal',
- description: `A helper for modal dialog.`,
- renderWidget: () => m(ModalShowcase),
- }),
+ compressSpace: true,
+ }),
+ );
+ },
+ }),
+ m(WidgetShowcase, {
+ label: 'Modal',
+ description: `A helper for modal dialog.`,
+ renderWidget: () => m(ModalShowcase),
+ }),
+ m(WidgetShowcase, {
+ label: 'TreeTable',
+ description: `Hierarchical tree with multiple columns`,
+ renderWidget: () => {
+ const attrs: TreeTableAttrs<File> = {
+ rows: files,
+ getChildren: (file) => file.children,
+ columns: [
+ {name: 'Name', getData: (file) => file.name},
+ {name: 'Size', getData: (file) => file.size},
+ {name: 'Date', getData: (file) => file.date},
+ ],
+ };
+ return m(TreeTable<File>, attrs);
+ },
+ }),
);
},
});
diff --git a/ui/src/plugins/dev.perfetto.AndroidLongBatteryTracing/index.ts b/ui/src/plugins/dev.perfetto.AndroidLongBatteryTracing/index.ts
index d3216df..180ac5f 100644
--- a/ui/src/plugins/dev.perfetto.AndroidLongBatteryTracing/index.ts
+++ b/ui/src/plugins/dev.perfetto.AndroidLongBatteryTracing/index.ts
@@ -249,6 +249,37 @@
SELECT ts, dur, slot_name || "-" || attrib_name || "=" || name AS name
FROM Stage3`;
+const MODEM_CELL_RESELECTION = `
+ with base as (
+ select
+ ts,
+ s.name as raw_ril,
+ ifnull(str_split(str_split(s.name, 'CellIdentityLte{', 1), ', operatorNames', 0),
+ str_split(str_split(s.name, 'CellIdentityNr{', 1), ', operatorNames', 0)) as cell_id
+ from track t join slice s on t.id = s.track_id
+ where t.name = 'RIL' and s.name like '%DATA_REGISTRATION_STATE%'
+ ),
+ base2 as (
+ select
+ ts,
+ raw_ril,
+ case
+ when cell_id like '%earfcn%' then 'LTE ' || cell_id
+ when cell_id like '%nrarfcn%' then 'NR ' || cell_id
+ when cell_id is null then 'Unknown'
+ else cell_id
+ end as cell_id
+ from base
+ ),
+ base3 as (
+ select ts, cell_id , lag(cell_id) over (order by ts) as lag_cell_id, raw_ril
+ from base2
+ )
+ select ts, 0 as dur, cell_id as name, raw_ril
+ from base3
+ where cell_id != lag_cell_id
+ order by ts`;
+
const SUSPEND_RESUME = `
SELECT
ts,
@@ -956,6 +987,92 @@
const BT_HAL_CRASHES_COLUMNS = ['metric_id', 'error_code', 'vendor_error_code'];
+const BT_BYTES = `
+ with step1 as (
+ select
+ ts,
+ EXTRACT_ARG(arg_set_id, 'bluetooth_bytes_transfer.uid') as uid,
+ EXTRACT_ARG(arg_set_id, 'bluetooth_bytes_transfer.tx_bytes') as tx_bytes,
+ EXTRACT_ARG(arg_set_id, 'bluetooth_bytes_transfer.rx_bytes') as rx_bytes
+ from track t join slice s on t.id = s.track_id
+ where t.name = 'Statsd Atoms'
+ and s.name = 'bluetooth_bytes_transfer'
+ ),
+ step2 as (
+ select
+ ts,
+ lead(ts) over (partition by uid order by ts) - ts as dur,
+ uid,
+ lead(tx_bytes) over (partition by uid order by ts) - tx_bytes as tx_bytes,
+ lead(rx_bytes) over (partition by uid order by ts) - rx_bytes as rx_bytes
+ from step1
+ ),
+ step3 as (
+ select
+ ts,
+ dur,
+ uid % 100000 as uid,
+ sum(tx_bytes) as tx_bytes,
+ sum(rx_bytes) as rx_bytes
+ from step2
+ where tx_bytes >=0 and rx_bytes >=0
+ group by 1,2,3
+ having tx_bytes > 0 or rx_bytes > 0
+ ),
+ app_package_list as (
+ select
+ uid,
+ group_concat(package_name) as package_name
+ from package_list
+ where uid >= 10000
+ group by 1
+ )
+ select
+ ts,
+ dur,
+ case
+ when pl.package_name is null then 'uid=' || uid
+ else pl.package_name
+ end || ' TX ' || tx_bytes || ' bytes / RX ' || rx_bytes || ' bytes' as name
+ from step3 left join app_package_list pl using(uid)
+`;
+
+const BT_ACTIVITY = `
+ with step1 as (
+ select
+ EXTRACT_ARG(arg_set_id, 'bluetooth_activity_info.timestamp_millis') * 1000000 as ts,
+ case EXTRACT_ARG(arg_set_id, 'bluetooth_activity_info.bluetooth_stack_state')
+ when 1 then 'active'
+ when 2 then 'scanning'
+ when 3 then 'idle'
+ else 'invalid'
+ end as bluetooth_stack_state,
+ EXTRACT_ARG(arg_set_id, 'bluetooth_activity_info.controller_idle_time_millis') * 1000000 as controller_idle_dur,
+ EXTRACT_ARG(arg_set_id, 'bluetooth_activity_info.controller_tx_time_millis') * 1000000 as controller_tx_dur,
+ EXTRACT_ARG(arg_set_id, 'bluetooth_activity_info.controller_rx_time_millis') * 1000000 as controller_rx_dur
+ from track t join slice s on t.id = s.track_id
+ where t.name = 'Statsd Atoms'
+ and s.name = 'bluetooth_activity_info'
+ ),
+ step2 as (
+ select
+ ts,
+ lead(ts) over (order by ts) - ts as dur,
+ bluetooth_stack_state,
+ lead(controller_idle_dur) over (order by ts) - controller_idle_dur as controller_idle_dur,
+ lead(controller_tx_dur) over (order by ts) - controller_tx_dur as controller_tx_dur,
+ lead(controller_rx_dur) over (order by ts) - controller_rx_dur as controller_rx_dur
+ from step1
+ )
+ select
+ ts,
+ dur * controller_rx_dur / dur as dur,
+ cast(100.0 * controller_rx_dur / dur as int) || '% RX, ' ||
+ cast(100.0 * controller_tx_dur / dur as int) || '% TX, ' || bluetooth_stack_state as name
+ from step2
+ where controller_rx_dur > 0
+`;
+
class AndroidLongBatteryTracing implements Plugin {
onActivate(_: PluginContext): void {}
@@ -1188,11 +1305,15 @@
rilStrength('LTE', 'rsrp');
rilStrength('LTE', 'rssi');
- rilStrength('NR', 'rssi');
+ rilStrength('NR', 'rsrp');
rilStrength('NR', 'rssi');
this.addSliceTrack(
ctx, 'Modem channel config', MODEM_RIL_CHANNELS, groupName);
+
+ this.addSliceTrack(
+ ctx, 'Modem cell reselection', MODEM_CELL_RESELECTION, groupName,
+ ['raw_ril']);
}
async addKernelWakelocks(ctx: PluginContextTrace, features: Set<string>):
@@ -1317,6 +1438,9 @@
BT_LINK_LEVEL_EVENTS_COLUMNS);
this.addSliceTrack(ctx, 'A2DP Audio', BT_A2DP_AUDIO, groupName);
this.addSliceTrack(
+ ctx, 'Bytes Transferred (L2CAP/RFCOMM)', BT_BYTES, groupName);
+ this.addSliceTrack(ctx, 'Activity info', BT_ACTIVITY, groupName);
+ this.addSliceTrack(
ctx, 'Quality reports', BT_QUALITY_REPORTS, groupName,
BT_QUALITY_REPORTS_COLUMNS);
this.addSliceTrack(
diff --git a/ui/src/plugins/dev.perfetto.AndroidPerfTraceCounters/index.ts b/ui/src/plugins/dev.perfetto.AndroidPerfTraceCounters/index.ts
index b9a24cd..eb596c2 100644
--- a/ui/src/plugins/dev.perfetto.AndroidPerfTraceCounters/index.ts
+++ b/ui/src/plugins/dev.perfetto.AndroidPerfTraceCounters/index.ts
@@ -19,11 +19,23 @@
PluginDescriptor,
} from '../../public';
import {addDebugSliceTrack} from '../../public';
+import {runQuery} from '../../common/queries';
+
+const PERF_TRACE_COUNTERS_PRECONDITION = `
+ SELECT
+ str_value
+ FROM metadata
+ WHERE
+ name = 'trace_config_pbtxt'
+ AND str_value GLOB '*ftrace_events: "perf_trace_counters/sched_switch_with_ctrs"*'
+`;
class AndroidPerfTraceCounters implements Plugin {
onActivate(_: PluginContext): void {}
async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
+ const resp = await runQuery(PERF_TRACE_COUNTERS_PRECONDITION, ctx.engine);
+ if (resp.totalRowCount === 0) return;
ctx.registerCommand({
id: 'dev.perfetto.AndroidPerfTraceCounters#ThreadRuntimeIPC',
name: 'Add a track to show a thread runtime ipc',
@@ -33,56 +45,56 @@
if (tid === null) return;
}
const sqlPrefix = `
-WITH
- sched_switch_ipc AS (
- SELECT
- ts,
- EXTRACT_ARG(arg_set_id, 'prev_pid') AS tid,
- EXTRACT_ARG(arg_set_id, 'prev_comm') AS thread_name,
- EXTRACT_ARG(arg_set_id, 'inst') / (EXTRACT_ARG(arg_set_id, 'cyc') * 1.0) AS ipc,
- EXTRACT_ARG(arg_set_id, 'inst') AS instruction,
- EXTRACT_ARG(arg_set_id, 'cyc') AS cycle,
- EXTRACT_ARG(arg_set_id, 'stallbm') AS stall_backend_mem,
- EXTRACT_ARG(arg_set_id, 'l3dm') AS l3_cache_miss
- FROM ftrace_event
- WHERE name = 'sched_switch_with_ctrs' AND tid = ${tid}
- ),
- target_thread_sched_slice AS (
- SELECT s.*, t.tid, t.name FROM sched s LEFT JOIN thread t USING (utid) WHERE t.tid = ${
- tid}
- ),
- target_thread_ipc_slice AS (
- SELECT
- (
- SELECT
- ts
- FROM target_thread_sched_slice ts
- WHERE ts.tid = ssi.tid AND ts.ts < ssi.ts
- ORDER BY ts.ts DESC
- LIMIT 1
- ) AS ts,
- (
- SELECT
- dur
- FROM target_thread_sched_slice ts
- WHERE ts.tid = ssi.tid AND ts.ts < ssi.ts
- ORDER BY ts.ts DESC
- LIMIT 1
- ) AS dur,
- ssi.ipc,
- ssi.instruction,
- ssi.cycle,
- ssi.stall_backend_mem,
- ssi.l3_cache_miss
- FROM sched_switch_ipc ssi
- )
-`;
+ WITH
+ sched_switch_ipc AS (
+ SELECT
+ ts,
+ EXTRACT_ARG(arg_set_id, 'prev_pid') AS tid,
+ EXTRACT_ARG(arg_set_id, 'prev_comm') AS thread_name,
+ EXTRACT_ARG(arg_set_id, 'inst') / (EXTRACT_ARG(arg_set_id, 'cyc') * 1.0) AS ipc,
+ EXTRACT_ARG(arg_set_id, 'inst') AS instruction,
+ EXTRACT_ARG(arg_set_id, 'cyc') AS cycle,
+ EXTRACT_ARG(arg_set_id, 'stallbm') AS stall_backend_mem,
+ EXTRACT_ARG(arg_set_id, 'l3dm') AS l3_cache_miss
+ FROM ftrace_event
+ WHERE name = 'sched_switch_with_ctrs' AND tid = ${tid}
+ ),
+ target_thread_sched_slice AS (
+ SELECT s.*, t.tid, t.name FROM sched s LEFT JOIN thread t USING (utid)
+ WHERE t.tid = ${tid}
+ ),
+ target_thread_ipc_slice AS (
+ SELECT
+ (
+ SELECT
+ ts
+ FROM target_thread_sched_slice ts
+ WHERE ts.tid = ssi.tid AND ts.ts < ssi.ts
+ ORDER BY ts.ts DESC
+ LIMIT 1
+ ) AS ts,
+ (
+ SELECT
+ dur
+ FROM target_thread_sched_slice ts
+ WHERE ts.tid = ssi.tid AND ts.ts < ssi.ts
+ ORDER BY ts.ts DESC
+ LIMIT 1
+ ) AS dur,
+ ssi.ipc,
+ ssi.instruction,
+ ssi.cycle,
+ ssi.stall_backend_mem,
+ ssi.l3_cache_miss
+ FROM sched_switch_ipc ssi
+ )
+ `;
await addDebugSliceTrack(
ctx.engine,
{
sqlSource: sqlPrefix + `
-SELECT * FROM target_thread_ipc_slice WHERE ts IS NOT NULL`,
+ SELECT * FROM target_thread_ipc_slice WHERE ts IS NOT NULL`,
},
'Rutime IPC:' + tid,
{ts: 'ts', dur: 'dur', name: 'ipc'},
@@ -90,15 +102,16 @@
);
ctx.tabs.openQuery(
sqlPrefix + `
-SELECT
- (sum(instruction) * 1.0 / sum(cycle)*1.0) AS avg_ipc,
- sum(dur)/1e6 as total_runtime_ms,
- sum(instruction) AS total_instructions,
- sum(cycle) AS total_cycles,
- sum(stall_backend_mem) as total_stall_backend_mem,
- sum(l3_cache_miss) as total_l3_cache_miss
-FROM target_thread_ipc_slice WHERE ts IS NOT NULL`,
- 'target thread ipc statistic');
+ SELECT
+ (sum(instruction) * 1.0 / sum(cycle)*1.0) AS avg_ipc,
+ sum(dur)/1e6 as total_runtime_ms,
+ sum(instruction) AS total_instructions,
+ sum(cycle) AS total_cycles,
+ sum(stall_backend_mem) as total_stall_backend_mem,
+ sum(l3_cache_miss) as total_l3_cache_miss
+ FROM target_thread_ipc_slice WHERE ts IS NOT NULL`,
+ 'target thread ipc statistic',
+ );
},
});
}
diff --git a/ui/src/public/index.ts b/ui/src/public/index.ts
index 7d9e1bd..7ea2bbe 100644
--- a/ui/src/public/index.ts
+++ b/ui/src/public/index.ts
@@ -312,7 +312,6 @@
}
export interface Tab {
- hasContent?(): boolean;
render(): m.Children;
getTitle(): string;
}
diff --git a/ui/src/tracks/chrome_scroll_jank/event_latency_details_panel.ts b/ui/src/tracks/chrome_scroll_jank/event_latency_details_panel.ts
index 51de4b7..2829f57 100644
--- a/ui/src/tracks/chrome_scroll_jank/event_latency_details_panel.ts
+++ b/ui/src/tracks/chrome_scroll_jank/event_latency_details_panel.ts
@@ -14,26 +14,16 @@
import m from 'mithril';
-import {duration, time} from '../../base/time';
+import {Duration, duration, time} from '../../base/time';
import {raf} from '../../core/raf_scheduler';
-import {
- BottomTab,
- bottomTabRegistry,
- NewBottomTabArgs,
-} from '../../frontend/bottom_tab';
-import {
- GenericSliceDetailsTabConfig,
-} from '../../frontend/generic_slice_details_tab';
+import {BottomTab, bottomTabRegistry, NewBottomTabArgs} from '../../frontend/bottom_tab';
+import {GenericSliceDetailsTabConfig} from '../../frontend/generic_slice_details_tab';
import {hasArgs, renderArguments} from '../../frontend/slice_args';
import {renderDetails} from '../../frontend/slice_details';
-import {getSlice, SliceDetails, sliceRef} from '../../frontend/sql/slice';
+import {getDescendantSliceTree, getSlice, getSliceFromConstraints, SliceDetails, sliceRef, SliceTreeNode} from '../../frontend/sql/slice';
import {asSliceSqlId, SliceSqlId} from '../../frontend/sql_types';
-import {
- ColumnDescriptor,
- Table,
- TableData,
- widgetColumn,
-} from '../../frontend/tables/table';
+import {ColumnDescriptor, Table, TableData, widgetColumn} from '../../frontend/tables/table';
+import {TreeTable, TreeTableAttrs} from '../../frontend/widgets/treetable';
import {NUM, STR} from '../../trace_processor/query_result';
import {DetailsShell} from '../../widgets/details_shell';
import {GridLayout, GridLayoutColumn} from '../../widgets/grid_layout';
@@ -41,21 +31,54 @@
import {MultiParagraphText, TextParagraph} from '../../widgets/text_paragraph';
import {Tree, TreeNode} from '../../widgets/tree';
-import {
- EventLatencyCauseThreadTracks,
- EventLatencyStage,
- getCauseLink,
- getEventLatencyCauseTracks,
- getScrollJankCauseStage,
-} from './scroll_jank_cause_link_utils';
+import {EventLatencyCauseThreadTracks, EventLatencyStage, getCauseLink, getEventLatencyCauseTracks, getScrollJankCauseStage} from './scroll_jank_cause_link_utils';
import {ScrollJankCauseMap} from './scroll_jank_cause_map';
-import {
- getScrollJankSlices,
- getSliceForTrack,
- ScrollJankSlice,
-} from './scroll_jank_slice';
+import {getScrollJankSlices, getSliceForTrack, ScrollJankSlice} from './scroll_jank_slice';
import {ScrollJankV3Track} from './scroll_jank_v3_track';
+// Given a node in the slice tree, return a path from root to it.
+function getPath(slice: SliceTreeNode): string[] {
+ const result: string[] = [];
+ let node: SliceTreeNode|undefined = slice;
+ while (node.parent !== undefined) {
+ result.push(node.name);
+ node = node.parent;
+ }
+ return result.reverse();
+}
+
+// Given a slice tree node and a path, find the node following the path from
+// the given slice, or `undefined` if not found.
+function findSliceInTreeByPath(
+ slice: SliceTreeNode|undefined, path: string[]): SliceTreeNode|undefined {
+ if (slice === undefined) {
+ return undefined;
+ }
+ let result = slice;
+ for (const segment of path) {
+ let found = false;
+ for (const child of result.children) {
+ if (child.name === segment) {
+ found = true;
+ result = child;
+ break;
+ }
+ }
+ if (!found) {
+ return undefined;
+ }
+ }
+ return result;
+}
+
+function durationDelta(value: duration, base?: duration): string {
+ if (base === undefined) {
+ return 'NULL';
+ }
+ const delta = value - base;
+ return `${delta > 0 ? '+' : ''}${Duration.humanise(delta)}`;
+}
+
export class EventLatencySliceDetailsPanel extends
BottomTab<GenericSliceDetailsTabConfig> {
static readonly kind = 'dev.perfetto.EventLatencySliceDetailsPanel';
@@ -76,6 +99,12 @@
// is stores information about the current stage;
private relevantThreadStage: EventLatencyStage|undefined;
private relevantThreadTracks: EventLatencyCauseThreadTracks[] = [];
+ // Stages tree for the current EventLatency.
+ private eventLatencyBreakdown?: SliceTreeNode;
+ // Stages tree for the next EventLatency.
+ private nextEventLatencyBreakdown?: SliceTreeNode;
+ // Stages tree for the prev EventLatency.
+ private prevEventLatencyBreakdown?: SliceTreeNode;
static create(args: NewBottomTabArgs<GenericSliceDetailsTabConfig>):
EventLatencySliceDetailsPanel {
@@ -105,6 +134,8 @@
await this.loadSlice();
await this.loadJankSlice();
await this.loadRelevantThreads();
+ await this.loadEventLatencyBreakdown();
+
this.loaded = true;
}
@@ -167,6 +198,50 @@
}
}
+ async loadEventLatencyBreakdown() {
+ if (this.topEventLatencyId === undefined) {
+ return;
+ }
+ this.eventLatencyBreakdown =
+ await getDescendantSliceTree(this.engine, this.topEventLatencyId);
+
+ // TODO(altimin): this should be based on an stdlib table and consider only
+ // EventLatencies within the same scroll.
+ // This is a copy of the statement in event_latency_track. It should move to
+ // stdlib instead of living in the UI code.
+ const whereClause = `
+ EXTRACT_ARG(arg_set_id, 'event_latency.event_type') IN (
+ 'FIRST_GESTURE_SCROLL_UPDATE',
+ 'GESTURE_SCROLL_UPDATE',
+ 'INERTIAL_GESTURE_SCROLL_UPDATE')
+ AND HAS_DESCENDANT_SLICE_WITH_NAME(
+ id,
+ 'SubmitCompositorFrameToPresentationCompositorFrame')`;
+ const prevEventLatency = await getSliceFromConstraints(this.engine, {
+ filters: [
+ `name = 'EventLatency'`, `id < ${this.topEventLatencyId}`, whereClause,
+ ],
+ orderBy: [{fieldName: 'id', direction: 'DESC'}],
+ limit: 1,
+ });
+ if (prevEventLatency.length > 0) {
+ this.prevEventLatencyBreakdown =
+ await getDescendantSliceTree(this.engine, prevEventLatency[0].id);
+ }
+
+ const nextEventLatency = await getSliceFromConstraints(this.engine, {
+ filters: [
+ `name = 'EventLatency'`, `id > ${this.topEventLatencyId}`, whereClause,
+ ],
+ orderBy: ['id'],
+ limit: 1,
+ });
+ if (nextEventLatency.length > 0) {
+ this.nextEventLatencyBreakdown =
+ await getDescendantSliceTree(this.engine, nextEventLatency[0].id);
+ }
+ }
+
private getRelevantLinks(): m.Child {
if (!this.sliceDetails) return undefined;
@@ -289,6 +364,45 @@
);
}
+ private getBreakdownSection(): m.Child {
+ if (this.eventLatencyBreakdown === undefined) {
+ return undefined;
+ }
+
+ const attrs: TreeTableAttrs<SliceTreeNode> = {
+ rows: [this.eventLatencyBreakdown],
+ getChildren: (slice) => slice.children,
+ columns: [
+ {name: 'Name', getData: (slice) => slice.name},
+ {name: 'Duration', getData: (slice) => Duration.humanise(slice.dur)},
+ {
+ name: 'vs prev',
+ getData: (slice) => durationDelta(
+ slice.dur,
+ findSliceInTreeByPath(
+ this.prevEventLatencyBreakdown, getPath(slice))
+ ?.dur),
+ },
+ {
+ name: 'vs next',
+ getData: (slice) => durationDelta(
+ slice.dur,
+ findSliceInTreeByPath(
+ this.nextEventLatencyBreakdown, getPath(slice))
+ ?.dur),
+ },
+ ],
+ };
+
+ return m(
+ Section,
+ {
+ title: 'EventLatency Stage Breakdown',
+ },
+ m(TreeTable<SliceTreeNode>, attrs),
+ );
+ }
+
private getDescriptionText(): m.Child {
return m(
MultiParagraphText,
@@ -331,6 +445,7 @@
rightSideWidgets.push(stageWidget);
}
rightSideWidgets.push(this.getLinksSection());
+ rightSideWidgets.push(this.getBreakdownSection());
return m(
DetailsShell,
@@ -339,17 +454,11 @@
description: this.name,
},
m(GridLayout,
- m(GridLayoutColumn,
- renderDetails(slice),
+ m(GridLayoutColumn, renderDetails(slice),
hasArgs(slice.args) &&
- m(Section,
- {title: 'Arguments'},
+ m(Section, {title: 'Arguments'},
m(Tree, renderArguments(this.engine, slice.args)))),
- m(GridLayoutColumn,
- m(Section,
- {title: 'Description'},
- m('.div', this.getDescriptionText())),
- this.getLinksSection())),
+ m(GridLayoutColumn, rightSideWidgets)),
);
} else {
return m(DetailsShell, {title: 'Slice', description: 'Loading...'});
diff --git a/ui/src/tracks/chrome_scroll_jank/scroll_jank_cause_link_utils.ts b/ui/src/tracks/chrome_scroll_jank/scroll_jank_cause_link_utils.ts
index 48fcef2..b1043de 100644
--- a/ui/src/tracks/chrome_scroll_jank/scroll_jank_cause_link_utils.ts
+++ b/ui/src/tracks/chrome_scroll_jank/scroll_jank_cause_link_utils.ts
@@ -178,7 +178,7 @@
dur: duration|undefined): m.Child {
const trackKeys: string[] = [];
for (const trackId of threadTracks.trackIds) {
- const trackKey = globals.state.trackKeyByTrackId[trackId];
+ const trackKey = globals.trackManager.trackKeyByTrackId.get(trackId);
if (trackKey === undefined) {
return `Could not locate track ${trackId} for thread ${
threadTracks.thread} in the global state`;
diff --git a/ui/src/tracks/flows/index.ts b/ui/src/tracks/flows/index.ts
deleted file mode 100644
index 626c671..0000000
--- a/ui/src/tracks/flows/index.ts
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright (C) 2024 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-import {
- FlowEventsAreaSelectedPanel,
- FlowEventsPanel,
-} from '../../frontend/flow_events_panel';
-import {globals} from '../../frontend/globals';
-import {
- Plugin,
- PluginContext,
- PluginContextTrace,
- PluginDescriptor,
-} from '../../public';
-
-class FlowsPlugin implements Plugin {
- onActivate(_ctx: PluginContext): void {}
-
- async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
- const tabUri = 'perfetto.Flows#FlowEvents';
- ctx.registerTab({
- isEphemeral: false,
- uri: tabUri,
- content: {
- render: () => {
- const selection = globals.state.currentSelection;
- if (selection?.kind === 'AREA') {
- return m(FlowEventsAreaSelectedPanel);
- } else {
- return m(FlowEventsPanel);
- }
- },
- getTitle: () => 'Flow Events',
- },
- });
-
- ctx.registerCommand({
- id: 'perfetto.Flows#ShowFlowsTab',
- name: `Show Flows Tab`,
- callback: () => {
- ctx.tabs.showTab(tabUri);
- },
- });
- }
-}
-
-export const plugin: PluginDescriptor = {
- pluginId: 'perfetto.Flows',
- plugin: FlowsPlugin,
-};
diff --git a/ui/src/tracks/pivot_table/index.ts b/ui/src/tracks/pivot_table/index.ts
deleted file mode 100644
index e9d14cd..0000000
--- a/ui/src/tracks/pivot_table/index.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright (C) 2024 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-import {PIVOT_TABLE_REDUX_FLAG} from '../../controller/pivot_table_controller';
-import {globals} from '../../frontend/globals';
-import {PivotTable} from '../../frontend/pivot_table';
-import {
- Plugin,
- PluginContext,
- PluginContextTrace,
- PluginDescriptor,
-} from '../../public';
-
-class PivotTablePlugin implements Plugin {
- onActivate(_ctx: PluginContext): void {}
-
- async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
- if (PIVOT_TABLE_REDUX_FLAG.get()) {
- ctx.registerTab({
- isEphemeral: false,
- uri: 'perfetto.PivotTable#PivotTable',
- content: {
- render: () => m(PivotTable, {
- selectionArea:
- globals.state.nonSerializableState.pivotTable.selectionArea,
- }),
- getTitle: () => 'Pivot Table',
- },
- });
- }
- }
-}
-
-export const plugin: PluginDescriptor = {
- pluginId: 'perfetto.PivotTable',
- plugin: PivotTablePlugin,
-};
diff --git a/ui/src/widgets/button.ts b/ui/src/widgets/button.ts
index 3d4d230..3f73969 100644
--- a/ui/src/widgets/button.ts
+++ b/ui/src/widgets/button.ts
@@ -92,3 +92,12 @@
);
}
}
+
+/**
+ * Space buttons out with a little gap between each one.
+ */
+export class ButtonBar implements m.ClassComponent {
+ view({children}: m.CVnode): m.Children {
+ return m('.pf-button-bar', children);
+ }
+}
diff --git a/ui/src/widgets/tree.ts b/ui/src/widgets/tree.ts
index 1dd1340..17b405b 100644
--- a/ui/src/widgets/tree.ts
+++ b/ui/src/widgets/tree.ts
@@ -19,12 +19,15 @@
import {scheduleFullRedraw} from './raf';
-// Heirachical tree layout but right values are horizontally aligned.
+// Heirachical tree layout with left and right values.
+// Right and left values of the same indentation level are horizontally aligned.
// Example:
-// foo bar
+// foo bar
// ├ baz qux
// └ quux corge
-// grault garply
+// ├ looong_left aaa
+// └ a bbb
+// grault garply
interface TreeAttrs {
// Space delimited class list applied to our tree element.