Merge "tp: Add time_to_{unit} conversion to stdlib" into main
diff --git a/Android.bp b/Android.bp
index dec60eb..1a7fd1d 100644
--- a/Android.bp
+++ b/Android.bp
@@ -10718,6 +10718,8 @@
"src/protozero/test/example_proto/extensions.proto",
"src/protozero/test/example_proto/library.proto",
"src/protozero/test/example_proto/library_internals/galaxies.proto",
+ "src/protozero/test/example_proto/other_package/test_messages.proto",
+ "src/protozero/test/example_proto/subpackage/test_messages.proto",
"src/protozero/test/example_proto/test_messages.proto",
"src/protozero/test/example_proto/upper_import.proto",
],
@@ -10747,6 +10749,8 @@
name: "perfetto_src_protozero_testing_messages_cpp_gen",
srcs: [
":perfetto_src_protozero_testing_messages_cpp",
+ ":perfetto_src_protozero_testing_messages_other_package_cpp",
+ ":perfetto_src_protozero_testing_messages_subpackage_cpp",
],
tools: [
"aprotoc",
@@ -10767,6 +10771,8 @@
name: "perfetto_src_protozero_testing_messages_cpp_gen_headers",
srcs: [
":perfetto_src_protozero_testing_messages_cpp",
+ ":perfetto_src_protozero_testing_messages_other_package_cpp",
+ ":perfetto_src_protozero_testing_messages_subpackage_cpp",
],
tools: [
"aprotoc",
@@ -10803,6 +10809,8 @@
name: "perfetto_src_protozero_testing_messages_lite_gen",
srcs: [
":perfetto_src_protozero_testing_messages_lite",
+ ":perfetto_src_protozero_testing_messages_other_package_lite",
+ ":perfetto_src_protozero_testing_messages_subpackage_lite",
],
tools: [
"aprotoc",
@@ -10822,6 +10830,8 @@
name: "perfetto_src_protozero_testing_messages_lite_gen_headers",
srcs: [
":perfetto_src_protozero_testing_messages_lite",
+ ":perfetto_src_protozero_testing_messages_other_package_lite",
+ ":perfetto_src_protozero_testing_messages_subpackage_lite",
],
tools: [
"aprotoc",
@@ -10840,6 +10850,266 @@
],
}
+// GN: //src/protozero:testing_messages_other_package_cpp
+filegroup {
+ name: "perfetto_src_protozero_testing_messages_other_package_cpp",
+ srcs: [
+ "src/protozero/test/example_proto/other_package/test_messages.proto",
+ ],
+}
+
+// GN: //src/protozero:testing_messages_other_package_cpp
+genrule {
+ name: "perfetto_src_protozero_testing_messages_other_package_cpp_gen",
+ srcs: [
+ ":perfetto_src_protozero_testing_messages_other_package_cpp",
+ ],
+ tools: [
+ "aprotoc",
+ "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
+ ],
+ cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_src_protozero_testing_messages_other_package_cpp)",
+ out: [
+ "external/perfetto/src/protozero/test/example_proto/other_package/test_messages.gen.cc",
+ ],
+}
+
+// GN: //src/protozero:testing_messages_other_package_cpp
+genrule {
+ name: "perfetto_src_protozero_testing_messages_other_package_cpp_gen_headers",
+ srcs: [
+ ":perfetto_src_protozero_testing_messages_other_package_cpp",
+ ],
+ tools: [
+ "aprotoc",
+ "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
+ ],
+ cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_src_protozero_testing_messages_other_package_cpp)",
+ out: [
+ "external/perfetto/src/protozero/test/example_proto/other_package/test_messages.gen.h",
+ ],
+ export_include_dirs: [
+ ".",
+ "protos",
+ ],
+}
+
+// GN: //src/protozero:testing_messages_other_package_lite
+filegroup {
+ name: "perfetto_src_protozero_testing_messages_other_package_lite",
+ srcs: [
+ "src/protozero/test/example_proto/other_package/test_messages.proto",
+ ],
+}
+
+// GN: //src/protozero:testing_messages_other_package_lite
+genrule {
+ name: "perfetto_src_protozero_testing_messages_other_package_lite_gen",
+ srcs: [
+ ":perfetto_src_protozero_testing_messages_other_package_lite",
+ ],
+ tools: [
+ "aprotoc",
+ ],
+ cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_src_protozero_testing_messages_other_package_lite)",
+ out: [
+ "external/perfetto/src/protozero/test/example_proto/other_package/test_messages.pb.cc",
+ ],
+}
+
+// GN: //src/protozero:testing_messages_other_package_lite
+genrule {
+ name: "perfetto_src_protozero_testing_messages_other_package_lite_gen_headers",
+ srcs: [
+ ":perfetto_src_protozero_testing_messages_other_package_lite",
+ ],
+ tools: [
+ "aprotoc",
+ ],
+ cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_src_protozero_testing_messages_other_package_lite)",
+ out: [
+ "external/perfetto/src/protozero/test/example_proto/other_package/test_messages.pb.h",
+ ],
+ export_include_dirs: [
+ ".",
+ "protos",
+ ],
+}
+
+// GN: //src/protozero:testing_messages_other_package_zero
+filegroup {
+ name: "perfetto_src_protozero_testing_messages_other_package_zero",
+ srcs: [
+ "src/protozero/test/example_proto/other_package/test_messages.proto",
+ ],
+}
+
+// GN: //src/protozero:testing_messages_other_package_zero
+genrule {
+ name: "perfetto_src_protozero_testing_messages_other_package_zero_gen",
+ srcs: [
+ ":perfetto_src_protozero_testing_messages_other_package_zero",
+ ],
+ tools: [
+ "aprotoc",
+ "protozero_plugin",
+ ],
+ cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_src_protozero_testing_messages_other_package_zero)",
+ out: [
+ "external/perfetto/src/protozero/test/example_proto/other_package/test_messages.pbzero.cc",
+ ],
+}
+
+// GN: //src/protozero:testing_messages_other_package_zero
+genrule {
+ name: "perfetto_src_protozero_testing_messages_other_package_zero_gen_headers",
+ srcs: [
+ ":perfetto_src_protozero_testing_messages_other_package_zero",
+ ],
+ tools: [
+ "aprotoc",
+ "protozero_plugin",
+ ],
+ cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_src_protozero_testing_messages_other_package_zero)",
+ out: [
+ "external/perfetto/src/protozero/test/example_proto/other_package/test_messages.pbzero.h",
+ ],
+ export_include_dirs: [
+ ".",
+ "protos",
+ ],
+}
+
+// GN: //src/protozero:testing_messages_subpackage_cpp
+filegroup {
+ name: "perfetto_src_protozero_testing_messages_subpackage_cpp",
+ srcs: [
+ "src/protozero/test/example_proto/subpackage/test_messages.proto",
+ ],
+}
+
+// GN: //src/protozero:testing_messages_subpackage_cpp
+genrule {
+ name: "perfetto_src_protozero_testing_messages_subpackage_cpp_gen",
+ srcs: [
+ ":perfetto_src_protozero_testing_messages_subpackage_cpp",
+ ],
+ tools: [
+ "aprotoc",
+ "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
+ ],
+ cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_src_protozero_testing_messages_subpackage_cpp)",
+ out: [
+ "external/perfetto/src/protozero/test/example_proto/subpackage/test_messages.gen.cc",
+ ],
+}
+
+// GN: //src/protozero:testing_messages_subpackage_cpp
+genrule {
+ name: "perfetto_src_protozero_testing_messages_subpackage_cpp_gen_headers",
+ srcs: [
+ ":perfetto_src_protozero_testing_messages_subpackage_cpp",
+ ],
+ tools: [
+ "aprotoc",
+ "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
+ ],
+ cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_src_protozero_testing_messages_subpackage_cpp)",
+ out: [
+ "external/perfetto/src/protozero/test/example_proto/subpackage/test_messages.gen.h",
+ ],
+ export_include_dirs: [
+ ".",
+ "protos",
+ ],
+}
+
+// GN: //src/protozero:testing_messages_subpackage_lite
+filegroup {
+ name: "perfetto_src_protozero_testing_messages_subpackage_lite",
+ srcs: [
+ "src/protozero/test/example_proto/subpackage/test_messages.proto",
+ ],
+}
+
+// GN: //src/protozero:testing_messages_subpackage_lite
+genrule {
+ name: "perfetto_src_protozero_testing_messages_subpackage_lite_gen",
+ srcs: [
+ ":perfetto_src_protozero_testing_messages_subpackage_lite",
+ ],
+ tools: [
+ "aprotoc",
+ ],
+ cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_src_protozero_testing_messages_subpackage_lite)",
+ out: [
+ "external/perfetto/src/protozero/test/example_proto/subpackage/test_messages.pb.cc",
+ ],
+}
+
+// GN: //src/protozero:testing_messages_subpackage_lite
+genrule {
+ name: "perfetto_src_protozero_testing_messages_subpackage_lite_gen_headers",
+ srcs: [
+ ":perfetto_src_protozero_testing_messages_subpackage_lite",
+ ],
+ tools: [
+ "aprotoc",
+ ],
+ cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_src_protozero_testing_messages_subpackage_lite)",
+ out: [
+ "external/perfetto/src/protozero/test/example_proto/subpackage/test_messages.pb.h",
+ ],
+ export_include_dirs: [
+ ".",
+ "protos",
+ ],
+}
+
+// GN: //src/protozero:testing_messages_subpackage_zero
+filegroup {
+ name: "perfetto_src_protozero_testing_messages_subpackage_zero",
+ srcs: [
+ "src/protozero/test/example_proto/subpackage/test_messages.proto",
+ ],
+}
+
+// GN: //src/protozero:testing_messages_subpackage_zero
+genrule {
+ name: "perfetto_src_protozero_testing_messages_subpackage_zero_gen",
+ srcs: [
+ ":perfetto_src_protozero_testing_messages_subpackage_zero",
+ ],
+ tools: [
+ "aprotoc",
+ "protozero_plugin",
+ ],
+ cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_src_protozero_testing_messages_subpackage_zero)",
+ out: [
+ "external/perfetto/src/protozero/test/example_proto/subpackage/test_messages.pbzero.cc",
+ ],
+}
+
+// GN: //src/protozero:testing_messages_subpackage_zero
+genrule {
+ name: "perfetto_src_protozero_testing_messages_subpackage_zero_gen_headers",
+ srcs: [
+ ":perfetto_src_protozero_testing_messages_subpackage_zero",
+ ],
+ tools: [
+ "aprotoc",
+ "protozero_plugin",
+ ],
+ cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_src_protozero_testing_messages_subpackage_zero)",
+ out: [
+ "external/perfetto/src/protozero/test/example_proto/subpackage/test_messages.pbzero.h",
+ ],
+ export_include_dirs: [
+ ".",
+ "protos",
+ ],
+}
+
// GN: //src/protozero:testing_messages_zero
filegroup {
name: "perfetto_src_protozero_testing_messages_zero",
@@ -10856,6 +11126,8 @@
genrule {
name: "perfetto_src_protozero_testing_messages_zero_gen",
srcs: [
+ ":perfetto_src_protozero_testing_messages_other_package_zero",
+ ":perfetto_src_protozero_testing_messages_subpackage_zero",
":perfetto_src_protozero_testing_messages_zero",
],
tools: [
@@ -10876,6 +11148,8 @@
genrule {
name: "perfetto_src_protozero_testing_messages_zero_gen_headers",
srcs: [
+ ":perfetto_src_protozero_testing_messages_other_package_zero",
+ ":perfetto_src_protozero_testing_messages_subpackage_zero",
":perfetto_src_protozero_testing_messages_zero",
],
tools: [
@@ -12514,11 +12788,15 @@
filegroup {
name: "perfetto_src_trace_redaction_trace_redaction",
srcs: [
+ "src/trace_redaction/build_timeline.cc",
"src/trace_redaction/find_package_uid.cc",
+ "src/trace_redaction/optimize_timeline.cc",
"src/trace_redaction/populate_allow_lists.cc",
+ "src/trace_redaction/process_thread_timeline.cc",
"src/trace_redaction/proto_util.cc",
"src/trace_redaction/prune_package_list.cc",
"src/trace_redaction/scrub_ftrace_events.cc",
+ "src/trace_redaction/scrub_process_trees.cc",
"src/trace_redaction/scrub_trace_packet.cc",
"src/trace_redaction/trace_redaction_framework.cc",
"src/trace_redaction/trace_redactor.cc",
@@ -12529,7 +12807,9 @@
filegroup {
name: "perfetto_src_trace_redaction_unittests",
srcs: [
+ "src/trace_redaction/build_timeline_unittest.cc",
"src/trace_redaction/find_package_uid_unittest.cc",
+ "src/trace_redaction/process_thread_timeline_unittest.cc",
"src/trace_redaction/proto_util_unittest.cc",
"src/trace_redaction/prune_package_list_unittest.cc",
"src/trace_redaction/scrub_ftrace_events_unittest.cc",
@@ -13927,6 +14207,12 @@
":perfetto_src_protozero_protozero",
":perfetto_src_protozero_testing_messages_cpp_gen",
":perfetto_src_protozero_testing_messages_lite_gen",
+ ":perfetto_src_protozero_testing_messages_other_package_cpp_gen",
+ ":perfetto_src_protozero_testing_messages_other_package_lite_gen",
+ ":perfetto_src_protozero_testing_messages_other_package_zero_gen",
+ ":perfetto_src_protozero_testing_messages_subpackage_cpp_gen",
+ ":perfetto_src_protozero_testing_messages_subpackage_lite_gen",
+ ":perfetto_src_protozero_testing_messages_subpackage_zero_gen",
":perfetto_src_protozero_testing_messages_zero_gen",
":perfetto_src_protozero_unittests",
":perfetto_src_shared_lib_intern_map",
@@ -14225,6 +14511,12 @@
"perfetto_src_perfetto_cmd_protos_cpp_gen_headers",
"perfetto_src_protozero_testing_messages_cpp_gen_headers",
"perfetto_src_protozero_testing_messages_lite_gen_headers",
+ "perfetto_src_protozero_testing_messages_other_package_cpp_gen_headers",
+ "perfetto_src_protozero_testing_messages_other_package_lite_gen_headers",
+ "perfetto_src_protozero_testing_messages_other_package_zero_gen_headers",
+ "perfetto_src_protozero_testing_messages_subpackage_cpp_gen_headers",
+ "perfetto_src_protozero_testing_messages_subpackage_lite_gen_headers",
+ "perfetto_src_protozero_testing_messages_subpackage_zero_gen_headers",
"perfetto_src_protozero_testing_messages_zero_gen_headers",
"perfetto_src_trace_processor_gen_cc_test_messages_descriptor",
"perfetto_src_trace_processor_importers_proto_gen_cc_chrome_track_event_descriptor",
diff --git a/docs/concepts/concurrent-tracing-sessions.md b/docs/concepts/concurrent-tracing-sessions.md
new file mode 100644
index 0000000..63f6dd4
--- /dev/null
+++ b/docs/concepts/concurrent-tracing-sessions.md
@@ -0,0 +1,76 @@
+# Concurrent tracing sessions
+
+Perfetto supports multiple concurrent tracing sessions.
+Sessions are isolated from each other each and each session can choose a different mix of producers and data sources in its [config](config.md) and, in general, it will only receive events specified by that config.
+This is a powerful mechanism which allows great flexibility when collecting traces from the lab or field.
+However there are a few caveats to bear in mind with concurrent tracing sessions:
+1. [Some data sources do not support concurrent sessions](#some-data-sources-do-not-support-concurrent-sessions)
+2. [Some settings are per session while others are per producer](#some-settings-are-per-session-while-others-are-per-producer)
+3. Due to the [way atrace works works](#atrace) if a session requests *any* atrace category or app it receives *all* atrace events enabled on the device
+4. [Various limits](#various-limits) apply
+
+## Some data sources do not support concurrent sessions
+
+Whilst most data sources implemented with the Perfetto SDK as well as most data sources provided by the Perfetto team, do support concurrent tracing sessions some do not.
+This can be due to:
+
+- Hardware or driver constraints
+- Difficulty of implementing the config muxing
+- Perfetto SDK: users may [opt-out of multiple session support](https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/include/perfetto/tracing/data_source.h;l=266;drc=f988c792c18f93841b14ffa71019fdedf7ab2f03)
+
+### Known to work
+- `traced_probes` data sources ([linux.ftrace](/docs/reference/trace-config-proto.autogen#FtraceConfig), [linux.process_stats](/docs/reference/trace-config-proto.autogen#ProcessStatsConfig), [linux.sys_stats](/docs/reference/trace-config-proto.autogen#SysStatsConfig), [linux.system_info](https://perfetto.dev/docs/reference/trace-config-proto.autogen#SystemInfoConfig), etc)
+
+### Known to work with caveats
+- `heapprofd` supports multiple sessions but each process can only be in one session.
+- `traced_perf` in general supports multiple sessions but the kernel has a limit on counters so may reject a config.
+
+### Known not to work
+- `traced metatracing`
+
+## Some settings are per session while others are per producer
+
+Most buffer sizes and timings specified in the config are per session.
+For example the buffer [sizes](https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/protos/perfetto/config/trace_config.proto;l=32?q=f:perfetto%20f:trace_config&ss=android%2Fplatform%2Fsuperproject%2Fmain).
+
+However some parameters configure per-producer settings: for example the [size and layout](https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/protos/perfetto/config/trace_config.proto;l=182;drc=488df1649781de42b72e981c5e79ad922508d1e5) of the shmem buffer between the producer and traced.
+While that is general data source setting the same can apply to data source specific settings.
+For example the ftrace [kernel buffer size and drain period](https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/protos/perfetto/config/ftrace/ftrace_config.proto;l=32;drc=6a3d3540e68f3d5949b5d86ca736bfd7f811deff) are settings that have to be shared between all users of `traced_probes`.
+
+Bear in mind that
+- Some resources like the shmem buffers are shared by all sessions
+- As suggested by the comments in linked code above some settings are best treated as 'hints' since another config may have already set them before you get a chance to.
+
+## Atrace
+
+Atrace is an Android specific mechanism for doing userland instrumentation and the only available tracing method prior to the introduction of the Perfetto SDK into Android.
+It still powers [os.Trace](https://developer.android.com/reference/android/os/Trace) (as used by platform and application Java code) and [ATRACE_*](https://cs.android.com/android/platform/superproject/main/+/main:system/core/libcutils/include/cutils/trace.h;l=188;drc=0c44d8d68d56c7aecb828d8d87fba7dcb114f3d9) (as used by platform C++).
+
+
+Atrace (both prior to Perfetto and via Perfetto) works as follows:
+- Configuration:
+ - Users choose zero or more 'categories' from a hardcoded list
+ - Users choose zero or more package names including globs
+- This sets:
+ - Some kernel ftrace events
+ - A [system property bitmask](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/native/cmds/atrace/atrace.cpp;l=306;drc=c8af4d3407f3d6be46fafdfc044ace55944fb4b7) (for the atrace categories)
+ - A [system property](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/native/cmds/atrace/atrace.cpp;l=306;bpv=1;bpt=1) for each package.
+- When the Java or C++ tracing APIs are called we examine the system props.
+- If the relevant category or package is enabled we write the event to `trace_marker`
+
+As mentioned, each category may enable a number of kernel ftrace events.
+For example the 'sched' atrace category enables the `sched/sched_switch` ftrace event.
+Kernel ftrace events do not suffer from the current session issues so will not be described further.
+
+For the userland instrumentation:
+- Perfetto ensures the union of all atrace packages categories are installed
+- However since:
+ - the atrace system properties are global
+ - we cannot tell which event comes from which category/package
+Every session that requests *any* atrace event gets *all* enabled atrace events.
+
+## Various limits
+- Perfetto SDK: Max 8 datasource instances per datasource type per producer
+- `traced`: Limit of [15 concurrent sessions](https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/src/tracing/service/tracing_service_impl.cc;l=114?q=kMaxConcurrentTracingSessions%20)
+- `traced`: Limit of [5 (10 for statsd) concurrent sessions per UID](https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/src/tracing/service/tracing_service_impl.cc;l=115;drc=17d5806d458e214bdb829deeeb08b098c2b5254d)
+
diff --git a/docs/contributing/common-tasks.md b/docs/contributing/common-tasks.md
index feb7aaa..11d4f14 100644
--- a/docs/contributing/common-tasks.md
+++ b/docs/contributing/common-tasks.md
@@ -16,22 +16,23 @@
## Contribute to SQL standard library
-1. Add or edit an SQL file inside `perfetto/src/trace_processor/stdlib/`.
-2. For a new file inside an existing module add the file to the corresponding `BUILD.gn`.
-3. For a new module (subdirectory of `/stdlib/`), module name (directory name) has to be added to the list in `/stdlib/BUILD.gn`.
+1. Add or edit an SQL file inside `perfetto/src/trace_processor/stdlib/`. This SQL file will be a new standard library module.
+2. For a new file inside an existing package add the file to the corresponding `BUILD.gn`.
+3. For a new package (subdirectory of `/stdlib/`), the package name (directory name) has to be added to the list in `/stdlib/BUILD.gn`.
Files inside the standard library have to be formatted in a very specific way, as its structure is used to generate documentation. There are presubmit checks, but they are not infallible.
-- Running the file cannot generate any data. There can be only
- `CREATE PERFETTO FUNCTION`, `CREATE PERFETTO TABLE` and `CREATE PERFETTO VIEW` statements inside.
-- The name of each table/view/function needs to start with `{module_name}_` or `{internal_}`.
- The names of views/tables/functions are must be `[a-z_]`. When a module is imported (using the `IMPORT` function), objects prefixed with internal should not be used.
- - The only exception is the `common` module. The name of functions/views/tables inside should not be prefixed with `common_`, as they are supposed to be module agnostic and widely used.
+- Running the file cannot generate any data. There can be only `CREATE PERFETTO {FUNCTION|TABLE|VIEW|MACRO}` statements inside.
+- The name of each standard library object needs to start with `{module_name}_` or be prefixed with an underscore(`_`) for internal objects.
+ The names must only contain lower and upper case letters and underscores. When a module is included (using the `INCLUDE PERFETTO MODULE`) the internal objects should not be treated as an API.
- Every table or view should have [a schema](/docs/analysis/perfetto-sql-syntax.md#tableview-schema).
+
+### Documentation
+
- Every non internal object, as well as its function arguments and columns in its schema have to be prefixed with an SQL comment documenting it.
- Any text is going to be parsed as markdown, so usage of markdown functionality (code, links, lists) is encouraged.
- Whitespaces in anything apart from descriptions are ignored, so comments can be formatted neatly.
- If the line with description exceeds 80 chars, description can be continued in following lines.
+- Any text is going to be parsed as markdown, so usage of markdown functionality (code, links, lists) is encouraged.
+Whitespaces in anything apart from descriptions are ignored, so comments can be formatted neatly.
+If the line with description exceeds 80 chars, description can be continued in following lines.
- **Table/view**: each has to have schema, object description and a comment above each column's definition in the schema.
- Description is any text in the comment above `CREATE PERFETTO {TABLE,VIEW}` statement.
- Column's comment is the text immediately above column definition in the schema.
@@ -65,9 +66,9 @@
slice.name AS slice_name,
COUNT(*) AS event_count
FROM slice
-INNER JOIN thread_track ON slice.track_id = thread_track.id
-INNER JOIN thread ON thread.utid = thread_track.utid
-INNER JOIN process ON thread.upid = process.upid
+JOIN thread_track ON slice.track_id = thread_track.id
+JOIN thread ON thread.utid = thread_track.utid
+JOIN process ON thread.upid = process.upid
WHERE
slice.name GLOB 'binder*'
GROUP BY
@@ -75,17 +76,6 @@
slice_name;
```
-Example of function in module `common`:
-```sql
--- Extracts an int value with the given name from the metadata table.
-CREATE PERFETTO FUNCTION extract_int_metadata(
- -- The name of the metadata entry.
- name STRING)
--- int_value for the given name. NULL if there's no such entry.
-RETURNS LONG
-AS SELECT int_value FROM metadata WHERE name = ($name)
-```
-
Example of table function in module `android`:
```sql
-- Given a launch id and GLOB for a slice name, returns columns for matching slices.
@@ -108,7 +98,12 @@
arg_set_id INT
)
AS
-SELECT slice_name, slice_ts, slice_dur, thread_name, arg_set_id
+SELECT
+ slice_name,
+ slice_ts,
+ slice_dur,
+ thread_name,
+ arg_set_id
FROM thread_slices_for_all_launches
WHERE launch_id = $launch_id AND slice_name GLOB $slice_name;
```
diff --git a/docs/toc.md b/docs/toc.md
index 2ac2751..f828874 100644
--- a/docs/toc.md
+++ b/docs/toc.md
@@ -66,6 +66,7 @@
* [Service model](concepts/service-model.md)
* [Clock synchronization](concepts/clock-sync.md)
* [Detached mode](concepts/detached-mode.md)
+ * [Concurrent tracing sessions](concepts/concurrent-tracing-sessions.md)
* [Reference](#)
* [Trace Config proto](reference/trace-config-proto.autogen)
diff --git a/include/perfetto/ext/base/sys_types.h b/include/perfetto/ext/base/sys_types.h
index cbb085d..bcb6433 100644
--- a/include/perfetto/ext/base/sys_types.h
+++ b/include/perfetto/ext/base/sys_types.h
@@ -29,7 +29,7 @@
#if !PERFETTO_BUILDFLAG(PERFETTO_COMPILER_GCC)
// MinGW has these. clang-cl and MSVC, which use just the Windows SDK, don't.
-using uid_t = unsigned int;
+using uid_t = int;
using pid_t = int;
#endif // !GCC
diff --git a/include/perfetto/public/protos/common/data_source_descriptor.pzc.h b/include/perfetto/public/protos/common/data_source_descriptor.pzc.h
index 21e62fb..e2cd133 100644
--- a/include/perfetto/public/protos/common/data_source_descriptor.pzc.h
+++ b/include/perfetto/public/protos/common/data_source_descriptor.pzc.h
@@ -56,6 +56,11 @@
handles_incremental_state_clear,
4);
PERFETTO_PB_FIELD(perfetto_protos_DataSourceDescriptor,
+ VARINT,
+ bool,
+ no_flush,
+ 9);
+PERFETTO_PB_FIELD(perfetto_protos_DataSourceDescriptor,
MSG,
perfetto_protos_GpuCounterDescriptor,
gpu_counter_descriptor,
diff --git a/include/perfetto/public/protos/config/data_source_config.pzc.h b/include/perfetto/public/protos/config/data_source_config.pzc.h
index 718a3bc..ed7632b 100644
--- a/include/perfetto/public/protos/config/data_source_config.pzc.h
+++ b/include/perfetto/public/protos/config/data_source_config.pzc.h
@@ -26,12 +26,14 @@
#include "perfetto/public/pb_macros.h"
PERFETTO_PB_MSG_DECL(perfetto_protos_AndroidGameInterventionListConfig);
+PERFETTO_PB_MSG_DECL(perfetto_protos_AndroidInputEventConfig);
PERFETTO_PB_MSG_DECL(perfetto_protos_AndroidLogConfig);
PERFETTO_PB_MSG_DECL(perfetto_protos_AndroidPolledStateConfig);
PERFETTO_PB_MSG_DECL(perfetto_protos_AndroidPowerConfig);
PERFETTO_PB_MSG_DECL(perfetto_protos_AndroidSdkSyspropGuardConfig);
PERFETTO_PB_MSG_DECL(perfetto_protos_AndroidSystemPropertyConfig);
PERFETTO_PB_MSG_DECL(perfetto_protos_ChromeConfig);
+PERFETTO_PB_MSG_DECL(perfetto_protos_EtwConfig);
PERFETTO_PB_MSG_DECL(perfetto_protos_FtraceConfig);
PERFETTO_PB_MSG_DECL(perfetto_protos_GpuCounterConfig);
PERFETTO_PB_MSG_DECL(perfetto_protos_HeapprofdConfig);
@@ -42,6 +44,7 @@
PERFETTO_PB_MSG_DECL(perfetto_protos_PackagesListConfig);
PERFETTO_PB_MSG_DECL(perfetto_protos_PerfEventConfig);
PERFETTO_PB_MSG_DECL(perfetto_protos_ProcessStatsConfig);
+PERFETTO_PB_MSG_DECL(perfetto_protos_ProtoLogConfig);
PERFETTO_PB_MSG_DECL(perfetto_protos_StatsdTracingConfig);
PERFETTO_PB_MSG_DECL(perfetto_protos_SurfaceFlingerLayersConfig);
PERFETTO_PB_MSG_DECL(perfetto_protos_SurfaceFlingerTransactionsConfig);
@@ -49,6 +52,7 @@
PERFETTO_PB_MSG_DECL(perfetto_protos_SystemInfoConfig);
PERFETTO_PB_MSG_DECL(perfetto_protos_TestConfig);
PERFETTO_PB_MSG_DECL(perfetto_protos_TrackEventConfig);
+PERFETTO_PB_MSG_DECL(perfetto_protos_V8Config);
PERFETTO_PB_MSG_DECL(perfetto_protos_VulkanMemoryConfig);
PERFETTO_PB_ENUM_IN_MSG(perfetto_protos_DataSourceConfig, SessionInitiator){
@@ -196,6 +200,11 @@
101);
PERFETTO_PB_FIELD(perfetto_protos_DataSourceConfig,
MSG,
+ perfetto_protos_V8Config,
+ v8_config,
+ 127);
+PERFETTO_PB_FIELD(perfetto_protos_DataSourceConfig,
+ MSG,
perfetto_protos_InterceptorConfig,
interceptor_config,
115);
@@ -220,6 +229,21 @@
android_sdk_sysprop_guard_config,
124);
PERFETTO_PB_FIELD(perfetto_protos_DataSourceConfig,
+ MSG,
+ perfetto_protos_EtwConfig,
+ etw_config,
+ 125);
+PERFETTO_PB_FIELD(perfetto_protos_DataSourceConfig,
+ MSG,
+ perfetto_protos_ProtoLogConfig,
+ protolog_config,
+ 126);
+PERFETTO_PB_FIELD(perfetto_protos_DataSourceConfig,
+ MSG,
+ perfetto_protos_AndroidInputEventConfig,
+ android_input_event_config,
+ 128);
+PERFETTO_PB_FIELD(perfetto_protos_DataSourceConfig,
STRING,
const char*,
legacy_config,
diff --git a/include/perfetto/public/protos/config/trace_config.pzc.h b/include/perfetto/public/protos/config/trace_config.pzc.h
index a2b8432..76492e3 100644
--- a/include/perfetto/public/protos/config/trace_config.pzc.h
+++ b/include/perfetto/public/protos/config/trace_config.pzc.h
@@ -80,6 +80,8 @@
SFP_MATCH_BREAK) = 3,
PERFETTO_PB_ENUM_IN_MSG_ENTRY(perfetto_protos_TraceConfig_TraceFilter,
SFP_ATRACE_MATCH_BREAK) = 4,
+ PERFETTO_PB_ENUM_IN_MSG_ENTRY(perfetto_protos_TraceConfig_TraceFilter,
+ SFP_ATRACE_REPEATED_SEARCH_REDACT_GROUPS) = 5,
};
PERFETTO_PB_ENUM_IN_MSG(perfetto_protos_TraceConfig_TriggerConfig, TriggerMode){
@@ -204,6 +206,11 @@
bugreport_score,
30);
PERFETTO_PB_FIELD(perfetto_protos_TraceConfig,
+ STRING,
+ const char*,
+ bugreport_filename,
+ 38);
+PERFETTO_PB_FIELD(perfetto_protos_TraceConfig,
MSG,
perfetto_protos_TraceConfig_TriggerConfig,
trigger_config,
@@ -234,11 +241,6 @@
compression_type,
24);
PERFETTO_PB_FIELD(perfetto_protos_TraceConfig,
- VARINT,
- bool,
- compress_from_cli,
- 37);
-PERFETTO_PB_FIELD(perfetto_protos_TraceConfig,
MSG,
perfetto_protos_TraceConfig_IncidentReportConfig,
incident_report_config,
diff --git a/include/perfetto/public/protos/trace/interned_data/interned_data.pzc.h b/include/perfetto/public/protos/trace/interned_data/interned_data.pzc.h
index 3c8f88a..71e26fa 100644
--- a/include/perfetto/public/protos/trace/interned_data/interned_data.pzc.h
+++ b/include/perfetto/public/protos/trace/interned_data/interned_data.pzc.h
@@ -35,6 +35,11 @@
PERFETTO_PB_MSG_DECL(perfetto_protos_InternedGpuRenderStageSpecification);
PERFETTO_PB_MSG_DECL(perfetto_protos_InternedGraphicsContext);
PERFETTO_PB_MSG_DECL(perfetto_protos_InternedString);
+PERFETTO_PB_MSG_DECL(perfetto_protos_InternedV8Isolate);
+PERFETTO_PB_MSG_DECL(perfetto_protos_InternedV8JsFunction);
+PERFETTO_PB_MSG_DECL(perfetto_protos_InternedV8JsScript);
+PERFETTO_PB_MSG_DECL(perfetto_protos_InternedV8String);
+PERFETTO_PB_MSG_DECL(perfetto_protos_InternedV8WasmScript);
PERFETTO_PB_MSG_DECL(perfetto_protos_LogMessageBody);
PERFETTO_PB_MSG_DECL(perfetto_protos_Mapping);
PERFETTO_PB_MSG_DECL(perfetto_protos_NetworkPacketContext);
@@ -153,5 +158,40 @@
perfetto_protos_NetworkPacketContext,
packet_context,
30);
+PERFETTO_PB_FIELD(perfetto_protos_InternedData,
+ MSG,
+ perfetto_protos_InternedV8String,
+ v8_js_function_name,
+ 31);
+PERFETTO_PB_FIELD(perfetto_protos_InternedData,
+ MSG,
+ perfetto_protos_InternedV8JsFunction,
+ v8_js_function,
+ 32);
+PERFETTO_PB_FIELD(perfetto_protos_InternedData,
+ MSG,
+ perfetto_protos_InternedV8JsScript,
+ v8_js_script,
+ 33);
+PERFETTO_PB_FIELD(perfetto_protos_InternedData,
+ MSG,
+ perfetto_protos_InternedV8WasmScript,
+ v8_wasm_script,
+ 34);
+PERFETTO_PB_FIELD(perfetto_protos_InternedData,
+ MSG,
+ perfetto_protos_InternedV8Isolate,
+ v8_isolate,
+ 35);
+PERFETTO_PB_FIELD(perfetto_protos_InternedData,
+ MSG,
+ perfetto_protos_InternedString,
+ protolog_string_args,
+ 36);
+PERFETTO_PB_FIELD(perfetto_protos_InternedData,
+ MSG,
+ perfetto_protos_InternedString,
+ protolog_stacktrace,
+ 37);
#endif // INCLUDE_PERFETTO_PUBLIC_PROTOS_TRACE_INTERNED_DATA_INTERNED_DATA_PZC_H_
diff --git a/include/perfetto/public/protos/trace/trace_packet.pzc.h b/include/perfetto/public/protos/trace/trace_packet.pzc.h
index 52df003..d83ab18 100644
--- a/include/perfetto/public/protos/trace/trace_packet.pzc.h
+++ b/include/perfetto/public/protos/trace/trace_packet.pzc.h
@@ -29,6 +29,7 @@
PERFETTO_PB_MSG_DECL(perfetto_protos_AndroidCameraSessionStats);
PERFETTO_PB_MSG_DECL(perfetto_protos_AndroidEnergyEstimationBreakdown);
PERFETTO_PB_MSG_DECL(perfetto_protos_AndroidGameInterventionList);
+PERFETTO_PB_MSG_DECL(perfetto_protos_AndroidInputEvent);
PERFETTO_PB_MSG_DECL(perfetto_protos_AndroidLogPacket);
PERFETTO_PB_MSG_DECL(perfetto_protos_AndroidSystemProperty);
PERFETTO_PB_MSG_DECL(perfetto_protos_BatteryCounters);
@@ -39,6 +40,7 @@
PERFETTO_PB_MSG_DECL(perfetto_protos_CpuInfo);
PERFETTO_PB_MSG_DECL(perfetto_protos_DeobfuscationMapping);
PERFETTO_PB_MSG_DECL(perfetto_protos_EntityStateResidency);
+PERFETTO_PB_MSG_DECL(perfetto_protos_EtwTraceEventBundle);
PERFETTO_PB_MSG_DECL(perfetto_protos_ExtensionDescriptor);
PERFETTO_PB_MSG_DECL(perfetto_protos_FrameTimelineEvent);
PERFETTO_PB_MSG_DECL(perfetto_protos_FtraceEventBundle);
@@ -66,6 +68,11 @@
PERFETTO_PB_MSG_DECL(perfetto_protos_ProcessTree);
PERFETTO_PB_MSG_DECL(perfetto_protos_ProfilePacket);
PERFETTO_PB_MSG_DECL(perfetto_protos_ProfiledFrameSymbols);
+PERFETTO_PB_MSG_DECL(perfetto_protos_ProtoLogMessage);
+PERFETTO_PB_MSG_DECL(perfetto_protos_ProtoLogViewerConfig);
+PERFETTO_PB_MSG_DECL(perfetto_protos_RemoteClockSync);
+PERFETTO_PB_MSG_DECL(perfetto_protos_ShellHandlerMappings);
+PERFETTO_PB_MSG_DECL(perfetto_protos_ShellTransition);
PERFETTO_PB_MSG_DECL(perfetto_protos_SmapsPacket);
PERFETTO_PB_MSG_DECL(perfetto_protos_StatsdAtom);
PERFETTO_PB_MSG_DECL(perfetto_protos_StreamingAllocation);
@@ -87,6 +94,11 @@
PERFETTO_PB_MSG_DECL(perfetto_protos_TranslationTable);
PERFETTO_PB_MSG_DECL(perfetto_protos_Trigger);
PERFETTO_PB_MSG_DECL(perfetto_protos_UiState);
+PERFETTO_PB_MSG_DECL(perfetto_protos_V8CodeMove);
+PERFETTO_PB_MSG_DECL(perfetto_protos_V8InternalCode);
+PERFETTO_PB_MSG_DECL(perfetto_protos_V8JsCode);
+PERFETTO_PB_MSG_DECL(perfetto_protos_V8RegExpCode);
+PERFETTO_PB_MSG_DECL(perfetto_protos_V8WasmCode);
PERFETTO_PB_MSG_DECL(perfetto_protos_VulkanApiEvent);
PERFETTO_PB_MSG_DECL(perfetto_protos_VulkanMemoryEvent);
@@ -423,6 +435,66 @@
94);
PERFETTO_PB_FIELD(perfetto_protos_TracePacket,
MSG,
+ perfetto_protos_ShellTransition,
+ shell_transition,
+ 96);
+PERFETTO_PB_FIELD(perfetto_protos_TracePacket,
+ MSG,
+ perfetto_protos_ShellHandlerMappings,
+ shell_handler_mappings,
+ 97);
+PERFETTO_PB_FIELD(perfetto_protos_TracePacket,
+ MSG,
+ perfetto_protos_ProtoLogMessage,
+ protolog_message,
+ 104);
+PERFETTO_PB_FIELD(perfetto_protos_TracePacket,
+ MSG,
+ perfetto_protos_ProtoLogViewerConfig,
+ protolog_viewer_config,
+ 105);
+PERFETTO_PB_FIELD(perfetto_protos_TracePacket,
+ MSG,
+ perfetto_protos_EtwTraceEventBundle,
+ etw_events,
+ 95);
+PERFETTO_PB_FIELD(perfetto_protos_TracePacket,
+ MSG,
+ perfetto_protos_V8JsCode,
+ v8_js_code,
+ 99);
+PERFETTO_PB_FIELD(perfetto_protos_TracePacket,
+ MSG,
+ perfetto_protos_V8InternalCode,
+ v8_internal_code,
+ 100);
+PERFETTO_PB_FIELD(perfetto_protos_TracePacket,
+ MSG,
+ perfetto_protos_V8WasmCode,
+ v8_wasm_code,
+ 101);
+PERFETTO_PB_FIELD(perfetto_protos_TracePacket,
+ MSG,
+ perfetto_protos_V8RegExpCode,
+ v8_reg_exp_code,
+ 102);
+PERFETTO_PB_FIELD(perfetto_protos_TracePacket,
+ MSG,
+ perfetto_protos_V8CodeMove,
+ v8_code_move,
+ 103);
+PERFETTO_PB_FIELD(perfetto_protos_TracePacket,
+ MSG,
+ perfetto_protos_AndroidInputEvent,
+ android_input_event,
+ 106);
+PERFETTO_PB_FIELD(perfetto_protos_TracePacket,
+ MSG,
+ perfetto_protos_RemoteClockSync,
+ remote_clock_sync,
+ 107);
+PERFETTO_PB_FIELD(perfetto_protos_TracePacket,
+ MSG,
perfetto_protos_TestEvent,
for_testing,
900);
@@ -467,5 +539,10 @@
bool,
first_packet_on_sequence,
87);
+PERFETTO_PB_FIELD(perfetto_protos_TracePacket,
+ VARINT,
+ uint32_t,
+ machine_id,
+ 98);
#endif // INCLUDE_PERFETTO_PUBLIC_PROTOS_TRACE_TRACE_PACKET_PZC_H_
diff --git a/include/perfetto/public/protos/trace/track_event/track_event.pzc.h b/include/perfetto/public/protos/trace/track_event/track_event.pzc.h
index 5553376..2ee81f1 100644
--- a/include/perfetto/public/protos/trace/track_event/track_event.pzc.h
+++ b/include/perfetto/public/protos/trace/track_event/track_event.pzc.h
@@ -41,6 +41,7 @@
PERFETTO_PB_MSG_DECL(perfetto_protos_ChromeWindowHandleEventInfo);
PERFETTO_PB_MSG_DECL(perfetto_protos_DebugAnnotation);
PERFETTO_PB_MSG_DECL(perfetto_protos_LogMessage);
+PERFETTO_PB_MSG_DECL(perfetto_protos_Screenshot);
PERFETTO_PB_MSG_DECL(perfetto_protos_SourceLocation);
PERFETTO_PB_MSG_DECL(perfetto_protos_TaskExecution);
PERFETTO_PB_MSG_DECL(perfetto_protos_TrackEvent_LegacyEvent);
@@ -246,6 +247,11 @@
49);
PERFETTO_PB_FIELD(perfetto_protos_TrackEvent,
MSG,
+ perfetto_protos_Screenshot,
+ screenshot,
+ 50);
+PERFETTO_PB_FIELD(perfetto_protos_TrackEvent,
+ MSG,
perfetto_protos_SourceLocation,
source_location,
33);
diff --git a/infra/perfetto.dev/src/gen_stdlib_docs_md.py b/infra/perfetto.dev/src/gen_stdlib_docs_md.py
index 6fc5283..e1e60bd 100644
--- a/infra/perfetto.dev/src/gen_stdlib_docs_md.py
+++ b/infra/perfetto.dev/src/gen_stdlib_docs_md.py
@@ -22,216 +22,7 @@
import json
from typing import Any, List, Dict
-
-# Escapes special characters in a markdown table.
-def escape_in_table(desc: str):
- return desc.replace('|', '\\|')
-
-
-# Responsible for module level markdown generation.
-class ModuleMd:
-
- def __init__(self, module_name: str, module_files: List[Dict[str,
- Any]]) -> None:
- self.module_name = module_name
- self.files_md = sorted([
- FileMd(module_name, file_dict) for file_dict in module_files
- ], key=lambda x: x.import_key)
- self.summary_objs = '\n'.join(
- file.summary_objs for file in self.files_md if file.summary_objs)
- self.summary_funs = '\n'.join(
- file.summary_funs for file in self.files_md if file.summary_funs)
- self.summary_view_funs = '\n'.join(file.summary_view_funs
- for file in self.files_md
- if file.summary_view_funs)
- self.summary_macros = '\n'.join(
- file.summary_macros for file in self.files_md if file.summary_macros)
-
- def print_description(self):
- if not self.files_md:
- return ''
-
- long_s = []
- long_s.append(f'## Module: {self.module_name}')
-
- if self.module_name == 'prelude':
- # Prelude is a special module which is automatically imported and doesn't
- # have any include keys.
- objs = '\n'.join(obj for file in self.files_md for obj in file.objs)
- if objs:
- long_s.append('#### Views/Tables')
- long_s.append(objs)
- funs = '\n'.join(fun for file in self.files_md for fun in file.funs)
- if funs:
- long_s.append('#### Functions')
- long_s.append(funs)
- table_funs = '\n'.join(
- view_fun for file in self.files_md for view_fun in file.view_funs)
- if table_funs:
- long_s.append('#### Table Functions')
- long_s.append(table_funs)
- macros = '\n'.join(
- macro for file in self.files_md for macro in file.macros)
- if macros:
- long_s.append('#### Macros')
- long_s.append(macros)
- return '\n'.join(long_s)
-
- for file in self.files_md:
- if not any((file.objs, file.funs, file.view_funs, file.macros)):
- continue
-
- long_s.append(f'### {file.import_key}')
- if file.objs:
- long_s.append('#### Views/Tables')
- long_s.append('\n'.join(file.objs))
- if file.funs:
- long_s.append('#### Functions')
- long_s.append('\n'.join(file.funs))
- if file.view_funs:
- long_s.append('#### Table Functions')
- long_s.append('\n'.join(file.view_funs))
- if file.macros:
- long_s.append('#### Macros')
- long_s.append('\n'.join(file.macros))
-
- return '\n'.join(long_s)
-
-
-# Responsible for file level markdown generation.
-class FileMd:
-
- def __init__(self, module_name, file_dict):
- self.import_key = file_dict['import_key']
- import_key_name = self.import_key if module_name != 'prelude' else 'N/A'
- self.objs, self.funs, self.view_funs, self.macros = [], [], [], []
- summary_objs_list, summary_funs_list, summary_view_funs_list, summary_macros_list = [], [], [], []
-
- # Add imports if in file.
- for data in file_dict['imports']:
- # Anchor
- anchor = f'''obj/{module_name}/{data['name']}'''
-
- # Add summary of imported view/table
- summary_objs_list.append(f'''[{data['name']}](#{anchor})|'''
- f'''{import_key_name}|'''
- f'''{escape_in_table(data['summary_desc'])}''')
-
- self.objs.append(f'''\n\n<a name="{anchor}"></a>'''
- f'''**{data['name']}**, {data['type']}\n\n'''
- f'''{escape_in_table(data['desc'])}\n''')
-
- self.objs.append(
- 'Column | Type | Description\n------ | --- | -----------')
- for name, info in data['cols'].items():
- self.objs.append(
- f'{name} | {info["type"]} | {escape_in_table(info["desc"])}')
-
- self.objs.append('\n\n')
-
- # Add functions if in file
- for data in file_dict['functions']:
- # Anchor
- anchor = f'''fun/{module_name}/{data['name']}'''
-
- # Add summary of imported function
- summary_funs_list.append(f'''[{data['name']}](#{anchor})|'''
- f'''{import_key_name}|'''
- f'''{data['return_type']}|'''
- f'''{escape_in_table(data['summary_desc'])}''')
- self.funs.append(
- f'''\n\n<a name="{anchor}"></a>'''
- f'''**{data['name']}**\n\n'''
- f'''{data['desc']}\n\n'''
- f'''Returns: {data['return_type']}, {data['return_desc']}\n\n''')
- if data['args']:
- self.funs.append('Argument | Type | Description\n'
- '-------- | ---- | -----------')
- for name, arg_dict in data['args'].items():
- self.funs.append(
- f'''{name} | {arg_dict['type']} | {escape_in_table(arg_dict['desc'])}'''
- )
-
- self.funs.append('\n\n')
-
- # Add table functions if in file
- for data in file_dict['table_functions']:
- # Anchor
- anchor = rf'''view_fun/{module_name}/{data['name']}'''
- # Add summary of imported view function
- summary_view_funs_list.append(
- f'''[{data['name']}](#{anchor})|'''
- f'''{import_key_name}|'''
- f'''{escape_in_table(data['summary_desc'])}''')
-
- self.view_funs.append(f'''\n\n<a name="{anchor}"></a>'''
- f'''**{data['name']}**\n'''
- f'''{data['desc']}\n\n''')
- if data['args']:
- self.view_funs.append('Argument | Type | Description\n'
- '-------- | ---- | -----------')
- for name, arg_dict in data['args'].items():
- self.view_funs.append(
- f'''{name} | {arg_dict['type']} | {escape_in_table(arg_dict['desc'])}'''
- )
- self.view_funs.append('\n')
- self.view_funs.append('Column | Type | Description\n'
- '------ | -- | -----------')
- for name, column in data['cols'].items():
- self.view_funs.append(f'{name} | {column["type"]} | {column["desc"]}')
-
- self.view_funs.append('\n\n')
-
- # Add macros if in file
- for data in file_dict['macros']:
- # Anchor
- anchor = rf'''macro/{module_name}/{data['name']}'''
- # Add summary of imported view function
- summary_macros_list.append(f'''[{data['name']}](#{anchor})|'''
- f'''{import_key_name}|'''
- f'''{escape_in_table(data['summary_desc'])}''')
-
- self.macros.append(
- f'''\n\n<a name="{anchor}"></a>'''
- f'''**{data['name']}**\n'''
- f'''{data['desc']}\n\n'''
- f'''Returns: {data['return_type']}, {data['return_desc']}\n\n''')
- if data['args']:
- self.macros.append('Argument | Type | Description\n'
- '-------- | ---- | -----------')
- for name, arg_dict in data['args'].items():
- self.macros.append(
- f'''{name} | {arg_dict['type']} | {escape_in_table(arg_dict['desc'])}'''
- )
- self.macros.append('\n')
- self.macros.append('\n\n')
-
- self.summary_objs = '\n'.join(summary_objs_list)
- self.summary_funs = '\n'.join(summary_funs_list)
- self.summary_view_funs = '\n'.join(summary_view_funs_list)
- self.summary_macros = '\n'.join(summary_macros_list)
-
-
-def main():
- parser = argparse.ArgumentParser()
- parser.add_argument('--input', required=True)
- parser.add_argument('--output', required=True)
- args = parser.parse_args()
-
- with open(args.input) as f:
- modules_json_dict = json.load(f)
-
- modules_dict: Dict[str, ModuleMd] = {}
-
- for module_name, module_files in modules_json_dict.items():
- # Remove 'common' when it has been removed from the code.
- if module_name not in ['deprecated', 'common']:
- modules_dict[module_name] = ModuleMd(module_name, module_files)
-
- prelude_module = modules_dict.pop('prelude')
-
- with open(args.output, 'w') as f:
- f.write('''
+INTRODUCTION = '''
# PerfettoSQL standard library
*This page documents the PerfettoSQL standard library.*
@@ -271,71 +62,294 @@
<!-- TODO(b/290185551): talk about experimental module and contributions. -->
## Summary
-''')
+'''
+
+
+def _escape_in_table(desc: str):
+ """Escapes special characters in a markdown table."""
+ return desc.replace('|', '\\|')
+
+
+def _md_table(cols: List[str]):
+ col_str = ' | '.join(cols) + '\n'
+ lines = ['-' * len(col) for col in cols]
+ underlines = ' | '.join(lines)
+ return col_str + underlines
+
+
+def _write_summary(sql_type: str, table_cols: List[str],
+ summary_objs: List[str]) -> str:
+ table_data = '\n'.join(s.strip() for s in summary_objs if s)
+ return f"""
+### {sql_type}
+
+{_md_table(table_cols)}
+{table_data}
+
+"""
+
+
+class FileMd:
+ """Responsible for file level markdown generation."""
+
+ def __init__(self, module_name, file_dict):
+ self.import_key = file_dict['import_key']
+ import_key_name = self.import_key if module_name != 'prelude' else 'N/A'
+ self.objs, self.funs, self.view_funs, self.macros = [], [], [], []
+ summary_objs_list, summary_funs_list, summary_view_funs_list, summary_macros_list = [], [], [], []
+
+ # Add imports if in file.
+ for data in file_dict['imports']:
+ # Anchor
+ anchor = f'''obj/{module_name}/{data['name']}'''
+
+ # Add summary of imported view/table
+ summary_objs_list.append(f'''[{data['name']}](#{anchor})|'''
+ f'''{import_key_name}|'''
+ f'''{_escape_in_table(data['summary_desc'])}''')
+
+ self.objs.append(f'''\n\n<a name="{anchor}"></a>'''
+ f'''**{data['name']}**, {data['type']}\n\n'''
+ f'''{_escape_in_table(data['desc'])}\n''')
+
+ self.objs.append(_md_table(['Column', 'Type', 'Description']))
+ for name, info in data['cols'].items():
+ self.objs.append(
+ f'{name} | {info["type"]} | {_escape_in_table(info["desc"])}')
+
+ self.objs.append('\n\n')
+
+ # Add functions if in file
+ for data in file_dict['functions']:
+ # Anchor
+ anchor = f'''fun/{module_name}/{data['name']}'''
+
+ # Add summary of imported function
+ summary_funs_list.append(f'''[{data['name']}](#{anchor})|'''
+ f'''{import_key_name}|'''
+ f'''{data['return_type']}|'''
+ f'''{_escape_in_table(data['summary_desc'])}''')
+ self.funs.append(
+ f'''\n\n<a name="{anchor}"></a>'''
+ f'''**{data['name']}**\n\n'''
+ f'''{data['desc']}\n\n'''
+ f'''Returns: {data['return_type']}, {data['return_desc']}\n\n''')
+ if data['args']:
+ self.funs.append(_md_table(['Argument', 'Type', 'Description']))
+ for name, arg_dict in data['args'].items():
+ self.funs.append(
+ f'''{name} | {arg_dict['type']} | {_escape_in_table(arg_dict['desc'])}'''
+ )
+
+ self.funs.append('\n\n')
+
+ # Add table functions if in file
+ for data in file_dict['table_functions']:
+ # Anchor
+ anchor = rf'''view_fun/{module_name}/{data['name']}'''
+ # Add summary of imported view function
+ summary_view_funs_list.append(
+ f'''[{data['name']}](#{anchor})|'''
+ f'''{import_key_name}|'''
+ f'''{_escape_in_table(data['summary_desc'])}''')
+
+ self.view_funs.append(f'''\n\n<a name="{anchor}"></a>'''
+ f'''**{data['name']}**\n'''
+ f'''{data['desc']}\n\n''')
+ if data['args']:
+ self.funs.append(_md_table(['Argument', 'Type', 'Description']))
+ for name, arg_dict in data['args'].items():
+ self.view_funs.append(
+ f'''{name} | {arg_dict['type']} | {_escape_in_table(arg_dict['desc'])}'''
+ )
+ self.view_funs.append('\n')
+ self.view_funs.append(_md_table(['Column', 'Type', 'Description']))
+ for name, column in data['cols'].items():
+ self.view_funs.append(f'{name} | {column["type"]} | {column["desc"]}')
+
+ self.view_funs.append('\n\n')
+
+ # Add macros if in file
+ for data in file_dict['macros']:
+ # Anchor
+ anchor = rf'''macro/{module_name}/{data['name']}'''
+ # Add summary of imported view function
+ summary_macros_list.append(
+ f'''[{data['name']}](#{anchor})|'''
+ f'''{import_key_name}|'''
+ f'''{_escape_in_table(data['summary_desc'])}''')
+
+ self.macros.append(
+ f'''\n\n<a name="{anchor}"></a>'''
+ f'''**{data['name']}**\n'''
+ f'''{data['desc']}\n\n'''
+ f'''Returns: {data['return_type']}, {data['return_desc']}\n\n''')
+ if data['args']:
+ self.macros.append(_md_table(['Argument', 'Type', 'Description']))
+ for name, arg_dict in data['args'].items():
+ self.macros.append(
+ f'''{name} | {arg_dict['type']} | {_escape_in_table(arg_dict['desc'])}'''
+ )
+ self.macros.append('\n')
+ self.macros.append('\n\n')
+
+ self.summary_objs = '\n'.join(summary_objs_list)
+ self.summary_funs = '\n'.join(summary_funs_list)
+ self.summary_view_funs = '\n'.join(summary_view_funs_list)
+ self.summary_macros = '\n'.join(summary_macros_list)
+
+
+class ModuleMd:
+ """Responsible for module level markdown generation."""
+
+ def __init__(self, module_name: str, module_files: List[Dict[str,
+ Any]]) -> None:
+ self.module_name = module_name
+ self.files_md = sorted(
+ [FileMd(module_name, file_dict) for file_dict in module_files],
+ key=lambda x: x.import_key)
+ self.summary_objs = '\n'.join(
+ file.summary_objs for file in self.files_md if file.summary_objs)
+ self.summary_funs = '\n'.join(
+ file.summary_funs for file in self.files_md if file.summary_funs)
+ self.summary_view_funs = '\n'.join(file.summary_view_funs
+ for file in self.files_md
+ if file.summary_view_funs)
+ self.summary_macros = '\n'.join(
+ file.summary_macros for file in self.files_md if file.summary_macros)
+
+ def get_prelude_description(self) -> str:
+ if not self.module_name == 'prelude':
+ raise ValueError("Only callable on prelude module")
+
+ lines = []
+ lines.append(f'## Module: {self.module_name}')
+
+ # Prelude is a special module which is automatically imported and doesn't
+ # have any include keys.
+ objs = '\n'.join(obj for file in self.files_md for obj in file.objs)
+ if objs:
+ lines.append('#### Views/Tables')
+ lines.append(objs)
+
+ funs = '\n'.join(fun for file in self.files_md for fun in file.funs)
+ if funs:
+ lines.append('#### Functions')
+ lines.append(funs)
+
+ table_funs = '\n'.join(
+ view_fun for file in self.files_md for view_fun in file.view_funs)
+ if table_funs:
+ lines.append('#### Table Functions')
+ lines.append(table_funs)
+
+ macros = '\n'.join(macro for file in self.files_md for macro in file.macros)
+ if macros:
+ lines.append('#### Macros')
+ lines.append(macros)
+
+ return '\n'.join(lines)
+
+ def get_description(self) -> str:
+ if not self.files_md:
+ return ''
+
+ if self.module_name == 'prelude':
+ raise ValueError("Can't be called with prelude module")
+
+ lines = []
+ lines.append(f'## Module: {self.module_name}')
+
+ for file in self.files_md:
+ if not any((file.objs, file.funs, file.view_funs, file.macros)):
+ continue
+
+ lines.append(f'### {file.import_key}')
+ if file.objs:
+ lines.append('#### Views/Tables')
+ lines.append('\n'.join(file.objs))
+ if file.funs:
+ lines.append('#### Functions')
+ lines.append('\n'.join(file.funs))
+ if file.view_funs:
+ lines.append('#### Table Functions')
+ lines.append('\n'.join(file.view_funs))
+ if file.macros:
+ lines.append('#### Macros')
+ lines.append('\n'.join(file.macros))
+
+ return '\n'.join(lines)
+
+
+def main():
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--input', required=True)
+ parser.add_argument('--output', required=True)
+ args = parser.parse_args()
+
+ with open(args.input) as f:
+ modules_json_dict = json.load(f)
+
+ # Fetch the modules from json documentation.
+ modules_dict: Dict[str, ModuleMd] = {}
+ for module_name, module_files in modules_json_dict.items():
+ # Remove 'common' when it has been removed from the code.
+ if module_name not in ['deprecated', 'common']:
+ modules_dict[module_name] = ModuleMd(module_name, module_files)
+
+ prelude_module = modules_dict.pop('prelude')
+
+ with open(args.output, 'w') as f:
+ f.write(INTRODUCTION)
summary_objs = [prelude_module.summary_objs
] if prelude_module.summary_objs else []
summary_objs += [
module.summary_objs
- for name, module in modules_dict.items()
- if (module.summary_objs and name != 'experimental')
+ for module in modules_dict.values()
+ if (module.summary_objs)
]
summary_funs = [prelude_module.summary_funs
] if prelude_module.summary_funs else []
- summary_funs += [
- module.summary_funs
- for name, module in modules_dict.items()
- if (module.summary_funs and name != 'experimental')
- ]
+ summary_funs += [module.summary_funs for module in modules_dict.values()]
summary_view_funs = [prelude_module.summary_view_funs
] if prelude_module.summary_view_funs else []
summary_view_funs += [
- module.summary_view_funs
- for name, module in modules_dict.items()
- if (module.summary_view_funs and name != 'experimental')
+ module.summary_view_funs for module in modules_dict.values()
]
summary_macros = [prelude_module.summary_macros
] if prelude_module.summary_macros else []
summary_macros += [
- module.summary_macros
- for name, module in modules_dict.items()
- if (module.summary_macros and name != 'experimental')
+ module.summary_macros for module in modules_dict.values()
]
if summary_objs:
- f.write('### Views/tables\n\n'
- 'Name | Import | Description\n'
- '---- | ------ | -----------\n')
- f.write('\n'.join(summary_objs))
- f.write('\n')
+ f.write(
+ _write_summary('Views/tables', ['Name', 'Import', 'Description'],
+ summary_objs))
if summary_funs:
- f.write('### Functions\n\n'
- 'Name | Import | Return type | Description\n'
- '---- | ------ | ----------- | -----------\n')
- f.write('\n'.join(summary_funs))
- f.write('\n')
+ f.write(
+ _write_summary('Functions',
+ ['Name', 'Import', 'Return type', 'Description'],
+ summary_funs))
if summary_view_funs:
- f.write('### Table Functions\n\n'
- 'Name | Import | Description\n'
- '---- | ------ | -----------\n')
- f.write('\n'.join(summary_view_funs))
- f.write('\n')
+ f.write(
+ _write_summary('Table functions', ['Name', 'Import', 'Description'],
+ summary_view_funs))
if summary_macros:
- f.write('### Macros\n\n'
- 'Name | Import | Description\n'
- '---- | ------ | -----------\n')
- f.write('\n'.join(summary_macros))
- f.write('\n')
+ f.write(
+ _write_summary('Macros', ['Name', 'Import', 'Description'],
+ summary_macros))
f.write('\n\n')
- f.write(prelude_module.print_description())
+ f.write(prelude_module.get_prelude_description())
f.write('\n')
f.write('\n'.join(
- module.print_description() for module in modules_dict.values()))
+ module.get_description() for module in modules_dict.values()))
return 0
diff --git a/protos/perfetto/trace/ftrace/ftrace_event.proto b/protos/perfetto/trace/ftrace/ftrace_event.proto
index def2a72..5c0cc3e 100644
--- a/protos/perfetto/trace/ftrace/ftrace_event.proto
+++ b/protos/perfetto/trace/ftrace/ftrace_event.proto
@@ -606,5 +606,6 @@
SchedSwitchWithCtrsFtraceEvent sched_switch_with_ctrs = 487;
GpuWorkPeriodFtraceEvent gpu_work_period = 488;
RpmStatusFtraceEvent rpm_status = 489;
+ PanelWriteGenericFtraceEvent panel_write_generic = 490;
}
}
diff --git a/protos/perfetto/trace/ftrace/panel.proto b/protos/perfetto/trace/ftrace/panel.proto
index 5b84b3b..fde72d9 100644
--- a/protos/perfetto/trace/ftrace/panel.proto
+++ b/protos/perfetto/trace/ftrace/panel.proto
@@ -18,3 +18,11 @@
optional uint32 tx_buf = 2;
optional uint32 type = 3;
}
+message PanelWriteGenericFtraceEvent {
+ optional int32 pid = 1;
+ optional string trace_name = 2;
+ optional uint32 trace_begin = 3;
+ optional string name = 4;
+ optional uint32 type = 5;
+ optional int32 value = 6;
+}
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index b657572..97962a7 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -9256,6 +9256,14 @@
optional uint32 tx_buf = 2;
optional uint32 type = 3;
}
+message PanelWriteGenericFtraceEvent {
+ optional int32 pid = 1;
+ optional string trace_name = 2;
+ optional uint32 trace_begin = 3;
+ optional string name = 4;
+ optional uint32 type = 5;
+ optional int32 value = 6;
+}
// End of protos/perfetto/trace/ftrace/panel.proto
@@ -10584,6 +10592,7 @@
SchedSwitchWithCtrsFtraceEvent sched_switch_with_ctrs = 487;
GpuWorkPeriodFtraceEvent gpu_work_period = 488;
RpmStatusFtraceEvent rpm_status = 489;
+ PanelWriteGenericFtraceEvent panel_write_generic = 490;
}
}
diff --git a/python/generators/sql_processing/docs_parse.py b/python/generators/sql_processing/docs_parse.py
index 6e8cd60..9062973 100644
--- a/python/generators/sql_processing/docs_parse.py
+++ b/python/generators/sql_processing/docs_parse.py
@@ -17,7 +17,7 @@
from dataclasses import dataclass
import re
import sys
-from typing import Any, Dict, List, Optional, Set, Tuple, NamedTuple
+from typing import Dict, List, Optional, Set, NamedTuple
from python.generators.sql_processing.docs_extractor import DocsExtractor
from python.generators.sql_processing.utils import ObjKind
@@ -31,30 +31,21 @@
from python.generators.sql_processing.utils import ARG_ANNOTATION_PATTERN
-def is_internal(name: str) -> bool:
+def _is_internal(name: str) -> bool:
return re.match(r'^_.*', name, re.IGNORECASE) is not None
-def is_snake_case(s: str) -> bool:
- """Returns true if the string is snake_case."""
+def _is_snake_case(s: str) -> bool:
return re.fullmatch(r'^[a-z_0-9]*$', s) is not None
-# Parse a SQL comment (i.e. -- Foo\n -- bar.) into a string (i.e. "Foo bar.").
def parse_comment(comment: str) -> str:
+ """Parse a SQL comment (i.e. -- Foo\n -- bar.) into a string (i.e. "Foo bar.")."""
return ' '.join(line.strip().lstrip('--').lstrip()
for line in comment.strip().split('\n'))
-
-class Arg(NamedTuple):
- # TODO(b/307926059): the type is missing on old-style documentation for
- # tables. Make it "str" after stdlib is migrated.
- type: Optional[str]
- description: str
-
-
-# Returns: error message if the name is not correct, None otherwise.
def get_module_prefix_error(name: str, path: str, module: str) -> Optional[str]:
+ """Returns error message if the name is not correct, None otherwise."""
prefix = name.lower().split('_')[0]
if module in ["common", "prelude", "deprecated"]:
if prefix == module:
@@ -77,6 +68,13 @@
f'with one of following names: {", ".join(allowed_prefixes)}')
+class Arg(NamedTuple):
+ # TODO(b/307926059): the type is missing on old-style documentation for
+ # tables. Make it "str" after stdlib is migrated.
+ type: Optional[str]
+ description: str
+
+
class AbstractDocParser(ABC):
@dataclass
@@ -244,7 +242,7 @@
f'{type} "{self.name}": CREATE OR REPLACE is not allowed in stdlib '
f'as standard library modules can only included once. Please just '
f'use CREATE instead.')
- if is_internal(self.name):
+ if _is_internal(self.name):
return None
is_perfetto_table_or_view = (
@@ -294,12 +292,12 @@
f'use CREATE instead.')
# Ignore internal functions.
- if is_internal(self.name):
+ if _is_internal(self.name):
return None
name = self._parse_name()
- if not is_snake_case(name):
+ if not _is_snake_case(name):
self._error(f'Function name "{name}" is not snake_case'
f' (should be {name.casefold()})')
@@ -345,14 +343,14 @@
f'use CREATE instead.')
# Ignore internal functions.
- if is_internal(self.name):
+ if _is_internal(self.name):
return None
self._validate_only_contains_annotations(doc.annotations,
{'@arg', '@column'})
name = self._parse_name()
- if not is_snake_case(name):
+ if not _is_snake_case(name):
self._error(f'Function name "{name}" is not snake_case'
f' (should be "{name.casefold()}")')
@@ -396,13 +394,13 @@
f'use CREATE instead.')
# Ignore internal macros.
- if is_internal(self.name):
+ if _is_internal(self.name):
return None
self._validate_only_contains_annotations(doc.annotations, set())
name = self._parse_name()
- if not is_snake_case(name):
+ if not _is_snake_case(name):
self._error(f'Macro name "{name}" is not snake_case'
f' (should be "{name.casefold()}")')
@@ -416,6 +414,7 @@
class ParsedFile:
+ """Data class containing all of the docmentation of single SQL file"""
errors: List[str] = []
table_views: List[TableOrView] = []
functions: List[Function] = []
@@ -432,9 +431,9 @@
self.macros = macros
-# Reads the provided SQL and, if possible, generates a dictionary with data
-# from documentation together with errors from validation of the schema.
def parse_file(path: str, sql: str) -> Optional[ParsedFile]:
+ """Reads the provided SQL and, if possible, generates a dictionary with data
+ from documentation together with errors from validation of the schema."""
if sys.platform.startswith('win'):
path = path.replace('\\', '/')
diff --git a/src/protozero/BUILD.gn b/src/protozero/BUILD.gn
index 4d940bc..d1285bb 100644
--- a/src/protozero/BUILD.gn
+++ b/src/protozero/BUILD.gn
@@ -61,6 +61,12 @@
":protozero",
":testing_messages_cpp",
":testing_messages_lite",
+ ":testing_messages_other_package_cpp",
+ ":testing_messages_other_package_lite",
+ ":testing_messages_other_package_zero",
+ ":testing_messages_subpackage_cpp",
+ ":testing_messages_subpackage_lite",
+ ":testing_messages_subpackage_zero",
":testing_messages_zero",
"../../gn:default_deps",
"../../gn:gtest_and_gmock",
@@ -87,6 +93,10 @@
# Generates both xxx.pbzero.h and xxx.pb.h (official proto).
perfetto_proto_library("testing_messages_@TYPE@") {
+ deps = [
+ ":testing_messages_other_package_@TYPE@",
+ ":testing_messages_subpackage_@TYPE@",
+ ]
sources = [
"test/example_proto/extensions.proto",
"test/example_proto/library.proto",
@@ -97,6 +107,16 @@
proto_path = perfetto_root_path
}
+perfetto_proto_library("testing_messages_other_package_@TYPE@") {
+ sources = [ "test/example_proto/other_package/test_messages.proto" ]
+ proto_path = perfetto_root_path
+}
+
+perfetto_proto_library("testing_messages_subpackage_@TYPE@") {
+ sources = [ "test/example_proto/subpackage/test_messages.proto" ]
+ proto_path = perfetto_root_path
+}
+
perfetto_proto_library("test_messages_descriptor") {
proto_generators = [ "descriptor" ]
generate_descriptor = "test_messages.descriptor"
diff --git a/src/protozero/protoc_plugin/cppgen_plugin.cc b/src/protozero/protoc_plugin/cppgen_plugin.cc
index f134caa..4257613 100644
--- a/src/protozero/protoc_plugin/cppgen_plugin.cc
+++ b/src/protozero/protoc_plugin/cppgen_plugin.cc
@@ -95,7 +95,13 @@
return full_type;
}
+ template <class T>
+ bool HasSamePackage(const T* descriptor) const {
+ return descriptor->file()->package() == package_;
+ }
+
mutable std::string wrapper_namespace_;
+ mutable std::string package_;
};
CppObjGenerator::CppObjGenerator() = default;
@@ -116,6 +122,8 @@
}
}
+ package_ = file->package();
+
auto get_file_name = [](const FileDescriptor* proto) {
return StripSuffix(proto->name(), ".proto") + ".gen";
};
@@ -372,10 +380,16 @@
return constref ? "const std::string&" : "std::string";
case FieldDescriptor::TYPE_MESSAGE:
assert(!field->options().lazy());
- return constref ? "const " + GetFullName(field->message_type()) + "&"
- : GetFullName(field->message_type());
+ return constref
+ ? "const " +
+ GetFullName(field->message_type(),
+ !HasSamePackage(field->message_type())) +
+ "&"
+ : GetFullName(field->message_type(),
+ !HasSamePackage(field->message_type()));
case FieldDescriptor::TYPE_ENUM:
- return GetFullName(field->enum_type());
+ return GetFullName(field->enum_type(),
+ !HasSamePackage(field->enum_type()));
case FieldDescriptor::TYPE_GROUP:
abort();
}
diff --git a/src/protozero/protoc_plugin/protozero_c_plugin.cc b/src/protozero/protoc_plugin/protozero_c_plugin.cc
index 27fe24f..5bd05dd 100644
--- a/src/protozero/protoc_plugin/protozero_c_plugin.cc
+++ b/src/protozero/protoc_plugin/protozero_c_plugin.cc
@@ -127,24 +127,12 @@
error_ = reason;
}
- // Get full name (including outer descriptors) of proto descriptor.
- template <class T>
- inline std::string GetDescriptorName(const T* descriptor) {
- if (!package_.empty()) {
- return StripPrefix(descriptor->full_name(), package_ + ".");
- }
- return descriptor->full_name();
- }
-
// Get C++ class name corresponding to proto descriptor.
// Nested names are splitted by underscores. Underscores in type names aren't
// prohibited but not recommended in order to avoid name collisions.
template <class T>
- inline std::string GetCppClassName(const T* descriptor, bool full = false) {
- std::string name = StripChars(GetDescriptorName(descriptor), ".", '_');
- if (full)
- name = full_namespace_prefix_ + "_" + name;
- return name;
+ inline std::string GetCppClassName(const T* descriptor) {
+ return StripChars(descriptor->full_name(), ".", '_');
}
const char* FieldTypeToPackedBufferType(FieldDescriptor::Type type) {
@@ -213,7 +201,7 @@
case FieldDescriptor::TYPE_DOUBLE:
return "double";
case FieldDescriptor::TYPE_ENUM:
- return "enum " + GetCppClassName(field->enum_type(), true);
+ return "enum " + GetCppClassName(field->enum_type());
case FieldDescriptor::TYPE_STRING:
case FieldDescriptor::TYPE_BYTES:
return "const char*";
@@ -305,10 +293,6 @@
while (!stack.empty()) {
const FileDescriptor* imp = stack.back();
stack.pop_back();
- // Having imports under different packages leads to unnecessary
- // complexity with namespaces.
- if (imp->package() != package_)
- Abort("Imported proto must be in the same package.");
for (int i = 0; i < imp->public_dependency_count(); ++i) {
stack.push_back(imp->public_dependency(i));
@@ -427,7 +411,7 @@
// Print forward declarations.
for (const Descriptor* message : referenced_messages_) {
stub_h_->Print("PERFETTO_PB_MSG_DECL($class$);\n", "class",
- GetCppClassName(message, true));
+ GetCppClassName(message));
}
stub_h_->Print("\n");
@@ -436,11 +420,11 @@
void GenerateEnumDescriptor(const EnumDescriptor* enumeration) {
if (enumeration->containing_type()) {
stub_h_->Print("PERFETTO_PB_ENUM_IN_MSG($msg$, $class$){\n", "msg",
- GetCppClassName(enumeration->containing_type(), true),
- "class", enumeration->name());
+ GetCppClassName(enumeration->containing_type()), "class",
+ enumeration->name());
} else {
stub_h_->Print("PERFETTO_PB_ENUM($class$){\n", "class",
- GetCppClassName(enumeration, true));
+ GetCppClassName(enumeration));
}
stub_h_->Indent();
@@ -451,8 +435,8 @@
if (enumeration->containing_type()) {
stub_h_->Print(
"PERFETTO_PB_ENUM_IN_MSG_ENTRY($msg$, $val$) = $number$,\n", "msg",
- GetCppClassName(enumeration->containing_type(), true), "val",
- value_name, "number", std::to_string(value->number()));
+ GetCppClassName(enumeration->containing_type()), "val", value_name,
+ "number", std::to_string(value->number()));
} else {
stub_h_->Print("PERFETTO_PB_ENUM_ENTRY($val$) = $number$, \n", "val",
full_namespace_prefix_ + "_" + value_name, "number",
@@ -532,8 +516,7 @@
void GenerateNestedMessageFieldDescriptor(const std::string& message_cpp_type,
const FieldDescriptor* field) {
- std::string inner_class =
- full_namespace_prefix_ + "_" + GetCppClassName(field->message_type());
+ std::string inner_class = GetCppClassName(field->message_type());
stub_h_->Print(
"PERFETTO_PB_FIELD($class$, MSG, $inner_class$, $name$, $id$);\n",
"class", message_cpp_type, "id", std::to_string(field->number()),
@@ -542,12 +525,11 @@
void GenerateMessageDescriptor(const Descriptor* message) {
stub_h_->Print("PERFETTO_PB_MSG($name$);\n", "name",
- GetCppClassName(message, true));
+ GetCppClassName(message));
// Field descriptors.
for (int i = 0; i < message->field_count(); ++i) {
- GenerateFieldDescriptor(GetCppClassName(message, true),
- message->field(i));
+ GenerateFieldDescriptor(GetCppClassName(message), message->field(i));
}
stub_h_->Print("\n");
}
diff --git a/src/protozero/protoc_plugin/protozero_plugin.cc b/src/protozero/protoc_plugin/protozero_plugin.cc
index 7d46a50..5b08292 100644
--- a/src/protozero/protoc_plugin/protozero_plugin.cc
+++ b/src/protozero/protoc_plugin/protozero_plugin.cc
@@ -42,6 +42,7 @@
using google::protobuf::compiler::GeneratorContext;
using google::protobuf::io::Printer;
using google::protobuf::io::ZeroCopyOutputStream;
+using perfetto::base::ReplaceAll;
using perfetto::base::SplitString;
using perfetto::base::StripChars;
using perfetto::base::StripPrefix;
@@ -126,14 +127,9 @@
error_ = reason;
}
- // Get full name (including outer descriptors) of proto descriptor.
template <class T>
- inline std::string GetDescriptorName(const T* descriptor) {
- if (!package_.empty()) {
- return StripPrefix(descriptor->full_name(), package_ + ".");
- } else {
- return descriptor->full_name();
- }
+ bool HasSamePackage(const T* descriptor) const {
+ return descriptor->file()->package() == package_;
}
// Get C++ class name corresponding to proto descriptor.
@@ -141,9 +137,28 @@
// prohibited but not recommended in order to avoid name collisions.
template <class T>
inline std::string GetCppClassName(const T* descriptor, bool full = false) {
- std::string name = StripChars(GetDescriptorName(descriptor), ".", '_');
- if (full)
- name = full_namespace_prefix_ + name;
+ std::string package = descriptor->file()->package();
+ std::string name = StripPrefix(descriptor->full_name(), package + ".");
+ name = StripChars(name, ".", '_');
+
+ if (full && !package.empty()) {
+ auto get_full_namespace = [&]() {
+ std::vector<std::string> namespaces = SplitString(package, ".");
+ if (!wrapper_namespace_.empty())
+ namespaces.push_back(wrapper_namespace_);
+
+ std::string result = "";
+ for (const std::string& ns : namespaces) {
+ result += "::";
+ result += ns;
+ }
+ return result;
+ };
+
+ std::string namespaces = ReplaceAll(package, ".", "::");
+ name = get_full_namespace() + "::" + name;
+ }
+
return name;
}
@@ -305,12 +320,14 @@
case FieldDescriptor::TYPE_DOUBLE:
return "double";
case FieldDescriptor::TYPE_ENUM:
- return GetCppClassName(field->enum_type(), true);
+ return GetCppClassName(field->enum_type(),
+ !HasSamePackage(field->enum_type()));
case FieldDescriptor::TYPE_STRING:
case FieldDescriptor::TYPE_BYTES:
return "std::string";
case FieldDescriptor::TYPE_MESSAGE:
- return GetCppClassName(field->message_type());
+ return GetCppClassName(field->message_type(),
+ !HasSamePackage(field->message_type()));
case FieldDescriptor::TYPE_GROUP:
Abort("Groups not supported.");
return "";
@@ -405,11 +422,6 @@
while (!stack.empty()) {
const FileDescriptor* import = stack.back();
stack.pop_back();
- // Having imports under different packages leads to unnecessary
- // complexity with namespaces.
- if (import->package() != package_)
- Abort("Imported proto must be in the same package.");
-
for (int i = 0; i < import->public_dependency_count(); ++i) {
stack.push_back(import->public_dependency(i));
}
@@ -504,32 +516,70 @@
}
stub_h_->Print("\n");
+ PrintForwardDeclarations();
+
// Print namespaces.
for (const std::string& ns : namespaces_) {
stub_h_->Print("namespace $ns$ {\n", "ns", ns);
}
stub_h_->Print("\n");
+ }
- // Print forward declarations.
+ void PrintForwardDeclarations() {
+ struct Descriptors {
+ std::vector<const Descriptor*> messages_;
+ std::vector<const EnumDescriptor*> enums_;
+ };
+ std::map<std::string, Descriptors> package_to_descriptors;
+
for (const Descriptor* message : referenced_messages_) {
- stub_h_->Print("class $class$;\n", "class", GetCppClassName(message));
+ package_to_descriptors[message->file()->package()].messages_.push_back(
+ message);
}
- for (const EnumDescriptor* enumeration : referenced_enums_) {
- if (enumeration->containing_type()) {
- stub_h_->Print("namespace $namespace_name$ {\n", "namespace_name",
- GetNamespaceNameForInnerEnum(enumeration));
- }
- stub_h_->Print("enum $class$ : int32_t;\n", "class", enumeration->name());
- if (enumeration->containing_type()) {
- stub_h_->Print("} // namespace $namespace_name$\n", "namespace_name",
- GetNamespaceNameForInnerEnum(enumeration));
- stub_h_->Print("using $alias$ = $namespace_name$::$short_name$;\n",
- "alias", GetCppClassName(enumeration), "namespace_name",
- GetNamespaceNameForInnerEnum(enumeration), "short_name",
+ for (const EnumDescriptor* enumeration : referenced_enums_) {
+ package_to_descriptors[enumeration->file()->package()].enums_.push_back(
+ enumeration);
+ }
+
+ for (const auto& [package, descriptors] : package_to_descriptors) {
+ std::vector<std::string> namespaces = SplitString(package, ".");
+ namespaces.push_back(wrapper_namespace_);
+
+ // open namespaces
+ for (const auto& ns : namespaces) {
+ stub_h_->Print("namespace $ns$ {\n", "ns", ns);
+ }
+
+ for (const Descriptor* message : descriptors.messages_) {
+ stub_h_->Print("class $class$;\n", "class", GetCppClassName(message));
+ }
+
+ for (const EnumDescriptor* enumeration : descriptors.enums_) {
+ if (enumeration->containing_type()) {
+ stub_h_->Print("namespace $namespace_name$ {\n", "namespace_name",
+ GetNamespaceNameForInnerEnum(enumeration));
+ }
+ stub_h_->Print("enum $class$ : int32_t;\n", "class",
enumeration->name());
+
+ if (enumeration->containing_type()) {
+ stub_h_->Print("} // namespace $namespace_name$\n", "namespace_name",
+ GetNamespaceNameForInnerEnum(enumeration));
+ stub_h_->Print("using $alias$ = $namespace_name$::$short_name$;\n",
+ "alias", GetCppClassName(enumeration),
+ "namespace_name",
+ GetNamespaceNameForInnerEnum(enumeration),
+ "short_name", enumeration->name());
+ }
+ }
+
+ // close namespaces
+ for (auto it = namespaces.crbegin(); it != namespaces.crend(); ++it) {
+ stub_h_->Print("} // Namespace $ns$.\n", "ns", *it);
}
}
+
stub_h_->Print("\n");
}
@@ -675,7 +725,8 @@
void GenerateNestedMessageFieldDescriptor(const FieldDescriptor* field) {
std::string action = field->is_repeated() ? "add" : "set";
- std::string inner_class = GetCppClassName(field->message_type());
+ std::string inner_class = GetCppClassName(
+ field->message_type(), !HasSamePackage(field->message_type()));
stub_h_->Print(
"template <typename T = $inner_class$> T* $action$_$name$() {\n"
" return BeginNestedMessage<T>($id$);\n"
diff --git a/src/protozero/test/cppgen_conformance_unittest.cc b/src/protozero/test/cppgen_conformance_unittest.cc
index 5ac43aa..d8d284e 100644
--- a/src/protozero/test/cppgen_conformance_unittest.cc
+++ b/src/protozero/test/cppgen_conformance_unittest.cc
@@ -24,14 +24,20 @@
// Autogenerated headers in out/*/gen/
#include "src/protozero/test/example_proto/library.gen.h"
+#include "src/protozero/test/example_proto/other_package/test_messages.gen.h"
+#include "src/protozero/test/example_proto/subpackage/test_messages.gen.h"
#include "src/protozero/test/example_proto/test_messages.gen.h"
#include "src/protozero/test/example_proto/test_messages.pb.h"
// Generated by the cppgen compiler.
namespace pbtest = protozero::test::protos::gen;
+namespace pbtest_subpackage = protozero::test::protos::subpackage::gen;
+namespace pbtest_otherpackage = other_package::gen;
// Generated by the official protobuf compiler.
namespace pbgold = protozero::test::protos;
+namespace pbgold_subpackage = protozero::test::protos::subpackage;
+namespace pbgold_other_package = other_package;
namespace protozero {
namespace {
@@ -339,5 +345,57 @@
ASSERT_THAT(msg.field_sfixed64(), ElementsAreArray(exp_sfixed64));
}
}
+
+TEST(ProtoCppConformanceTest, DifferentPackages) {
+ pbtest::DifferentPackages msg;
+
+ // Pupulate fields defined in "protozero.test.protos.subpackage"
+ pbtest_subpackage::Message* msgSubpackage = msg.mutable_subpackage_message();
+ msgSubpackage->set_field_int32(1);
+ msgSubpackage->set_field_enum(pbtest_subpackage::Enum::A);
+ msgSubpackage->set_field_nested_enum(pbtest_subpackage::Message_NestedEnum_C);
+ msg.mutable_subpackage_nested_message()->set_field_int32(2);
+ msg.set_subpackage_enum(pbtest_subpackage::Enum::B);
+ msg.set_subpackage_nested_enum(pbtest_subpackage::Message_NestedEnum_D);
+
+ // Pupulate fields defined in "other_package"
+ pbtest_otherpackage::Message* msgOtherPackage =
+ msg.mutable_otherpackage_message();
+ msgOtherPackage->set_field_int32(11);
+ msgOtherPackage->set_field_enum(pbtest_otherpackage::Enum::A);
+ msgOtherPackage->set_field_nested_enum(
+ pbtest_otherpackage::Message_NestedEnum_C);
+ msg.mutable_otherpackage_nested_message()->set_field_int32(12);
+ msg.set_otherpackage_enum(pbtest_otherpackage::Enum::B);
+ msg.set_otherpackage_nested_enum(pbtest_otherpackage::Message_NestedEnum_D);
+
+ // Deserialize into golden proto
+ std::string serialized = msg.SerializeAsString();
+ pbgold::DifferentPackages gold_msg;
+ gold_msg.ParseFromString(serialized);
+ EXPECT_EQ(serialized.size(), static_cast<size_t>(gold_msg.ByteSizeLong()));
+
+ // Check fields defined in "protozero.test.protos.subpackage"
+ EXPECT_EQ(1, gold_msg.subpackage_message().field_int32());
+ EXPECT_EQ(pbgold_subpackage::Enum::A,
+ gold_msg.subpackage_message().field_enum());
+ EXPECT_EQ(pbgold_subpackage::Message_NestedEnum_C,
+ gold_msg.subpackage_message().field_nested_enum());
+ EXPECT_EQ(2, gold_msg.subpackage_nested_message().field_int32());
+ EXPECT_EQ(pbgold_subpackage::Enum::B, gold_msg.subpackage_enum());
+ EXPECT_EQ(pbgold_subpackage::Message_NestedEnum_D,
+ gold_msg.subpackage_nested_enum());
+
+ // Check fields defined in "other_package"
+ EXPECT_EQ(11, gold_msg.otherpackage_message().field_int32());
+ EXPECT_EQ(pbgold_other_package::Enum::A,
+ gold_msg.otherpackage_message().field_enum());
+ EXPECT_EQ(pbgold_other_package::Message_NestedEnum_C,
+ gold_msg.otherpackage_message().field_nested_enum());
+ EXPECT_EQ(12, gold_msg.otherpackage_nested_message().field_int32());
+ EXPECT_EQ(pbgold_other_package::Enum::B, gold_msg.otherpackage_enum());
+ EXPECT_EQ(pbgold_other_package::Message_NestedEnum_D,
+ gold_msg.otherpackage_nested_enum());
+}
} // namespace
} // namespace protozero
diff --git a/src/protozero/test/example_proto/other_package/test_messages.proto b/src/protozero/test/example_proto/other_package/test_messages.proto
new file mode 100644
index 0000000..d299082
--- /dev/null
+++ b/src/protozero/test/example_proto/other_package/test_messages.proto
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+
+package other_package;
+
+enum Enum {
+ A = 10;
+ B = 11;
+}
+
+message Message {
+ message NestedMessage {
+ optional int32 field_int32 = 1;
+ }
+
+ enum NestedEnum {
+ C = 12;
+ D = 13;
+ }
+
+ optional int32 field_int32 = 1;
+ optional Enum field_enum = 2;
+ optional NestedEnum field_nested_enum = 3;
+ optional NestedMessage field_nested_message = 4;
+}
diff --git a/src/protozero/test/example_proto/subpackage/test_messages.proto b/src/protozero/test/example_proto/subpackage/test_messages.proto
new file mode 100644
index 0000000..6f7c0cc
--- /dev/null
+++ b/src/protozero/test/example_proto/subpackage/test_messages.proto
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+
+package protozero.test.protos.subpackage;
+
+enum Enum {
+ A = 1;
+ B = 2;
+}
+
+message Message {
+ message NestedMessage {
+ optional int32 field_int32 = 1;
+ }
+
+ enum NestedEnum {
+ C = 3;
+ D = 4;
+ }
+
+ optional int32 field_int32 = 1;
+ optional Enum field_enum = 2;
+ optional NestedEnum field_nested_enum = 3;
+ optional NestedMessage field_nested_message = 4;
+}
diff --git a/src/protozero/test/example_proto/test_messages.proto b/src/protozero/test/example_proto/test_messages.proto
index e6ece33..b54d883 100644
--- a/src/protozero/test/example_proto/test_messages.proto
+++ b/src/protozero/test/example_proto/test_messages.proto
@@ -19,6 +19,8 @@
package protozero.test.protos;
import "src/protozero/test/example_proto/library.proto";
+import "src/protozero/test/example_proto/other_package/test_messages.proto";
+import "src/protozero/test/example_proto/subpackage/test_messages.proto";
// This file contains comprehensive set of supported message structures and
// data types. Unit tests depends on the plugin-processed version of this file.
@@ -179,3 +181,15 @@
repeated Sub2_V2 sub2_rep = 12;
optional Sub2_V2 sub2_lazy = 13 [lazy = true];
}
+
+message DifferentPackages {
+ optional subpackage.Message subpackage_message = 1;
+ optional subpackage.Message.NestedMessage subpackage_nested_message = 2;
+ optional subpackage.Enum subpackage_enum = 3;
+ optional subpackage.Message.NestedEnum subpackage_nested_enum = 4;
+
+ optional .other_package.Message otherpackage_message = 5;
+ optional .other_package.Message.NestedMessage otherpackage_nested_message = 6;
+ optional .other_package.Enum otherpackage_enum = 7;
+ optional .other_package.Message.NestedEnum otherpackage_nested_enum = 8;
+}
diff --git a/src/protozero/test/protozero_conformance_unittest.cc b/src/protozero/test/protozero_conformance_unittest.cc
index 74e2204..3a4b7cf 100644
--- a/src/protozero/test/protozero_conformance_unittest.cc
+++ b/src/protozero/test/protozero_conformance_unittest.cc
@@ -27,14 +27,20 @@
#include "src/protozero/test/example_proto/extensions.pb.h"
#include "src/protozero/test/example_proto/extensions.pbzero.h"
#include "src/protozero/test/example_proto/library.pbzero.h"
+#include "src/protozero/test/example_proto/other_package/test_messages.pbzero.h"
+#include "src/protozero/test/example_proto/subpackage/test_messages.pbzero.h"
#include "src/protozero/test/example_proto/test_messages.pb.h"
#include "src/protozero/test/example_proto/test_messages.pbzero.h"
// Generated by the protozero plugin.
namespace pbtest = protozero::test::protos::pbzero;
+namespace pbtest_subpackage = protozero::test::protos::subpackage::pbzero;
+namespace pbtest_otherpackage = other_package::pbzero;
// Generated by the official protobuf compiler.
namespace pbgold = protozero::test::protos;
+namespace pbgold_subpackage = protozero::test::protos::subpackage;
+namespace pbgold_other_package = other_package;
namespace protozero {
namespace {
@@ -310,5 +316,58 @@
"PING");
}
+TEST(ProtoZeroConformanceTest, DifferentPackages) {
+ HeapBuffered<pbtest::DifferentPackages> msg{kChunkSize, kChunkSize};
+
+ // Pupulate fields defined in "protozero.test.protos.subpackage"
+ pbtest_subpackage::Message* msgSubpackage = msg->set_subpackage_message();
+ msgSubpackage->set_field_int32(1);
+ msgSubpackage->set_field_enum(pbtest_subpackage::Enum::A);
+ msgSubpackage->set_field_nested_enum(
+ pbtest_subpackage::Message::NestedEnum::C);
+ msg->set_subpackage_nested_message()->set_field_int32(2);
+ msg->set_subpackage_enum(pbtest_subpackage::Enum::B);
+ msg->set_subpackage_nested_enum(pbtest_subpackage::Message_NestedEnum::D);
+
+ // Pupulate fields defined in "other_package"
+ pbtest_otherpackage::Message* msgOtherPackage =
+ msg->set_otherpackage_message();
+ msgOtherPackage->set_field_int32(11);
+ msgOtherPackage->set_field_enum(pbtest_otherpackage::Enum::A);
+ msgOtherPackage->set_field_nested_enum(
+ pbtest_otherpackage::Message::NestedEnum::C);
+ msg->set_otherpackage_nested_message()->set_field_int32(12);
+ msg->set_otherpackage_enum(pbtest_otherpackage::Enum::B);
+ msg->set_otherpackage_nested_enum(pbtest_otherpackage::Message_NestedEnum::D);
+
+ // Deserialize into golden proto
+ std::string serialized = msg.SerializeAsString();
+ pbgold::DifferentPackages gold_msg;
+ gold_msg.ParseFromString(serialized);
+ EXPECT_EQ(serialized.size(), static_cast<size_t>(gold_msg.ByteSizeLong()));
+
+ // Check fields defined in "protozero.test.protos.subpackage"
+ EXPECT_EQ(1, gold_msg.subpackage_message().field_int32());
+ EXPECT_EQ(pbgold_subpackage::Enum::A,
+ gold_msg.subpackage_message().field_enum());
+ EXPECT_EQ(pbgold_subpackage::Message_NestedEnum_C,
+ gold_msg.subpackage_message().field_nested_enum());
+ EXPECT_EQ(2, gold_msg.subpackage_nested_message().field_int32());
+ EXPECT_EQ(pbgold_subpackage::Enum::B, gold_msg.subpackage_enum());
+ EXPECT_EQ(pbgold_subpackage::Message_NestedEnum_D,
+ gold_msg.subpackage_nested_enum());
+
+ // Check fields defined in "other_package"
+ EXPECT_EQ(11, gold_msg.otherpackage_message().field_int32());
+ EXPECT_EQ(pbgold_other_package::Enum::A,
+ gold_msg.otherpackage_message().field_enum());
+ EXPECT_EQ(pbgold_other_package::Message_NestedEnum_C,
+ gold_msg.otherpackage_message().field_nested_enum());
+ EXPECT_EQ(12, gold_msg.otherpackage_nested_message().field_int32());
+ EXPECT_EQ(pbgold_other_package::Enum::B, gold_msg.otherpackage_enum());
+ EXPECT_EQ(pbgold_other_package::Message_NestedEnum_D,
+ gold_msg.otherpackage_nested_enum());
+}
+
} // namespace
} // namespace protozero
diff --git a/src/shared_lib/test/protos/other_package/test_messages.pzc.h b/src/shared_lib/test/protos/other_package/test_messages.pzc.h
new file mode 100644
index 0000000..f6a8260
--- /dev/null
+++ b/src/shared_lib/test/protos/other_package/test_messages.pzc.h
@@ -0,0 +1,65 @@
+/*
+ * 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.
+ */
+
+// Autogenerated by the ProtoZero C compiler plugin.
+// Invoked by tools/gen_c_protos
+// DO NOT EDIT.
+#ifndef SRC_SHARED_LIB_TEST_PROTOS_OTHER_PACKAGE_TEST_MESSAGES_PZC_H_
+#define SRC_SHARED_LIB_TEST_PROTOS_OTHER_PACKAGE_TEST_MESSAGES_PZC_H_
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "perfetto/public/pb_macros.h"
+
+PERFETTO_PB_MSG_DECL(other_package_Message_NestedMessage);
+
+PERFETTO_PB_ENUM(other_package_Enum){
+ PERFETTO_PB_ENUM_ENTRY(other_package_A) = 10,
+ PERFETTO_PB_ENUM_ENTRY(other_package_B) = 11,
+};
+
+PERFETTO_PB_ENUM_IN_MSG(other_package_Message, NestedEnum){
+ PERFETTO_PB_ENUM_IN_MSG_ENTRY(other_package_Message, C) = 12,
+ PERFETTO_PB_ENUM_IN_MSG_ENTRY(other_package_Message, D) = 13,
+};
+
+PERFETTO_PB_MSG(other_package_Message);
+PERFETTO_PB_FIELD(other_package_Message, VARINT, int32_t, field_int32, 1);
+PERFETTO_PB_FIELD(other_package_Message,
+ VARINT,
+ enum other_package_Enum,
+ field_enum,
+ 2);
+PERFETTO_PB_FIELD(other_package_Message,
+ VARINT,
+ enum other_package_Message_NestedEnum,
+ field_nested_enum,
+ 3);
+PERFETTO_PB_FIELD(other_package_Message,
+ MSG,
+ other_package_Message_NestedMessage,
+ field_nested_message,
+ 4);
+
+PERFETTO_PB_MSG(other_package_Message_NestedMessage);
+PERFETTO_PB_FIELD(other_package_Message_NestedMessage,
+ VARINT,
+ int32_t,
+ field_int32,
+ 1);
+
+#endif // SRC_SHARED_LIB_TEST_PROTOS_OTHER_PACKAGE_TEST_MESSAGES_PZC_H_
diff --git a/src/shared_lib/test/protos/subpackage/test_messages.pzc.h b/src/shared_lib/test/protos/subpackage/test_messages.pzc.h
new file mode 100644
index 0000000..24f963a
--- /dev/null
+++ b/src/shared_lib/test/protos/subpackage/test_messages.pzc.h
@@ -0,0 +1,71 @@
+/*
+ * 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.
+ */
+
+// Autogenerated by the ProtoZero C compiler plugin.
+// Invoked by tools/gen_c_protos
+// DO NOT EDIT.
+#ifndef SRC_SHARED_LIB_TEST_PROTOS_SUBPACKAGE_TEST_MESSAGES_PZC_H_
+#define SRC_SHARED_LIB_TEST_PROTOS_SUBPACKAGE_TEST_MESSAGES_PZC_H_
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "perfetto/public/pb_macros.h"
+
+PERFETTO_PB_MSG_DECL(protozero_test_protos_subpackage_Message_NestedMessage);
+
+PERFETTO_PB_ENUM(protozero_test_protos_subpackage_Enum){
+ PERFETTO_PB_ENUM_ENTRY(protozero_test_protos_subpackage_A) = 1,
+ PERFETTO_PB_ENUM_ENTRY(protozero_test_protos_subpackage_B) = 2,
+};
+
+PERFETTO_PB_ENUM_IN_MSG(protozero_test_protos_subpackage_Message, NestedEnum){
+ PERFETTO_PB_ENUM_IN_MSG_ENTRY(protozero_test_protos_subpackage_Message,
+ C) = 3,
+ PERFETTO_PB_ENUM_IN_MSG_ENTRY(protozero_test_protos_subpackage_Message,
+ D) = 4,
+};
+
+PERFETTO_PB_MSG(protozero_test_protos_subpackage_Message);
+PERFETTO_PB_FIELD(protozero_test_protos_subpackage_Message,
+ VARINT,
+ int32_t,
+ field_int32,
+ 1);
+PERFETTO_PB_FIELD(protozero_test_protos_subpackage_Message,
+ VARINT,
+ enum protozero_test_protos_subpackage_Enum,
+ field_enum,
+ 2);
+PERFETTO_PB_FIELD(protozero_test_protos_subpackage_Message,
+ VARINT,
+ enum protozero_test_protos_subpackage_Message_NestedEnum,
+ field_nested_enum,
+ 3);
+PERFETTO_PB_FIELD(protozero_test_protos_subpackage_Message,
+ MSG,
+ protozero_test_protos_subpackage_Message_NestedMessage,
+ field_nested_message,
+ 4);
+
+PERFETTO_PB_MSG(protozero_test_protos_subpackage_Message_NestedMessage);
+PERFETTO_PB_FIELD(protozero_test_protos_subpackage_Message_NestedMessage,
+ VARINT,
+ int32_t,
+ field_int32,
+ 1);
+
+#endif // SRC_SHARED_LIB_TEST_PROTOS_SUBPACKAGE_TEST_MESSAGES_PZC_H_
diff --git a/src/shared_lib/test/protos/test_messages.pzc.h b/src/shared_lib/test/protos/test_messages.pzc.h
index 5198ee7..e566576 100644
--- a/src/shared_lib/test/protos/test_messages.pzc.h
+++ b/src/shared_lib/test/protos/test_messages.pzc.h
@@ -25,7 +25,11 @@
#include "perfetto/public/pb_macros.h"
#include "src/shared_lib/test/protos/library.pzc.h"
+#include "src/shared_lib/test/protos/other_package/test_messages.pzc.h"
+#include "src/shared_lib/test/protos/subpackage/test_messages.pzc.h"
+PERFETTO_PB_MSG_DECL(other_package_Message);
+PERFETTO_PB_MSG_DECL(other_package_Message_NestedMessage);
PERFETTO_PB_MSG_DECL(protozero_test_protos_EveryField);
PERFETTO_PB_MSG_DECL(protozero_test_protos_NestedA_NestedB);
PERFETTO_PB_MSG_DECL(protozero_test_protos_NestedA_NestedB_NestedC);
@@ -33,6 +37,8 @@
PERFETTO_PB_MSG_DECL(protozero_test_protos_TestVersioning_V2_Sub1_V2);
PERFETTO_PB_MSG_DECL(protozero_test_protos_TestVersioning_V2_Sub2_V2);
PERFETTO_PB_MSG_DECL(protozero_test_protos_TransgalacticMessage);
+PERFETTO_PB_MSG_DECL(protozero_test_protos_subpackage_Message);
+PERFETTO_PB_MSG_DECL(protozero_test_protos_subpackage_Message_NestedMessage);
PERFETTO_PB_ENUM(protozero_test_protos_SmallEnum){
PERFETTO_PB_ENUM_ENTRY(protozero_test_protos_TO_BE) = 1,
@@ -71,6 +77,48 @@
PERFETTO_PB_ENUM_IN_MSG_ENTRY(protozero_test_protos_EveryField, PONG) = 2,
};
+PERFETTO_PB_MSG(protozero_test_protos_DifferentPackages);
+PERFETTO_PB_FIELD(protozero_test_protos_DifferentPackages,
+ MSG,
+ protozero_test_protos_subpackage_Message,
+ subpackage_message,
+ 1);
+PERFETTO_PB_FIELD(protozero_test_protos_DifferentPackages,
+ MSG,
+ protozero_test_protos_subpackage_Message_NestedMessage,
+ subpackage_nested_message,
+ 2);
+PERFETTO_PB_FIELD(protozero_test_protos_DifferentPackages,
+ VARINT,
+ enum protozero_test_protos_subpackage_Enum,
+ subpackage_enum,
+ 3);
+PERFETTO_PB_FIELD(protozero_test_protos_DifferentPackages,
+ VARINT,
+ enum protozero_test_protos_subpackage_Message_NestedEnum,
+ subpackage_nested_enum,
+ 4);
+PERFETTO_PB_FIELD(protozero_test_protos_DifferentPackages,
+ MSG,
+ other_package_Message,
+ otherpackage_message,
+ 5);
+PERFETTO_PB_FIELD(protozero_test_protos_DifferentPackages,
+ MSG,
+ other_package_Message_NestedMessage,
+ otherpackage_nested_message,
+ 6);
+PERFETTO_PB_FIELD(protozero_test_protos_DifferentPackages,
+ VARINT,
+ enum other_package_Enum,
+ otherpackage_enum,
+ 7);
+PERFETTO_PB_FIELD(protozero_test_protos_DifferentPackages,
+ VARINT,
+ enum other_package_Message_NestedEnum,
+ otherpackage_nested_enum,
+ 8);
+
PERFETTO_PB_MSG(protozero_test_protos_TestVersioning_V2);
PERFETTO_PB_FIELD(protozero_test_protos_TestVersioning_V2,
VARINT,
diff --git a/src/tools/ftrace_proto_gen/event_list b/src/tools/ftrace_proto_gen/event_list
index b4c80bc..ccb0eee 100644
--- a/src/tools/ftrace_proto_gen/event_list
+++ b/src/tools/ftrace_proto_gen/event_list
@@ -484,3 +484,4 @@
perf_trace_counters/sched_switch_with_ctrs
power/gpu_work_period
rpm/rpm_status
+panel/panel_write_generic
\ No newline at end of file
diff --git a/src/trace_processor/importers/common/sched_event_tracker.h b/src/trace_processor/importers/common/sched_event_tracker.h
index f83ffb6..3eba6ae 100644
--- a/src/trace_processor/importers/common/sched_event_tracker.h
+++ b/src/trace_processor/importers/common/sched_event_tracker.h
@@ -17,12 +17,10 @@
#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_SCHED_EVENT_TRACKER_H_
#define SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_SCHED_EVENT_TRACKER_H_
-#include "perfetto/ext/base/string_view.h"
#include "src/trace_processor/importers/common/event_tracker.h"
-#include "src/trace_processor/importers/common/system_info_tracker.h"
+
#include "src/trace_processor/storage/trace_storage.h"
#include "src/trace_processor/types/destructible.h"
-#include "src/trace_processor/types/task_state.h"
#include "src/trace_processor/types/trace_processor_context.h"
namespace perfetto {
@@ -84,20 +82,6 @@
slices->mutable_end_state()->Set(pending_slice_idx, prev_state);
}
- // TODO(rsavitski): fold back into ftrace parser, this is specific to Linux.
- PERFETTO_ALWAYS_INLINE
- StringId TaskStateToStringId(int64_t task_state_int) {
- using ftrace_utils::TaskState;
-
- std::optional<VersionNumber> kernel_version =
- SystemInfoTracker::GetOrCreate(context_)->GetKernelVersion();
- TaskState task_state = TaskState::FromRawPrevState(
- static_cast<uint16_t>(task_state_int), kernel_version);
- return task_state.is_valid()
- ? context_->storage->InternString(task_state.ToString().data())
- : kNullStringId;
- }
-
private:
TraceProcessorContext* const context_;
};
diff --git a/src/trace_processor/importers/ftrace/ftrace_descriptors.cc b/src/trace_processor/importers/ftrace/ftrace_descriptors.cc
index a88cc26..21435f2 100644
--- a/src/trace_processor/importers/ftrace/ftrace_descriptors.cc
+++ b/src/trace_processor/importers/ftrace/ftrace_descriptors.cc
@@ -24,7 +24,7 @@
namespace trace_processor {
namespace {
-std::array<FtraceMessageDescriptor, 490> descriptors{{
+std::array<FtraceMessageDescriptor, 491> descriptors{{
{nullptr, 0, {}},
{nullptr, 0, {}},
{nullptr, 0, {}},
@@ -5396,6 +5396,19 @@
{"status", ProtoSchemaType::kInt32},
},
},
+ {
+ "panel_write_generic",
+ 6,
+ {
+ {},
+ {"pid", ProtoSchemaType::kInt32},
+ {"trace_name", ProtoSchemaType::kString},
+ {"trace_begin", ProtoSchemaType::kUint32},
+ {"name", ProtoSchemaType::kString},
+ {"type", ProtoSchemaType::kUint32},
+ {"value", ProtoSchemaType::kInt32},
+ },
+ },
}};
} // namespace
diff --git a/src/trace_processor/importers/ftrace/ftrace_parser.cc b/src/trace_processor/importers/ftrace/ftrace_parser.cc
index 95cf0d8..6e1e1bb 100644
--- a/src/trace_processor/importers/ftrace/ftrace_parser.cc
+++ b/src/trace_processor/importers/ftrace/ftrace_parser.cc
@@ -1319,7 +1319,7 @@
StringId name_id = message_strings.field_name_ids[field_id];
// Check if this field represents a kernel function.
- const auto* it = std::find_if(
+ const auto it = std::find_if(
kKernelFunctionFields.begin(), kKernelFunctionFields.end(),
[ftrace_id, field_id](const FtraceEventAndFieldId& ev) {
return ev.event_id == ftrace_id && ev.field_id == field_id;
diff --git a/src/trace_processor/importers/ftrace/ftrace_sched_event_tracker.cc b/src/trace_processor/importers/ftrace/ftrace_sched_event_tracker.cc
index 67c3691..1a4f5d7 100644
--- a/src/trace_processor/importers/ftrace/ftrace_sched_event_tracker.cc
+++ b/src/trace_processor/importers/ftrace/ftrace_sched_event_tracker.cc
@@ -18,14 +18,17 @@
#include <limits>
+#include "perfetto/ext/base/string_view.h"
#include "src/trace_processor/importers/common/args_tracker.h"
#include "src/trace_processor/importers/common/event_tracker.h"
#include "src/trace_processor/importers/common/process_tracker.h"
#include "src/trace_processor/importers/common/sched_event_state.h"
#include "src/trace_processor/importers/common/sched_event_tracker.h"
+#include "src/trace_processor/importers/common/system_info_tracker.h"
#include "src/trace_processor/importers/common/thread_state_tracker.h"
#include "src/trace_processor/importers/ftrace/ftrace_descriptors.h"
#include "src/trace_processor/storage/stats.h"
+#include "src/trace_processor/types/task_state.h"
#include "src/trace_processor/types/trace_processor_context.h"
#include "src/trace_processor/types/variadic.h"
@@ -84,8 +87,7 @@
bool prev_pid_match_prev_next_pid = false;
auto* pending_sched = sched_event_state_.GetPendingSchedInfoForCpu(cpu);
uint32_t pending_slice_idx = pending_sched->pending_slice_storage_idx;
- StringId prev_state_string_id = context_->sched_event_tracker
- ->TaskStateToStringId(prev_state);
+ StringId prev_state_string_id = TaskStateToStringId(prev_state);
if (prev_state_string_id == kNullStringId) {
context_->storage->IncrementStats(stats::task_state_invalid);
}
@@ -158,8 +160,7 @@
// Close the pending slice if any (we won't have one when processing the first
// two compact events for a given cpu).
uint32_t pending_slice_idx = pending_sched->pending_slice_storage_idx;
- StringId prev_state_str_id =
- context_->sched_event_tracker->TaskStateToStringId(prev_state);
+ StringId prev_state_str_id = TaskStateToStringId(prev_state);
if (prev_state_str_id == kNullStringId) {
context_->storage->IncrementStats(stats::task_state_invalid);
}
@@ -303,5 +304,17 @@
}
}
+StringId FtraceSchedEventTracker::TaskStateToStringId(int64_t task_state_int) {
+ using ftrace_utils::TaskState;
+ std::optional<VersionNumber> kernel_version =
+ SystemInfoTracker::GetOrCreate(context_)->GetKernelVersion();
+
+ TaskState task_state = TaskState::FromRawPrevState(
+ static_cast<uint16_t>(task_state_int), kernel_version);
+ return task_state.is_valid()
+ ? context_->storage->InternString(task_state.ToString().data())
+ : kNullStringId;
+}
+
} // namespace trace_processor
} // namespace perfetto
diff --git a/src/trace_processor/importers/ftrace/ftrace_sched_event_tracker.h b/src/trace_processor/importers/ftrace/ftrace_sched_event_tracker.h
index 42b0d4f..7a03dfa 100644
--- a/src/trace_processor/importers/ftrace/ftrace_sched_event_tracker.h
+++ b/src/trace_processor/importers/ftrace/ftrace_sched_event_tracker.h
@@ -38,10 +38,8 @@
explicit FtraceSchedEventTracker(TraceProcessorContext*);
~FtraceSchedEventTracker() override;
- FtraceSchedEventTracker(
- const FtraceSchedEventTracker& ftrace_sched_event_tracker) = delete;
- FtraceSchedEventTracker& operator=(
- const FtraceSchedEventTracker& ftrace_sched_event_tracker) = delete;
+ FtraceSchedEventTracker(const FtraceSchedEventTracker&) = delete;
+ FtraceSchedEventTracker& operator=(const FtraceSchedEventTracker&) = delete;
static FtraceSchedEventTracker* GetOrCreate(TraceProcessorContext* context) {
if (!context->ftrace_sched_tracker) {
@@ -97,6 +95,8 @@
bool parse_only_into_raw);
private:
+ StringId TaskStateToStringId(int64_t task_state_int);
+
static constexpr uint8_t kSchedSwitchMaxFieldId = 7;
std::array<StringId, kSchedSwitchMaxFieldId + 1> sched_switch_field_ids_;
StringId sched_switch_id_;
diff --git a/src/trace_processor/types/trace_processor_context.h b/src/trace_processor/types/trace_processor_context.h
index 9a89eb3..164df00 100644
--- a/src/trace_processor/types/trace_processor_context.h
+++ b/src/trace_processor/types/trace_processor_context.h
@@ -113,6 +113,7 @@
// type is only available in storage_full target. To access these fields use
// the GetOrCreate() method on their subclass type, e.g.
// SyscallTracker::GetOrCreate(context)
+ // clang-format off
std::unique_ptr<Destructible> android_probes_tracker; // AndroidProbesTracker
std::unique_ptr<Destructible> binder_tracker; // BinderTracker
std::unique_ptr<Destructible> heap_graph_tracker; // HeapGraphTracker
@@ -125,12 +126,11 @@
std::unique_ptr<Destructible> i2c_tracker; // I2CTracker
std::unique_ptr<Destructible> perf_data_tracker; // PerfDataTracker
std::unique_ptr<Destructible> content_analyzer; // ProtoContentAnalyzer
- std::unique_ptr<Destructible>
- shell_transitions_tracker; // ShellTransitionsTracker
- std::unique_ptr<Destructible>
- ftrace_sched_tracker; // FtraceSchedEventTracker
- std::unique_ptr<Destructible> v8_tracker; // V8Tracker
- std::unique_ptr<Destructible> jit_tracker; // JitTracker
+ std::unique_ptr<Destructible> shell_transitions_tracker; // ShellTransitionsTracker
+ std::unique_ptr<Destructible> ftrace_sched_tracker; // FtraceSchedEventTracker
+ std::unique_ptr<Destructible> v8_tracker; // V8Tracker
+ std::unique_ptr<Destructible> jit_tracker; // JitTracker
+ // clang-format on
// These fields are trace readers which will be called by |forwarding_parser|
// once the format of the trace is discovered. They are placed here as they
diff --git a/src/trace_redaction/BUILD.gn b/src/trace_redaction/BUILD.gn
index b662902..c73d36f 100644
--- a/src/trace_redaction/BUILD.gn
+++ b/src/trace_redaction/BUILD.gn
@@ -28,16 +28,24 @@
source_set("trace_redaction") {
sources = [
+ "build_timeline.cc",
+ "build_timeline.h",
"find_package_uid.cc",
"find_package_uid.h",
+ "optimize_timeline.cc",
+ "optimize_timeline.h",
"populate_allow_lists.cc",
"populate_allow_lists.h",
+ "process_thread_timeline.cc",
+ "process_thread_timeline.h",
"proto_util.cc",
"proto_util.h",
"prune_package_list.cc",
"prune_package_list.h",
"scrub_ftrace_events.cc",
"scrub_ftrace_events.h",
+ "scrub_process_trees.cc",
+ "scrub_process_trees.h",
"scrub_trace_packet.cc",
"scrub_trace_packet.h",
"trace_redaction_framework.cc",
@@ -56,6 +64,7 @@
"../../protos/perfetto/trace/android:cpp",
"../../protos/perfetto/trace/android:zero",
"../../protos/perfetto/trace/ftrace:zero",
+ "../../protos/perfetto/trace/ps:zero",
"../trace_processor:storage_minimal",
]
}
@@ -64,6 +73,7 @@
testonly = true
sources = [
"scrub_ftrace_events_integrationtest.cc",
+ "scrub_process_trees_integrationtest.cc",
"trace_redactor_integrationtest.cc",
]
deps = [
@@ -74,6 +84,7 @@
"../../protos/perfetto/trace:non_minimal_zero",
"../../protos/perfetto/trace/android:zero",
"../../protos/perfetto/trace/ftrace:zero",
+ "../../protos/perfetto/trace/ps:zero",
"../base:test_support",
]
}
@@ -81,7 +92,9 @@
perfetto_unittest_source_set("unittests") {
testonly = true
sources = [
+ "build_timeline_unittest.cc",
"find_package_uid_unittest.cc",
+ "process_thread_timeline_unittest.cc",
"proto_util_unittest.cc",
"prune_package_list_unittest.cc",
"scrub_ftrace_events_unittest.cc",
diff --git a/src/trace_redaction/README.md b/src/trace_redaction/README.md
new file mode 100644
index 0000000..225f261
--- /dev/null
+++ b/src/trace_redaction/README.md
@@ -0,0 +1,134 @@
+
+# Timeline
+
+## Intro
+
+The timeline is at the center of the redaction system. It provides an
+efficient method to find which package a thread/process belongs to.
+
+The timeline allows queries to be connected to time. Without this, there's a
+significant privacy conern because a pid can be recycled. Just because the pid
+is excluded from redaction before time T, doesn't mean it should be redacted
+after time T.
+
+## General Structure
+
+The timeline uses an event-based pattern using two events:
+
+- __Open Event:__ Marks the begining of a pid's new lifespan.
+- __Close Event:__ Marks the end of a pids's lifespan.
+
+An event-based structure (compared to a span-based structure) is used as it is
+better suited to handle errors/issues in the underlying data. For example, if a
+pid doesn't explictly ends before being reused (e.g. two back-to-back open
+events), the event-based structure "just works".
+
+Open events contain the thread's full state. The close event only contains the
+information needed to reference the thread's previous event.
+
+```c++
+struct Open {
+ uint64_t ts;
+ int32_t pid;
+ int32_t ppid;
+ uint64_t uid;
+};
+
+struct Close {
+ uint64_t ts;
+ int32_t pid;
+};
+```
+
+The vast majory of threads will have one event, an open event provided by the
+`ProcessTree`. For some threads, they will have multiple open (`ProcessTree`,
+`NewTask`) and close events (`ProcFree`) in alternating order.
+
+## Query
+
+```c++
+struct Slice {
+ int32_t pid;
+ uint64_t uid;
+};
+
+class Timeline {
+ Slice Query(uint64_t ts, int32_t pid) const;
+};
+
+```
+
+Events, regardless of type, are stored in contiguous memory and are ordered
+first by pid and second by time. This is done to allow events to be found
+via a binary search.
+
+The vast majory of threads will have one event, the open event. Some threads
+may have close and re-open events.
+
+To handle a query,
+
+1. Use a binary search to find the lower bound for `pid` (the first instance of
+ `pid`)
+1. Scan forward to find the last event before `ts` (for `pid`)
+
+If an event was found:
+
+```c++
+if (e.type == kOpen && uid != 0)
+ return Slice(pid, e.uid);
+
+// The pid is active, check the parent for a uid.
+if (e.type == kOpen && uid == 0)
+ return Query(ts, e.ppid);
+
+return Slice(pid, kNoPackage);
+```
+
+If `pid` does not have an immediate package (`uid`), the parent must be
+searched. The parent-child tree is short, so the recursive search will be
+relatively short. To minimize this even more, a union-find operation is applied
+because any queries can be made.
+
+__Simple runtime overview:__
+
+Initialization:
+
+- $sort + union\ find$
+
+- $nlogn + mlogn$
+ - where $n=events$
+ - and $m=approx\ average\ depth$
+
+Query:
+
+- $P(p) = m_p * (logn + e_p)$
+ - where $m_p=\ distance\ from\ pid\ to\ uid$
+ - and $n=events$
+ - and $e_p=number\ of\ events\ for\ process\ p$
+
+- Because of the union-find in initialization, $m_p \to 0$
+
+To further reduce the runtime, the search domain is reduces by remove all open
+events for $pids$ that don't connect to a target $uid$. By removing open events,
+and close events, there are two advantages:
+
+1. Removing open events are safe and simple. By removing open events, those pids
+can never be marked by active. Keeping the close events effectively reminds the
+system that the pid is not active.
+
+1. The number of open events exceeds the number of close events. Removing open
+events will have a greater effect on the number of events.
+
+__Example:__
+
+|Name|Value|Notes|
+|-|-|-|
+|tids|3666|Total number of threads.|
+|freed threads|5|Number of threads that were freed.|
+|reused threads|0|No threads were used more than one time.|
+|process tids|64|Total number of threads connected to the target process.|
+
+After initialization, there would only be 64 open events and 5 close events.
+This means that every uid lookup would be $logn\ |\ n=64 = 6$. Finding the uid
+given a pid is one of the most common operations during redaction because uid
+determines if something needs to be redacted.
diff --git a/src/trace_redaction/build_timeline.cc b/src/trace_redaction/build_timeline.cc
new file mode 100644
index 0000000..e94ddaa
--- /dev/null
+++ b/src/trace_redaction/build_timeline.cc
@@ -0,0 +1,144 @@
+/*
+ * 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/build_timeline.h"
+
+#include "perfetto/ext/base/status_or.h"
+#include "src/trace_redaction/process_thread_timeline.h"
+#include "src/trace_redaction/trace_redaction_framework.h"
+
+#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h"
+#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h"
+#include "protos/perfetto/trace/ftrace/sched.pbzero.h"
+#include "protos/perfetto/trace/ftrace/task.pbzero.h"
+#include "protos/perfetto/trace/ps/process_tree.pbzero.h"
+#include "protos/perfetto/trace/trace_packet.pbzero.h"
+
+namespace perfetto::trace_redaction {
+namespace {
+
+using TracePacket = protos::pbzero::TracePacket;
+using ProcessTree = protos::pbzero::ProcessTree;
+using FtraceEvent = protos::pbzero::FtraceEvent;
+using FtraceEventBundle = protos::pbzero::FtraceEventBundle;
+using SchedProcessFreeFtraceEvent = protos::pbzero::SchedProcessFreeFtraceEvent;
+using TaskNewtaskFtraceEvent = protos::pbzero::TaskNewtaskFtraceEvent;
+
+void MarkOpen(uint64_t ts,
+ ProcessTree::Process::Decoder process,
+ ProcessThreadTimeline* timeline) {
+ // The uid in the process tree is a int32_t, but in the package list, the uid
+ // is a uint64_t.
+ auto uid = static_cast<uint64_t>(process.uid());
+ auto e = ProcessThreadTimeline::Event::Open(ts, process.pid(), process.ppid(),
+ uid);
+ timeline->Append(e);
+}
+
+void MarkOpen(uint64_t ts,
+ ProcessTree::Thread::Decoder thread,
+ ProcessThreadTimeline* timeline) {
+ auto e = ProcessThreadTimeline::Event::Open(ts, thread.tid(), thread.tgid());
+ timeline->Append(e);
+}
+
+void MarkClose(const FtraceEvent::Decoder& event,
+ SchedProcessFreeFtraceEvent::Decoder process_free,
+ ProcessThreadTimeline* timeline) {
+ auto e = ProcessThreadTimeline::Event::Close(event.timestamp(),
+ process_free.pid());
+ timeline->Append(e);
+}
+
+void MarkOpen(const FtraceEvent::Decoder& event,
+ TaskNewtaskFtraceEvent::Decoder new_task,
+ ProcessThreadTimeline* timeline) {
+ // Event though pid() is uint32_t. all other pid values use int32_t, so it's
+ // assumed to be safe to narrow-cast it.
+ auto ppid = static_cast<int32_t>(event.pid());
+ auto e = ProcessThreadTimeline::Event::Open(event.timestamp(), new_task.pid(),
+ ppid);
+ timeline->Append(e);
+}
+
+void AppendEvents(uint64_t ts,
+ ProcessTree::Decoder tree,
+ ProcessThreadTimeline* timeline) {
+ for (auto it = tree.processes(); it; ++it) {
+ MarkOpen(ts, ProcessTree::Process::Decoder(*it), timeline);
+ }
+
+ for (auto it = tree.threads(); it; ++it) {
+ MarkOpen(ts, ProcessTree::Thread::Decoder(*it), timeline);
+ }
+}
+
+void AppendEvents(FtraceEventBundle::Decoder ftrace_events,
+ ProcessThreadTimeline* timeline) {
+ for (auto it = ftrace_events.event(); it; ++it) {
+ FtraceEvent::Decoder event(*it);
+
+ if (event.has_task_newtask()) {
+ MarkOpen(event, TaskNewtaskFtraceEvent::Decoder(event.task_newtask()),
+ timeline);
+ continue;
+ }
+
+ if (event.has_sched_process_free()) {
+ MarkClose(
+ event,
+ SchedProcessFreeFtraceEvent::Decoder(event.sched_process_free()),
+ timeline);
+ continue;
+ }
+ }
+}
+
+} // namespace
+
+base::StatusOr<CollectPrimitive::ContinueCollection> BuildTimeline::Collect(
+ const TracePacket::Decoder& packet,
+ Context* context) const {
+ // TODO(vaage): This should only be true on the first call. However, that
+ // means a branch is called N times when N-1 times it will be false. This may
+ // be common across Collect primitives. Having a "begin" and "end" end-points.
+ if (!context->timeline) {
+ context->timeline = std::make_unique<ProcessThreadTimeline>();
+ }
+
+ // Unlike ftrace events, process trees do not provide per-process or
+ // per-thread timing information. The packet has timestamp and the process
+ // tree has collection_end_timestamp (collection_end_timestamp > timestamp).
+ //
+ // The packet's timestamp based on the assumption that in order to be
+ // collected, the processes and threads had to exist before "now".
+ if (packet.has_process_tree()) {
+ AppendEvents(packet.timestamp(),
+ ProcessTree::Decoder(packet.process_tree()),
+ context->timeline.get());
+ return ContinueCollection::kNextPacket;
+ }
+
+ if (packet.has_ftrace_events()) {
+ AppendEvents(FtraceEventBundle::Decoder(packet.ftrace_events()),
+ context->timeline.get());
+ return ContinueCollection::kNextPacket;
+ }
+
+ return ContinueCollection::kNextPacket;
+}
+
+} // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/build_timeline.h b/src/trace_redaction/build_timeline.h
new file mode 100644
index 0000000..49b92bf
--- /dev/null
+++ b/src/trace_redaction/build_timeline.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_TRACE_REDACTION_BUILD_TIMELINE_H_
+#define SRC_TRACE_REDACTION_BUILD_TIMELINE_H_
+
+#include "perfetto/ext/base/status_or.h"
+#include "src/trace_redaction/trace_redaction_framework.h"
+
+#include "protos/perfetto/trace/trace_packet.pbzero.h"
+
+namespace perfetto::trace_redaction {
+
+// Creates events from process_tree, task_newtask, and sched_process_free
+// packets and stores them in a timeline.
+class BuildTimeline : public CollectPrimitive {
+ public:
+ base::StatusOr<ContinueCollection> Collect(
+ const protos::pbzero::TracePacket::Decoder& packet,
+ Context* context) const override;
+};
+
+} // namespace perfetto::trace_redaction
+
+#endif // SRC_TRACE_REDACTION_BUILD_TIMELINE_H_
diff --git a/src/trace_redaction/build_timeline_unittest.cc b/src/trace_redaction/build_timeline_unittest.cc
new file mode 100644
index 0000000..39eb877
--- /dev/null
+++ b/src/trace_redaction/build_timeline_unittest.cc
@@ -0,0 +1,225 @@
+
+/*
+ * 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 <string>
+
+#include "src/base/test/status_matchers.h"
+#include "src/trace_redaction/build_timeline.h"
+#include "src/trace_redaction/trace_redaction_framework.h"
+
+#include "protos/perfetto/trace/ftrace/ftrace_event.gen.h"
+#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.gen.h"
+#include "protos/perfetto/trace/ftrace/sched.gen.h"
+#include "protos/perfetto/trace/ps/process_tree.gen.h"
+#include "protos/perfetto/trace/trace_packet.gen.h"
+
+namespace perfetto::trace_redaction {
+
+// Test packet (a small clip of a later trace):
+//
+// packet {
+// process_tree{
+// processes {
+// pid: 1093
+// ppid: 1
+// cmdline: "zygote"
+// uid: 0
+// }
+// processes {
+// pid: 7105
+// ppid: 1093
+// cmdline: "com.Unity.com.unity.multiplayer.samples.coop"
+// uid: 10252
+// }
+// threads {
+// tid: 7127
+// tgid: 7105
+// }
+// collection_end_timestamp: 6702093738547594
+// }
+// trusted_uid: 9999
+// timestamp: 6702093635419927
+// trusted_packet_sequence_id: 6
+// incremental_state_cleared: true
+// previous_packet_dropped: true
+// }
+
+namespace {
+
+constexpr uint64_t kNoPackage = 0;
+constexpr uint64_t kUnityPackage = 10252;
+
+constexpr uint64_t kZygotePid = 1093;
+constexpr uint64_t kUnityPid = 7105;
+constexpr uint64_t kUnityTid = 7127;
+
+constexpr uint64_t kProcessTreeTimestamp = 6702093635419927;
+constexpr uint64_t kThreadFreeTimestamp = 6702094703928940;
+
+class TestParams {
+ public:
+ TestParams(uint64_t ts, int32_t pid, uint64_t uid)
+ : ts_(ts), pid_(pid), uid_(uid) {}
+
+ uint64_t ts() const { return ts_; }
+ int32_t pid() const { return pid_; }
+ uint64_t uid() const { return uid_; }
+
+ private:
+ uint64_t ts_;
+ int32_t pid_;
+ uint64_t uid_;
+};
+
+} // namespace
+
+class BuildTimelineTest : public testing::Test,
+ public testing::WithParamInterface<TestParams> {
+ protected:
+ base::StatusOr<CollectPrimitive::ContinueCollection> PushProcessTreePacket(
+ uint64_t timestamp) {
+ protos::gen::TracePacket packet;
+ packet.set_trusted_uid(9999);
+ packet.set_timestamp(timestamp);
+ packet.set_trusted_packet_sequence_id(6);
+ packet.set_incremental_state_cleared(true);
+ packet.set_previous_packet_dropped(true);
+
+ auto* process_tree = packet.mutable_process_tree();
+
+ auto* zygote = process_tree->add_processes();
+ zygote->set_pid(kZygotePid);
+ zygote->set_ppid(1);
+ zygote->add_cmdline("zygote");
+ zygote->set_uid(0);
+
+ auto* unity = process_tree->add_processes();
+ unity->set_pid(kUnityPid);
+ unity->set_ppid(1093);
+ unity->add_cmdline("com.Unity.com.unity.multiplayer.samples.coop");
+ unity->set_uid(kUnityPackage);
+
+ auto* thread = process_tree->add_threads();
+ thread->set_tid(kUnityTid);
+ thread->set_tgid(kUnityPid);
+
+ process_tree->set_collection_end_timestamp(timestamp);
+
+ std::string packet_str = packet.SerializeAsString();
+ return build_.Collect(protos::pbzero::TracePacket::Decoder(packet_str),
+ &context_);
+ }
+
+ base::StatusOr<CollectPrimitive::ContinueCollection>
+ PushSchedProcessFreePacket(uint64_t timestamp) {
+ protos::gen::TracePacket packet;
+
+ packet.set_trusted_uid(9999);
+ packet.set_timestamp(timestamp);
+ packet.set_trusted_packet_sequence_id(6);
+ packet.set_incremental_state_cleared(true);
+ packet.set_previous_packet_dropped(true);
+
+ auto* ftrace_events = packet.mutable_ftrace_events();
+ auto* ftrace_event = ftrace_events->add_event();
+ ftrace_event->set_timestamp(timestamp);
+ ftrace_event->set_pid(10); // kernel thread - e.g. "rcuop/0"
+
+ auto* process_free = ftrace_event->mutable_sched_process_free();
+ process_free->set_comm("UnityMain");
+ process_free->set_pid(kUnityTid);
+ process_free->set_prio(120);
+
+ std::string packet_str = packet.SerializeAsString();
+ return build_.Collect(protos::pbzero::TracePacket::Decoder(packet_str),
+ &context_);
+ }
+
+ BuildTimeline build_;
+ Context context_;
+};
+
+class BuildTimelineWithProcessTree : public BuildTimelineTest {};
+
+TEST_P(BuildTimelineWithProcessTree, FindsOpenSpans) {
+ auto params = GetParam();
+
+ auto result = PushProcessTreePacket(kProcessTreeTimestamp);
+ ASSERT_OK(result) << result.status().message();
+
+ context_.timeline->Sort();
+
+ auto slice = context_.timeline->Search(params.ts(), params.pid());
+ ASSERT_EQ(slice.pid, params.pid());
+ ASSERT_EQ(slice.uid, params.uid());
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ AcrossWholeTimeline,
+ BuildTimelineWithProcessTree,
+ testing::Values(
+ // Before the processes/threads existed.
+ TestParams(0, kZygotePid, kNoPackage),
+ TestParams(0, kUnityPid, kNoPackage),
+ TestParams(0, kUnityTid, kNoPackage),
+
+ // When the process tree started.
+ TestParams(kProcessTreeTimestamp, kZygotePid, kNoPackage),
+ TestParams(kProcessTreeTimestamp, kUnityPid, kUnityPackage),
+ TestParams(kProcessTreeTimestamp, kUnityTid, kUnityPackage),
+
+ // After the process tree started.
+ TestParams(kProcessTreeTimestamp + 1, kZygotePid, kNoPackage),
+ TestParams(kProcessTreeTimestamp + 1, kUnityPid, kUnityPackage),
+ TestParams(kProcessTreeTimestamp + 1, kUnityTid, kUnityPackage)));
+
+// Assumes all BuildTimelineWithProcessTree tests pass.
+class BuildTimelineWithFreeProcess : public BuildTimelineTest {};
+
+TEST_P(BuildTimelineWithFreeProcess, FindsClosedSpans) {
+ auto params = GetParam();
+
+ auto result = PushProcessTreePacket(kProcessTreeTimestamp);
+ ASSERT_OK(result) << result.status().message();
+
+ result = PushSchedProcessFreePacket(kThreadFreeTimestamp);
+ ASSERT_OK(result) << result.status().message();
+
+ context_.timeline->Sort();
+
+ auto slice = context_.timeline->Search(params.ts(), params.pid());
+ ASSERT_EQ(slice.pid, params.pid());
+ ASSERT_EQ(slice.uid, params.uid());
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ AcrossWholeTimeline,
+ BuildTimelineWithFreeProcess,
+ testing::Values(
+ TestParams(kThreadFreeTimestamp - 1, kZygotePid, kNoPackage),
+ TestParams(kThreadFreeTimestamp - 1, kUnityPid, kUnityPackage),
+ TestParams(kThreadFreeTimestamp - 1, kUnityTid, kUnityPackage),
+
+ TestParams(kThreadFreeTimestamp, kZygotePid, kNoPackage),
+ TestParams(kThreadFreeTimestamp, kUnityPid, kUnityPackage),
+ TestParams(kThreadFreeTimestamp, kUnityTid, kNoPackage),
+
+ TestParams(kThreadFreeTimestamp + 1, kZygotePid, kNoPackage),
+ TestParams(kThreadFreeTimestamp + 1, kUnityPid, kUnityPackage),
+ TestParams(kThreadFreeTimestamp + 1, kUnityTid, kNoPackage)));
+
+} // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/main.cc b/src/trace_redaction/main.cc
index 153e437..a5c3564 100644
--- a/src/trace_redaction/main.cc
+++ b/src/trace_redaction/main.cc
@@ -16,9 +16,13 @@
#include "perfetto/base/logging.h"
#include "perfetto/base/status.h"
+#include "src/trace_redaction/build_timeline.h"
#include "src/trace_redaction/find_package_uid.h"
+#include "src/trace_redaction/optimize_timeline.h"
+#include "src/trace_redaction/populate_allow_lists.h"
#include "src/trace_redaction/prune_package_list.h"
#include "src/trace_redaction/scrub_ftrace_events.h"
+#include "src/trace_redaction/scrub_process_trees.h"
#include "src/trace_redaction/scrub_trace_packet.h"
#include "src/trace_redaction/trace_redaction_framework.h"
#include "src/trace_redaction/trace_redactor.h"
@@ -33,13 +37,17 @@
// Add all collectors.
redactor.collectors()->emplace_back(new FindPackageUid());
+ redactor.collectors()->emplace_back(new BuildTimeline());
- // TODO(vaage): Add all builders.
+ // Add all builders.
+ redactor.builders()->emplace_back(new PopulateAllowlists());
+ redactor.builders()->emplace_back(new OptimizeTimeline());
// Add all transforms.
redactor.transformers()->emplace_back(new PrunePackageList());
redactor.transformers()->emplace_back(new ScrubTracePacket());
redactor.transformers()->emplace_back(new ScrubFtraceEvents());
+ redactor.transformers()->emplace_back(new ScrubProcessTrees());
Context context;
context.package_name = package_name;
diff --git a/src/trace_redaction/optimize_timeline.cc b/src/trace_redaction/optimize_timeline.cc
new file mode 100644
index 0000000..f4f09e6
--- /dev/null
+++ b/src/trace_redaction/optimize_timeline.cc
@@ -0,0 +1,53 @@
+/*
+ * 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/optimize_timeline.h"
+
+#include "perfetto/base/status.h"
+#include "src/trace_redaction/trace_redaction_framework.h"
+
+namespace perfetto::trace_redaction {
+
+base::Status OptimizeTimeline::Build(Context* context) const {
+ if (!context->timeline) {
+ return base::ErrStatus(
+ "Cannot optimize a null timeline. Are you missing BuildTimeline or an "
+ "alternative?");
+ }
+
+ if (!context->package_uid.has_value()) {
+ return base::ErrStatus(
+ "Missing package uid. Are you missing FindPackageUid or an "
+ "alternative?");
+ }
+
+ auto* timeline = context->timeline.get();
+
+ // Change the timeline from read-only to write only mode.
+ timeline->Sort();
+
+ // Goes over the whole timeline, reducing the distance between a pid and its
+ // uid.
+ timeline->Flatten();
+
+ // Reduce the number of events. This makes the timeline specific to the
+ // package uid (i.e. either 0 or package_uid will be returned).
+ timeline->Reduce(*context->package_uid);
+
+ return base::OkStatus();
+}
+
+} // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/optimize_timeline.h b/src/trace_redaction/optimize_timeline.h
new file mode 100644
index 0000000..e142851
--- /dev/null
+++ b/src/trace_redaction/optimize_timeline.h
@@ -0,0 +1,33 @@
+/*
+ * 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_OPTIMIZE_TIMELINE_H_
+#define SRC_TRACE_REDACTION_OPTIMIZE_TIMELINE_H_
+
+#include "perfetto/base/status.h"
+#include "src/trace_redaction/trace_redaction_framework.h"
+
+namespace perfetto::trace_redaction {
+
+// Converts a timeline from a write-only structure to a read-only structure.
+class OptimizeTimeline : public BuildPrimitive {
+ public:
+ base::Status Build(Context* context) const override;
+};
+
+} // namespace perfetto::trace_redaction
+
+#endif // SRC_TRACE_REDACTION_OPTIMIZE_TIMELINE_H_
diff --git a/src/trace_redaction/process_thread_timeline.cc b/src/trace_redaction/process_thread_timeline.cc
new file mode 100644
index 0000000..7bd4ea4
--- /dev/null
+++ b/src/trace_redaction/process_thread_timeline.cc
@@ -0,0 +1,227 @@
+/*
+ * 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/process_thread_timeline.h"
+
+#include <algorithm>
+#include <cstddef>
+#include <cstdint>
+#include <optional>
+
+namespace perfetto::trace_redaction {
+namespace {
+// Limit the number of iterations to avoid an infinite loop. 10 is a generous
+// number of iterations.
+constexpr size_t kMaxSearchDepth = 10;
+
+bool OrderByPid(const ProcessThreadTimeline::Event& left,
+ const ProcessThreadTimeline::Event& right) {
+ return left.pid() < right.pid();
+}
+
+} // namespace
+
+void ProcessThreadTimeline::Append(const Event& event) {
+ write_only_events_.push_back(event);
+}
+
+void ProcessThreadTimeline::Sort() {
+ write_only_events_.sort(OrderByPid);
+
+ // Copy all events that don't match adjacent events. This should reduce the
+ // number of events because process trees may contain the same data
+ // back-to-back.
+ read_only_events_.reserve(write_only_events_.size());
+
+ for (auto event : write_only_events_) {
+ if (read_only_events_.empty() || event != read_only_events_.back()) {
+ read_only_events_.push_back(event);
+ }
+ }
+
+ // Events have been moved from the write-only list to the read-only vector.
+ // The resources backing the write-only list can be release.
+ write_only_events_.clear();
+}
+
+void ProcessThreadTimeline::Flatten() {
+ // Union-find-like action to collapse the tree.
+ for (auto& event : read_only_events_) {
+ if (event.type() != Event::Type::kOpen) {
+ continue;
+ }
+
+ auto event_with_package = Search(0, event.ts(), event.pid());
+
+ if (event_with_package.has_value()) {
+ event = Event::Open(event.ts(), event.pid(), event.ppid(),
+ event_with_package->uid());
+ }
+ }
+}
+
+void ProcessThreadTimeline::Reduce(uint64_t package_uid) {
+ auto remove_open_events = [package_uid](const Event& event) {
+ return event.uid() != package_uid && event.type() == Event::Type::kOpen;
+ };
+
+ read_only_events_.erase(
+ std::remove_if(read_only_events_.begin(), read_only_events_.end(),
+ remove_open_events),
+ read_only_events_.end());
+}
+
+ProcessThreadTimeline::Slice ProcessThreadTimeline::Search(uint64_t ts,
+ int32_t pid) const {
+ Slice s;
+ s.pid = pid;
+ s.uid = 0;
+
+ auto e = Search(0, ts, pid);
+ if (e.has_value()) {
+ s.uid = e->uid();
+ }
+
+ return s;
+}
+
+std::optional<ProcessThreadTimeline::Event>
+ProcessThreadTimeline::Search(size_t depth, uint64_t ts, int32_t pid) const {
+ if (depth >= kMaxSearchDepth) {
+ return std::nullopt;
+ }
+
+ auto event = FindPreviousEvent(ts, pid);
+
+ if (!TestEvent(event)) {
+ return event;
+ }
+
+ if (event->uid() != 0) {
+ return event;
+ }
+
+ return Search(depth + 1, ts, event->ppid());
+}
+
+std::optional<size_t> ProcessThreadTimeline::GetDepth(uint64_t ts,
+ int32_t pid) const {
+ return GetDepth(0, ts, pid);
+}
+
+std::optional<size_t> ProcessThreadTimeline::GetDepth(size_t depth,
+ uint64_t ts,
+ int32_t pid) const {
+ if (depth >= kMaxSearchDepth) {
+ return std::nullopt;
+ }
+
+ auto event = FindPreviousEvent(ts, pid);
+
+ if (!TestEvent(event)) {
+ return std::nullopt;
+ }
+
+ if (event->uid() != 0) {
+ return depth;
+ }
+
+ return GetDepth(depth + 1, ts, event->ppid());
+}
+
+std::optional<ProcessThreadTimeline::Event>
+ProcessThreadTimeline::FindPreviousEvent(uint64_t ts, int32_t pid) const {
+ Event fake = Event::Close(ts, pid);
+
+ // Events are in ts-order within each pid-group. See Optimize(), Because each
+ // group is small (the vast majority will have two events [start + event, no
+ // reuse]).
+ //
+ // Find the first process event. Then perform a linear search. There won't be
+ // many events per process.
+ auto at = std::lower_bound(read_only_events_.begin(), read_only_events_.end(),
+ fake, OrderByPid);
+
+ // `pid` was not found in `read_only_events_`.
+ if (at == read_only_events_.end()) {
+ return std::nullopt;
+ }
+
+ // "no best option".
+ std::optional<Event> best;
+
+ // Run through all events (related to this pid) and find the last event that
+ // comes before ts. If the events were in order by time, the search could be
+ // more efficient, but the gains are margin because:
+ //
+ // 1. The number of edge cases go up.
+ //
+ // 2. The code is harder to read.
+ //
+ // 3. The performance gains are minimal or non-existant because of the small
+ // number of events.
+ for (; at != read_only_events_.end() && at->pid() == pid; ++at) {
+ if (at->ts() > ts) {
+ continue; // Ignore events in the future.
+ }
+
+ // All ts values are positive. However, ts_at and ts_best are both less than
+ // ts (see early condition), meaning they can be considered negative values.
+ //
+ // at best ts
+ // <---+-----------+-------------+---->
+ // 31 64 93
+ //
+ // at best ts
+ // <---+-----------+-------------+---->
+ // -62 -29 0
+ //
+ // This means that the latest ts value under ts is the closest to ts.
+ if (!best.has_value() || at->ts() > best->ts()) {
+ best = *at;
+ }
+ }
+
+ if (best.has_value() &&
+ best->type() != ProcessThreadTimeline::Event::Type::kOpen) {
+ return std::nullopt;
+ }
+
+ return best;
+}
+
+bool ProcessThreadTimeline::TestEvent(std::optional<Event> event) const {
+ if (!event.has_value()) {
+ return false;
+ }
+
+ // The thread/process was freed. It won't exist until a new open event.
+ if (event->type() != Event::Type::kOpen) {
+ return false;
+ }
+
+ // It is a rare case in production, but a common case in tests, the top-level
+ // event will have no parent but will have the uid. So, to avoid make the
+ // tests fragile and without taking on any risk, the uid should be checked
+ // before the ppid.
+ if (event->uid() != 0) {
+ return true;
+ }
+
+ return event->ppid() != 0;
+}
+
+} // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/process_thread_timeline.h b/src/trace_redaction/process_thread_timeline.h
new file mode 100644
index 0000000..3a6f6ac
--- /dev/null
+++ b/src/trace_redaction/process_thread_timeline.h
@@ -0,0 +1,187 @@
+/*
+ * 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_PROCESS_THREAD_TIMELINE_H_
+#define SRC_TRACE_REDACTION_PROCESS_THREAD_TIMELINE_H_
+
+#include <cstdint>
+#include <list>
+#include <optional>
+#include <vector>
+
+namespace perfetto::trace_redaction {
+
+class ProcessThreadTimeline {
+ public:
+ // Opened and closed events are used to mark the start and end of lifespans.
+ class Event {
+ public:
+ enum class Type { kInvalid, kOpen, kClose };
+
+ Event()
+ : type_(ProcessThreadTimeline::Event::Type::kInvalid),
+ ts_(0),
+ pid_(0),
+ ppid_(0),
+ uid_(0) {}
+
+ Type type() const { return type_; }
+
+ uint64_t ts() const { return ts_; }
+
+ int32_t pid() const { return pid_; }
+
+ int32_t ppid() const { return ppid_; }
+
+ uint64_t uid() const { return uid_; }
+
+ bool operator==(const Event& o) const {
+ switch (type_) {
+ case Type::kOpen:
+ return o.type_ == Type::kOpen && ts_ == o.ts_ && pid_ == o.pid_ &&
+ ppid_ == o.ppid_ && uid_ == o.uid_;
+
+ case Type::kClose:
+ return o.type_ == Type::kClose && ts_ == o.ts_ && pid_ == o.pid_;
+
+ case Type::kInvalid:
+ return o.type_ == Type::kInvalid;
+ }
+
+ return false;
+ }
+
+ bool operator!=(const Event& o) const { return !(*this == o); }
+
+ static Event Open(uint64_t ts, int32_t pid, int32_t ppid, uint64_t uid) {
+ return Event(ProcessThreadTimeline::Event::Type::kOpen, ts, pid, ppid,
+ uid);
+ }
+
+ static Event Open(uint64_t ts, int32_t pid, int32_t ppid) {
+ return Event(ProcessThreadTimeline::Event::Type::kOpen, ts, pid, ppid, 0);
+ }
+
+ static Event Close(uint64_t ts, int32_t pid) {
+ return Event(ProcessThreadTimeline::Event::Type::kClose, ts, pid, 0, 0);
+ }
+
+ private:
+ Event(Type type, uint64_t ts, int32_t pid, int32_t ppid, uint64_t uid)
+ : type_(type), ts_(ts), pid_(pid), ppid_(ppid), uid_(uid) {}
+
+ Type type_ = Type::kInvalid;
+
+ // Valid: open & close
+ uint64_t ts_ = 0;
+
+ // Valid: open & close
+ int32_t pid_ = -1;
+
+ // Valid: open
+ int32_t ppid_ = -1;
+
+ // Valid: open
+ uint64_t uid_ = 0;
+ };
+
+ // The state of a process at a specific point in time.
+ struct Slice {
+ int32_t pid = -1;
+
+ // It is safe to use 0 as the invalid value because that's effectively
+ // what happening in the trace.
+ uint64_t uid = 0;
+ };
+
+ ProcessThreadTimeline() = default;
+
+ ProcessThreadTimeline(const ProcessThreadTimeline&) = delete;
+ ProcessThreadTimeline& operator=(const ProcessThreadTimeline&) = delete;
+ ProcessThreadTimeline(ProcessThreadTimeline&&) = delete;
+ ProcessThreadTimeline& operator=(ProcessThreadTimeline&&) = delete;
+
+ void Append(const Event& event);
+
+ // REQUIRED: Sorts all events by pid, making it possible to locate the subset
+ // of events connected to a pid. Events are not sorted by time because the
+ // subset of events will, on average, be trivally small.
+ void Sort();
+
+ // OPTIONAL: minimizes the distance between the leaf nodes and the package
+ // nodes (a node with a uid value not equal to zero).
+ void Flatten();
+
+ // OPTIONAL: Removes events from the timeline that:
+ //
+ // 1. Reduces the number of events in the timeline to shrink the search
+ // space.
+ //
+ // 2. Does not invalidate the timeline.
+ //
+ // This can only be called after calling Sort(). Calling Reduce() before
+ // Sort() has undefined behaviour. Calling Reduce() after AppendOpen() if
+ // AppendClose() (without a call to Sort() call) has undefined behaviour.
+ void Reduce(uint64_t package_uid);
+
+ // Returns a snapshot that contains a process's pid and ppid, but contains the
+ // first uid found in its parent-child chain. If a uid cannot be found, uid=0
+ // is returned.
+ //
+ // `Sort()` must be called before this.
+ Slice Search(uint64_t ts, int32_t pid) const;
+
+ // Finds the distance between pid and its uid.
+ //
+ // Returns -1 it pid has no connection to a uid.
+ // Returns 0 if pid has an immediately connection to a uid.
+ //
+ // Return n where: n is the number of pids between the given pid and the pid
+ // connected to the uid. For example, assume D() is a
+ // function that measures the distance between two nodes in
+ // the same chain:
+ //
+ // | pid | depth
+ // | a : 0
+ // | b : 1
+ // | c : 2 --> uid = 98
+ //
+ // D(a) = 2
+ // D(b) = 1
+ // D(c) = 0
+ std::optional<size_t> GetDepth(uint64_t ts, int32_t pid) const;
+
+ private:
+ // Effectively this is the same as:
+ //
+ // events_for(pid).before(ts).sort_by_time().last()
+ std::optional<Event> FindPreviousEvent(uint64_t ts, int32_t pid) const;
+
+ std::optional<Event> Search(size_t depth, uint64_t ts, int32_t pid) const;
+
+ std::optional<size_t> GetDepth(size_t depth, uint64_t ts, int32_t pid) const;
+
+ bool TestEvent(std::optional<Event> event) const;
+
+ // The number of events are unclear. Use a list when in "write-only" mode and
+ // then change to a vector for "read-only" mode.
+ std::list<Event> write_only_events_;
+ std::vector<Event> read_only_events_;
+};
+
+} // namespace perfetto::trace_redaction
+
+#endif // SRC_TRACE_REDACTION_PROCESS_THREAD_TIMELINE_H_
diff --git a/src/trace_redaction/process_thread_timeline_unittest.cc b/src/trace_redaction/process_thread_timeline_unittest.cc
new file mode 100644
index 0000000..ea7e5e5
--- /dev/null
+++ b/src/trace_redaction/process_thread_timeline_unittest.cc
@@ -0,0 +1,325 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cstdint>
+
+#include "src/trace_redaction/process_thread_timeline.h"
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto::trace_redaction {
+
+namespace {
+
+class SliceTestParams {
+ public:
+ SliceTestParams(uint64_t ts, int32_t pid, uint64_t uid)
+ : ts_(ts), pid_(pid), uid_(uid) {}
+
+ uint64_t ts() const { return ts_; }
+ int32_t pid() const { return pid_; }
+ uint64_t uid() const { return uid_; }
+
+ private:
+ uint64_t ts_;
+ int32_t pid_;
+ uint64_t uid_;
+};
+
+class DepthTestParams {
+ public:
+ DepthTestParams(uint64_t ts,
+ int32_t pid,
+ std::optional<size_t> raw_depth,
+ std::optional<size_t> flat_depth)
+ : ts_(ts), pid_(pid), raw_depth_(raw_depth), flat_depth_(flat_depth) {}
+
+ uint64_t ts() const { return ts_; }
+ int32_t pid() const { return pid_; }
+ std::optional<size_t> raw_depth() const { return raw_depth_; }
+ std::optional<size_t> flat_depth() const { return flat_depth_; }
+
+ private:
+ uint64_t ts_;
+ int32_t pid_;
+ std::optional<size_t> raw_depth_;
+ std::optional<size_t> flat_depth_;
+};
+
+constexpr uint64_t kTimeA = 0;
+constexpr uint64_t kTimeB = 10;
+constexpr uint64_t kTimeC = 20;
+constexpr uint64_t kTimeD = 30;
+constexpr uint64_t kTimeE = 40;
+constexpr uint64_t kTimeF = 50;
+constexpr uint64_t kTimeG = 60;
+constexpr uint64_t kTimeH = 70;
+constexpr uint64_t kTimeI = 70;
+
+constexpr int32_t kPidA = 1;
+constexpr int32_t kPidB = 2;
+constexpr int32_t kPidC = 3;
+
+constexpr uint64_t kNoPackage = 0;
+
+constexpr int32_t kUidA = 98;
+constexpr int32_t kUidB = 99;
+
+} // namespace
+
+class TimelineEventsTest : public testing::Test,
+ public testing::WithParamInterface<SliceTestParams> {
+ protected:
+ ProcessThreadTimeline timeline_;
+};
+
+class TimelineEventsOpenAndCloseSingleTest : public TimelineEventsTest {};
+
+TEST_P(TimelineEventsOpenAndCloseSingleTest, PidsEndOnClose) {
+ auto params = GetParam();
+
+ timeline_.Append(
+ ProcessThreadTimeline::Event::Open(kTimeB, kPidB, kPidA, kUidA));
+ timeline_.Append(ProcessThreadTimeline::Event::Close(kTimeD, kPidB));
+
+ timeline_.Sort();
+ timeline_.Flatten();
+
+ auto slice = timeline_.Search(params.ts(), params.pid());
+ ASSERT_EQ(slice.pid, params.pid());
+ ASSERT_EQ(slice.uid, params.uid());
+}
+
+INSTANTIATE_TEST_SUITE_P(AcrossWholeTimeline,
+ TimelineEventsOpenAndCloseSingleTest,
+ testing::Values(
+ // No UID found before opening event.
+ SliceTestParams(kTimeA, kPidB, kNoPackage),
+
+ // UID found when opening event starts.
+ SliceTestParams(kTimeB, kPidB, kUidA),
+
+ // UID found between opening and close events.
+ SliceTestParams(kTimeC, kPidB, kUidA),
+
+ // UID is no longer found at the close event.
+ SliceTestParams(kTimeD, kPidB, kNoPackage),
+
+ // UID is no longer found after the close event.
+ SliceTestParams(kTimeE, kPidB, kNoPackage)));
+
+class TimelineEventsOpenAfterOpenTest : public TimelineEventsTest {};
+
+// |--- PID A --- >
+// |--- PID A --- >
+TEST_P(TimelineEventsOpenAfterOpenTest, FindsUid) {
+ auto params = GetParam();
+
+ timeline_.Append(
+ ProcessThreadTimeline::Event::Open(kTimeB, kPidB, kPidA, kUidA));
+ timeline_.Append(
+ ProcessThreadTimeline::Event::Open(kTimeD, kPidB, kPidA, kUidB));
+
+ timeline_.Sort();
+
+ auto slice = timeline_.Search(params.ts(), params.pid());
+ ASSERT_EQ(slice.pid, params.pid());
+ ASSERT_EQ(slice.uid, params.uid());
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ AcrossWholeTimeline,
+ TimelineEventsOpenAfterOpenTest,
+ testing::Values(SliceTestParams(kTimeA, kPidB, kNoPackage),
+ SliceTestParams(kTimeB, kPidB, kUidA),
+ SliceTestParams(kTimeC, kPidB, kUidA),
+ SliceTestParams(kTimeD, kPidB, kUidB),
+ SliceTestParams(kTimeE, kPidB, kUidB)));
+
+class TimelineEventsOverlappingRangesTest : public TimelineEventsTest {};
+
+TEST_P(TimelineEventsOverlappingRangesTest, FindsUid) {
+ auto params = GetParam();
+
+ // |----- PID_A -----|
+ // |----- PID_B -----|
+ timeline_.Append(ProcessThreadTimeline::Event::Open(kTimeA, kPidA, 0, kUidA));
+ timeline_.Append(ProcessThreadTimeline::Event::Open(kTimeC, kPidB, 0, kUidB));
+ timeline_.Append(ProcessThreadTimeline::Event::Close(kTimeE, kPidA));
+ timeline_.Append(ProcessThreadTimeline::Event::Close(kTimeG, kPidB));
+
+ timeline_.Sort();
+
+ auto slice = timeline_.Search(params.ts(), params.pid());
+ ASSERT_EQ(slice.pid, params.pid());
+ ASSERT_EQ(slice.uid, params.uid());
+}
+
+INSTANTIATE_TEST_SUITE_P(AcrossWholeTimeline,
+ TimelineEventsOverlappingRangesTest,
+ testing::Values(
+ // When pid A starts and before pid B starts.
+ SliceTestParams(kTimeA, kPidA, kUidA),
+ SliceTestParams(kTimeA, kPidB, kNoPackage),
+
+ // After pid A starts and before pid B starts.
+ SliceTestParams(kTimeB, kPidA, kUidA),
+ SliceTestParams(kTimeB, kPidB, kNoPackage),
+
+ // After pid A starts and when pid B starts.
+ SliceTestParams(kTimeC, kPidA, kUidA),
+ SliceTestParams(kTimeC, kPidB, kUidB),
+
+ // After pid A and pid starts.
+ SliceTestParams(kTimeD, kPidA, kUidA),
+ SliceTestParams(kTimeD, kPidB, kUidB),
+
+ // When pid A closes but before pid B closes.
+ SliceTestParams(kTimeE, kPidA, kNoPackage),
+ SliceTestParams(kTimeE, kPidB, kUidB),
+
+ // After pid A closes but before pid B closes.
+ SliceTestParams(kTimeF, kPidA, kNoPackage),
+ SliceTestParams(kTimeF, kPidB, kUidB),
+
+ // After pid A closes and when pid B closes.
+ SliceTestParams(kTimeG, kPidA, kNoPackage),
+ SliceTestParams(kTimeG, kPidB, kNoPackage)));
+
+class TimelineEventsParentChildTest : public TimelineEventsTest {};
+
+TEST_P(TimelineEventsParentChildTest, FindsUid) {
+ auto params = GetParam();
+
+ // |------------- PID_A ------------->
+ // |----- PID_B -----|
+ timeline_.Append(ProcessThreadTimeline::Event::Open(kTimeA, kPidA, 0, kUidA));
+ timeline_.Append(ProcessThreadTimeline::Event::Open(kTimeC, kPidB, kPidA));
+ timeline_.Append(ProcessThreadTimeline::Event::Close(kTimeE, kPidB));
+
+ timeline_.Sort();
+
+ auto slice = timeline_.Search(params.ts(), params.pid());
+ ASSERT_EQ(slice.pid, params.pid());
+ ASSERT_EQ(slice.uid, params.uid());
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ AcrossWholeTimeline,
+ TimelineEventsParentChildTest,
+ testing::Values(SliceTestParams(kTimeB, kPidB, kNoPackage),
+ SliceTestParams(kTimeC, kPidB, kUidA),
+ SliceTestParams(kTimeD, kPidB, kUidA),
+ SliceTestParams(kTimeE, kPidB, kNoPackage)));
+
+class TimelineEventsFlattenTest
+ : public testing::Test,
+ public testing::WithParamInterface<DepthTestParams> {
+ protected:
+ ProcessThreadTimeline timeline_;
+};
+
+TEST_P(TimelineEventsFlattenTest, BeforeFlatten) {
+ auto params = GetParam();
+
+ // |---------- PID_A ----------|
+ // |----- PID_B -----|
+ // |-- PID_C --|
+ timeline_.Append(ProcessThreadTimeline::Event::Open(kTimeB, kPidA, 0, kUidA));
+ timeline_.Append(ProcessThreadTimeline::Event::Open(kTimeC, kPidB, kPidA));
+ timeline_.Append(ProcessThreadTimeline::Event::Open(kTimeD, kPidC, kPidB));
+
+ // Time E is when all spans are valid.
+
+ timeline_.Append(ProcessThreadTimeline::Event::Close(kTimeF, kPidC));
+ timeline_.Append(ProcessThreadTimeline::Event::Close(kTimeG, kPidB));
+ timeline_.Append(ProcessThreadTimeline::Event::Close(kTimeH, kPidA));
+
+ timeline_.Sort();
+
+ auto depth = timeline_.GetDepth(params.ts(), params.pid());
+ ASSERT_EQ(depth, params.raw_depth());
+}
+
+TEST_P(TimelineEventsFlattenTest, AfterFlatten) {
+ auto params = GetParam();
+
+ // |---------- PID_A ----------|
+ // |----- PID_B -----|
+ // |-- PID_C --|
+ timeline_.Append(ProcessThreadTimeline::Event::Open(kTimeB, kPidA, 0, kUidA));
+ timeline_.Append(ProcessThreadTimeline::Event::Open(kTimeC, kPidB, kPidA));
+ timeline_.Append(ProcessThreadTimeline::Event::Open(kTimeD, kPidC, kPidB));
+
+ // Time E is when all spans are valid.
+
+ timeline_.Append(ProcessThreadTimeline::Event::Close(kTimeF, kPidC));
+ timeline_.Append(ProcessThreadTimeline::Event::Close(kTimeG, kPidB));
+ timeline_.Append(ProcessThreadTimeline::Event::Close(kTimeH, kPidA));
+
+ timeline_.Sort();
+ timeline_.Flatten();
+
+ auto depth = timeline_.GetDepth(params.ts(), params.pid());
+ ASSERT_EQ(depth, params.flat_depth());
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ AcrossWholeTimeline,
+ TimelineEventsFlattenTest,
+ testing::Values(
+ // Pid A
+ DepthTestParams(kTimeA, kPidA, std::nullopt, std::nullopt),
+ DepthTestParams(kTimeB, kPidA, 0, 0),
+ DepthTestParams(kTimeC, kPidA, 0, 0),
+ DepthTestParams(kTimeD, kPidA, 0, 0),
+ DepthTestParams(kTimeE, kPidA, 0, 0),
+ DepthTestParams(kTimeF, kPidA, 0, 0),
+ DepthTestParams(kTimeG, kPidA, 0, 0),
+ DepthTestParams(kTimeH,
+ kPidA,
+ std::nullopt,
+ std::nullopt), // pid A ends
+ DepthTestParams(kTimeI, kPidA, std::nullopt, std::nullopt),
+
+ // Pid B
+ DepthTestParams(kTimeA, kPidB, std::nullopt, std::nullopt),
+ DepthTestParams(kTimeB, kPidB, std::nullopt, std::nullopt),
+ DepthTestParams(kTimeC, kPidB, 1, 0),
+ DepthTestParams(kTimeD, kPidB, 1, 0),
+ DepthTestParams(kTimeE, kPidB, 1, 0),
+ DepthTestParams(kTimeF, kPidB, 1, 0),
+ DepthTestParams(kTimeG,
+ kPidB,
+ std::nullopt,
+ std::nullopt), // pid B ends
+ DepthTestParams(kTimeH, kPidB, std::nullopt, std::nullopt),
+ DepthTestParams(kTimeI, kPidB, std::nullopt, std::nullopt),
+
+ // Pid C
+ DepthTestParams(kTimeA, kPidC, std::nullopt, std::nullopt),
+ DepthTestParams(kTimeB, kPidC, std::nullopt, std::nullopt),
+ DepthTestParams(kTimeC, kPidC, std::nullopt, std::nullopt),
+ DepthTestParams(kTimeD, kPidC, 2, 0),
+ DepthTestParams(kTimeE, kPidC, 2, 0),
+ DepthTestParams(kTimeF,
+ kPidC,
+ std::nullopt,
+ std::nullopt), // pid C ends
+ DepthTestParams(kTimeG, kPidC, std::nullopt, std::nullopt),
+ DepthTestParams(kTimeH, kPidC, std::nullopt, std::nullopt),
+ DepthTestParams(kTimeI, kPidC, std::nullopt, std::nullopt)));
+
+} // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/prune_package_list.cc b/src/trace_redaction/prune_package_list.cc
index 83d2355..a85a18f 100644
--- a/src/trace_redaction/prune_package_list.cc
+++ b/src/trace_redaction/prune_package_list.cc
@@ -34,8 +34,9 @@
return base::ErrStatus("PrunePackageList: missing package uid.");
}
- if (protos::pbzero::TracePacket::Decoder trace_packet_decoder(*packet);
- !trace_packet_decoder.has_packages_list()) {
+ protos::pbzero::TracePacket::Decoder trace_packet_decoder(*packet);
+
+ if (!trace_packet_decoder.has_packages_list()) {
return base::OkStatus();
}
diff --git a/src/trace_redaction/scrub_process_trees.cc b/src/trace_redaction/scrub_process_trees.cc
new file mode 100644
index 0000000..449a056
--- /dev/null
+++ b/src/trace_redaction/scrub_process_trees.cc
@@ -0,0 +1,175 @@
+/*
+ * 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/scrub_process_trees.h"
+
+#include <string>
+
+#include "perfetto/base/status.h"
+#include "perfetto/protozero/scattered_heap_buffer.h"
+#include "src/trace_redaction/proto_util.h"
+#include "src/trace_redaction/trace_redaction_framework.h"
+
+#include "protos/perfetto/trace/ps/process_tree.pbzero.h"
+#include "protos/perfetto/trace/trace_packet.pbzero.h"
+
+namespace perfetto::trace_redaction {
+namespace {
+
+constexpr auto kThreadsFieldNumber =
+ protos::pbzero::ProcessTree::kThreadsFieldNumber;
+constexpr auto kTimestampFieldNumber =
+ protos::pbzero::TracePacket::kTimestampFieldNumber;
+constexpr auto kProcessTreeFieldNumber =
+ protos::pbzero::TracePacket::kProcessTreeFieldNumber;
+constexpr auto kProcessesFieldNumber =
+ protos::pbzero::ProcessTree::kProcessesFieldNumber;
+
+// Skips the cmdline fields.
+void ClearProcessName(protozero::ConstBytes bytes,
+ protos::pbzero::ProcessTree::Process* message) {
+ protozero::ProtoDecoder decoder(bytes);
+
+ for (auto field = decoder.ReadField(); field; field = decoder.ReadField()) {
+ if (field.id() !=
+ protos::pbzero::ProcessTree::Process::kCmdlineFieldNumber) {
+ proto_util::AppendField(field, message);
+ }
+ }
+}
+
+void ScrubProcess(protozero::Field field,
+ const ProcessThreadTimeline& timeline,
+ uint64_t now,
+ uint64_t uid,
+ protos::pbzero::ProcessTree* message) {
+ if (field.id() != kProcessesFieldNumber) {
+ PERFETTO_FATAL(
+ "ScrubProcess() should only be called with a ProcessTree::Processes");
+ }
+
+ protos::pbzero::ProcessTree::Process::Decoder decoder(field.as_bytes());
+ auto slice = timeline.Search(now, decoder.pid());
+
+ if (NormalizeUid(slice.uid) == NormalizeUid(uid)) {
+ proto_util::AppendField(field, message);
+ } else {
+ ClearProcessName(field.as_bytes(), message->add_processes());
+ }
+}
+
+// The thread name is unused, but it's safer to remove it.
+void ClearThreadName(protozero::ConstBytes bytes,
+ protos::pbzero::ProcessTree::Thread* message) {
+ protozero::ProtoDecoder decoder(bytes);
+
+ for (auto field = decoder.ReadField(); field; field = decoder.ReadField()) {
+ if (field.id() != protos::pbzero::ProcessTree::Thread::kNameFieldNumber) {
+ proto_util::AppendField(field, message);
+ }
+ }
+}
+
+void ScrubThread(protozero::Field field,
+ const ProcessThreadTimeline& timeline,
+ uint64_t now,
+ uint64_t uid,
+ protos::pbzero::ProcessTree* message) {
+ if (field.id() != kThreadsFieldNumber) {
+ PERFETTO_FATAL(
+ "ScrubThread() should only be called with a ProcessTree::Threads");
+ }
+
+ protos::pbzero::ProcessTree::Thread::Decoder thread_decoder(field.as_bytes());
+ auto slice = timeline.Search(now, thread_decoder.tid());
+
+ if (NormalizeUid(slice.uid) == NormalizeUid(uid)) {
+ proto_util::AppendField(field, message);
+ } else {
+ ClearThreadName(field.as_bytes(), message->add_threads());
+ }
+}
+
+} // namespace
+
+base::Status ScrubProcessTrees::Transform(const Context& context,
+ std::string* packet) const {
+ if (!context.package_uid.has_value()) {
+ return base::ErrStatus("Missing package uid.");
+ }
+
+ if (context.timeline == nullptr) {
+ return base::ErrStatus("Missing timeline.");
+ }
+
+ protozero::ProtoDecoder decoder(*packet);
+
+ if (!decoder.FindField(kProcessTreeFieldNumber).valid()) {
+ return base::OkStatus();
+ }
+
+ auto timestamp_field = decoder.FindField(kTimestampFieldNumber);
+
+ if (!timestamp_field.valid()) {
+ return base::ErrStatus("Could not find timestamp in trace packet");
+ }
+
+ auto timestamp = timestamp_field.as_uint64();
+
+ auto uid = context.package_uid.value();
+
+ const auto& timeline = *context.timeline.get();
+
+ protozero::HeapBuffered<protos::pbzero::TracePacket> message;
+
+ for (auto packet_field = decoder.ReadField(); packet_field.valid();
+ packet_field = decoder.ReadField()) {
+ if (packet_field.id() != kProcessTreeFieldNumber) {
+ proto_util::AppendField(packet_field, message.get());
+ continue;
+ }
+
+ auto* process_tree_message = message->set_process_tree();
+
+ protozero::ProtoDecoder process_tree_decoder(packet_field.as_bytes());
+
+ for (auto process_tree_field = process_tree_decoder.ReadField();
+ process_tree_field.valid();
+ process_tree_field = process_tree_decoder.ReadField()) {
+ switch (process_tree_field.id()) {
+ case kProcessesFieldNumber:
+ ScrubProcess(process_tree_field, timeline, timestamp, uid,
+ process_tree_message);
+ break;
+
+ case kThreadsFieldNumber:
+ ScrubThread(process_tree_field, timeline, timestamp, uid,
+ process_tree_message);
+ break;
+
+ default:
+ proto_util::AppendField(process_tree_field, process_tree_message);
+ break;
+ }
+ }
+ }
+
+ packet->assign(message.SerializeAsString());
+
+ return base::OkStatus();
+}
+
+} // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/scrub_process_trees.h b/src/trace_redaction/scrub_process_trees.h
new file mode 100644
index 0000000..61cb85a
--- /dev/null
+++ b/src/trace_redaction/scrub_process_trees.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_TRACE_REDACTION_SCRUB_PROCESS_TREES_H_
+#define SRC_TRACE_REDACTION_SCRUB_PROCESS_TREES_H_
+
+#include <string>
+
+#include "perfetto/base/status.h"
+#include "src/trace_redaction/trace_redaction_framework.h"
+
+namespace perfetto::trace_redaction {
+
+// Removes process names and thread names from process_trees if their pids/tids
+// are not connected to the target package.
+class ScrubProcessTrees final : public TransformPrimitive {
+ public:
+ base::Status Transform(const Context& context,
+ std::string* packet) const override;
+};
+
+} // namespace perfetto::trace_redaction
+
+#endif // SRC_TRACE_REDACTION_SCRUB_PROCESS_TREES_H_
diff --git a/src/trace_redaction/scrub_process_trees_integrationtest.cc b/src/trace_redaction/scrub_process_trees_integrationtest.cc
new file mode 100644
index 0000000..42328c1
--- /dev/null
+++ b/src/trace_redaction/scrub_process_trees_integrationtest.cc
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include "perfetto/base/status.h"
+#include "perfetto/ext/base/file_utils.h"
+#include "protos/perfetto/trace/ps/process_tree.pbzero.h"
+#include "protos/perfetto/trace/trace.pbzero.h"
+#include "src/base/test/status_matchers.h"
+#include "src/base/test/tmp_dir_tree.h"
+#include "src/base/test/utils.h"
+#include "src/trace_redaction/build_timeline.h"
+#include "src/trace_redaction/find_package_uid.h"
+#include "src/trace_redaction/optimize_timeline.h"
+#include "src/trace_redaction/scrub_process_trees.h"
+#include "src/trace_redaction/trace_redaction_framework.h"
+#include "src/trace_redaction/trace_redactor.h"
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto::trace_redaction {
+
+namespace {
+
+constexpr std::string_view kTracePath =
+ "test/data/trace-redaction-general.pftrace";
+constexpr std::string_view kProcessName =
+ "com.Unity.com.unity.multiplayer.samples.coop";
+
+class ScrubProcessTreesIntegrationTest : public testing::Test {
+ protected:
+ void SetUp() override {
+ src_trace_ = base::GetTestDataPath(std::string(kTracePath));
+
+ // ScrubProcessTrees depends on:
+ // - FindPackageUid (uid)
+ // - OptimizeTimeline (sealed + optimized timeline)
+ //
+ // OptimizeTimeline depends on:
+ // - FindPackageUid (uid)
+ // - BuildTimeline (timeline)
+ //
+ // BuildTimeline depends on.... nothing
+ // FindPackageUid depends on... nothing
+
+ redactor_.collectors()->emplace_back(new FindPackageUid());
+ redactor_.collectors()->emplace_back(new BuildTimeline());
+ redactor_.builders()->emplace_back(new OptimizeTimeline());
+ redactor_.transformers()->emplace_back(new ScrubProcessTrees());
+
+ // In this case, the process and package have the same name.
+ context_.package_name = kProcessName;
+
+ dest_trace_ = tmp_dir_.AbsolutePath("dst.pftrace");
+ tmp_dir_.TrackFile("dst.pftrace");
+ }
+
+ static base::StatusOr<std::string> ReadRawTrace(const std::string& path) {
+ std::string redacted_buffer;
+
+ if (base::ReadFile(path, &redacted_buffer)) {
+ return redacted_buffer;
+ }
+
+ return base::ErrStatus("Failed to read %s", path.c_str());
+ }
+
+ std::string src_trace_;
+ std::string dest_trace_;
+
+ base::TmpDirTree tmp_dir_;
+
+ Context context_;
+ TraceRedactor redactor_;
+};
+
+TEST_F(ScrubProcessTreesIntegrationTest, RemovesProcessNamesFromProcessTrees) {
+ ASSERT_OK(redactor_.Redact(src_trace_, dest_trace_, &context_));
+ ASSERT_OK_AND_ASSIGN(auto redacted_buffer, ReadRawTrace(dest_trace_));
+
+ protos::pbzero::Trace::Decoder trace(redacted_buffer);
+
+ for (auto packet_it = trace.packet(); packet_it; ++packet_it) {
+ protos::pbzero::TracePacket::Decoder packet(*packet_it);
+
+ if (!packet.has_process_tree()) {
+ continue;
+ }
+
+ protos::pbzero::ProcessTree::Decoder process_tree(packet.process_tree());
+
+ for (auto process_it = process_tree.processes(); process_it; ++process_it) {
+ protos::pbzero::ProcessTree::Process::Decoder process(*process_it);
+
+ std::vector<std::string> cmdline;
+ for (auto cmd_it = process.cmdline(); cmd_it; ++cmd_it) {
+ cmdline.push_back(cmd_it->as_std_string());
+ }
+
+ // It's okay to be empty.
+ if (cmdline.empty()) {
+ continue;
+ }
+
+ if (cmdline.size() == 1) {
+ ASSERT_EQ(cmdline[0], kProcessName);
+ continue;
+ }
+
+ // If there are more than
+ for (const auto& token : cmdline) {
+ ASSERT_TRUE(token.empty());
+ }
+ }
+ }
+}
+
+} // namespace
+} // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/trace_redaction_framework.h b/src/trace_redaction/trace_redaction_framework.h
index d22ecd2..96f7bf1 100644
--- a/src/trace_redaction/trace_redaction_framework.h
+++ b/src/trace_redaction/trace_redaction_framework.h
@@ -18,14 +18,15 @@
#define SRC_TRACE_REDACTION_TRACE_REDACTION_FRAMEWORK_H_
#include <cstdint>
+#include <memory>
#include <optional>
#include <string>
#include "perfetto/base/flat_set.h"
#include "perfetto/base/status.h"
#include "perfetto/ext/base/status_or.h"
-
#include "protos/perfetto/trace/trace_packet.pbzero.h"
+#include "src/trace_redaction/process_thread_timeline.h"
namespace perfetto::trace_redaction {
@@ -156,6 +157,24 @@
// 3. In this example, a cpu_idle event populates the one-of slot in the
// ftrace event
base::FlatSet<uint32_t> ftrace_packet_allow_list;
+
+ // The timeline is a query-focused data structure that connects a pid to a
+ // uid at specific point in time.
+ //
+ // A timeline has two modes:
+ //
+ // 1. write-only
+ // 2. read-only
+ //
+ // Attempting to use the timeline incorrectly results in undefined behaviour.
+ //
+ // To use a timeline, the primitive needs to be "built" (add events) and then
+ // "sealed" (transition to read-only).
+ //
+ // A timeline must have Sort() called to change from write-only to read-only.
+ // After Sort(), Flatten() and Reduce() can be called (optional) to improve
+ // the practical look-up times (compared to theoretical look-up times).
+ std::unique_ptr<ProcessThreadTimeline> timeline;
};
// 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 bd7847d..3ee73da 100644
--- a/src/trace_redaction/trace_redactor_integrationtest.cc
+++ b/src/trace_redaction/trace_redactor_integrationtest.cc
@@ -41,61 +41,31 @@
namespace perfetto::trace_redaction {
+// TODO(vaage): Add tests for the untested and/or included primitives:
+//
+// 1. Scrub process tree
+
namespace {
using FtraceEvent = protos::pbzero::FtraceEvent;
-using PackagesList = protos::pbzero::PackagesList;
-using PackageInfo = protos::pbzero::PackagesList::PackageInfo;
-using Trace = protos::pbzero::Trace;
-using TracePacket = protos::pbzero::TracePacket;
constexpr std::string_view kTracePath =
"test/data/trace-redaction-general.pftrace";
+// Set the package name to "just some package name". If a specific package name
+// is needed, the test it should overwrite this value.
+constexpr std::string_view kPackageName =
+ "com.Unity.com.unity.multiplayer.samples.coop";
constexpr uint64_t kPackageUid = 10252;
class TraceRedactorIntegrationTest : public testing::Test {
- public:
- TraceRedactorIntegrationTest() = default;
- ~TraceRedactorIntegrationTest() override = default;
-
protected:
void SetUp() override {
src_trace_ = base::GetTestDataPath(std::string(kTracePath));
-
- // Add every primitive to the redactor. This should mirror the production
- // configuration. This configuration may differ to help with verifying the
- // results.
- redactor_.collectors()->emplace_back(new FindPackageUid());
- redactor_.builders()->emplace_back(new PopulateAllowlists());
- redactor_.transformers()->emplace_back(new PrunePackageList());
- redactor_.transformers()->emplace_back(new ScrubTracePacket());
- redactor_.transformers()->emplace_back(new ScrubFtraceEvents());
-
- // Set the package name to "just some package name". If a specific package
- // name is needed, it should overwrite this value.
- context_.package_name = "com.google.omadm.trigger";
+ context_.package_name = kPackageName;
}
const std::string& src_trace() const { return src_trace_; }
- 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;
- }
-
static base::StatusOr<std::string> ReadRawTrace(const std::string& path) {
std::string redacted_buffer;
@@ -106,11 +76,164 @@
return base::ErrStatus("Failed to read %s", path.c_str());
}
+ std::string src_trace_;
+ base::TmpDirTree tmp_dir_;
+
+ Context context_;
+ TraceRedactor redactor_;
+};
+
+class PackageListTraceRedactorIntegrationTest
+ : public TraceRedactorIntegrationTest {
+ protected:
+ void SetUp() override {
+ TraceRedactorIntegrationTest::SetUp();
+
+ redactor_.collectors()->emplace_back(new FindPackageUid());
+ redactor_.transformers()->emplace_back(new PrunePackageList());
+ }
+
+ std::vector<protozero::ConstBytes> GetPackageInfos(
+ const protos::pbzero::Trace::Decoder& trace) const {
+ std::vector<protozero::ConstBytes> infos;
+
+ for (auto packet_it = trace.packet(); packet_it; ++packet_it) {
+ protos::pbzero::TracePacket::Decoder packet_decoder(*packet_it);
+ if (packet_decoder.has_packages_list()) {
+ protos::pbzero::PackagesList::Decoder list_it(
+ packet_decoder.packages_list());
+ for (auto info_it = list_it.packages(); info_it; ++info_it) {
+ protos::pbzero::PackagesList::PackageInfo::Decoder info(*info_it);
+ infos.push_back(*info_it);
+ }
+ }
+ }
+
+ return infos;
+ }
+};
+
+TEST_F(PackageListTraceRedactorIntegrationTest,
+ FindsPackageAndFiltersPackageList) {
+ auto result = redactor_.Redact(
+ src_trace(), tmp_dir_.AbsolutePath("dst.pftrace"), &context_);
+
+ ASSERT_OK(result) << result.message();
+
+ tmp_dir_.TrackFile("dst.pftrace");
+
+ ASSERT_OK_AND_ASSIGN(auto redacted_buffer,
+ ReadRawTrace(tmp_dir_.AbsolutePath("dst.pftrace")));
+
+ protos::pbzero::Trace::Decoder redacted_trace(redacted_buffer);
+ std::vector<protozero::ConstBytes> infos = GetPackageInfos(redacted_trace);
+
+ ASSERT_TRUE(context_.package_uid.has_value());
+ ASSERT_EQ(NormalizeUid(context_.package_uid.value()),
+ NormalizeUid(kPackageUid));
+
+ // 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_EQ(infos.size(), 2u);
+
+ std::vector<protos::pbzero::PackagesList::PackageInfo::Decoder> decoders;
+ decoders.emplace_back(infos[0]);
+ decoders.emplace_back(infos[1]);
+
+ for (auto& decoder : decoders) {
+ ASSERT_TRUE(decoder.has_name());
+ ASSERT_EQ(decoder.name().ToStdString(),
+ "com.Unity.com.unity.multiplayer.samples.coop");
+
+ ASSERT_TRUE(decoder.has_uid());
+ ASSERT_EQ(NormalizeUid(decoder.uid()), NormalizeUid(kPackageUid));
+ }
+}
+
+// It is possible for multiple packages to share a uid. The names will appears
+// across multiple package lists. The only time the package name appears is in
+// the package list, so there is no way to differentiate these packages (only
+// the uid is used later), so each entry should remain.
+TEST_F(PackageListTraceRedactorIntegrationTest, RetainsAllInstancesOfUid) {
+ context_.package_name = "com.google.android.networkstack.tethering";
+
+ auto result = redactor_.Redact(
+ src_trace(), tmp_dir_.AbsolutePath("dst.pftrace"), &context_);
+
+ ASSERT_OK(result) << result.message();
+
+ tmp_dir_.TrackFile("dst.pftrace");
+
+ ASSERT_OK_AND_ASSIGN(auto redacted_buffer,
+ ReadRawTrace(tmp_dir_.AbsolutePath("dst.pftrace")));
+
+ protos::pbzero::Trace::Decoder redacted_trace(redacted_buffer);
+ std::vector<protozero::ConstBytes> infos = GetPackageInfos(redacted_trace);
+
+ ASSERT_EQ(infos.size(), 8u);
+
+ std::array<std::string, 8> package_names;
+
+ for (size_t i = 0; i < infos.size(); ++i) {
+ protos::pbzero::PackagesList::PackageInfo::Decoder info(infos[i]);
+ ASSERT_TRUE(info.has_name());
+ package_names[i] = info.name().ToStdString();
+ }
+
+ std::sort(package_names.begin(), package_names.end());
+ ASSERT_EQ(package_names[0], "com.google.android.cellbroadcastservice");
+ ASSERT_EQ(package_names[1], "com.google.android.cellbroadcastservice");
+ ASSERT_EQ(package_names[2], "com.google.android.networkstack");
+ ASSERT_EQ(package_names[3], "com.google.android.networkstack");
+ ASSERT_EQ(package_names[4],
+ "com.google.android.networkstack.permissionconfig");
+ ASSERT_EQ(package_names[5],
+ "com.google.android.networkstack.permissionconfig");
+ ASSERT_EQ(package_names[6], "com.google.android.networkstack.tethering");
+ ASSERT_EQ(package_names[7], "com.google.android.networkstack.tethering");
+}
+
+// Broadphase redactions are meant to remove large pieces of information (e.g.
+// whole packets). They need to be tested on their own because finer-grain
+// redactions can make it harder to verify the results. For example, broadphase
+// redactions should not remove rename events. There is a finer grain redaction
+// that takes care of those events. However, it the "scrub rename events"
+// redaction removes all the rename events, it could look like the broadphase
+// primitive did it.
+class BroadphaseTraceRedactorIntegrationTest
+ : public TraceRedactorIntegrationTest {
+ protected:
+ void SetUp() override {
+ TraceRedactorIntegrationTest::SetUp();
+
+ redactor_.collectors()->emplace_back(new FindPackageUid());
+ redactor_.builders()->emplace_back(new PopulateAllowlists());
+ redactor_.transformers()->emplace_back(new ScrubTracePacket());
+ redactor_.transformers()->emplace_back(new ScrubFtraceEvents());
+ }
+
+ static base::StatusOr<protozero::ConstBytes> FindFirstFtraceEvents(
+ const protos::pbzero::Trace::Decoder& trace) {
+ for (auto packet_it = trace.packet(); packet_it; ++packet_it) {
+ protos::pbzero::TracePacket::Decoder packet(*packet_it);
+
+ if (packet.has_ftrace_events()) {
+ return packet.ftrace_events();
+ }
+ }
+
+ return base::ErrStatus("Failed to find ftrace events");
+ }
+
// NOTE - this will include fields like "timestamp" and "pid".
- static void GetEventFields(const Trace::Decoder& trace,
+ static void GetEventFields(const protos::pbzero::Trace::Decoder& trace,
base::FlatSet<uint32_t>* set) {
for (auto packet_it = trace.packet(); packet_it; ++packet_it) {
- TracePacket::Decoder packet(*packet_it);
+ protos::pbzero::TracePacket::Decoder packet(*packet_it);
if (!packet.has_ftrace_events()) {
continue;
@@ -132,110 +255,10 @@
}
}
}
-
- static base::StatusOr<protozero::ConstBytes> FindFirstFtraceEvents(
- const Trace::Decoder& trace) {
- for (auto packet_it = trace.packet(); packet_it; ++packet_it) {
- TracePacket::Decoder packet(*packet_it);
-
- if (packet.has_ftrace_events()) {
- return packet.ftrace_events();
- }
- }
-
- return base::ErrStatus("Failed to find ftrace events");
- }
-
- std::string src_trace_;
- base::TmpDirTree tmp_dir_;
-
- Context context_;
- TraceRedactor redactor_;
};
-TEST_F(TraceRedactorIntegrationTest, FindsPackageAndFiltersPackageList) {
- context_.package_name = "com.Unity.com.unity.multiplayer.samples.coop";
-
- auto result = redactor_.Redact(
- src_trace(), tmp_dir_.AbsolutePath("dst.pftrace"), &context_);
- tmp_dir_.TrackFile("dst.pftrace");
-
- ASSERT_OK(result);
-
- ASSERT_OK_AND_ASSIGN(auto redacted_buffer,
- ReadRawTrace(tmp_dir_.AbsolutePath("dst.pftrace")));
-
- Trace::Decoder redacted_trace(redacted_buffer);
- std::vector<protozero::ConstBytes> infos = GetPackageInfos(redacted_trace);
-
- ASSERT_TRUE(context_.package_uid.has_value());
- ASSERT_EQ(NormalizeUid(context_.package_uid.value()),
- NormalizeUid(kPackageUid));
-
- // 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_EQ(infos.size(), 2u);
-
- std::array<PackageInfo::Decoder, 2> decoders = {
- PackageInfo::Decoder(infos[0]), PackageInfo::Decoder(infos[1])};
-
- for (auto& decoder : decoders) {
- ASSERT_TRUE(decoder.has_name());
- ASSERT_EQ(decoder.name().ToStdString(),
- "com.Unity.com.unity.multiplayer.samples.coop");
-
- ASSERT_TRUE(decoder.has_uid());
- ASSERT_EQ(NormalizeUid(decoder.uid()), NormalizeUid(kPackageUid));
- }
-}
-
-// It is possible for multiple packages to share a uid. The names will appears
-// across multiple package lists. The only time the package name appears is in
-// the package list, so there is no way to differentiate these packages (only
-// the uid is used later), so each entry should remain.
-TEST_F(TraceRedactorIntegrationTest, RetainsAllInstancesOfUid) {
- context_.package_name = "com.google.android.networkstack.tethering";
-
- auto result = redactor_.Redact(
- src_trace(), tmp_dir_.AbsolutePath("dst.pftrace"), &context_);
- tmp_dir_.TrackFile("dst.pftrace");
- ASSERT_OK(result);
-
- ASSERT_OK_AND_ASSIGN(auto redacted_buffer,
- ReadRawTrace(tmp_dir_.AbsolutePath("dst.pftrace")));
-
- Trace::Decoder redacted_trace(redacted_buffer);
- std::vector<protozero::ConstBytes> infos = GetPackageInfos(redacted_trace);
-
- ASSERT_EQ(infos.size(), 8u);
-
- std::array<std::string, 8> package_names;
-
- for (size_t i = 0; i < infos.size(); ++i) {
- PackageInfo::Decoder info(infos[i]);
- ASSERT_TRUE(info.has_name());
- package_names[i] = info.name().ToStdString();
- }
-
- std::sort(package_names.begin(), package_names.end());
- ASSERT_EQ(package_names[0], "com.google.android.cellbroadcastservice");
- ASSERT_EQ(package_names[1], "com.google.android.cellbroadcastservice");
- ASSERT_EQ(package_names[2], "com.google.android.networkstack");
- ASSERT_EQ(package_names[3], "com.google.android.networkstack");
- ASSERT_EQ(package_names[4],
- "com.google.android.networkstack.permissionconfig");
- ASSERT_EQ(package_names[5],
- "com.google.android.networkstack.permissionconfig");
- ASSERT_EQ(package_names[6], "com.google.android.networkstack.tethering");
- ASSERT_EQ(package_names[7], "com.google.android.networkstack.tethering");
-}
-
// Makes sure all not-allowed ftrace event is removed from a trace.
-TEST_F(TraceRedactorIntegrationTest, RemovesFtraceEvents) {
+TEST_F(BroadphaseTraceRedactorIntegrationTest, RemovesFtraceEvents) {
auto pre_redaction_file = src_trace();
auto post_redaction_file = tmp_dir_.AbsolutePath("dst.pftrace");
@@ -243,7 +266,7 @@
// events are not in the allowlist and should be dropped.
auto pre_redaction_buffer = ReadRawTrace(pre_redaction_file);
ASSERT_OK(pre_redaction_buffer) << pre_redaction_buffer.status().message();
- Trace::Decoder pre_redaction_trace(*pre_redaction_buffer);
+ protos::pbzero::Trace::Decoder pre_redaction_trace(*pre_redaction_buffer);
base::FlatSet<uint32_t> pre_redaction_event_types;
GetEventFields(pre_redaction_trace, &pre_redaction_event_types);
@@ -258,7 +281,7 @@
auto post_redaction_buffer = ReadRawTrace(post_redaction_file);
ASSERT_OK(post_redaction_buffer) << post_redaction_buffer.status().message();
- Trace::Decoder post_redaction_trace(*post_redaction_buffer);
+ protos::pbzero::Trace::Decoder post_redaction_trace(*post_redaction_buffer);
base::FlatSet<uint32_t> post_redaction_event_types;
GetEventFields(post_redaction_trace, &post_redaction_event_types);
@@ -269,7 +292,7 @@
// When a event is dropped from ftrace_events, only that event should be droped,
// the other events in the ftrace_events should be retained.
-TEST_F(TraceRedactorIntegrationTest,
+TEST_F(BroadphaseTraceRedactorIntegrationTest,
RetainsFtraceEventsWhenRemovingFtraceEvent) {
auto pre_redaction_file = src_trace();
auto post_redaction_file = tmp_dir_.AbsolutePath("dst.pftrace");
@@ -277,7 +300,7 @@
auto pre_redaction_buffer = ReadRawTrace(pre_redaction_file);
ASSERT_OK(pre_redaction_buffer) << pre_redaction_buffer.status().message();
- Trace::Decoder pre_redaction_trace(*pre_redaction_buffer);
+ protos::pbzero::Trace::Decoder pre_redaction_trace(*pre_redaction_buffer);
auto pre_redaction_first_events = FindFirstFtraceEvents(pre_redaction_trace);
ASSERT_OK(pre_redaction_first_events)
@@ -291,7 +314,7 @@
auto post_redaction_buffer = ReadRawTrace(post_redaction_file);
ASSERT_OK(post_redaction_buffer) << post_redaction_buffer.status().message();
- Trace::Decoder post_redaction_trace(*post_redaction_buffer);
+ protos::pbzero::Trace::Decoder post_redaction_trace(*post_redaction_buffer);
auto post_redaction_ftrace_events =
FindFirstFtraceEvents(post_redaction_trace);
diff --git a/src/traced/probes/ftrace/event_info.cc b/src/traced/probes/ftrace/event_info.cc
index 46b7d24..6db79ff 100644
--- a/src/traced/probes/ftrace/event_info.cc
+++ b/src/traced/probes/ftrace/event_info.cc
@@ -7361,6 +7361,31 @@
kUnsetFtraceId,
430,
kUnsetSize},
+ {"panel_write_generic",
+ "panel",
+ {
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "pid", 1, ProtoSchemaType::kInt32,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "trace_name", 2, ProtoSchemaType::kString,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "trace_begin", 3, ProtoSchemaType::kUint32,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "name", 4, ProtoSchemaType::kString,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "type", 5, ProtoSchemaType::kUint32,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+ "value", 6, ProtoSchemaType::kInt32,
+ TranslationStrategy::kInvalidTranslationStrategy},
+ },
+ kUnsetFtraceId,
+ 490,
+ kUnsetSize},
{"sched_switch_with_ctrs",
"perf_trace_counters",
{
diff --git a/src/traced/probes/ftrace/ftrace_config_muxer.cc b/src/traced/probes/ftrace/ftrace_config_muxer.cc
index 7fcfef1..97969be 100644
--- a/src/traced/probes/ftrace/ftrace_config_muxer.cc
+++ b/src/traced/probes/ftrace/ftrace_config_muxer.cc
@@ -203,6 +203,9 @@
AddEventGroup(table, "g2d", &events);
InsertEvent("g2d", "tracing_mark_write", &events);
InsertEvent("g2d", "g2d_perf_update_qos", &events);
+
+ AddEventGroup(table, "panel", &events);
+ InsertEvent("panel", "panel_write_generic", &events);
continue;
}
diff --git a/src/traced/probes/ftrace/test/data/synthetic/available_events b/src/traced/probes/ftrace/test/data/synthetic/available_events
index c57e8f9..299c4ac 100644
--- a/src/traced/probes/ftrace/test/data/synthetic/available_events
+++ b/src/traced/probes/ftrace/test/data/synthetic/available_events
@@ -13,3 +13,4 @@
power:suspend_resume
cpuhp:cpuhp_pause
lwis:tracing_mark_write
+panel:panel_write_generic
diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/panel/panel_write_generic/format b/src/traced/probes/ftrace/test/data/synthetic/events/panel/panel_write_generic/format
new file mode 100644
index 0000000..550e6ef
--- /dev/null
+++ b/src/traced/probes/ftrace/test/data/synthetic/events/panel/panel_write_generic/format
@@ -0,0 +1,14 @@
+name: panel_write_generic
+ID: 1122
+format:
+ field:unsigned short common_type; offset:0; size:2; signed:0;
+ field:unsigned char common_flags; offset:2; size:1; signed:0;
+ field:unsigned char common_preempt_count; offset:3; size:1; signed:0;
+ field:int common_pid; offset:4; size:4; signed:1;
+
+ field:char type; offset:8; size:1; signed:0;
+ field:int pid; offset:12; size:4; signed:1;
+ field:__data_loc char[] name; offset:16; size:4; signed:0;
+ field:int value; offset:20; size:4; signed:1;
+
+print fmt: "%c|%d|%s|%d", REC->type, REC->pid, __get_str(name), REC->value
diff --git a/src/tracing/service/trace_buffer_unittest.cc b/src/tracing/service/trace_buffer_unittest.cc
index 906b0df..65cb425 100644
--- a/src/tracing/service/trace_buffer_unittest.cc
+++ b/src/tracing/service/trace_buffer_unittest.cc
@@ -812,24 +812,24 @@
TraceBuffer::PacketSequenceProperties sequence_properties;
ASSERT_THAT(ReadPacket(&sequence_properties),
ElementsAre(FakePacketFragment(10, 'a')));
- ASSERT_EQ(11u, sequence_properties.producer_uid_trusted());
+ ASSERT_EQ(static_cast<uid_t>(11), sequence_properties.producer_uid_trusted());
ASSERT_THAT(
ReadPacket(&sequence_properties),
ElementsAre(FakePacketFragment(10, 'b'), FakePacketFragment(10, 'e')));
- ASSERT_EQ(11u, sequence_properties.producer_uid_trusted());
+ ASSERT_EQ(static_cast<uid_t>(11), sequence_properties.producer_uid_trusted());
ASSERT_THAT(ReadPacket(&sequence_properties),
ElementsAre(FakePacketFragment(10, 'f')));
- ASSERT_EQ(11u, sequence_properties.producer_uid_trusted());
+ ASSERT_EQ(static_cast<uid_t>(11), sequence_properties.producer_uid_trusted());
ASSERT_THAT(ReadPacket(&sequence_properties),
ElementsAre(FakePacketFragment(10, 'c')));
- ASSERT_EQ(22u, sequence_properties.producer_uid_trusted());
+ ASSERT_EQ(static_cast<uid_t>(22), sequence_properties.producer_uid_trusted());
ASSERT_THAT(ReadPacket(&sequence_properties),
ElementsAre(FakePacketFragment(10, 'd')));
- ASSERT_EQ(22u, sequence_properties.producer_uid_trusted());
+ ASSERT_EQ(static_cast<uid_t>(22), sequence_properties.producer_uid_trusted());
ASSERT_THAT(ReadPacket(), IsEmpty());
}
diff --git a/tools/gen_c_protos b/tools/gen_c_protos
index 38a7491..370e59a 100755
--- a/tools/gen_c_protos
+++ b/tools/gen_c_protos
@@ -49,6 +49,8 @@
'files': [
'src/protozero/test/example_proto/library.proto',
'src/protozero/test/example_proto/library_internals/galaxies.proto',
+ 'src/protozero/test/example_proto/other_package/test_messages.proto',
+ 'src/protozero/test/example_proto/subpackage/test_messages.proto',
'src/protozero/test/example_proto/test_messages.proto',
'src/protozero/test/example_proto/upper_import.proto',
],
diff --git a/ui/src/common/actions.ts b/ui/src/common/actions.ts
index 139fc7b..a4f0213 100644
--- a/ui/src/common/actions.ts
+++ b/ui/src/common/actions.ts
@@ -516,9 +516,12 @@
selectNote(state: StateDraft, args: {id: string}): void {
if (args.id) {
- state.currentSelection = {
- kind: 'NOTE',
- id: args.id,
+ state.selection = {
+ kind: 'legacy',
+ legacySelection: {
+ kind: 'NOTE',
+ id: args.id,
+ },
};
}
},
@@ -553,32 +556,33 @@
state: StateDraft,
args: {color: string; persistent: boolean},
): void {
- if (
- state.currentSelection === null ||
- state.currentSelection.kind !== 'AREA'
- ) {
+ if (state.selection.kind !== 'legacy') {
return;
}
+ if (state.selection.legacySelection.kind !== 'AREA') {
+ return;
+ }
+ const legacySelection = state.selection.legacySelection;
const id = args.persistent ? generateNextId(state) : '0';
const color = args.persistent ? args.color : '#344596';
state.notes[id] = {
noteType: 'AREA',
id,
- areaId: state.currentSelection.areaId,
+ areaId: legacySelection.areaId,
color,
text: '',
};
- state.currentSelection.noteId = id;
+ legacySelection.noteId = id;
},
toggleMarkCurrentArea(state: StateDraft, args: {persistent: boolean}) {
- const selection = state.currentSelection;
+ const selection = state.selection;
if (
- selection != null &&
- selection.kind === 'AREA' &&
- selection.noteId !== undefined
+ selection.kind === 'legacy' &&
+ selection.legacySelection.kind === 'AREA' &&
+ selection.legacySelection.noteId !== undefined
) {
- this.removeNote(state, {id: selection.noteId});
+ this.removeNote(state, {id: selection.legacySelection.noteId});
} else {
const color = randomColor();
this.markCurrentArea(state, {color, persistent: args.persistent});
@@ -621,17 +625,19 @@
delete state.notes[args.id];
// For regular notes, we clear the current selection but for an area note
// we only want to clear the note/marking and leave the area selected.
- if (state.currentSelection === null) return;
+ if (state.selection.kind !== 'legacy') return;
if (
- state.currentSelection.kind === 'NOTE' &&
- state.currentSelection.id === args.id
+ state.selection.legacySelection.kind === 'NOTE' &&
+ state.selection.legacySelection.id === args.id
) {
- state.currentSelection = null;
+ state.selection = {
+ kind: 'empty',
+ };
} else if (
- state.currentSelection.kind === 'AREA' &&
- state.currentSelection.noteId === args.id
+ state.selection.legacySelection.kind === 'AREA' &&
+ state.selection.legacySelection.noteId === args.id
) {
- state.currentSelection.noteId = undefined;
+ state.selection.legacySelection.noteId = undefined;
}
},
@@ -639,10 +645,13 @@
state: StateDraft,
args: {id: number; trackKey: string; scroll?: boolean},
): void {
- state.currentSelection = {
- kind: 'SLICE',
- id: args.id,
- trackKey: args.trackKey,
+ state.selection = {
+ kind: 'legacy',
+ legacySelection: {
+ kind: 'SLICE',
+ id: args.id,
+ trackKey: args.trackKey,
+ },
};
state.pendingScrollId = args.scroll ? args.id : undefined;
},
@@ -651,12 +660,15 @@
state: StateDraft,
args: {leftTs: time; rightTs: time; id: number; trackKey: string},
): void {
- state.currentSelection = {
- kind: 'COUNTER',
- leftTs: args.leftTs,
- rightTs: args.rightTs,
- id: args.id,
- trackKey: args.trackKey,
+ state.selection = {
+ kind: 'legacy',
+ legacySelection: {
+ kind: 'COUNTER',
+ leftTs: args.leftTs,
+ rightTs: args.rightTs,
+ id: args.id,
+ trackKey: args.trackKey,
+ },
};
},
@@ -664,12 +676,15 @@
state: StateDraft,
args: {id: number; upid: number; ts: time; type: ProfileType},
): void {
- state.currentSelection = {
- kind: 'HEAP_PROFILE',
- id: args.id,
- upid: args.upid,
- ts: args.ts,
- type: args.type,
+ state.selection = {
+ kind: 'legacy',
+ legacySelection: {
+ kind: 'HEAP_PROFILE',
+ id: args.id,
+ upid: args.upid,
+ ts: args.ts,
+ type: args.type,
+ },
};
this.openFlamegraph(state, {
type: args.type,
@@ -690,13 +705,16 @@
type: ProfileType;
},
): void {
- state.currentSelection = {
- kind: 'PERF_SAMPLES',
- id: args.id,
- upid: args.upid,
- leftTs: args.leftTs,
- rightTs: args.rightTs,
- type: args.type,
+ state.selection = {
+ kind: 'legacy',
+ legacySelection: {
+ kind: 'PERF_SAMPLES',
+ id: args.id,
+ upid: args.upid,
+ leftTs: args.leftTs,
+ rightTs: args.rightTs,
+ type: args.type,
+ },
};
this.openFlamegraph(state, {
type: args.type,
@@ -732,11 +750,14 @@
state: StateDraft,
args: {id: number; utid: number; ts: time},
): void {
- state.currentSelection = {
- kind: 'CPU_PROFILE_SAMPLE',
- id: args.id,
- utid: args.utid,
- ts: args.ts,
+ state.selection = {
+ kind: 'legacy',
+ legacySelection: {
+ kind: 'CPU_PROFILE_SAMPLE',
+ id: args.id,
+ utid: args.utid,
+ ts: args.ts,
+ },
};
},
@@ -768,11 +789,14 @@
state: StateDraft,
args: {id: number; trackKey: string; table?: string; scroll?: boolean},
): void {
- state.currentSelection = {
- kind: 'CHROME_SLICE',
- id: args.id,
- trackKey: args.trackKey,
- table: args.table,
+ state.selection = {
+ kind: 'legacy',
+ legacySelection: {
+ kind: 'CHROME_SLICE',
+ id: args.id,
+ trackKey: args.trackKey,
+ table: args.table,
+ },
};
state.pendingScrollId = args.scroll ? args.id : undefined;
},
@@ -796,16 +820,19 @@
...args.detailsPanelConfig.config,
};
- state.currentSelection = {
- kind: 'GENERIC_SLICE',
- id: args.id,
- sqlTableName: args.sqlTableName,
- start: args.start,
- duration: args.duration,
- trackKey: args.trackKey,
- detailsPanelConfig: {
- kind: args.detailsPanelConfig.kind,
- config: detailsPanelConfig,
+ state.selection = {
+ kind: 'legacy',
+ legacySelection: {
+ kind: 'GENERIC_SLICE',
+ id: args.id,
+ sqlTableName: args.sqlTableName,
+ start: args.start,
+ duration: args.duration,
+ trackKey: args.trackKey,
+ detailsPanelConfig: {
+ kind: args.detailsPanelConfig.kind,
+ config: detailsPanelConfig,
+ },
},
};
},
@@ -818,10 +845,13 @@
state: StateDraft,
args: {id: number; trackKey: string},
): void {
- state.currentSelection = {
- kind: 'THREAD_STATE',
- id: args.id,
- trackKey: args.trackKey,
+ state.selection = {
+ kind: 'legacy',
+ legacySelection: {
+ kind: 'THREAD_STATE',
+ id: args.id,
+ trackKey: args.trackKey,
+ },
};
},
@@ -829,16 +859,21 @@
state: StateDraft,
args: {id: number; trackKey: string; scroll?: boolean},
): void {
- state.currentSelection = {
- kind: 'LOG',
- id: args.id,
- trackKey: args.trackKey,
+ state.selection = {
+ kind: 'legacy',
+ legacySelection: {
+ kind: 'LOG',
+ id: args.id,
+ trackKey: args.trackKey,
+ },
};
state.pendingScrollId = args.scroll ? args.id : undefined;
},
deselect(state: StateDraft, _: {}): void {
- state.currentSelection = null;
+ state.selection = {
+ kind: 'empty',
+ };
},
updateLogsPagination(state: StateDraft, args: Pagination): void {
@@ -919,7 +954,10 @@
assertTrue(start <= end);
const areaId = generateNextId(state);
state.areas[areaId] = {id: areaId, start, end, tracks};
- state.currentSelection = {kind: 'AREA', areaId};
+ state.selection = {
+ kind: 'legacy',
+ legacySelection: {kind: 'AREA', areaId},
+ };
},
editArea(state: StateDraft, args: {area: Area; areaId: string}): void {
@@ -932,10 +970,13 @@
state: StateDraft,
args: {areaId: string; noteId: string},
): void {
- state.currentSelection = {
- kind: 'AREA',
- areaId: args.areaId,
- noteId: args.noteId,
+ state.selection = {
+ kind: 'legacy',
+ legacySelection: {
+ kind: 'AREA',
+ areaId: args.areaId,
+ noteId: args.noteId,
+ },
};
},
@@ -943,9 +984,14 @@
state: StateDraft,
args: {id: string; isTrackGroup: boolean},
) {
- const selection = state.currentSelection;
- if (selection === null || selection.kind !== 'AREA') return;
- const areaId = selection.areaId;
+ const selection = state.selection;
+ if (
+ selection.kind !== 'legacy' ||
+ selection.legacySelection.kind !== 'AREA'
+ ) {
+ return;
+ }
+ const areaId = selection.legacySelection.areaId;
const index = state.areas[areaId].tracks.indexOf(args.id);
if (index > -1) {
state.areas[areaId].tracks.splice(index, 1);
@@ -973,7 +1019,7 @@
// selection to be updated and this leads to bugs for people who do:
// if (oldSelection !== state.selection) etc.
// To solve this re-create the selection object here:
- state.currentSelection = Object.assign({}, state.currentSelection);
+ state.selection = Object.assign({}, state.selection);
},
setVisibleTraceTime(state: StateDraft, args: VisibleState): void {
diff --git a/ui/src/common/empty_state.ts b/ui/src/common/empty_state.ts
index f3f8a0e..f6b2fee 100644
--- a/ui/src/common/empty_state.ts
+++ b/ui/src/common/empty_state.ts
@@ -139,7 +139,9 @@
},
status: {msg: '', timestamp: 0},
- currentSelection: null,
+ selection: {
+ kind: 'empty',
+ },
currentFlamegraphState: null,
traceConversionInProgress: false,
diff --git a/ui/src/common/selection_observer.ts b/ui/src/common/selection_observer.ts
index 5bf8dfb..8cb7f12 100644
--- a/ui/src/common/selection_observer.ts
+++ b/ui/src/common/selection_observer.ts
@@ -12,17 +12,17 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {Selection} from './state';
+import {LegacySelection} from './state';
export type SelectionChangedObserver = (
- selection: Selection | undefined,
+ selection: LegacySelection | undefined,
openCurrentSelectionTab: boolean,
) => void;
const selectionObservers: SelectionChangedObserver[] = [];
export function onSelectionChanged(
- selection: Selection | undefined,
+ selection: LegacySelection | undefined,
openCurrentSelectionTab: boolean,
) {
for (const observer of selectionObservers) {
diff --git a/ui/src/common/state.ts b/ui/src/common/state.ts
index f7c2562..11fe179 100644
--- a/ui/src/common/state.ts
+++ b/ui/src/common/state.ts
@@ -134,7 +134,9 @@
// 44. Add TabsV2 state.
// 45. Remove v1 tracks.
// 46. Remove trackKeyByTrackId.
-export const STATE_VERSION = 46;
+// 47. Selection V2
+// 48. Rename legacySelection -> selection and introduce new Selection type.
+export const STATE_VERSION = 48;
export const SCROLLING_TRACK_GROUP = 'ScrollingTracks';
@@ -386,7 +388,7 @@
detailsPanelConfig: {kind: string; config: GenericSliceDetailsTabConfigBase};
}
-export type Selection = (
+export type LegacySelection = (
| NoteSelection
| SliceSelection
| CounterSelection
@@ -399,7 +401,41 @@
| LogSelection
| GenericSliceSelection
) & {trackKey?: string};
-export type SelectionKind = Selection['kind']; // 'THREAD_STATE' | 'SLICE' ...
+export type SelectionKind = LegacySelection['kind']; // 'THREAD_STATE' | 'SLICE' ...
+
+export interface LegacySelectionWrapper {
+ kind: 'legacy';
+ legacySelection: LegacySelection;
+}
+
+export interface SingleSelection {
+ kind: 'single';
+ trackKey: string;
+ eventId: string;
+}
+
+export interface NewAreaSelection {
+ kind: 'area';
+ trackKey: string;
+ start: time;
+ end: time;
+}
+
+export interface UnionSelection {
+ kind: 'union';
+ selections: Selection[];
+}
+
+export interface EmptySelection {
+ kind: 'empty';
+}
+
+export type Selection =
+ | SingleSelection
+ | NewAreaSelection
+ | UnionSelection
+ | EmptySelection
+ | LegacySelectionWrapper;
export interface Pagination {
offset: number;
@@ -573,7 +609,7 @@
permalink: PermalinkConfig;
notes: ObjectById<Note | AreaNote>;
status: Status;
- currentSelection: Selection | null;
+ selection: Selection;
currentFlamegraphState: FlamegraphState | null;
logsPagination: Pagination;
ftracePagination: Pagination;
@@ -997,3 +1033,11 @@
}
return parentId;
}
+
+export function getLegacySelection(state: State): LegacySelection | null {
+ const selection = state.selection;
+ if (selection.kind === 'legacy') {
+ return selection.legacySelection;
+ }
+ return null;
+}
diff --git a/ui/src/controller/aggregation/aggregation_controller.ts b/ui/src/controller/aggregation/aggregation_controller.ts
index 1809a9c..2e8cdb2 100644
--- a/ui/src/controller/aggregation/aggregation_controller.ts
+++ b/ui/src/controller/aggregation/aggregation_controller.ts
@@ -19,7 +19,7 @@
ColumnDef,
ThreadStateExtra,
} from '../../common/aggregation_data';
-import {Area, Sorting} from '../../common/state';
+import {Area, Sorting, getLegacySelection} from '../../common/state';
import {globals} from '../../frontend/globals';
import {publishAggregateData} from '../../frontend/publish';
import {Engine} from '../../trace_processor/engine';
@@ -61,7 +61,7 @@
}
run() {
- const selection = globals.state.currentSelection;
+ const selection = getLegacySelection(globals.state);
if (selection === null || selection.kind !== 'AREA') {
publishAggregateData({
data: {
diff --git a/ui/src/controller/area_selection_handler.ts b/ui/src/controller/area_selection_handler.ts
index 3efaca8..0104ca8 100644
--- a/ui/src/controller/area_selection_handler.ts
+++ b/ui/src/controller/area_selection_handler.ts
@@ -12,14 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {Area, AreaById} from '../common/state';
+import {Area, AreaById, getLegacySelection} from '../common/state';
import {globals} from '../frontend/globals';
export class AreaSelectionHandler {
private previousArea?: Area;
getAreaChange(): [boolean, AreaById | undefined] {
- const currentSelection = globals.state.currentSelection;
+ const currentSelection = getLegacySelection(globals.state);
if (currentSelection === null || currentSelection.kind !== 'AREA') {
return [false, undefined];
}
diff --git a/ui/src/controller/area_selection_handler_unittest.ts b/ui/src/controller/area_selection_handler_unittest.ts
index 6b7d312..7f27a03 100644
--- a/ui/src/controller/area_selection_handler_unittest.ts
+++ b/ui/src/controller/area_selection_handler_unittest.ts
@@ -33,7 +33,10 @@
id: areaId,
};
globals.store.edit((draft) => {
- draft.currentSelection = {kind: 'AREA', areaId: areaId};
+ draft.selection = {
+ kind: 'legacy',
+ legacySelection: {kind: 'AREA', areaId: areaId},
+ };
draft.areas[areaId] = latestArea;
});
@@ -53,9 +56,12 @@
id: previousAreaId,
};
globals.store.edit((draft) => {
- draft.currentSelection = {
- kind: 'AREA',
- areaId: previousAreaId,
+ draft.selection = {
+ kind: 'legacy',
+ legacySelection: {
+ kind: 'AREA',
+ areaId: previousAreaId,
+ },
};
draft.areas[previousAreaId] = previous;
});
@@ -64,9 +70,12 @@
const currentAreaId = '1';
globals.store.edit((draft) => {
- draft.currentSelection = {
- kind: 'AREA',
- areaId: currentAreaId,
+ draft.selection = {
+ kind: 'legacy',
+ legacySelection: {
+ kind: 'AREA',
+ areaId: currentAreaId,
+ },
};
});
const [hasAreaChanged, selectedArea] = areaSelectionHandler.getAreaChange();
@@ -77,13 +86,19 @@
test('UndefinedAreaAfterUndefinedArea', () => {
globals.store.edit((draft) => {
- draft.currentSelection = {kind: 'AREA', areaId: '0'};
+ draft.selection = {
+ kind: 'legacy',
+ legacySelection: {kind: 'AREA', areaId: '0'},
+ };
});
const areaSelectionHandler = new AreaSelectionHandler();
areaSelectionHandler.getAreaChange();
globals.store.edit((draft) => {
- draft.currentSelection = {kind: 'AREA', areaId: '1'};
+ draft.selection = {
+ kind: 'legacy',
+ legacySelection: {kind: 'AREA', areaId: '1'},
+ };
});
const [hasAreaChanged, selectedArea] = areaSelectionHandler.getAreaChange();
@@ -100,9 +115,12 @@
id: previousAreaId,
};
globals.store.edit((draft) => {
- draft.currentSelection = {
- kind: 'AREA',
- areaId: previousAreaId,
+ draft.selection = {
+ kind: 'legacy',
+ legacySelection: {
+ kind: 'AREA',
+ areaId: previousAreaId,
+ },
};
draft.areas[previousAreaId] = previous;
});
@@ -117,9 +135,12 @@
id: currentAreaId,
};
globals.store.edit((draft) => {
- draft.currentSelection = {
- kind: 'AREA',
- areaId: currentAreaId,
+ draft.selection = {
+ kind: 'legacy',
+ legacySelection: {
+ kind: 'AREA',
+ areaId: currentAreaId,
+ },
};
draft.areas[currentAreaId] = current;
});
@@ -138,9 +159,12 @@
id: previousAreaId,
};
globals.store.edit((draft) => {
- draft.currentSelection = {
- kind: 'AREA',
- areaId: previousAreaId,
+ draft.selection = {
+ kind: 'legacy',
+ legacySelection: {
+ kind: 'AREA',
+ areaId: previousAreaId,
+ },
};
draft.areas[previousAreaId] = previous;
});
@@ -155,9 +179,12 @@
id: currentAreaId,
};
globals.store.edit((draft) => {
- draft.currentSelection = {
- kind: 'AREA',
- areaId: currentAreaId,
+ draft.selection = {
+ kind: 'legacy',
+ legacySelection: {
+ kind: 'AREA',
+ areaId: currentAreaId,
+ },
};
draft.areas[currentAreaId] = current;
});
@@ -169,17 +196,23 @@
test('NonAreaSelectionAfterUndefinedArea', () => {
globals.store.edit((draft) => {
- draft.currentSelection = {kind: 'AREA', areaId: '0'};
+ draft.selection = {
+ kind: 'legacy',
+ legacySelection: {kind: 'AREA', areaId: '0'},
+ };
});
const areaSelectionHandler = new AreaSelectionHandler();
areaSelectionHandler.getAreaChange();
globals.store.edit((draft) => {
- draft.currentSelection = {
- kind: 'COUNTER',
- leftTs: Time.fromRaw(0n),
- rightTs: Time.fromRaw(0n),
- id: 1,
+ draft.selection = {
+ kind: 'legacy',
+ legacySelection: {
+ kind: 'COUNTER',
+ leftTs: Time.fromRaw(0n),
+ rightTs: Time.fromRaw(0n),
+ id: 1,
+ },
};
});
const [hasAreaChanged, selectedArea] = areaSelectionHandler.getAreaChange();
diff --git a/ui/src/controller/cpu_profile_controller.ts b/ui/src/controller/cpu_profile_controller.ts
index 9b7aca2..fd410bf 100644
--- a/ui/src/controller/cpu_profile_controller.ts
+++ b/ui/src/controller/cpu_profile_controller.ts
@@ -12,7 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {CallsiteInfo, CpuProfileSampleSelection} from '../common/state';
+import {
+ CallsiteInfo,
+ CpuProfileSampleSelection,
+ getLegacySelection,
+} from '../common/state';
import {CpuProfileDetails, globals} from '../frontend/globals';
import {publishCpuProfileDetails} from '../frontend/publish';
import {Engine} from '../trace_processor/engine';
@@ -34,7 +38,7 @@
}
run() {
- const selection = globals.state.currentSelection;
+ const selection = getLegacySelection(globals.state);
if (!selection || selection.kind !== 'CPU_PROFILE_SAMPLE') {
return;
}
diff --git a/ui/src/controller/flow_events_controller.ts b/ui/src/controller/flow_events_controller.ts
index 8eaa585..d53142c 100644
--- a/ui/src/controller/flow_events_controller.ts
+++ b/ui/src/controller/flow_events_controller.ts
@@ -13,7 +13,7 @@
// limitations under the License.
import {Time} from '../base/time';
-import {Area} from '../common/state';
+import {Area, getLegacySelection} from '../common/state';
import {featureFlags} from '../core/feature_flags';
import {Flow, globals} from '../frontend/globals';
import {publishConnectedFlows, publishSelectedFlows} from '../frontend/publish';
@@ -443,7 +443,7 @@
}
refreshVisibleFlows() {
- const selection = globals.state.currentSelection;
+ const selection = getLegacySelection(globals.state);
if (!selection) {
this.lastSelectedKind = 'NONE';
publishConnectedFlows([]);
diff --git a/ui/src/controller/pivot_table_controller.ts b/ui/src/controller/pivot_table_controller.ts
index d01b642..b85f827 100644
--- a/ui/src/controller/pivot_table_controller.ts
+++ b/ui/src/controller/pivot_table_controller.ts
@@ -21,6 +21,7 @@
PivotTableQueryMetadata,
PivotTableResult,
PivotTableState,
+ getLegacySelection,
} from '../common/state';
import {featureFlags} from '../core/feature_flags';
import {globals} from '../frontend/globals';
@@ -298,7 +299,7 @@
}
const pivotTableState = globals.state.nonSerializableState.pivotTable;
- const selection = globals.state.currentSelection;
+ const selection = getLegacySelection(globals.state);
if (
pivotTableState.queryRequested ||
diff --git a/ui/src/controller/selection_controller.ts b/ui/src/controller/selection_controller.ts
index ea1db14..d30b458 100644
--- a/ui/src/controller/selection_controller.ts
+++ b/ui/src/controller/selection_controller.ts
@@ -15,7 +15,7 @@
import {assertTrue} from '../base/logging';
import {Time, time} from '../base/time';
import {Args, ArgValue} from '../common/arg_types';
-import {ChromeSliceSelection} from '../common/state';
+import {ChromeSliceSelection, getLegacySelection} from '../common/state';
import {
CounterDetails,
globals,
@@ -68,7 +68,7 @@
}
run() {
- const selection = globals.state.currentSelection;
+ const selection = getLegacySelection(globals.state);
if (!selection || selection.kind === 'AREA') return;
const selectWithId = [
@@ -281,7 +281,7 @@
}
// Check selection is still the same on completion of query.
- if (selection === globals.state.currentSelection) {
+ if (selection === getLegacySelection(globals.state)) {
publishSliceDetails(selected);
}
}
@@ -352,7 +352,7 @@
`;
const result = await this.args.engine.query(query);
- const selection = globals.state.currentSelection;
+ const selection = getLegacySelection(globals.state);
if (result.numRows() > 0 && selection) {
const row = result.firstRow({
ts: LONG,
@@ -379,7 +379,7 @@
WHERE sched.id = ${id}`;
const result = await this.args.engine.query(sqlQuery);
// Check selection is still the same on completion of query.
- const selection = globals.state.currentSelection;
+ const selection = getLegacySelection(globals.state);
if (result.numRows() > 0 && selection) {
const row = result.firstRow({
ts: LONG,
diff --git a/ui/src/controller/trace_controller.ts b/ui/src/controller/trace_controller.ts
index 56507d9..5bdc934 100644
--- a/ui/src/controller/trace_controller.ts
+++ b/ui/src/controller/trace_controller.ts
@@ -32,6 +32,7 @@
EngineMode,
PendingDeeplinkState,
ProfileType,
+ getLegacySelection,
} from '../common/state';
import {featureFlags, Flag, PERF_SAMPLE_FLAG} from '../core/feature_flags';
import {BottomTabList} from '../frontend/bottom_tab';
@@ -675,8 +676,9 @@
// If the trace was shared via a permalink, it might already have a
// selection. Emit onSelectionChanged to ensure that the components (like
// current selection details) react to it.
- if (globals.state.currentSelection !== null) {
- onSelectionChanged(globals.state.currentSelection, true);
+ const currentSelection = getLegacySelection(globals.state);
+ if (currentSelection !== null) {
+ onSelectionChanged(currentSelection, true);
}
globals.dispatch(Actions.maybeExpandOnlyTrackGroup({}));
diff --git a/ui/src/core/selection_manager.ts b/ui/src/core/selection_manager.ts
new file mode 100644
index 0000000..0a5b9be
--- /dev/null
+++ b/ui/src/core/selection_manager.ts
@@ -0,0 +1,13 @@
+// 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.
diff --git a/ui/src/frontend/aggregation_panel.ts b/ui/src/frontend/aggregation_panel.ts
index cbd97d9..6f7f2ce 100644
--- a/ui/src/frontend/aggregation_panel.ts
+++ b/ui/src/frontend/aggregation_panel.ts
@@ -15,6 +15,7 @@
import m from 'mithril';
import {Actions} from '../common/actions';
+import {getLegacySelection} from '../common/state';
import {
AggregateData,
Column,
@@ -39,7 +40,7 @@
implements m.ClassComponent<AggregationPanelAttrs>
{
view({attrs}: m.CVnode<AggregationPanelAttrs>) {
- if (!globals.state.currentSelection) {
+ if (!getLegacySelection(globals.state)) {
return m(
EmptyState,
{
@@ -156,7 +157,7 @@
}
showTimeRange() {
- const selection = globals.state.currentSelection;
+ const selection = getLegacySelection(globals.state);
if (selection === null || selection.kind !== 'AREA') return undefined;
const selectedArea = globals.state.areas[selection.areaId];
const duration = selectedArea.end - selectedArea.start;
diff --git a/ui/src/frontend/app.ts b/ui/src/frontend/app.ts
index 0c63e9e..bad01f0 100644
--- a/ui/src/frontend/app.ts
+++ b/ui/src/frontend/app.ts
@@ -22,6 +22,7 @@
import {undoCommonChatAppReplacements} from '../base/string_utils';
import {duration, Span, Time, time, TimeSpan} from '../base/time';
import {Actions} from '../common/actions';
+import {getLegacySelection} from '../common/state';
import {runQuery} from '../common/queries';
import {
DurationPrecision,
@@ -237,7 +238,7 @@
}
private getFirstUtidOfSelectionOrVisibleWindow(): number {
- const selection = globals.state.currentSelection;
+ const selection = getLegacySelection(globals.state);
if (selection && selection.kind === 'AREA') {
const selectedArea = globals.state.areas[selection.areaId];
const firstThreadStateTrack = selectedArea.tracks.find((trackId) => {
@@ -562,7 +563,7 @@
id: 'perfetto.MarkArea',
name: 'Mark area',
callback: () => {
- const selection = globals.state.currentSelection;
+ const selection = getLegacySelection(globals.state);
if (selection && selection.kind === 'AREA') {
globals.dispatch(Actions.toggleMarkCurrentArea({persistent: false}));
} else if (selection) {
@@ -575,7 +576,7 @@
id: 'perfetto.MarkAreaPersistent',
name: 'Mark area (persistent)',
callback: () => {
- const selection = globals.state.currentSelection;
+ const selection = getLegacySelection(globals.state);
if (selection && selection.kind === 'AREA') {
globals.dispatch(Actions.toggleMarkCurrentArea({persistent: true}));
} else if (selection) {
@@ -614,7 +615,7 @@
callback: () => {
let tracksToSelect: string[] = [];
- const selection = globals.state.currentSelection;
+ const selection = getLegacySelection(globals.state);
if (selection !== null && selection.kind === 'AREA') {
const area = globals.state.areas[selection.areaId];
const coversEntireTimeRange =
diff --git a/ui/src/frontend/base_slice_track.ts b/ui/src/frontend/base_slice_track.ts
index e4203f0..ef3b60b 100644
--- a/ui/src/frontend/base_slice_track.ts
+++ b/ui/src/frontend/base_slice_track.ts
@@ -25,7 +25,11 @@
} from '../common/canvas_utils';
import {colorCompare} from '../core/color';
import {UNEXPECTED_PINK} from '../core/colorizer';
-import {Selection, SelectionKind} from '../common/state';
+import {
+ LegacySelection,
+ SelectionKind,
+ getLegacySelection,
+} from '../common/state';
import {featureFlags} from '../core/feature_flags';
import {raf} from '../core/raf_scheduler';
import {EngineProxy, Slice, SliceRect, Track} from '../public';
@@ -300,7 +304,7 @@
}
}
- protected isSelectionHandled(selection: Selection): boolean {
+ protected isSelectionHandled(selection: LegacySelection): boolean {
// TODO(hjd): Remove when updating selection.
// We shouldn't know here about CHROME_SLICE. Maybe should be set by
// whatever deals with that. Dunno the namespace of selection is weird. For
@@ -360,7 +364,7 @@
vizTime.end.toTime('ceil'),
);
- let selection = globals.state.currentSelection;
+ let selection = getLegacySelection(globals.state);
if (!selection || !this.isSelectionHandled(selection)) {
selection = null;
}
diff --git a/ui/src/frontend/details_panel.ts b/ui/src/frontend/details_panel.ts
index a93418d..8ca09aa 100644
--- a/ui/src/frontend/details_panel.ts
+++ b/ui/src/frontend/details_panel.ts
@@ -20,7 +20,7 @@
import {isEmptyData} from '../common/aggregation_data';
import {LogExists, LogExistsKey} from '../common/logs';
import {addSelectionChangeObserver} from '../common/selection_observer';
-import {Selection} from '../common/state';
+import {LegacySelection, getLegacySelection} from '../common/state';
import {AggregationPanel} from './aggregation_panel';
import {ChromeSliceDetailsTab} from './chrome_slice_details_tab';
@@ -50,7 +50,7 @@
}
function handleSelectionChange(
- newSelection: Selection | undefined,
+ newSelection: LegacySelection | undefined,
openCurrentSelectionTab: boolean,
): void {
const currentSelectionTag = CURRENT_SELECTION_TAG;
@@ -140,7 +140,7 @@
}
}
- const curSelection = globals.state.currentSelection;
+ const curSelection = getLegacySelection(globals.state);
if (curSelection) {
switch (curSelection.kind) {
case 'NOTE':
diff --git a/ui/src/frontend/drag_handle.ts b/ui/src/frontend/drag_handle.ts
index cfe7ceb..70aea03 100644
--- a/ui/src/frontend/drag_handle.ts
+++ b/ui/src/frontend/drag_handle.ts
@@ -21,6 +21,7 @@
import {DEFAULT_DETAILS_CONTENT_HEIGHT} from './css_constants';
import {DragGestureHandler} from './drag_gesture_handler';
+import {globals} from './globals';
const DRAG_HANDLE_HEIGHT_PX = 28;
const UP_ICON = 'keyboard_arrow_up';
@@ -102,7 +103,7 @@
// We can't get real fullscreen height until the pan_and_zoom_handler
// exists.
private fullscreenHeight = 0;
- private trash: Trash = new Trash();
+ private trash = new Trash();
oncreate({dom, attrs}: m.CVnodeDOM<DragHandleAttrs>) {
this.resize = attrs.resize;
@@ -118,6 +119,31 @@
this.onDragEnd.bind(this),
),
);
+ const cmd = globals.commandManager.registerCommand({
+ id: 'perfetto.ToggleDrawer',
+ name: 'Toggle drawer',
+ defaultHotkey: 'Q',
+ callback: () => {
+ this.toggleVisibility();
+ },
+ });
+ this.trash.add(cmd);
+ }
+
+ private toggleVisibility() {
+ if (this.height === 0) {
+ this.isClosed = false;
+ if (this.previousHeight === 0) {
+ this.previousHeight = getDefaultDetailsHeight();
+ }
+ this.resize(this.previousHeight);
+ } else {
+ this.isFullscreen = false;
+ this.isClosed = true;
+ this.previousHeight = this.height;
+ this.resize(0);
+ }
+ raf.scheduleFullRedraw();
}
onupdate({attrs}: m.CVnodeDOM<DragHandleAttrs>) {
@@ -216,19 +242,7 @@
}),
m(Button, {
onclick: () => {
- if (this.height === 0) {
- this.isClosed = false;
- if (this.previousHeight === 0) {
- this.previousHeight = getDefaultDetailsHeight();
- }
- this.resize(this.previousHeight);
- } else {
- this.isFullscreen = false;
- this.isClosed = true;
- this.previousHeight = this.height;
- this.resize(0);
- }
- raf.scheduleFullRedraw();
+ this.toggleVisibility();
},
title,
icon,
diff --git a/ui/src/frontend/flow_events_panel.ts b/ui/src/frontend/flow_events_panel.ts
index ee34b88..02bf691 100644
--- a/ui/src/frontend/flow_events_panel.ts
+++ b/ui/src/frontend/flow_events_panel.ts
@@ -16,6 +16,7 @@
import {Icons} from '../base/semantic_icons';
import {Actions} from '../common/actions';
+import {getLegacySelection} from '../common/state';
import {raf} from '../core/raf_scheduler';
import {Flow, globals} from './globals';
@@ -39,7 +40,7 @@
export class FlowEventsPanel implements m.ClassComponent {
view() {
- const selection = globals.state.currentSelection;
+ const selection = getLegacySelection(globals.state);
if (!selection) {
return m(
EmptyState,
@@ -145,7 +146,7 @@
export class FlowEventsAreaSelectedPanel implements m.ClassComponent {
view() {
- const selection = globals.state.currentSelection;
+ const selection = getLegacySelection(globals.state);
if (!selection || selection.kind !== 'AREA') {
return;
}
diff --git a/ui/src/frontend/globals.ts b/ui/src/frontend/globals.ts
index 35cd660..4528881 100644
--- a/ui/src/frontend/globals.ts
+++ b/ui/src/frontend/globals.ts
@@ -38,6 +38,7 @@
ProfileType,
RESOLUTION_DEFAULT,
State,
+ getLegacySelection,
} from '../common/state';
import {TabManager} from '../common/tab_registry';
import {TimestampFormat, timestampFormat} from '../core/timestamp_format';
@@ -644,12 +645,14 @@
// HACK(stevegolton + altimin): This is a workaround to allow passing the
// next tab state to the Bottom Tab API
- if (this.state.currentSelection !== previousState.currentSelection) {
+ const currentSelection = getLegacySelection(this.state);
+ const previousSelection = getLegacySelection(previousState);
+ if (currentSelection !== previousSelection) {
// TODO(altimin): Currently we are not triggering this when changing
// the set of selected tracks via toggling per-track checkboxes.
// Fix that.
onSelectionChanged(
- this.state.currentSelection ?? undefined,
+ currentSelection ?? undefined,
switchToCurrentSelectionTab,
);
}
@@ -809,7 +812,7 @@
}
findTimeRangeOfSelection(): {start: time; end: time} {
- const selection = this.state.currentSelection;
+ const selection = getLegacySelection(this.state);
let start = Time.INVALID;
let end = Time.INVALID;
if (selection === null) {
diff --git a/ui/src/frontend/keyboard_event_handler.ts b/ui/src/frontend/keyboard_event_handler.ts
index e73a5d1..60b7c8f 100644
--- a/ui/src/frontend/keyboard_event_handler.ts
+++ b/ui/src/frontend/keyboard_event_handler.ts
@@ -13,7 +13,7 @@
// limitations under the License.
import {Actions} from '../common/actions';
-import {Area} from '../common/state';
+import {Area, getLegacySelection} from '../common/state';
import {Flow, globals} from './globals';
import {focusHorizontalRange, verticalScrollToTrack} from './scroll_helper';
@@ -44,13 +44,11 @@
// Change focus to the next flow event (matching the direction)
export function focusOtherFlow(direction: Direction) {
- if (
- !globals.state.currentSelection ||
- globals.state.currentSelection.kind !== 'CHROME_SLICE'
- ) {
+ const currentSelection = getLegacySelection(globals.state);
+ if (!currentSelection || currentSelection.kind !== 'CHROME_SLICE') {
return;
}
- const sliceId = globals.state.currentSelection.id;
+ const sliceId = currentSelection.id;
if (sliceId === -1) {
return;
}
@@ -78,14 +76,12 @@
// Select the slice connected to the flow in focus
export function moveByFocusedFlow(direction: Direction): void {
- if (
- !globals.state.currentSelection ||
- globals.state.currentSelection.kind !== 'CHROME_SLICE'
- ) {
+ const currentSelection = getLegacySelection(globals.state);
+ if (!currentSelection || currentSelection.kind !== 'CHROME_SLICE') {
return;
}
- const sliceId = globals.state.currentSelection.id;
+ const sliceId = currentSelection.id;
const flowId =
direction === 'Backward'
? globals.state.focusedFlowIdLeft
@@ -117,21 +113,16 @@
export function lockSliceSpan(persistent = false) {
const range = globals.findTimeRangeOfSelection();
- if (
- range.start !== -1n &&
- range.end !== -1n &&
- globals.state.currentSelection !== null
- ) {
- const tracks = globals.state.currentSelection.trackKey
- ? [globals.state.currentSelection.trackKey]
- : [];
+ const currentSelection = getLegacySelection(globals.state);
+ if (range.start !== -1n && range.end !== -1n && currentSelection !== null) {
+ const tracks = currentSelection.trackKey ? [currentSelection.trackKey] : [];
const area: Area = {start: range.start, end: range.end, tracks};
globals.dispatch(Actions.markArea({area, persistent}));
}
}
export function findCurrentSelection() {
- const selection = globals.state.currentSelection;
+ const selection = getLegacySelection(globals.state);
if (selection === null) return;
const range = globals.findTimeRangeOfSelection();
diff --git a/ui/src/frontend/notes_panel.ts b/ui/src/frontend/notes_panel.ts
index dfc7800..23dcbbc 100644
--- a/ui/src/frontend/notes_panel.ts
+++ b/ui/src/frontend/notes_panel.ts
@@ -19,7 +19,7 @@
import {Time} from '../base/time';
import {Actions} from '../common/actions';
import {randomColor} from '../core/colorizer';
-import {AreaNote, Note} from '../common/state';
+import {AreaNote, Note, getLegacySelection} from '../common/state';
import {raf} from '../core/raf_scheduler';
import {Button} from '../widgets/button';
@@ -177,7 +177,7 @@
this.hoveredX !== null && this.mouseOverNote(this.hoveredX, note);
if (currentIsHovered) aNoteIsHovered = true;
- const selection = globals.state.currentSelection;
+ const selection = getLegacySelection(globals.state);
const isSelected =
selection !== null &&
((selection.kind === 'NOTE' && selection.id === note.id) ||
diff --git a/ui/src/frontend/publish.ts b/ui/src/frontend/publish.ts
index ee9f55e..18b5773 100644
--- a/ui/src/frontend/publish.ts
+++ b/ui/src/frontend/publish.ts
@@ -26,6 +26,7 @@
import {CurrentSearchResults, SearchSummary} from '../common/search_data';
import {raf} from '../core/raf_scheduler';
import {HttpRpcState} from '../trace_processor/http_rpc_engine';
+import {getLegacySelection} from '../common/state';
import {
CounterDetails,
@@ -212,8 +213,9 @@
// focus. In all other cases the focusedFlowId(Left|Right) will be set to -1.
globals.dispatch(Actions.setHighlightedFlowLeftId({flowId: -1}));
globals.dispatch(Actions.setHighlightedFlowRightId({flowId: -1}));
- if (globals.state.currentSelection?.kind === 'CHROME_SLICE') {
- const sliceId = globals.state.currentSelection.id;
+ const currentSelection = getLegacySelection(globals.state);
+ if (currentSelection?.kind === 'CHROME_SLICE') {
+ const sliceId = currentSelection.id;
for (const flow of globals.connectedFlows) {
if (flow.begin.sliceId === sliceId) {
globals.dispatch(Actions.setHighlightedFlowRightId({flowId: flow.id}));
diff --git a/ui/src/frontend/slice_track.ts b/ui/src/frontend/slice_track.ts
index 2f9eb40..cde7027 100644
--- a/ui/src/frontend/slice_track.ts
+++ b/ui/src/frontend/slice_track.ts
@@ -20,6 +20,7 @@
import {TrackData} from '../common/track_data';
import {TimelineFetcher} from '../common/track_helper';
import {SliceRect, Track} from '../public';
+import {getLegacySelection} from '../common/state';
import {CROP_INCOMPLETE_SLICE_FLAG} from './base_slice_track';
import {checkerboardExcept} from './checkerboard';
@@ -153,7 +154,7 @@
height: SLICE_HEIGHT,
};
- const currentSelection = globals.state.currentSelection;
+ const currentSelection = getLegacySelection(globals.state);
const isSelected =
currentSelection &&
currentSelection.kind === 'CHROME_SLICE' &&
diff --git a/ui/src/frontend/tab_panel.ts b/ui/src/frontend/tab_panel.ts
index 82414eb..23e8095 100644
--- a/ui/src/frontend/tab_panel.ts
+++ b/ui/src/frontend/tab_panel.ts
@@ -16,6 +16,7 @@
import {Gate} from '../base/mithril_utils';
import {Actions} from '../common/actions';
+import {getLegacySelection} from '../common/state';
import {EmptyState} from '../widgets/empty_state';
import {
@@ -62,7 +63,7 @@
if (
!this.hasBeenDragged &&
- (tabs.length > 0 || globals.state.currentSelection)
+ (tabs.length > 0 || getLegacySelection(globals.state))
) {
this.detailsHeight = getDefaultDetailsHeight();
}
@@ -128,7 +129,7 @@
}
private renderCSTabContent(): {isLoading: boolean; content: m.Children} {
- const cs = globals.state.currentSelection;
+ const cs = getLegacySelection(globals.state);
if (!cs) {
return {
isLoading: false,
diff --git a/ui/src/frontend/time_selection_panel.ts b/ui/src/frontend/time_selection_panel.ts
index 0da9a23..d77b095 100644
--- a/ui/src/frontend/time_selection_panel.ts
+++ b/ui/src/frontend/time_selection_panel.ts
@@ -32,6 +32,7 @@
import {PanelSize} from './panel';
import {Panel} from './panel_container';
import {renderDuration} from './widgets/duration';
+import {getLegacySelection} from '../common/state';
export interface BBox {
x: number;
@@ -172,7 +173,7 @@
}
const localArea = globals.timeline.selectedArea;
- const selection = globals.state.currentSelection;
+ const selection = getLegacySelection(globals.state);
if (localArea !== undefined) {
const start = Time.min(localArea.start, localArea.end);
const end = Time.max(localArea.start, localArea.end);
diff --git a/ui/src/frontend/track_group_panel.ts b/ui/src/frontend/track_group_panel.ts
index 627ac4e..0f0f401 100644
--- a/ui/src/frontend/track_group_panel.ts
+++ b/ui/src/frontend/track_group_panel.ts
@@ -16,7 +16,7 @@
import {Icons} from '../base/semantic_icons';
import {Actions} from '../common/actions';
-import {getContainingTrackId} from '../common/state';
+import {getContainingTrackId, getLegacySelection} from '../common/state';
import {TrackCacheEntry} from '../common/track_cache';
import {TrackTags} from '../public';
@@ -77,7 +77,7 @@
}
}
- const selection = globals.state.currentSelection;
+ const selection = getLegacySelection(globals.state);
const trackGroup = globals.state.trackGroups[trackGroupId];
let checkBox = Icons.BlankCheckbox;
@@ -173,7 +173,7 @@
highlightIfTrackSelected(ctx: CanvasRenderingContext2D, size: PanelSize) {
const {visibleTimeScale} = globals.timeline;
- const selection = globals.state.currentSelection;
+ const selection = getLegacySelection(globals.state);
if (!selection || selection.kind !== 'AREA') return;
const selectedArea = globals.state.areas[selection.areaId];
const selectedAreaDuration = selectedArea.end - selectedArea.start;
diff --git a/ui/src/frontend/track_panel.ts b/ui/src/frontend/track_panel.ts
index d186fdb..2e15d67 100644
--- a/ui/src/frontend/track_panel.ts
+++ b/ui/src/frontend/track_panel.ts
@@ -36,6 +36,7 @@
import {Popup} from '../widgets/popup';
import {canvasClip} from '../common/canvas_utils';
import {TimeScale} from './time_scale';
+import {getLegacySelection} from '../common/state';
function getTitleSize(title: string): string | undefined {
const length = title.length;
@@ -62,7 +63,7 @@
}
function isSelected(id: string) {
- const selection = globals.state.currentSelection;
+ const selection = getLegacySelection(globals.state);
if (selection === null || selection.kind !== 'AREA') return false;
const selectedArea = globals.state.areas[selection.areaId];
return selectedArea.tracks.includes(id);
@@ -143,6 +144,8 @@
}
}
+ const currentSelection = getLegacySelection(globals.state);
+
return m(
`.track-shell[draggable=true]`,
{
@@ -183,8 +186,7 @@
showButton: isPinned(attrs.trackKey),
fullHeight: true,
}),
- globals.state.currentSelection !== null &&
- globals.state.currentSelection.kind === 'AREA'
+ currentSelection !== null && currentSelection.kind === 'AREA'
? m(TrackButton, {
action: (e: MouseEvent) => {
globals.dispatch(
@@ -473,7 +475,7 @@
highlightIfTrackSelected(ctx: CanvasRenderingContext2D, size: PanelSize) {
const {visibleTimeScale} = globals.timeline;
- const selection = globals.state.currentSelection;
+ const selection = getLegacySelection(globals.state);
if (!selection || selection.kind !== 'AREA') {
return;
}
@@ -574,9 +576,10 @@
visibleTimeScale: TimeScale,
size: PanelSize,
) {
- if (globals.state.currentSelection !== null) {
+ const currentSelection = getLegacySelection(globals.state);
+ if (currentSelection !== null) {
if (
- globals.state.currentSelection.kind === 'SLICE' &&
+ currentSelection.kind === 'SLICE' &&
globals.sliceDetails.wakeupTs !== undefined
) {
drawVerticalLineAtTime(
diff --git a/ui/src/frontend/viewer_page.ts b/ui/src/frontend/viewer_page.ts
index 0c8a185..ef2686a 100644
--- a/ui/src/frontend/viewer_page.ts
+++ b/ui/src/frontend/viewer_page.ts
@@ -40,6 +40,7 @@
import {TrackGroupPanel} from './track_group_panel';
import {TrackPanel} from './track_panel';
import {assertExists} from '../base/logging';
+import {getLegacySelection} from '../common/state';
const OVERVIEW_PANEL_FLAG = featureFlags.register({
id: 'overviewVisible',
@@ -51,7 +52,7 @@
// Checks if the mousePos is within 3px of the start or end of the
// current selected time range.
function onTimeRangeBoundary(mousePos: number): 'START' | 'END' | null {
- const selection = globals.state.currentSelection;
+ const selection = getLegacySelection(globals.state);
if (selection !== null && selection.kind === 'AREA') {
// If frontend selectedArea exists then we are in the process of editing the
// time range and need to use that value instead.
@@ -152,7 +153,7 @@
const {visibleTimeScale} = timeline;
this.keepCurrentSelection = true;
if (editing) {
- const selection = globals.state.currentSelection;
+ const selection = getLegacySelection(globals.state);
if (selection !== null && selection.kind === 'AREA') {
const area = globals.timeline.selectedArea
? globals.timeline.selectedArea
@@ -208,7 +209,7 @@
// If we are editing we need to pass the current id through to ensure
// the marked area with that id is also updated.
if (edit) {
- const selection = globals.state.currentSelection;
+ const selection = getLegacySelection(globals.state);
if (selection !== null && selection.kind === 'AREA' && area) {
globals.dispatch(
Actions.editArea({area, areaId: selection.areaId}),
diff --git a/ui/src/plugins/dev.perfetto.AndroidLongBatteryTracing/index.ts b/ui/src/plugins/dev.perfetto.AndroidLongBatteryTracing/index.ts
index 6bc1b49..a1a2842 100644
--- a/ui/src/plugins/dev.perfetto.AndroidLongBatteryTracing/index.ts
+++ b/ui/src/plugins/dev.perfetto.AndroidLongBatteryTracing/index.ts
@@ -1047,16 +1047,13 @@
from step3 left join app_package_list pl using(uid)
`;
+// See go/bt_system_context_report for reference on the bit-twiddling.
const BT_ACTIVITY = `
+ create perfetto table bt_activity as
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.bluetooth_stack_state') 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
@@ -1076,11 +1073,27 @@
)
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
+ dur,
+ bluetooth_stack_state & 0x0000000F as acl_active_count,
+ bluetooth_stack_state & 0x000000F0 >> 4 as acl_sniff_count,
+ bluetooth_stack_state & 0x00000F00 >> 8 as acl_ble_count,
+ bluetooth_stack_state & 0x0000F000 >> 12 as advertising_count,
+ case bluetooth_stack_state & 0x000F0000 >> 16
+ when 0 then 'disabled'
+ when 1 then '<5%'
+ when 2 then '5% to 10%'
+ when 3 then '10% to 25%'
+ when 4 then '25% to 100%'
+ else 'invalid'
+ end as le_scan_duty_cycle,
+ bluetooth_stack_state & 0x00100000 >> 20 as inquiry_active,
+ bluetooth_stack_state & 0x00200000 >> 21 as sco_active,
+ bluetooth_stack_state & 0x00400000 >> 22 as a2dp_active,
+ bluetooth_stack_state & 0x00800000 >> 23 as le_audio_active,
+ max(0, 100.0 * controller_idle_dur / dur) as controller_idle_pct,
+ max(0, 100.0 * controller_tx_dur / dur) as controller_tx_pct,
+ max(0, 100.0 * controller_rx_dur / dur) as controller_rx_pct
from step2
- where controller_rx_dur > 0
`;
class AndroidLongBatteryTracing implements Plugin {
@@ -1564,7 +1577,82 @@
BT_BYTES,
groupName,
);
- this.addSliceTrack(ctx, 'Activity info', BT_ACTIVITY, groupName);
+ await ctx.engine.query(BT_ACTIVITY);
+ this.addCounterTrack(
+ ctx,
+ 'ACL Classic Active Count',
+ 'select ts, dur, acl_active_count as value from bt_activity',
+ groupName,
+ );
+ this.addCounterTrack(
+ ctx,
+ 'ACL Classic Sniff Count',
+ 'select ts, dur, acl_sniff_count as value from bt_activity',
+ groupName,
+ );
+ this.addCounterTrack(
+ ctx,
+ 'ACL BLE Count',
+ 'select ts, dur, acl_ble_count as value from bt_activity',
+ groupName,
+ );
+ this.addCounterTrack(
+ ctx,
+ 'Advertising Instance Count',
+ 'select ts, dur, advertising_count as value from bt_activity',
+ groupName,
+ );
+ this.addSliceTrack(
+ ctx,
+ 'LE Scan Duty Cycle',
+ 'select ts, dur, le_scan_duty_cycle as name from bt_activity',
+ groupName,
+ );
+ this.addSliceTrack(
+ ctx,
+ 'Inquiry Active',
+ "select ts, dur, 'Active' as name from bt_activity where inquiry_active",
+ groupName,
+ );
+ this.addSliceTrack(
+ ctx,
+ 'SCO Active',
+ "select ts, dur, 'Active' as name from bt_activity where sco_active",
+ groupName,
+ );
+ this.addSliceTrack(
+ ctx,
+ 'A2DP Active',
+ "select ts, dur, 'Active' as name from bt_activity where a2dp_active",
+ groupName,
+ );
+ this.addSliceTrack(
+ ctx,
+ 'LE Audio Active',
+ "select ts, dur, 'Active' as name from bt_activity where le_audio_active",
+ groupName,
+ );
+ this.addCounterTrack(
+ ctx,
+ 'Controller Idle Time',
+ 'select ts, dur, controller_idle_pct as value from bt_activity',
+ groupName,
+ {yRangeSharingKey: 'bt_controller_time', unit: '%'},
+ );
+ this.addCounterTrack(
+ ctx,
+ 'Controller TX Time',
+ 'select ts, dur, controller_tx_pct as value from bt_activity',
+ groupName,
+ {yRangeSharingKey: 'bt_controller_time', unit: '%'},
+ );
+ this.addCounterTrack(
+ ctx,
+ 'Controller RX Time',
+ 'select ts, dur, controller_rx_pct as value from bt_activity',
+ groupName,
+ {yRangeSharingKey: 'bt_controller_time', unit: '%'},
+ );
this.addSliceTrack(
ctx,
'Quality reports',
diff --git a/ui/src/public/index.ts b/ui/src/public/index.ts
index 3ba4c6a..19c1867 100644
--- a/ui/src/public/index.ts
+++ b/ui/src/public/index.ts
@@ -18,9 +18,10 @@
import {duration, time} from '../base/time';
import {Migrate, Store} from '../base/store';
import {ColorScheme} from '../core/colorizer';
-import {Selection} from '../common/state';
+import {LegacySelection} from '../common/state';
import {PanelSize} from '../frontend/panel';
import {EngineProxy} from '../trace_processor/engine';
+import {UntypedEventSet} from '../common/event_set';
export {EngineProxy} from '../trace_processor/engine';
export {
@@ -208,6 +209,11 @@
onMouseMove?(position: {x: number; y: number}): void;
onMouseClick?(position: {x: number; y: number}): boolean;
onMouseOut?(): void;
+
+ /**
+ * Optional: Get the event set that represents this track's data.
+ */
+ getEventSet?(): UntypedEventSet;
}
// A definition of a track, including a renderer implementation and metadata.
@@ -325,7 +331,7 @@
}
export interface DetailsPanel {
- render(selection: Selection): m.Children;
+ render(selection: LegacySelection): m.Children;
isLoading?(): boolean;
}
diff --git a/ui/src/public/utils.ts b/ui/src/public/utils.ts
index 55e6bce..60c4487 100644
--- a/ui/src/public/utils.ts
+++ b/ui/src/public/utils.ts
@@ -14,7 +14,7 @@
import m from 'mithril';
-import {Selection} from '../common/state';
+import {LegacySelection} from '../common/state';
import {BottomTab} from '../frontend/bottom_tab';
import {DetailsPanel, Tab} from '.';
@@ -97,7 +97,7 @@
}
export interface BottomTabAdapterAttrs {
- tabFactory: (sel: Selection) => BottomTab | undefined;
+ tabFactory: (sel: LegacySelection) => BottomTab | undefined;
}
/**
@@ -129,7 +129,7 @@
})
*/
export class BottomTabToSCSAdapter implements DetailsPanel {
- private oldSelection?: Selection;
+ private oldSelection?: LegacySelection;
private bottomTab?: BottomTab;
private attrs: BottomTabAdapterAttrs;
@@ -137,7 +137,7 @@
this.attrs = attrs;
}
- render(selection: Selection): m.Children {
+ render(selection: LegacySelection): m.Children {
// Detect selection changes, assuming selection is immutable
if (selection !== this.oldSelection) {
this.oldSelection = selection;
diff --git a/ui/src/tracks/chrome_scroll_jank/event_latency_track.ts b/ui/src/tracks/chrome_scroll_jank/event_latency_track.ts
index dac1878..85a2a70 100644
--- a/ui/src/tracks/chrome_scroll_jank/event_latency_track.ts
+++ b/ui/src/tracks/chrome_scroll_jank/event_latency_track.ts
@@ -29,6 +29,7 @@
ScrollJankTracks as DecideTracksResult,
} from './index';
import {JANK_COLOR} from './jank_colors';
+import {getLegacySelection} from '../../common/state';
export const JANKY_LATENCY_NAME = 'Janky EventLatency';
@@ -85,7 +86,7 @@
onUpdatedSlices(slices: EventLatencyTrackTypes['slice'][]) {
for (const slice of slices) {
- const currentSelection = globals.state.currentSelection;
+ const currentSelection = getLegacySelection(globals.state);
const isSelected =
currentSelection &&
currentSelection.kind === 'GENERIC_SLICE' &&
diff --git a/ui/src/tracks/chrome_scroll_jank/scroll_jank_v3_track.ts b/ui/src/tracks/chrome_scroll_jank/scroll_jank_v3_track.ts
index fb77174..ba44b9b 100644
--- a/ui/src/tracks/chrome_scroll_jank/scroll_jank_v3_track.ts
+++ b/ui/src/tracks/chrome_scroll_jank/scroll_jank_v3_track.ts
@@ -31,6 +31,7 @@
import {JANK_COLOR} from './jank_colors';
import {ScrollJankV3DetailsPanel} from './scroll_jank_v3_details_panel';
import {getColorForSlice} from '../../core/colorizer';
+import {getLegacySelection} from '../../common/state';
const UNKNOWN_SLICE_NAME = 'Unknown';
const JANK_SLICE_NAME = ' Jank';
@@ -100,7 +101,7 @@
onUpdatedSlices(slices: EventLatencyTrackTypes['slice'][]) {
for (const slice of slices) {
- const currentSelection = globals.state.currentSelection;
+ const currentSelection = getLegacySelection(globals.state);
const isSelected =
currentSelection &&
currentSelection.kind === 'GENERIC_SLICE' &&
diff --git a/ui/src/tracks/cpu_profile/index.ts b/ui/src/tracks/cpu_profile/index.ts
index fb7c7c5..c3a2b57 100644
--- a/ui/src/tracks/cpu_profile/index.ts
+++ b/ui/src/tracks/cpu_profile/index.ts
@@ -14,6 +14,7 @@
import {searchSegment} from '../../base/binary_search';
import {duration, Time, time} from '../../base/time';
+import {getLegacySelection} from '../../common/state';
import {Actions} from '../../common/actions';
import {colorForSample} from '../../core/colorizer';
import {TrackData} from '../../common/track_data';
@@ -117,7 +118,7 @@
for (let i = 0; i < data.tsStarts.length; i++) {
const centerX = Time.fromRaw(data.tsStarts[i]);
- const selection = globals.state.currentSelection;
+ const selection = getLegacySelection(globals.state);
const isHovered = this.hoveredTs === centerX;
const isSelected =
selection !== null &&
diff --git a/ui/src/tracks/cpu_slices/index.ts b/ui/src/tracks/cpu_slices/index.ts
index 647baed..b4c5fe0 100644
--- a/ui/src/tracks/cpu_slices/index.ts
+++ b/ui/src/tracks/cpu_slices/index.ts
@@ -20,6 +20,7 @@
import {Duration, duration, Time, time} from '../../base/time';
import {Actions} from '../../common/actions';
import {calcCachedBucketSize} from '../../common/cache_utils';
+import {getLegacySelection} from '../../common/state';
import {
cropText,
drawDoubleHeadedArrow,
@@ -392,7 +393,7 @@
ctx.fillText(subTitle, rectXCenter, MARGIN_TOP + RECT_HEIGHT / 2 + 9);
}
- const selection = globals.state.currentSelection;
+ const selection = getLegacySelection(globals.state);
const details = globals.sliceDetails;
if (selection !== null && selection.kind === 'SLICE') {
const [startIndex, endIndex] = searchEq(data.ids, selection.id);
diff --git a/ui/src/tracks/custom_sql_table_slices/index.ts b/ui/src/tracks/custom_sql_table_slices/index.ts
index 88795af..2222f9b 100644
--- a/ui/src/tracks/custom_sql_table_slices/index.ts
+++ b/ui/src/tracks/custom_sql_table_slices/index.ts
@@ -17,7 +17,7 @@
import {Disposable, DisposableCallback} from '../../base/disposable';
import {Actions} from '../../common/actions';
import {generateSqlWithInternalLayout} from '../../common/internal_layout_utils';
-import {Selection} from '../../common/state';
+import {LegacySelection} from '../../common/state';
import {OnSliceClickArgs} from '../../frontend/base_slice_track';
import {GenericSliceDetailsTabConfigBase} from '../../frontend/generic_slice_details_tab';
import {globals} from '../../frontend/globals';
@@ -105,7 +105,7 @@
return `SELECT * FROM ${this.tableName}`;
}
- isSelectionHandled(selection: Selection) {
+ isSelectionHandled(selection: LegacySelection) {
if (selection.kind !== 'GENERIC_SLICE') {
return false;
}
diff --git a/ui/src/tracks/heap_profile/index.ts b/ui/src/tracks/heap_profile/index.ts
index d734327..b905ef7 100644
--- a/ui/src/tracks/heap_profile/index.ts
+++ b/ui/src/tracks/heap_profile/index.ts
@@ -13,7 +13,7 @@
// limitations under the License.
import {Actions} from '../../common/actions';
-import {ProfileType, Selection} from '../../common/state';
+import {ProfileType, LegacySelection} from '../../common/state';
import {profileType} from '../../controller/flamegraph_controller';
import {
BASE_ROW,
@@ -109,7 +109,7 @@
);
}
- protected isSelectionHandled(selection: Selection): boolean {
+ protected isSelectionHandled(selection: LegacySelection): boolean {
return selection.kind === 'HEAP_PROFILE';
}
}
diff --git a/ui/src/tracks/perf_samples_profile/index.ts b/ui/src/tracks/perf_samples_profile/index.ts
index 37422d4..6720142 100644
--- a/ui/src/tracks/perf_samples_profile/index.ts
+++ b/ui/src/tracks/perf_samples_profile/index.ts
@@ -15,7 +15,7 @@
import {searchSegment} from '../../base/binary_search';
import {duration, Time, time} from '../../base/time';
import {Actions} from '../../common/actions';
-import {ProfileType} from '../../common/state';
+import {ProfileType, getLegacySelection} from '../../common/state';
import {TrackData} from '../../common/track_data';
import {TimelineFetcher} from '../../common/track_helper';
import {FLAMEGRAPH_HOVERED_COLOR} from '../../frontend/flamegraph';
@@ -114,7 +114,7 @@
for (let i = 0; i < data.tsStarts.length; i++) {
const centerX = Time.fromRaw(data.tsStarts[i]);
- const selection = globals.state.currentSelection;
+ const selection = getLegacySelection(globals.state);
const isHovered = this.hoveredTs === centerX;
const isSelected =
selection !== null &&
diff --git a/ui/src/tracks/thread_state/thread_state_v2.ts b/ui/src/tracks/thread_state/thread_state_v2.ts
index 631160d..4ab000f 100644
--- a/ui/src/tracks/thread_state/thread_state_v2.ts
+++ b/ui/src/tracks/thread_state/thread_state_v2.ts
@@ -14,7 +14,7 @@
import {Actions} from '../../common/actions';
import {colorForState} from '../../core/colorizer';
-import {Selection} from '../../common/state';
+import {LegacySelection} from '../../common/state';
import {translateState} from '../../common/thread_state';
import {
BASE_ROW,
@@ -102,7 +102,7 @@
);
}
- protected isSelectionHandled(selection: Selection): boolean {
+ protected isSelectionHandled(selection: LegacySelection): boolean {
return selection.kind === 'THREAD_STATE';
}
}