aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSteve Golton <stevegolton@google.com>2024-05-01 18:27:21 +0100
committerSteve Golton <stevegolton@google.com>2024-05-01 18:27:21 +0100
commit6ebf16051501bd2fe77fe2e7705a7956077c638a (patch)
treeab871a4821b12af61cfc8d23f2d46d72d4987c9f
parenta9b411b46dcbb0c08772b06f72eec7b8390210a6 (diff)
downloadperfetto-ui-canary.tar.gz
ui: Release canaryui-canary
Commands: $ git fetch origin $ git checkout -B ui-canary -t origin/ui-canary $ git merge --strategy=ours origin/main $ git diff --binary origin/main | git apply --reverse --index $ git commit --amend Check end state is correct: $ git diff ui-canary origin/main [no output] $ git rev-list --count ui-canary..origin/main 0 Change-Id: Id90d7499e500b964afe18dfcde66b3cb0fa51b4a
-rw-r--r--Android.bp239
-rw-r--r--Android.bp.extras3
-rw-r--r--BUILD20
-rw-r--r--CHANGELOG13
-rw-r--r--OWNERS1
-rw-r--r--bazel/proto_gen.bzl9
-rw-r--r--docs/contributing/build-instructions.md1
-rw-r--r--docs/contributing/ui-plugins.md7
-rw-r--r--gn/proto_library.gni2
-rw-r--r--gn/standalone/proto_library.gni4
-rw-r--r--infra/OWNERS1
-rw-r--r--protos/perfetto/ipc/OWNERS1
-rw-r--r--protos/perfetto/metrics/android/BUILD.gn1
-rw-r--r--protos/perfetto/metrics/android/android_boot.proto31
-rw-r--r--protos/perfetto/metrics/android/android_oom_adjuster_metric.proto53
-rw-r--r--protos/perfetto/metrics/metrics.proto5
-rw-r--r--protos/perfetto/metrics/perfetto_merged_metrics.proto72
-rw-r--r--protos/third_party/chromium/chrome_track_event.proto8
-rw-r--r--python/generators/trace_processor_table/serialize.py10
-rw-r--r--python/perfetto/prebuilts/perfetto_prebuilts.py55
-rw-r--r--python/perfetto/trace_processor/metrics.descriptor67
-rw-r--r--src/protozero/protoc_plugin/protozero_c_plugin.cc6
-rw-r--r--src/protozero/protoc_plugin/protozero_plugin.cc46
-rw-r--r--src/trace_processor/containers/BUILD.gn2
-rw-r--r--src/trace_processor/containers/implicit_segment_forest.h139
-rw-r--r--src/trace_processor/containers/implicit_segment_forest_unittest.cc76
-rw-r--r--src/trace_processor/db/column/BUILD.gn1
-rw-r--r--src/trace_processor/db/column/dense_null_overlay.cc6
-rw-r--r--src/trace_processor/db/column/fake_storage.cc61
-rw-r--r--src/trace_processor/db/column/fake_storage_unittest.cc210
-rw-r--r--src/trace_processor/db/column/null_overlay.cc6
-rw-r--r--src/trace_processor/db/column/numeric_storage.h16
-rw-r--r--src/trace_processor/db/column/numeric_storage_unittest.cc5
-rw-r--r--src/trace_processor/db/column/range_overlay_unittest.cc7
-rw-r--r--src/trace_processor/db/column/set_id_storage.cc10
-rw-r--r--src/trace_processor/db/column/types.h9
-rw-r--r--src/trace_processor/db/column/utils.h58
-rw-r--r--src/trace_processor/db/query_executor_benchmark.cc42
-rw-r--r--src/trace_processor/db/table.cc9
-rw-r--r--src/trace_processor/db/table.h3
-rw-r--r--src/trace_processor/importers/ftrace/ftrace_descriptors.cc48
-rw-r--r--src/trace_processor/importers/proto/default_modules.cc4
-rw-r--r--src/trace_processor/importers/proto/heap_graph_tracker.cc33
-rw-r--r--src/trace_processor/importers/proto/metadata_minimal_module.cc5
-rw-r--r--src/trace_processor/importers/proto/metadata_module.cc6
-rw-r--r--src/trace_processor/importers/proto/network_trace_module_unittest.cc4
-rw-r--r--src/trace_processor/importers/proto/proto_trace_parser_unittest.cc8
-rw-r--r--src/trace_processor/importers/proto/winscope/BUILD.gn2
-rw-r--r--src/trace_processor/importers/proto/winscope/protolog_messages_tracker.cc25
-rw-r--r--src/trace_processor/importers/proto/winscope/protolog_messages_tracker.h24
-rw-r--r--src/trace_processor/importers/proto/winscope/protolog_parser.cc98
-rw-r--r--src/trace_processor/importers/proto/winscope/protolog_parser.h18
-rw-r--r--src/trace_processor/metrics/sql/android/BUILD.gn1
-rw-r--r--src/trace_processor/metrics/sql/android/android_boot.sql264
-rw-r--r--src/trace_processor/metrics/sql/android/android_oom_adjuster.sql139
-rw-r--r--src/trace_processor/metrics/sql/chrome/chrome_scroll_inputs_per_frame.sql33
-rw-r--r--src/trace_processor/perfetto_sql/intrinsics/functions/layout_functions.cc7
-rw-r--r--src/trace_processor/perfetto_sql/intrinsics/functions/structural_tree_partition.cc1
-rw-r--r--src/trace_processor/perfetto_sql/intrinsics/functions/to_ftrace.cc4
-rw-r--r--src/trace_processor/perfetto_sql/intrinsics/operators/BUILD.gn5
-rw-r--r--src/trace_processor/perfetto_sql/intrinsics/operators/counter_mipmap_operator.cc265
-rw-r--r--src/trace_processor/perfetto_sql/intrinsics/operators/counter_mipmap_operator.h127
-rw-r--r--src/trace_processor/perfetto_sql/intrinsics/operators/slice_mipmap_operator.cc273
-rw-r--r--src/trace_processor/perfetto_sql/intrinsics/operators/slice_mipmap_operator.h128
-rw-r--r--src/trace_processor/perfetto_sql/intrinsics/table_functions/ancestor.cc5
-rw-r--r--src/trace_processor/perfetto_sql/intrinsics/table_functions/connected_flow.cc4
-rw-r--r--src/trace_processor/perfetto_sql/intrinsics/table_functions/descendant.cc16
-rw-r--r--src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_flat_slice_unittest.cc10
-rw-r--r--src/trace_processor/perfetto_sql/intrinsics/table_functions/flamegraph_construction_algorithms.cc18
-rw-r--r--src/trace_processor/perfetto_sql/stdlib/BUILD.gn1
-rw-r--r--src/trace_processor/perfetto_sql/stdlib/OWNERS6
-rw-r--r--src/trace_processor/perfetto_sql/stdlib/android/frames/timeline.sql54
-rw-r--r--src/trace_processor/perfetto_sql/stdlib/android/process_metadata.sql4
-rw-r--r--src/trace_processor/perfetto_sql/stdlib/android/startup/BUILD.gn1
-rw-r--r--src/trace_processor/perfetto_sql/stdlib/android/startup/startups_maxsdk28.sql14
-rw-r--r--src/trace_processor/perfetto_sql/stdlib/android/startup/time_to_display.sql164
-rw-r--r--src/trace_processor/perfetto_sql/stdlib/chrome/chrome_scrolls.sql31
-rw-r--r--src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/scroll_jank_v3.sql179
-rw-r--r--src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/scroll_offsets.sql205
-rw-r--r--src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/utils.sql15
-rw-r--r--src/trace_processor/perfetto_sql/stdlib/common/OWNERS1
-rw-r--r--src/trace_processor/perfetto_sql/stdlib/graphs/partition.sql39
-rw-r--r--src/trace_processor/perfetto_sql/stdlib/memory/heap_graph_dominator_tree.sql32
-rw-r--r--src/trace_processor/perfetto_sql/stdlib/viz/summary/BUILD.gn24
-rw-r--r--src/trace_processor/perfetto_sql/stdlib/viz/summary/processes.sql93
-rw-r--r--src/trace_processor/perfetto_sql/stdlib/viz/summary/slices.sql24
-rw-r--r--src/trace_processor/perfetto_sql/stdlib/viz/summary/threads.sql77
-rw-r--r--src/trace_processor/perfetto_sql/stdlib/viz/summary/tracks.sql36
-rw-r--r--src/trace_processor/sqlite/db_sqlite_table.cc50
-rw-r--r--src/trace_processor/sqlite/db_sqlite_table.h4
-rw-r--r--src/trace_processor/storage/trace_storage.h5
-rw-r--r--src/trace_processor/tables/BUILD.gn1
-rw-r--r--src/trace_processor/tables/py_tables_benchmark.cc101
-rw-r--r--src/trace_processor/tables/py_tables_unittest.cc32
-rw-r--r--src/trace_processor/trace_processor_impl.cc8
-rw-r--r--src/trace_processor/types/trace_processor_context.h31
-rw-r--r--src/trace_processor/util/profile_builder.cc6
-rw-r--r--src/trace_redaction/BUILD.gn5
-rw-r--r--src/trace_redaction/collect_frame_cookies.cc212
-rw-r--r--src/trace_redaction/collect_frame_cookies.h60
-rw-r--r--src/trace_redaction/collect_frame_cookies_unittest.cc346
-rw-r--r--src/trace_redaction/filter_packet_using_allowlist.cc24
-rw-r--r--src/trace_redaction/filter_packet_using_allowlist.h4
-rw-r--r--src/trace_redaction/filter_packet_using_allowlist_unittest.cc136
-rw-r--r--src/trace_redaction/frame_cookie.h42
-rw-r--r--src/trace_redaction/main.cc4
-rw-r--r--src/trace_redaction/populate_allow_lists.cc31
-rw-r--r--src/trace_redaction/redact_sched_switch.cc8
-rw-r--r--src/trace_redaction/redact_sched_switch_integrationtest.cc9
-rw-r--r--src/trace_redaction/redact_sched_switch_unittest.cc36
-rw-r--r--src/trace_redaction/scrub_trace_packet.cc32
-rw-r--r--src/trace_redaction/scrub_trace_packet.h10
-rw-r--r--src/trace_redaction/trace_redaction_framework.h31
-rw-r--r--src/traced/probes/ftrace/OWNERS1
-rw-r--r--src/traced/probes/ftrace/event_info.cc48
-rw-r--r--test/cts/OWNERS1
-rw-r--r--test/cts/heapprofd_test_cts.cc4
-rw-r--r--test/data/api31_startup_cold.perfetto-trace.sha2561
-rw-r--r--test/data/api31_startup_warm.perfetto-trace.sha2561
-rw-r--r--test/data/chrome/scroll_offsets_trace_2.pftrace.sha2561
-rw-r--r--test/data/chrome_input_with_frame_view.pftrace.sha2562
-rw-r--r--test/data/ui-screenshots/ui-android_trace_30s_expand_camera.png.sha2562
-rw-r--r--test/data/ui-screenshots/ui-android_trace_30s_load.png.sha2562
-rw-r--r--test/data/ui-screenshots/ui-chrome_missing_track_names_load.png.sha2562
-rw-r--r--test/data/ui-screenshots/ui-chrome_rendering_desktop_expand_browser_proc.png.sha2562
-rw-r--r--test/data/ui-screenshots/ui-chrome_rendering_desktop_load.png.sha2562
-rw-r--r--test/data/ui-screenshots/ui-chrome_rendering_desktop_select_slice_with_flows.png.sha2562
-rw-r--r--test/data/ui-screenshots/ui-modal_dialog_dismiss_1.png.sha2562
-rw-r--r--test/data/ui-screenshots/ui-modal_dialog_dismiss_2.png.sha2562
-rw-r--r--test/data/ui-screenshots/ui-modal_dialog_show_dialog_1.png.sha2562
-rw-r--r--test/data/ui-screenshots/ui-modal_dialog_show_dialog_2.png.sha2562
-rw-r--r--test/data/ui-screenshots/ui-modal_dialog_switch_page_no_dialog.png.sha2562
-rw-r--r--test/data/ui-screenshots/ui-routing_navigate_navigate_back_and_forward.png.sha2562
-rw-r--r--test/data/ui-screenshots/ui-routing_navigate_open_trace_from_url.png.sha2562
-rw-r--r--test/data/ui-screenshots/ui-routing_open_invalid_trace_from_blank_page.png.sha2562
-rw-r--r--test/data/ui-screenshots/ui-routing_open_trace_and_go_back_to_landing_page.png.sha2562
-rw-r--r--test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_access_subpage_then_go_back.png.sha2562
-rw-r--r--test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_open_first_trace_from_url.png.sha2562
-rw-r--r--test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_open_second_trace_from_url.png.sha2562
-rw-r--r--test/data/ui-screenshots/ui-routing_start_from_no_trace_go_back_to_first_trace.png.sha2562
-rw-r--r--test/data/ui-screenshots/ui-routing_start_from_no_trace_go_to_page_with_no_trace.png.sha2562
-rw-r--r--test/data/ui-screenshots/ui-routing_start_from_no_trace_open_invalid_trace.png.sha2562
-rw-r--r--test/data/ui-screenshots/ui-routing_start_from_no_trace_open_second_trace.png.sha2562
-rw-r--r--test/data/ui-screenshots/ui-routing_start_from_no_trace_open_trace_.png.sha2562
-rw-r--r--test/data/ui-screenshots/ui-routing_start_from_no_trace_refresh.png.sha2562
-rw-r--r--test/trace_processor/diff_tests/metrics/android/android_boot.out972
-rw-r--r--test/trace_processor/diff_tests/metrics/android/android_oom_adjuster.out2745
-rw-r--r--test/trace_processor/diff_tests/metrics/android/tests.py6
-rw-r--r--test/trace_processor/diff_tests/metrics/chrome/tests_scroll_jank.py44
-rw-r--r--test/trace_processor/diff_tests/parser/android/protolog_packet_loss.textproto118
-rw-r--r--test/trace_processor/diff_tests/parser/android/tests_protolog.py10
-rw-r--r--test/trace_processor/diff_tests/parser/parsing/chrome_metadata.out4
-rw-r--r--test/trace_processor/diff_tests/parser/parsing/tests.py7
-rw-r--r--test/trace_processor/diff_tests/stdlib/OWNERS2
-rw-r--r--test/trace_processor/diff_tests/stdlib/android/frames_tests.py68
-rw-r--r--test/trace_processor/diff_tests/stdlib/android/startups_tests.py88
-rwxr-xr-xtest/trace_processor/diff_tests/stdlib/chrome/tests_scroll_jank.py60
-rw-r--r--test/trace_processor/diff_tests/stdlib/graphs/partition_tests.py4
-rw-r--r--test/trace_processor/diff_tests/stdlib/memory/heap_graph_dominator_tree_tests.py2
-rw-r--r--test/trace_processor/diff_tests/stdlib/memory/heap_graph_for_dominator_tree.textproto12
-rw-r--r--test/trace_processor/diff_tests/stdlib/slices/tests.py26
-rw-r--r--test/vts/OWNERS1
-rwxr-xr-xtools/cpu_profile55
-rwxr-xr-xtools/gen_android_bp60
-rwxr-xr-xtools/gen_tp_table_headers.py7
-rw-r--r--tools/gn_utils.py3
-rwxr-xr-xtools/heap_profile55
-rwxr-xr-xtools/record_android_trace55
-rwxr-xr-xtools/trace_processor53
-rwxr-xr-xtools/tracebox53
-rwxr-xr-xtools/traceconv53
-rw-r--r--ui/OWNERS1
-rw-r--r--ui/package.json2
-rw-r--r--ui/pnpm-lock.yaml287
-rw-r--r--ui/release/OWNERS1
-rw-r--r--ui/release/channels.json2
-rw-r--r--ui/src/assets/details.scss241
-rw-r--r--ui/src/assets/perfetto.scss1
-rw-r--r--ui/src/assets/widgets/details_shell.scss2
-rw-r--r--ui/src/assets/widgets/tree.scss96
-rw-r--r--ui/src/assets/widgets/virtual_table.scss89
-rw-r--r--ui/src/base/geom.ts30
-rw-r--r--ui/src/base/logging.ts37
-rw-r--r--ui/src/common/actions.ts7
-rw-r--r--ui/src/common/empty_state.ts1
-rw-r--r--ui/src/common/plugins.ts11
-rw-r--r--ui/src/common/recordingV2/recording_config_utils.ts2
-rw-r--r--ui/src/common/search_data.ts8
-rw-r--r--ui/src/common/state.ts3
-rw-r--r--ui/src/common/thread_state.ts13
-rw-r--r--ui/src/controller/aggregation/counter_aggregation_controller.ts93
-rw-r--r--ui/src/controller/flamegraph_controller.ts58
-rw-r--r--ui/src/controller/pivot_table_controller.ts23
-rw-r--r--ui/src/controller/search_controller.ts143
-rw-r--r--ui/src/controller/trace_controller.ts27
-rw-r--r--ui/src/controller/track_decider.ts548
-rw-r--r--ui/src/core/colorizer.ts2
-rw-r--r--ui/src/core/default_plugins.ts2
-rw-r--r--ui/src/core/timeline_cache.ts68
-rw-r--r--ui/src/core/timeline_cache_unittest.ts61
-rw-r--r--ui/src/frontend/app.ts8
-rw-r--r--ui/src/frontend/base_counter_track.ts221
-rw-r--r--ui/src/frontend/base_slice_track.ts242
-rw-r--r--ui/src/frontend/base_slice_track_unittest.ts16
-rw-r--r--ui/src/frontend/css_constants.ts1
-rw-r--r--ui/src/frontend/error_dialog.ts2
-rw-r--r--ui/src/frontend/globals.ts8
-rw-r--r--ui/src/frontend/pivot_table_argument_popup.ts52
-rw-r--r--ui/src/frontend/search_handler.ts111
-rw-r--r--ui/src/frontend/simple_counter_track.ts5
-rw-r--r--ui/src/frontend/simple_slice_track.ts4
-rw-r--r--ui/src/frontend/tables/attribute_modal_holder.ts3
-rw-r--r--ui/src/frontend/tickmark_panel.ts28
-rw-r--r--ui/src/frontend/track_panel.ts1
-rw-r--r--ui/src/frontend/widgets_page.ts52
-rw-r--r--ui/src/plugins/dev.perfetto.AndroidCujs/index.ts74
-rw-r--r--ui/src/plugins/dev.perfetto.AndroidNetwork/index.ts4
-rw-r--r--ui/src/plugins/dev.perfetto.CoreCommands/index.ts46
-rw-r--r--ui/src/plugins/dev.perfetto.RestorePinnedTracks/OWNERS2
-rw-r--r--ui/src/plugins/dev.perfetto.RestorePinnedTracks/index.ts135
-rw-r--r--ui/src/plugins/org.kernel.LinuxKernelDevices/index.ts20
-rw-r--r--ui/src/public/index.ts12
-rw-r--r--ui/src/public/utils.ts2
-rw-r--r--ui/src/tracks/android_log/index.ts2
-rw-r--r--ui/src/tracks/android_log/logs_panel.ts226
-rw-r--r--ui/src/tracks/async_slices/index.ts123
-rw-r--r--ui/src/tracks/chrome_critical_user_interactions/index.ts2
-rw-r--r--ui/src/tracks/chrome_scroll_jank/event_latency_track.ts4
-rw-r--r--ui/src/tracks/chrome_scroll_jank/scroll_delta_graph.ts39
-rw-r--r--ui/src/tracks/chrome_scroll_jank/scroll_jank_v3_track.ts4
-rw-r--r--ui/src/tracks/chrome_scroll_jank/scroll_track.ts4
-rw-r--r--ui/src/tracks/counter/index.ts32
-rw-r--r--ui/src/tracks/cpu_freq/index.ts416
-rw-r--r--ui/src/tracks/cpu_slices/index.ts131
-rw-r--r--ui/src/tracks/debug/slice_track.ts4
-rw-r--r--ui/src/tracks/frames/index.ts65
-rw-r--r--ui/src/tracks/ftrace/ftrace_explorer.ts174
-rw-r--r--ui/src/tracks/ftrace/ftrace_track.ts3
-rw-r--r--ui/src/tracks/ftrace/index.ts2
-rw-r--r--ui/src/tracks/process_summary/index.ts293
-rw-r--r--ui/src/tracks/process_summary/process_scheduling_track.ts155
-rw-r--r--ui/src/tracks/process_summary/process_summary_track.ts8
-rw-r--r--ui/src/tracks/sched/index.ts6
-rw-r--r--ui/src/tracks/thread_state/index.ts4
-rw-r--r--ui/src/tracks/thread_state/thread_state_v2.ts9
-rw-r--r--ui/src/widgets/details_shell.ts3
-rw-r--r--ui/src/widgets/tree.ts37
-rw-r--r--ui/src/widgets/virtual_scroll_container.ts49
-rw-r--r--ui/src/widgets/virtual_scroll_helper.ts150
-rw-r--r--ui/src/widgets/virtual_table.ts262
250 files changed, 10860 insertions, 4043 deletions
diff --git a/Android.bp b/Android.bp
index f8e8b65d9..94b84ff36 100644
--- a/Android.bp
+++ b/Android.bp
@@ -5168,6 +5168,7 @@ genrule {
"protos/perfetto/metrics/android/android_boot_unagg.proto",
"protos/perfetto/metrics/android/android_frame_timeline_metric.proto",
"protos/perfetto/metrics/android/android_garbage_collection_unagg_metric.proto",
+ "protos/perfetto/metrics/android/android_oom_adjuster_metric.proto",
"protos/perfetto/metrics/android/android_sysui_notifications_blocking_calls_metric.proto",
"protos/perfetto/metrics/android/android_trusty_workqueues.proto",
"protos/perfetto/metrics/android/anr_metric.proto",
@@ -5257,6 +5258,7 @@ genrule {
"protos/perfetto/metrics/android/android_boot_unagg.proto",
"protos/perfetto/metrics/android/android_frame_timeline_metric.proto",
"protos/perfetto/metrics/android/android_garbage_collection_unagg_metric.proto",
+ "protos/perfetto/metrics/android/android_oom_adjuster_metric.proto",
"protos/perfetto/metrics/android/android_sysui_notifications_blocking_calls_metric.proto",
"protos/perfetto/metrics/android/android_trusty_workqueues.proto",
"protos/perfetto/metrics/android/anr_metric.proto",
@@ -5321,6 +5323,7 @@ genrule {
genrule {
name: "perfetto_protos_perfetto_metrics_webview_descriptor",
srcs: [
+ ":libprotobuf-internal-descriptor-proto",
"protos/perfetto/metrics/android/ad_services_metric.proto",
"protos/perfetto/metrics/android/android_blocking_call.proto",
"protos/perfetto/metrics/android/android_blocking_calls_cuj_metric.proto",
@@ -5329,6 +5332,7 @@ genrule {
"protos/perfetto/metrics/android/android_boot_unagg.proto",
"protos/perfetto/metrics/android/android_frame_timeline_metric.proto",
"protos/perfetto/metrics/android/android_garbage_collection_unagg_metric.proto",
+ "protos/perfetto/metrics/android/android_oom_adjuster_metric.proto",
"protos/perfetto/metrics/android/android_sysui_notifications_blocking_calls_metric.proto",
"protos/perfetto/metrics/android/android_trusty_workqueues.proto",
"protos/perfetto/metrics/android/anr_metric.proto",
@@ -5385,7 +5389,7 @@ genrule {
tools: [
"aprotoc",
],
- cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --descriptor_set_out=$(out) $(in)",
+ cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --proto_path=external/protobuf/src --descriptor_set_out=$(out) $(in)",
out: [
"perfetto_protos_perfetto_metrics_webview_descriptor.bin",
],
@@ -11304,6 +11308,7 @@ filegroup {
name: "perfetto_src_trace_processor_containers_unittests",
srcs: [
"src/trace_processor/containers/bit_vector_unittest.cc",
+ "src/trace_processor/containers/implicit_segment_forest_unittest.cc",
"src/trace_processor/containers/null_term_string_view_unittest.cc",
"src/trace_processor/containers/row_map_unittest.cc",
"src/trace_processor/containers/string_pool_unittest.cc",
@@ -11343,6 +11348,7 @@ filegroup {
srcs: [
"src/trace_processor/db/column/arrangement_overlay_unittest.cc",
"src/trace_processor/db/column/dense_null_overlay_unittest.cc",
+ "src/trace_processor/db/column/fake_storage_unittest.cc",
"src/trace_processor/db/column/id_storage_unittest.cc",
"src/trace_processor/db/column/null_overlay_unittest.cc",
"src/trace_processor/db/column/numeric_storage_unittest.cc",
@@ -12037,6 +12043,7 @@ genrule {
"src/trace_processor/metrics/sql/android/android_multiuser.sql",
"src/trace_processor/metrics/sql/android/android_multiuser_populator.sql",
"src/trace_processor/metrics/sql/android/android_netperf.sql",
+ "src/trace_processor/metrics/sql/android/android_oom_adjuster.sql",
"src/trace_processor/metrics/sql/android/android_other_traces.sql",
"src/trace_processor/metrics/sql/android/android_package_list.sql",
"src/trace_processor/metrics/sql/android/android_powrails.sql",
@@ -12285,6 +12292,8 @@ filegroup {
filegroup {
name: "perfetto_src_trace_processor_perfetto_sql_intrinsics_operators_operators",
srcs: [
+ "src/trace_processor/perfetto_sql/intrinsics/operators/counter_mipmap_operator.cc",
+ "src/trace_processor/perfetto_sql/intrinsics/operators/slice_mipmap_operator.cc",
"src/trace_processor/perfetto_sql/intrinsics/operators/span_join_operator.cc",
"src/trace_processor/perfetto_sql/intrinsics/operators/window_operator.cc",
],
@@ -12431,6 +12440,7 @@ genrule {
"src/trace_processor/perfetto_sql/stdlib/android/startup/startups_maxsdk28.sql",
"src/trace_processor/perfetto_sql/stdlib/android/startup/startups_minsdk29.sql",
"src/trace_processor/perfetto_sql/stdlib/android/startup/startups_minsdk33.sql",
+ "src/trace_processor/perfetto_sql/stdlib/android/startup/time_to_display.sql",
"src/trace_processor/perfetto_sql/stdlib/android/statsd.sql",
"src/trace_processor/perfetto_sql/stdlib/android/suspend.sql",
"src/trace_processor/perfetto_sql/stdlib/android/thread.sql",
@@ -12485,6 +12495,10 @@ genrule {
"src/trace_processor/perfetto_sql/stdlib/stack_trace/jit.sql",
"src/trace_processor/perfetto_sql/stdlib/time/conversion.sql",
"src/trace_processor/perfetto_sql/stdlib/v8/jit.sql",
+ "src/trace_processor/perfetto_sql/stdlib/viz/summary/processes.sql",
+ "src/trace_processor/perfetto_sql/stdlib/viz/summary/slices.sql",
+ "src/trace_processor/perfetto_sql/stdlib/viz/summary/threads.sql",
+ "src/trace_processor/perfetto_sql/stdlib/viz/summary/tracks.sql",
"src/trace_processor/perfetto_sql/stdlib/wattson/arm_dsu.sql",
"src/trace_processor/perfetto_sql/stdlib/wattson/cpu_freq.sql",
"src/trace_processor/perfetto_sql/stdlib/wattson/cpu_idle.sql",
@@ -12891,6 +12905,7 @@ filegroup {
filegroup {
name: "perfetto_src_trace_redaction_trace_redaction",
srcs: [
+ "src/trace_redaction/collect_frame_cookies.cc",
"src/trace_redaction/collect_timeline_events.cc",
"src/trace_redaction/filter_ftrace_using_allowlist.cc",
"src/trace_redaction/filter_packet_using_allowlist.cc",
@@ -12921,6 +12936,7 @@ filegroup {
filegroup {
name: "perfetto_src_trace_redaction_unittests",
srcs: [
+ "src/trace_redaction/collect_frame_cookies_unittest.cc",
"src/trace_redaction/collect_timeline_events_unittest.cc",
"src/trace_redaction/filter_ftrace_using_allowlist_unittest.cc",
"src/trace_redaction/filter_packet_using_allowlist_unittest.cc",
@@ -13780,6 +13796,224 @@ filegroup {
}
// GN: [//protos/perfetto/trace:non_minimal_source_set, //protos/perfetto/trace:minimal_source_set]
+filegroup {
+ name: "perfetto_trace_filegroup_proto",
+ srcs: [
+ "protos/perfetto/common/android_energy_consumer_descriptor.proto",
+ "protos/perfetto/common/android_log_constants.proto",
+ "protos/perfetto/common/builtin_clock.proto",
+ "protos/perfetto/common/commit_data_request.proto",
+ "protos/perfetto/common/data_source_descriptor.proto",
+ "protos/perfetto/common/descriptor.proto",
+ "protos/perfetto/common/ftrace_descriptor.proto",
+ "protos/perfetto/common/gpu_counter_descriptor.proto",
+ "protos/perfetto/common/interceptor_descriptor.proto",
+ "protos/perfetto/common/observable_events.proto",
+ "protos/perfetto/common/perf_events.proto",
+ "protos/perfetto/common/protolog_common.proto",
+ "protos/perfetto/common/sys_stats_counters.proto",
+ "protos/perfetto/common/trace_stats.proto",
+ "protos/perfetto/common/tracing_service_capabilities.proto",
+ "protos/perfetto/common/tracing_service_state.proto",
+ "protos/perfetto/common/track_event_descriptor.proto",
+ "protos/perfetto/config/android/android_game_intervention_list_config.proto",
+ "protos/perfetto/config/android/android_input_event_config.proto",
+ "protos/perfetto/config/android/android_log_config.proto",
+ "protos/perfetto/config/android/android_polled_state_config.proto",
+ "protos/perfetto/config/android/android_sdk_sysprop_guard_config.proto",
+ "protos/perfetto/config/android/android_system_property_config.proto",
+ "protos/perfetto/config/android/network_trace_config.proto",
+ "protos/perfetto/config/android/packages_list_config.proto",
+ "protos/perfetto/config/android/pixel_modem_config.proto",
+ "protos/perfetto/config/android/protolog_config.proto",
+ "protos/perfetto/config/android/surfaceflinger_layers_config.proto",
+ "protos/perfetto/config/android/surfaceflinger_transactions_config.proto",
+ "protos/perfetto/config/chrome/chrome_config.proto",
+ "protos/perfetto/config/chrome/scenario_config.proto",
+ "protos/perfetto/config/chrome/v8_config.proto",
+ "protos/perfetto/config/data_source_config.proto",
+ "protos/perfetto/config/etw/etw_config.proto",
+ "protos/perfetto/config/ftrace/ftrace_config.proto",
+ "protos/perfetto/config/gpu/gpu_counter_config.proto",
+ "protos/perfetto/config/gpu/vulkan_memory_config.proto",
+ "protos/perfetto/config/inode_file/inode_file_config.proto",
+ "protos/perfetto/config/interceptor_config.proto",
+ "protos/perfetto/config/interceptors/console_config.proto",
+ "protos/perfetto/config/power/android_power_config.proto",
+ "protos/perfetto/config/process_stats/process_stats_config.proto",
+ "protos/perfetto/config/profiling/heapprofd_config.proto",
+ "protos/perfetto/config/profiling/java_hprof_config.proto",
+ "protos/perfetto/config/profiling/perf_event_config.proto",
+ "protos/perfetto/config/statsd/atom_ids.proto",
+ "protos/perfetto/config/statsd/statsd_tracing_config.proto",
+ "protos/perfetto/config/stress_test_config.proto",
+ "protos/perfetto/config/sys_stats/sys_stats_config.proto",
+ "protos/perfetto/config/system_info/system_info.proto",
+ "protos/perfetto/config/test_config.proto",
+ "protos/perfetto/config/trace_config.proto",
+ "protos/perfetto/config/track_event/track_event_config.proto",
+ "protos/perfetto/trace/android/android_game_intervention_list.proto",
+ "protos/perfetto/trace/android/android_input_event.proto",
+ "protos/perfetto/trace/android/android_log.proto",
+ "protos/perfetto/trace/android/android_system_property.proto",
+ "protos/perfetto/trace/android/camera_event.proto",
+ "protos/perfetto/trace/android/frame_timeline_event.proto",
+ "protos/perfetto/trace/android/gpu_mem_event.proto",
+ "protos/perfetto/trace/android/graphics_frame_event.proto",
+ "protos/perfetto/trace/android/initial_display_state.proto",
+ "protos/perfetto/trace/android/network_trace.proto",
+ "protos/perfetto/trace/android/packages_list.proto",
+ "protos/perfetto/trace/android/pixel_modem_events.proto",
+ "protos/perfetto/trace/android/protolog.proto",
+ "protos/perfetto/trace/android/shell_transition.proto",
+ "protos/perfetto/trace/android/surfaceflinger_common.proto",
+ "protos/perfetto/trace/android/surfaceflinger_layers.proto",
+ "protos/perfetto/trace/android/surfaceflinger_transactions.proto",
+ "protos/perfetto/trace/chrome/chrome_benchmark_metadata.proto",
+ "protos/perfetto/trace/chrome/chrome_metadata.proto",
+ "protos/perfetto/trace/chrome/chrome_trace_event.proto",
+ "protos/perfetto/trace/chrome/chrome_trigger.proto",
+ "protos/perfetto/trace/chrome/v8.proto",
+ "protos/perfetto/trace/clock_snapshot.proto",
+ "protos/perfetto/trace/etw/etw.proto",
+ "protos/perfetto/trace/etw/etw_event.proto",
+ "protos/perfetto/trace/etw/etw_event_bundle.proto",
+ "protos/perfetto/trace/extension_descriptor.proto",
+ "protos/perfetto/trace/filesystem/inode_file_map.proto",
+ "protos/perfetto/trace/ftrace/android_fs.proto",
+ "protos/perfetto/trace/ftrace/binder.proto",
+ "protos/perfetto/trace/ftrace/block.proto",
+ "protos/perfetto/trace/ftrace/cgroup.proto",
+ "protos/perfetto/trace/ftrace/clk.proto",
+ "protos/perfetto/trace/ftrace/cma.proto",
+ "protos/perfetto/trace/ftrace/compaction.proto",
+ "protos/perfetto/trace/ftrace/cpuhp.proto",
+ "protos/perfetto/trace/ftrace/cros_ec.proto",
+ "protos/perfetto/trace/ftrace/dma_fence.proto",
+ "protos/perfetto/trace/ftrace/dmabuf_heap.proto",
+ "protos/perfetto/trace/ftrace/dpu.proto",
+ "protos/perfetto/trace/ftrace/drm.proto",
+ "protos/perfetto/trace/ftrace/ext4.proto",
+ "protos/perfetto/trace/ftrace/f2fs.proto",
+ "protos/perfetto/trace/ftrace/fastrpc.proto",
+ "protos/perfetto/trace/ftrace/fence.proto",
+ "protos/perfetto/trace/ftrace/filemap.proto",
+ "protos/perfetto/trace/ftrace/ftrace.proto",
+ "protos/perfetto/trace/ftrace/ftrace_event.proto",
+ "protos/perfetto/trace/ftrace/ftrace_event_bundle.proto",
+ "protos/perfetto/trace/ftrace/ftrace_stats.proto",
+ "protos/perfetto/trace/ftrace/g2d.proto",
+ "protos/perfetto/trace/ftrace/generic.proto",
+ "protos/perfetto/trace/ftrace/gpu_mem.proto",
+ "protos/perfetto/trace/ftrace/gpu_scheduler.proto",
+ "protos/perfetto/trace/ftrace/hyp.proto",
+ "protos/perfetto/trace/ftrace/i2c.proto",
+ "protos/perfetto/trace/ftrace/ion.proto",
+ "protos/perfetto/trace/ftrace/ipi.proto",
+ "protos/perfetto/trace/ftrace/irq.proto",
+ "protos/perfetto/trace/ftrace/kmem.proto",
+ "protos/perfetto/trace/ftrace/kvm.proto",
+ "protos/perfetto/trace/ftrace/lowmemorykiller.proto",
+ "protos/perfetto/trace/ftrace/lwis.proto",
+ "protos/perfetto/trace/ftrace/mali.proto",
+ "protos/perfetto/trace/ftrace/mdss.proto",
+ "protos/perfetto/trace/ftrace/mm_event.proto",
+ "protos/perfetto/trace/ftrace/net.proto",
+ "protos/perfetto/trace/ftrace/oom.proto",
+ "protos/perfetto/trace/ftrace/panel.proto",
+ "protos/perfetto/trace/ftrace/perf_trace_counters.proto",
+ "protos/perfetto/trace/ftrace/power.proto",
+ "protos/perfetto/trace/ftrace/printk.proto",
+ "protos/perfetto/trace/ftrace/raw_syscalls.proto",
+ "protos/perfetto/trace/ftrace/regulator.proto",
+ "protos/perfetto/trace/ftrace/rpm.proto",
+ "protos/perfetto/trace/ftrace/samsung.proto",
+ "protos/perfetto/trace/ftrace/sched.proto",
+ "protos/perfetto/trace/ftrace/scm.proto",
+ "protos/perfetto/trace/ftrace/sde.proto",
+ "protos/perfetto/trace/ftrace/signal.proto",
+ "protos/perfetto/trace/ftrace/skb.proto",
+ "protos/perfetto/trace/ftrace/sock.proto",
+ "protos/perfetto/trace/ftrace/sync.proto",
+ "protos/perfetto/trace/ftrace/synthetic.proto",
+ "protos/perfetto/trace/ftrace/systrace.proto",
+ "protos/perfetto/trace/ftrace/task.proto",
+ "protos/perfetto/trace/ftrace/tcp.proto",
+ "protos/perfetto/trace/ftrace/test_bundle_wrapper.proto",
+ "protos/perfetto/trace/ftrace/thermal.proto",
+ "protos/perfetto/trace/ftrace/trusty.proto",
+ "protos/perfetto/trace/ftrace/ufs.proto",
+ "protos/perfetto/trace/ftrace/v4l2.proto",
+ "protos/perfetto/trace/ftrace/virtio_gpu.proto",
+ "protos/perfetto/trace/ftrace/virtio_video.proto",
+ "protos/perfetto/trace/ftrace/vmscan.proto",
+ "protos/perfetto/trace/ftrace/workqueue.proto",
+ "protos/perfetto/trace/gpu/gpu_counter_event.proto",
+ "protos/perfetto/trace/gpu/gpu_log.proto",
+ "protos/perfetto/trace/gpu/gpu_render_stage_event.proto",
+ "protos/perfetto/trace/gpu/vulkan_api_event.proto",
+ "protos/perfetto/trace/gpu/vulkan_memory_event.proto",
+ "protos/perfetto/trace/interned_data/interned_data.proto",
+ "protos/perfetto/trace/memory_graph.proto",
+ "protos/perfetto/trace/perfetto/perfetto_metatrace.proto",
+ "protos/perfetto/trace/perfetto/tracing_service_event.proto",
+ "protos/perfetto/trace/power/android_energy_estimation_breakdown.proto",
+ "protos/perfetto/trace/power/android_entity_state_residency.proto",
+ "protos/perfetto/trace/power/battery_counters.proto",
+ "protos/perfetto/trace/power/power_rails.proto",
+ "protos/perfetto/trace/profiling/deobfuscation.proto",
+ "protos/perfetto/trace/profiling/heap_graph.proto",
+ "protos/perfetto/trace/profiling/profile_common.proto",
+ "protos/perfetto/trace/profiling/profile_packet.proto",
+ "protos/perfetto/trace/profiling/smaps.proto",
+ "protos/perfetto/trace/ps/process_stats.proto",
+ "protos/perfetto/trace/ps/process_tree.proto",
+ "protos/perfetto/trace/remote_clock_sync.proto",
+ "protos/perfetto/trace/statsd/statsd_atom.proto",
+ "protos/perfetto/trace/sys_stats/sys_stats.proto",
+ "protos/perfetto/trace/system_info.proto",
+ "protos/perfetto/trace/system_info/cpu_info.proto",
+ "protos/perfetto/trace/test_event.proto",
+ "protos/perfetto/trace/test_extensions.proto",
+ "protos/perfetto/trace/trace.proto",
+ "protos/perfetto/trace/trace_packet.proto",
+ "protos/perfetto/trace/trace_packet_defaults.proto",
+ "protos/perfetto/trace/trace_uuid.proto",
+ "protos/perfetto/trace/track_event/chrome_active_processes.proto",
+ "protos/perfetto/trace/track_event/chrome_application_state_info.proto",
+ "protos/perfetto/trace/track_event/chrome_compositor_scheduler_state.proto",
+ "protos/perfetto/trace/track_event/chrome_content_settings_event_info.proto",
+ "protos/perfetto/trace/track_event/chrome_frame_reporter.proto",
+ "protos/perfetto/trace/track_event/chrome_histogram_sample.proto",
+ "protos/perfetto/trace/track_event/chrome_keyed_service.proto",
+ "protos/perfetto/trace/track_event/chrome_latency_info.proto",
+ "protos/perfetto/trace/track_event/chrome_legacy_ipc.proto",
+ "protos/perfetto/trace/track_event/chrome_message_pump.proto",
+ "protos/perfetto/trace/track_event/chrome_mojo_event_info.proto",
+ "protos/perfetto/trace/track_event/chrome_process_descriptor.proto",
+ "protos/perfetto/trace/track_event/chrome_renderer_scheduler_state.proto",
+ "protos/perfetto/trace/track_event/chrome_thread_descriptor.proto",
+ "protos/perfetto/trace/track_event/chrome_user_event.proto",
+ "protos/perfetto/trace/track_event/chrome_window_handle_event_info.proto",
+ "protos/perfetto/trace/track_event/counter_descriptor.proto",
+ "protos/perfetto/trace/track_event/debug_annotation.proto",
+ "protos/perfetto/trace/track_event/log_message.proto",
+ "protos/perfetto/trace/track_event/pixel_modem.proto",
+ "protos/perfetto/trace/track_event/process_descriptor.proto",
+ "protos/perfetto/trace/track_event/range_of_interest.proto",
+ "protos/perfetto/trace/track_event/screenshot.proto",
+ "protos/perfetto/trace/track_event/source_location.proto",
+ "protos/perfetto/trace/track_event/task_execution.proto",
+ "protos/perfetto/trace/track_event/thread_descriptor.proto",
+ "protos/perfetto/trace/track_event/track_descriptor.proto",
+ "protos/perfetto/trace/track_event/track_event.proto",
+ "protos/perfetto/trace/translation/translation_table.proto",
+ "protos/perfetto/trace/trigger.proto",
+ "protos/perfetto/trace/ui_state.proto",
+ ],
+}
+
+// GN: [//protos/perfetto/trace:non_minimal_source_set, //protos/perfetto/trace:minimal_source_set]
java_library {
name: "perfetto_trace_java_protos",
srcs: [
@@ -16147,11 +16381,10 @@ license {
],
}
-// TODO(b/315118713): use list of proto file sources instead of merged proto
gensrcs {
name: "perfetto_trace_javastream_protos",
srcs: [
- "protos/perfetto/trace/perfetto_trace.proto",
+ ":perfetto_trace_filegroup_proto",
],
tools: [
"aprotoc",
diff --git a/Android.bp.extras b/Android.bp.extras
index 3aab5aff2..3ebf51e72 100644
--- a/Android.bp.extras
+++ b/Android.bp.extras
@@ -172,11 +172,10 @@ license {
],
}
-// TODO(b/315118713): use list of proto file sources instead of merged proto
gensrcs {
name: "perfetto_trace_javastream_protos",
srcs: [
- "protos/perfetto/trace/perfetto_trace.proto",
+ ":perfetto_trace_filegroup_proto",
],
tools: [
"aprotoc",
diff --git a/BUILD b/BUILD
index 12fe725b0..c6408f179 100644
--- a/BUILD
+++ b/BUILD
@@ -1366,6 +1366,7 @@ perfetto_cc_library(
":include_perfetto_public_base",
":include_perfetto_public_protozero",
"src/trace_processor/containers/bit_vector.h",
+ "src/trace_processor/containers/implicit_segment_forest.h",
"src/trace_processor/containers/null_term_string_view.h",
"src/trace_processor/containers/row_map.h",
"src/trace_processor/containers/row_map_algorithms.h",
@@ -1997,6 +1998,7 @@ perfetto_filegroup(
"src/trace_processor/metrics/sql/android/android_multiuser.sql",
"src/trace_processor/metrics/sql/android/android_multiuser_populator.sql",
"src/trace_processor/metrics/sql/android/android_netperf.sql",
+ "src/trace_processor/metrics/sql/android/android_oom_adjuster.sql",
"src/trace_processor/metrics/sql/android/android_other_traces.sql",
"src/trace_processor/metrics/sql/android/android_package_list.sql",
"src/trace_processor/metrics/sql/android/android_powrails.sql",
@@ -2312,6 +2314,10 @@ perfetto_cc_tp_tables(
perfetto_filegroup(
name = "src_trace_processor_perfetto_sql_intrinsics_operators_operators",
srcs = [
+ "src/trace_processor/perfetto_sql/intrinsics/operators/counter_mipmap_operator.cc",
+ "src/trace_processor/perfetto_sql/intrinsics/operators/counter_mipmap_operator.h",
+ "src/trace_processor/perfetto_sql/intrinsics/operators/slice_mipmap_operator.cc",
+ "src/trace_processor/perfetto_sql/intrinsics/operators/slice_mipmap_operator.h",
"src/trace_processor/perfetto_sql/intrinsics/operators/span_join_operator.cc",
"src/trace_processor/perfetto_sql/intrinsics/operators/span_join_operator.h",
"src/trace_processor/perfetto_sql/intrinsics/operators/window_operator.cc",
@@ -2417,6 +2423,7 @@ perfetto_filegroup(
"src/trace_processor/perfetto_sql/stdlib/android/startup/startups_maxsdk28.sql",
"src/trace_processor/perfetto_sql/stdlib/android/startup/startups_minsdk29.sql",
"src/trace_processor/perfetto_sql/stdlib/android/startup/startups_minsdk33.sql",
+ "src/trace_processor/perfetto_sql/stdlib/android/startup/time_to_display.sql",
],
)
@@ -2620,6 +2627,17 @@ perfetto_filegroup(
],
)
+# GN target: //src/trace_processor/perfetto_sql/stdlib/viz/summary:summary
+perfetto_filegroup(
+ name = "src_trace_processor_perfetto_sql_stdlib_viz_summary_summary",
+ srcs = [
+ "src/trace_processor/perfetto_sql/stdlib/viz/summary/processes.sql",
+ "src/trace_processor/perfetto_sql/stdlib/viz/summary/slices.sql",
+ "src/trace_processor/perfetto_sql/stdlib/viz/summary/threads.sql",
+ "src/trace_processor/perfetto_sql/stdlib/viz/summary/tracks.sql",
+ ],
+)
+
# GN target: //src/trace_processor/perfetto_sql/stdlib/wattson:wattson
perfetto_filegroup(
name = "src_trace_processor_perfetto_sql_stdlib_wattson_wattson",
@@ -2655,6 +2673,7 @@ perfetto_cc_amalgamated_sql(
":src_trace_processor_perfetto_sql_stdlib_stack_trace_stack_trace",
":src_trace_processor_perfetto_sql_stdlib_time_time",
":src_trace_processor_perfetto_sql_stdlib_v8_v8",
+ ":src_trace_processor_perfetto_sql_stdlib_viz_summary_summary",
":src_trace_processor_perfetto_sql_stdlib_wattson_wattson",
],
outs = [
@@ -4428,6 +4447,7 @@ perfetto_proto_library(
"protos/perfetto/metrics/android/android_boot_unagg.proto",
"protos/perfetto/metrics/android/android_frame_timeline_metric.proto",
"protos/perfetto/metrics/android/android_garbage_collection_unagg_metric.proto",
+ "protos/perfetto/metrics/android/android_oom_adjuster_metric.proto",
"protos/perfetto/metrics/android/android_sysui_notifications_blocking_calls_metric.proto",
"protos/perfetto/metrics/android/android_trusty_workqueues.proto",
"protos/perfetto/metrics/android/anr_metric.proto",
diff --git a/CHANGELOG b/CHANGELOG
index b600600b0..99ff035f1 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -4,7 +4,18 @@ Unreleased:
Trace Processor:
*
UI:
- *
+ * Add tracks to the list of searchable items.
+ * Use mipmaps to improve track query performance on large traces.
+ * Fix slow scrolling bug in ftrace explorer tab on low DPI machines.
+ * Overhaul track decider queries to improve trace load times.
+ * Add track
+ * Tidy up command names & remove some example ones.
+ * Remove arg auto-completion in pivot table.
+ * Show dominator tree views by default.
+ * Fix counter event selection off-by-one error.
+ * Add viewport control to the plugin API.
+ * Sticky track titles to improve track button accessibility in tall tracks.
+ * A handful of small bugfixes.
SDK:
* The TRACE_EVENT macro used to reject `const char *` event names: either
`StaticString` or `DynamicString` needed to be specified. In the last year
diff --git a/OWNERS b/OWNERS
index 3c29a2089..b2a133100 100644
--- a/OWNERS
+++ b/OWNERS
@@ -7,7 +7,6 @@ primiano@google.com
skyostil@google.com
# UI, Ftrace interop, traced_probes, protozero, Android internals.
-hjd@google.com
# Trace Processor, metrics, infra.
lalitm@google.com
diff --git a/bazel/proto_gen.bzl b/bazel/proto_gen.bzl
index cd31d442d..c5fd127c2 100644
--- a/bazel/proto_gen.bzl
+++ b/bazel/proto_gen.bzl
@@ -26,6 +26,11 @@ def _proto_gen_impl(ctx):
for dep in ctx.attr.deps
for f in dep[ProtoInfo].transitive_imports.to_list()
]
+ proto_paths = [
+ f
+ for dep in ctx.attr.deps
+ for f in dep[ProtoInfo].transitive_proto_path.to_list()
+ ]
proto_path = "."
@@ -58,8 +63,10 @@ def _proto_gen_impl(ctx):
out_files += [ctx.actions.declare_file(base_path + ".%s.cc" % suffix)]
arguments = [
- "--proto_path=" + proto_path,
+ "--proto_path=" + proto_path
+ for proto_path in proto_paths
]
+
plugin_deps = []
if ctx.attr.plugin:
wrap_arg = ctx.attr.wrapper_namespace
diff --git a/docs/contributing/build-instructions.md b/docs/contributing/build-instructions.md
index c192898f5..85025d6b3 100644
--- a/docs/contributing/build-instructions.md
+++ b/docs/contributing/build-instructions.md
@@ -535,6 +535,7 @@ In `.vscode/settings.json`:
"./ui",
],
"prettier.configPath": "ui/.prettierrc.yml",
+ "typescript.preferences.importModuleSpecifier": "relative",
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
diff --git a/docs/contributing/ui-plugins.md b/docs/contributing/ui-plugins.md
index 09fa88e3e..9b1535af2 100644
--- a/docs/contributing/ui-plugins.md
+++ b/docs/contributing/ui-plugins.md
@@ -36,9 +36,10 @@ Notes on naming:
- Commands should have ids with the pattern `example.com#DoSomething`
- Command's ids should be prefixed with the id of the plugin which
provides them.
-- Commands names should have the form "Verb something something".
- Good: "Pin janky frame timeline tracks"
- Bad: "Tracks are Displayed if Janky"
+- Command names should have the form "Verb something something", and should be
+ in normal sentence case. I.e. don't capitalize the first letter of each word.
+ - Good: "Pin janky frame timeline tracks"
+ - Bad: "Tracks are Displayed if Janky"
### Start the dev server
```sh
diff --git a/gn/proto_library.gni b/gn/proto_library.gni
index f69d3ce74..a7a77c613 100644
--- a/gn/proto_library.gni
+++ b/gn/proto_library.gni
@@ -371,7 +371,7 @@ template("perfetto_proto_library") {
metadata = {
proto_library_sources = invoker.sources
- import_dirs = import_dirs_
+ proto_import_dirs = import_dirs_
exports = get_path_info(public_deps_, "abspath")
}
forward_variables_from(invoker, vars_to_forward)
diff --git a/gn/standalone/proto_library.gni b/gn/standalone/proto_library.gni
index 07b8140ac..1a23a97ad 100644
--- a/gn/standalone/proto_library.gni
+++ b/gn/standalone/proto_library.gni
@@ -170,6 +170,10 @@ template("proto_library") {
]
}
+ metadata = {
+ proto_import_dirs = import_dirs
+ }
+
if (generate_cc) {
cc_generator_options_ = ""
if (defined(invoker.cc_generator_options)) {
diff --git a/infra/OWNERS b/infra/OWNERS
index a4b3fef71..0178ed0ec 100644
--- a/infra/OWNERS
+++ b/infra/OWNERS
@@ -1,3 +1,2 @@
-hjd@google.com
lalitm@google.com
primiano@google.com
diff --git a/protos/perfetto/ipc/OWNERS b/protos/perfetto/ipc/OWNERS
index a10c8b820..bead02d78 100644
--- a/protos/perfetto/ipc/OWNERS
+++ b/protos/perfetto/ipc/OWNERS
@@ -4,6 +4,5 @@
set noparent
eseckler@google.com
-hjd@google.com
primiano@google.com
skyostil@google.com
diff --git a/protos/perfetto/metrics/android/BUILD.gn b/protos/perfetto/metrics/android/BUILD.gn
index 5b8fab6dd..5430c22fc 100644
--- a/protos/perfetto/metrics/android/BUILD.gn
+++ b/protos/perfetto/metrics/android/BUILD.gn
@@ -28,6 +28,7 @@ perfetto_proto_library("@TYPE@") {
"android_boot_unagg.proto",
"android_frame_timeline_metric.proto",
"android_garbage_collection_unagg_metric.proto",
+ "android_oom_adjuster_metric.proto",
"android_sysui_notifications_blocking_calls_metric.proto",
"android_trusty_workqueues.proto",
"anr_metric.proto",
diff --git a/protos/perfetto/metrics/android/android_boot.proto b/protos/perfetto/metrics/android/android_boot.proto
index a043efe06..9f90ba555 100644
--- a/protos/perfetto/metrics/android/android_boot.proto
+++ b/protos/perfetto/metrics/android/android_boot.proto
@@ -56,8 +56,39 @@ message AndroidBootMetric {
optional int64 alloc_gc_count = 11;
optional double mb_per_ms_of_gc = 12;
}
+ message OomAdjusterTransitionCounts {
+ // name of the item aggregated by. example: process_name, oom_adjuster_reason.
+ optional string name = 1;
+ // name of previous oom bucket.
+ optional string src_bucket = 2;
+ // name of oom bucket.
+ optional string dest_bucket = 3;
+ // count of transitions
+ optional int64 count = 4;
+ }
+ message OomAdjBucketDurationAggregation {
+ // name of the item aggregated by. example: process_name, oom_adjuster_reason
+ optional string name = 1;
+ // name of oom bucket.
+ optional string bucket = 2;
+ // Duration of the time in the bucket
+ optional int64 total_dur = 3;
+ }
+ message OomAdjDurationAggregation {
+ optional int64 min_oom_adj_dur = 1;
+ optional int64 max_oom_adj_dur = 2;
+ optional double avg_oom_adj_dur = 3;
+ optional int64 oom_adj_event_count = 4;
+ optional string oom_adj_reason = 5;
+ }
optional ProcessStartAggregation full_trace_process_start_aggregation = 6;
optional ProcessStartAggregation post_boot_process_start_aggregation = 7;
optional GarbageCollectionAggregation full_trace_gc_aggregation = 8;
optional GarbageCollectionAggregation post_boot_gc_aggregation = 9;
+ repeated OomAdjusterTransitionCounts post_boot_oom_adjuster_transition_counts_global = 10;
+ repeated OomAdjusterTransitionCounts post_boot_oom_adjuster_transition_counts_by_process = 11;
+ repeated OomAdjusterTransitionCounts post_boot_oom_adjuster_transition_counts_by_oom_adj_reason = 12;
+ repeated OomAdjBucketDurationAggregation post_boot_oom_adj_bucket_duration_agg_global = 13;
+ repeated OomAdjBucketDurationAggregation post_boot_oom_adj_bucket_duration_agg_by_process = 14;
+ repeated OomAdjDurationAggregation post_boot_oom_adj_duration_agg = 15;
}
diff --git a/protos/perfetto/metrics/android/android_oom_adjuster_metric.proto b/protos/perfetto/metrics/android/android_oom_adjuster_metric.proto
new file mode 100644
index 000000000..95af7b4b0
--- /dev/null
+++ b/protos/perfetto/metrics/android/android_oom_adjuster_metric.proto
@@ -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.
+ */
+
+syntax = "proto2";
+
+package perfetto.protos;
+
+message AndroidOomAdjusterMetric {
+ message OomAdjusterTransitionCounts {
+ // name of the item aggregated by. example: process_name, oom_adjuster_reason.
+ optional string name = 1;
+ // name of previous oom bucket.
+ optional string src_bucket = 2;
+ // name of oom bucket.
+ optional string dest_bucket = 3;
+ // count of transitions
+ optional int64 count = 4;
+ }
+ message OomAdjBucketDurationAggregation {
+ // name of the item aggregated by. example: process_name, oom_adjuster_reason
+ optional string name = 1;
+ // name of oom bucket.
+ optional string bucket = 2;
+ // Duration of the time in the bucket
+ optional int64 total_dur = 3;
+ }
+ message OomAdjDurationAggregation {
+ optional int64 min_oom_adj_dur = 1;
+ optional int64 max_oom_adj_dur = 2;
+ optional double avg_oom_adj_dur = 3;
+ optional int64 oom_adj_event_count = 4;
+ optional string oom_adj_reason = 5;
+ }
+ repeated OomAdjusterTransitionCounts oom_adjuster_transition_counts_global = 1;
+ repeated OomAdjusterTransitionCounts oom_adjuster_transition_counts_by_process = 2;
+ repeated OomAdjusterTransitionCounts oom_adjuster_transition_counts_by_oom_adj_reason = 3;
+ repeated OomAdjBucketDurationAggregation oom_adj_bucket_duration_agg_global = 4;
+ repeated OomAdjBucketDurationAggregation oom_adj_bucket_duration_agg_by_process = 5;
+ repeated OomAdjDurationAggregation oom_adj_duration_agg = 6;
+} \ No newline at end of file
diff --git a/protos/perfetto/metrics/metrics.proto b/protos/perfetto/metrics/metrics.proto
index 83395ecb4..c3025c301 100644
--- a/protos/perfetto/metrics/metrics.proto
+++ b/protos/perfetto/metrics/metrics.proto
@@ -71,6 +71,7 @@ import "protos/perfetto/metrics/android/binder_metric.proto";
import "protos/perfetto/metrics/android/monitor_contention_metric.proto";
import "protos/perfetto/metrics/android/monitor_contention_agg_metric.proto";
import "protos/perfetto/metrics/android/app_process_starts_metric.proto";
+import "protos/perfetto/metrics/android/android_oom_adjuster_metric.proto";
// Trace processor metadata
message TraceMetadata {
@@ -298,6 +299,10 @@ message TraceMetrics {
// All blocking calls (e.g. binder calls) for a trace.
optional AndroidBlockingCallsUnagg android_blocking_calls_unagg = 65;
+ // Android OOM unaggregated metrics.
+ optional AndroidOomAdjusterMetric android_oom_adjuster = 66;
+
+ // Android
// Demo extensions.
extensions 450 to 499;
diff --git a/protos/perfetto/metrics/perfetto_merged_metrics.proto b/protos/perfetto/metrics/perfetto_merged_metrics.proto
index 6b7a611d2..86f7d5986 100644
--- a/protos/perfetto/metrics/perfetto_merged_metrics.proto
+++ b/protos/perfetto/metrics/perfetto_merged_metrics.proto
@@ -203,10 +203,41 @@ message AndroidBootMetric {
optional int64 alloc_gc_count = 11;
optional double mb_per_ms_of_gc = 12;
}
+ message OomAdjusterTransitionCounts {
+ // name of the item aggregated by. example: process_name, oom_adjuster_reason.
+ optional string name = 1;
+ // name of previous oom bucket.
+ optional string src_bucket = 2;
+ // name of oom bucket.
+ optional string dest_bucket = 3;
+ // count of transitions
+ optional int64 count = 4;
+ }
+ message OomAdjBucketDurationAggregation {
+ // name of the item aggregated by. example: process_name, oom_adjuster_reason
+ optional string name = 1;
+ // name of oom bucket.
+ optional string bucket = 2;
+ // Duration of the time in the bucket
+ optional int64 total_dur = 3;
+ }
+ message OomAdjDurationAggregation {
+ optional int64 min_oom_adj_dur = 1;
+ optional int64 max_oom_adj_dur = 2;
+ optional double avg_oom_adj_dur = 3;
+ optional int64 oom_adj_event_count = 4;
+ optional string oom_adj_reason = 5;
+ }
optional ProcessStartAggregation full_trace_process_start_aggregation = 6;
optional ProcessStartAggregation post_boot_process_start_aggregation = 7;
optional GarbageCollectionAggregation full_trace_gc_aggregation = 8;
optional GarbageCollectionAggregation post_boot_gc_aggregation = 9;
+ repeated OomAdjusterTransitionCounts post_boot_oom_adjuster_transition_counts_global = 10;
+ repeated OomAdjusterTransitionCounts post_boot_oom_adjuster_transition_counts_by_process = 11;
+ repeated OomAdjusterTransitionCounts post_boot_oom_adjuster_transition_counts_by_oom_adj_reason = 12;
+ repeated OomAdjBucketDurationAggregation post_boot_oom_adj_bucket_duration_agg_global = 13;
+ repeated OomAdjBucketDurationAggregation post_boot_oom_adj_bucket_duration_agg_by_process = 14;
+ repeated OomAdjDurationAggregation post_boot_oom_adj_duration_agg = 15;
}
// End of protos/perfetto/metrics/android/android_boot.proto
@@ -365,6 +396,43 @@ message AndroidFrameTimelineMetric {
// End of protos/perfetto/metrics/android/android_frame_timeline_metric.proto
+// Begin of protos/perfetto/metrics/android/android_oom_adjuster_metric.proto
+
+message AndroidOomAdjusterMetric {
+ message OomAdjusterTransitionCounts {
+ // name of the item aggregated by. example: process_name, oom_adjuster_reason.
+ optional string name = 1;
+ // name of previous oom bucket.
+ optional string src_bucket = 2;
+ // name of oom bucket.
+ optional string dest_bucket = 3;
+ // count of transitions
+ optional int64 count = 4;
+ }
+ message OomAdjBucketDurationAggregation {
+ // name of the item aggregated by. example: process_name, oom_adjuster_reason
+ optional string name = 1;
+ // name of oom bucket.
+ optional string bucket = 2;
+ // Duration of the time in the bucket
+ optional int64 total_dur = 3;
+ }
+ message OomAdjDurationAggregation {
+ optional int64 min_oom_adj_dur = 1;
+ optional int64 max_oom_adj_dur = 2;
+ optional double avg_oom_adj_dur = 3;
+ optional int64 oom_adj_event_count = 4;
+ optional string oom_adj_reason = 5;
+ }
+ repeated OomAdjusterTransitionCounts oom_adjuster_transition_counts_global = 1;
+ repeated OomAdjusterTransitionCounts oom_adjuster_transition_counts_by_process = 2;
+ repeated OomAdjusterTransitionCounts oom_adjuster_transition_counts_by_oom_adj_reason = 3;
+ repeated OomAdjBucketDurationAggregation oom_adj_bucket_duration_agg_global = 4;
+ repeated OomAdjBucketDurationAggregation oom_adj_bucket_duration_agg_by_process = 5;
+ repeated OomAdjDurationAggregation oom_adj_duration_agg = 6;
+}
+// End of protos/perfetto/metrics/android/android_oom_adjuster_metric.proto
+
// Begin of protos/perfetto/metrics/android/android_sysui_notifications_blocking_calls_metric.proto
// Blocking calls inside System UI Notifications. Shows count and total duration for each.
@@ -2721,6 +2789,10 @@ message TraceMetrics {
// All blocking calls (e.g. binder calls) for a trace.
optional AndroidBlockingCallsUnagg android_blocking_calls_unagg = 65;
+ // Android OOM unaggregated metrics.
+ optional AndroidOomAdjusterMetric android_oom_adjuster = 66;
+
+ // Android
// Demo extensions.
extensions 450 to 499;
diff --git a/protos/third_party/chromium/chrome_track_event.proto b/protos/third_party/chromium/chrome_track_event.proto
index 29a4cb3e3..8b766cbc4 100644
--- a/protos/third_party/chromium/chrome_track_event.proto
+++ b/protos/third_party/chromium/chrome_track_event.proto
@@ -39,6 +39,7 @@ message BlinkTaskScope {
TASK_SCOPE_SCHEDULER_POST_TASK = 6;
TASK_SCOPE_REQUEST_IDLE_CALLBACK = 7;
TASK_SCOPE_XML_HTTP_REQUEST = 8;
+ TASK_SCOPE_SOFT_NAVIGATION = 9;
}
optional TaskScopeType type = 1;
optional int64 scope_task_id = 2;
@@ -303,6 +304,9 @@ message RenderProcessHostCleanup {
// Number of "pending reuse" references active in the RenderProcessHost,
// recorded when Cleanup() was called.
optional uint32 pending_reuse_ref_count = 5;
+ // Number of NavigationStateKeepAlive references active in the
+ // RenderProcessHost, recorded when Cleanup() was called.
+ optional uint32 navigation_state_keepalive_count = 6;
}
message ChildProcessLauncherPriority {
@@ -596,7 +600,7 @@ message ChromeThreadPoolTask {
optional ShutdownBehavior shutdown_behavior = 4;
}
-// TODO(crbug.com/1258495): Add more information.
+// TODO(crbug.com/40797026): Add more information.
message BackForwardCacheCanStoreDocumentResult {
enum BackForwardCacheNotRestoredReason {
NOT_MAIN_FRAME = 1;
@@ -722,7 +726,7 @@ message RendererMainThreadTaskExecution {
TASK_TYPE_WORKER_THREAD_TASK_QUEUE_COMPOSITOR = 48;
TASK_TYPE_COMPOSITOR_THREAD_TASK_QUEUE_INPUT = 49;
- // TODO(crbug.com/860545): Obsolete. Remove.
+ // TODO(crbug.com/40583778): Obsolete. Remove.
TASK_TYPE_NETWORKING_WITH_URL_LOADER_ANNOTATION = 50;
TASK_TYPE_WORKER_ANIMATION = 51;
diff --git a/python/generators/trace_processor_table/serialize.py b/python/generators/trace_processor_table/serialize.py
index b9d51703e..1e613e30c 100644
--- a/python/generators/trace_processor_table/serialize.py
+++ b/python/generators/trace_processor_table/serialize.py
@@ -674,15 +674,13 @@ class {self.table_name} : public macros_internal::MacroTable {{
Iterator IterateRows() {{ return Iterator(this, Table::IterateRows()); }}
- ConstIterator FilterToIterator(
- const std::vector<Constraint>& cs) const {{
+ ConstIterator FilterToIterator(const Query& q) const {{
return ConstIterator(
- this, ApplyAndIterateRows(QueryToRowMap(cs, {{}})));
+ this, ApplyAndIterateRows(QueryToRowMap(q)));
}}
- Iterator FilterToIterator(
- const std::vector<Constraint>& cs) {{
- return Iterator(this, ApplyAndIterateRows(QueryToRowMap(cs, {{}})));
+ Iterator FilterToIterator(const Query& q) {{
+ return Iterator(this, ApplyAndIterateRows(QueryToRowMap(q)));
}}
void ShrinkToFit() {{
diff --git a/python/perfetto/prebuilts/perfetto_prebuilts.py b/python/perfetto/prebuilts/perfetto_prebuilts.py
index 087a33707..61283cc40 100644
--- a/python/perfetto/prebuilts/perfetto_prebuilts.py
+++ b/python/perfetto/prebuilts/perfetto_prebuilts.py
@@ -46,6 +46,9 @@ import os
import platform
import subprocess
import sys
+import threading
+
+DOWNLOAD_LOCK = threading.Lock()
def download_or_get_cached(file_name, url, sha256):
@@ -62,28 +65,36 @@ def download_or_get_cached(file_name, url, sha256):
sha256_path = os.path.join(dir, file_name + '.sha256')
needs_download = True
- # Avoid recomputing the SHA-256 on each invocation. The SHA-256 of the last
- # download is cached into file_name.sha256, just check if that matches.
- if os.path.exists(bin_path) and os.path.exists(sha256_path):
- with open(sha256_path, 'rb') as f:
- digest = f.read().decode()
- if digest == sha256:
- needs_download = False
-
- if needs_download:
- # Either the filed doesn't exist or the SHA256 doesn't match.
- tmp_path = bin_path + '.tmp'
- print('Downloading ' + url)
- subprocess.check_call(['curl', '-f', '-L', '-#', '-o', tmp_path, url])
- with open(tmp_path, 'rb') as fd:
- actual_sha256 = hashlib.sha256(fd.read()).hexdigest()
- if actual_sha256 != sha256:
- raise Exception('Checksum mismatch for %s (actual: %s, expected: %s)' %
- (url, actual_sha256, sha256))
- os.chmod(tmp_path, 0o755)
- os.replace(tmp_path, bin_path)
- with open(sha256_path, 'w') as f:
- f.write(sha256)
+ try:
+ # In BatchTraceProcessor, many threads can be trying to execute the below
+ # code in parallel. For this reason, protect the whole operation with a
+ # lock.
+ DOWNLOAD_LOCK.acquire()
+
+ # Avoid recomputing the SHA-256 on each invocation. The SHA-256 of the last
+ # download is cached into file_name.sha256, just check if that matches.
+ if os.path.exists(bin_path) and os.path.exists(sha256_path):
+ with open(sha256_path, 'rb') as f:
+ digest = f.read().decode()
+ if digest == sha256:
+ needs_download = False
+
+ if needs_download:
+ # Either the filed doesn't exist or the SHA256 doesn't match.
+ tmp_path = bin_path + '.tmp'
+ print('Downloading ' + url)
+ subprocess.check_call(['curl', '-f', '-L', '-#', '-o', tmp_path, url])
+ with open(tmp_path, 'rb') as fd:
+ actual_sha256 = hashlib.sha256(fd.read()).hexdigest()
+ if actual_sha256 != sha256:
+ raise Exception('Checksum mismatch for %s (actual: %s, expected: %s)' %
+ (url, actual_sha256, sha256))
+ os.chmod(tmp_path, 0o755)
+ os.replace(tmp_path, bin_path)
+ with open(sha256_path, 'w') as f:
+ f.write(sha256)
+ finally:
+ DOWNLOAD_LOCK.release()
return bin_path
diff --git a/python/perfetto/trace_processor/metrics.descriptor b/python/perfetto/trace_processor/metrics.descriptor
index cc4850f97..c337e462d 100644
--- a/python/perfetto/trace_processor/metrics.descriptor
+++ b/python/perfetto/trace_processor/metrics.descriptor
@@ -25,11 +25,11 @@ adIdMetricT
app_set_id_metric ( 2).perfetto.protos.AdServicesAppSetIdMetricRappSetIdMetricM
odp_metric ( 2..perfetto.protos.OnDevicePersonalizationMetricR odpMetric
-¼
+ï
2protos/perfetto/metrics/android/android_boot.protoperfetto.protos"p
ProcessStateDurations
total_dur (RtotalDur:
-uninterruptible_sleep_dur (RuninterruptibleSleepDur"‚
+uninterruptible_sleep_dur (RuninterruptibleSleepDur"µ
AndroidBootMetric^
system_server_durations ( 2&.perfetto.protos.ProcessStateDurationsRsystemServerDurationsU
systemui_durations ( 2&.perfetto.protos.ProcessStateDurationsRsystemuiDurationsU
@@ -39,7 +39,14 @@ odp_metric ( 2..perfetto.protos.OnDevicePersonalizationMetricR odpMetric
$full_trace_process_start_aggregation ( 2:.perfetto.protos.AndroidBootMetric.ProcessStartAggregationR fullTraceProcessStartAggregationˆ
#post_boot_process_start_aggregation ( 2:.perfetto.protos.AndroidBootMetric.ProcessStartAggregationRpostBootProcessStartAggregationz
full_trace_gc_aggregation ( 2?.perfetto.protos.AndroidBootMetric.GarbageCollectionAggregationRfullTraceGcAggregationx
-post_boot_gc_aggregation ( 2?.perfetto.protos.AndroidBootMetric.GarbageCollectionAggregationRpostBootGcAggregation9
+post_boot_gc_aggregation ( 2?.perfetto.protos.AndroidBootMetric.GarbageCollectionAggregationRpostBootGcAggregation¢
+/post_boot_oom_adjuster_transition_counts_global
+ ( 2>.perfetto.protos.AndroidBootMetric.OomAdjusterTransitionCountsR)postBootOomAdjusterTransitionCountsGlobal©
+3post_boot_oom_adjuster_transition_counts_by_process ( 2>.perfetto.protos.AndroidBootMetric.OomAdjusterTransitionCountsR,postBootOomAdjusterTransitionCountsByProcessµ
+:post_boot_oom_adjuster_transition_counts_by_oom_adj_reason ( 2>.perfetto.protos.AndroidBootMetric.OomAdjusterTransitionCountsR1postBootOomAdjusterTransitionCountsByOomAdjReasonŸ
+,post_boot_oom_adj_bucket_duration_agg_global ( 2B.perfetto.protos.AndroidBootMetric.OomAdjBucketDurationAggregationR%postBootOomAdjBucketDurationAggGlobal¦
+0post_boot_oom_adj_bucket_duration_agg_by_process ( 2B.perfetto.protos.AndroidBootMetric.OomAdjBucketDurationAggregationR(postBootOomAdjBucketDurationAggByProcess
+post_boot_oom_adj_duration_agg ( 2<.perfetto.protos.AndroidBootMetric.OomAdjDurationAggregationRpostBootOomAdjDurationAgg9
LauncherBreakdown$
cold_start_dur (R coldStartDur™
ProcessStartAggregation&
@@ -59,7 +66,24 @@ $full_trace_process_start_aggregation ( 2:.perfetto.protos.AndroidBootMetric.
explicit_gc_count
(RexplicitGcCount$
alloc_gc_count (R allocGcCount$
-mb_per_ms_of_gc (R mbPerMsOfGc
+mb_per_ms_of_gc (R mbPerMsOfGc‡
+OomAdjusterTransitionCounts
+name ( Rname
+
+src_bucket ( R srcBucket
+ dest_bucket ( R
+destBucket
+count (Rcountj
+OomAdjBucketDurationAggregation
+name ( Rname
+bucket ( Rbucket
+ total_dur (RtotalDurå
+OomAdjDurationAggregation%
+min_oom_adj_dur (R minOomAdjDur%
+max_oom_adj_dur (R maxOomAdjDur%
+avg_oom_adj_dur (R avgOomAdjDur-
+oom_adj_event_count (RoomAdjEventCount$
+oom_adj_reason ( R oomAdjReason
Ž
?protos/perfetto/metrics/android/app_process_starts_metric.protoperfetto.protos"¹
AndroidAppProcessStartsMetricV
@@ -1203,8 +1227,34 @@ Cprotos/perfetto/metrics/android/monitor_contention_agg_metric.protoperfetto.p
total_contention_dur (RtotalContentionDur?
main_thread_contention_count (RmainThreadContentionCount;
main_thread_contention_dur (RmainThreadContentionDur
-›G
-%protos/perfetto/metrics/metrics.protoperfetto.protos8protos/perfetto/metrics/android/ad_services_metric.proto2protos/perfetto/metrics/android/android_boot.proto8protos/perfetto/metrics/android/android_boot_unagg.protoMprotos/perfetto/metrics/android/android_garbage_collection_unagg_metric.protoKprotos/perfetto/metrics/android/sysui_notif_shade_list_builder_metric.protoRprotos/perfetto/metrics/android/sysui_update_notif_on_ui_mode_changed_metric.protoCprotos/perfetto/metrics/android/android_frame_timeline_metric.proto0protos/perfetto/metrics/android/anr_metric.proto1protos/perfetto/metrics/android/batt_metric.protoWprotos/perfetto/metrics/android/android_sysui_notifications_blocking_calls_metric.protoGprotos/perfetto/metrics/android/android_blocking_calls_cuj_metric.protoBprotos/perfetto/metrics/android/android_blocking_calls_unagg.proto3protos/perfetto/metrics/android/codec_metrics.proto0protos/perfetto/metrics/android/cpu_metric.proto3protos/perfetto/metrics/android/camera_metric.proto9protos/perfetto/metrics/android/camera_unagg_metric.proto5protos/perfetto/metrics/android/display_metrics.proto5protos/perfetto/metrics/android/dma_heap_metric.proto1protos/perfetto/metrics/android/dvfs_metric.proto4protos/perfetto/metrics/android/fastrpc_metric.proto0protos/perfetto/metrics/android/g2d_metric.proto0protos/perfetto/metrics/android/gpu_metric.proto0protos/perfetto/metrics/android/hwcomposer.proto1protos/perfetto/metrics/android/hwui_metric.proto/protos/perfetto/metrics/android/io_metric.proto5protos/perfetto/metrics/android/io_unagg_metric.proto0protos/perfetto/metrics/android/ion_metric.proto8protos/perfetto/metrics/android/irq_runtime_metric.proto5protos/perfetto/metrics/android/jank_cuj_metric.proto9protos/perfetto/metrics/android/java_heap_histogram.proto5protos/perfetto/metrics/android/java_heap_stats.proto0protos/perfetto/metrics/android/lmk_metric.proto7protos/perfetto/metrics/android/lmk_reason_metric.proto0protos/perfetto/metrics/android/mem_metric.proto6protos/perfetto/metrics/android/mem_unagg_metric.proto6protos/perfetto/metrics/android/multiuser_metric.proto4protos/perfetto/metrics/android/network_metric.proto2protos/perfetto/metrics/android/other_traces.proto2protos/perfetto/metrics/android/package_list.proto5protos/perfetto/metrics/android/powrails_metric.proto4protos/perfetto/metrics/android/profiler_smaps.proto7protos/perfetto/metrics/android/rt_runtime_metric.proto0protos/perfetto/metrics/android/simpleperf.proto4protos/perfetto/metrics/android/startup_metric.proto4protos/perfetto/metrics/android/surfaceflinger.proto0protos/perfetto/metrics/android/task_names.proto3protos/perfetto/metrics/android/trace_quality.proto?protos/perfetto/metrics/android/android_trusty_workqueues.proto9protos/perfetto/metrics/android/unsymbolized_frames.proto3protos/perfetto/metrics/android/binder_metric.proto?protos/perfetto/metrics/android/monitor_contention_metric.protoCprotos/perfetto/metrics/android/monitor_contention_agg_metric.proto?protos/perfetto/metrics/android/app_process_starts_metric.proto"î
+Aprotos/perfetto/metrics/android/android_oom_adjuster_metric.protoperfetto.protos"‹
+AndroidOomAdjusterMetric—
+%oom_adjuster_transition_counts_global ( 2E.perfetto.protos.AndroidOomAdjusterMetric.OomAdjusterTransitionCountsR!oomAdjusterTransitionCountsGlobalž
+)oom_adjuster_transition_counts_by_process ( 2E.perfetto.protos.AndroidOomAdjusterMetric.OomAdjusterTransitionCountsR$oomAdjusterTransitionCountsByProcessª
+0oom_adjuster_transition_counts_by_oom_adj_reason ( 2E.perfetto.protos.AndroidOomAdjusterMetric.OomAdjusterTransitionCountsR)oomAdjusterTransitionCountsByOomAdjReason”
+"oom_adj_bucket_duration_agg_global ( 2I.perfetto.protos.AndroidOomAdjusterMetric.OomAdjBucketDurationAggregationRoomAdjBucketDurationAggGlobal›
+&oom_adj_bucket_duration_agg_by_process ( 2I.perfetto.protos.AndroidOomAdjusterMetric.OomAdjBucketDurationAggregationR oomAdjBucketDurationAggByProcesst
+oom_adj_duration_agg ( 2C.perfetto.protos.AndroidOomAdjusterMetric.OomAdjDurationAggregationRoomAdjDurationAgg‡
+OomAdjusterTransitionCounts
+name ( Rname
+
+src_bucket ( R srcBucket
+ dest_bucket ( R
+destBucket
+count (Rcountj
+OomAdjBucketDurationAggregation
+name ( Rname
+bucket ( Rbucket
+ total_dur (RtotalDurå
+OomAdjDurationAggregation%
+min_oom_adj_dur (R minOomAdjDur%
+max_oom_adj_dur (R maxOomAdjDur%
+avg_oom_adj_dur (R avgOomAdjDur-
+oom_adj_event_count (RoomAdjEventCount$
+oom_adj_reason ( R oomAdjReason
+»H
+%protos/perfetto/metrics/metrics.protoperfetto.protos8protos/perfetto/metrics/android/ad_services_metric.proto2protos/perfetto/metrics/android/android_boot.proto8protos/perfetto/metrics/android/android_boot_unagg.protoMprotos/perfetto/metrics/android/android_garbage_collection_unagg_metric.protoKprotos/perfetto/metrics/android/sysui_notif_shade_list_builder_metric.protoRprotos/perfetto/metrics/android/sysui_update_notif_on_ui_mode_changed_metric.protoCprotos/perfetto/metrics/android/android_frame_timeline_metric.proto0protos/perfetto/metrics/android/anr_metric.proto1protos/perfetto/metrics/android/batt_metric.protoWprotos/perfetto/metrics/android/android_sysui_notifications_blocking_calls_metric.protoGprotos/perfetto/metrics/android/android_blocking_calls_cuj_metric.protoBprotos/perfetto/metrics/android/android_blocking_calls_unagg.proto3protos/perfetto/metrics/android/codec_metrics.proto0protos/perfetto/metrics/android/cpu_metric.proto3protos/perfetto/metrics/android/camera_metric.proto9protos/perfetto/metrics/android/camera_unagg_metric.proto5protos/perfetto/metrics/android/display_metrics.proto5protos/perfetto/metrics/android/dma_heap_metric.proto1protos/perfetto/metrics/android/dvfs_metric.proto4protos/perfetto/metrics/android/fastrpc_metric.proto0protos/perfetto/metrics/android/g2d_metric.proto0protos/perfetto/metrics/android/gpu_metric.proto0protos/perfetto/metrics/android/hwcomposer.proto1protos/perfetto/metrics/android/hwui_metric.proto/protos/perfetto/metrics/android/io_metric.proto5protos/perfetto/metrics/android/io_unagg_metric.proto0protos/perfetto/metrics/android/ion_metric.proto8protos/perfetto/metrics/android/irq_runtime_metric.proto5protos/perfetto/metrics/android/jank_cuj_metric.proto9protos/perfetto/metrics/android/java_heap_histogram.proto5protos/perfetto/metrics/android/java_heap_stats.proto0protos/perfetto/metrics/android/lmk_metric.proto7protos/perfetto/metrics/android/lmk_reason_metric.proto0protos/perfetto/metrics/android/mem_metric.proto6protos/perfetto/metrics/android/mem_unagg_metric.proto6protos/perfetto/metrics/android/multiuser_metric.proto4protos/perfetto/metrics/android/network_metric.proto2protos/perfetto/metrics/android/other_traces.proto2protos/perfetto/metrics/android/package_list.proto5protos/perfetto/metrics/android/powrails_metric.proto4protos/perfetto/metrics/android/profiler_smaps.proto7protos/perfetto/metrics/android/rt_runtime_metric.proto0protos/perfetto/metrics/android/simpleperf.proto4protos/perfetto/metrics/android/startup_metric.proto4protos/perfetto/metrics/android/surfaceflinger.proto0protos/perfetto/metrics/android/task_names.proto3protos/perfetto/metrics/android/trace_quality.proto?protos/perfetto/metrics/android/android_trusty_workqueues.proto9protos/perfetto/metrics/android/unsymbolized_frames.proto3protos/perfetto/metrics/android/binder_metric.proto?protos/perfetto/metrics/android/monitor_contention_metric.protoCprotos/perfetto/metrics/android/monitor_contention_agg_metric.proto?protos/perfetto/metrics/android/app_process_starts_metric.protoAprotos/perfetto/metrics/android/android_oom_adjuster_metric.proto"î
TraceMetadata*
trace_duration_ns (RtraceDurationNs
@@ -1234,7 +1284,7 @@ trace_uuid ( R traceUuid:
Source
SOURCE_UNKNOWN
SOURCE_TRACE
-SOURCE_ANALYSIS"·'
+SOURCE_ANALYSIS"”(
TraceMetricsH
android_batt ( 2%.perfetto.protos.AndroidBatteryMetricR androidBattB
android_cpu ( 2!.perfetto.protos.AndroidCpuMetricR
@@ -1299,5 +1349,6 @@ androidAnrw
android_boot_unagg> ( 2!.perfetto.protos.AndroidBootUnaggRandroidBootUnagg}
android_garbage_collection_unagg? ( 24.perfetto.protos.AndroidGarbageCollectionUnaggMetricRandroidGarbageCollectionUnagg]
android_auto_multiuser@ ( 2'.perfetto.protos.AndroidMultiuserMetricRandroidAutoMultiuserk
-android_blocking_calls_unaggA ( 2*.perfetto.protos.AndroidBlockingCallsUnaggRandroidBlockingCallsUnagg*Âô*ôé*éÑ*ÑÅJJ
+android_blocking_calls_unaggA ( 2*.perfetto.protos.AndroidBlockingCallsUnaggRandroidBlockingCallsUnagg[
+android_oom_adjusterB ( 2).perfetto.protos.AndroidOomAdjusterMetricRandroidOomAdjuster*Âô*ôé*éÑ*ÑÅJJ
 J JJJJJJ \ No newline at end of file
diff --git a/src/protozero/protoc_plugin/protozero_c_plugin.cc b/src/protozero/protoc_plugin/protozero_c_plugin.cc
index 5bd05dd57..804968eef 100644
--- a/src/protozero/protoc_plugin/protozero_c_plugin.cc
+++ b/src/protozero/protoc_plugin/protozero_c_plugin.cc
@@ -258,8 +258,9 @@ class GeneratorJob {
for (int i = 0; i < source_->enum_type_count(); ++i)
enums_.push_back(source_->enum_type(i));
- if (source_->extension_count() > 0)
- Abort("top-level extension blocks are not supported");
+ if (source_->extension_count() > 0) {
+ // TODO(b/336524288): emit field numbers
+ }
for (const Descriptor* message : messages_) {
for (int i = 0; i < message->enum_type_count(); ++i) {
@@ -293,7 +294,6 @@ class GeneratorJob {
while (!stack.empty()) {
const FileDescriptor* imp = stack.back();
stack.pop_back();
-
for (int i = 0; i < imp->public_dependency_count(); ++i) {
stack.push_back(imp->public_dependency(i));
}
diff --git a/src/protozero/protoc_plugin/protozero_plugin.cc b/src/protozero/protoc_plugin/protozero_plugin.cc
index 5b082920c..26f311004 100644
--- a/src/protozero/protoc_plugin/protozero_plugin.cc
+++ b/src/protozero/protoc_plugin/protozero_plugin.cc
@@ -371,6 +371,12 @@ class GeneratorJob {
// name of this message is used to group them.
std::string extension_name = extension->extension_scope()->name();
extensions_[extension_name].push_back(extension);
+
+ if (extension->message_type()) {
+ // Emit a forward declaration of nested message types, as the outer
+ // class will refer to them when creating type aliases.
+ referenced_messages_.insert(extension->message_type());
+ }
}
} else {
messages_.push_back(message);
@@ -387,8 +393,9 @@ class GeneratorJob {
for (int i = 0; i < source_->enum_type_count(); ++i)
enums_.push_back(source_->enum_type(i));
- if (source_->extension_count() > 0)
- Abort("top-level extension blocks are not supported");
+ if (source_->extension_count() > 0) {
+ // TODO(b/336524288): emit field numbers
+ }
for (const Descriptor* message : messages_) {
for (int i = 0; i < message->enum_type_count(); ++i) {
@@ -755,6 +762,15 @@ case $full_class$::$value_name$:
if (field->is_repeated() && !field->is_packed())
has_nonpacked_repeated_fields = true;
}
+ // Iterate over all fields in "extend" blocks.
+ for (int i = 0; i < message->extension_range_count(); ++i) {
+ Descriptor::ExtensionRange::Proto range;
+ message->extension_range(i)->CopyTo(&range);
+ int candidate = range.end() - 1;
+ if (candidate > kMaxDecoderFieldId)
+ continue;
+ max_field_id = std::max(max_field_id, candidate);
+ }
std::string class_name = GetCppClassName(message) + "_Decoder";
stub_h_->Print(
@@ -880,7 +896,8 @@ case $full_class$::$value_name$:
}
void GenerateConstantsForMessageFields(const Descriptor* message) {
- const bool has_fields = (message->field_count() > 0);
+ const bool has_fields =
+ message->field_count() > 0 || message->extension_count() > 0;
// Field number constants.
if (has_fields) {
@@ -893,6 +910,15 @@ case $full_class$::$value_name$:
GetFieldNumberConstant(field), "id",
std::to_string(field->number()));
}
+
+ for (int i = 0; i < message->extension_count(); ++i) {
+ const FieldDescriptor* field = message->extension(i);
+
+ stub_h_->Print("$name$ = $id$,\n", "name",
+ GetFieldNumberConstant(field), "id",
+ std::to_string(field->number()));
+ }
+
stub_h_->Outdent();
stub_h_->Print("};\n");
}
@@ -1049,6 +1075,20 @@ static constexpr $field_metadata_type$ $field_metadata_var${};
}
GenerateFieldDescriptor(extension_name, field);
}
+
+ if (!descriptors.empty()) {
+ stub_h_->Print("enum : int32_t {\n");
+ stub_h_->Indent();
+
+ for (const FieldDescriptor* field : descriptors) {
+ stub_h_->Print("$name$ = $id$,\n", "name",
+ GetFieldNumberConstant(field), "id",
+ std::to_string(field->number()));
+ }
+ stub_h_->Outdent();
+ stub_h_->Print("};\n");
+ }
+
stub_h_->Outdent();
stub_h_->Print("};\n");
}
diff --git a/src/trace_processor/containers/BUILD.gn b/src/trace_processor/containers/BUILD.gn
index 43e9603b6..c724df867 100644
--- a/src/trace_processor/containers/BUILD.gn
+++ b/src/trace_processor/containers/BUILD.gn
@@ -22,6 +22,7 @@ import("../../../gn/test.gni")
perfetto_component("containers") {
public = [
"bit_vector.h",
+ "implicit_segment_forest.h",
"null_term_string_view.h",
"row_map.h",
"row_map_algorithms.h",
@@ -44,6 +45,7 @@ perfetto_unittest_source_set("unittests") {
testonly = true
sources = [
"bit_vector_unittest.cc",
+ "implicit_segment_forest_unittest.cc",
"null_term_string_view_unittest.cc",
"row_map_unittest.cc",
"string_pool_unittest.cc",
diff --git a/src/trace_processor/containers/implicit_segment_forest.h b/src/trace_processor/containers/implicit_segment_forest.h
new file mode 100644
index 000000000..547abc660
--- /dev/null
+++ b/src/trace_processor/containers/implicit_segment_forest.h
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_CONTAINERS_IMPLICIT_SEGMENT_FOREST_H_
+#define SRC_TRACE_PROCESSOR_CONTAINERS_IMPLICIT_SEGMENT_FOREST_H_
+
+#include <cstddef>
+#include <cstdint>
+#include <vector>
+
+#include "perfetto/base/logging.h"
+
+namespace perfetto::trace_processor {
+
+// An implementation of a segment tree data structure [1] with:
+// 1) parent-child relationships are implicit, saving memory.
+// 2) the requirement for the number of values being a power of two, turning
+// the tree into a forest.
+//
+// Segment trees are a very powerful data structure allowing O(log(n)) aggregate
+// queries to be performed on an arbitrary range of elements in an array.
+// Specifically, for `T x[n]`, and an associative and commutative operation
+// AggOp (e.g. +, *, min, max, etc.), segment trees can compute
+// ```
+// T y = AggOp()(x[i], x[i + 1], x[i + 2], ..., x[j])
+// ```
+// in O(log(n)) time.
+//
+// Practically, in trace processor, this is useful for computing aggregations
+// over events in a trace. For example:
+// ```
+// struct Slice { int64_t ts; int64_t dur; };
+// struct MaxDurSlice {
+// Slice operator()(const Slice& a, const Slice& b) {
+// return a.dur < b.dur ? b : a;
+// }
+// }
+// using MipMap = ImplicitSegmentForest<Slice, MaxDurSlice>;
+// ```
+// allows building a "mipmap" [2] of a track in a trace in a UI. The UI can show
+// a representation of the items in the track when very zoomed out while
+// skipping the rendering slices which are smaller than one pixel.
+//
+// The design and implementation of this class takes heavy inspiration from
+// Tristan Hume's "IForestIndex" data structure [3] as described in his blog
+// post [4].
+//
+// [1] https://en.algorithmica.org/hpc/data-structures/segment-trees/
+// [2] https://en.wikipedia.org/wiki/Mipmap
+// [3]
+// https://github.com/trishume/gigatrace/blob/dfde0d7244f356bdc9aeefb387d904dd8b09d94a/src/iforest.rs
+// [4] https://thume.ca/2021/03/14/iforests/
+template <typename T, typename AggOp>
+class ImplicitSegmentForest {
+ public:
+ // Computes the aggregation (as specified by operator() in AggOp) over all
+ // elements in the tree between the indices [start, end). Requires that
+ // start < end.
+ //
+ // Complexity:
+ // This function performs O(log(n)) operations (n = end - start).
+ //
+ // Returns:
+ // 1) values[start]: if start + 1 == end
+ // 2) AggOp()(values[start], ..., values[end - 1]) otherwise
+ T Query(uint32_t start, uint32_t end) const {
+ PERFETTO_DCHECK(start < end);
+
+ const uint32_t in_start = start * 2;
+ const uint32_t in_end = end * 2;
+
+ uint32_t first_skip = LargestPrefixInsideSkip(in_start, in_end);
+ T aggregated = values_[AggNode(in_start, first_skip)];
+ for (uint32_t i = in_start + first_skip; i < in_end;) {
+ uint32_t skip = LargestPrefixInsideSkip(i, in_end);
+ aggregated = AggOp()(aggregated, values_[AggNode(i, skip)]);
+ i += skip;
+ }
+ return aggregated;
+ }
+
+ // Pushes a new element to right-most part of the tree. This index of this
+ // element can be used in future calls to |Query|.
+ void Push(T v) {
+ values_.emplace_back(std::move(v));
+
+ size_t len = values_.size();
+ auto levels_to_index = static_cast<uint32_t>(__builtin_ctzl(
+ static_cast<unsigned long>(~len))) -
+ 1;
+
+ size_t cur = len - 1;
+ for (uint32_t level = 0; level < levels_to_index; ++level) {
+ size_t prev_higher_level = cur - (1 << level);
+ values_[prev_higher_level] =
+ AggOp()(values_[prev_higher_level], values_[cur]);
+ cur = prev_higher_level;
+ }
+ values_.emplace_back(values_[len - (1 << levels_to_index)]);
+ }
+
+ // Returns the value at |n| in the tree: this corresponds to the |n|th
+ // element |Push|-ed into the tree.
+ const T& operator[](uint32_t n) { return values_[n * 2]; }
+
+ // Returns the number of elements pushed into the forest.
+ uint32_t size() const { return static_cast<uint32_t>(values_.size() / 2); }
+
+ private:
+ static uint32_t Lsp(uint32_t x) { return x & -x; }
+ static uint32_t Msp(uint32_t x) {
+ return (1u << (sizeof(x) * 8 - 1)) >> __builtin_clz(x);
+ }
+ static uint32_t LargestPrefixInsideSkip(uint32_t min, uint32_t max) {
+ return Lsp(min | Msp(max - min));
+ }
+ static uint32_t AggNode(uint32_t i, uint32_t offset) {
+ return i + (offset >> 1) - 1;
+ }
+
+ std::vector<T> values_;
+};
+
+} // namespace perfetto::trace_processor
+
+#endif // SRC_TRACE_PROCESSOR_CONTAINERS_IMPLICIT_SEGMENT_FOREST_H_
diff --git a/src/trace_processor/containers/implicit_segment_forest_unittest.cc b/src/trace_processor/containers/implicit_segment_forest_unittest.cc
new file mode 100644
index 000000000..16dd2629c
--- /dev/null
+++ b/src/trace_processor/containers/implicit_segment_forest_unittest.cc
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/trace_processor/containers/implicit_segment_forest.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <numeric>
+#include <random>
+#include <vector>
+
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto::trace_processor {
+namespace {
+
+struct Value {
+ uint32_t value;
+};
+
+struct Sum {
+ Value operator()(const Value& a, const Value& b) {
+ return Value{a.value + b.value};
+ }
+};
+
+TEST(ImplicitSegmentTree, SimpleSum) {
+ std::vector<uint32_t> res = {209, 330, 901, 3, 10, 0, 3903, 309, 490};
+
+ ImplicitSegmentForest<Value, Sum> forest;
+ for (uint32_t x : res) {
+ forest.Push(Value{x});
+ }
+
+ for (uint32_t i = 0; i < res.size(); ++i) {
+ for (uint32_t j = i + 1; j < res.size(); ++j) {
+ ASSERT_EQ(forest.Query(i, j).value,
+ std::accumulate(res.begin() + i, res.begin() + j, 0u));
+ }
+ }
+}
+
+TEST(ImplicitSegmentTree, Stress) {
+ static constexpr size_t kCount = 9249;
+ std::minstd_rand0 rng(42);
+
+ std::vector<uint32_t> res;
+ ImplicitSegmentForest<Value, Sum> forest;
+ for (uint32_t i = 0; i < kCount; ++i) {
+ res.push_back(static_cast<uint32_t>(rng()));
+ forest.Push(Value{res.back()});
+ }
+
+ for (uint32_t i = 0; i < 10000; ++i) {
+ uint32_t s = rng() % kCount;
+ uint32_t e = s + 1 + (rng() % (kCount - s));
+ ASSERT_EQ(forest.Query(s, e).value,
+ std::accumulate(res.begin() + s, res.begin() + e, 0u));
+ }
+}
+
+} // namespace
+} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/db/column/BUILD.gn b/src/trace_processor/db/column/BUILD.gn
index 74d16110f..d53e7ae23 100644
--- a/src/trace_processor/db/column/BUILD.gn
+++ b/src/trace_processor/db/column/BUILD.gn
@@ -75,6 +75,7 @@ perfetto_unittest_source_set("unittests") {
sources = [
"arrangement_overlay_unittest.cc",
"dense_null_overlay_unittest.cc",
+ "fake_storage_unittest.cc",
"id_storage_unittest.cc",
"null_overlay_unittest.cc",
"numeric_storage_unittest.cc",
diff --git a/src/trace_processor/db/column/dense_null_overlay.cc b/src/trace_processor/db/column/dense_null_overlay.cc
index e171ec0fe..55003fae2 100644
--- a/src/trace_processor/db/column/dense_null_overlay.cc
+++ b/src/trace_processor/db/column/dense_null_overlay.cc
@@ -82,10 +82,10 @@ RangeOrBitVector DenseNullOverlay::ChainImpl::SearchValidated(FilterOp op,
case SearchValidationResult::kNoData: {
// There is no need to search in underlying storage. It's enough to
// intersect the |non_null_|.
- BitVector res = non_null_->IntersectRange(in.start, in.end);
- res.Not();
+ BitVector res = non_null_->Copy();
res.Resize(in.end, false);
- return RangeOrBitVector(std::move(res));
+ res.Not();
+ return RangeOrBitVector(res.IntersectRange(in.start, in.end));
}
case SearchValidationResult::kAllData:
return RangeOrBitVector(in);
diff --git a/src/trace_processor/db/column/fake_storage.cc b/src/trace_processor/db/column/fake_storage.cc
index f587c7787..babfb7c4d 100644
--- a/src/trace_processor/db/column/fake_storage.cc
+++ b/src/trace_processor/db/column/fake_storage.cc
@@ -41,6 +41,7 @@ FakeStorageChain::FakeStorageChain(uint32_t size,
SingleSearchResult FakeStorageChain::SingleSearch(FilterOp,
SqlValue,
uint32_t i) const {
+ PERFETTO_CHECK(i < size_);
switch (strategy_) {
case kAll:
return SingleSearchResult::kMatch;
@@ -115,37 +116,37 @@ Range FakeStorageChain::OrderedIndexSearchValidated(
FilterOp,
SqlValue,
const OrderedIndices& indices) const {
- if (strategy_ == kAll) {
- return {0, indices.size};
- }
-
- if (strategy_ == kNone) {
- return {};
- }
-
- if (strategy_ == kRange) {
- // We are looking at intersection of |range_| and |indices_|.
- const uint32_t* first_in_range = std::partition_point(
- indices.data, indices.data + indices.size,
- [this](uint32_t i) { return !range_.Contains(i); });
- const uint32_t* first_outside_range =
- std::partition_point(first_in_range, indices.data + indices.size,
- [this](uint32_t i) { return range_.Contains(i); });
- return {static_cast<uint32_t>(std::distance(indices.data, first_in_range)),
- static_cast<uint32_t>(
- std::distance(indices.data, first_outside_range))};
- }
-
- PERFETTO_DCHECK(strategy_ == kBitVector);
- // We are looking at intersection of |range_| and |bit_vector_|.
- const uint32_t* first_set = std::partition_point(
- indices.data, indices.data + indices.size,
- [this](uint32_t i) { return !bit_vector_.IsSet(i); });
- const uint32_t* first_non_set =
- std::partition_point(first_set, indices.data + indices.size,
- [this](uint32_t i) { return bit_vector_.IsSet(i); });
- return {static_cast<uint32_t>(std::distance(indices.data, first_set)),
+ switch (strategy_) {
+ case kAll:
+ return {0, indices.size};
+ case kNone:
+ return {};
+ case kRange: {
+ // We are looking at intersection of |range_| and |indices_|.
+ const uint32_t* first_in_range = std::partition_point(
+ indices.data, indices.data + indices.size,
+ [this](uint32_t i) { return !range_.Contains(i); });
+ const uint32_t* first_outside_range = std::partition_point(
+ first_in_range, indices.data + indices.size,
+ [this](uint32_t i) { return range_.Contains(i); });
+ return {
+ static_cast<uint32_t>(std::distance(indices.data, first_in_range)),
+ static_cast<uint32_t>(
+ std::distance(indices.data, first_outside_range))};
+ }
+ case kBitVector:
+ // We are looking at intersection of |range_| and |bit_vector_|.
+ const uint32_t* first_set = std::partition_point(
+ indices.data, indices.data + indices.size,
+ [this](uint32_t i) { return !bit_vector_.IsSet(i); });
+ const uint32_t* first_non_set = std::partition_point(
+ first_set, indices.data + indices.size,
+ [this](uint32_t i) { return bit_vector_.IsSet(i); });
+ return {
+ static_cast<uint32_t>(std::distance(indices.data, first_set)),
static_cast<uint32_t>(std::distance(indices.data, first_non_set))};
+ }
+ PERFETTO_FATAL("For GCC");
}
void FakeStorageChain::StableSort(SortToken*, SortToken*, SortDirection) const {
diff --git a/src/trace_processor/db/column/fake_storage_unittest.cc b/src/trace_processor/db/column/fake_storage_unittest.cc
new file mode 100644
index 000000000..0bc02119e
--- /dev/null
+++ b/src/trace_processor/db/column/fake_storage_unittest.cc
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "src/trace_processor/db/column/fake_storage.h"
+
+#include <cstdint>
+#include <limits>
+#include <vector>
+
+#include "perfetto/trace_processor/basic_types.h"
+#include "src/trace_processor/containers/bit_vector.h"
+#include "src/trace_processor/db/column/data_layer.h"
+#include "src/trace_processor/db/column/types.h"
+#include "src/trace_processor/db/column/utils.h"
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto::trace_processor {
+
+inline bool operator==(const Range& a, const Range& b) {
+ return std::tie(a.start, a.end) == std::tie(b.start, b.end);
+}
+
+inline bool operator==(const BitVector& a, const BitVector& b) {
+ return a.size() == b.size() && a.CountSetBits() == b.CountSetBits();
+}
+
+namespace column {
+namespace {
+
+using testing::ElementsAre;
+using testing::IsEmpty;
+
+using Indices = DataLayerChain::Indices;
+using OrderedIndices = DataLayerChain::OrderedIndices;
+
+TEST(FakeStorage, ValidateSearchConstraints) {
+ {
+ // All passes
+ auto fake = FakeStorageChain::SearchAll(10);
+ EXPECT_EQ(fake->ValidateSearchConstraints(FilterOp::kEq, SqlValue()),
+ SearchValidationResult::kOk);
+ }
+ {
+ // None passes
+ auto fake = FakeStorageChain::SearchNone(10);
+ EXPECT_EQ(fake->ValidateSearchConstraints(FilterOp::kEq, SqlValue()),
+ SearchValidationResult::kOk);
+ }
+ {
+ // Index vector
+ auto fake =
+ FakeStorageChain::SearchSubset(5, std::vector<uint32_t>{1, 2, 3, 4, 5});
+ EXPECT_EQ(fake->ValidateSearchConstraints(FilterOp::kEq, SqlValue()),
+ SearchValidationResult::kOk);
+ }
+ {
+ // BitVector
+ auto fake = FakeStorageChain::SearchSubset(5, BitVector{0, 1, 0, 1, 0});
+ EXPECT_EQ(fake->ValidateSearchConstraints(FilterOp::kEq, SqlValue()),
+ SearchValidationResult::kOk);
+ }
+ {
+ // Range
+ auto fake = FakeStorageChain::SearchSubset(5, Range(1, 4));
+ EXPECT_EQ(fake->ValidateSearchConstraints(FilterOp::kEq, SqlValue()),
+ SearchValidationResult::kOk);
+ }
+}
+
+TEST(FakeStorage, SingleSearch) {
+ {
+ // All passes
+ auto fake = FakeStorageChain::SearchAll(10);
+ EXPECT_EQ(fake->SingleSearch(FilterOp::kEq, SqlValue(), 5u),
+ SingleSearchResult::kMatch);
+ }
+ {
+ // None passes
+ auto fake = FakeStorageChain::SearchNone(10);
+ EXPECT_EQ(fake->SingleSearch(FilterOp::kEq, SqlValue(), 5u),
+ SingleSearchResult::kNoMatch);
+ }
+ {
+ // Index vector
+ auto fake =
+ FakeStorageChain::SearchSubset(5, std::vector<uint32_t>{1, 2, 3, 4, 5});
+ EXPECT_EQ(fake->SingleSearch(FilterOp::kEq, SqlValue(), 0u),
+ SingleSearchResult::kNoMatch);
+ EXPECT_EQ(fake->SingleSearch(FilterOp::kEq, SqlValue(), 1u),
+ SingleSearchResult::kMatch);
+ }
+ {
+ // BitVector
+ auto fake = FakeStorageChain::SearchSubset(5, BitVector{0, 1, 0, 1, 0});
+ EXPECT_EQ(fake->SingleSearch(FilterOp::kEq, SqlValue(), 0),
+ SingleSearchResult::kNoMatch);
+ EXPECT_EQ(fake->SingleSearch(FilterOp::kEq, SqlValue(), 1u),
+ SingleSearchResult::kMatch);
+ }
+ {
+ // Range
+ auto fake = FakeStorageChain::SearchSubset(5, Range(1, 4));
+ EXPECT_EQ(fake->SingleSearch(FilterOp::kEq, SqlValue(), 0),
+ SingleSearchResult::kNoMatch);
+ EXPECT_EQ(fake->SingleSearch(FilterOp::kEq, SqlValue(), 1u),
+ SingleSearchResult::kMatch);
+ }
+}
+
+TEST(FakeStorage, IndexSearchValidated) {
+ {
+ // All passes
+ Indices indices = Indices::CreateWithIndexPayloadForTesting(
+ {1u, 0u, 3u}, Indices::State::kNonmonotonic);
+ auto fake = FakeStorageChain::SearchAll(5);
+ fake->IndexSearch(FilterOp::kGe, SqlValue::Long(0u), indices);
+ ASSERT_THAT(utils::ExtractPayloadForTesting(indices), ElementsAre(0, 1, 2));
+ }
+ {
+ // None passes
+ Indices indices = Indices::CreateWithIndexPayloadForTesting(
+ {1u, 0u, 3u}, Indices::State::kNonmonotonic);
+ auto fake = FakeStorageChain::SearchNone(5);
+ fake->IndexSearch(FilterOp::kGe, SqlValue::Long(0u), indices);
+ EXPECT_TRUE(utils::ExtractPayloadForTesting(indices).empty());
+ }
+ {
+ // BitVector
+ Indices indices = Indices::CreateWithIndexPayloadForTesting(
+ {1u, 0u, 3u}, Indices::State::kNonmonotonic);
+ auto fake = FakeStorageChain::SearchSubset(5, BitVector{0, 1, 0, 1, 0});
+ fake->IndexSearch(FilterOp::kGe, SqlValue::Long(0u), indices);
+ ASSERT_THAT(utils::ExtractPayloadForTesting(indices), ElementsAre(0, 2));
+ }
+ {
+ // Index vector
+ Indices indices = Indices::CreateWithIndexPayloadForTesting(
+ {1u, 0u, 3u}, Indices::State::kNonmonotonic);
+ auto fake =
+ FakeStorageChain::SearchSubset(5, std::vector<uint32_t>{1, 2, 3});
+ fake->IndexSearch(FilterOp::kGe, SqlValue::Long(0u), indices);
+ ASSERT_THAT(utils::ExtractPayloadForTesting(indices), ElementsAre(0, 2));
+ }
+ {
+ // Range
+ Indices indices = Indices::CreateWithIndexPayloadForTesting(
+ {1u, 0u, 3u}, Indices::State::kNonmonotonic);
+ auto fake = FakeStorageChain::SearchSubset(5, Range(1, 4));
+ fake->IndexSearch(FilterOp::kGe, SqlValue::Long(0u), indices);
+ ASSERT_THAT(utils::ExtractPayloadForTesting(indices), ElementsAre(0, 2));
+ }
+}
+
+TEST(FakeStorage, OrderedIndexSearchValidated) {
+ std::vector<uint32_t> table_idx{4, 3, 2, 1};
+ OrderedIndices indices{table_idx.data(), uint32_t(table_idx.size()),
+ Indices::State::kNonmonotonic};
+ {
+ // All passes
+ auto fake = FakeStorageChain::SearchAll(5);
+ Range ret =
+ fake->OrderedIndexSearch(FilterOp::kGe, SqlValue::Long(0u), indices);
+ EXPECT_EQ(ret, Range(0, 4));
+ }
+ {
+ // None passes
+ auto fake = FakeStorageChain::SearchNone(5);
+ Range ret =
+ fake->OrderedIndexSearch(FilterOp::kGe, SqlValue::Long(0u), indices);
+ EXPECT_EQ(ret, Range(0, 0));
+ }
+ {
+ // BitVector
+ auto fake = FakeStorageChain::SearchSubset(5, BitVector{0, 0, 1, 1, 1});
+ Range ret =
+ fake->OrderedIndexSearch(FilterOp::kGe, SqlValue::Long(0u), indices);
+ EXPECT_EQ(ret, Range(0, 3));
+ }
+ {
+ // Index vector
+ auto fake =
+ FakeStorageChain::SearchSubset(5, std::vector<uint32_t>{1, 2, 3});
+ Range ret =
+ fake->OrderedIndexSearch(FilterOp::kGe, SqlValue::Long(0u), indices);
+ EXPECT_EQ(ret, Range(1, 4));
+ }
+ {
+ // Range
+ auto fake = FakeStorageChain::SearchSubset(5, Range(1, 4));
+ Range ret =
+ fake->OrderedIndexSearch(FilterOp::kGe, SqlValue::Long(0u), indices);
+ EXPECT_EQ(ret, Range(1, 4));
+ }
+}
+
+} // namespace
+} // namespace column
+} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/db/column/null_overlay.cc b/src/trace_processor/db/column/null_overlay.cc
index fa1e679e0..b1f8f4571 100644
--- a/src/trace_processor/db/column/null_overlay.cc
+++ b/src/trace_processor/db/column/null_overlay.cc
@@ -129,10 +129,10 @@ RangeOrBitVector NullOverlay::ChainImpl::SearchValidated(FilterOp op,
case SearchValidationResult::kNoData: {
// There is no need to search in underlying storage. It's enough to
// intersect the |non_null_|.
- BitVector res = non_null_->IntersectRange(in.start, in.end);
- res.Not();
+ BitVector res = non_null_->Copy();
res.Resize(in.end, false);
- return RangeOrBitVector(std::move(res));
+ res.Not();
+ return RangeOrBitVector(res.IntersectRange(in.start, in.end));
}
case SearchValidationResult::kAllData:
return RangeOrBitVector(in);
diff --git a/src/trace_processor/db/column/numeric_storage.h b/src/trace_processor/db/column/numeric_storage.h
index f7cb51ca1..821fd4075 100644
--- a/src/trace_processor/db/column/numeric_storage.h
+++ b/src/trace_processor/db/column/numeric_storage.h
@@ -100,21 +100,7 @@ class NumericStorage final : public NumericStorageBase {
SingleSearchResult SingleSearch(FilterOp op,
SqlValue sql_val,
uint32_t i) const override {
- if constexpr (std::is_same_v<T, double>) {
- if (sql_val.type != SqlValue::kDouble) {
- return SingleSearchResult::kNeedsFullSearch;
- }
- return utils::SingleSearchNumeric(op, (*vector_)[i],
- sql_val.double_value);
- } else {
- if (sql_val.type != SqlValue::kLong ||
- sql_val.long_value > std::numeric_limits<T>::max() ||
- sql_val.long_value < std::numeric_limits<T>::min()) {
- return SingleSearchResult::kNeedsFullSearch;
- }
- return utils::SingleSearchNumeric(op, (*vector_)[i],
- static_cast<T>(sql_val.long_value));
- }
+ return utils::SingleSearchNumeric(op, (*vector_)[i], sql_val);
}
void StableSort(SortToken* start,
diff --git a/src/trace_processor/db/column/numeric_storage_unittest.cc b/src/trace_processor/db/column/numeric_storage_unittest.cc
index 2b20306da..46ef9202f 100644
--- a/src/trace_processor/db/column/numeric_storage_unittest.cc
+++ b/src/trace_processor/db/column/numeric_storage_unittest.cc
@@ -187,6 +187,11 @@ TEST(NumericStorage, SingleSearch) {
SingleSearchResult::kMatch);
ASSERT_EQ(chain->SingleSearch(FilterOp::kGe, SqlValue::Long(0), 5),
SingleSearchResult::kNoMatch);
+
+ ASSERT_EQ(chain->SingleSearch(FilterOp::kIsNull, SqlValue(), 0),
+ SingleSearchResult::kNoMatch);
+ ASSERT_EQ(chain->SingleSearch(FilterOp::kIsNotNull, SqlValue(), 0),
+ SingleSearchResult::kMatch);
}
TEST(NumericStorage, Search) {
diff --git a/src/trace_processor/db/column/range_overlay_unittest.cc b/src/trace_processor/db/column/range_overlay_unittest.cc
index a965be36c..240e998ce 100644
--- a/src/trace_processor/db/column/range_overlay_unittest.cc
+++ b/src/trace_processor/db/column/range_overlay_unittest.cc
@@ -95,14 +95,17 @@ TEST(RangeOverlay, SearchBitVector) {
TEST(RangeOverlay, IndexSearch) {
auto fake =
FakeStorageChain::SearchSubset(8, BitVector({0, 1, 0, 1, 0, 1, 0, 0}));
+
+ // {true, false}
Range range(3, 5);
RangeOverlay storage(&range);
auto chain = storage.MakeChain(std::move(fake));
+ // {true, false, true}
Indices indices = Indices::CreateWithIndexPayloadForTesting(
- {1u, 0u, 3u}, Indices::State::kNonmonotonic);
+ {0, 1, 0}, Indices::State::kNonmonotonic);
chain->IndexSearch(FilterOp::kGe, SqlValue::Long(0u), indices);
- ASSERT_THAT(utils::ExtractPayloadForTesting(indices), ElementsAre(1u));
+ ASSERT_THAT(utils::ExtractPayloadForTesting(indices), ElementsAre(0, 2));
}
TEST(RangeOverlay, StableSort) {
diff --git a/src/trace_processor/db/column/set_id_storage.cc b/src/trace_processor/db/column/set_id_storage.cc
index 6e4719f4d..ae1c167c2 100644
--- a/src/trace_processor/db/column/set_id_storage.cc
+++ b/src/trace_processor/db/column/set_id_storage.cc
@@ -72,15 +72,7 @@ SetIdStorage::ChainImpl::ChainImpl(const std::vector<uint32_t>* values)
SingleSearchResult SetIdStorage::ChainImpl::SingleSearch(FilterOp op,
SqlValue sql_val,
uint32_t i) const {
- if (sql_val.type != SqlValue::kLong ||
- sql_val.long_value > std::numeric_limits<uint32_t>::max() ||
- sql_val.long_value < std::numeric_limits<uint32_t>::min()) {
- // Because of the large amount of code needing for handling comparisions
- // with doubles or out of range values, just defer to the full search.
- return SingleSearchResult::kNeedsFullSearch;
- }
- return utils::SingleSearchNumeric(op, (*values_)[i],
- static_cast<uint32_t>(sql_val.long_value));
+ return utils::SingleSearchNumeric(op, (*values_)[i], sql_val);
}
SearchValidationResult SetIdStorage::ChainImpl::ValidateSearchConstraints(
diff --git a/src/trace_processor/db/column/types.h b/src/trace_processor/db/column/types.h
index b59084be4..6ec11fa7e 100644
--- a/src/trace_processor/db/column/types.h
+++ b/src/trace_processor/db/column/types.h
@@ -96,6 +96,15 @@ struct Order {
bool desc;
};
+// Structured data used to determine what Trace Processor will query using
+// CEngine.
+struct Query {
+ // Query constraints.
+ std::vector<Constraint> constraints;
+ // Query order bys.
+ std::vector<Order> orders;
+};
+
// The enum type of the column.
// Public only to stop GCC complaining about templates being defined in a
// non-namespace scope (see ColumnTypeHelper below).
diff --git a/src/trace_processor/db/column/utils.h b/src/trace_processor/db/column/utils.h
index 14c645653..9533d8cd7 100644
--- a/src/trace_processor/db/column/utils.h
+++ b/src/trace_processor/db/column/utils.h
@@ -19,7 +19,9 @@
#include <algorithm>
#include <cstdint>
#include <functional>
+#include <limits>
#include <optional>
+#include <type_traits>
#include <vector>
#include "perfetto/base/logging.h"
@@ -29,6 +31,36 @@
#include "src/trace_processor/db/column/types.h"
namespace perfetto::trace_processor::column::utils {
+namespace internal {
+
+template <typename T, typename Comparator>
+SingleSearchResult SingleSearchNumeric(T left, const SqlValue& right_v) {
+ if constexpr (std::is_same_v<T, double>) {
+ if (right_v.type != SqlValue::kDouble) {
+ // Because of the large amount of code needing for handling comparisons
+ // with integers, just defer to the full search.
+ return SingleSearchResult::kNeedsFullSearch;
+ }
+ return Comparator()(left, right_v.double_value)
+ ? SingleSearchResult::kMatch
+ : SingleSearchResult::kNoMatch;
+ } else if constexpr (std::is_integral_v<T>) {
+ if (right_v.type != SqlValue::kLong ||
+ right_v.long_value > std::numeric_limits<T>::max() ||
+ right_v.long_value < std::numeric_limits<T>::min()) {
+ // Because of the large amount of code needing for handling comparisons
+ // with doubles or out of range values, just defer to the full search.
+ return SingleSearchResult::kNeedsFullSearch;
+ }
+ return Comparator()(left, static_cast<T>(right_v.long_value))
+ ? SingleSearchResult::kMatch
+ : SingleSearchResult::kNoMatch;
+ } else {
+ static_assert(std::is_same_v<T, void>, "Illegal type");
+ }
+}
+
+} // namespace internal
template <typename Comparator, typename ValType, typename DataType>
void LinearSearchWithComparator(ValType val,
@@ -77,27 +109,25 @@ void IndexSearchWithComparator(ValType val,
}
template <typename T>
-SingleSearchResult SingleSearchNumeric(FilterOp op, T left, T right) {
+SingleSearchResult SingleSearchNumeric(FilterOp op,
+ T left,
+ const SqlValue& right_v) {
switch (op) {
case FilterOp::kEq:
- return std::equal_to<T>()(left, right) ? SingleSearchResult::kMatch
- : SingleSearchResult::kNoMatch;
+ return internal::SingleSearchNumeric<T, std::equal_to<T>>(left, right_v);
case FilterOp::kNe:
- return std::not_equal_to<T>()(left, right) ? SingleSearchResult::kMatch
- : SingleSearchResult::kNoMatch;
+ return internal::SingleSearchNumeric<T, std::not_equal_to<T>>(left,
+ right_v);
case FilterOp::kGe:
- return std::greater_equal<T>()(left, right)
- ? SingleSearchResult::kMatch
- : SingleSearchResult::kNoMatch;
+ return internal::SingleSearchNumeric<T, std::greater_equal<T>>(left,
+ right_v);
case FilterOp::kGt:
- return std::greater<T>()(left, right) ? SingleSearchResult::kMatch
- : SingleSearchResult::kNoMatch;
+ return internal::SingleSearchNumeric<T, std::greater<T>>(left, right_v);
case FilterOp::kLe:
- return std::less_equal<T>()(left, right) ? SingleSearchResult::kMatch
- : SingleSearchResult::kNoMatch;
+ return internal::SingleSearchNumeric<T, std::less_equal<T>>(left,
+ right_v);
case FilterOp::kLt:
- return std::less<T>()(left, right) ? SingleSearchResult::kMatch
- : SingleSearchResult::kNoMatch;
+ return internal::SingleSearchNumeric<T, std::less<T>>(left, right_v);
case FilterOp::kIsNotNull:
return SingleSearchResult::kMatch;
case FilterOp::kGlob:
diff --git a/src/trace_processor/db/query_executor_benchmark.cc b/src/trace_processor/db/query_executor_benchmark.cc
index 8502e040c..35f743a02 100644
--- a/src/trace_processor/db/query_executor_benchmark.cc
+++ b/src/trace_processor/db/query_executor_benchmark.cc
@@ -234,15 +234,17 @@ struct HeapGraphObjectTableForBenchmark {
void BenchmarkSliceTableFilter(benchmark::State& state,
SliceTableForBenchmark& table,
std::initializer_list<Constraint> c) {
+ Query q;
+ q.constraints = c;
for (auto _ : state) {
- benchmark::DoNotOptimize(table.table_.QueryToRowMap(c, {}));
+ benchmark::DoNotOptimize(table.table_.QueryToRowMap(q));
}
state.counters["s/row"] =
benchmark::Counter(static_cast<double>(table.table_.row_count()),
benchmark::Counter::kIsIterationInvariantRate |
benchmark::Counter::kInvert);
state.counters["s/out"] = benchmark::Counter(
- static_cast<double>(table.table_.QueryToRowMap(c, {}).size()),
+ static_cast<double>(table.table_.QueryToRowMap(q).size()),
benchmark::Counter::kIsIterationInvariantRate |
benchmark::Counter::kInvert);
}
@@ -263,15 +265,17 @@ void BenchmarkExpectedFrameTableFilter(
benchmark::State& state,
ExpectedFrameTimelineTableForBenchmark& table,
Constraint c) {
+ Query q;
+ q.constraints = {c};
for (auto _ : state) {
- benchmark::DoNotOptimize(table.table_.QueryToRowMap({c}, {}));
+ benchmark::DoNotOptimize(table.table_.QueryToRowMap(q));
}
state.counters["s/row"] =
benchmark::Counter(static_cast<double>(table.table_.row_count()),
benchmark::Counter::kIsIterationInvariantRate |
benchmark::Counter::kInvert);
state.counters["s/out"] = benchmark::Counter(
- static_cast<double>(table.table_.QueryToRowMap({c}, {}).size()),
+ static_cast<double>(table.table_.QueryToRowMap(q).size()),
benchmark::Counter::kIsIterationInvariantRate |
benchmark::Counter::kInvert);
}
@@ -279,15 +283,17 @@ void BenchmarkExpectedFrameTableFilter(
void BenchmarkFtraceEventTableFilter(benchmark::State& state,
FtraceEventTableForBenchmark& table,
std::initializer_list<Constraint> c) {
+ Query q;
+ q.constraints = c;
for (auto _ : state) {
- benchmark::DoNotOptimize(table.table_.QueryToRowMap(c, {}));
+ benchmark::DoNotOptimize(table.table_.QueryToRowMap(q));
}
state.counters["s/row"] =
benchmark::Counter(static_cast<double>(table.table_.row_count()),
benchmark::Counter::kIsIterationInvariantRate |
benchmark::Counter::kInvert);
state.counters["s/out"] = benchmark::Counter(
- static_cast<double>(table.table_.QueryToRowMap({c}, {}).size()),
+ static_cast<double>(table.table_.QueryToRowMap(q).size()),
benchmark::Counter::kIsIterationInvariantRate |
benchmark::Counter::kInvert);
}
@@ -424,15 +430,17 @@ void BM_QEFilterWithArrangement(benchmark::State& state) {
Constraint c{table.table_.track_id().index_in_table(), FilterOp::kGt,
SqlValue::Long(10)};
+ Query q;
+ q.constraints = {c};
for (auto _ : state) {
- benchmark::DoNotOptimize(slice_sorted_with_duration.QueryToRowMap({c}, {}));
+ benchmark::DoNotOptimize(slice_sorted_with_duration.QueryToRowMap(q));
}
state.counters["s/row"] = benchmark::Counter(
static_cast<double>(slice_sorted_with_duration.row_count()),
benchmark::Counter::kIsIterationInvariantRate |
benchmark::Counter::kInvert);
state.counters["s/out"] = benchmark::Counter(
- static_cast<double>(table.table_.QueryToRowMap({c}, {}).size()),
+ static_cast<double>(table.table_.QueryToRowMap(q).size()),
benchmark::Counter::kIsIterationInvariantRate |
benchmark::Counter::kInvert);
}
@@ -443,15 +451,17 @@ void BM_QEDenseNullFilter(benchmark::State& state) {
HeapGraphObjectTableForBenchmark table(state);
Constraint c{table.table_.reference_set_id().index_in_table(), FilterOp::kGt,
SqlValue::Long(1000)};
+ Query q;
+ q.constraints = {c};
for (auto _ : state) {
- benchmark::DoNotOptimize(table.table_.QueryToRowMap({c}, {}));
+ benchmark::DoNotOptimize(table.table_.QueryToRowMap(q));
}
state.counters["s/row"] =
benchmark::Counter(static_cast<double>(table.table_.row_count()),
benchmark::Counter::kIsIterationInvariantRate |
benchmark::Counter::kInvert);
state.counters["s/out"] = benchmark::Counter(
- static_cast<double>(table.table_.QueryToRowMap({c}, {}).size()),
+ static_cast<double>(table.table_.QueryToRowMap(q).size()),
benchmark::Counter::kIsIterationInvariantRate |
benchmark::Counter::kInvert);
}
@@ -461,15 +471,17 @@ void BM_QEDenseNullFilterIsNull(benchmark::State& state) {
HeapGraphObjectTableForBenchmark table(state);
Constraint c{table.table_.reference_set_id().index_in_table(),
FilterOp::kIsNull, SqlValue()};
+ Query q;
+ q.constraints = {c};
for (auto _ : state) {
- benchmark::DoNotOptimize(table.table_.QueryToRowMap({c}, {}));
+ benchmark::DoNotOptimize(table.table_.QueryToRowMap(q));
}
state.counters["s/row"] =
benchmark::Counter(static_cast<double>(table.table_.row_count()),
benchmark::Counter::kIsIterationInvariantRate |
benchmark::Counter::kInvert);
state.counters["s/out"] = benchmark::Counter(
- static_cast<double>(table.table_.QueryToRowMap({c}, {}).size()),
+ static_cast<double>(table.table_.QueryToRowMap(q).size()),
benchmark::Counter::kIsIterationInvariantRate |
benchmark::Counter::kInvert);
}
@@ -500,15 +512,17 @@ void BM_QEFilterOrderedArrangement(benchmark::State& state) {
Constraint c{table.table_.dur().index_in_table(), FilterOp::kGt,
SqlValue::Long(10)};
+ Query q;
+ q.constraints = {c};
for (auto _ : state) {
- benchmark::DoNotOptimize(slice_sorted_with_duration.QueryToRowMap({c}, {}));
+ benchmark::DoNotOptimize(slice_sorted_with_duration.QueryToRowMap(q));
}
state.counters["s/row"] = benchmark::Counter(
static_cast<double>(slice_sorted_with_duration.row_count()),
benchmark::Counter::kIsIterationInvariantRate |
benchmark::Counter::kInvert);
state.counters["s/out"] = benchmark::Counter(
- static_cast<double>(table.table_.QueryToRowMap({c}, {}).size()),
+ static_cast<double>(table.table_.QueryToRowMap(q).size()),
benchmark::Counter::kIsIterationInvariantRate |
benchmark::Counter::kInvert);
}
diff --git a/src/trace_processor/db/table.cc b/src/trace_processor/db/table.cc
index 641c7459d..0505bf438 100644
--- a/src/trace_processor/db/table.cc
+++ b/src/trace_processor/db/table.cc
@@ -87,8 +87,9 @@ Table Table::CopyExceptOverlays() const {
return {string_pool_, row_count_, std::move(cols), {}};
}
-RowMap Table::QueryToRowMap(const std::vector<Constraint>& cs,
- const std::vector<Order>& ob) const {
+RowMap Table::QueryToRowMap(const Query& q) const {
+ const auto& cs = q.constraints;
+ const auto& ob = q.orders;
// We need to delay creation of the chains to this point because of Chrome
// does not want the binary size overhead of including the chain
// implementations. As they also don't query tables (instead just iterating)
@@ -134,7 +135,9 @@ Table Table::Sort(const std::vector<Order>& ob) const {
// Return a copy of this table with the RowMaps using the computed ordered
// RowMap.
Table table = CopyExceptOverlays();
- RowMap rm = QueryToRowMap({}, ob);
+ Query q;
+ q.orders = ob;
+ RowMap rm = QueryToRowMap(q);
for (const ColumnStorageOverlay& overlay : overlays_) {
table.overlays_.emplace_back(overlay.SelectRows(rm));
PERFETTO_DCHECK(table.overlays_.back().size() == table.row_count());
diff --git a/src/trace_processor/db/table.h b/src/trace_processor/db/table.h
index 7d531061f..655fa7307 100644
--- a/src/trace_processor/db/table.h
+++ b/src/trace_processor/db/table.h
@@ -130,8 +130,7 @@ class Table {
// Filters and sorts the tables with the arguments specified, returning the
// result as a RowMap.
- RowMap QueryToRowMap(const std::vector<Constraint>&,
- const std::vector<Order>&) const;
+ RowMap QueryToRowMap(const Query&) const;
// Applies the RowMap |rm| onto this table and returns an iterator over the
// resulting rows.
diff --git a/src/trace_processor/importers/ftrace/ftrace_descriptors.cc b/src/trace_processor/importers/ftrace/ftrace_descriptors.cc
index 3b846f4e1..0955a25ae 100644
--- a/src/trace_processor/importers/ftrace/ftrace_descriptors.cc
+++ b/src/trace_processor/importers/ftrace/ftrace_descriptors.cc
@@ -24,7 +24,7 @@ namespace perfetto {
namespace trace_processor {
namespace {
-std::array<FtraceMessageDescriptor, 498> descriptors{{
+std::array<FtraceMessageDescriptor, 502> descriptors{{
{nullptr, 0, {}},
{nullptr, 0, {}},
{nullptr, 0, {}},
@@ -5501,6 +5501,52 @@ std::array<FtraceMessageDescriptor, 498> descriptors{{
{"prefree_seg", ProtoSchemaType::kUint32},
},
},
+ {
+ "fastrpc_dma_free",
+ 3,
+ {
+ {},
+ {"cid", ProtoSchemaType::kInt32},
+ {"phys", ProtoSchemaType::kUint64},
+ {"size", ProtoSchemaType::kUint64},
+ },
+ },
+ {
+ "fastrpc_dma_alloc",
+ 5,
+ {
+ {},
+ {"cid", ProtoSchemaType::kInt32},
+ {"phys", ProtoSchemaType::kUint64},
+ {"size", ProtoSchemaType::kUint64},
+ {"attr", ProtoSchemaType::kUint64},
+ {"mflags", ProtoSchemaType::kInt32},
+ },
+ },
+ {
+ "fastrpc_dma_unmap",
+ 3,
+ {
+ {},
+ {"cid", ProtoSchemaType::kInt32},
+ {"phys", ProtoSchemaType::kUint64},
+ {"size", ProtoSchemaType::kUint64},
+ },
+ },
+ {
+ "fastrpc_dma_map",
+ 7,
+ {
+ {},
+ {"cid", ProtoSchemaType::kInt32},
+ {"fd", ProtoSchemaType::kInt32},
+ {"phys", ProtoSchemaType::kUint64},
+ {"size", ProtoSchemaType::kUint64},
+ {"len", ProtoSchemaType::kUint64},
+ {"attr", ProtoSchemaType::kUint32},
+ {"mflags", ProtoSchemaType::kInt32},
+ },
+ },
}};
} // namespace
diff --git a/src/trace_processor/importers/proto/default_modules.cc b/src/trace_processor/importers/proto/default_modules.cc
index b1cfc934c..f7611d70c 100644
--- a/src/trace_processor/importers/proto/default_modules.cc
+++ b/src/trace_processor/importers/proto/default_modules.cc
@@ -28,13 +28,13 @@ namespace perfetto {
namespace trace_processor {
void RegisterDefaultModules(TraceProcessorContext* context) {
- context->modules.emplace_back(new FtraceModule());
- context->modules.emplace_back(new EtwModule());
// Ftrace and Etw modules are special, because they have an extra method for
// parsing the ftrace/etw packets. So we need to store a pointer to it
// separately.
+ context->modules.emplace_back(new FtraceModule());
context->ftrace_module =
static_cast<FtraceModule*>(context->modules.back().get());
+ context->modules.emplace_back(new EtwModule());
context->etw_module = static_cast<EtwModule*>(context->modules.back().get());
context->modules.emplace_back(new TrackEventModule(context));
diff --git a/src/trace_processor/importers/proto/heap_graph_tracker.cc b/src/trace_processor/importers/proto/heap_graph_tracker.cc
index 72c0f33fa..097f3983d 100644
--- a/src/trace_processor/importers/proto/heap_graph_tracker.cc
+++ b/src/trace_processor/importers/proto/heap_graph_tracker.cc
@@ -62,8 +62,9 @@ void ForReferenceSet(TraceStorage* storage,
return;
auto* ref = storage->mutable_heap_graph_reference_table();
- auto it =
- ref->FilterToIterator({ref->reference_set_id().eq(*reference_set_id)});
+ Query q;
+ q.constraints = {ref->reference_set_id().eq(*reference_set_id)};
+ auto it = ref->FilterToIterator(q);
for (; it; ++it) {
if (!fn(it.row_reference()))
@@ -92,10 +93,10 @@ std::optional<ObjectTable::Id> GetReferredObj(const TraceStorage& storage,
uint32_t ref_set_id,
const std::string& field_name) {
const auto& refs_tbl = storage.heap_graph_reference_table();
-
- auto refs_it = refs_tbl.FilterToIterator(
- {refs_tbl.reference_set_id().eq(ref_set_id),
- refs_tbl.field_name().eq(NullTermStringView(field_name))});
+ Query q;
+ q.constraints = {refs_tbl.reference_set_id().eq(ref_set_id),
+ refs_tbl.field_name().eq(NullTermStringView(field_name))};
+ auto refs_it = refs_tbl.FilterToIterator(q);
if (!refs_it) {
return std::nullopt;
}
@@ -110,8 +111,10 @@ BuildSuperclassMap(UniquePid upid, int64_t ts, TraceStorage* storage) {
// Resolve superclasses by iterating heap graph objects and identifying the
// superClass field.
const auto& objects_tbl = storage->heap_graph_object_table();
- auto obj_it = objects_tbl.FilterToIterator(
- {objects_tbl.upid().eq(upid), objects_tbl.graph_sample_ts().eq(ts)});
+ Query q;
+ q.constraints = {objects_tbl.upid().eq(upid),
+ objects_tbl.graph_sample_ts().eq(ts)};
+ auto obj_it = objects_tbl.FilterToIterator(q);
for (; obj_it; ++obj_it) {
auto obj_id = obj_it.id();
auto class_descriptor = GetClassDescriptor(*storage, obj_id);
@@ -673,14 +676,16 @@ void HeapGraphTracker::PopulateNativeSize(const SequenceState& seq) {
};
std::vector<Cleaner> cleaners;
- auto class_it =
- class_tbl.FilterToIterator({class_tbl.name().eq("sun.misc.Cleaner")});
+ Query q;
+ q.constraints = {class_tbl.name().eq("sun.misc.Cleaner")};
+ auto class_it = class_tbl.FilterToIterator(q);
for (; class_it; ++class_it) {
auto class_id = class_it.id();
- auto obj_it = objects_tbl.FilterToIterator(
- {objects_tbl.type_id().eq(class_id.value),
- objects_tbl.upid().eq(seq.current_upid),
- objects_tbl.graph_sample_ts().eq(seq.current_ts)});
+ Query query;
+ query.constraints = {objects_tbl.type_id().eq(class_id.value),
+ objects_tbl.upid().eq(seq.current_upid),
+ objects_tbl.graph_sample_ts().eq(seq.current_ts)};
+ auto obj_it = objects_tbl.FilterToIterator(query);
for (; obj_it; ++obj_it) {
ObjectTable::Id cleaner_obj_id = obj_it.id();
std::optional<ObjectTable::Id> referent_id =
diff --git a/src/trace_processor/importers/proto/metadata_minimal_module.cc b/src/trace_processor/importers/proto/metadata_minimal_module.cc
index df4dd3fed..691127586 100644
--- a/src/trace_processor/importers/proto/metadata_minimal_module.cc
+++ b/src/trace_processor/importers/proto/metadata_minimal_module.cc
@@ -189,11 +189,6 @@ void MetadataMinimalModule::ParseChromeMetadataPacket(ConstBytes blob) {
return;
protos::pbzero::BackgroundTracingMetadata::TriggerRule::Decoder
triggered_rule_decoder(triggered_rule.data, triggered_rule.size);
- if (!triggered_rule_decoder.has_name_hash())
- return;
- metadata->SetDynamicMetadata(
- storage->InternString("cr-triggered_rule_name_hash"),
- Variadic::Integer(triggered_rule_decoder.name_hash()));
}
}
diff --git a/src/trace_processor/importers/proto/metadata_module.cc b/src/trace_processor/importers/proto/metadata_module.cc
index 4daccbff4..86cc72b71 100644
--- a/src/trace_processor/importers/proto/metadata_module.cc
+++ b/src/trace_processor/importers/proto/metadata_module.cc
@@ -44,6 +44,7 @@ MetadataModule::MetadataModule(TraceProcessorContext* context)
context_->storage->InternString("trusted_producer_uid")) {
RegisterForField(TracePacket::kUiStateFieldNumber, context);
RegisterForField(TracePacket::kTriggerFieldNumber, context);
+ RegisterForField(TracePacket::kChromeTriggerFieldNumber, context);
RegisterForField(TracePacket::kTraceUuidFieldNumber, context);
}
@@ -134,6 +135,11 @@ void MetadataModule::ParseChromeTrigger(int64_t ts, ConstBytes blob) {
}
context_->slice_tracker->Scoped(ts, track_id, cat_id, name_id,
/* duration = */ 0);
+
+ MetadataTracker* metadata = context_->metadata_tracker.get();
+ metadata->SetDynamicMetadata(
+ context_->storage->InternString("cr-triggered_rule_name_hash"),
+ Variadic::Integer(trigger.trigger_name_hash()));
}
void MetadataModule::ParseTraceUuid(ConstBytes blob) {
diff --git a/src/trace_processor/importers/proto/network_trace_module_unittest.cc b/src/trace_processor/importers/proto/network_trace_module_unittest.cc
index 79ba5deaa..0d9f53cd1 100644
--- a/src/trace_processor/importers/proto/network_trace_module_unittest.cc
+++ b/src/trace_processor/importers/proto/network_trace_module_unittest.cc
@@ -70,7 +70,9 @@ class NetworkTraceModuleTest : public testing::Test {
bool HasArg(ArgSetId sid, base::StringView key, Variadic value) {
StringId key_id = storage_->InternString(key);
const auto& a = storage_->arg_table();
- for (auto it = a.FilterToIterator({a.arg_set_id().eq(sid)}); it; ++it) {
+ Query q;
+ q.constraints = {a.arg_set_id().eq(sid)};
+ for (auto it = a.FilterToIterator(q); it; ++it) {
if (it.key() == key_id) {
EXPECT_EQ(it.flat_key(), key_id);
if (storage_->GetArgValue(it.row_number().row_number()) == value) {
diff --git a/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc b/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc
index 545418f14..20e008566 100644
--- a/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc
+++ b/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc
@@ -298,7 +298,9 @@ class ProtoTraceParserTest : public ::testing::Test {
bool HasArg(ArgSetId set_id, StringId key_id, Variadic value) {
const auto& args = storage_->arg_table();
- RowMap rm = args.QueryToRowMap({args.arg_set_id().eq(set_id)}, {});
+ Query q;
+ q.constraints = {args.arg_set_id().eq(set_id)};
+ RowMap rm = args.QueryToRowMap(q);
bool found = false;
for (auto it = rm.IterateRows(); it; it.Next()) {
if (args.key()[it.index()] == key_id) {
@@ -448,7 +450,9 @@ TEST_F(ProtoTraceParserTest, LoadGenericFtrace) {
auto set_id = raw.arg_set_id()[raw.row_count() - 1];
const auto& args = storage_->arg_table();
- RowMap rm = args.QueryToRowMap({args.arg_set_id().eq(set_id)}, {});
+ Query q;
+ q.constraints = {args.arg_set_id().eq(set_id)};
+ RowMap rm = args.QueryToRowMap(q);
auto row = rm.Get(0);
diff --git a/src/trace_processor/importers/proto/winscope/BUILD.gn b/src/trace_processor/importers/proto/winscope/BUILD.gn
index 47e5cb4cc..93f9d1bd8 100644
--- a/src/trace_processor/importers/proto/winscope/BUILD.gn
+++ b/src/trace_processor/importers/proto/winscope/BUILD.gn
@@ -41,6 +41,8 @@ source_set("full") {
"../../../../../protos/perfetto/trace/android:zero",
"../../../../../protos/perfetto/trace/interned_data:zero",
"../../../../../protos/perfetto/trace/profiling:zero",
+ "../../../../protozero",
+ "../../../containers",
"../../../storage",
"../../../tables",
"../../../types",
diff --git a/src/trace_processor/importers/proto/winscope/protolog_messages_tracker.cc b/src/trace_processor/importers/proto/winscope/protolog_messages_tracker.cc
index caf481d04..1cfd530cd 100644
--- a/src/trace_processor/importers/proto/winscope/protolog_messages_tracker.cc
+++ b/src/trace_processor/importers/proto/winscope/protolog_messages_tracker.cc
@@ -14,16 +14,15 @@
* limitations under the License.
*/
-#include "protolog_messages_tracker.h"
-#include "perfetto/ext/base/crash_keys.h"
-#include "src/trace_processor/importers/common/process_tracker.h"
-#include "src/trace_processor/storage/metadata.h"
-#include "src/trace_processor/types/trace_processor_context.h"
+#include "src/trace_processor/importers/proto/winscope/protolog_messages_tracker.h"
-namespace perfetto {
-namespace trace_processor {
-ProtoLogMessagesTracker::ProtoLogMessagesTracker() {}
+#include <cstdint>
+#include <optional>
+#include <vector>
+namespace perfetto::trace_processor {
+
+ProtoLogMessagesTracker::ProtoLogMessagesTracker() = default;
ProtoLogMessagesTracker::~ProtoLogMessagesTracker() = default;
void ProtoLogMessagesTracker::TrackMessage(
@@ -34,18 +33,14 @@ void ProtoLogMessagesTracker::TrackMessage(
.first->emplace_back(tracked_protolog_message);
}
-const std::optional<
- std::vector<ProtoLogMessagesTracker::TrackedProtoLogMessage>*>
+std::optional<std::vector<ProtoLogMessagesTracker::TrackedProtoLogMessage>*>
ProtoLogMessagesTracker::GetTrackedMessagesByMessageId(uint64_t message_id) {
- auto tracked_messages = tracked_protolog_messages.Find(message_id);
-
+ auto* tracked_messages = tracked_protolog_messages.Find(message_id);
if (tracked_messages == nullptr) {
// No tracked messages found for this id
return std::nullopt;
}
-
return tracked_messages;
}
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/importers/proto/winscope/protolog_messages_tracker.h b/src/trace_processor/importers/proto/winscope/protolog_messages_tracker.h
index 0f47c323f..de0285f04 100644
--- a/src/trace_processor/importers/proto/winscope/protolog_messages_tracker.h
+++ b/src/trace_processor/importers/proto/winscope/protolog_messages_tracker.h
@@ -17,12 +17,18 @@
#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_WINSCOPE_PROTOLOG_MESSAGES_TRACKER_H_
#define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_WINSCOPE_PROTOLOG_MESSAGES_TRACKER_H_
-#include "perfetto/trace_processor/basic_types.h"
+#include <cstdint>
+#include <optional>
+#include <string>
+#include <vector>
+
+#include "perfetto/ext/base/flat_hash_map.h"
#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/tables/winscope_tables_py.h"
+#include "src/trace_processor/types/destructible.h"
#include "src/trace_processor/types/trace_processor_context.h"
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
class ProtoLogMessagesTracker : public Destructible {
public:
@@ -41,16 +47,15 @@ class ProtoLogMessagesTracker : public Destructible {
};
static ProtoLogMessagesTracker* GetOrCreate(TraceProcessorContext* context) {
- if (!context->shell_transitions_tracker) {
- context->shell_transitions_tracker.reset(new ProtoLogMessagesTracker());
+ if (!context->protolog_messages_tracker) {
+ context->protolog_messages_tracker.reset(new ProtoLogMessagesTracker());
}
return static_cast<ProtoLogMessagesTracker*>(
- context->shell_transitions_tracker.get());
+ context->protolog_messages_tracker.get());
}
void TrackMessage(TrackedProtoLogMessage tracked_protolog_message);
- const std::optional<
- std::vector<ProtoLogMessagesTracker::TrackedProtoLogMessage>*>
+ std::optional<std::vector<ProtoLogMessagesTracker::TrackedProtoLogMessage>*>
GetTrackedMessagesByMessageId(uint64_t message_id);
private:
@@ -58,7 +63,6 @@ class ProtoLogMessagesTracker : public Destructible {
tracked_protolog_messages;
};
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor
#endif // SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_WINSCOPE_PROTOLOG_MESSAGES_TRACKER_H_
diff --git a/src/trace_processor/importers/proto/winscope/protolog_parser.cc b/src/trace_processor/importers/proto/winscope/protolog_parser.cc
index 15c19aca7..a95777da9 100644
--- a/src/trace_processor/importers/proto/winscope/protolog_parser.cc
+++ b/src/trace_processor/importers/proto/winscope/protolog_parser.cc
@@ -15,23 +15,33 @@
*/
#include "src/trace_processor/importers/proto/winscope/protolog_parser.h"
-#include "src/trace_processor/importers/proto/winscope/protolog_messages_tracker.h"
+#include <cinttypes>
+#include <cstddef>
+#include <cstdint>
+#include <optional>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "perfetto/ext/base/flat_hash_map.h"
#include "perfetto/ext/base/string_utils.h"
+#include "perfetto/ext/base/string_view.h"
+#include "perfetto/protozero/field.h"
#include "protos/perfetto/trace/android/protolog.pbzero.h"
#include "protos/perfetto/trace/interned_data/interned_data.pbzero.h"
#include "protos/perfetto/trace/profiling/profile_common.pbzero.h"
#include "protos/perfetto/trace/profiling/profile_packet.pbzero.h"
-#include "src/trace_processor/importers/common/args_tracker.h"
+#include "src/trace_processor/containers/string_pool.h"
#include "src/trace_processor/importers/proto/packet_sequence_state_generation.h"
+#include "src/trace_processor/importers/proto/winscope/protolog_messages_tracker.h"
#include "src/trace_processor/importers/proto/winscope/winscope.descriptor.h"
-#include "src/trace_processor/importers/proto/winscope/winscope_args_parser.h"
+#include "src/trace_processor/storage/stats.h"
#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/tables/winscope_tables_py.h"
#include "src/trace_processor/types/trace_processor_context.h"
-#include <sstream>
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
enum ProtoLogLevel : int32_t {
DEBUG = 1,
@@ -79,44 +89,27 @@ void ProtoLogParser::ParseProtoLogMessage(
std::vector<std::string> string_params;
if (protolog_message.has_str_param_iids()) {
- if (sequence_state->state()->IsIncrementalStateValid()) {
- for (auto it = protolog_message.str_param_iids(); it; ++it) {
- auto decoder =
- sequence_state->state()
- ->current_generation()
- ->LookupInternedMessage<protos::pbzero::InternedData::
- kProtologStringArgsFieldNumber,
- protos::pbzero::InternedString>(
- it.field().as_uint32());
-
- if (!decoder) {
- // This shouldn't happen since we already checked the incremental
- // state is valid.
- string_params.emplace_back("<ERROR>");
- context_->storage->IncrementStats(
- stats::winscope_protolog_missing_interned_arg_parse_errors);
- continue;
- }
-
- string_params.emplace_back(decoder->str().ToStdString());
+ for (auto it = protolog_message.str_param_iids(); it; ++it) {
+ auto* decoder = sequence_state->LookupInternedMessage<
+ protos::pbzero::InternedData::kProtologStringArgsFieldNumber,
+ protos::pbzero::InternedString>(it.field().as_uint32());
+ if (!decoder) {
+ // This shouldn't happen since we already checked the incremental
+ // state is valid.
+ string_params.emplace_back("<ERROR>");
+ context_->storage->IncrementStats(
+ stats::winscope_protolog_missing_interned_arg_parse_errors);
+ continue;
}
- } else {
- // If the incremental state is not valid we will not be able to decode
- // the interned strings correctly with 100% certainty so we will provide
- // string parameters that are not decoded.
- string_params.emplace_back("<MISSING_STR_ARG>");
+ string_params.emplace_back(decoder->str().ToStdString());
}
}
std::optional<StringId> stacktrace = std::nullopt;
if (protolog_message.has_stacktrace_iid()) {
- auto stacktrace_decoder =
- sequence_state->state()
- ->current_generation()
- ->LookupInternedMessage<
- protos::pbzero::InternedData::kProtologStacktraceFieldNumber,
- protos::pbzero::InternedString>(
- protolog_message.stacktrace_iid());
+ auto* stacktrace_decoder = sequence_state->LookupInternedMessage<
+ protos::pbzero::InternedData::kProtologStacktraceFieldNumber,
+ protos::pbzero::InternedString>(protolog_message.stacktrace_iid());
if (!stacktrace_decoder) {
// This shouldn't happen since we already checked the incremental
@@ -135,7 +128,7 @@ void ProtoLogParser::ParseProtoLogMessage(
tables::ProtoLogTable::Row row;
auto row_id = protolog_table->Insert(row).id;
- auto protolog_message_tracker =
+ auto* protolog_message_tracker =
ProtoLogMessagesTracker::GetOrCreate(context_);
struct ProtoLogMessagesTracker::TrackedProtoLogMessage tracked_message = {
protolog_message.message_id(),
@@ -154,13 +147,13 @@ void ProtoLogParser::ParseProtoLogViewerConfig(protozero::ConstBytes blob) {
protos::pbzero::ProtoLogViewerConfig::Decoder protolog_viewer_config(blob);
- std::unordered_map<uint32_t, std::string> group_tags;
+ base::FlatHashMap<uint32_t, std::string> group_tags;
for (auto it = protolog_viewer_config.groups(); it; ++it) {
protos::pbzero::ProtoLogViewerConfig::Group::Decoder group(*it);
- group_tags.insert({group.id(), group.tag().ToStdString()});
+ group_tags.Insert(group.id(), group.tag().ToStdString());
}
- auto protolog_message_tracker =
+ auto* protolog_message_tracker =
ProtoLogMessagesTracker::GetOrCreate(context_);
for (auto it = protolog_viewer_config.messages(); it; ++it) {
@@ -172,7 +165,7 @@ void ProtoLogParser::ParseProtoLogViewerConfig(protozero::ConstBytes blob) {
message_data.message_id());
if (tracked_messages_opt.has_value()) {
- auto group_tag = group_tags.find(message_data.group_id())->second;
+ auto* group_tag = group_tags.Find(message_data.group_id());
for (const auto& tracked_message : *tracked_messages_opt.value()) {
auto formatted_message = FormatMessage(
@@ -211,7 +204,8 @@ void ProtoLogParser::ParseProtoLogViewerConfig(protozero::ConstBytes blob) {
}
row.set_level(level);
- auto tag = context_->storage->InternString(base::StringView(group_tag));
+ auto tag =
+ context_->storage->InternString(base::StringView(*group_tag));
row.set_tag(tag);
auto message = context_->storage->InternString(
@@ -227,7 +221,7 @@ void ProtoLogParser::ParseProtoLogViewerConfig(protozero::ConstBytes blob) {
}
std::string ProtoLogParser::FormatMessage(
- const std::string message,
+ const std::string& message,
const std::vector<int64_t>& sint64_params,
const std::vector<double>& double_params,
const std::vector<bool>& boolean_params,
@@ -282,13 +276,14 @@ std::string ProtoLogParser::FormatMessage(
break;
}
case 's': {
- formatted_message.append(str_params_itr->c_str());
+ formatted_message.append(*str_params_itr);
++str_params_itr;
break;
- case 'b':
- formatted_message.append(*boolean_params_itr ? "true" : "false");
- ++boolean_params_itr;
- break;
+ }
+ case 'b': {
+ formatted_message.append(*boolean_params_itr ? "true" : "false");
+ ++boolean_params_itr;
+ break;
}
default:
// Should never happen
@@ -306,5 +301,4 @@ std::string ProtoLogParser::FormatMessage(
return formatted_message;
}
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/importers/proto/winscope/protolog_parser.h b/src/trace_processor/importers/proto/winscope/protolog_parser.h
index 97d918301..4c0db6feb 100644
--- a/src/trace_processor/importers/proto/winscope/protolog_parser.h
+++ b/src/trace_processor/importers/proto/winscope/protolog_parser.h
@@ -17,15 +17,15 @@
#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_WINSCOPE_PROTOLOG_PARSER_H_
#define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_WINSCOPE_PROTOLOG_PARSER_H_
-#include "protos/perfetto/trace/android/protolog.pbzero.h"
-#include "src/trace_processor/importers/proto/packet_sequence_state.h"
+#include <cstdint>
+#include <string>
+#include <vector>
+
#include "src/trace_processor/storage/trace_storage.h"
#include "src/trace_processor/util/descriptors.h"
#include "src/trace_processor/util/proto_to_args_parser.h"
-namespace perfetto {
-
-namespace trace_processor {
+namespace perfetto::trace_processor {
class TraceProcessorContext;
@@ -38,15 +38,12 @@ class ProtoLogParser {
void ParseProtoLogViewerConfig(protozero::ConstBytes);
private:
- std::string FormatMessage(const std::string message,
+ std::string FormatMessage(const std::string& message,
const std::vector<int64_t>& sint64_params,
const std::vector<double>& double_params,
const std::vector<bool>& boolean_params,
const std::vector<std::string>& string_params);
- static constexpr auto* kProtoLogMessageProtoName =
- "perfetto.protos.ProtoLogMessage";
-
TraceProcessorContext* const context_;
DescriptorPool pool_;
util::ProtoToArgsParser args_parser_;
@@ -59,7 +56,6 @@ class ProtoLogParser {
const StringId log_level_wtf_string_id_;
const StringId log_level_unknown_string_id_;
};
-} // namespace trace_processor
-} // namespace perfetto
+} // namespace perfetto::trace_processor
#endif // SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_WINSCOPE_PROTOLOG_PARSER_H_
diff --git a/src/trace_processor/metrics/sql/android/BUILD.gn b/src/trace_processor/metrics/sql/android/BUILD.gn
index bd77d0a62..7de8cf6c3 100644
--- a/src/trace_processor/metrics/sql/android/BUILD.gn
+++ b/src/trace_processor/metrics/sql/android/BUILD.gn
@@ -56,6 +56,7 @@ perfetto_sql_source_set("android") {
"android_multiuser.sql",
"android_multiuser_populator.sql",
"android_netperf.sql",
+ "android_oom_adjuster.sql",
"android_other_traces.sql",
"android_package_list.sql",
"android_powrails.sql",
diff --git a/src/trace_processor/metrics/sql/android/android_boot.sql b/src/trace_processor/metrics/sql/android/android_boot.sql
index 34d1c403d..91403878f 100644
--- a/src/trace_processor/metrics/sql/android/android_boot.sql
+++ b/src/trace_processor/metrics/sql/android/android_boot.sql
@@ -17,6 +17,7 @@
INCLUDE PERFETTO MODULE android.process_metadata;
INCLUDE PERFETTO MODULE android.app_process_starts;
INCLUDE PERFETTO MODULE android.garbage_collection;
+INCLUDE PERFETTO MODULE android.oom_adjuster;
CREATE OR REPLACE PERFETTO FUNCTION get_durations(process_name STRING)
RETURNS TABLE(uint_sleep_dur LONG, total_dur LONG) AS
@@ -27,6 +28,55 @@ FROM android_process_metadata
INNER JOIN thread ON thread.upid=android_process_metadata.upid
INNER JOIN thread_state ON thread.utid=thread_state.utid WHERE android_process_metadata.process_name=$process_name;
+CREATE OR REPLACE PERFETTO FUNCTION first_user_unlocked() RETURNS INT AS
+SELECT COALESCE(MIN(ts), 0) FROM thread_slice
+WHERE name GLOB "*android.intent.action.USER_UNLOCKED*";
+
+DROP TABLE IF EXISTS _oom_adj_events_with_src_bucket;
+CREATE PERFETTO TABLE _oom_adj_events_with_src_bucket
+AS
+SELECT
+ LAG(bucket) OVER (PARTITION BY upid ORDER BY ts) AS src_bucket,
+ ts,
+ bucket,
+ process_name,
+ oom_adj_reason
+FROM android_oom_adj_intervals;
+
+DROP VIEW IF EXISTS oom_adj_events_by_process_name;
+CREATE PERFETTO VIEW oom_adj_events_by_process_name AS
+SELECT
+ src_bucket,
+ bucket,
+ count(ts) as count,
+ process_name
+FROM _oom_adj_events_with_src_bucket
+ WHERE ts > first_user_unlocked()
+GROUP BY process_name, bucket, src_bucket;
+
+DROP VIEW IF EXISTS oom_adj_events_global_by_bucket;
+CREATE PERFETTO VIEW oom_adj_events_global_by_bucket AS
+SELECT
+ src_bucket,
+ bucket,
+ count(ts) as count,
+ NULL as name
+FROM _oom_adj_events_with_src_bucket
+WHERE
+ ts > first_user_unlocked()
+GROUP BY bucket, src_bucket;
+
+DROP VIEW IF EXISTS oom_adj_events_by_oom_adj_reason;
+CREATE PERFETTO VIEW oom_adj_events_by_oom_adj_reason AS
+SELECT
+ src_bucket,
+ bucket,
+ count(ts) as count,
+ oom_adj_reason as name
+FROM _oom_adj_events_with_src_bucket
+WHERE ts > first_user_unlocked()
+GROUP BY bucket, src_bucket, oom_adj_reason;
+
DROP VIEW IF EXISTS android_boot_output;
CREATE PERFETTO VIEW android_boot_output AS
SELECT AndroidBootMetric(
@@ -59,23 +109,23 @@ SELECT AndroidBootMetric(
'total_start_sum', (SELECT SUM(total_dur) FROM android_app_process_starts),
'num_of_processes', (SELECT COUNT(*) FROM android_app_process_starts),
'average_start_time', (SELECT AVG(total_dur) FROM android_app_process_starts)))
- FROM android_app_process_starts),
+ FROM android_app_process_starts),
'post_boot_process_start_aggregation', (
SELECT NULL_IF_EMPTY(AndroidBootMetric_ProcessStartAggregation(
- 'total_start_sum', (SELECT SUM(total_dur) FROM android_app_process_starts
- WHERE proc_start_ts > (SELECT COALESCE(MIN(ts), 0)
- FROM thread_slice WHERE name GLOB "*android.intent.action.USER_UNLOCKED*"
- ORDER BY ts ASC LIMIT 1 )
- ),
- 'num_of_processes', (SELECT COUNT(*) FROM android_app_process_starts
- WHERE proc_start_ts > (SELECT COALESCE(MIN(ts), 0) FROM thread_slice
- WHERE name GLOB "*android.intent.action.USER_UNLOCKED*" ORDER BY ts
- ASC LIMIT 1 )
- ),
- 'average_start_time', (SELECT AVG(total_dur) FROM android_app_process_starts
- WHERE proc_start_ts > (SELECT COALESCE(MIN(ts), 0) FROM thread_slice
- WHERE name GLOB "*android.intent.action.USER_UNLOCKED*" ORDER BY ts
- ASC LIMIT 1 )
+ 'total_start_sum', (
+ SELECT SUM(total_dur)
+ FROM android_app_process_starts
+ WHERE proc_start_ts > first_user_unlocked()
+ ),
+ 'num_of_processes', (
+ SELECT COUNT(*)
+ FROM android_app_process_starts
+ WHERE proc_start_ts > first_user_unlocked()
+ ),
+ 'average_start_time', (
+ SELECT AVG(total_dur)
+ FROM android_app_process_starts
+ WHERE proc_start_ts > first_user_unlocked()
)
))
),
@@ -85,16 +135,23 @@ SELECT AndroidBootMetric(
),
'num_of_processes_with_gc', (SELECT COUNT(process_name) FROM android_garbage_collection_events
),
- 'num_of_threads_with_gc', (SELECT SUM(cnt) FROM (SELECT COUNT(*) AS cnt
- FROM android_garbage_collection_events
- GROUP by thread_name, process_name)
+ 'num_of_threads_with_gc', (
+ SELECT SUM(cnt) FROM (
+ SELECT COUNT(*) AS cnt
+ FROM android_garbage_collection_events
+ GROUP by thread_name, process_name
+ )
),
'avg_gc_duration', (SELECT AVG(gc_dur) FROM android_garbage_collection_events),
'avg_running_gc_duration', (SELECT AVG(gc_running_dur) FROM android_garbage_collection_events),
- 'full_gc_count', (SELECT COUNT(*) FROM android_garbage_collection_events
+ 'full_gc_count', (
+ SELECT COUNT(*)
+ FROM android_garbage_collection_events
WHERE gc_type = "full"
),
- 'collector_transition_gc_count', (SELECT COUNT(*) FROM android_garbage_collection_events
+ 'collector_transition_gc_count', (
+ SELECT COUNT(*)
+ FROM android_garbage_collection_events
WHERE gc_type = "collector_transition"
),
'young_gc_count', (SELECT COUNT(*) FROM android_garbage_collection_events
@@ -117,68 +174,153 @@ SELECT AndroidBootMetric(
'post_boot_gc_aggregation', (
SELECT NULL_IF_EMPTY(AndroidBootMetric_GarbageCollectionAggregation(
'total_gc_count', (SELECT COUNT(*) FROM android_garbage_collection_events
- WHERE gc_ts > (SELECT COALESCE(MIN(ts), 0)
- FROM thread_slice WHERE name GLOB "*android.intent.action.USER_UNLOCKED*"
- ORDER BY ts ASC LIMIT 1 )
+ WHERE gc_ts > first_user_unlocked()
),
'num_of_processes_with_gc', (SELECT COUNT(process_name) FROM android_garbage_collection_events
- WHERE gc_ts > (SELECT COALESCE(MIN(ts), 0)
- FROM thread_slice WHERE name GLOB "*android.intent.action.USER_UNLOCKED*"
- ORDER BY ts ASC LIMIT 1 )
+ WHERE gc_ts > first_user_unlocked()
),
'num_of_threads_with_gc', (SELECT SUM(cnt) FROM (SELECT COUNT(*) AS cnt
FROM android_garbage_collection_events
- WHERE gc_ts > (SELECT COALESCE(MIN(ts), 0) FROM thread_slice
- WHERE name GLOB "*android.intent.action.USER_UNLOCKED*" ORDER BY ts
- ASC LIMIT 1 )
+ WHERE gc_ts > first_user_unlocked()
GROUP by thread_name, process_name)
),
'avg_gc_duration', (SELECT AVG(gc_dur) FROM android_garbage_collection_events
- WHERE gc_ts > (SELECT COALESCE(MIN(ts), 0) FROM thread_slice
- WHERE name GLOB "*android.intent.action.USER_UNLOCKED*" ORDER BY ts
- ASC LIMIT 1 )
+ WHERE gc_ts > first_user_unlocked()
),
'avg_running_gc_duration', (SELECT AVG(gc_running_dur) FROM android_garbage_collection_events
- WHERE gc_ts > (SELECT COALESCE(MIN(ts), 0) FROM thread_slice
- WHERE name GLOB "*android.intent.action.USER_UNLOCKED*" ORDER BY ts
- ASC LIMIT 1 )
+ WHERE gc_ts > first_user_unlocked()
),
'full_gc_count', (SELECT COUNT(*) FROM android_garbage_collection_events
- WHERE gc_type = "full" AND gc_ts > (SELECT COALESCE(MIN(ts), 0)
- FROM thread_slice WHERE name GLOB "*android.intent.action.USER_UNLOCKED*"
- ORDER BY ts ASC LIMIT 1 )
+ WHERE gc_type = "full" AND gc_ts > first_user_unlocked()
),
'collector_transition_gc_count', (SELECT COUNT(*) FROM android_garbage_collection_events
- WHERE gc_type = "collector_transition" AND gc_ts > (SELECT COALESCE(MIN(ts), 0)
- FROM thread_slice WHERE name GLOB "*android.intent.action.USER_UNLOCKED*"
- ORDER BY ts ASC LIMIT 1 )
+ WHERE gc_type = "collector_transition" AND gc_ts > (
+ SELECT COALESCE(MIN(ts), 0)
+ FROM thread_slice
+ WHERE name GLOB "*android.intent.action.USER_UNLOCKED*"
+ ORDER BY ts ASC LIMIT 1
+ )
),
- 'young_gc_count', (SELECT COUNT(*) FROM android_garbage_collection_events
- WHERE gc_type = "young" AND gc_ts > (SELECT COALESCE(MIN(ts), 0)
- FROM thread_slice WHERE name GLOB "*android.intent.action.USER_UNLOCKED*"
- ORDER BY ts ASC LIMIT 1 )
+ 'young_gc_count', (
+ SELECT COUNT(*)
+ FROM android_garbage_collection_events
+ WHERE gc_type = "young" AND gc_ts > (
+ SELECT COALESCE(MIN(ts), 0)
+ FROM thread_slice
+ WHERE name GLOB "*android.intent.action.USER_UNLOCKED*"
+ ORDER BY ts ASC LIMIT 1
+ )
),
- 'native_alloc_gc_count', (SELECT COUNT(*) FROM android_garbage_collection_events
- WHERE gc_type = "native_alloc" AND gc_ts > (SELECT COALESCE(MIN(ts), 0)
- FROM thread_slice WHERE name GLOB "*android.intent.action.USER_UNLOCKED*"
- ORDER BY ts ASC LIMIT 1 )
+ 'native_alloc_gc_count', (
+ SELECT COUNT(*)
+ FROM android_garbage_collection_events
+ WHERE gc_type = "native_alloc" AND gc_ts > first_user_unlocked()
),
- 'explicit_gc_count', (SELECT COUNT(*) FROM android_garbage_collection_events
- WHERE gc_type = "explicit_gc" AND gc_ts > (SELECT COALESCE(MIN(ts), 0)
- FROM thread_slice WHERE name GLOB "*android.intent.action.USER_UNLOCKED*"
- ORDER BY ts ASC LIMIT 1 )
+ 'explicit_gc_count', (
+ SELECT COUNT(*) FROM android_garbage_collection_events
+ WHERE gc_type = "explicit_gc" AND gc_ts > (
+ SELECT COALESCE(MIN(ts), 0)
+ FROM thread_slice
+ WHERE name GLOB "*android.intent.action.USER_UNLOCKED*"
+ ORDER BY ts ASC LIMIT 1
+ )
),
- 'alloc_gc_count', (SELECT COUNT(*) FROM android_garbage_collection_events
- WHERE gc_type = "alloc_gc" AND gc_ts > (SELECT COALESCE(MIN(ts), 0)
- FROM thread_slice WHERE name GLOB "*android.intent.action.USER_UNLOCKED*"
- ORDER BY ts ASC LIMIT 1 )
+ 'alloc_gc_count', (
+ SELECT COUNT(*) FROM android_garbage_collection_events
+ WHERE gc_type = "alloc_gc" AND gc_ts > first_user_unlocked()
),
- 'mb_per_ms_of_gc', (SELECT SUM(reclaimed_mb)/SUM(gc_running_dur/1e6) AS mb_per_ms_dur
+ 'mb_per_ms_of_gc', (
+ SELECT
+ SUM(reclaimed_mb)/SUM(gc_running_dur/1e6) AS mb_per_ms_dur
FROM android_garbage_collection_events
- WHERE gc_ts > (SELECT COALESCE(MIN(ts), 0) FROM thread_slice
- WHERE name GLOB "*android.intent.action.USER_UNLOCKED*" ORDER BY ts
- ASC LIMIT 1 )
+ WHERE gc_ts > first_user_unlocked()
)
))
+ ),
+ 'post_boot_oom_adjuster_transition_counts_by_process', (
+ SELECT RepeatedField(
+ AndroidBootMetric_OomAdjusterTransitionCounts(
+ 'name', process_name,
+ 'src_bucket', src_bucket,
+ 'dest_bucket', bucket,
+ 'count', count
+ )
+ ) FROM oom_adj_events_by_process_name
+ ),
+ 'post_boot_oom_adjuster_transition_counts_global', (
+ SELECT RepeatedField(
+ AndroidBootMetric_OomAdjusterTransitionCounts(
+ 'name', name,
+ 'src_bucket', src_bucket,
+ 'dest_bucket', bucket,
+ 'count', count
+ )
+ )
+ FROM oom_adj_events_global_by_bucket
+ ),
+ 'post_boot_oom_adjuster_transition_counts_by_oom_adj_reason',(
+ SELECT RepeatedField(
+ AndroidBootMetric_OomAdjusterTransitionCounts(
+ 'name', name,
+ 'src_bucket', src_bucket,
+ 'dest_bucket', bucket,
+ 'count', count
+ )
+ )
+ FROM oom_adj_events_by_oom_adj_reason
+ ),
+ 'post_boot_oom_adj_bucket_duration_agg_global',(SELECT RepeatedField(
+ AndroidBootMetric_OomAdjBucketDurationAggregation(
+ 'name', name,
+ 'bucket', bucket,
+ 'total_dur', total_dur
+ ))
+ FROM (
+ SELECT
+ NULL as name,
+ bucket,
+ SUM(dur) as total_dur
+ FROM android_oom_adj_intervals
+ WHERE ts > first_user_unlocked()
+ GROUP BY bucket)
+ ),
+ 'post_boot_oom_adj_bucket_duration_agg_by_process',(SELECT RepeatedField(
+ AndroidBootMetric_OomAdjBucketDurationAggregation(
+ 'name', name,
+ 'bucket', bucket,
+ 'total_dur', total_dur
+ )
+ )
+ FROM (
+ SELECT
+ process_name as name,
+ bucket,
+ SUM(dur) as total_dur
+ FROM android_oom_adj_intervals
+ WHERE ts > first_user_unlocked()
+ AND process_name IS NOT NULL
+ GROUP BY process_name, bucket)
+ ),
+ 'post_boot_oom_adj_duration_agg',
+ (SELECT RepeatedField(
+ AndroidBootMetric_OomAdjDurationAggregation(
+ 'min_oom_adj_dur', min_oom_adj_dur,
+ 'max_oom_adj_dur', max_oom_adj_dur,
+ 'avg_oom_adj_dur', avg_oom_adj_dur,
+ 'oom_adj_event_count', oom_adj_event_count,
+ 'oom_adj_reason', oom_adj_reason
+ )
+ )
+ FROM (
+ SELECT
+ MIN(oom_adj_dur) as min_oom_adj_dur,
+ MAX(oom_adj_dur) as max_oom_adj_dur,
+ AVG(oom_adj_dur) as avg_oom_adj_dur,
+ COUNT(DISTINCT(oom_adj_id)) oom_adj_event_count,
+ oom_adj_reason
+ FROM android_oom_adj_intervals
+ WHERE ts > first_user_unlocked()
+ GROUP BY oom_adj_reason
)
+ )
);
diff --git a/src/trace_processor/metrics/sql/android/android_oom_adjuster.sql b/src/trace_processor/metrics/sql/android/android_oom_adjuster.sql
new file mode 100644
index 000000000..7b752a89d
--- /dev/null
+++ b/src/trace_processor/metrics/sql/android/android_oom_adjuster.sql
@@ -0,0 +1,139 @@
+--
+-- Copyright 2024 The Android Open Source Project
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- https://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+INCLUDE PERFETTO MODULE android.oom_adjuster;
+
+DROP TABLE IF EXISTS _oom_adj_events_with_src_bucket;
+CREATE PERFETTO TABLE _oom_adj_events_with_src_bucket
+AS
+SELECT
+ LAG(bucket) OVER (PARTITION BY upid ORDER BY ts) AS src_bucket,
+ ts,
+ bucket,
+ process_name,
+ oom_adj_reason
+FROM android_oom_adj_intervals;
+
+DROP VIEW IF EXISTS oom_adj_events_by_process_name;
+CREATE PERFETTO VIEW oom_adj_events_by_process_name AS
+SELECT
+ src_bucket,
+ bucket,
+ count(ts) as count,
+ process_name
+FROM _oom_adj_events_with_src_bucket
+GROUP BY process_name, bucket, src_bucket;
+
+DROP VIEW IF EXISTS oom_adj_events_global_by_bucket;
+CREATE PERFETTO VIEW oom_adj_events_global_by_bucket AS
+SELECT
+ src_bucket,
+ bucket,
+ count(ts) as count,
+ NULL as name
+FROM _oom_adj_events_with_src_bucket
+GROUP BY bucket, src_bucket;
+
+DROP VIEW IF EXISTS oom_adj_events_by_oom_adj_reason;
+CREATE PERFETTO VIEW oom_adj_events_by_oom_adj_reason AS
+SELECT
+ src_bucket,
+ bucket,
+ count(ts) as count,
+ oom_adj_reason as name
+FROM _oom_adj_events_with_src_bucket
+GROUP BY bucket, src_bucket, oom_adj_reason;
+
+DROP VIEW IF EXISTS android_oom_adjuster_output;
+CREATE PERFETTO VIEW android_oom_adjuster_output AS
+SELECT AndroidOomAdjusterMetric(
+ 'oom_adjuster_transition_counts_by_process', (
+ SELECT RepeatedField(
+ AndroidOomAdjusterMetric_OomAdjusterTransitionCounts(
+ 'name', process_name,
+ 'src_bucket', src_bucket,
+ 'dest_bucket', bucket,
+ 'count', count
+ )
+ ) FROM oom_adj_events_by_process_name
+ ),
+ 'oom_adjuster_transition_counts_global', (
+ SELECT RepeatedField(
+ AndroidOomAdjusterMetric_OomAdjusterTransitionCounts(
+ 'name', name,
+ 'src_bucket', src_bucket,
+ 'dest_bucket', bucket,
+ 'count', count
+ )
+ )
+ FROM oom_adj_events_global_by_bucket
+ ),
+ 'oom_adjuster_transition_counts_by_oom_adj_reason',(
+ SELECT RepeatedField(
+ AndroidOomAdjusterMetric_OomAdjusterTransitionCounts(
+ 'name', name,
+ 'src_bucket', src_bucket,
+ 'dest_bucket', bucket,
+ 'count', count
+ )
+ )
+ FROM oom_adj_events_by_oom_adj_reason
+ ),
+ 'oom_adj_bucket_duration_agg_global',(SELECT RepeatedField(
+ AndroidOomAdjusterMetric_OomAdjBucketDurationAggregation(
+ 'name', name,
+ 'bucket', bucket,
+ 'total_dur', total_dur
+ )
+ )
+ FROM (
+ SELECT NULL as name, bucket, SUM(dur) as total_dur
+ FROM android_oom_adj_intervals GROUP BY bucket
+ )
+ ),
+ 'oom_adj_bucket_duration_agg_by_process',(SELECT RepeatedField(
+ AndroidOomAdjusterMetric_OomAdjBucketDurationAggregation(
+ 'name', name,
+ 'bucket', bucket,
+ 'total_dur', total_dur
+ )
+ )
+ FROM (
+ SELECT process_name as name, bucket, SUM(dur) as total_dur
+ FROM android_oom_adj_intervals
+ WHERE process_name IS NOT NULL
+ GROUP BY process_name, bucket
+ )
+ ),
+ 'oom_adj_duration_agg', (SELECT RepeatedField(
+ AndroidOomAdjusterMetric_OomAdjDurationAggregation(
+ 'min_oom_adj_dur', min_oom_adj_dur,
+ 'max_oom_adj_dur', max_oom_adj_dur,
+ 'avg_oom_adj_dur', avg_oom_adj_dur,
+ 'oom_adj_event_count', oom_adj_event_count,
+ 'oom_adj_reason', oom_adj_reason
+ )
+ )
+ FROM (
+ SELECT
+ MIN(oom_adj_dur) as min_oom_adj_dur,
+ MAX(oom_adj_dur) as max_oom_adj_dur,
+ AVG(oom_adj_dur) as avg_oom_adj_dur,
+ COUNT(DISTINCT(oom_adj_id)) oom_adj_event_count,
+ oom_adj_reason
+ FROM android_oom_adj_intervals GROUP BY oom_adj_reason
+ )
+ )
+);
diff --git a/src/trace_processor/metrics/sql/chrome/chrome_scroll_inputs_per_frame.sql b/src/trace_processor/metrics/sql/chrome/chrome_scroll_inputs_per_frame.sql
index c9d69ca3c..94d279406 100644
--- a/src/trace_processor/metrics/sql/chrome/chrome_scroll_inputs_per_frame.sql
+++ b/src/trace_processor/metrics/sql/chrome/chrome_scroll_inputs_per_frame.sql
@@ -20,18 +20,23 @@
-- 1 input per frame as flings are generated once per vsync.
-- The numbers mentioned above are estimates in the ideal case scenario.
+INCLUDE PERFETTO MODULE chrome.scroll_jank.utils;
+INCLUDE PERFETTO MODULE common.slices;
+
-- Grab all GestureScrollUpdate slices.
DROP VIEW IF EXISTS chrome_all_scroll_updates;
CREATE PERFETTO VIEW chrome_all_scroll_updates AS
SELECT
- id,
- EXTRACT_ARG(arg_set_id, 'chrome_latency_info.gesture_scroll_id') AS scroll_id,
- EXTRACT_ARG(arg_set_id, 'chrome_latency_info.is_coalesced') AS is_coalesced,
+ S.id,
+ chrome_get_most_recent_scroll_begin_id(ts) AS scroll_id,
+ has_descendant_slice_with_name(S.id, "SubmitCompositorFrameToPresentationCompositorFrame")
+ AS is_presented,
ts,
dur,
track_id
-FROM slice
-WHERE name = "InputLatency::GestureScrollUpdate";
+FROM slice S JOIN args USING(arg_set_id)
+WHERE NAME = "EventLatency"
+AND args.string_value GLOB "*GESTURE_SCROLL_UPDATE";
-- Count number of input GestureScrollUpdates per scroll.
DROP VIEW IF EXISTS chrome_update_count_per_scroll;
@@ -46,26 +51,26 @@ GROUP BY scroll_id;
-- Count the number of input GestureScrollUpdates that were converted
-- frames per scroll.
-DROP VIEW IF EXISTS chrome_non_coalesced_update_count_per_scroll;
-CREATE PERFETTO VIEW chrome_non_coalesced_update_count_per_scroll AS
+DROP VIEW IF EXISTS chrome_presented_update_count_per_scroll;
+CREATE PERFETTO VIEW chrome_presented_update_count_per_scroll AS
SELECT
- CAST(COUNT() AS FLOAT) AS non_coalesced_count,
+ CAST(COUNT() AS FLOAT) AS presented_count,
scroll_id,
id,
track_id,
dur
FROM chrome_all_scroll_updates
-WHERE NOT is_coalesced
+WHERE is_presented
GROUP BY scroll_id;
-- Get the average number of inputs per frame per scroll.
DROP VIEW IF EXISTS chrome_avg_scroll_inputs_per_frame;
CREATE PERFETTO VIEW chrome_avg_scroll_inputs_per_frame AS
SELECT
- count / non_coalesced_count AS avg_inputs_per_frame_per_scroll,
+ count / presented_count AS avg_inputs_per_frame_per_scroll,
scroll_id,
- non_coalesced_count
-FROM chrome_non_coalesced_update_count_per_scroll
+ presented_count
+FROM chrome_presented_update_count_per_scroll
JOIN chrome_update_count_per_scroll USING(scroll_id);
-- Get the last scroll update event that wasn't coalesced before the
@@ -75,14 +80,14 @@ CREATE PERFETTO VIEW chrome_frame_main_input_id AS
SELECT
id,
scroll_id,
- is_coalesced,
+ is_presented,
ts,
dur,
track_id,
(SELECT
MAX(id)
FROM chrome_all_scroll_updates parent_scrolls
- WHERE NOT is_coalesced
+ WHERE is_presented
AND parent_scrolls.ts <= scrolls.ts) AS presented_scroll_id
FROM chrome_all_scroll_updates scrolls;
diff --git a/src/trace_processor/perfetto_sql/intrinsics/functions/layout_functions.cc b/src/trace_processor/perfetto_sql/intrinsics/functions/layout_functions.cc
index 33843761d..4029384a4 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/functions/layout_functions.cc
+++ b/src/trace_processor/perfetto_sql/intrinsics/functions/layout_functions.cc
@@ -16,6 +16,7 @@
#include <cstddef>
#include <cstdint>
+#include <limits>
#include <queue>
#include <vector>
#include "perfetto/base/logging.h"
@@ -64,8 +65,10 @@ Please specify "ORDER BY ts" in the window clause.
size_t depth = SelectAvailableDepth(is_busy);
// If the slice has an end and is not an instant, schedule this depth
// to be marked available again when it ends.
- if (dur > 0) {
- slice_ends_.push({ts + dur, depth});
+ if (dur != 0) {
+ int64_t ts_end =
+ dur == -1 ? std::numeric_limits<int64_t>::max() : ts + dur;
+ slice_ends_.push({ts_end, depth});
}
last_depth_ = depth;
return base::OkStatus();
diff --git a/src/trace_processor/perfetto_sql/intrinsics/functions/structural_tree_partition.cc b/src/trace_processor/perfetto_sql/intrinsics/functions/structural_tree_partition.cc
index c3d2ac1cd..2d6854859 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/functions/structural_tree_partition.cc
+++ b/src/trace_processor/perfetto_sql/intrinsics/functions/structural_tree_partition.cc
@@ -178,6 +178,7 @@ void StructuralTreePartition::Final(sqlite3_context* ctx) {
// Keep track of the fact this node was processed and update the ancestor
// id for all children.
ss.first_pass_done = true;
+ ss.prev_ancestor_id_for_group = ancestor_id_for_group[ss.row.group];
ancestor_id_for_group[ss.row.group] = ss.row.id;
const auto* start = helper.ChildrenForIdBegin(ss.row.id);
diff --git a/src/trace_processor/perfetto_sql/intrinsics/functions/to_ftrace.cc b/src/trace_processor/perfetto_sql/intrinsics/functions/to_ftrace.cc
index 59e571faa..387664e6a 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/functions/to_ftrace.cc
+++ b/src/trace_processor/perfetto_sql/intrinsics/functions/to_ftrace.cc
@@ -157,7 +157,9 @@ ArgsSerializer::ArgsSerializer(
// We assume that the row map is a contiguous range (which is always the case
// because arg_set_ids are contiguous by definition).
- row_map_ = args.QueryToRowMap({set_ids.eq(arg_set_id_)}, {});
+ Query q;
+ q.constraints = {set_ids.eq(arg_set_id_)};
+ row_map_ = args.QueryToRowMap(q);
start_row_ = row_map_.empty() ? 0 : row_map_.Get(0);
// If the vector already has entries, we've previously cached the mapping
diff --git a/src/trace_processor/perfetto_sql/intrinsics/operators/BUILD.gn b/src/trace_processor/perfetto_sql/intrinsics/operators/BUILD.gn
index bbcb771fb..49a4a81ad 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/operators/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/intrinsics/operators/BUILD.gn
@@ -18,6 +18,10 @@ assert(enable_perfetto_trace_processor_sqlite)
source_set("operators") {
sources = [
+ "counter_mipmap_operator.cc",
+ "counter_mipmap_operator.h",
+ "slice_mipmap_operator.cc",
+ "slice_mipmap_operator.h",
"span_join_operator.cc",
"span_join_operator.h",
"window_operator.cc",
@@ -30,6 +34,7 @@ source_set("operators") {
"../../../../../include/perfetto/trace_processor",
"../../../../../protos/perfetto/trace_processor:zero",
"../../../../base",
+ "../../../containers",
"../../../sqlite",
"../../../util",
"../../engine",
diff --git a/src/trace_processor/perfetto_sql/intrinsics/operators/counter_mipmap_operator.cc b/src/trace_processor/perfetto_sql/intrinsics/operators/counter_mipmap_operator.cc
new file mode 100644
index 000000000..83321ce05
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/intrinsics/operators/counter_mipmap_operator.cc
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/trace_processor/perfetto_sql/intrinsics/operators/counter_mipmap_operator.h"
+
+#include <sqlite3.h>
+#include <algorithm>
+#include <cstddef>
+#include <cstdint>
+#include <functional>
+#include <iterator>
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "perfetto/base/logging.h"
+#include "perfetto/base/status.h"
+#include "perfetto/ext/base/status_or.h"
+#include "src/trace_processor/containers/implicit_segment_forest.h"
+#include "src/trace_processor/sqlite/bindings/sqlite_result.h"
+#include "src/trace_processor/sqlite/module_lifecycle_manager.h"
+#include "src/trace_processor/sqlite/sql_source.h"
+#include "src/trace_processor/sqlite/sqlite_utils.h"
+
+namespace perfetto::trace_processor {
+namespace {
+
+constexpr char kSchema[] = R"(
+ CREATE TABLE x(
+ in_window_start BIGINT HIDDEN,
+ in_window_end BIGINT HIDDEN,
+ in_window_step BIGINT HIDDEN,
+ min_value DOUBLE,
+ max_value DOUBLE,
+ last_ts BIGINT,
+ last_value DOUBLE,
+ PRIMARY KEY(last_ts)
+ ) WITHOUT ROWID
+)";
+
+enum ColumnIndex : size_t {
+ kInWindowStart = 0,
+ kInWindowEnd,
+ kInWindowStep,
+
+ kMinValue,
+ kMaxValue,
+ kLastTs,
+ kLastValue,
+};
+
+constexpr size_t kArgCount = kInWindowStep + 1;
+
+bool IsArgColumn(size_t index) {
+ return index < kArgCount;
+}
+
+using Counter = CounterMipmapOperator::Counter;
+using Agg = CounterMipmapOperator::Agg;
+using Forest = ImplicitSegmentForest<Counter, Agg>;
+
+} // namespace
+
+int CounterMipmapOperator::Create(sqlite3* db,
+ void* raw_ctx,
+ int argc,
+ const char* const* argv,
+ sqlite3_vtab** vtab,
+ char** zErr) {
+ if (argc != 4) {
+ *zErr = sqlite3_mprintf("counter_mipmap: wrong number of arguments");
+ return SQLITE_ERROR;
+ }
+
+ if (int ret = sqlite3_declare_vtab(db, kSchema); ret != SQLITE_OK) {
+ return ret;
+ }
+
+ auto* ctx = GetContext(raw_ctx);
+ auto state = std::make_unique<State>();
+
+ std::string sql = "SELECT ts, value FROM ";
+ sql.append(argv[3]);
+ auto res = ctx->engine->ExecuteUntilLastStatement(
+ SqlSource::FromTraceProcessorImplementation(std::move(sql)));
+ if (!res.ok()) {
+ *zErr = sqlite3_mprintf("%s", res.status().c_message());
+ return SQLITE_ERROR;
+ }
+ do {
+ int64_t ts = sqlite3_column_int64(res->stmt.sqlite_stmt(), 0);
+ auto value =
+ static_cast<float>(sqlite3_column_double(res->stmt.sqlite_stmt(), 1));
+ state->timestamps.push_back(ts);
+ state->forest.Push(Counter{value, value});
+ } while (res->stmt.Step());
+ if (!res->stmt.status().ok()) {
+ *zErr = sqlite3_mprintf("%s", res->stmt.status().c_message());
+ return SQLITE_ERROR;
+ }
+
+ std::unique_ptr<Vtab> vtab_res = std::make_unique<Vtab>();
+ vtab_res->state = ctx->manager.OnCreate(argv, std::move(state));
+ *vtab = vtab_res.release();
+ return SQLITE_OK;
+}
+
+int CounterMipmapOperator::Destroy(sqlite3_vtab* vtab) {
+ std::unique_ptr<Vtab> tab(GetVtab(vtab));
+ sqlite::ModuleStateManager<CounterMipmapOperator>::OnDestroy(tab->state);
+ return SQLITE_OK;
+}
+
+int CounterMipmapOperator::Connect(sqlite3* db,
+ void* raw_ctx,
+ int argc,
+ const char* const* argv,
+ sqlite3_vtab** vtab,
+ char**) {
+ PERFETTO_CHECK(argc == 4);
+ if (int ret = sqlite3_declare_vtab(db, kSchema); ret != SQLITE_OK) {
+ return ret;
+ }
+ auto* ctx = GetContext(raw_ctx);
+ std::unique_ptr<Vtab> res = std::make_unique<Vtab>();
+ res->state = ctx->manager.OnConnect(argv);
+ *vtab = res.release();
+ return SQLITE_OK;
+}
+
+int CounterMipmapOperator::Disconnect(sqlite3_vtab* vtab) {
+ std::unique_ptr<Vtab> tab(GetVtab(vtab));
+ sqlite::ModuleStateManager<CounterMipmapOperator>::OnDisconnect(tab->state);
+ return SQLITE_OK;
+}
+
+int CounterMipmapOperator::BestIndex(sqlite3_vtab*, sqlite3_index_info* info) {
+ base::Status status =
+ sqlite::utils::ValidateFunctionArguments(info, kArgCount, IsArgColumn);
+ if (!status.ok()) {
+ return SQLITE_CONSTRAINT;
+ }
+ if (info->nConstraint != kArgCount) {
+ return SQLITE_CONSTRAINT;
+ }
+ return SQLITE_OK;
+}
+
+int CounterMipmapOperator::Open(sqlite3_vtab*, sqlite3_vtab_cursor** cursor) {
+ std::unique_ptr<Cursor> c = std::make_unique<Cursor>();
+ *cursor = c.release();
+ return SQLITE_OK;
+}
+
+int CounterMipmapOperator::Close(sqlite3_vtab_cursor* cursor) {
+ std::unique_ptr<Cursor> c(GetCursor(cursor));
+ return SQLITE_OK;
+}
+
+int CounterMipmapOperator::Filter(sqlite3_vtab_cursor* cursor,
+ int,
+ const char*,
+ int argc,
+ sqlite3_value** argv) {
+ auto* c = GetCursor(cursor);
+ auto* t = GetVtab(c->pVtab);
+ auto* state =
+ sqlite::ModuleStateManager<CounterMipmapOperator>::GetState(t->state);
+ PERFETTO_CHECK(argc == kArgCount);
+
+ int64_t start_ts = sqlite3_value_int64(argv[0]);
+ int64_t end_ts = sqlite3_value_int64(argv[1]);
+ int64_t step_ts = sqlite3_value_int64(argv[2]);
+ if (start_ts == end_ts) {
+ return sqlite::utils::SetError(t, "counter_mipmap: empty range provided");
+ }
+
+ c->index = 0;
+ c->counters.clear();
+
+ // If there is a counter value before the start of this window, include it in
+ // the aggregation as well becaue it contributes to what should be rendered
+ // here.
+ auto ts_lb = std::lower_bound(state->timestamps.begin(),
+ state->timestamps.end(), start_ts);
+ if (ts_lb != state->timestamps.begin() &&
+ (ts_lb == state->timestamps.end() || *ts_lb != start_ts)) {
+ --ts_lb;
+ }
+ int64_t start_idx = std::distance(state->timestamps.begin(), ts_lb);
+ for (int64_t s = start_ts; s < end_ts; s += step_ts) {
+ int64_t end_idx =
+ std::distance(state->timestamps.begin(),
+ std::lower_bound(state->timestamps.begin() +
+ static_cast<int64_t>(start_idx),
+ state->timestamps.end(), s + step_ts));
+ if (start_idx == end_idx) {
+ continue;
+ }
+ c->counters.emplace_back(Cursor::Result{
+ state->forest.Query(static_cast<uint32_t>(start_idx),
+ static_cast<uint32_t>(end_idx)),
+ state->forest[static_cast<uint32_t>(end_idx) - 1],
+ state->timestamps[static_cast<uint32_t>(end_idx) - 1],
+ });
+ start_idx = end_idx;
+ }
+ return SQLITE_OK;
+}
+
+int CounterMipmapOperator::Next(sqlite3_vtab_cursor* cursor) {
+ GetCursor(cursor)->index++;
+ return SQLITE_OK;
+}
+
+int CounterMipmapOperator::Eof(sqlite3_vtab_cursor* cursor) {
+ auto* c = GetCursor(cursor);
+ return c->index >= c->counters.size();
+}
+
+int CounterMipmapOperator::Column(sqlite3_vtab_cursor* cursor,
+ sqlite3_context* ctx,
+ int N) {
+ auto* t = GetVtab(cursor->pVtab);
+ auto* c = GetCursor(cursor);
+ const auto& res = c->counters[c->index];
+ switch (N) {
+ case ColumnIndex::kMinValue:
+ sqlite::result::Double(ctx, static_cast<double>(res.min_max_counter.min));
+ return SQLITE_OK;
+ case ColumnIndex::kMaxValue:
+ sqlite::result::Double(ctx, static_cast<double>(res.min_max_counter.max));
+ return SQLITE_OK;
+ case ColumnIndex::kLastTs:
+ sqlite::result::Long(ctx, res.last_ts);
+ return SQLITE_OK;
+ case ColumnIndex::kLastValue:
+ PERFETTO_DCHECK(
+ std::equal_to<>()(res.last_counter.min, res.last_counter.max));
+ sqlite::result::Double(ctx, static_cast<double>(res.last_counter.min));
+ return SQLITE_OK;
+ default:
+ return sqlite::utils::SetError(t, "Bad column");
+ }
+ PERFETTO_FATAL("For GCC");
+}
+
+int CounterMipmapOperator::Rowid(sqlite3_vtab_cursor*, sqlite_int64*) {
+ return SQLITE_ERROR;
+}
+
+} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/perfetto_sql/intrinsics/operators/counter_mipmap_operator.h b/src/trace_processor/perfetto_sql/intrinsics/operators/counter_mipmap_operator.h
new file mode 100644
index 000000000..1b0897cc0
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/intrinsics/operators/counter_mipmap_operator.h
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_OPERATORS_COUNTER_MIPMAP_OPERATOR_H_
+#define SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_OPERATORS_COUNTER_MIPMAP_OPERATOR_H_
+
+#include <sqlite3.h>
+#include <cstdint>
+#include <vector>
+
+#include "src/trace_processor/containers/implicit_segment_forest.h"
+#include "src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h"
+#include "src/trace_processor/sqlite/bindings/sqlite_module.h"
+#include "src/trace_processor/sqlite/module_lifecycle_manager.h"
+
+namespace perfetto::trace_processor {
+
+// Operator for building "mipmaps" [1] over the counter-like tracks.
+//
+// In the context of trace data, mipmap really means aggregating the counter
+// values in a given time period into the {min, max, last} value for that
+// period, allowing UIs to efficiently display the contents of a counter track
+// when very zoomed out.
+//
+// Specifically, we are computing the query:
+// ```
+// select
+// last_value(ts),
+// min(value),
+// max(value),
+// last_value(value)
+// from $input in
+// where in.ts_end >= $window_start and in.ts <= $window_end
+// group by ts / $window_resolution
+// order by ts
+// ```
+// but in O(logn) time by using a segment-tree like data structure (see
+// ImplicitSegmentForest).
+//
+// [1] https://en.wikipedia.org/wiki/Mipmap
+struct CounterMipmapOperator : sqlite::Module<CounterMipmapOperator> {
+ struct Counter {
+ float min;
+ float max;
+ };
+ struct Agg {
+ Counter operator()(const Counter& a, const Counter& b) {
+ Counter res;
+ res.min = b.min < a.min ? b.min : a.min;
+ res.max = b.max > a.max ? b.max : a.max;
+ return res;
+ }
+ };
+ struct State {
+ ImplicitSegmentForest<Counter, Agg> forest;
+ std::vector<int64_t> timestamps;
+ };
+ struct Context {
+ explicit Context(PerfettoSqlEngine* _engine) : engine(_engine) {}
+ PerfettoSqlEngine* engine;
+ sqlite::ModuleStateManager<CounterMipmapOperator> manager;
+ };
+ struct Vtab : sqlite::Module<CounterMipmapOperator>::Vtab {
+ sqlite::ModuleStateManager<CounterMipmapOperator>::PerVtabState* state;
+ };
+ struct Cursor : sqlite::Module<CounterMipmapOperator>::Cursor {
+ struct Result {
+ Counter min_max_counter;
+ Counter last_counter;
+ int64_t last_ts;
+ };
+ std::vector<Result> counters;
+ uint32_t index;
+ };
+
+ static constexpr auto kType = kCreateOnly;
+ static constexpr bool kSupportsWrites = false;
+ static constexpr bool kDoesOverloadFunctions = false;
+
+ static int Create(sqlite3*,
+ void*,
+ int,
+ const char* const*,
+ sqlite3_vtab**,
+ char**);
+ static int Destroy(sqlite3_vtab*);
+
+ static int Connect(sqlite3*,
+ void*,
+ int,
+ const char* const*,
+ sqlite3_vtab**,
+ char**);
+ static int Disconnect(sqlite3_vtab*);
+
+ static int BestIndex(sqlite3_vtab*, sqlite3_index_info*);
+
+ static int Open(sqlite3_vtab*, sqlite3_vtab_cursor**);
+ static int Close(sqlite3_vtab_cursor*);
+
+ static int Filter(sqlite3_vtab_cursor*,
+ int,
+ const char*,
+ int,
+ sqlite3_value**);
+ static int Next(sqlite3_vtab_cursor*);
+ static int Eof(sqlite3_vtab_cursor*);
+ static int Column(sqlite3_vtab_cursor*, sqlite3_context*, int);
+ static int Rowid(sqlite3_vtab_cursor*, sqlite_int64*);
+};
+
+} // namespace perfetto::trace_processor
+
+#endif // SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_OPERATORS_COUNTER_MIPMAP_OPERATOR_H_
diff --git a/src/trace_processor/perfetto_sql/intrinsics/operators/slice_mipmap_operator.cc b/src/trace_processor/perfetto_sql/intrinsics/operators/slice_mipmap_operator.cc
new file mode 100644
index 000000000..f1c6b084c
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/intrinsics/operators/slice_mipmap_operator.cc
@@ -0,0 +1,273 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/trace_processor/perfetto_sql/intrinsics/operators/slice_mipmap_operator.h"
+
+#include <sqlite3.h>
+#include <algorithm>
+#include <cstddef>
+#include <cstdint>
+#include <iterator>
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "perfetto/base/logging.h"
+#include "perfetto/base/status.h"
+#include "perfetto/ext/base/status_or.h"
+#include "perfetto/public/compiler.h"
+#include "src/trace_processor/containers/implicit_segment_forest.h"
+#include "src/trace_processor/sqlite/bindings/sqlite_result.h"
+#include "src/trace_processor/sqlite/module_lifecycle_manager.h"
+#include "src/trace_processor/sqlite/sql_source.h"
+#include "src/trace_processor/sqlite/sqlite_utils.h"
+
+namespace perfetto::trace_processor {
+namespace {
+
+constexpr char kSliceSchema[] = R"(
+ CREATE TABLE x(
+ in_window_start BIGINT HIDDEN,
+ in_window_end BIGINT HIDDEN,
+ in_window_step BIGINT HIDDEN,
+ ts BIGINT,
+ id BIGINT,
+ dur BIGINT,
+ depth INTEGER,
+ PRIMARY KEY(id)
+ ) WITHOUT ROWID
+)";
+
+enum ColumnIndex : size_t {
+ kInWindowStart = 0,
+ kInWindowEnd,
+ kInWindowStep,
+
+ kTs,
+ kId,
+ kDur,
+ kDepth,
+};
+
+constexpr size_t kArgCount = kInWindowStep + 1;
+
+bool IsArgColumn(size_t index) {
+ return index < kArgCount;
+}
+
+} // namespace
+
+int SliceMipmapOperator::Create(sqlite3* db,
+ void* raw_ctx,
+ int argc,
+ const char* const* argv,
+ sqlite3_vtab** vtab,
+ char** zErr) {
+ if (argc != 4) {
+ *zErr = sqlite3_mprintf("slice_mipmap: wrong number of arguments");
+ return SQLITE_ERROR;
+ }
+
+ if (int ret = sqlite3_declare_vtab(db, kSliceSchema); ret != SQLITE_OK) {
+ return ret;
+ }
+
+ auto* ctx = GetContext(raw_ctx);
+ auto state = std::make_unique<State>();
+
+ std::string sql = "SELECT * FROM ";
+ sql.append(argv[3]);
+ auto res = ctx->engine->ExecuteUntilLastStatement(
+ SqlSource::FromTraceProcessorImplementation(std::move(sql)));
+ if (!res.ok()) {
+ *zErr = sqlite3_mprintf("%s", res.status().c_message());
+ return SQLITE_ERROR;
+ }
+ do {
+ auto id =
+ static_cast<uint32_t>(sqlite3_column_int64(res->stmt.sqlite_stmt(), 0));
+ int64_t ts = sqlite3_column_int64(res->stmt.sqlite_stmt(), 1);
+ int64_t dur = sqlite3_column_int64(res->stmt.sqlite_stmt(), 2);
+ auto depth =
+ static_cast<uint32_t>(sqlite3_column_int64(res->stmt.sqlite_stmt(), 3));
+ if (PERFETTO_UNLIKELY(depth >= state->by_depth.size())) {
+ state->by_depth.resize(depth + 1);
+ }
+ auto& by_depth = state->by_depth[depth];
+ by_depth.forest.Push(
+ Slice{dur, id, static_cast<uint32_t>(by_depth.forest.size())});
+ by_depth.timestamps.push_back(ts);
+ } while (res->stmt.Step());
+ if (!res->stmt.status().ok()) {
+ *zErr = sqlite3_mprintf("%s", res->stmt.status().c_message());
+ return SQLITE_ERROR;
+ }
+
+ std::unique_ptr<Vtab> vtab_res = std::make_unique<Vtab>();
+ vtab_res->state = ctx->manager.OnCreate(argv, std::move(state));
+ *vtab = vtab_res.release();
+ return SQLITE_OK;
+}
+
+int SliceMipmapOperator::Destroy(sqlite3_vtab* vtab) {
+ std::unique_ptr<Vtab> tab(GetVtab(vtab));
+ sqlite::ModuleStateManager<SliceMipmapOperator>::OnDestroy(tab->state);
+ return SQLITE_OK;
+}
+
+int SliceMipmapOperator::Connect(sqlite3* db,
+ void* raw_ctx,
+ int argc,
+ const char* const* argv,
+ sqlite3_vtab** vtab,
+ char**) {
+ PERFETTO_CHECK(argc == 4);
+ if (int ret = sqlite3_declare_vtab(db, kSliceSchema); ret != SQLITE_OK) {
+ return ret;
+ }
+ auto* ctx = GetContext(raw_ctx);
+ std::unique_ptr<Vtab> res = std::make_unique<Vtab>();
+ res->state = ctx->manager.OnConnect(argv);
+ *vtab = res.release();
+ return SQLITE_OK;
+}
+
+int SliceMipmapOperator::Disconnect(sqlite3_vtab* vtab) {
+ std::unique_ptr<Vtab> tab(GetVtab(vtab));
+ sqlite::ModuleStateManager<SliceMipmapOperator>::OnDisconnect(tab->state);
+ return SQLITE_OK;
+}
+
+int SliceMipmapOperator::BestIndex(sqlite3_vtab*, sqlite3_index_info* info) {
+ base::Status status =
+ sqlite::utils::ValidateFunctionArguments(info, kArgCount, IsArgColumn);
+ if (!status.ok()) {
+ return SQLITE_CONSTRAINT;
+ }
+ if (info->nConstraint != kArgCount) {
+ return SQLITE_CONSTRAINT;
+ }
+ return SQLITE_OK;
+}
+
+int SliceMipmapOperator::Open(sqlite3_vtab*, sqlite3_vtab_cursor** cursor) {
+ std::unique_ptr<Cursor> c = std::make_unique<Cursor>();
+ *cursor = c.release();
+ return SQLITE_OK;
+}
+
+int SliceMipmapOperator::Close(sqlite3_vtab_cursor* cursor) {
+ std::unique_ptr<Cursor> c(GetCursor(cursor));
+ return SQLITE_OK;
+}
+
+int SliceMipmapOperator::Filter(sqlite3_vtab_cursor* cursor,
+ int,
+ const char*,
+ int argc,
+ sqlite3_value** argv) {
+ auto* c = GetCursor(cursor);
+ auto* t = GetVtab(c->pVtab);
+ auto* state =
+ sqlite::ModuleStateManager<SliceMipmapOperator>::GetState(t->state);
+ PERFETTO_CHECK(argc == kArgCount);
+
+ c->results.clear();
+ c->index = 0;
+
+ int64_t start = sqlite3_value_int64(argv[0]);
+ int64_t end = sqlite3_value_int64(argv[1]);
+ int64_t step = sqlite3_value_int64(argv[2]);
+
+ if (start == end) {
+ return sqlite::utils::SetError(t, "slice_mipmap: empty range provided");
+ }
+
+ for (uint32_t depth = 0; depth < state->by_depth.size(); ++depth) {
+ auto& by_depth = state->by_depth[depth];
+ const auto& tses = by_depth.timestamps;
+
+ // If the slice before this window overlaps with the current window, move
+ // the iterator back one to consider it as well.
+ auto start_idx = static_cast<uint32_t>(std::distance(
+ tses.begin(), std::lower_bound(tses.begin(), tses.end(), start)));
+ if (start_idx != 0 &&
+ (static_cast<size_t>(start_idx) == tses.size() ||
+ (tses[start_idx] != start &&
+ tses[start_idx] + by_depth.forest[start_idx].dur > start))) {
+ --start_idx;
+ }
+
+ for (int64_t s = start; s < end; s += step) {
+ auto end_idx = static_cast<uint32_t>(std::distance(
+ tses.begin(),
+ std::lower_bound(tses.begin() + static_cast<int64_t>(start_idx),
+ tses.end(), s + step)));
+ if (start_idx == end_idx) {
+ continue;
+ }
+ auto res = by_depth.forest.Query(start_idx, end_idx);
+ c->results.emplace_back(Cursor::Result{
+ tses[res.idx],
+ res.dur,
+ res.id,
+ depth,
+ });
+ start_idx = end_idx;
+ }
+ }
+ return SQLITE_OK;
+}
+
+int SliceMipmapOperator::Next(sqlite3_vtab_cursor* cursor) {
+ GetCursor(cursor)->index++;
+ return SQLITE_OK;
+}
+
+int SliceMipmapOperator::Eof(sqlite3_vtab_cursor* cursor) {
+ auto* c = GetCursor(cursor);
+ return c->index >= c->results.size();
+}
+
+int SliceMipmapOperator::Column(sqlite3_vtab_cursor* cursor,
+ sqlite3_context* ctx,
+ int N) {
+ auto* t = GetVtab(cursor->pVtab);
+ auto* c = GetCursor(cursor);
+ switch (N) {
+ case ColumnIndex::kTs:
+ sqlite::result::Long(ctx, c->results[c->index].timestamp);
+ return SQLITE_OK;
+ case ColumnIndex::kId:
+ sqlite::result::Long(ctx, c->results[c->index].id);
+ return SQLITE_OK;
+ case ColumnIndex::kDur:
+ sqlite::result::Long(ctx, c->results[c->index].dur);
+ return SQLITE_OK;
+ case ColumnIndex::kDepth:
+ sqlite::result::Long(ctx, c->results[c->index].depth);
+ return SQLITE_OK;
+ default:
+ return sqlite::utils::SetError(t, "Bad column");
+ }
+ PERFETTO_FATAL("For GCC");
+}
+
+int SliceMipmapOperator::Rowid(sqlite3_vtab_cursor*, sqlite_int64*) {
+ return SQLITE_ERROR;
+}
+
+} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/perfetto_sql/intrinsics/operators/slice_mipmap_operator.h b/src/trace_processor/perfetto_sql/intrinsics/operators/slice_mipmap_operator.h
new file mode 100644
index 000000000..329122582
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/intrinsics/operators/slice_mipmap_operator.h
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_OPERATORS_SLICE_MIPMAP_OPERATOR_H_
+#define SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_OPERATORS_SLICE_MIPMAP_OPERATOR_H_
+
+#include <sqlite3.h>
+#include <cstdint>
+#include <vector>
+
+#include "src/trace_processor/containers/implicit_segment_forest.h"
+#include "src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h"
+#include "src/trace_processor/sqlite/bindings/sqlite_module.h"
+#include "src/trace_processor/sqlite/module_lifecycle_manager.h"
+
+namespace perfetto::trace_processor {
+
+// Operator for building "mipmaps" [1] over the slices in the trace.
+//
+// In this context mipmap really means aggregating the slices in a given
+// time period by max(dur) for that period, allowing UIs to efficiently display
+// the contents of slice tracks when very zoomed out.
+//
+// Specifically, we are computing the query:
+// ```
+// select
+// depth,
+// max(dur) as dur,
+// id,
+// ts
+// from $input in
+// where in.ts_end >= $window_start and in.ts <= $window_end
+// group by depth, ts / $window_resolution
+// order by ts
+// ```
+// but in O(logn) time by using a segment-tree like data structure (see
+// ImplicitSegmentForest).
+//
+// [1] https://en.wikipedia.org/wiki/Mipmap
+struct SliceMipmapOperator : sqlite::Module<SliceMipmapOperator> {
+ struct Slice {
+ int64_t dur;
+ uint32_t id;
+ uint32_t idx;
+ };
+ struct Agg {
+ Slice operator()(const Slice& a, const Slice& b) {
+ return a.dur < b.dur ? b : a;
+ }
+ };
+ struct PerDepth {
+ ImplicitSegmentForest<Slice, Agg> forest;
+ std::vector<int64_t> timestamps;
+ };
+ struct State {
+ std::vector<PerDepth> by_depth;
+ };
+ struct Context {
+ explicit Context(PerfettoSqlEngine* _engine) : engine(_engine) {}
+ PerfettoSqlEngine* engine;
+ sqlite::ModuleStateManager<SliceMipmapOperator> manager;
+ };
+ struct Vtab : sqlite::Module<SliceMipmapOperator>::Vtab {
+ sqlite::ModuleStateManager<SliceMipmapOperator>::PerVtabState* state;
+ };
+ struct Cursor : sqlite::Module<SliceMipmapOperator>::Cursor {
+ struct Result {
+ int64_t timestamp;
+ int64_t dur;
+ uint32_t id;
+ uint32_t depth;
+ };
+ std::vector<Result> results;
+ uint32_t index = 0;
+ };
+
+ static constexpr auto kType = kCreateOnly;
+ static constexpr bool kSupportsWrites = false;
+ static constexpr bool kDoesOverloadFunctions = false;
+
+ static int Create(sqlite3*,
+ void*,
+ int,
+ const char* const*,
+ sqlite3_vtab**,
+ char**);
+ static int Destroy(sqlite3_vtab*);
+
+ static int Connect(sqlite3*,
+ void*,
+ int,
+ const char* const*,
+ sqlite3_vtab**,
+ char**);
+ static int Disconnect(sqlite3_vtab*);
+
+ static int BestIndex(sqlite3_vtab*, sqlite3_index_info*);
+
+ static int Open(sqlite3_vtab*, sqlite3_vtab_cursor**);
+ static int Close(sqlite3_vtab_cursor*);
+
+ static int Filter(sqlite3_vtab_cursor*,
+ int,
+ const char*,
+ int,
+ sqlite3_value**);
+ static int Next(sqlite3_vtab_cursor*);
+ static int Eof(sqlite3_vtab_cursor*);
+ static int Column(sqlite3_vtab_cursor*, sqlite3_context*, int);
+ static int Rowid(sqlite3_vtab_cursor*, sqlite_int64*);
+};
+
+} // namespace perfetto::trace_processor
+
+#endif // SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_OPERATORS_SLICE_MIPMAP_OPERATOR_H_
diff --git a/src/trace_processor/perfetto_sql/intrinsics/table_functions/ancestor.cc b/src/trace_processor/perfetto_sql/intrinsics/table_functions/ancestor.cc
index f102b1f16..ab9ea1445 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/table_functions/ancestor.cc
+++ b/src/trace_processor/perfetto_sql/intrinsics/table_functions/ancestor.cc
@@ -142,8 +142,9 @@ base::StatusOr<std::unique_ptr<Table>> Ancestor::ComputeTable(
// Find the all slice ids that have the stack id and find all the
// ancestors of the slice ids.
const auto& slice_table = storage_->slice_table();
- auto it =
- slice_table.FilterToIterator({slice_table.stack_id().eq(start_id)});
+ Query q;
+ q.constraints = {slice_table.stack_id().eq(start_id)};
+ auto it = slice_table.FilterToIterator(q);
std::vector<tables::SliceTable::RowNumber> ancestors;
for (; it; ++it) {
RETURN_IF_ERROR(GetAncestors(slice_table, it.id(), ancestors));
diff --git a/src/trace_processor/perfetto_sql/intrinsics/table_functions/connected_flow.cc b/src/trace_processor/perfetto_sql/intrinsics/table_functions/connected_flow.cc
index 1e4f10e0f..40890366c 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/table_functions/connected_flow.cc
+++ b/src/trace_processor/perfetto_sql/intrinsics/table_functions/connected_flow.cc
@@ -159,7 +159,9 @@ class BFS {
const TypedColumn<SliceId>& start_col =
flow_direction == FlowDirection::OUTGOING ? flow.slice_out()
: flow.slice_in();
- auto it = flow.FilterToIterator({start_col.eq(slice_id.value)});
+ Query q;
+ q.constraints = {start_col.eq(slice_id.value)};
+ auto it = flow.FilterToIterator(q);
for (; it; ++it) {
flow_rows_.push_back(it.row_number());
diff --git a/src/trace_processor/perfetto_sql/intrinsics/table_functions/descendant.cc b/src/trace_processor/perfetto_sql/intrinsics/table_functions/descendant.cc
index 8f3ec43e2..d1b965f0d 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/table_functions/descendant.cc
+++ b/src/trace_processor/perfetto_sql/intrinsics/table_functions/descendant.cc
@@ -77,21 +77,23 @@ base::Status GetDescendants(
// track are always perfectly stacked).
// For unfinshed slices (i.e. -1 dur), we need to consider until the end of
// the trace so we cannot add any similar constraint.
- std::vector<Constraint> cs;
+ Query q;
if (start_ref->dur() >= 0) {
- cs.emplace_back(slices.ts().le(start_ref->ts() + start_ref->dur()));
+ q.constraints.emplace_back(
+ slices.ts().le(start_ref->ts() + start_ref->dur()));
}
// All nested descendents must be on the same track, with a ts greater than
// |start_ref.ts| and whose depth is larger than |start_ref|'s.
- cs.emplace_back(slices.ts().ge(start_ref->ts()));
- cs.emplace_back(slices.track_id().eq(start_ref->track_id().value));
- cs.emplace_back(slices.depth().gt(start_ref->depth()));
+ q.constraints.emplace_back(slices.ts().ge(start_ref->ts()));
+ q.constraints.emplace_back(slices.track_id().eq(start_ref->track_id().value));
+ q.constraints.emplace_back(slices.depth().gt(start_ref->depth()));
// It's important we insert directly into |row_numbers_accumulator| and not
// overwrite it because we expect the existing elements in
// |row_numbers_accumulator| to be preserved.
- for (auto it = slices.FilterToIterator(cs); it; ++it) {
+
+ for (auto it = slices.FilterToIterator(q); it; ++it) {
row_numbers_accumulator.emplace_back(it.row_number());
}
return base::OkStatus();
@@ -136,7 +138,7 @@ base::StatusOr<std::unique_ptr<Table>> Descendant::ComputeTable(
}
case Type::kSliceByStack: {
auto sbs_cs = {slices.stack_id().eq(start_id)};
- for (auto it = slices.FilterToIterator(sbs_cs); it; ++it) {
+ for (auto it = slices.FilterToIterator(Query{sbs_cs, {}}); it; ++it) {
RETURN_IF_ERROR(GetDescendants(slices, it.id(), descendants));
}
return ExtendWithStartId<tables::DescendantSliceByStackTable>(
diff --git a/src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_flat_slice_unittest.cc b/src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_flat_slice_unittest.cc
index c22362dd1..3a107d2fe 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_flat_slice_unittest.cc
+++ b/src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_flat_slice_unittest.cc
@@ -100,8 +100,9 @@ TEST(ExperimentalFlatSlice, Smoke) {
inserter.Populate(table);
auto out = ExperimentalFlatSlice::ComputeFlatSliceTable(table, &pool, 0, 400);
- auto it = out->ApplyAndIterateRows(out->QueryToRowMap(
- {}, {out->track_id().ascending(), out->ts().ascending()}));
+ Query q;
+ q.orders = {out->track_id().ascending(), out->ts().ascending()};
+ auto it = out->ApplyAndIterateRows(out->QueryToRowMap(q));
TableAsserter asserter(std::move(it));
@@ -183,8 +184,9 @@ TEST(ExperimentalFlatSlice, Bounds) {
auto out =
ExperimentalFlatSlice::ComputeFlatSliceTable(table, &pool, start, end);
- auto it = out->ApplyAndIterateRows(out->QueryToRowMap(
- {}, {out->track_id().ascending(), out->ts().ascending()}));
+ Query q;
+ q.orders = {out->track_id().ascending(), out->ts().ascending()};
+ auto it = out->ApplyAndIterateRows(out->QueryToRowMap(q));
TableAsserter asserter(std::move(it));
diff --git a/src/trace_processor/perfetto_sql/intrinsics/table_functions/flamegraph_construction_algorithms.cc b/src/trace_processor/perfetto_sql/intrinsics/table_functions/flamegraph_construction_algorithms.cc
index 56c3bf8ef..4ad0b9aa2 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/table_functions/flamegraph_construction_algorithms.cc
+++ b/src/trace_processor/perfetto_sql/intrinsics/table_functions/flamegraph_construction_algorithms.cc
@@ -330,8 +330,10 @@ std::unique_ptr<tables::ExperimentalFlamegraphTable> BuildHeapProfileFlamegraph(
storage->heap_profile_allocation_table();
// PASS OVER ALLOCATIONS:
// Aggregate allocations into the newly built tree.
- auto it = allocation_tbl.FilterToIterator(
- {allocation_tbl.ts().le(timestamp), allocation_tbl.upid().eq(upid)});
+ Query q;
+ q.constraints = {allocation_tbl.ts().le(timestamp),
+ allocation_tbl.upid().eq(upid)};
+ auto it = allocation_tbl.FilterToIterator(q);
if (!it) {
return nullptr;
}
@@ -367,7 +369,7 @@ BuildNativeCallStackSamplingFlamegraph(
std::unordered_set<UniqueTid> utids;
{
auto it = storage->thread_table().FilterToIterator(
- {storage->thread_table().upid().is_not_null()});
+ Query{{storage->thread_table().upid().is_not_null()}, {}});
for (; it; ++it) {
if (upids.count(*it.upid())) {
utids.emplace(it.id().value);
@@ -391,7 +393,7 @@ BuildNativeCallStackSamplingFlamegraph(
}
std::vector<uint32_t> cs_rows;
{
- auto it = storage->perf_sample_table().FilterToIterator(cs);
+ auto it = storage->perf_sample_table().FilterToIterator(Query{cs, {}});
for (; it; ++it) {
if (utids.find(it.utid()) != utids.end()) {
cs_rows.push_back(it.row_number().row_number());
@@ -404,10 +406,10 @@ BuildNativeCallStackSamplingFlamegraph(
}
// The logic underneath is selecting a default timestamp to be used by all
- // frames which do not have a timestamp. The timestamp is taken from the query
- // value and it's not meaningful for the row. It prevents however the rows
- // with no timestamp from being filtered out by Sqlite, after we create the
- // table ExperimentalFlamegraphTable in this class.
+ // frames which do not have a timestamp. The timestamp is taken from the
+ // query value and it's not meaningful for the row. It prevents however the
+ // rows with no timestamp from being filtered out by Sqlite, after we create
+ // the table ExperimentalFlamegraphTable in this class.
int64_t default_timestamp = 0;
if (!time_constraints.empty()) {
auto& tc = time_constraints[0];
diff --git a/src/trace_processor/perfetto_sql/stdlib/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/BUILD.gn
index 4c492d1e0..349e2f695 100644
--- a/src/trace_processor/perfetto_sql/stdlib/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/stdlib/BUILD.gn
@@ -36,6 +36,7 @@ perfetto_amalgamated_sql_header("stdlib") {
"stack_trace",
"time",
"v8",
+ "viz/summary",
"wattson",
]
generated_header = "stdlib.h"
diff --git a/src/trace_processor/perfetto_sql/stdlib/OWNERS b/src/trace_processor/perfetto_sql/stdlib/OWNERS
index 0479e4ba2..2d6db698b 100644
--- a/src/trace_processor/perfetto_sql/stdlib/OWNERS
+++ b/src/trace_processor/perfetto_sql/stdlib/OWNERS
@@ -1,3 +1,5 @@
-# These can't be added inside chrome/ bacause the whole directory is
-# blown away on every import.
+# These can't be added inside chrome/ because the whole directory is
+# blown away on every import. Subdirectories of `chrome` will also
+# need CHROMIUM_OWNERS added here.
per-file chrome/* = file://protos/third_party/CHROMIUM_OWNERS
+per-file chrome/scroll_jank/* = file://protos/third_party/CHROMIUM_OWNERS
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/frames/timeline.sql b/src/trace_processor/perfetto_sql/stdlib/android/frames/timeline.sql
index 3d29281f1..3a036e32b 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/frames/timeline.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/android/frames/timeline.sql
@@ -28,15 +28,18 @@ CREATE PERFETTO FUNCTION _get_frame_table_with_id(
-- Parsed frame id.
frame_id INT,
-- Utid.
- utid INT
+ utid INT,
+ -- Upid.
+ upid INT
) AS
WITH all_found AS (
SELECT
id,
cast_int!(STR_SPLIT(name, ' ', 1)) AS frame_id,
- utid
+ utid,
+ upid
FROM thread_slice
- WHERE name GLOB $glob_str
+ WHERE name GLOB $glob_str AND depth = 0
)
SELECT *
FROM all_found
@@ -50,12 +53,17 @@ CREATE PERFETTO TABLE android_frames_choreographer_do_frame(
-- Frame id
frame_id INT,
-- Utid of the UI thread
- ui_thread_utid INT
+ ui_thread_utid INT,
+ -- Upid of application process
+ upid INT
) AS
SELECT
id,
frame_id,
- utid AS ui_thread_utid
+ utid AS ui_thread_utid,
+ upid
+-- Some OEMs have customized `doFrame` to add more information, but we've only
+-- observed it added after the frame ID (b/303823815).
FROM _get_frame_table_with_id('Choreographer#doFrame*');
-- All of the `DrawFrame` slices with their frame id and render thread.
@@ -68,12 +76,15 @@ CREATE PERFETTO TABLE android_frames_draw_frame(
-- Frame id
frame_id INT,
-- Utid of the render thread
- render_thread_utid INT
+ render_thread_utid INT,
+ -- Upid of application process
+ upid INT
) AS
SELECT
id,
frame_id,
- utid AS render_thread_utid
+ utid AS render_thread_utid,
+ upid
FROM _get_frame_table_with_id('DrawFrame*');
-- `actual_frame_timeline_slice` returns the same slice on different tracks.
@@ -105,10 +116,11 @@ CREATE PERFETTO TABLE android_frames(
frame_id INT,
-- Timestamp of the frame. Start of the frame as defined by the start of
-- "Choreographer#doFrame" slice and the same as the start of the frame in
- -- `actual_frame_timeline_slice .
+ -- `actual_frame_timeline_slice if present.
ts INT,
-- Duration of the frame, as defined by the duration of the corresponding
- -- `actual_frame_timeline_slice` duration.
+ -- `actual_frame_timeline_slice` or, if not present the time between the
+ -- `ts` and the end of the final `DrawFrame`.
dur INT,
-- `slice.id` of "Choreographer#doFrame" slice.
do_frame_id INT,
@@ -123,11 +135,22 @@ CREATE PERFETTO TABLE android_frames(
-- `utid` of the UI thread.
ui_thread_utid INT
) AS
-WITH frames_sdk_after_28 AS (
+WITH fallback AS MATERIALIZED (
+ SELECT
+ frame_id,
+ do_frame_slice.ts AS ts,
+ MAX(draw_frame_slice.ts + draw_frame_slice.dur) - do_frame_slice.ts AS dur
+ FROM android_frames_choreographer_do_frame do_frame
+ JOIN android_frames_draw_frame draw_frame USING (frame_id, upid)
+ JOIN slice do_frame_slice ON (do_frame.id = do_frame_slice.id)
+ JOIN slice draw_frame_slice ON (draw_frame.id = draw_frame_slice.id)
+GROUP BY 1
+),
+frames_sdk_after_28 AS (
SELECT
frame_id,
- ts,
- dur,
+ COALESCE(act.ts, fallback.ts) AS ts,
+ COALESCE(act.dur, fallback.dur) AS dur,
do_frame.id AS do_frame_id,
draw_frame.id AS draw_frame_id,
draw_frame.render_thread_utid,
@@ -136,9 +159,10 @@ SELECT
act.id AS actual_frame_timeline_id,
exp.id AS expected_frame_timeline_id
FROM android_frames_choreographer_do_frame do_frame
-JOIN android_frames_draw_frame draw_frame USING (frame_id)
-JOIN _distinct_from_actual_timeline_slice act USING (frame_id)
-JOIN _distinct_from_expected_timeline_slice exp USING (frame_id)
+JOIN android_frames_draw_frame draw_frame USING (frame_id, upid)
+JOIN fallback USING (frame_id)
+LEFT JOIN _distinct_from_actual_timeline_slice act USING (frame_id)
+LEFT JOIN _distinct_from_expected_timeline_slice exp USING (frame_id)
ORDER BY frame_id
),
all_frames AS (
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/process_metadata.sql b/src/trace_processor/perfetto_sql/stdlib/android/process_metadata.sql
index 86c099bd7..a50b03306 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/process_metadata.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/android/process_metadata.sql
@@ -70,8 +70,8 @@ LEFT JOIN package_list plist
)
OR
(
- -- isolated processes can only be matched based on the name prefix
+ -- isolated processes can only be matched based on the name
process.android_appid >= 90000 AND process.android_appid < 100000
- AND STR_SPLIT(process.name, ':', 0) GLOB plist.package_name || '*'
+ AND STR_SPLIT(process.name, ':', 0) = plist.package_name
)
);
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/startup/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/android/startup/BUILD.gn
index 8206b4ac9..c495f08f4 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/startup/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/stdlib/android/startup/BUILD.gn
@@ -21,5 +21,6 @@ perfetto_sql_source_set("startup") {
"startups_maxsdk28.sql",
"startups_minsdk29.sql",
"startups_minsdk33.sql",
+ "time_to_display.sql",
]
}
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/startup/startups_maxsdk28.sql b/src/trace_processor/perfetto_sql/stdlib/android/startup/startups_maxsdk28.sql
index 5188b6380..3faf09110 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/startup/startups_maxsdk28.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/android/startup/startups_maxsdk28.sql
@@ -23,7 +23,8 @@ WITH warm_and_cold AS (
SELECT
le.ts,
le.ts_end AS ts_end,
- package_name AS package
+ package_name AS package,
+ NULL AS startup_type
FROM _startup_events le
),
-- Hot starts don’t have a launching slice so we use activityResume as a
@@ -36,13 +37,18 @@ maybe_hot AS (
sl.ts,
rs.ts + rs.dur AS ts_end,
-- We use the process name as the package as we have no better option.
- process_name AS package
+ process_name AS package,
+ "hot" AS startup_type
FROM thread_slice sl
JOIN android_first_frame_after(sl.ts) rs
WHERE name = 'activityResume'
-- Remove any launches here where the activityResume slices happens during
-- a warm/cold startup.
- AND sl.ts NOT IN (SELECT ts FROM warm_and_cold)
+ AND NOT EXISTS (
+ SELECT 1
+ FROM warm_and_cold wac
+ WHERE sl.ts BETWEEN wac.ts AND wac.ts_end
+ LIMIT 1)
),
cold_warm_hot AS (
SELECT * FROM warm_and_cold
@@ -57,7 +63,7 @@ SELECT
ts_end,
ts_end - ts AS dur,
package,
- NULL AS startup_type
+ startup_type
FROM cold_warm_hot;
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/startup/time_to_display.sql b/src/trace_processor/perfetto_sql/stdlib/android/startup/time_to_display.sql
new file mode 100644
index 000000000..7264bb75e
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/android/startup/time_to_display.sql
@@ -0,0 +1,164 @@
+--
+-- Copyright 2024 The Android Open Source Project
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- https://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+
+INCLUDE PERFETTO MODULE android.startup.startups;
+INCLUDE PERFETTO MODULE android.frames.timeline;
+INCLUDE PERFETTO MODULE slices.with_context;
+
+CREATE PERFETTO VIEW _startups_with_upid AS
+WITH joined_with_processes AS (
+ SELECT
+ s.*,
+ p.upid
+ FROM android_startups s
+ LEFT JOIN android_startup_processes p USING (startup_id)
+),
+fallback AS (
+ SELECT
+ s.*,
+ upid FROM android_startups s
+ JOIN process p ON (p.name glob s.package)
+)
+ SELECT
+ j.startup_id,
+ j.ts,
+ j.ts_end,
+ j.dur,
+ j.package,
+ j.startup_type,
+ COALESCE(j.upid, f.upid) AS upid
+ FROM joined_with_processes j
+ LEFT JOIN fallback f ON (j.upid IS NULL AND j.startup_id = f.startup_id);
+
+-- Get Time To Initial Display of the startup calculated as time between the
+-- startup started and the first frame that was started by Choreographer on the
+-- UI thread of the startup finished drawing.
+-- TTID (https://developer.android.com/topic/performance/vitals/launch-time#time-initial)
+-- Googlers: see go/android-performance-metrics-glossary for details.
+CREATE PERFETTO TABLE _ttid AS
+WITH frames_with_upid AS (
+ SELECT
+ f.*,
+ upid
+ FROM android_frames f
+ JOIN thread t ON (f.ui_thread_utid = t.utid)
+),
+ -- First `DrawFrame` on Render Thread after the startup.
+first_frame_for_startup AS (
+ SELECT
+ startup_id,
+ frame_id,
+ s.ts AS startup_ts,
+ draw_frame_id,
+ s.upid
+ FROM _startups_with_upid s
+ JOIN frames_with_upid f ON (s.upid = f.upid AND s.ts <= f.ts)
+ GROUP BY startup_id
+ ORDER BY startup_id, f.ts)
+SELECT
+ startup_id,
+ frame_id,
+ draw_frame_id,
+ ts + dur - startup_ts AS ttid,
+ upid
+FROM first_frame_for_startup
+JOIN slice ON (slice.id = draw_frame_id);
+
+-- Get Time To Full Display of the startup calculated as time between the
+-- startup started and the first frame that was started by Choreographer after
+-- or containing the `reportFullyDrawn()` slice on the UI thread of the startup
+-- finished drawing.
+-- TTFD (https://developer.android.com/topic/performance/vitals/launch-time#retrieve-TTFD)
+-- Googlers: see go/android-performance-metrics-glossary for details.
+CREATE PERFETTO TABLE _ttfd AS
+-- First `reportFullyDrawn` slice for each startup.
+WITH first_report_fully_drawn_for_startup AS (
+ SELECT
+ startup_id,
+ s.ts AS startup_ts,
+ t.ts AS report_fully_drawn_ts,
+ t.utid,
+ s.upid
+FROM _startups_with_upid s
+JOIN thread_slice t ON (s.upid = t.upid AND t.ts >= s.ts)
+WHERE name GLOB "reportFullyDrawn*" AND t.is_main_thread = 1
+GROUP BY startup_id
+ORDER BY startup_id, t.ts),
+-- After the first `reportFullyDrawn` find the first `Choreographer#DoFrame` on
+-- the UI thread and it's first `DrawFrame` on Render Thread.
+first_frame_after_report_for_startup AS (
+ SELECT
+ startup_id,
+ frame_id,
+ startup_ts,
+ draw_frame_id,
+ s.upid
+ FROM first_report_fully_drawn_for_startup s
+ JOIN android_frames f ON (
+ s.utid = f.ui_thread_utid
+ -- We are looking for the first DrawFrame that was started by the first
+ -- "Choreographer#DoFrame" on UI thread after or containing
+ -- reportFullyDrawn. In Android UIs, it's common to have UI code happen
+ -- either before a frame, or during it, and generally non-trivial amounts
+ -- of "update UI model" code doesn't try to differentiate these. We account
+ -- for both of these by looking for the first UI slice that ends after the
+ -- "reportFullyDrawnSlice" begins.
+ AND report_fully_drawn_ts < (f.ts + f.dur))
+ GROUP BY startup_id
+ ORDER BY startup_id, f.ts
+)
+-- Get TTFD as the difference between the start of the startup and the end of
+-- `DrawFrame` slice we previously found.
+SELECT
+ startup_id,
+ frame_id,
+ draw_frame_id,
+ ts + dur - startup_ts AS ttfd,
+ upid
+FROM first_frame_after_report_for_startup
+JOIN slice ON (slice.id = draw_frame_id);
+
+-- Startup metric defintions, which focus on the observable time range:
+-- TTID - Time To Initial Display
+-- * https://developer.android.com/topic/performance/vitals/launch-time#time-initial
+-- * end of first RenderThread.DrawFrame - bindApplication
+-- TTFD - Time To Full Display
+-- * https://developer.android.com/topic/performance/vitals/launch-time#retrieve-TTFD
+-- * end of next RT.DrawFrame, after reportFullyDrawn called - bindApplication
+-- Googlers: see go/android-performance-metrics-glossary for details.
+CREATE PERFETTO TABLE android_startup_time_to_display(
+ -- Startup id.
+ startup_id INT,
+ -- Time to initial display (TTID)
+ time_to_inital_display INT,
+ -- Time to full display (TTFD)
+ time_to_full_display INT,
+ -- `android_frames.frame_id` of frame for initial display
+ ttid_frame_id INT,
+ -- `android_frames.frame_id` of frame for full display
+ ttfd_frame_id INT,
+ -- `process.upid` of the startup
+ upid INT
+) AS
+SELECT
+ startup_id,
+ ttid AS time_to_inital_display,
+ ttfd AS time_to_full_display,
+ _ttid.frame_id AS ttid_frame_id,
+ _ttfd.frame_id AS ttfd_frame_id,
+ _ttid.upid
+FROM android_startups
+LEFT JOIN _ttid USING (startup_id)
+LEFT JOIN _ttfd USING (startup_id);
diff --git a/src/trace_processor/perfetto_sql/stdlib/chrome/chrome_scrolls.sql b/src/trace_processor/perfetto_sql/stdlib/chrome/chrome_scrolls.sql
index f6e29f9e9..fcc893306 100644
--- a/src/trace_processor/perfetto_sql/stdlib/chrome/chrome_scrolls.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/chrome/chrome_scrolls.sql
@@ -2,6 +2,8 @@
-- Use of this source code is governed by a BSD-style license that can be
-- found in the LICENSE file.
+INCLUDE PERFETTO MODULE chrome.scroll_jank.utils;
+
-- Defines slices for all of the individual scrolls in a trace based on the
-- LatencyInfo-based scroll definition.
--
@@ -19,36 +21,39 @@ CREATE PERFETTO TABLE chrome_scrolls(
ts INT,
-- The duration of the scroll.
dur INT,
- -- The earliest timestamp of the InputLatency::GestureScrollBegin for the
+ -- The earliest timestamp of the EventLatency slice of the GESTURE_SCROLL_BEGIN type for the
-- corresponding scroll id.
gesture_scroll_begin_ts INT,
- -- The earliest timestamp of the InputLatency::GestureScrollEnd for the
+ -- The earliest timestamp of the EventLatency slice of the GESTURE_SCROLL_END type /
+ -- the latest timestamp of the EventLatency slice of the GESTURE_SCROLL_UPDATE type for the
-- corresponding scroll id.
gesture_scroll_end_ts INT
) AS
WITH all_scrolls AS (
SELECT
- name,
- ts,
- dur,
- extract_arg(arg_set_id, 'chrome_latency_info.gesture_scroll_id') AS scroll_id
- FROM slice
- WHERE name GLOB 'InputLatency::GestureScroll*'
- AND extract_arg(arg_set_id, 'chrome_latency_info.gesture_scroll_id') IS NOT NULL
+ args.string_value AS name,
+ S.ts AS ts,
+ S.dur AS dur,
+ chrome_get_most_recent_scroll_begin_id(S.ts) AS scroll_id
+ FROM slice AS S JOIN args USING(arg_set_id)
+ WHERE name="EventLatency"
+ AND args.string_value GLOB "*GESTURE_SCROLL*"
),
scroll_starts AS (
SELECT
scroll_id,
MIN(ts) AS gesture_scroll_begin_ts
FROM all_scrolls
- WHERE name = 'InputLatency::GestureScrollBegin'
+ WHERE name = "GESTURE_SCROLL_BEGIN"
GROUP BY scroll_id
-), scroll_ends AS (
+),
+scroll_ends AS (
SELECT
scroll_id,
- MIN(ts) AS gesture_scroll_end_ts
+ MAX(ts) AS gesture_scroll_end_ts
FROM all_scrolls
- WHERE name = 'InputLatency::GestureScrollEnd'
+ WHERE name GLOB "*GESTURE_SCROLL_UPDATE"
+ OR name = "GESTURE_SCROLL_END"
GROUP BY scroll_id
)
SELECT
diff --git a/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/scroll_jank_v3.sql b/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/scroll_jank_v3.sql
index 4bc951337..589adecd1 100644
--- a/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/scroll_jank_v3.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/scroll_jank_v3.sql
@@ -8,22 +8,7 @@ INCLUDE PERFETTO MODULE deprecated.v42.common.slices;
-- in BTP.
INCLUDE PERFETTO MODULE chrome.metadata;
INCLUDE PERFETTO MODULE chrome.scroll_jank.scroll_jank_v3_cause;
-
--- Checks if slice has a descendant with provided name.
-CREATE PERFETTO FUNCTION _has_descendant_slice_with_name(
- -- Id of the slice to check descendants of.
- id INT,
- -- Name of potential descendant slice.
- descendant_name STRING
-)
--- Whether `descendant_name` is a name of an descendant slice.
-RETURNS BOOL AS
-SELECT EXISTS(
- SELECT 1
- FROM descendant_slice($id)
- WHERE name = $descendant_name
- LIMIT 1
-);
+INCLUDE PERFETTO MODULE chrome.scroll_jank.utils;
-- Finds the end timestamp for a given slice's descendant with a given name.
-- If there are multiple descendants with a given name, the function will return the
@@ -58,9 +43,8 @@ SELECT
FROM slice
WHERE $id = id;
-
-- Grabs all gesture updates with respective scroll ids and start/end
--- timestamps, regardless of being coalesced.
+-- timestamps, regardless of being presented.
CREATE PERFETTO TABLE chrome_gesture_scroll_updates(
-- The start timestamp of the scroll.
ts INT,
@@ -72,82 +56,100 @@ CREATE PERFETTO TABLE chrome_gesture_scroll_updates(
scroll_update_id INT,
-- The id of the scroll.
scroll_id INT,
- -- Whether this input event was coalesced.
- is_coalesced BOOL
+ -- Whether this input event was presented.
+ is_presented BOOL,
+ -- Frame presentation timestamp aka the timestamp of the
+ -- SwapEndToPresentationCompositorFrame substage.
+ presentation_timestamp INT,
+ -- EventLatency event type.
+ event_type INT
) AS
SELECT
- ts,
- dur,
- id,
- -- TODO(b/250089570) Add trace_id to EventLatency and update this script to use it.
- EXTRACT_ARG(arg_set_id, 'chrome_latency_info.trace_id') AS scroll_update_id,
- EXTRACT_ARG(arg_set_id, 'chrome_latency_info.gesture_scroll_id') AS scroll_id,
- EXTRACT_ARG(arg_set_id, 'chrome_latency_info.is_coalesced') AS is_coalesced
-FROM slice
-WHERE name = "InputLatency::GestureScrollUpdate" AND dur != -1;
+ slice.ts,
+ slice.dur,
+ slice.id,
+ EXTRACT_arg(arg_set_id, "event_latency.event_latency_id") AS scroll_update_id,
+ chrome_get_most_recent_scroll_begin_id(slice.ts) AS scroll_id,
+ has_descendant_slice_with_name(slice.id, "SubmitCompositorFrameToPresentationCompositorFrame")
+ AS is_presented,
+ _descendant_slice_end(slice.id, "SwapEndToPresentationCompositorFrame") AS presentation_timestamp,
+ EXTRACT_ARG(arg_set_id, 'event_latency.event_type') AS event_type
+FROM slice JOIN args USING(arg_set_id)
+WHERE name = "EventLatency"
+AND args.string_value GLOB "*GESTURE_SCROLL_UPDATE";
-CREATE PERFETTO TABLE _non_coalesced_gesture_scrolls AS
+CREATE PERFETTO TABLE _presented_gesture_scrolls AS
SELECT
id,
ts,
dur,
scroll_update_id,
- scroll_id
-FROM chrome_gesture_scroll_updates
-WHERE is_coalesced = false
+ scroll_id,
+ presentation_timestamp,
+ event_type
+FROM chrome_gesture_scroll_updates
+WHERE is_presented = true
ORDER BY ts ASC;
-- Scroll updates, corresponding to all input events that were converted to a
-- presented scroll update.
CREATE PERFETTO TABLE chrome_presented_gesture_scrolls(
- -- Minimum slice id for input presented in this frame, the non coalesced input.
+ -- Minimum slice id for input presented in this frame, the non-presented input.
id INT,
-- The start timestamp for producing the frame.
ts INT,
-- The duration between producing and presenting the frame.
dur INT,
- -- The timestamp of the last input that arrived and got coalesced into the frame.
- last_coalesced_input_ts INT,
+ -- The timestamp of the last input that arrived and got presented in the frame.
+ last_presented_input_ts INT,
-- The id of the scroll update event, a unique identifier to the gesture.
scroll_update_id INT,
-- The id of the ongoing scroll.
- scroll_id INT
+ scroll_id INT,
+ -- Frame presentation timestamp.
+ presentation_timestamp INT,
+ -- EventLatency event type.
+ event_type INT
) AS
WITH
-scroll_updates_with_coalesce_info as MATERIALIZED (
+scroll_updates_with_presentation_info as MATERIALIZED (
SELECT
id,
ts,
- -- For each scroll update, find the latest non-coalesced update which
- -- happened before it. For coalesced scroll updates, this will be the
- -- presented scroll update they have been coalesced into.
+ -- For each scroll update, find the latest presented update which
+ -- started before it.
(
SELECT id
- FROM _non_coalesced_gesture_scrolls non_coalesced
- WHERE non_coalesced.ts <= scroll_update.ts
+ FROM _presented_gesture_scrolls _presented
+ WHERE _presented.ts <= scroll_update.ts
ORDER BY ts DESC
LIMIT 1
- ) as coalesced_to_scroll_update_slice_id
+ ) as presented_to_scroll_update_slice_id
FROM chrome_gesture_scroll_updates scroll_update
- ORDER BY coalesced_to_scroll_update_slice_id, ts
+ ORDER BY presented_to_scroll_update_slice_id, ts
)
SELECT
id,
ts,
dur,
- -- Find the latest input that was coalesced into this scroll update.
+ -- Find the latest input that was presented in this scroll update.
(
- SELECT coalesce_info.ts
- FROM scroll_updates_with_coalesce_info coalesce_info
+ SELECT presentation_info.ts
+ FROM scroll_updates_with_presentation_info presentation_info
WHERE
- coalesce_info.coalesced_to_scroll_update_slice_id =
- _non_coalesced_gesture_scrolls.id
+ presentation_info.presented_to_scroll_update_slice_id =
+ _presented_gesture_scrolls.id
ORDER BY ts DESC
LIMIT 1
- ) as last_coalesced_input_ts,
+ ) as last_presented_input_ts,
scroll_update_id,
- scroll_id
-FROM _non_coalesced_gesture_scrolls;
+ scroll_id,
+ presentation_timestamp,
+ event_type
+FROM _presented_gesture_scrolls
+-- TODO(b/247542163): remove this condition when all stages
+-- of EventLatency are recorded correctly.
+WHERE presentation_timestamp IS NOT NULL;
-- Associate every trace_id with it's perceived delta_y on the screen after
-- prediction.
@@ -163,50 +165,15 @@ SELECT
FROM slice
WHERE name = "InputHandlerProxy::HandleGestureScrollUpdate_Result";
--- Extract event latency timestamps, to later use it for joining
--- with gesture scroll updates, as event latencies don't have trace
--- ids associated with it.
-CREATE PERFETTO TABLE chrome_gesture_scroll_event_latencies(
- -- Start timestamp for the EventLatency.
- ts INT,
- -- Slice id of the EventLatency.
- event_latency_id INT,
- -- Duration of the EventLatency.
- dur INT,
- -- End timestamp for input aka the timestamp of the LatchToSwapEnd substage.
- input_latency_end_ts INT,
- -- Frame presentation timestamp aka the timestamp of the
- -- SwapEndToPresentationCompositorFrame substage.
- presentation_timestamp INT,
- -- EventLatency event type.
- event_type INT
-) AS
-SELECT
- slice.ts,
- slice.id AS event_latency_id,
- slice.dur AS dur,
- _descendant_slice_end(slice.id, "LatchToSwapEnd") AS input_latency_end_ts,
- _descendant_slice_end(slice.id, "SwapEndToPresentationCompositorFrame") AS presentation_timestamp,
- EXTRACT_ARG(arg_set_id, 'event_latency.event_type') AS event_type
-FROM slice
-WHERE name = "EventLatency"
- AND event_type in (
- "GESTURE_SCROLL_UPDATE",
- "FIRST_GESTURE_SCROLL_UPDATE",
- "INERTIAL_GESTURE_SCROLL_UPDATE")
- AND _has_descendant_slice_with_name(slice.id, "SwapEndToPresentationCompositorFrame");
-
--- Join presented gesture scrolls with their respective event
--- latencies based on |LatchToSwapEnd| timestamp, as it's the
--- end timestamp for both the gesture scroll update slice and
--- the LatchToSwapEnd slice.
+-- Obtain the subset of input events that were fully presented, as indicated
+-- by the presence of SwapEndToPresentationCompositorFrame.
CREATE PERFETTO TABLE chrome_full_frame_view(
-- ID of the frame.
id INT,
-- Start timestamp of the frame.
ts INT,
- -- The timestamp of the last coalesced input.
- last_coalesced_input_ts INT,
+ -- The timestamp of the last presented input.
+ last_presented_input_ts INT,
-- ID of the associated scroll.
scroll_id INT,
-- ID of the associated scroll update.
@@ -221,16 +188,18 @@ CREATE PERFETTO TABLE chrome_full_frame_view(
SELECT
frames.id,
frames.ts,
- frames.last_coalesced_input_ts,
+ frames.last_presented_input_ts,
frames.scroll_id,
frames.scroll_update_id,
- events.event_latency_id,
- events.dur,
- events.presentation_timestamp
+ frames.id AS event_latency_id,
+ frames.dur,
+ frames.presentation_timestamp
FROM chrome_presented_gesture_scrolls frames
-JOIN chrome_gesture_scroll_event_latencies events
- ON frames.ts = events.ts
- AND events.input_latency_end_ts = (frames.ts + frames.dur);
+WHERE frames.event_type in (
+ "GESTURE_SCROLL_UPDATE",
+ "FIRST_GESTURE_SCROLL_UPDATE",
+ "INERTIAL_GESTURE_SCROLL_UPDATE")
+ AND has_descendant_slice_with_name(frames.id, "SwapEndToPresentationCompositorFrame");
-- Join deltas with EventLatency data.
CREATE PERFETTO TABLE chrome_full_frame_delta_view(
@@ -242,8 +211,8 @@ CREATE PERFETTO TABLE chrome_full_frame_delta_view(
scroll_id INT,
-- ID of the associated scroll update.
scroll_update_id INT,
- -- The timestamp of the last coalesced input.
- last_coalesced_input_ts INT,
+ -- The timestamp of the last presented input.
+ last_presented_input_ts INT,
-- The perceived delta_y on the screen post prediction.
delta_y INT,
-- ID of the associated EventLatency.
@@ -258,7 +227,7 @@ SELECT
frames.ts,
frames.scroll_id,
frames.scroll_update_id,
- frames.last_coalesced_input_ts,
+ frames.last_presented_input_ts,
deltas.delta_y,
frames.event_latency_id,
frames.dur,
@@ -272,7 +241,7 @@ LEFT JOIN chrome_scroll_updates_with_deltas deltas
CREATE PERFETTO VIEW chrome_merged_frame_view(
-- ID of the frame.
id INT,
- -- The timestamp of the last coalesced input.
+ -- The timestamp of the last presented input.
max_start_ts INT,
-- The earliest frame start timestamp.
min_start_ts INT,
@@ -295,7 +264,7 @@ CREATE PERFETTO VIEW chrome_merged_frame_view(
) AS
SELECT
id,
- MAX(last_coalesced_input_ts) AS max_start_ts,
+ MAX(last_presented_input_ts) AS max_start_ts,
MIN(ts) AS min_start_ts,
scroll_id,
scroll_update_id,
@@ -461,7 +430,7 @@ CREATE PERFETTO VIEW chrome_unique_frame_presentation_ts(
) AS
SELECT DISTINCT
presentation_timestamp
-FROM chrome_gesture_scroll_event_latencies;
+FROM chrome_presented_gesture_scrolls;
-- Dividing missed frames over total frames to get janky frame percentage.
-- This represents the v3 scroll jank metrics.
diff --git a/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/scroll_offsets.sql b/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/scroll_offsets.sql
index 8df09370b..f1bfd3723 100644
--- a/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/scroll_offsets.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/scroll_offsets.sql
@@ -29,189 +29,104 @@
-- various stages of input processing, and are unified by a single
-- scroll_update_id value, recorded as scroll_deltas.trace_id in each event.
+INCLUDE PERFETTO MODULE chrome.chrome_scrolls;
INCLUDE PERFETTO MODULE chrome.scroll_jank.scroll_jank_v3;
--- Non-coalesced scroll update events and their timestamps.
-CREATE PERFETTO VIEW _non_coalesced_scrolls AS
-SELECT
- scroll_update_id,
- ts
-FROM chrome_gesture_scroll_updates
-WHERE is_coalesced = False;
-
-- All (coalesced and non-coalesced) vertical scrolling deltas and their
-- associated scroll ids. Delta values are recorded after being scaled to the
-- device's screen size in the TranslateAndScaleWebInputEvent trace event. In
-- this trace event, the deltas recorded represent the true (read "original")
-- values that the Browser receives from Android, and the only processing is
-- scaling and translation.
-CREATE PERFETTO TABLE _scroll_deltas AS
+CREATE PERFETTO TABLE _translate_and_scale_scroll_deltas AS
SELECT
EXTRACT_ARG(arg_set_id, 'scroll_deltas.trace_id') AS scroll_update_id,
- EXTRACT_ARG(arg_set_id, 'scroll_deltas.original_delta_y') AS delta_y,
- EXTRACT_ARG(arg_set_id, 'scroll_deltas.original_delta_y') IS NOT NULL AS is_coalesced
-FROM slice
-WHERE name = "TranslateAndScaleWebInputEvent";
-
--- Associate the raw (original) deltas (_scroll_deltas) with the
--- corresponding non-coalesced scroll updates
--- (_non_coalesced_scroll_updates) to get the timestamp of the event
--- those deltas. This allows for ordering delta recordings to track them over
--- time.
-CREATE PERFETTO VIEW _non_coalesced_deltas AS
-SELECT
- scroll_update_id,
- ts,
- delta_y
-FROM _non_coalesced_scrolls
-INNER JOIN _scroll_deltas
- USING (scroll_update_id);
-
--- Selecting information scroll update events that have been coalesced,
--- including timestamp and the specific event (scroll update id) it was
--- coalesced into. Recordings of deltas will need to be associated with the
--- timestamp of the scroll update they were coalesced into.
-CREATE PERFETTO TABLE _scroll_update_coalesce_info AS
-SELECT
- ts,
- EXTRACT_ARG(arg_set_id, 'scroll_deltas.coalesced_to_trace_id') AS coalesced_to_scroll_update_id,
- EXTRACT_ARG(arg_set_id, 'scroll_deltas.trace_id') AS scroll_update_id
+ EXTRACT_ARG(arg_set_id, 'scroll_deltas.original_delta_y') AS delta_y
FROM slice
-WHERE name = "WebCoalescedInputEvent::CoalesceWith" AND
- coalesced_to_scroll_update_id IS NOT NULL;
+WHERE slice.name = 'TranslateAndScaleWebInputEvent';
--- Associate the raw (original) deltas (_scroll_deltas) with the
--- corresponding coalesced scroll updates (_scroll_update_coalesce_info)
--- to get the timestamp of the event those deltas were coalesced into. This
--- allows us to get the scaled coordinates for all of the input events
--- (original input coordinates can't be used due to scaling).
-CREATE PERFETTO VIEW _coalesced_deltas AS
+-- Associate the gesture scroll update OS timestamp with the delta.
+CREATE PERFETTO TABLE _scroll_deltas_with_timestamp AS
SELECT
- _scroll_update_coalesce_info.coalesced_to_scroll_update_id AS scroll_update_id,
- ts,
- _scroll_deltas.delta_y AS delta_y,
- TRUE AS is_coalesced
-FROM _scroll_update_coalesce_info
-LEFT JOIN _scroll_deltas
- USING (scroll_update_id);
+ slice.ts AS input_ts,
+ data.scroll_update_id,
+ data.delta_y
+FROM _translate_and_scale_scroll_deltas data
+ JOIN slice ON slice.name = 'EventLatency'
+ AND data.scroll_update_id = EXTRACT_ARG(arg_set_id,
+ 'event_latency.event_latency_id');
--- All of the presented frame scroll update ids.
-CREATE PERFETTO VIEW chrome_deltas_presented_frame_scroll_update_ids(
- -- A scroll update id that was included in the presented frame.
- -- There may be zero, one, or more.
- scroll_update_id INT,
- -- Slice id
- id INT
-) AS
+-- Associate the scroll update/delta with the correct scroll.
+CREATE PERFETTO TABLE _scroll_deltas_with_scroll_id AS
SELECT
- args.int_value AS scroll_update_id,
- slice.id
-FROM args
-LEFT JOIN slice
- USING (arg_set_id)
-WHERE slice.name = 'PresentedFrameInformation'
-AND args.flat_key GLOB 'scroll_deltas.trace_ids_in_gpu_frame*';;
+ scrolls.id AS scroll_id,
+ deltas.input_ts,
+ deltas.scroll_update_id,
+ deltas.delta_y
+FROM _scroll_deltas_with_timestamp deltas
+ LEFT JOIN chrome_scrolls scrolls
+ ON deltas.input_ts >= scrolls.ts
+ AND deltas.input_ts <= scrolls.ts + scrolls.dur;
--- When every GestureScrollUpdate event is processed, the offset set by the
--- compositor is recorded. This offset is scaled to the device screen size, and
--- can be used to calculate deltas.
-CREATE PERFETTO VIEW _presented_frame_offsets AS
+-- Associate the presentation timestamp/deltas with the user deltas.
+CREATE PERFETTO TABLE _scroll_deltas_with_delays AS
SELECT
- EXTRACT_ARG(arg_set_id, 'scroll_deltas.trace_id') AS scroll_update_id,
- EXTRACT_ARG(arg_set_id, 'scroll_deltas.visual_offset_y') AS visual_offset_y
-FROM slice
-WHERE name = 'InputHandlerProxy::HandleGestureScrollUpdate_Result';
+ deltas.scroll_id,
+ delay.total_delta,
+ delay.scroll_update_id,
+ delay.presentation_timestamp AS presentation_timestamp,
+ deltas.input_ts,
+ deltas.delta_y
+FROM _scroll_deltas_with_scroll_id AS deltas
+ LEFT JOIN chrome_frame_info_with_delay AS delay USING(scroll_update_id);
-- The raw coordinates and pixel offsets for all input events which were part of
--- a scroll. This includes input events that were converted to scroll events
--- which were presented (_non_coalesced_scrolls) and scroll events which
--- were coalesced (_coalesced_deltas).
+-- a scroll.
CREATE PERFETTO TABLE chrome_scroll_input_offsets(
-- Trace id associated with the scroll.
+ scroll_id INT,
+ -- Trace id associated with the scroll.
scroll_update_id INT,
-- Timestamp the of the scroll input event.
ts INT,
- -- The delta in raw coordinates between this scroll update event and the previous.
+ -- The delta in raw coordinates between this scroll update event and the
+ -- previous.
delta_y INT,
- -- The pixel offset of this scroll update event compared to the previous one.
- offset_y INT
+ -- The pixel offset of this scroll update event compared to the initial one.
+ relative_offset_y INT
) AS
--- First collect all coalesced and non-coalesced deltas so that the offsets
--- can be calculated from them in order of timestamp.
-WITH all_deltas AS (
- SELECT
- scroll_update_id,
- ts,
- delta_y
- FROM _non_coalesced_deltas
- WHERE delta_y IS NOT NULL
- UNION
- SELECT
- scroll_update_id,
- ts,
- delta_y
- FROM _coalesced_deltas
- WHERE delta_y IS NOT NULL
- ORDER BY scroll_update_id, ts)
SELECT
+ scroll_id,
scroll_update_id,
- ts,
+ input_ts AS ts,
delta_y,
- SUM(IFNULL(delta_y, 0)) OVER (
- ORDER BY scroll_update_id, ts
- ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS offset_y
-FROM all_deltas;
-
--- Calculate the total visual offset for all presented frames (non-coalesced
--- scroll updates) that have raw deltas recorded. These visual offsets
--- correspond with the inverse of the deltas for the presented frame.
-CREATE PERFETTO VIEW _preprocessed_presented_frame_offsets AS
-SELECT
- chrome_full_frame_view.scroll_update_id,
- chrome_full_frame_view.presentation_timestamp AS ts,
- chrome_deltas_presented_frame_scroll_update_ids.id,
- _presented_frame_offsets.visual_offset_y -
- LAG(_presented_frame_offsets.visual_offset_y)
- OVER (ORDER BY chrome_full_frame_view.presentation_timestamp)
- AS presented_frame_visual_offset_y
-FROM chrome_full_frame_view
-LEFT JOIN _scroll_deltas
- USING (scroll_update_id)
-LEFT JOIN chrome_deltas_presented_frame_scroll_update_ids
- USING (scroll_update_id)
-LEFT JOIN _presented_frame_offsets
- USING (scroll_update_id)
-WHERE _scroll_deltas.delta_y IS NOT NULL;
+ SUM(IFNULL(delta_y, 0)) OVER ( PARTITION BY scroll_id
+ ORDER BY scroll_update_id, input_ts
+ ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS relative_offset_y
+FROM _scroll_deltas_with_delays;
-- The scrolling offsets for the actual (applied) scroll events. These are not
-- necessarily inclusive of all user scroll events, rather those scroll events
-- that are actually processed.
CREATE PERFETTO TABLE chrome_presented_scroll_offsets(
- -- Trace Id associated with the scroll.
+ -- Trace id associated with the scroll.
+ scroll_id INT,
+ -- Trace id associated with the scroll update event.
scroll_update_id INT,
-- Presentation timestamp.
ts INT,
- -- The delta in coordinates as processed by Chrome between this scroll update
- -- event and the previous.
+ -- The delta in raw coordinates between this scroll update event and the
+ -- previous.
delta_y INT,
- -- The pixel offset of this scroll update (the presented frame) compared to
- -- the previous one.
- offset_y INT
+ -- The pixel offset of this scroll update event compared to the initial one.
+ relative_offset_y INT
) AS
-WITH all_deltas AS (
- SELECT
- scroll_update_id,
- id,
- MAX(ts) AS ts,
- SUM(presented_frame_visual_offset_y) * -1 AS delta_y
- FROM _preprocessed_presented_frame_offsets
- GROUP BY id
- ORDER BY ts)
SELECT
+ scroll_id,
scroll_update_id,
- ts,
- delta_y,
- SUM(IFNULL(delta_y, 0)) OVER (
- ORDER BY scroll_update_id, ts
- ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS offset_y
-FROM all_deltas;
+ presentation_timestamp AS ts,
+ total_delta AS delta_y,
+ SUM(IFNULL(total_delta, 0)) OVER ( PARTITION BY scroll_id
+ ORDER BY scroll_update_id, presentation_timestamp
+ ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS relative_offset_y
+FROM _scroll_deltas_with_delays; \ No newline at end of file
diff --git a/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/utils.sql b/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/utils.sql
index 74141e6c3..b7b6badd1 100644
--- a/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/utils.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/utils.sql
@@ -103,3 +103,18 @@ FROM slice s
WHERE
category GLOB "*scheduler.long_tasks*"
AND name = $name;
+
+-- Extracts scroll id for the EventLatency slice at `ts`.
+CREATE PERFETTO FUNCTION chrome_get_most_recent_scroll_begin_id(
+ -- Timestamp of the EventLatency slice to get the scroll id for.
+ ts INT)
+-- The event_latency_id of the EventLatency slice with the type
+-- GESTURE_SCROLL_BEGIN that is the closest to `ts`.
+RETURNS INT AS
+SELECT EXTRACT_ARG(arg_set_id, "event_latency.event_latency_id")
+FROM slice
+WHERE name="EventLatency"
+AND EXTRACT_ARG(arg_set_id, "event_latency.event_type") = "GESTURE_SCROLL_BEGIN"
+AND ts<=$ts
+ORDER BY ts DESC
+LIMIT 1;
diff --git a/src/trace_processor/perfetto_sql/stdlib/common/OWNERS b/src/trace_processor/perfetto_sql/stdlib/common/OWNERS
index a2788c4dd..0a16b3f25 100644
--- a/src/trace_processor/perfetto_sql/stdlib/common/OWNERS
+++ b/src/trace_processor/perfetto_sql/stdlib/common/OWNERS
@@ -6,4 +6,3 @@ lalitm@google.com
# For emergency reviews
primiano@google.com
-hjd@google.com
diff --git a/src/trace_processor/perfetto_sql/stdlib/graphs/partition.sql b/src/trace_processor/perfetto_sql/stdlib/graphs/partition.sql
index 44cd8cef7..d406aea3c 100644
--- a/src/trace_processor/perfetto_sql/stdlib/graphs/partition.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/graphs/partition.sql
@@ -22,30 +22,34 @@
--
-- Example:
-- Input
+--
-- id | parent_id | group_key
--- ---+-----------+--------
+-- ---|-----------|----------
-- 1 | NULL | 1
-- 2 | 1 | 1
--- 3 | 2 | 2
--- 4 | 2 | 2
--- 5 | 4 | 1
--- 6 | 4 | 3
+-- 3 | NULL | 2
+-- 4 | NULL | 2
+-- 5 | 2 | 1
+-- 6 | NULL | 3
-- 7 | 4 | 2
+-- 8 | 4 | 1
--
-- Or as a graph:
+-- ```
-- 1 (1)
-- /
-- 2 (1)
-- / \
-- 3 (2) 4 (2)
--- \
--- 5 (1)
--- / \
--- 6 (3) 7 (2)
---
+-- / \
+-- 5 (1) 8 (1)
+-- / \
+-- 6 (3) 7 (2)
+-- ```
-- Possible output (order of rows is implementation-defined)
+--
-- id | parent_id | group_key
--- ---+-----------+-------
+-- ---|-----------|-------
-- 1 | NULL | 1
-- 2 | 1 | 1
-- 3 | NULL | 2
@@ -53,13 +57,16 @@
-- 5 | 2 | 1
-- 6 | NULL | 3
-- 7 | 4 | 2
+-- 8 | 2 | 1
--
-- Or as a forest:
--- 1 (1) 3 (2) 4 (2) 6 (3)
--- | |
--- 2 (1) 7 (2)
--- |
--- 5 (1)
+-- ```
+-- 1 (1) 3 (2) 4 (2) 6 (3)
+-- | |
+-- 2 (1) 7 (2)
+-- / \
+-- 5 (1) 8 (1)
+-- ```
CREATE PERFETTO MACRO tree_structural_partition_by_group(
-- A table/view/subquery corresponding to a tree which should be partitioned.
-- This table must have the columns "id", "parent_id" and "group_key".
diff --git a/src/trace_processor/perfetto_sql/stdlib/memory/heap_graph_dominator_tree.sql b/src/trace_processor/perfetto_sql/stdlib/memory/heap_graph_dominator_tree.sql
index 28edf7f3b..f0cfae106 100644
--- a/src/trace_processor/perfetto_sql/stdlib/memory/heap_graph_dominator_tree.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/memory/heap_graph_dominator_tree.sql
@@ -18,20 +18,21 @@ INCLUDE PERFETTO MODULE graphs.dominator_tree;
-- Excluding following types from the graph as they share objects' ownership
-- with their real (more interesting) owners and will mask their idom to be the
-- "super root".
-CREATE PERFETTO TABLE _excluded_type_ids AS
-WITH RECURSIVE class_visitor(type_id) AS (
- SELECT id AS type_id
- FROM heap_graph_class
- WHERE name IN (
- 'java.lang.ref.PhantomReference',
- 'java.lang.ref.FinalizerReference'
- )
- UNION ALL
- SELECT child.id AS type_id
- FROM heap_graph_class child
- JOIN class_visitor parent ON parent.type_id = child.superclass_id
-)
-SELECT * FROM class_visitor;
+CREATE PERFETTO TABLE _ref_type_ids AS
+SELECT id AS type_id FROM heap_graph_class
+WHERE kind IN (
+ 'KIND_FINALIZER_REFERENCE',
+ 'KIND_PHANTOM_REFERENCE',
+ 'KIND_SOFT_REFERENCE',
+ 'KIND_WEAK_REFERENCE');
+
+CREATE PERFETTO TABLE _excluded_refs AS
+SELECT ref.id
+ FROM _ref_type_ids
+ JOIN heap_graph_object robj USING (type_id)
+ JOIN heap_graph_reference ref USING (reference_set_id)
+WHERE ref.field_name = 'java.lang.ref.Reference.referent'
+ORDER BY ref.id;
-- The assigned id of the "super root".
-- Since a Java heap graph is a "forest" structure, we need to add a imaginary
@@ -48,7 +49,8 @@ SELECT
ref.owned_id AS dest_node_id
FROM heap_graph_reference ref
JOIN heap_graph_object source_node ON ref.owner_id = source_node.id
-WHERE source_node.reachable AND source_node.type_id NOT IN _excluded_type_ids
+WHERE source_node.reachable
+ AND ref.id NOT IN _excluded_refs
AND ref.owned_id IS NOT NULL
UNION ALL
SELECT
diff --git a/src/trace_processor/perfetto_sql/stdlib/viz/summary/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/viz/summary/BUILD.gn
new file mode 100644
index 000000000..3f664bd72
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/viz/summary/BUILD.gn
@@ -0,0 +1,24 @@
+# Copyright (C) 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import("../../../../../../gn/perfetto_sql.gni")
+
+perfetto_sql_source_set("summary") {
+ sources = [
+ "processes.sql",
+ "slices.sql",
+ "threads.sql",
+ "tracks.sql",
+ ]
+}
diff --git a/src/trace_processor/perfetto_sql/stdlib/viz/summary/processes.sql b/src/trace_processor/perfetto_sql/stdlib/viz/summary/processes.sql
new file mode 100644
index 000000000..18aa202fb
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/viz/summary/processes.sql
@@ -0,0 +1,93 @@
+--
+-- Copyright 2024 The Android Open Source Project
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- https://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+
+INCLUDE PERFETTO MODULE viz.summary.slices;
+INCLUDE PERFETTO MODULE viz.summary.threads;
+
+CREATE PERFETTO TABLE _process_track_summary AS
+SELECT upid, SUM(cnt) AS slice_count
+FROM process_track
+JOIN _slice_track_summary USING (id)
+GROUP BY upid;
+
+CREATE PERFETTO TABLE _heap_profile_allocation_summary AS
+SELECT upid, COUNT() AS allocation_count
+FROM heap_profile_allocation
+GROUP BY upid;
+
+CREATE PERFETTO TABLE _heap_profile_graph_summary AS
+SELECT upid, COUNT() AS graph_object_count
+FROM heap_graph_object;
+
+CREATE PERFETTO TABLE _thread_process_grouped_summary AS
+SELECT
+ upid,
+ MAX(max_running_dur) AS max_running_dur,
+ SUM(sum_running_dur) AS sum_running_dur,
+ SUM(running_count) AS running_count,
+ SUM(slice_count) AS slice_count,
+ SUM(perf_sample_count) AS perf_sample_count
+FROM _thread_available_info_summary
+JOIN thread USING (utid)
+WHERE upid IS NOT NULL
+GROUP BY upid;
+
+CREATE PERFETTO TABLE _process_available_info_summary AS
+WITH r AS (
+ SELECT
+ upid,
+ t_summary.upid as summary_upid,
+ t_summary.max_running_dur AS max_running_dur,
+ t_summary.sum_running_dur,
+ t_summary.running_count,
+ t_summary.slice_count AS thread_slice_count,
+ t_summary.perf_sample_count AS perf_sample_count,
+ (
+ SELECT slice_count
+ FROM _process_track_summary
+ WHERE upid = p.upid
+ ) AS process_slice_count,
+ (
+ SELECT allocation_count
+ FROM _heap_profile_allocation_summary
+ WHERE upid = p.upid
+ ) AS allocation_count,
+ (
+ SELECT graph_object_count
+ FROM _heap_profile_graph_summary
+ WHERE upid = p.upid
+ ) AS graph_object_count
+ FROM process p
+ LEFT JOIN _thread_process_grouped_summary t_summary USING (upid)
+)
+SELECT
+ upid,
+ IFNULL(max_running_dur, 0) AS max_running_dur,
+ IFNULL(sum_running_dur, 0) AS sum_running_dur,
+ IFNULL(running_count, 0) AS running_count,
+ IFNULL(thread_slice_count, 0) AS thread_slice_count,
+ IFNULL(perf_sample_count, 0) AS perf_sample_count,
+ IFNULL(process_slice_count, 0) AS process_slice_count,
+ IFNULL(allocation_count, 0) AS allocation_count,
+ IFNULL(graph_object_count, 0) AS graph_object_count
+FROM r
+WHERE
+ NOT(
+ r.summary_upid IS NULL
+ AND process_slice_count IS NULL
+ AND allocation_count IS NULL
+ AND graph_object_count IS NULL
+ )
+ OR upid IN (SELECT upid FROM process_counter_track);
diff --git a/src/trace_processor/perfetto_sql/stdlib/viz/summary/slices.sql b/src/trace_processor/perfetto_sql/stdlib/viz/summary/slices.sql
new file mode 100644
index 000000000..c6988591b
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/viz/summary/slices.sql
@@ -0,0 +1,24 @@
+--
+-- Copyright 2024 The Android Open Source Project
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- https://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+
+CREATE PERFETTO TABLE _slice_track_summary AS
+SELECT
+ track_id as id,
+ COUNT() AS cnt,
+ MIN(dur) AS min_dur,
+ MAX(dur) AS max_dur,
+ MAX(depth) AS max_depth
+FROM slice
+GROUP BY track_id;
diff --git a/src/trace_processor/perfetto_sql/stdlib/viz/summary/threads.sql b/src/trace_processor/perfetto_sql/stdlib/viz/summary/threads.sql
new file mode 100644
index 000000000..ed74acf73
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/viz/summary/threads.sql
@@ -0,0 +1,77 @@
+--
+-- Copyright 2024 The Android Open Source Project
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- https://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+
+INCLUDE PERFETTO MODULE viz.summary.slices;
+
+CREATE PERFETTO TABLE _sched_summary AS
+SELECT
+ utid,
+ MAX(dur) AS max_running_dur,
+ SUM(dur) AS sum_running_dur,
+ COUNT() AS running_count
+FROM sched
+WHERE utid != 0 AND dur != -1
+GROUP BY utid;
+
+CREATE PERFETTO TABLE _thread_track_summary AS
+SELECT utid, SUM(cnt) AS slice_count
+FROM thread_track
+JOIN _slice_track_summary USING (id)
+GROUP BY utid;
+
+CREATE PERFETTO TABLE _perf_sample_summary AS
+SELECT utid, count() AS perf_sample_cnt
+FROM perf_sample
+WHERE callsite_id IS NOT NULL
+GROUP BY utid;
+
+CREATE PERFETTO TABLE _thread_available_info_summary AS
+WITH raw AS (
+ SELECT
+ utid,
+ ss.max_running_dur,
+ ss.sum_running_dur,
+ ss.running_count,
+ (
+ SELECT slice_count
+ FROM _thread_track_summary
+ WHERE utid = t.utid
+ ) AS slice_count,
+ (
+ SELECT perf_sample_cnt
+ FROM _perf_sample_summary
+ WHERE utid = t.utid
+ ) AS perf_sample_count
+ FROM thread t
+ LEFT JOIN _sched_summary ss USING (utid)
+)
+SELECT
+ utid,
+ IFNULL(max_running_dur, 0) AS max_running_dur,
+ IFNULL(sum_running_dur, 0) AS sum_running_dur,
+ IFNULL(running_count, 0) AS running_count,
+ IFNULL(slice_count, 0) AS slice_count,
+ IFNULL(perf_sample_count, 0) AS perf_sample_count
+FROM raw r
+WHERE
+ NOT (
+ r.max_running_dur IS NULL
+ AND r.sum_running_dur IS NULL
+ AND r.running_count IS NULL
+ AND r.slice_count IS NULL
+ AND r.perf_sample_count IS NULL
+ )
+ OR utid IN (SELECT utid FROM cpu_profile_stack_sample)
+ OR utid IN (SELECT utid FROM thread_counter_track);
diff --git a/src/trace_processor/perfetto_sql/stdlib/viz/summary/tracks.sql b/src/trace_processor/perfetto_sql/stdlib/viz/summary/tracks.sql
new file mode 100644
index 000000000..7a1d5d081
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/viz/summary/tracks.sql
@@ -0,0 +1,36 @@
+--
+-- Copyright 2024 The Android Open Source Project
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- https://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+
+INCLUDE PERFETTO MODULE viz.summary.slices;
+
+CREATE PERFETTO TABLE _process_track_summary_by_upid_and_name AS
+SELECT
+ upid,
+ name,
+ GROUP_CONCAT(id) AS track_ids,
+ COUNT() AS track_count
+FROM process_track
+JOIN _slice_track_summary USING (id)
+GROUP BY upid, name;
+
+CREATE PERFETTO TABLE _uid_track_track_summary_by_uid_and_name AS
+SELECT
+ uid,
+ name,
+ GROUP_CONCAT(id) AS track_ids,
+ COUNT() AS track_count
+FROM uid_track
+JOIN _slice_track_summary USING (id)
+GROUP BY uid, name;
diff --git a/src/trace_processor/sqlite/db_sqlite_table.cc b/src/trace_processor/sqlite/db_sqlite_table.cc
index eb612806f..dc64f347b 100644
--- a/src/trace_processor/sqlite/db_sqlite_table.cc
+++ b/src/trace_processor/sqlite/db_sqlite_table.cc
@@ -138,20 +138,22 @@ std::string CreateTableStatementFromSchema(const Table::Schema& schema,
return stmt;
}
-base::StatusOr<SqlValue> SqliteValueToSqlValueChecked(sqlite3_value* value,
- const Constraint& cs) {
- SqlValue v = sqlite::utils::SqliteValueToSqlValue(value);
+int SqliteValueToSqlValueChecked(SqlValue* sql_val,
+ sqlite3_value* value,
+ const Constraint& cs,
+ sqlite3_vtab* vtab) {
+ *sql_val = sqlite::utils::SqliteValueToSqlValue(value);
if constexpr (regex::IsRegexSupported()) {
if (cs.op == FilterOp::kRegex) {
if (cs.value.type != SqlValue::kString) {
- return base::ErrStatus("Value has to be a string");
+ return sqlite::utils::SetError(vtab, "Value has to be a string");
}
if (auto st = regex::Regex::Create(cs.value.AsString()); !st.ok()) {
- return st.status();
+ return sqlite::utils::SetError(vtab, st.status().c_message());
}
}
}
- return v;
+ return SQLITE_OK;
}
int UpdateConstraintsAndOrderByFromIndex(DbSqliteModule::Cursor* c,
@@ -166,18 +168,18 @@ int UpdateConstraintsAndOrderByFromIndex(DbSqliteModule::Cursor* c,
// We reuse this vector to reduce memory allocations on nested subqueries.
uint32_t c_offset = 0;
- c->constraints.resize(cs_count);
- for (auto& cs : c->constraints) {
+ c->query.constraints.resize(cs_count);
+ for (auto& cs : c->query.constraints) {
PERFETTO_CHECK(splitter.Next());
cs.col_idx = *base::CStringToUInt32(splitter.cur_token());
PERFETTO_CHECK(splitter.Next());
cs.op = static_cast<FilterOp>(*base::CStringToUInt32(splitter.cur_token()));
- auto value_or = SqliteValueToSqlValueChecked(argv[c_offset++], cs);
- if (!value_or.ok()) {
- return sqlite::utils::SetError(c->pVtab, value_or.status().c_message());
+ if (int ret = SqliteValueToSqlValueChecked(&cs.value, argv[c_offset++], cs,
+ c->pVtab);
+ ret != SQLITE_OK) {
+ return ret;
}
- cs.value = *value_or;
}
PERFETTO_CHECK(splitter.Next());
@@ -187,8 +189,8 @@ int UpdateConstraintsAndOrderByFromIndex(DbSqliteModule::Cursor* c,
uint32_t ob_count = *base::CStringToUInt32(splitter.cur_token() + 1);
// We reuse this vector to reduce memory allocations on nested subqueries.
- c->orders.resize(ob_count);
- for (auto& ob : c->orders) {
+ c->query.orders.resize(ob_count);
+ for (auto& ob : c->query.orders) {
PERFETTO_CHECK(splitter.Next());
ob.col_idx = *base::CStringToUInt32(splitter.cur_token());
PERFETTO_CHECK(splitter.Next());
@@ -216,13 +218,13 @@ PERFETTO_ALWAYS_INLINE void TryCacheCreateSortedTable(
// If we have more than one constraint, we can't cache the table using
// this method.
- if (cursor->constraints.size() != 1) {
+ if (cursor->query.constraints.size() != 1) {
return;
}
// If the constraing is not an equality constraint, there's little
// benefit to caching
- const auto& c = cursor->constraints.front();
+ const auto& c = cursor->query.constraints.front();
if (c.op != FilterOp::kEq) {
return;
}
@@ -242,7 +244,7 @@ void FilterAndSortMetatrace(const std::string& table_name,
DbSqliteModule::Cursor* cursor,
metatrace::Record* r) {
r->AddArg("Table", table_name);
- for (const Constraint& c : cursor->constraints) {
+ for (const Constraint& c : cursor->query.constraints) {
SafeStringWriter writer;
writer.AppendString(schema.columns[c.col_idx].name);
@@ -303,7 +305,7 @@ void FilterAndSortMetatrace(const std::string& table_name,
r->AddArg("Constraint", writer.GetStringView());
}
- for (const auto& o : cursor->orders) {
+ for (const auto& o : cursor->query.orders) {
SafeStringWriter writer;
writer.AppendString(schema.columns[o.col_idx].name);
if (o.desc)
@@ -571,12 +573,12 @@ int DbSqliteModule::Filter(sqlite3_vtab_cursor* cursor,
size_t offset = c->table_function_arguments.size();
bool is_same_idx = idx_num == c->last_idx_num;
if (PERFETTO_LIKELY(is_same_idx)) {
- for (auto& cs : c->constraints) {
- auto value_or = SqliteValueToSqlValueChecked(argv[offset++], cs);
- if (!value_or.ok()) {
- return sqlite::utils::SetError(c->pVtab, value_or.status().c_message());
+ for (auto& cs : c->query.constraints) {
+ if (int ret = SqliteValueToSqlValueChecked(&cs.value, argv[offset++], cs,
+ c->pVtab);
+ ret != SQLITE_OK) {
+ return ret;
}
- cs.value = *value_or;
}
} else {
if (int r = UpdateConstraintsAndOrderByFromIndex(c, idx_str, argv + offset);
@@ -623,7 +625,7 @@ int DbSqliteModule::Filter(sqlite3_vtab_cursor* cursor,
const auto* source_table =
c->sorted_cache_table ? &*c->sorted_cache_table : c->upstream_table;
- RowMap filter_map = source_table->QueryToRowMap(c->constraints, c->orders);
+ RowMap filter_map = source_table->QueryToRowMap(c->query);
if (filter_map.IsRange() && filter_map.size() <= 1) {
// Currently, our criteria where we have a special fast path is if it's
// a single ranged row. We have this fast path for joins on id columns
diff --git a/src/trace_processor/sqlite/db_sqlite_table.h b/src/trace_processor/sqlite/db_sqlite_table.h
index 401c370a2..a94cbe693 100644
--- a/src/trace_processor/sqlite/db_sqlite_table.h
+++ b/src/trace_processor/sqlite/db_sqlite_table.h
@@ -109,8 +109,8 @@ struct DbSqliteModule : public sqlite::Module<DbSqliteModule> {
Mode mode = Mode::kSingleRow;
int last_idx_num = -1;
- std::vector<Constraint> constraints;
- std::vector<Order> orders;
+ Query query;
+
std::vector<SqlValue> table_function_arguments;
};
struct QueryCost {
diff --git a/src/trace_processor/storage/trace_storage.h b/src/trace_processor/storage/trace_storage.h
index 21b609081..792ad4f55 100644
--- a/src/trace_processor/storage/trace_storage.h
+++ b/src/trace_processor/storage/trace_storage.h
@@ -885,8 +885,9 @@ class TraceStorage {
const char* key,
std::optional<Variadic>* result) const {
const auto& args = arg_table();
- RowMap filtered = args.QueryToRowMap(
- {args.arg_set_id().eq(arg_set_id), args.key().eq(key)}, {});
+ Query q;
+ q.constraints = {args.arg_set_id().eq(arg_set_id), args.key().eq(key)};
+ RowMap filtered = args.QueryToRowMap(q);
if (filtered.empty()) {
*result = std::nullopt;
return util::OkStatus();
diff --git a/src/trace_processor/tables/BUILD.gn b/src/trace_processor/tables/BUILD.gn
index 8339d9533..7cf8bb0dd 100644
--- a/src/trace_processor/tables/BUILD.gn
+++ b/src/trace_processor/tables/BUILD.gn
@@ -77,6 +77,7 @@ if (enable_perfetto_benchmarks) {
"../../../include/perfetto/trace_processor",
"../containers",
"../db",
+ "../db/column",
]
sources = [ "py_tables_benchmark.cc" ]
}
diff --git a/src/trace_processor/tables/py_tables_benchmark.cc b/src/trace_processor/tables/py_tables_benchmark.cc
index 6aea3d515..ed19da17e 100644
--- a/src/trace_processor/tables/py_tables_benchmark.cc
+++ b/src/trace_processor/tables/py_tables_benchmark.cc
@@ -22,6 +22,7 @@
#include "perfetto/trace_processor/basic_types.h"
#include "src/trace_processor/containers/row_map.h"
#include "src/trace_processor/containers/string_pool.h"
+#include "src/trace_processor/db/column/types.h"
#include "src/trace_processor/db/table.h"
#include "src/trace_processor/tables/py_tables_benchmark_py.h"
@@ -56,6 +57,7 @@ void TableSortArgs(benchmark::internal::Benchmark* b) {
} // namespace
+using perfetto::trace_processor::Query;
using perfetto::trace_processor::RowMap;
using perfetto::trace_processor::SqlValue;
using perfetto::trace_processor::StringPool;
@@ -98,14 +100,15 @@ BENCHMARK(BM_TableIteratorChild)->Apply(TableFilterArgs);
static void BM_TableFilterRootId(benchmark::State& state) {
StringPool pool;
RootTestTable root(&pool);
+ Query q;
+ q.constraints = {root.id().eq(30)};
uint32_t size = static_cast<uint32_t>(state.range(0));
for (uint32_t i = 0; i < size; ++i)
root.Insert({});
for (auto _ : state) {
- benchmark::DoNotOptimize(
- root.ApplyAndIterateRows(root.QueryToRowMap({root.id().eq(30)}, {})));
+ benchmark::DoNotOptimize(root.ApplyAndIterateRows(root.QueryToRowMap(q)));
}
}
BENCHMARK(BM_TableFilterRootId)->Apply(TableFilterArgs);
@@ -113,6 +116,9 @@ BENCHMARK(BM_TableFilterRootId)->Apply(TableFilterArgs);
static void BM_TableFilterRootIdAndOther(benchmark::State& state) {
StringPool pool;
RootTestTable root(&pool);
+ Query q;
+ q.constraints = {root.id().eq(root.row_count() - 1),
+ root.root_non_null().gt(100)};
uint32_t size = static_cast<uint32_t>(state.range(0));
@@ -123,9 +129,7 @@ static void BM_TableFilterRootIdAndOther(benchmark::State& state) {
}
for (auto _ : state) {
- benchmark::DoNotOptimize(root.ApplyAndIterateRows(root.QueryToRowMap(
- {root.id().eq(root.row_count() - 1), root.root_non_null().gt(100)},
- {})));
+ benchmark::DoNotOptimize(root.ApplyAndIterateRows(root.QueryToRowMap(q)));
}
}
BENCHMARK(BM_TableFilterRootIdAndOther)->Apply(TableFilterArgs);
@@ -134,6 +138,8 @@ static void BM_TableFilterChildId(benchmark::State& state) {
StringPool pool;
RootTestTable root(&pool);
ChildTestTable child(&pool, &root);
+ Query q;
+ q.constraints = {child.id().eq(30)};
uint32_t size = static_cast<uint32_t>(state.range(0));
for (uint32_t i = 0; i < size; ++i) {
@@ -142,8 +148,7 @@ static void BM_TableFilterChildId(benchmark::State& state) {
}
for (auto _ : state) {
- benchmark::DoNotOptimize(child.ApplyAndIterateRows(
- child.QueryToRowMap({child.id().eq(30)}, {})));
+ benchmark::DoNotOptimize(child.ApplyAndIterateRows(child.QueryToRowMap(q)));
}
}
BENCHMARK(BM_TableFilterChildId)->Apply(TableFilterArgs);
@@ -152,6 +157,8 @@ static void BM_TableFilterChildIdAndSortedInRoot(benchmark::State& state) {
StringPool pool;
RootTestTable root(&pool);
ChildTestTable child(&pool, &root);
+ Query q;
+ q.constraints = {child.id().eq(30), child.root_sorted().gt(1024)};
uint32_t size = static_cast<uint32_t>(state.range(0));
for (uint32_t i = 0; i < size; ++i) {
@@ -165,8 +172,7 @@ static void BM_TableFilterChildIdAndSortedInRoot(benchmark::State& state) {
}
for (auto _ : state) {
- benchmark::DoNotOptimize(child.ApplyAndIterateRows(child.QueryToRowMap(
- {child.id().eq(30), child.root_sorted().gt(1024)}, {})));
+ benchmark::DoNotOptimize(child.ApplyAndIterateRows(child.QueryToRowMap(q)));
}
}
BENCHMARK(BM_TableFilterChildIdAndSortedInRoot)->Apply(TableFilterArgs);
@@ -174,6 +180,8 @@ BENCHMARK(BM_TableFilterChildIdAndSortedInRoot)->Apply(TableFilterArgs);
static void BM_TableFilterRootNonNullEqMatchMany(benchmark::State& state) {
StringPool pool;
RootTestTable root(&pool);
+ Query q;
+ q.constraints = {root.root_non_null().eq(0)};
auto size = static_cast<uint32_t>(state.range(0));
uint32_t partitions = size / 1024;
@@ -185,8 +193,7 @@ static void BM_TableFilterRootNonNullEqMatchMany(benchmark::State& state) {
}
for (auto _ : state) {
- benchmark::DoNotOptimize(root.ApplyAndIterateRows(
- root.QueryToRowMap({root.root_non_null().eq(0)}, {})));
+ benchmark::DoNotOptimize(root.ApplyAndIterateRows(root.QueryToRowMap(q)));
}
}
BENCHMARK(BM_TableFilterRootNonNullEqMatchMany)->Apply(TableFilterArgs);
@@ -194,6 +201,8 @@ BENCHMARK(BM_TableFilterRootNonNullEqMatchMany)->Apply(TableFilterArgs);
static void BM_TableFilterRootMultipleNonNull(benchmark::State& state) {
StringPool pool;
RootTestTable root(&pool);
+ Query q;
+ q.constraints = {root.root_non_null().lt(4), root.root_non_null_2().lt(10)};
auto size = static_cast<uint32_t>(state.range(0));
uint32_t partitions = size / 512;
@@ -207,8 +216,7 @@ static void BM_TableFilterRootMultipleNonNull(benchmark::State& state) {
}
for (auto _ : state) {
- benchmark::DoNotOptimize(root.ApplyAndIterateRows(root.QueryToRowMap(
- {root.root_non_null().lt(4), root.root_non_null_2().lt(10)}, {})));
+ benchmark::DoNotOptimize(root.ApplyAndIterateRows(root.QueryToRowMap(q)));
}
}
BENCHMARK(BM_TableFilterRootMultipleNonNull)->Apply(TableFilterArgs);
@@ -216,6 +224,8 @@ BENCHMARK(BM_TableFilterRootMultipleNonNull)->Apply(TableFilterArgs);
static void BM_TableFilterRootNullableEqMatchMany(benchmark::State& state) {
StringPool pool;
RootTestTable root(&pool);
+ Query q;
+ q.constraints = {root.root_nullable().eq(1)};
auto size = static_cast<uint32_t>(state.range(0));
uint32_t partitions = size / 512;
@@ -231,8 +241,7 @@ static void BM_TableFilterRootNullableEqMatchMany(benchmark::State& state) {
}
for (auto _ : state) {
- benchmark::DoNotOptimize(root.ApplyAndIterateRows(
- root.QueryToRowMap({root.root_nullable().eq(1)}, {})));
+ benchmark::DoNotOptimize(root.ApplyAndIterateRows(root.QueryToRowMap(q)));
}
}
BENCHMARK(BM_TableFilterRootNullableEqMatchMany)->Apply(TableFilterArgs);
@@ -241,6 +250,8 @@ static void BM_TableFilterChildNonNullEqMatchMany(benchmark::State& state) {
StringPool pool;
RootTestTable root(&pool);
ChildTestTable child(&pool, &root);
+ Query q;
+ q.constraints = {child.child_non_null().eq(0)};
auto size = static_cast<uint32_t>(state.range(0));
uint32_t partitions = size / 1024;
@@ -254,8 +265,7 @@ static void BM_TableFilterChildNonNullEqMatchMany(benchmark::State& state) {
}
for (auto _ : state) {
- benchmark::DoNotOptimize(child.ApplyAndIterateRows(
- child.QueryToRowMap({child.child_non_null().eq(0)}, {})));
+ benchmark::DoNotOptimize(child.ApplyAndIterateRows(child.QueryToRowMap(q)));
}
}
BENCHMARK(BM_TableFilterChildNonNullEqMatchMany)->Apply(TableFilterArgs);
@@ -264,6 +274,8 @@ static void BM_TableFilterChildNullableEqMatchMany(benchmark::State& state) {
StringPool pool;
RootTestTable root(&pool);
ChildTestTable child(&pool, &root);
+ Query q;
+ q.constraints = {child.child_nullable().eq(1)};
auto size = static_cast<uint32_t>(state.range(0));
uint32_t partitions = size / 512;
@@ -280,8 +292,7 @@ static void BM_TableFilterChildNullableEqMatchMany(benchmark::State& state) {
}
for (auto _ : state) {
- benchmark::DoNotOptimize(child.ApplyAndIterateRows(
- child.QueryToRowMap({child.child_nullable().eq(1)}, {})));
+ benchmark::DoNotOptimize(child.ApplyAndIterateRows(child.QueryToRowMap(q)));
}
}
BENCHMARK(BM_TableFilterChildNullableEqMatchMany)->Apply(TableFilterArgs);
@@ -291,6 +302,8 @@ static void BM_TableFilterChildNonNullEqMatchManyInParent(
StringPool pool;
RootTestTable root(&pool);
ChildTestTable child(&pool, &root);
+ Query q;
+ q.constraints = {child.root_non_null().eq(0)};
auto size = static_cast<uint32_t>(state.range(0));
uint32_t partitions = size / 1024;
@@ -304,8 +317,7 @@ static void BM_TableFilterChildNonNullEqMatchManyInParent(
}
for (auto _ : state) {
- benchmark::DoNotOptimize(child.ApplyAndIterateRows(
- child.QueryToRowMap({child.root_non_null().eq(0)}, {})));
+ benchmark::DoNotOptimize(child.ApplyAndIterateRows(child.QueryToRowMap(q)));
}
}
BENCHMARK(BM_TableFilterChildNonNullEqMatchManyInParent)
@@ -328,9 +340,10 @@ static void BM_TableFilterChildNullableEqMatchManyInParent(
child.Insert(row);
}
+ Query q;
+ q.constraints = {child.root_nullable().eq(1)};
for (auto _ : state) {
- benchmark::DoNotOptimize(child.ApplyAndIterateRows(
- child.QueryToRowMap({child.root_nullable().eq(1)}, {})));
+ benchmark::DoNotOptimize(child.ApplyAndIterateRows(child.QueryToRowMap(q)));
}
}
BENCHMARK(BM_TableFilterChildNullableEqMatchManyInParent)
@@ -348,9 +361,10 @@ static void BM_TableFilterParentSortedEq(benchmark::State& state) {
root.Insert(row);
}
+ Query q;
+ q.constraints = {root.root_sorted().eq(22)};
for (auto _ : state) {
- benchmark::DoNotOptimize(root.ApplyAndIterateRows(
- root.QueryToRowMap({root.root_sorted().eq(22)}, {})));
+ benchmark::DoNotOptimize(root.ApplyAndIterateRows(root.QueryToRowMap(q)));
}
}
BENCHMARK(BM_TableFilterParentSortedEq)->Apply(TableFilterArgs);
@@ -373,10 +387,11 @@ static void BM_TableFilterParentSortedAndOther(benchmark::State& state) {
// We choose to search for the last group as if there is O(n^2), it will
// be more easily visible.
uint32_t last_group = ((size - 1) / 10) * 10;
+ Query q;
+ q.constraints = {root.root_sorted().eq(last_group),
+ root.root_non_null().eq(size - 1)};
for (auto _ : state) {
- benchmark::DoNotOptimize(root.ApplyAndIterateRows(root.QueryToRowMap(
- {root.root_sorted().eq(last_group), root.root_non_null().eq(size - 1)},
- {})));
+ benchmark::DoNotOptimize(root.ApplyAndIterateRows(root.QueryToRowMap(q)));
}
}
BENCHMARK(BM_TableFilterParentSortedAndOther)->Apply(TableFilterArgs);
@@ -395,9 +410,10 @@ static void BM_TableFilterChildSortedEq(benchmark::State& state) {
child.Insert(row);
}
+ Query q;
+ q.constraints = {child.child_sorted().eq(22)};
for (auto _ : state) {
- benchmark::DoNotOptimize(child.ApplyAndIterateRows(
- child.QueryToRowMap({child.child_sorted().eq(22)}, {})));
+ benchmark::DoNotOptimize(child.ApplyAndIterateRows(child.QueryToRowMap(q)));
}
}
BENCHMARK(BM_TableFilterChildSortedEq)->Apply(TableFilterArgs);
@@ -419,9 +435,10 @@ static void BM_TableFilterChildSortedEqInParent(benchmark::State& state) {
child.Insert(row);
}
+ Query q;
+ q.constraints = {child.root_sorted().eq(22)};
for (auto _ : state) {
- benchmark::DoNotOptimize(child.ApplyAndIterateRows(
- child.QueryToRowMap({child.root_sorted().eq(22)}, {})));
+ benchmark::DoNotOptimize(child.ApplyAndIterateRows(child.QueryToRowMap(q)));
}
}
BENCHMARK(BM_TableFilterChildSortedEqInParent)->Apply(TableFilterArgs);
@@ -441,9 +458,10 @@ static void BM_TableSortRootNonNull(benchmark::State& state) {
root.Insert(row);
}
+ Query q;
+ q.orders = {root.root_non_null().ascending()};
for (auto _ : state) {
- benchmark::DoNotOptimize(root.ApplyAndIterateRows(
- root.QueryToRowMap({}, {root.root_non_null().ascending()})));
+ benchmark::DoNotOptimize(root.ApplyAndIterateRows(root.QueryToRowMap(q)));
}
}
BENCHMARK(BM_TableSortRootNonNull)->Apply(TableSortArgs);
@@ -464,9 +482,10 @@ static void BM_TableSortRootNullable(benchmark::State& state) {
root.Insert(row);
}
+ Query q;
+ q.orders = {root.root_nullable().ascending()};
for (auto _ : state) {
- benchmark::DoNotOptimize(root.ApplyAndIterateRows(
- root.QueryToRowMap({}, {root.root_nullable().ascending()})));
+ benchmark::DoNotOptimize(root.ApplyAndIterateRows(root.QueryToRowMap(q)));
}
}
BENCHMARK(BM_TableSortRootNullable)->Apply(TableSortArgs);
@@ -493,9 +512,10 @@ static void BM_TableSortChildNonNullInParent(benchmark::State& state) {
child.Insert(child_row);
}
+ Query q;
+ q.orders = {child.root_non_null().ascending()};
for (auto _ : state) {
- benchmark::DoNotOptimize(child.ApplyAndIterateRows(
- child.QueryToRowMap({}, {child.root_non_null().ascending()})));
+ benchmark::DoNotOptimize(child.ApplyAndIterateRows(child.QueryToRowMap(q)));
}
}
BENCHMARK(BM_TableSortChildNonNullInParent)->Apply(TableSortArgs);
@@ -524,9 +544,10 @@ static void BM_TableSortChildNullableInParent(benchmark::State& state) {
child.Insert(child_row);
}
+ Query q;
+ q.orders = {child.root_nullable().ascending()};
for (auto _ : state) {
- benchmark::DoNotOptimize(child.ApplyAndIterateRows(
- child.QueryToRowMap({}, {child.root_nullable().ascending()})));
+ benchmark::DoNotOptimize(child.ApplyAndIterateRows(child.QueryToRowMap(q)));
}
}
BENCHMARK(BM_TableSortChildNullableInParent)->Apply(TableSortArgs);
diff --git a/src/trace_processor/tables/py_tables_unittest.cc b/src/trace_processor/tables/py_tables_unittest.cc
index 09056d8bf..1f4a4ef6f 100644
--- a/src/trace_processor/tables/py_tables_unittest.cc
+++ b/src/trace_processor/tables/py_tables_unittest.cc
@@ -243,14 +243,16 @@ TEST_F(PyTablesUnittest, SetIdColumns) {
// Verify that not-present ids are not returned.
{
static constexpr uint32_t kFilterArgSetId = 1;
- auto res =
- table.QueryToRowMap({table.arg_set_id().eq(kFilterArgSetId)}, {});
+ Query q;
+ q.constraints = {table.arg_set_id().eq(kFilterArgSetId)};
+ auto res = table.QueryToRowMap(q);
ASSERT_TRUE(res.empty());
}
{
static constexpr uint32_t kFilterArgSetId = 9;
- auto res =
- table.QueryToRowMap({table.arg_set_id().eq(kFilterArgSetId)}, {});
+ Query q;
+ q.constraints = {table.arg_set_id().eq(kFilterArgSetId)};
+ auto res = table.QueryToRowMap(q);
ASSERT_TRUE(res.empty());
}
@@ -260,8 +262,9 @@ TEST_F(PyTablesUnittest, SetIdColumns) {
// Verify that filtering equality for real arg set ids works as expected.
{
static constexpr uint32_t kFilterArgSetId = 4;
- auto res =
- table.QueryToRowMap({table.arg_set_id().eq(kFilterArgSetId)}, {});
+ Query q;
+ q.constraints = {table.arg_set_id().eq(kFilterArgSetId)};
+ auto res = table.QueryToRowMap(q);
ASSERT_EQ(res.size(), 4u);
for (auto it = table.ApplyAndIterateRows(std::move(res)); it; ++it) {
auto arg_set_id =
@@ -271,8 +274,9 @@ TEST_F(PyTablesUnittest, SetIdColumns) {
}
{
static constexpr uint32_t kFilterArgSetId = 0;
- auto res =
- table.QueryToRowMap({table.arg_set_id().eq(kFilterArgSetId)}, {});
+ Query q;
+ q.constraints = {table.arg_set_id().eq(kFilterArgSetId)};
+ auto res = table.QueryToRowMap(q);
ASSERT_EQ(res.size(), 2u);
for (auto it = table.ApplyAndIterateRows(std::move(res)); it; ++it) {
auto arg_set_id =
@@ -282,8 +286,9 @@ TEST_F(PyTablesUnittest, SetIdColumns) {
}
{
static constexpr uint32_t kFilterArgSetId = 8;
- auto res =
- table.QueryToRowMap({table.arg_set_id().eq(kFilterArgSetId)}, {});
+ Query q;
+ q.constraints = {table.arg_set_id().eq(kFilterArgSetId)};
+ auto res = table.QueryToRowMap(q);
ASSERT_EQ(res.size(), 1u);
for (auto it = table.ApplyAndIterateRows(std::move(res)); it; ++it) {
auto arg_set_id =
@@ -296,9 +301,10 @@ TEST_F(PyTablesUnittest, SetIdColumns) {
// column works.
{
static constexpr uint32_t kFilterArgSetId = 4;
- auto res = table.QueryToRowMap(
- {table.int_value().eq(200), table.arg_set_id().eq(kFilterArgSetId)},
- {});
+ Query q;
+ q.constraints = {table.int_value().eq(200),
+ table.arg_set_id().eq(kFilterArgSetId)};
+ auto res = table.QueryToRowMap(q);
ASSERT_EQ(res.size(), 2u);
for (auto it = table.ApplyAndIterateRows(std::move(res)); it; ++it) {
uint32_t arg_set_id =
diff --git a/src/trace_processor/trace_processor_impl.cc b/src/trace_processor/trace_processor_impl.cc
index b36a63a81..30c83e67b 100644
--- a/src/trace_processor/trace_processor_impl.cc
+++ b/src/trace_processor/trace_processor_impl.cc
@@ -81,6 +81,8 @@
#include "src/trace_processor/perfetto_sql/intrinsics/functions/to_ftrace.h"
#include "src/trace_processor/perfetto_sql/intrinsics/functions/utils.h"
#include "src/trace_processor/perfetto_sql/intrinsics/functions/window_functions.h"
+#include "src/trace_processor/perfetto_sql/intrinsics/operators/counter_mipmap_operator.h"
+#include "src/trace_processor/perfetto_sql/intrinsics/operators/slice_mipmap_operator.h"
#include "src/trace_processor/perfetto_sql/intrinsics/operators/span_join_operator.h"
#include "src/trace_processor/perfetto_sql/intrinsics/operators/window_operator.h"
#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/ancestor.h"
@@ -747,6 +749,12 @@ void TraceProcessorImpl::InitPerfettoSqlEngine() {
std::make_unique<SpanJoinOperatorModule::Context>(engine_.get()));
engine_->sqlite_engine()->RegisterVirtualTableModule<WindowOperatorModule>(
"window", std::make_unique<WindowOperatorModule::Context>());
+ engine_->sqlite_engine()->RegisterVirtualTableModule<CounterMipmapOperator>(
+ "__intrinsic_counter_mipmap",
+ std::make_unique<CounterMipmapOperator::Context>(engine_.get()));
+ engine_->sqlite_engine()->RegisterVirtualTableModule<SliceMipmapOperator>(
+ "__intrinsic_slice_mipmap",
+ std::make_unique<SliceMipmapOperator::Context>(engine_.get()));
// Initalize the tables and views in the prelude.
InitializePreludeTablesViews(db);
diff --git a/src/trace_processor/types/trace_processor_context.h b/src/trace_processor/types/trace_processor_context.h
index af2837fde..313683555 100644
--- a/src/trace_processor/types/trace_processor_context.h
+++ b/src/trace_processor/types/trace_processor_context.h
@@ -131,22 +131,23 @@ class TraceProcessorContext {
// 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
- std::unique_ptr<Destructible> syscall_tracker; // SyscallTracker
- std::unique_ptr<Destructible> system_info_tracker; // SystemInfoTracker
- std::unique_ptr<Destructible> v4l2_tracker; // V4l2Tracker
- std::unique_ptr<Destructible> virtio_video_tracker; // VirtioVideoTracker
- std::unique_ptr<Destructible> systrace_parser; // SystraceParser
- std::unique_ptr<Destructible> thread_state_tracker; // ThreadStateTracker
- 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> android_probes_tracker; // AndroidProbesTracker
+ std::unique_ptr<Destructible> binder_tracker; // BinderTracker
+ std::unique_ptr<Destructible> heap_graph_tracker; // HeapGraphTracker
+ std::unique_ptr<Destructible> syscall_tracker; // SyscallTracker
+ std::unique_ptr<Destructible> system_info_tracker; // SystemInfoTracker
+ std::unique_ptr<Destructible> v4l2_tracker; // V4l2Tracker
+ std::unique_ptr<Destructible> virtio_video_tracker; // VirtioVideoTracker
+ std::unique_ptr<Destructible> systrace_parser; // SystraceParser
+ std::unique_ptr<Destructible> thread_state_tracker; // ThreadStateTracker
+ 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> protolog_messages_tracker; // ProtoLogMessagesTracker
+ 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|
diff --git a/src/trace_processor/util/profile_builder.cc b/src/trace_processor/util/profile_builder.cc
index 951472bca..6948a8e8e 100644
--- a/src/trace_processor/util/profile_builder.cc
+++ b/src/trace_processor/util/profile_builder.cc
@@ -466,9 +466,9 @@ std::vector<GProfileBuilder::Line> GProfileBuilder::GetLinesForSymbolSetId(
using RowRef =
perfetto::trace_processor::tables::SymbolTable::ConstRowReference;
std::vector<RowRef> symbol_set;
- for (auto it = symbols.FilterToIterator(
- {symbols.symbol_set_id().eq(*symbol_set_id)});
- it; ++it) {
+ Query q;
+ q.constraints = {symbols.symbol_set_id().eq(*symbol_set_id)};
+ for (auto it = symbols.FilterToIterator(q); it; ++it) {
symbol_set.push_back(it.row_reference());
}
diff --git a/src/trace_redaction/BUILD.gn b/src/trace_redaction/BUILD.gn
index be268d72e..4f2fb5f92 100644
--- a/src/trace_redaction/BUILD.gn
+++ b/src/trace_redaction/BUILD.gn
@@ -28,6 +28,8 @@ executable("trace_redactor") {
source_set("trace_redaction") {
sources = [
+ "collect_frame_cookies.cc",
+ "collect_frame_cookies.h",
"collect_timeline_events.cc",
"collect_timeline_events.h",
"filter_ftrace_using_allowlist.cc",
@@ -42,6 +44,7 @@ source_set("trace_redaction") {
"filter_task_rename.h",
"find_package_uid.cc",
"find_package_uid.h",
+ "frame_cookie.h",
"optimize_timeline.cc",
"optimize_timeline.h",
"populate_allow_lists.cc",
@@ -122,6 +125,7 @@ source_set("integrationtests") {
perfetto_unittest_source_set("unittests") {
testonly = true
sources = [
+ "collect_frame_cookies_unittest.cc",
"collect_timeline_events_unittest.cc",
"filter_ftrace_using_allowlist_unittest.cc",
"filter_packet_using_allowlist_unittest.cc",
@@ -147,6 +151,7 @@ perfetto_unittest_source_set("unittests") {
"../../protos/perfetto/trace:non_minimal_cpp",
"../../protos/perfetto/trace:zero",
"../../protos/perfetto/trace/android:cpp",
+ "../../protos/perfetto/trace/android:zero",
"../../protos/perfetto/trace/ftrace:cpp",
"../../protos/perfetto/trace/ftrace:zero",
"../../protos/perfetto/trace/ps:cpp",
diff --git a/src/trace_redaction/collect_frame_cookies.cc b/src/trace_redaction/collect_frame_cookies.cc
new file mode 100644
index 000000000..13e553975
--- /dev/null
+++ b/src/trace_redaction/collect_frame_cookies.cc
@@ -0,0 +1,212 @@
+/*
+ * 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/collect_frame_cookies.h"
+
+#include "perfetto/base/status.h"
+#include "perfetto/protozero/field.h"
+#include "perfetto/protozero/proto_decoder.h"
+#include "src/trace_redaction/trace_redaction_framework.h"
+
+#include "protos/perfetto/trace/android/frame_timeline_event.pbzero.h"
+#include "protos/perfetto/trace/trace_packet.pbzero.h"
+
+namespace perfetto::trace_redaction {
+
+namespace {
+
+using FrameTimelineEvent = protos::pbzero::FrameTimelineEvent;
+
+struct Frame {
+ uint32_t id;
+ uint32_t pid;
+ uint32_t cookie;
+};
+
+constexpr Frame kActualDisplayFrameStart = {
+ FrameTimelineEvent::kActualDisplayFrameStartFieldNumber,
+ FrameTimelineEvent::ActualDisplayFrameStart::kPidFieldNumber,
+ FrameTimelineEvent::ActualDisplayFrameStart::kCookieFieldNumber,
+};
+
+constexpr Frame kExpectedDisplayFrameStart = {
+ FrameTimelineEvent::kExpectedDisplayFrameStartFieldNumber,
+ FrameTimelineEvent::ExpectedDisplayFrameStart::kPidFieldNumber,
+ FrameTimelineEvent::ExpectedDisplayFrameStart::kCookieFieldNumber,
+};
+
+constexpr Frame kActualSurfaceFrameStart = {
+ FrameTimelineEvent::kActualSurfaceFrameStartFieldNumber,
+ FrameTimelineEvent::ActualSurfaceFrameStart::kPidFieldNumber,
+ FrameTimelineEvent::ActualSurfaceFrameStart::kCookieFieldNumber,
+};
+
+constexpr Frame kExpectedSurfaceFrameStart = {
+ FrameTimelineEvent::kExpectedSurfaceFrameStartFieldNumber,
+ FrameTimelineEvent::ExpectedSurfaceFrameStart::kPidFieldNumber,
+ FrameTimelineEvent::ExpectedSurfaceFrameStart::kCookieFieldNumber,
+};
+
+// Do not use `pid` from `kFrameEnd`.
+constexpr Frame kFrameEnd = {
+ FrameTimelineEvent::kFrameEndFieldNumber,
+ 0,
+ FrameTimelineEvent::FrameEnd::kCookieFieldNumber,
+};
+
+} // namespace
+
+base::Status CollectFrameCookies::Begin(Context* context) const {
+ if (context->global_frame_cookies.empty()) {
+ return base::OkStatus();
+ }
+
+ return base::ErrStatus("FindFrameCookies: frame cookies already populated");
+}
+
+base::Status CollectFrameCookies::Collect(
+ const protos::pbzero::TracePacket::Decoder& packet,
+ Context* context) const {
+ // A frame cookie needs a time and pid for a timeline query. Ignore packets
+ // without a timestamp.
+ if (!packet.has_timestamp() || !packet.has_frame_timeline_event()) {
+ return base::OkStatus();
+ }
+
+ auto timestamp = packet.timestamp();
+
+ // Only use the start frames. They are the only ones with a pid. End events
+ // use the cookies to reference the pid in a start event.
+ auto handlers = {
+ kActualDisplayFrameStart,
+ kActualSurfaceFrameStart,
+ kExpectedDisplayFrameStart,
+ kExpectedSurfaceFrameStart,
+ };
+
+ // Timeline Event Decoder.
+ protozero::ProtoDecoder decoder(packet.frame_timeline_event());
+
+ // If no handler worked, cookie will not get added to the global cookie field.
+ for (const auto& handler : handlers) {
+ auto outer = decoder.FindField(handler.id);
+
+ if (!outer.valid()) {
+ continue;
+ }
+
+ protozero::ProtoDecoder inner(outer.as_bytes());
+
+ auto pid = inner.FindField(handler.pid);
+ auto cookie = inner.FindField(handler.cookie);
+
+ // This should be handled, but it is not valid. Drop the event by not adding
+ // it to the global_frame_cookies list.
+ if (!pid.valid() || !cookie.valid()) {
+ continue;
+ }
+
+ FrameCookie frame_cookie;
+ frame_cookie.pid = pid.as_int32();
+ frame_cookie.cookie = cookie.as_int64();
+ frame_cookie.ts = timestamp;
+
+ context->global_frame_cookies.push_back(frame_cookie);
+
+ break;
+ }
+
+ return base::OkStatus();
+}
+
+base::Status ReduceFrameCookies::Build(Context* context) const {
+ if (!context->package_uid.has_value()) {
+ return base::ErrStatus("ReduceFrameCookies: missing package uid.");
+ }
+
+ if (!context->timeline) {
+ return base::ErrStatus("ReduceFrameCookies: missing timeline.");
+ }
+
+ // Even though it is rare, it is possible for there to be no SurfaceFlinger
+ // frame cookies. Even through the main path handles this, we use this early
+ // exit to document this edge case.
+ if (context->global_frame_cookies.empty()) {
+ return base::OkStatus();
+ }
+
+ const auto* timeline = context->timeline.get();
+ auto uid = context->package_uid.value();
+
+ auto& package_frame_cookies = context->package_frame_cookies;
+
+ // Filter the global cookies down to cookies that belong to the target package
+ // (uid).
+ for (const auto& cookie : context->global_frame_cookies) {
+ auto cookie_slice = timeline->Search(cookie.ts, cookie.pid);
+
+ if (cookie_slice.uid == uid) {
+ package_frame_cookies.insert(cookie.cookie);
+ }
+ }
+
+ return base::OkStatus();
+}
+
+bool FilterFrameEvents::KeepField(const Context& context,
+ const protozero::Field& field) const {
+ // If this field is not a timeline event, then this primitive has no reason to
+ // reject this field.
+ //
+ // If it is a timeline event, the event's cookie must be in the package's
+ // cookies.
+ if (field.id() !=
+ protos::pbzero::TracePacket::kFrameTimelineEventFieldNumber) {
+ return true;
+ }
+
+ protozero::ProtoDecoder timeline_event_decoder(field.as_bytes());
+
+ auto handlers = {
+ kActualDisplayFrameStart,
+ kActualSurfaceFrameStart,
+ kExpectedDisplayFrameStart,
+ kExpectedSurfaceFrameStart,
+ kFrameEnd,
+ };
+
+ const auto& cookies = context.package_frame_cookies;
+
+ for (const auto& handler : handlers) {
+ auto event = timeline_event_decoder.FindField(handler.id);
+
+ if (!event.valid()) {
+ continue;
+ }
+
+ protozero::ProtoDecoder event_decoder(event.as_bytes());
+
+ auto cookie = event_decoder.FindField(handler.cookie);
+
+ if (cookie.valid() && cookies.count(cookie.as_int64())) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+} // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/collect_frame_cookies.h b/src/trace_redaction/collect_frame_cookies.h
new file mode 100644
index 000000000..7463ff61c
--- /dev/null
+++ b/src/trace_redaction/collect_frame_cookies.h
@@ -0,0 +1,60 @@
+/*
+ * 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_COLLECT_FRAME_COOKIES_H_
+#define SRC_TRACE_REDACTION_COLLECT_FRAME_COOKIES_H_
+
+#include "perfetto/protozero/field.h"
+#include "src/trace_redaction/scrub_trace_packet.h"
+#include "src/trace_redaction/trace_redaction_framework.h"
+
+#include "protos/perfetto/trace/trace_packet.pbzero.h"
+
+namespace perfetto::trace_redaction {
+
+// Populates Context::global_frame_cookies using FrameTimelineEvent messages.
+class CollectFrameCookies : public CollectPrimitive {
+ public:
+ base::Status Begin(Context* context) const override;
+
+ base::Status Collect(const protos::pbzero::TracePacket::Decoder& packet,
+ Context* context) const override;
+
+ private:
+ void OnTimelineEvent(const protos::pbzero::TracePacket::Decoder& packet,
+ protozero::ConstBytes bytes,
+ Context* context) const;
+};
+
+// Moves cookies from Context::global_frame_cookies to
+// Context::package_frame_cookies using Cookies::timeline and
+// Cookies::package_uid.
+class ReduceFrameCookies : public BuildPrimitive {
+ public:
+ base::Status Build(Context* context) const override;
+};
+
+// Flags start-frame and end-frame events as keep/drop using
+// Context::package_frame_cookies.
+class FilterFrameEvents : public TracePacketFilter {
+ public:
+ bool KeepField(const Context& context,
+ const protozero::Field& field) const override;
+};
+
+} // namespace perfetto::trace_redaction
+
+#endif // SRC_TRACE_REDACTION_COLLECT_FRAME_COOKIES_H_
diff --git a/src/trace_redaction/collect_frame_cookies_unittest.cc b/src/trace_redaction/collect_frame_cookies_unittest.cc
new file mode 100644
index 000000000..6e77231de
--- /dev/null
+++ b/src/trace_redaction/collect_frame_cookies_unittest.cc
@@ -0,0 +1,346 @@
+
+/*
+ * 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/collect_frame_cookies.h"
+#include "src/base/test/status_matchers.h"
+#include "src/trace_redaction/collect_timeline_events.h"
+#include "test/gtest_and_gmock.h"
+
+#include "protos/perfetto/trace/android/frame_timeline_event.gen.h"
+#include "protos/perfetto/trace/android/frame_timeline_event.pbzero.h"
+#include "protos/perfetto/trace/trace_packet.gen.h"
+#include "protos/perfetto/trace/trace_packet.pbzero.h"
+
+namespace perfetto::trace_redaction {
+namespace {
+
+constexpr uint64_t kTimestampA = 0;
+constexpr uint64_t kTimestampB = 1000;
+constexpr uint64_t kTimestampC = 2000;
+constexpr uint64_t kTimestampD = 3000;
+constexpr uint64_t kTimestampE = 3000;
+
+constexpr int64_t kCookieA = 1234;
+
+// Start at 1, amd not zero, because zero hnas special meaning (system uid).
+constexpr uint64_t kUidA = 1;
+
+constexpr int32_t kPidNone = 10;
+constexpr int32_t kPidA = 11;
+
+} // namespace
+
+class FrameCookieFixture {
+ protected:
+ std::string CreateStartEvent(int32_t field_id,
+ uint64_t ts,
+ int32_t pid,
+ int64_t cookie) const {
+ protos::gen::TracePacket packet;
+ packet.set_timestamp(ts);
+
+ switch (field_id) {
+ case protos::pbzero::FrameTimelineEvent::
+ kExpectedSurfaceFrameStartFieldNumber:
+ CreateExpectedSurfaceFrameStart(pid, cookie,
+ packet.mutable_frame_timeline_event());
+ break;
+
+ case protos::pbzero::FrameTimelineEvent::
+ kActualSurfaceFrameStartFieldNumber:
+ CreateActualSurfaceFrameStart(pid, cookie,
+ packet.mutable_frame_timeline_event());
+ break;
+
+ case protos::pbzero::FrameTimelineEvent::
+ kExpectedDisplayFrameStartFieldNumber:
+ CreateExpectedDisplayFrameStart(pid, cookie,
+ packet.mutable_frame_timeline_event());
+ break;
+
+ case protos::pbzero::FrameTimelineEvent::
+ kActualDisplayFrameStartFieldNumber:
+ CreateActualDisplayFrameStart(pid, cookie,
+ packet.mutable_frame_timeline_event());
+ break;
+
+ default:
+ PERFETTO_FATAL("Invalid field id");
+ break;
+ }
+
+ return packet.SerializeAsString();
+ }
+
+ std::string CreateFrameEnd(uint64_t ts, int64_t cookie) const {
+ protos::gen::TracePacket packet;
+ packet.set_timestamp(ts);
+
+ auto* start = packet.mutable_frame_timeline_event()->mutable_frame_end();
+ start->set_cookie(cookie);
+
+ return packet.SerializeAsString();
+ }
+
+ void CollectEvents(std::initializer_list<ProcessThreadTimeline::Event> events,
+ Context* context) const {
+ CollectTimelineEvents collect;
+ ASSERT_OK(collect.Begin(context));
+
+ for (const auto& event : events) {
+ context->timeline->Append(event);
+ }
+
+ ASSERT_OK(collect.End(context));
+ }
+
+ void CollectCookies(std::initializer_list<std::string> packets,
+ Context* context) const {
+ CollectFrameCookies collect;
+ ASSERT_OK(collect.Begin(context));
+
+ for (const auto& packet : packets) {
+ protos::pbzero::TracePacket::Decoder decoder(packet);
+ ASSERT_OK(collect.Collect(decoder, context));
+ }
+
+ ASSERT_OK(collect.End(context));
+ }
+
+ private:
+ void CreateExpectedSurfaceFrameStart(
+ int32_t pid,
+ int64_t cookie,
+ protos::gen::FrameTimelineEvent* event) const {
+ auto* start = event->mutable_expected_surface_frame_start();
+ start->set_cookie(cookie);
+ start->set_pid(pid);
+ }
+
+ void CreateActualSurfaceFrameStart(
+ int32_t pid,
+ int64_t cookie,
+ protos::gen::FrameTimelineEvent* event) const {
+ auto* start = event->mutable_actual_surface_frame_start();
+ start->set_cookie(cookie);
+ start->set_pid(pid);
+ }
+
+ void CreateExpectedDisplayFrameStart(
+ int32_t pid,
+ int64_t cookie,
+ protos::gen::FrameTimelineEvent* event) const {
+ auto* start = event->mutable_expected_display_frame_start();
+ start->set_cookie(cookie);
+ start->set_pid(pid);
+ }
+
+ void CreateActualDisplayFrameStart(
+ int32_t pid,
+ int64_t cookie,
+ protos::gen::FrameTimelineEvent* event) const {
+ auto* start = event->mutable_actual_display_frame_start();
+ start->set_cookie(cookie);
+ start->set_pid(pid);
+ }
+};
+
+class CollectFrameCookiesTest : public testing::Test,
+ protected FrameCookieFixture,
+ public testing::WithParamInterface<int32_t> {
+ protected:
+ Context context_;
+};
+
+TEST_P(CollectFrameCookiesTest, ExtractsExpectedSurfaceFrameStart) {
+ auto field_id = GetParam();
+
+ auto packet = CreateStartEvent(field_id, kTimestampA, kPidA, kCookieA);
+
+ CollectCookies({packet}, &context_);
+
+ ASSERT_EQ(context_.global_frame_cookies.size(), 1u);
+
+ auto& cookie = context_.global_frame_cookies.back();
+ ASSERT_EQ(cookie.cookie, kCookieA);
+ ASSERT_EQ(cookie.pid, kPidA);
+ ASSERT_EQ(cookie.ts, kTimestampA);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ EveryStartEventType,
+ CollectFrameCookiesTest,
+ testing::Values(
+ protos::pbzero::FrameTimelineEvent::
+ kExpectedSurfaceFrameStartFieldNumber,
+ protos::pbzero::FrameTimelineEvent::kActualSurfaceFrameStartFieldNumber,
+ protos::pbzero::FrameTimelineEvent::
+ kExpectedDisplayFrameStartFieldNumber,
+ protos::pbzero::FrameTimelineEvent::
+ kActualDisplayFrameStartFieldNumber));
+
+// End events have no influence during the collect phase because they don't have
+// a direct connection to a process. They're indirectly connected to a pid via a
+// start event (via a common cookie value).
+TEST_F(CollectFrameCookiesTest, IgnoresFrameEnd) {
+ CollectCookies({CreateFrameEnd(kTimestampA, kPidA)}, &context_);
+
+ ASSERT_TRUE(context_.global_frame_cookies.empty());
+}
+
+class ReduceFrameCookiesTest : public testing::Test,
+ protected FrameCookieFixture,
+ public testing::WithParamInterface<int32_t> {
+ protected:
+ void SetUp() {
+ context_.package_uid = kUidA;
+
+ // Time A +- Time B +- Time C +- Time D +- Time E
+ // | |
+ // +------------ Pid A ---------+
+ //
+ // The pid will be active from time b to time d. Time A will be used for
+ // "before active". Time C will be used for "while active". Time E will be
+ // used for "after active".
+ CollectEvents(
+ {
+ ProcessThreadTimeline::Event::Open(kTimestampB, kPidA, kPidNone,
+ kUidA),
+ ProcessThreadTimeline::Event::Close(kTimestampD, kPidA),
+ },
+ &context_);
+ }
+
+ ReduceFrameCookies reduce_;
+ Context context_;
+};
+
+TEST_P(ReduceFrameCookiesTest, RejectBeforeActive) {
+ auto field_id = GetParam();
+
+ // kTimestampA is before pid starts.
+ auto packet = CreateStartEvent(field_id, kTimestampA, kPidA, kCookieA);
+
+ CollectCookies({packet}, &context_);
+
+ ASSERT_OK(reduce_.Build(&context_));
+ ASSERT_FALSE(context_.package_frame_cookies.count(kCookieA));
+}
+
+TEST_P(ReduceFrameCookiesTest, AcceptDuringActive) {
+ auto field_id = GetParam();
+
+ // kTimestampC is between pid starts and ends.
+ auto packet = CreateStartEvent(field_id, kTimestampC, kPidA, kCookieA);
+
+ CollectCookies({packet}, &context_);
+
+ ASSERT_OK(reduce_.Build(&context_));
+ ASSERT_TRUE(context_.package_frame_cookies.count(kCookieA));
+}
+
+TEST_P(ReduceFrameCookiesTest, RejectAfterActive) {
+ auto field_id = GetParam();
+
+ // kTimestampE is after pid ends.
+ auto packet = CreateStartEvent(field_id, kTimestampE, kPidA, kCookieA);
+
+ CollectCookies({packet}, &context_);
+
+ ASSERT_OK(reduce_.Build(&context_));
+ ASSERT_FALSE(context_.package_frame_cookies.count(kCookieA));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ EveryStartEventType,
+ ReduceFrameCookiesTest,
+ testing::Values(
+ protos::pbzero::FrameTimelineEvent::
+ kExpectedSurfaceFrameStartFieldNumber,
+ protos::pbzero::FrameTimelineEvent::kActualSurfaceFrameStartFieldNumber,
+ protos::pbzero::FrameTimelineEvent::
+ kExpectedDisplayFrameStartFieldNumber,
+ protos::pbzero::FrameTimelineEvent::
+ kActualDisplayFrameStartFieldNumber));
+
+class FilterCookiesFieldsTest : public testing::Test,
+ protected FrameCookieFixture,
+ public testing::WithParamInterface<int32_t> {
+ protected:
+ protozero::Field ExtractTimelineEvent(const std::string& packet) const {
+ protozero::ProtoDecoder packet_decoder(packet);
+
+ // There must be one in order for the test to work, so we assume it's there.
+ return packet_decoder.FindField(
+ protos::pbzero::TracePacket::kFrameTimelineEventFieldNumber);
+ }
+
+ FilterFrameEvents filter_;
+ Context context_;
+};
+
+// If the event was within a valid pid's lifespan and was connected to the
+// package, it should be kept.
+TEST_P(FilterCookiesFieldsTest, IncludeIncludedStartCookies) {
+ context_.package_frame_cookies.insert(kCookieA);
+
+ auto field_id = GetParam();
+ auto packet = CreateStartEvent(field_id, kTimestampA, kPidA, kCookieA);
+ auto timeline_field = ExtractTimelineEvent(packet);
+
+ ASSERT_TRUE(filter_.KeepField(context_, timeline_field));
+}
+
+// If the event wasn't within a valid pid's lifespans and/or was connected to a
+// package, it should be removed.
+TEST_P(FilterCookiesFieldsTest, ExcludeMissingStartCookies) {
+ auto field_id = GetParam();
+ auto packet = CreateStartEvent(field_id, kTimestampA, kPidA, kCookieA);
+ auto timeline_field = ExtractTimelineEvent(packet);
+
+ ASSERT_FALSE(filter_.KeepField(context_, timeline_field));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ EveryStartEventType,
+ FilterCookiesFieldsTest,
+ testing::Values(
+ protos::pbzero::FrameTimelineEvent::
+ kExpectedSurfaceFrameStartFieldNumber,
+ protos::pbzero::FrameTimelineEvent::kActualSurfaceFrameStartFieldNumber,
+ protos::pbzero::FrameTimelineEvent::
+ kExpectedDisplayFrameStartFieldNumber,
+ protos::pbzero::FrameTimelineEvent::
+ kActualDisplayFrameStartFieldNumber));
+
+TEST_F(FilterCookiesFieldsTest, IncludeIncludedEndCookies) {
+ context_.package_frame_cookies.insert(kCookieA);
+
+ auto packet = CreateFrameEnd(kTimestampA, kCookieA);
+ auto timeline_field = ExtractTimelineEvent(packet);
+
+ ASSERT_TRUE(filter_.KeepField(context_, timeline_field));
+}
+
+TEST_F(FilterCookiesFieldsTest, ExcludeMissingEndCookies) {
+ auto packet = CreateFrameEnd(kTimestampA, kCookieA);
+ auto timeline_field = ExtractTimelineEvent(packet);
+
+ ASSERT_FALSE(filter_.KeepField(context_, timeline_field));
+}
+
+} // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/filter_packet_using_allowlist.cc b/src/trace_redaction/filter_packet_using_allowlist.cc
index 5b01c4d6c..b1a4dc42c 100644
--- a/src/trace_redaction/filter_packet_using_allowlist.cc
+++ b/src/trace_redaction/filter_packet_using_allowlist.cc
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-#include <string>
-
#include "perfetto/base/status.h"
#include "perfetto/protozero/field.h"
#include "src/trace_redaction/filter_packet_using_allowlist.h"
@@ -32,25 +30,11 @@ base::Status FilterPacketUsingAllowlist::VerifyContext(
return base::OkStatus();
}
-bool FilterPacketUsingAllowlist::KeepPacket(const Context& context,
- const std::string& bytes) const {
+bool FilterPacketUsingAllowlist::KeepField(
+ const Context& context,
+ const protozero::Field& field) const {
PERFETTO_DCHECK(!context.trace_packet_allow_list.empty());
-
- const auto& allow_list = context.trace_packet_allow_list;
-
- protozero::ProtoDecoder decoder(bytes);
-
- // A packet should only have one data type (proto oneof), but there are other
- // values in the packet (e.g. timestamp). If one field is in the allowlist,
- // then allow the whole trace packet.
- for (auto field = decoder.ReadField(); field.valid();
- field = decoder.ReadField()) {
- if (allow_list.count(field.id()) != 0) {
- return true;
- }
- }
-
- return false;
+ return field.valid() && context.trace_packet_allow_list.count(field.id());
}
} // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/filter_packet_using_allowlist.h b/src/trace_redaction/filter_packet_using_allowlist.h
index 7caa0eca0..9205e946c 100644
--- a/src/trace_redaction/filter_packet_using_allowlist.h
+++ b/src/trace_redaction/filter_packet_using_allowlist.h
@@ -31,8 +31,8 @@ class FilterPacketUsingAllowlist : public TracePacketFilter {
public:
base::Status VerifyContext(const Context& context) const override;
- bool KeepPacket(const Context& context,
- const std::string& bytes) const override;
+ bool KeepField(const Context& context,
+ const protozero::Field& field) const override;
};
} // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/filter_packet_using_allowlist_unittest.cc b/src/trace_redaction/filter_packet_using_allowlist_unittest.cc
index 25b227220..fad6879a3 100644
--- a/src/trace_redaction/filter_packet_using_allowlist_unittest.cc
+++ b/src/trace_redaction/filter_packet_using_allowlist_unittest.cc
@@ -14,135 +14,59 @@
* limitations under the License.
*/
-#include <string>
-
-#include "perfetto/ext/base/status_or.h"
-#include "src/base/test/status_matchers.h"
#include "src/trace_redaction/filter_packet_using_allowlist.h"
-#include "src/trace_redaction/scrub_trace_packet.h"
+#include "perfetto/protozero/scattered_heap_buffer.h"
#include "test/gtest_and_gmock.h"
-#include "protos/perfetto/trace/ps/process_tree.gen.h"
-#include "protos/perfetto/trace/trace_packet.gen.h"
#include "protos/perfetto/trace/trace_packet.pbzero.h"
+// TODO(vaage): These tests were used to test the filter-driver, but these tests
+// no longer do that. A new test suite should be created to test the driver code
+// with the different filters.
namespace perfetto::trace_redaction {
-TEST(FilterPacketUsingAllowlistParamErrorTest, ReturnErrorForNullPacket) {
- ScrubTracePacket transform_;
- transform_.emplace_back<FilterPacketUsingAllowlist>();
-
- // Have something in the allow-list to avoid that error.
- Context context;
- context.trace_packet_allow_list.insert(
- protos::pbzero::TracePacket::kProcessTreeFieldNumber);
+namespace {
- ASSERT_FALSE(transform_.Transform(context, nullptr).ok());
-}
+constexpr auto kJustSomeFieldId =
+ protos::pbzero::TracePacket::kProcessTreeFieldNumber;
-TEST(FilterPacketUsingAllowlistParamErrorTest, ReturnErrorForEmptyPacket) {
- ScrubTracePacket transform_;
- transform_.emplace_back<FilterPacketUsingAllowlist>();
+} // namespace
- // Have something in the allow-list to avoid that error.
+TEST(FilterPacketUsingAllowlistParamErrorTest, ReturnsErrorForEmptyAllowlist) {
Context context;
- context.trace_packet_allow_list.insert(
- protos::pbzero::TracePacket::kProcessTreeFieldNumber);
- std::string packet_str = "";
+ FilterPacketUsingAllowlist filter;
+ auto status = filter.VerifyContext(context);
- ASSERT_FALSE(transform_.Transform(context, &packet_str).ok());
+ ASSERT_FALSE(status.ok()) << status.message();
}
-class FilterPacketUsingAllowlistTest : public testing::Test {
- protected:
- void SetUp() override {
- transform_.emplace_back<FilterPacketUsingAllowlist>();
- }
-
- base::StatusOr<std::string> Redact(const protos::gen::TracePacket& packet) {
- auto str = packet.SerializeAsString();
- auto status = transform_.Transform(context_, &str);
-
- if (status.ok()) {
- return str;
- }
-
- return status;
- }
-
- Context context_;
-
- private:
- ScrubTracePacket transform_;
-};
-
-TEST_F(FilterPacketUsingAllowlistTest, ReturnErrorForEmptyAllowList) {
- // The context will have no allow-list entries. ScrubTracePacket should fail.
-
- protos::gen::TracePacket packet;
-
- auto status = Redact(packet);
- ASSERT_FALSE(status.ok()) << status.status().c_message();
-}
-
-// The whole packet should be dropped (cleared) when it has a data type not
-// included in the allow-list.
-TEST_F(FilterPacketUsingAllowlistTest, DropsOutsiderPacketType) {
- protos::gen::TracePacket packet;
- packet.set_timestamp(1234);
- packet.mutable_android_camera_frame_event(); // Creates and sets data.
-
- // Populate the allow-list with something that doesn't match the data in the
- // packet.
- context_.trace_packet_allow_list.insert(
- protos::pbzero::TracePacket::kProcessTreeFieldNumber);
-
- auto status = Redact(packet);
- ASSERT_OK(status) << status.status().c_message();
-
- ASSERT_TRUE(status->empty());
-}
-
-// Typically a trace packet should always have a data type (e.g. ProcessTree),
-// but it is possible that another transformation has cleared that data. If
-// that's the case, this primitive should treat it as an outsider.
-TEST_F(FilterPacketUsingAllowlistTest, DropsPacketsWithNoType) {
- protos::gen::TracePacket packet;
- packet.set_timestamp(1234);
-
- std::string packet_str = packet.SerializeAsString();
- ASSERT_GT(packet_str.size(), 0u);
-
- context_.trace_packet_allow_list.insert(
- protos::pbzero::TracePacket::kProcessTreeFieldNumber);
+TEST(FilterPacketUsingAllowlistParamErrorTest, ReturnsFalseForInvalidField) {
+ // Have something in the allow-list to avoid an error.
+ Context context;
+ context.trace_packet_allow_list.insert(kJustSomeFieldId);
- auto status = Redact(packet);
- ASSERT_OK(status) << status.status().c_message();
+ protozero::Field invalid = {};
+ ASSERT_FALSE(invalid.valid());
- ASSERT_TRUE(status->empty());
+ FilterPacketUsingAllowlist filter;
+ ASSERT_FALSE(filter.KeepField(context, invalid));
}
-// A packet should not change (at all) if it's in the allow-list.
-TEST_F(FilterPacketUsingAllowlistTest, SkipsAllowedPacket) {
- protos::gen::TracePacket packet;
- packet.set_timestamp(1234);
+TEST(FilterPacketUsingAllowlistParamErrorTest, ReturnsFalseForExcludedField) {
+ Context context;
+ context.trace_packet_allow_list.insert(kJustSomeFieldId);
- // Add a process tree to the packet. Process trees are in the allow-list.
- auto* process = packet.mutable_process_tree()->add_processes();
- process->set_uid(0);
- process->set_ppid(3);
- process->set_pid(7);
+ protozero::HeapBuffered<protos::pbzero::TracePacket> packet;
+ packet->set_timestamp(123456789);
- context_.trace_packet_allow_list.insert(
- protos::pbzero::TracePacket::kProcessTreeFieldNumber);
+ auto buffer = packet.SerializeAsString();
- auto status = Redact(packet);
- ASSERT_OK(status) << status.status().c_message();
+ protozero::ProtoDecoder decoder(buffer);
+ protozero::Field field = decoder.FindField(kJustSomeFieldId);
- // The transform shouldn't have changed the string, so the string before and
- // after should match.
- ASSERT_EQ(*status, packet.SerializeAsString());
+ FilterPacketUsingAllowlist filter;
+ ASSERT_FALSE(filter.KeepField(context, field));
}
} // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/frame_cookie.h b/src/trace_redaction/frame_cookie.h
new file mode 100644
index 000000000..bd96804f8
--- /dev/null
+++ b/src/trace_redaction/frame_cookie.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_TRACE_REDACTION_FRAME_COOKIE_H_
+#define SRC_TRACE_REDACTION_FRAME_COOKIE_H_
+
+#include <cstdint>
+
+struct FrameCookie {
+ // The timestamp from the trace packet.
+ uint64_t ts;
+
+ // The cookie value will be found inside of the start event (there are four
+ // different start types). This is the app's pid (main thread id).
+
+ // ExpectedSurfaceFrameStart: pid = app id
+ // ActualSurfaceFrameStart: pid = app id
+
+ // ExpectedDisplayFrameStart: pid = surface flinger
+ // ActualDisplayFrameStart: pid = surface flinger
+ int32_t pid;
+
+ // The cookie value will be found inside of the start event (there are four
+ // different start types). End events use the cookie to connect to the start
+ // event. Therefore end events don't need a pid.
+ int64_t cookie;
+};
+
+#endif // SRC_TRACE_REDACTION_FRAME_COOKIE_H_
diff --git a/src/trace_redaction/main.cc b/src/trace_redaction/main.cc
index 656fdef89..2e433c078 100644
--- a/src/trace_redaction/main.cc
+++ b/src/trace_redaction/main.cc
@@ -16,6 +16,7 @@
#include "perfetto/base/logging.h"
#include "perfetto/base/status.h"
+#include "src/trace_redaction/collect_frame_cookies.h"
#include "src/trace_redaction/collect_timeline_events.h"
#include "src/trace_redaction/filter_ftrace_using_allowlist.h"
#include "src/trace_redaction/filter_packet_using_allowlist.h"
@@ -49,15 +50,18 @@ static base::Status Main(std::string_view input,
// Add all collectors.
redactor.emplace_collect<FindPackageUid>();
redactor.emplace_collect<CollectTimelineEvents>();
+ redactor.emplace_collect<CollectFrameCookies>();
// Add all builders.
redactor.emplace_build<PopulateAllowlists>();
redactor.emplace_build<AllowSuspendResume>();
redactor.emplace_build<OptimizeTimeline>();
+ redactor.emplace_build<ReduceFrameCookies>();
// Add all transforms.
auto* scrub_packet = redactor.emplace_transform<ScrubTracePacket>();
scrub_packet->emplace_back<FilterPacketUsingAllowlist>();
+ scrub_packet->emplace_back<FilterFrameEvents>();
auto* scrub_ftrace_events = redactor.emplace_transform<ScrubFtraceEvents>();
scrub_ftrace_events->emplace_back<FilterFtraceUsingAllowlist>();
diff --git a/src/trace_redaction/populate_allow_lists.cc b/src/trace_redaction/populate_allow_lists.cc
index 380a15228..b2c94311e 100644
--- a/src/trace_redaction/populate_allow_lists.cc
+++ b/src/trace_redaction/populate_allow_lists.cc
@@ -25,6 +25,32 @@
namespace perfetto::trace_redaction {
base::Status PopulateAllowlists::Build(Context* context) const {
+ // These fields are top-level fields that outside the "oneof data" field.
+ std::initializer_list<uint32_t> required_trace_fields = {
+
+ protos::pbzero::TracePacket::kTimestampFieldNumber,
+ protos::pbzero::TracePacket::kTimestampClockIdFieldNumber,
+ protos::pbzero::TracePacket::kTrustedUidFieldNumber,
+ protos::pbzero::TracePacket::kTrustedPacketSequenceIdFieldNumber,
+ protos::pbzero::TracePacket::kTrustedPidFieldNumber,
+ protos::pbzero::TracePacket::kInternedDataFieldNumber,
+ protos::pbzero::TracePacket::kSequenceFlagsFieldNumber,
+
+ // DEPRECATED. Moved to SequenceFlags::SEQ_INCREMENTAL_STATE_CLEARED. So
+ // there is no reason to include it.
+ //
+ // protos::pbzero::TracePacket::incremental_state_cleared
+
+ protos::pbzero::TracePacket::kTracePacketDefaultsFieldNumber,
+ protos::pbzero::TracePacket::kPreviousPacketDroppedFieldNumber,
+ protos::pbzero::TracePacket::kFirstPacketOnSequenceFieldNumber,
+ protos::pbzero::TracePacket::kMachineIdFieldNumber,
+ };
+
+ for (auto item : required_trace_fields) {
+ context->trace_packet_allow_list.insert(item);
+ }
+
// TRACE PACKET NOTES
//
// protos::pbzero::TracePacket::kAndroidSystemPropertyFieldNumber
@@ -33,8 +59,7 @@ base::Status PopulateAllowlists::Build(Context* context) const {
// constraints around keys or values, making fine-grain redaction
// difficult. Because this packet's value has no measurable, the safest
// option to drop the whole packet.
-
- std::initializer_list<uint> trace_packets = {
+ std::initializer_list<uint32_t> trace_packets = {
protos::pbzero::TracePacket::kProcessTreeFieldNumber,
protos::pbzero::TracePacket::kProcessStatsFieldNumber,
protos::pbzero::TracePacket::kClockSnapshotFieldNumber,
@@ -59,7 +84,7 @@ base::Status PopulateAllowlists::Build(Context* context) const {
context->trace_packet_allow_list.insert(item);
}
- std::initializer_list<uint> ftrace_events = {
+ std::initializer_list<uint32_t> ftrace_events = {
protos::pbzero::FtraceEvent::kSchedSwitchFieldNumber,
protos::pbzero::FtraceEvent::kCpuFrequencyFieldNumber,
protos::pbzero::FtraceEvent::kCpuIdleFieldNumber,
diff --git a/src/trace_redaction/redact_sched_switch.cc b/src/trace_redaction/redact_sched_switch.cc
index 2f85efe38..55777f1df 100644
--- a/src/trace_redaction/redact_sched_switch.cc
+++ b/src/trace_redaction/redact_sched_switch.cc
@@ -88,13 +88,17 @@ base::Status RedactSchedSwitch::Redact(
switch (field.id()) {
case protos::pbzero::SchedSwitchFtraceEvent::kNextCommFieldNumber:
if (next_slice.uid == context.package_uid) {
- proto_util::AppendField(field, sched_switch_message);
+ sched_switch_message->set_next_comm(field.as_string());
+ } else {
+ sched_switch_message->set_next_comm("");
}
break;
case protos::pbzero::SchedSwitchFtraceEvent::kPrevCommFieldNumber:
if (prev_slice.uid == context.package_uid) {
- proto_util::AppendField(field, sched_switch_message);
+ sched_switch_message->set_prev_comm(field.as_string());
+ } else {
+ sched_switch_message->set_prev_comm("");
}
break;
diff --git a/src/trace_redaction/redact_sched_switch_integrationtest.cc b/src/trace_redaction/redact_sched_switch_integrationtest.cc
index 3b3523f3c..67948f2fe 100644
--- a/src/trace_redaction/redact_sched_switch_integrationtest.cc
+++ b/src/trace_redaction/redact_sched_switch_integrationtest.cc
@@ -175,18 +175,19 @@ TEST_F(RedactSchedSwitchIntegrationTest, ClearsNonTargetSwitchComms) {
const auto* next_comm = expected_names.Find(next_pid);
const auto* prev_comm = expected_names.Find(prev_pid);
+ EXPECT_TRUE(sched_decoder.has_next_comm());
+ EXPECT_TRUE(sched_decoder.has_prev_comm());
+
if (next_comm) {
- EXPECT_TRUE(sched_decoder.has_next_comm());
EXPECT_EQ(sched_decoder.next_comm().ToStdString(), *next_comm);
} else {
- EXPECT_FALSE(sched_decoder.has_next_comm());
+ EXPECT_EQ(sched_decoder.next_comm().size, 0u);
}
if (prev_comm) {
- EXPECT_TRUE(sched_decoder.has_prev_comm());
EXPECT_EQ(sched_decoder.prev_comm().ToStdString(), *prev_comm);
} else {
- EXPECT_FALSE(sched_decoder.has_prev_comm());
+ EXPECT_EQ(sched_decoder.prev_comm().size, 0u);
}
}
}
diff --git a/src/trace_redaction/redact_sched_switch_unittest.cc b/src/trace_redaction/redact_sched_switch_unittest.cc
index 174a35f1b..f6a4c1357 100644
--- a/src/trace_redaction/redact_sched_switch_unittest.cc
+++ b/src/trace_redaction/redact_sched_switch_unittest.cc
@@ -108,7 +108,7 @@ TEST_F(RedactSchedSwitchTest, RejectMissingTimeline) {
ASSERT_FALSE(result.ok());
}
-TEST_F(RedactSchedSwitchTest, ClearsPrevAndNext) {
+TEST_F(RedactSchedSwitchTest, ReplacePrevAndNextWithEmptyStrings) {
RedactSchedSwitch redact;
Context context;
@@ -131,15 +131,15 @@ TEST_F(RedactSchedSwitchTest, ClearsPrevAndNext) {
ASSERT_TRUE(event.has_sched_switch());
- // Pid should always carry over; only the comm value should get removed.
- ASSERT_TRUE(event.sched_switch().has_next_pid());
- ASSERT_FALSE(event.sched_switch().has_next_comm());
+ // Cleared prev and next comm.
+ ASSERT_TRUE(event.sched_switch().has_prev_comm());
+ ASSERT_TRUE(event.sched_switch().prev_comm().empty());
- ASSERT_TRUE(event.sched_switch().has_prev_pid());
- ASSERT_FALSE(event.sched_switch().has_prev_comm());
+ ASSERT_TRUE(event.sched_switch().has_next_comm());
+ ASSERT_TRUE(event.sched_switch().next_comm().empty());
}
-TEST_F(RedactSchedSwitchTest, ClearsPrev) {
+TEST_F(RedactSchedSwitchTest, ReplacePrevWithEmptyStrings) {
RedactSchedSwitch redact;
Context context;
@@ -162,15 +162,15 @@ TEST_F(RedactSchedSwitchTest, ClearsPrev) {
ASSERT_TRUE(event.has_sched_switch());
- // Pid should always carry over; only the comm value should get removed.
- ASSERT_TRUE(event.sched_switch().has_next_pid());
- ASSERT_TRUE(event.sched_switch().has_next_comm());
+ // Only cleared the prev comm.
+ ASSERT_TRUE(event.sched_switch().has_prev_comm());
+ ASSERT_TRUE(event.sched_switch().prev_comm().empty());
- ASSERT_TRUE(event.sched_switch().has_prev_pid());
- ASSERT_FALSE(event.sched_switch().has_prev_comm());
+ ASSERT_TRUE(event.sched_switch().has_next_comm());
+ ASSERT_FALSE(event.sched_switch().next_comm().empty());
}
-TEST_F(RedactSchedSwitchTest, ClearNext) {
+TEST_F(RedactSchedSwitchTest, ReplaceNextWithEmptyStrings) {
RedactSchedSwitch redact;
Context context;
@@ -193,12 +193,12 @@ TEST_F(RedactSchedSwitchTest, ClearNext) {
ASSERT_TRUE(event.has_sched_switch());
- // Pid should always carry over; only the comm value should get removed.
- ASSERT_TRUE(event.sched_switch().has_next_pid());
- ASSERT_FALSE(event.sched_switch().has_next_comm());
-
- ASSERT_TRUE(event.sched_switch().has_prev_pid());
ASSERT_TRUE(event.sched_switch().has_prev_comm());
+ ASSERT_FALSE(event.sched_switch().prev_comm().empty());
+
+ // Only cleared the next comm.
+ ASSERT_TRUE(event.sched_switch().has_next_comm());
+ ASSERT_TRUE(event.sched_switch().next_comm().empty());
}
} // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/scrub_trace_packet.cc b/src/trace_redaction/scrub_trace_packet.cc
index b8275e59c..fd29885ea 100644
--- a/src/trace_redaction/scrub_trace_packet.cc
+++ b/src/trace_redaction/scrub_trace_packet.cc
@@ -19,11 +19,18 @@
#include "src/trace_redaction/scrub_trace_packet.h"
#include "perfetto/base/status.h"
+#include "perfetto/protozero/scattered_heap_buffer.h"
+#include "src/trace_processor/util/status_macros.h"
+#include "src/trace_redaction/proto_util.h"
namespace perfetto::trace_redaction {
TracePacketFilter::~TracePacketFilter() = default;
+base::Status TracePacketFilter::VerifyContext(const Context&) const {
+ return base::OkStatus();
+}
+
base::Status ScrubTracePacket::Transform(const Context& context,
std::string* packet) const {
if (packet == nullptr || packet->empty()) {
@@ -31,26 +38,29 @@ base::Status ScrubTracePacket::Transform(const Context& context,
}
for (const auto& filter : filters_) {
- auto status = filter->VerifyContext(context);
-
- if (!status.ok()) {
- return status;
- }
+ RETURN_IF_ERROR(filter->VerifyContext(context));
}
- if (KeepEvent(context, *packet)) {
- return base::OkStatus();
+ protozero::HeapBuffered<protos::pbzero::TracePacket> new_packet;
+
+ protozero::ProtoDecoder decoder(*packet);
+
+ for (auto field = decoder.ReadField(); field.valid();
+ field = decoder.ReadField()) {
+ if (KeepEvent(context, field)) {
+ proto_util::AppendField(field, new_packet.get());
+ }
}
- packet->clear();
+ packet->assign(new_packet.SerializeAsString());
return base::OkStatus();
}
-// Logical AND of all filters.
+// Logical AND all filters.
bool ScrubTracePacket::KeepEvent(const Context& context,
- const std::string& bytes) const {
+ const protozero::Field& field) const {
for (const auto& filter : filters_) {
- if (!filter->KeepPacket(context, bytes)) {
+ if (!filter->KeepField(context, field)) {
return false;
}
}
diff --git a/src/trace_redaction/scrub_trace_packet.h b/src/trace_redaction/scrub_trace_packet.h
index 38dd2f186..22e506bdc 100644
--- a/src/trace_redaction/scrub_trace_packet.h
+++ b/src/trace_redaction/scrub_trace_packet.h
@@ -26,10 +26,12 @@ class TracePacketFilter {
virtual ~TracePacketFilter();
// Checks if the context contains all neccessary parameters.
- virtual base::Status VerifyContext(const Context& context) const = 0;
+ virtual base::Status VerifyContext(const Context& context) const;
- virtual bool KeepPacket(const Context& context,
- const std::string& bytes) const = 0;
+ // Checks if the field should be pass onto the new packet. Checks are a
+ // logical AND, so all filters must return true.
+ virtual bool KeepField(const Context& context,
+ const protozero::Field& field) const = 0;
};
class ScrubTracePacket : public TransformPrimitive {
@@ -43,7 +45,7 @@ class ScrubTracePacket : public TransformPrimitive {
}
private:
- bool KeepEvent(const Context& context, const std::string& bytes) const;
+ bool KeepEvent(const Context& context, const protozero::Field& field) const;
std::vector<std::unique_ptr<TracePacketFilter>> filters_;
};
diff --git a/src/trace_redaction/trace_redaction_framework.h b/src/trace_redaction/trace_redaction_framework.h
index f741f9b6f..cc135a930 100644
--- a/src/trace_redaction/trace_redaction_framework.h
+++ b/src/trace_redaction/trace_redaction_framework.h
@@ -21,9 +21,12 @@
#include <memory>
#include <optional>
#include <string>
+#include <unordered_set>
+#include <vector>
#include "perfetto/base/flat_set.h"
#include "perfetto/base/status.h"
+#include "src/trace_redaction/frame_cookie.h"
#include "src/trace_redaction/process_thread_timeline.h"
#include "protos/perfetto/trace/trace_packet.pbzero.h"
@@ -186,6 +189,34 @@ class Context {
// 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;
+
+ // All frame events:
+ //
+ // - ActualDisplayFrame
+ // - ActualSurfaceFrame
+ // - ExpectedDisplayFrame
+ // - ExpectedSurfaceFrame
+ //
+ // Connect a time, a pid, and a cookie value. Cookies are unqiue within a
+ // trace, so if a cookie was connected to the target package, it can always be
+ // used.
+ //
+ // End events (i.e. FrameEnd) only have a time and cookie value. The cookie
+ // value connects it to its start time.
+ //
+ // In the collect phase, all start events are collected and converted to a
+ // simpler structure.
+ //
+ // In the build phase, the cookies are filtered to only include the ones that
+ // belong to the target package. This is down in the build phase, and not the
+ // collect phase, because the timeline is needed to determine if the cookie
+ // belongs to the target package.
+ std::vector<FrameCookie> global_frame_cookies;
+
+ // The collect of cookies that belong to the target package. Because cookie
+ // values are unique within the scope of the trace, pid and time are no longer
+ // needed and a set can be used for faster queries.
+ std::unordered_set<int64_t> package_frame_cookies;
};
// Extracts low-level data from the trace and writes it into the context. The
diff --git a/src/traced/probes/ftrace/OWNERS b/src/traced/probes/ftrace/OWNERS
index 66ecb6cb1..52d5e506c 100644
--- a/src/traced/probes/ftrace/OWNERS
+++ b/src/traced/probes/ftrace/OWNERS
@@ -1,5 +1,4 @@
# People knowledgeable with traced_probes <> ftrace integration.
-hjd@google.com
primiano@google.com
rsavitski@google.com
skyostil@google.com
diff --git a/src/traced/probes/ftrace/event_info.cc b/src/traced/probes/ftrace/event_info.cc
index e11184ba5..09615fe11 100644
--- a/src/traced/probes/ftrace/event_info.cc
+++ b/src/traced/probes/ftrace/event_info.cc
@@ -7597,16 +7597,22 @@ std::vector<Event> GetStaticEventInfo() {
"panel",
{
{kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
- "type", 1, ProtoSchemaType::kUint32,
+ "pid", 1, ProtoSchemaType::kInt32,
TranslationStrategy::kInvalidTranslationStrategy},
{kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
- "pid", 2, ProtoSchemaType::kInt32,
+ "trace_name", 2, ProtoSchemaType::kString,
TranslationStrategy::kInvalidTranslationStrategy},
{kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
- "name", 3, ProtoSchemaType::kString,
+ "trace_begin", 3, ProtoSchemaType::kUint32,
TranslationStrategy::kInvalidTranslationStrategy},
{kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
- "value", 4, ProtoSchemaType::kInt32,
+ "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,
@@ -7616,55 +7622,55 @@ std::vector<Event> GetStaticEventInfo() {
"perf_trace_counters",
{
{kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
- "prev_comm", 1, ProtoSchemaType::kString,
+ "old_pid", 1, ProtoSchemaType::kInt32,
TranslationStrategy::kInvalidTranslationStrategy},
{kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
- "prev_pid", 2, ProtoSchemaType::kInt32,
+ "new_pid", 2, ProtoSchemaType::kInt32,
TranslationStrategy::kInvalidTranslationStrategy},
{kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
- "cyc", 3, ProtoSchemaType::kUint32,
+ "cctr", 3, ProtoSchemaType::kUint32,
TranslationStrategy::kInvalidTranslationStrategy},
{kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
- "inst", 4, ProtoSchemaType::kUint32,
+ "ctr0", 4, ProtoSchemaType::kUint32,
TranslationStrategy::kInvalidTranslationStrategy},
{kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
- "stallbm", 5, ProtoSchemaType::kUint32,
+ "ctr1", 5, ProtoSchemaType::kUint32,
TranslationStrategy::kInvalidTranslationStrategy},
{kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
- "l3dm", 6, ProtoSchemaType::kUint32,
+ "ctr2", 6, ProtoSchemaType::kUint32,
TranslationStrategy::kInvalidTranslationStrategy},
{kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
- "old_pid", 7, ProtoSchemaType::kInt32,
+ "ctr3", 7, ProtoSchemaType::kUint32,
TranslationStrategy::kInvalidTranslationStrategy},
{kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
- "new_pid", 8, ProtoSchemaType::kInt32,
+ "lctr0", 8, ProtoSchemaType::kUint32,
TranslationStrategy::kInvalidTranslationStrategy},
{kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
- "cctr", 9, ProtoSchemaType::kUint32,
+ "lctr1", 9, ProtoSchemaType::kUint32,
TranslationStrategy::kInvalidTranslationStrategy},
{kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
- "ctr0", 10, ProtoSchemaType::kUint32,
+ "ctr4", 10, ProtoSchemaType::kUint32,
TranslationStrategy::kInvalidTranslationStrategy},
{kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
- "ctr1", 11, ProtoSchemaType::kUint32,
+ "ctr5", 11, ProtoSchemaType::kUint32,
TranslationStrategy::kInvalidTranslationStrategy},
{kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
- "ctr2", 12, ProtoSchemaType::kUint32,
+ "prev_comm", 12, ProtoSchemaType::kString,
TranslationStrategy::kInvalidTranslationStrategy},
{kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
- "ctr3", 13, ProtoSchemaType::kUint32,
+ "prev_pid", 13, ProtoSchemaType::kInt32,
TranslationStrategy::kInvalidTranslationStrategy},
{kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
- "lctr0", 14, ProtoSchemaType::kUint32,
+ "cyc", 14, ProtoSchemaType::kUint32,
TranslationStrategy::kInvalidTranslationStrategy},
{kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
- "lctr1", 15, ProtoSchemaType::kUint32,
+ "inst", 15, ProtoSchemaType::kUint32,
TranslationStrategy::kInvalidTranslationStrategy},
{kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
- "ctr4", 16, ProtoSchemaType::kUint32,
+ "stallbm", 16, ProtoSchemaType::kUint32,
TranslationStrategy::kInvalidTranslationStrategy},
{kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
- "ctr5", 17, ProtoSchemaType::kUint32,
+ "l3dm", 17, ProtoSchemaType::kUint32,
TranslationStrategy::kInvalidTranslationStrategy},
},
kUnsetFtraceId,
diff --git a/test/cts/OWNERS b/test/cts/OWNERS
index 10fd9fe67..0a7edbb54 100644
--- a/test/cts/OWNERS
+++ b/test/cts/OWNERS
@@ -1,6 +1,5 @@
# Bug component: 323270
ddiproietto@google.com
-hjd@google.com
lalitm@google.com
primiano@google.com
rsavitski@google.com
diff --git a/test/cts/heapprofd_test_cts.cc b/test/cts/heapprofd_test_cts.cc
index c4eb97f1e..99d8c50e5 100644
--- a/test/cts/heapprofd_test_cts.cc
+++ b/test/cts/heapprofd_test_cts.cc
@@ -88,8 +88,8 @@ class ContentProviderReader {
bool multiuser_support = sdk && *sdk >= 34;
cmd_ = "content read";
if (multiuser_support) {
- // This command is available only starting from android U.
- cmd_ += " --user `cmd user get-main-user`";
+ // This is required only starting from android U.
+ cmd_ += " --user `am get-current-user`";
}
cmd_ += std::string(" --uri content://") + app + std::string("/") + path;
cmd_ += " >" + tempfile_;
diff --git a/test/data/api31_startup_cold.perfetto-trace.sha256 b/test/data/api31_startup_cold.perfetto-trace.sha256
new file mode 100644
index 000000000..98598a7e5
--- /dev/null
+++ b/test/data/api31_startup_cold.perfetto-trace.sha256
@@ -0,0 +1 @@
+aadf4a141dc0249abd4d7080a7d94bd614949eb0e7398fa6f3c75b503c70dade \ No newline at end of file
diff --git a/test/data/api31_startup_warm.perfetto-trace.sha256 b/test/data/api31_startup_warm.perfetto-trace.sha256
new file mode 100644
index 000000000..e04f24544
--- /dev/null
+++ b/test/data/api31_startup_warm.perfetto-trace.sha256
@@ -0,0 +1 @@
+4655c161656bf0eb66e8bc046ff899813e08bcf73f5e253ca88e8b3be231cb0e \ No newline at end of file
diff --git a/test/data/chrome/scroll_offsets_trace_2.pftrace.sha256 b/test/data/chrome/scroll_offsets_trace_2.pftrace.sha256
new file mode 100644
index 000000000..27d8ace3a
--- /dev/null
+++ b/test/data/chrome/scroll_offsets_trace_2.pftrace.sha256
@@ -0,0 +1 @@
+2ddd9f78d91d51e39c72c520bb54fdc9dbf1333ae722e87633fc345159296289 \ No newline at end of file
diff --git a/test/data/chrome_input_with_frame_view.pftrace.sha256 b/test/data/chrome_input_with_frame_view.pftrace.sha256
index ea5a60699..d0943a80e 100644
--- a/test/data/chrome_input_with_frame_view.pftrace.sha256
+++ b/test/data/chrome_input_with_frame_view.pftrace.sha256
@@ -1 +1 @@
-1e4e1b7098c3c1b900d31fa6d6791e7b022e85ecebbb560123ce7139b3f82231 \ No newline at end of file
+a93548822e481508c728ccc5da3ad34afcd0aec02ca7a7a4dad84ff340ee5975 \ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-android_trace_30s_expand_camera.png.sha256 b/test/data/ui-screenshots/ui-android_trace_30s_expand_camera.png.sha256
index a359061b2..ae1eb866b 100644
--- a/test/data/ui-screenshots/ui-android_trace_30s_expand_camera.png.sha256
+++ b/test/data/ui-screenshots/ui-android_trace_30s_expand_camera.png.sha256
@@ -1 +1 @@
-4b4bd13cbae5710efda25c6e2495c7ec5614d348701b9fec1fd554d5b1c61064 \ No newline at end of file
+27883a40c19205b1de4f9b87dfc22f4039986330074a7955e4432a045b657314 \ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-android_trace_30s_load.png.sha256 b/test/data/ui-screenshots/ui-android_trace_30s_load.png.sha256
index 02b1315e0..aa136cdce 100644
--- a/test/data/ui-screenshots/ui-android_trace_30s_load.png.sha256
+++ b/test/data/ui-screenshots/ui-android_trace_30s_load.png.sha256
@@ -1 +1 @@
-687a6fc2a478b048bdcbbbfe6231cf3b33463068c8100659a5be0220f41d07fb \ No newline at end of file
+b2c625bbdeb611de9a4bfbc3497353ee91322410bb867eee603bf2635353a21f \ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-chrome_missing_track_names_load.png.sha256 b/test/data/ui-screenshots/ui-chrome_missing_track_names_load.png.sha256
index ce2d330d5..81c36d88e 100644
--- a/test/data/ui-screenshots/ui-chrome_missing_track_names_load.png.sha256
+++ b/test/data/ui-screenshots/ui-chrome_missing_track_names_load.png.sha256
@@ -1 +1 @@
-ce747a5d7a1547aaa238bef8aa7b241d3e16cdc38dd34ef165e57aecb6659a92 \ No newline at end of file
+ff45b08442a6f59a4768f3ad697cffb43d19889f1f6523d5ed4ef83aff944864 \ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-chrome_rendering_desktop_expand_browser_proc.png.sha256 b/test/data/ui-screenshots/ui-chrome_rendering_desktop_expand_browser_proc.png.sha256
index 905ad7666..4d7b25a06 100644
--- a/test/data/ui-screenshots/ui-chrome_rendering_desktop_expand_browser_proc.png.sha256
+++ b/test/data/ui-screenshots/ui-chrome_rendering_desktop_expand_browser_proc.png.sha256
@@ -1 +1 @@
-c1fa13737c59bf1eeaa997037ca99117625b4d7956496702734317555ff5a2bc \ No newline at end of file
+161c05f6bae9b0e2142e73e83f3bcf7a9c689c58411db9e345a63ba882605557 \ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-chrome_rendering_desktop_load.png.sha256 b/test/data/ui-screenshots/ui-chrome_rendering_desktop_load.png.sha256
index de5d8b487..2d4876158 100644
--- a/test/data/ui-screenshots/ui-chrome_rendering_desktop_load.png.sha256
+++ b/test/data/ui-screenshots/ui-chrome_rendering_desktop_load.png.sha256
@@ -1 +1 @@
-b1cbf99aeee1bab2eabe0d1495697970535ef350f3d6e6d177ed21d469f3b11e \ No newline at end of file
+c0bf63de1c6c738fb994d64e5f2f7c5c0e4315f8627fc5fd68f5b906acbb814d \ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-chrome_rendering_desktop_select_slice_with_flows.png.sha256 b/test/data/ui-screenshots/ui-chrome_rendering_desktop_select_slice_with_flows.png.sha256
index 0843823c3..2ced23b13 100644
--- a/test/data/ui-screenshots/ui-chrome_rendering_desktop_select_slice_with_flows.png.sha256
+++ b/test/data/ui-screenshots/ui-chrome_rendering_desktop_select_slice_with_flows.png.sha256
@@ -1 +1 @@
-562482bc25d7a2d34b30d6003d100207e7f1eb0a48ea4549445a340e34a2e3b4 \ No newline at end of file
+0b5b8abecbbef18f25cbe0ddbfc7a8ff5e4aefb0c1ad07871cd72dc69d2647c9 \ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-modal_dialog_dismiss_1.png.sha256 b/test/data/ui-screenshots/ui-modal_dialog_dismiss_1.png.sha256
index 9c06aaacb..e9fa1e33e 100644
--- a/test/data/ui-screenshots/ui-modal_dialog_dismiss_1.png.sha256
+++ b/test/data/ui-screenshots/ui-modal_dialog_dismiss_1.png.sha256
@@ -1 +1 @@
-554b36f8fd3cf77c4e8e08fb53664dd1c5b54e2009045b513363c5d9ba861da9 \ No newline at end of file
+14056ade87692d8ec27ad4eb6c0c4fe55a14f87682b26af5acd829388326ed1b \ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-modal_dialog_dismiss_2.png.sha256 b/test/data/ui-screenshots/ui-modal_dialog_dismiss_2.png.sha256
index 0f29bad2d..0470eab22 100644
--- a/test/data/ui-screenshots/ui-modal_dialog_dismiss_2.png.sha256
+++ b/test/data/ui-screenshots/ui-modal_dialog_dismiss_2.png.sha256
@@ -1 +1 @@
-c7bb5ef50e96b3bdd96d684cbc65025e60ff9dd3281b220e4b99e07c2a896afe \ No newline at end of file
+ee1bbe38698683159bc2d51940ebda03e22f766302c26f71d9792fbd68f71fd7 \ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-modal_dialog_show_dialog_1.png.sha256 b/test/data/ui-screenshots/ui-modal_dialog_show_dialog_1.png.sha256
index ac18e7211..ace70f07b 100644
--- a/test/data/ui-screenshots/ui-modal_dialog_show_dialog_1.png.sha256
+++ b/test/data/ui-screenshots/ui-modal_dialog_show_dialog_1.png.sha256
@@ -1 +1 @@
-99c00e663442fd3ca7913b5ce7bea350af82f5ec8422d16f49175c91d484b52a \ No newline at end of file
+84f915ea5ab8d247a451de6844c66af348889f8ae789f8378b52660745f78ad2 \ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-modal_dialog_show_dialog_2.png.sha256 b/test/data/ui-screenshots/ui-modal_dialog_show_dialog_2.png.sha256
index ff1df49d5..8bf2a5316 100644
--- a/test/data/ui-screenshots/ui-modal_dialog_show_dialog_2.png.sha256
+++ b/test/data/ui-screenshots/ui-modal_dialog_show_dialog_2.png.sha256
@@ -1 +1 @@
-f345cfd5f9f2c5922d15c691dbe76d5d50ccfecdf35f2fd0033c1462299b0d17 \ No newline at end of file
+6906847e636f09c0d8476074392e6d79de745bed39adc79624a1a305477ad09b \ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-modal_dialog_switch_page_no_dialog.png.sha256 b/test/data/ui-screenshots/ui-modal_dialog_switch_page_no_dialog.png.sha256
index 58840ee1d..4b545db70 100644
--- a/test/data/ui-screenshots/ui-modal_dialog_switch_page_no_dialog.png.sha256
+++ b/test/data/ui-screenshots/ui-modal_dialog_switch_page_no_dialog.png.sha256
@@ -1 +1 @@
-b9146d41d92c3e164adb9ada4ed0b384f225220ef458a70c152a14c6bd29aef3 \ No newline at end of file
+d0a10b3fa63f100b16b8a595e805485cbb7b2ac7a7b25cc68024a726f4e69e6a \ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_navigate_navigate_back_and_forward.png.sha256 b/test/data/ui-screenshots/ui-routing_navigate_navigate_back_and_forward.png.sha256
index be09ae539..423413f73 100644
--- a/test/data/ui-screenshots/ui-routing_navigate_navigate_back_and_forward.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_navigate_navigate_back_and_forward.png.sha256
@@ -1 +1 @@
-b9f04ac7c1d9bc25023a22bd2e6d79c48cf44ac62da30db9454f3e367dc9a824 \ No newline at end of file
+a38e589b73daa4c8b1eb6fe5a592bc85fdd00139e7278e7289aed0d0c2a0b048 \ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_navigate_open_trace_from_url.png.sha256 b/test/data/ui-screenshots/ui-routing_navigate_open_trace_from_url.png.sha256
index 24ab3688d..1c8ee2061 100644
--- a/test/data/ui-screenshots/ui-routing_navigate_open_trace_from_url.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_navigate_open_trace_from_url.png.sha256
@@ -1 +1 @@
-f9b1725cd859b6cccf34d171e8b2e9d87b44a4fc241e9f577ccfde2ae3cc5216 \ No newline at end of file
+f7350a1ea142f50c7f8847cffa2ca1ec89faac07a57660b8b20f2d0d068f5747 \ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_open_invalid_trace_from_blank_page.png.sha256 b/test/data/ui-screenshots/ui-routing_open_invalid_trace_from_blank_page.png.sha256
index fa3e08248..bf4069851 100644
--- a/test/data/ui-screenshots/ui-routing_open_invalid_trace_from_blank_page.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_open_invalid_trace_from_blank_page.png.sha256
@@ -1 +1 @@
-82b386830bbfc6287b893331ecffcb1058634868a1bd43cce240b09297f060c7 \ No newline at end of file
+7031a1c1d49c9b953bd177ce268460d3702574b6d8b57542f9896c3cbcb91cac \ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_open_trace_and_go_back_to_landing_page.png.sha256 b/test/data/ui-screenshots/ui-routing_open_trace_and_go_back_to_landing_page.png.sha256
index 737aa225f..5b4b6e80c 100644
--- a/test/data/ui-screenshots/ui-routing_open_trace_and_go_back_to_landing_page.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_open_trace_and_go_back_to_landing_page.png.sha256
@@ -1 +1 @@
-148a4d82304842f610caa722e4cfc124eaa9e1da7b0ec1a1c0aeb790e03700c9 \ No newline at end of file
+bde6b98b6bf32e479277e34bbb410e08bf05840f6e34959c545ced0592f1f4c5 \ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_access_subpage_then_go_back.png.sha256 b/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_access_subpage_then_go_back.png.sha256
index 7b76fd1e7..c703153eb 100644
--- a/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_access_subpage_then_go_back.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_access_subpage_then_go_back.png.sha256
@@ -1 +1 @@
-431b27cc6333c21aac4bf681ab9d7f06e096f340f7913f1c1e3a943d4c4f0111 \ No newline at end of file
+4a24fb909c6346d5419905fc0a7ff69b9dda00fe77db6b953497d47f4dbe4149 \ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_open_first_trace_from_url.png.sha256 b/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_open_first_trace_from_url.png.sha256
index 24ab3688d..1c8ee2061 100644
--- a/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_open_first_trace_from_url.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_open_first_trace_from_url.png.sha256
@@ -1 +1 @@
-f9b1725cd859b6cccf34d171e8b2e9d87b44a4fc241e9f577ccfde2ae3cc5216 \ No newline at end of file
+f7350a1ea142f50c7f8847cffa2ca1ec89faac07a57660b8b20f2d0d068f5747 \ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_open_second_trace_from_url.png.sha256 b/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_open_second_trace_from_url.png.sha256
index 7b76fd1e7..c703153eb 100644
--- a/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_open_second_trace_from_url.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_open_second_trace_from_url.png.sha256
@@ -1 +1 @@
-431b27cc6333c21aac4bf681ab9d7f06e096f340f7913f1c1e3a943d4c4f0111 \ No newline at end of file
+4a24fb909c6346d5419905fc0a7ff69b9dda00fe77db6b953497d47f4dbe4149 \ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_start_from_no_trace_go_back_to_first_trace.png.sha256 b/test/data/ui-screenshots/ui-routing_start_from_no_trace_go_back_to_first_trace.png.sha256
index 7b76fd1e7..c703153eb 100644
--- a/test/data/ui-screenshots/ui-routing_start_from_no_trace_go_back_to_first_trace.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_start_from_no_trace_go_back_to_first_trace.png.sha256
@@ -1 +1 @@
-431b27cc6333c21aac4bf681ab9d7f06e096f340f7913f1c1e3a943d4c4f0111 \ No newline at end of file
+4a24fb909c6346d5419905fc0a7ff69b9dda00fe77db6b953497d47f4dbe4149 \ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_start_from_no_trace_go_to_page_with_no_trace.png.sha256 b/test/data/ui-screenshots/ui-routing_start_from_no_trace_go_to_page_with_no_trace.png.sha256
index 0aacaa460..517d3eb1a 100644
--- a/test/data/ui-screenshots/ui-routing_start_from_no_trace_go_to_page_with_no_trace.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_start_from_no_trace_go_to_page_with_no_trace.png.sha256
@@ -1 +1 @@
-539226bd5412f6f573473f91ae2fc6edda026744c143bdc4b027a069094de96f \ No newline at end of file
+2ce30cc545efe24f68a1b6efe7dd27971f6b40c777f08098f4d68c8e861f843f \ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_invalid_trace.png.sha256 b/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_invalid_trace.png.sha256
index a90276352..98854c1a7 100644
--- a/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_invalid_trace.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_invalid_trace.png.sha256
@@ -1 +1 @@
-eeb0625eb5286e0ca40b5d308608714ae7da0ca67d6bf07fac314a6639b5ddb5 \ No newline at end of file
+c662b150f085a83819bb6250c63095ace7c2d8fe0c44b9a7b16a0ed633177955 \ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_second_trace.png.sha256 b/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_second_trace.png.sha256
index 24ab3688d..1c8ee2061 100644
--- a/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_second_trace.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_second_trace.png.sha256
@@ -1 +1 @@
-f9b1725cd859b6cccf34d171e8b2e9d87b44a4fc241e9f577ccfde2ae3cc5216 \ No newline at end of file
+f7350a1ea142f50c7f8847cffa2ca1ec89faac07a57660b8b20f2d0d068f5747 \ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_trace_.png.sha256 b/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_trace_.png.sha256
index 7b76fd1e7..c703153eb 100644
--- a/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_trace_.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_trace_.png.sha256
@@ -1 +1 @@
-431b27cc6333c21aac4bf681ab9d7f06e096f340f7913f1c1e3a943d4c4f0111 \ No newline at end of file
+4a24fb909c6346d5419905fc0a7ff69b9dda00fe77db6b953497d47f4dbe4149 \ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_start_from_no_trace_refresh.png.sha256 b/test/data/ui-screenshots/ui-routing_start_from_no_trace_refresh.png.sha256
index 7b76fd1e7..c703153eb 100644
--- a/test/data/ui-screenshots/ui-routing_start_from_no_trace_refresh.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_start_from_no_trace_refresh.png.sha256
@@ -1 +1 @@
-431b27cc6333c21aac4bf681ab9d7f06e096f340f7913f1c1e3a943d4c4f0111 \ No newline at end of file
+4a24fb909c6346d5419905fc0a7ff69b9dda00fe77db6b953497d47f4dbe4149 \ No newline at end of file
diff --git a/test/trace_processor/diff_tests/metrics/android/android_boot.out b/test/trace_processor/diff_tests/metrics/android/android_boot.out
index 3bf13cce9..50f4757df 100644
--- a/test/trace_processor/diff_tests/metrics/android/android_boot.out
+++ b/test/trace_processor/diff_tests/metrics/android/android_boot.out
@@ -1,59 +1,915 @@
android_boot {
- system_server_durations {
- total_dur: 90219646678
- uninterruptible_sleep_dur: 618417159
- }
- systemui_durations {
- total_dur: 48481027953
- uninterruptible_sleep_dur: 796263
- }
- launcher_durations {
- total_dur: 23595248987
- uninterruptible_sleep_dur: 257290255
- }
- gms_durations {
- total_dur: 27804143410
- uninterruptible_sleep_dur: 101685087
- }
- launcher_breakdown {
- cold_start_dur: 403543498
- }
- full_trace_process_start_aggregation {
- total_start_sum: 10678297679
- num_of_processes: 29
- average_start_time: 368217161.3448276
- }
- post_boot_process_start_aggregation {
- total_start_sum: 6112984648
- num_of_processes: 21
- average_start_time: 291094507.04761904
- }
- full_trace_gc_aggregation {
- total_gc_count: 4
- num_of_processes_with_gc: 4
- num_of_threads_with_gc: 4
- avg_gc_duration: 260516077.75
- avg_running_gc_duration: 3902628.5
- full_gc_count: 4
- collector_transition_gc_count: 0
- young_gc_count: 0
- native_alloc_gc_count: 0
- explicit_gc_count: 0
- alloc_gc_count: 0
- mb_per_ms_of_gc: 0.8829305684617432
- }
- post_boot_gc_aggregation {
- total_gc_count: 4
- num_of_processes_with_gc: 4
- num_of_threads_with_gc: 4
- avg_gc_duration: 260516077.75
- avg_running_gc_duration: 3902628.5
- full_gc_count: 4
- collector_transition_gc_count: 0
- young_gc_count: 0
- native_alloc_gc_count: 0
- explicit_gc_count: 0
- alloc_gc_count: 0
- mb_per_ms_of_gc: 0.8829305684617432
- }
-} \ No newline at end of file
+system_server_durations {
+total_dur: 90219646678
+uninterruptible_sleep_dur: 618417159
+}
+systemui_durations {
+total_dur: 48481027953
+uninterruptible_sleep_dur: 796263
+}
+launcher_durations {
+total_dur: 23595248987
+uninterruptible_sleep_dur: 257290255
+}
+gms_durations {
+total_dur: 27804143410
+uninterruptible_sleep_dur: 101685087
+}
+launcher_breakdown {
+cold_start_dur: 403543498
+}
+full_trace_process_start_aggregation {
+total_start_sum: 10678297679
+num_of_processes: 29
+average_start_time: 368217161.3448276
+}
+post_boot_process_start_aggregation {
+total_start_sum: 6112984648
+num_of_processes: 21
+average_start_time: 291094507.04761904
+}
+full_trace_gc_aggregation {
+total_gc_count: 4
+num_of_processes_with_gc: 4
+num_of_threads_with_gc: 4
+avg_gc_duration: 260516077.75
+avg_running_gc_duration: 3902628.5
+full_gc_count: 4
+collector_transition_gc_count: 0
+young_gc_count: 0
+native_alloc_gc_count: 0
+explicit_gc_count: 0
+alloc_gc_count: 0
+mb_per_ms_of_gc: 0.8829305684617432
+}
+post_boot_gc_aggregation {
+total_gc_count: 4
+num_of_processes_with_gc: 4
+num_of_threads_with_gc: 4
+avg_gc_duration: 260516077.75
+avg_running_gc_duration: 3902628.5
+full_gc_count: 4
+collector_transition_gc_count: 0
+young_gc_count: 0
+native_alloc_gc_count: 0
+explicit_gc_count: 0
+alloc_gc_count: 0
+mb_per_ms_of_gc: 0.8829305684617432
+}
+post_boot_oom_adjuster_transition_counts_global {
+src_bucket: "cached_app"
+dest_bucket: "cached_app"
+count: 16
+}
+post_boot_oom_adjuster_transition_counts_global {
+src_bucket: "foreground_app"
+dest_bucket: "cached_app"
+count: 3
+}
+post_boot_oom_adjuster_transition_counts_global {
+src_bucket: "previous_app"
+dest_bucket: "cached_app"
+count: 2
+}
+post_boot_oom_adjuster_transition_counts_global {
+dest_bucket: "foreground_app"
+count: 21
+}
+post_boot_oom_adjuster_transition_counts_global {
+src_bucket: "cached_app"
+dest_bucket: "foreground_app"
+count: 4
+}
+post_boot_oom_adjuster_transition_counts_global {
+src_bucket: "perceptible_app"
+dest_bucket: "foreground_app"
+count: 1
+}
+post_boot_oom_adjuster_transition_counts_global {
+src_bucket: "perceptible_low_app"
+dest_bucket: "foreground_app"
+count: 2
+}
+post_boot_oom_adjuster_transition_counts_global {
+src_bucket: "previous_app"
+dest_bucket: "foreground_app"
+count: 1
+}
+post_boot_oom_adjuster_transition_counts_global {
+src_bucket: "visible_app"
+dest_bucket: "foreground_app"
+count: 3
+}
+post_boot_oom_adjuster_transition_counts_global {
+dest_bucket: "native"
+count: 4
+}
+post_boot_oom_adjuster_transition_counts_global {
+src_bucket: "foreground_app"
+dest_bucket: "perceptible_app"
+count: 2
+}
+post_boot_oom_adjuster_transition_counts_global {
+src_bucket: "foreground_app"
+dest_bucket: "perceptible_low_app"
+count: 3
+}
+post_boot_oom_adjuster_transition_counts_global {
+src_bucket: "foreground_app"
+dest_bucket: "persistent_service"
+count: 1
+}
+post_boot_oom_adjuster_transition_counts_global {
+src_bucket: "foreground_app"
+dest_bucket: "visible_app"
+count: 4
+}
+post_boot_oom_adjuster_transition_counts_by_process {
+name: "/system/bin/apexd"
+dest_bucket: "native"
+count: 1
+}
+post_boot_oom_adjuster_transition_counts_by_process {
+name: "<pre-initialized>"
+dest_bucket: "native"
+count: 2
+}
+post_boot_oom_adjuster_transition_counts_by_process {
+name: "android.process.acore"
+src_bucket: "previous_app"
+dest_bucket: "foreground_app"
+count: 1
+}
+post_boot_oom_adjuster_transition_counts_by_process {
+name: "com.amazon.mp3"
+dest_bucket: "foreground_app"
+count: 1
+}
+post_boot_oom_adjuster_transition_counts_by_process {
+name: "com.android.keychain"
+dest_bucket: "foreground_app"
+count: 1
+}
+post_boot_oom_adjuster_transition_counts_by_process {
+name: "com.android.printspooler"
+dest_bucket: "foreground_app"
+count: 1
+}
+post_boot_oom_adjuster_transition_counts_by_process {
+name: "com.android.providers.calendar"
+dest_bucket: "foreground_app"
+count: 1
+}
+post_boot_oom_adjuster_transition_counts_by_process {
+name: "com.android.settings"
+src_bucket: "foreground_app"
+dest_bucket: "cached_app"
+count: 1
+}
+post_boot_oom_adjuster_transition_counts_by_process {
+name: "com.apple.android.music"
+dest_bucket: "foreground_app"
+count: 1
+}
+post_boot_oom_adjuster_transition_counts_by_process {
+name: "com.blinkslabs.blinkist.android"
+dest_bucket: "foreground_app"
+count: 1
+}
+post_boot_oom_adjuster_transition_counts_by_process {
+name: "com.google.android.apps.books"
+dest_bucket: "foreground_app"
+count: 1
+}
+post_boot_oom_adjuster_transition_counts_by_process {
+name: "com.google.android.apps.dreamliner"
+src_bucket: "foreground_app"
+dest_bucket: "cached_app"
+count: 1
+}
+post_boot_oom_adjuster_transition_counts_by_process {
+name: "com.google.android.apps.dreamliner"
+dest_bucket: "foreground_app"
+count: 1
+}
+post_boot_oom_adjuster_transition_counts_by_process {
+name: "com.google.android.apps.dreamliner"
+src_bucket: "perceptible_low_app"
+dest_bucket: "foreground_app"
+count: 1
+}
+post_boot_oom_adjuster_transition_counts_by_process {
+name: "com.google.android.apps.dreamliner"
+src_bucket: "foreground_app"
+dest_bucket: "perceptible_low_app"
+count: 1
+}
+post_boot_oom_adjuster_transition_counts_by_process {
+name: "com.google.android.apps.messaging"
+dest_bucket: "native"
+count: 1
+}
+post_boot_oom_adjuster_transition_counts_by_process {
+name: "com.google.android.apps.messaging:rcs"
+src_bucket: "perceptible_app"
+dest_bucket: "foreground_app"
+count: 1
+}
+post_boot_oom_adjuster_transition_counts_by_process {
+name: "com.google.android.apps.messaging:rcs"
+src_bucket: "foreground_app"
+dest_bucket: "perceptible_app"
+count: 2
+}
+post_boot_oom_adjuster_transition_counts_by_process {
+name: "com.google.android.apps.nexuslauncher"
+src_bucket: "visible_app"
+dest_bucket: "foreground_app"
+count: 1
+}
+post_boot_oom_adjuster_transition_counts_by_process {
+name: "com.google.android.apps.wallpaper"
+dest_bucket: "foreground_app"
+count: 1
+}
+post_boot_oom_adjuster_transition_counts_by_process {
+name: "com.google.android.apps.wellbeing"
+src_bucket: "cached_app"
+dest_bucket: "cached_app"
+count: 5
+}
+post_boot_oom_adjuster_transition_counts_by_process {
+name: "com.google.android.apps.wellbeing"
+src_bucket: "cached_app"
+dest_bucket: "foreground_app"
+count: 1
+}
+post_boot_oom_adjuster_transition_counts_by_process {
+name: "com.google.android.apps.work.clouddpc"
+src_bucket: "cached_app"
+dest_bucket: "cached_app"
+count: 2
+}
+post_boot_oom_adjuster_transition_counts_by_process {
+name: "com.google.android.apps.youtube.music"
+dest_bucket: "foreground_app"
+count: 1
+}
+post_boot_oom_adjuster_transition_counts_by_process {
+name: "com.google.android.as.oss"
+src_bucket: "foreground_app"
+dest_bucket: "cached_app"
+count: 1
+}
+post_boot_oom_adjuster_transition_counts_by_process {
+name: "com.google.android.as.oss"
+dest_bucket: "foreground_app"
+count: 1
+}
+post_boot_oom_adjuster_transition_counts_by_process {
+name: "com.google.android.as.oss"
+src_bucket: "perceptible_low_app"
+dest_bucket: "foreground_app"
+count: 1
+}
+post_boot_oom_adjuster_transition_counts_by_process {
+name: "com.google.android.as.oss"
+src_bucket: "foreground_app"
+dest_bucket: "perceptible_low_app"
+count: 1
+}
+post_boot_oom_adjuster_transition_counts_by_process {
+name: "com.google.android.calendar"
+dest_bucket: "foreground_app"
+count: 1
+}
+post_boot_oom_adjuster_transition_counts_by_process {
+name: "com.google.android.carrier"
+src_bucket: "previous_app"
+dest_bucket: "cached_app"
+count: 1
+}
+post_boot_oom_adjuster_transition_counts_by_process {
+name: "com.google.android.cellbroadcastreceiver"
+src_bucket: "cached_app"
+dest_bucket: "cached_app"
+count: 1
+}
+post_boot_oom_adjuster_transition_counts_by_process {
+name: "com.google.android.deskclock"
+src_bucket: "cached_app"
+dest_bucket: "foreground_app"
+count: 1
+}
+post_boot_oom_adjuster_transition_counts_by_process {
+name: "com.google.android.devicelockcontroller"
+src_bucket: "cached_app"
+dest_bucket: "cached_app"
+count: 1
+}
+post_boot_oom_adjuster_transition_counts_by_process {
+name: "com.google.android.devicelockcontroller"
+src_bucket: "cached_app"
+dest_bucket: "foreground_app"
+count: 1
+}
+post_boot_oom_adjuster_transition_counts_by_process {
+name: "com.google.android.devicelockcontroller"
+src_bucket: "visible_app"
+dest_bucket: "foreground_app"
+count: 1
+}
+post_boot_oom_adjuster_transition_counts_by_process {
+name: "com.google.android.devicelockcontroller"
+src_bucket: "foreground_app"
+dest_bucket: "visible_app"
+count: 2
+}
+post_boot_oom_adjuster_transition_counts_by_process {
+name: "com.google.android.dialer"
+src_bucket: "cached_app"
+dest_bucket: "cached_app"
+count: 4
+}
+post_boot_oom_adjuster_transition_counts_by_process {
+name: "com.google.android.gms"
+src_bucket: "cached_app"
+dest_bucket: "cached_app"
+count: 2
+}
+post_boot_oom_adjuster_transition_counts_by_process {
+name: "com.google.android.gms"
+src_bucket: "cached_app"
+dest_bucket: "foreground_app"
+count: 1
+}
+post_boot_oom_adjuster_transition_counts_by_process {
+name: "com.google.android.gms"
+src_bucket: "visible_app"
+dest_bucket: "foreground_app"
+count: 1
+}
+post_boot_oom_adjuster_transition_counts_by_process {
+name: "com.google.android.gms"
+src_bucket: "foreground_app"
+dest_bucket: "visible_app"
+count: 1
+}
+post_boot_oom_adjuster_transition_counts_by_process {
+name: "com.google.android.googlequicksearchbox:interactor"
+src_bucket: "foreground_app"
+dest_bucket: "visible_app"
+count: 1
+}
+post_boot_oom_adjuster_transition_counts_by_process {
+name: "com.google.android.googlequicksearchbox:search"
+dest_bucket: "foreground_app"
+count: 1
+}
+post_boot_oom_adjuster_transition_counts_by_process {
+name: "com.google.android.keep"
+dest_bucket: "foreground_app"
+count: 1
+}
+post_boot_oom_adjuster_transition_counts_by_process {
+name: "com.google.android.permissioncontroller"
+src_bucket: "cached_app"
+dest_bucket: "cached_app"
+count: 1
+}
+post_boot_oom_adjuster_transition_counts_by_process {
+name: "com.google.android.providers.media.module"
+src_bucket: "foreground_app"
+dest_bucket: "persistent_service"
+count: 1
+}
+post_boot_oom_adjuster_transition_counts_by_process {
+name: "com.google.android.youtube"
+dest_bucket: "foreground_app"
+count: 1
+}
+post_boot_oom_adjuster_transition_counts_by_process {
+name: "com.google.ar.core"
+dest_bucket: "foreground_app"
+count: 1
+}
+post_boot_oom_adjuster_transition_counts_by_process {
+name: "com.google.ar.core"
+src_bucket: "foreground_app"
+dest_bucket: "perceptible_low_app"
+count: 1
+}
+post_boot_oom_adjuster_transition_counts_by_process {
+name: "com.google.euiccpixel"
+src_bucket: "previous_app"
+dest_bucket: "cached_app"
+count: 1
+}
+post_boot_oom_adjuster_transition_counts_by_process {
+name: "com.overdrive.mobile.android.libby"
+dest_bucket: "foreground_app"
+count: 1
+}
+post_boot_oom_adjuster_transition_counts_by_process {
+name: "com.patreon.android"
+dest_bucket: "foreground_app"
+count: 1
+}
+post_boot_oom_adjuster_transition_counts_by_process {
+name: "com.spotify.music"
+dest_bucket: "foreground_app"
+count: 1
+}
+post_boot_oom_adjuster_transition_counts_by_process {
+name: "org.thoughtcrime.securesms"
+dest_bucket: "foreground_app"
+count: 1
+}
+post_boot_oom_adjuster_transition_counts_by_process {
+name: "org.videolan.vlc"
+dest_bucket: "foreground_app"
+count: 1
+}
+post_boot_oom_adjuster_transition_counts_by_oom_adj_reason {
+name: "activityChange"
+src_bucket: "cached_app"
+dest_bucket: "cached_app"
+count: 3
+}
+post_boot_oom_adjuster_transition_counts_by_oom_adj_reason {
+name: "bindService"
+src_bucket: "cached_app"
+dest_bucket: "cached_app"
+count: 5
+}
+post_boot_oom_adjuster_transition_counts_by_oom_adj_reason {
+name: "getProvider"
+src_bucket: "cached_app"
+dest_bucket: "cached_app"
+count: 1
+}
+post_boot_oom_adjuster_transition_counts_by_oom_adj_reason {
+name: "startReceiver"
+src_bucket: "cached_app"
+dest_bucket: "cached_app"
+count: 7
+}
+post_boot_oom_adjuster_transition_counts_by_oom_adj_reason {
+name: "activityChange"
+src_bucket: "foreground_app"
+dest_bucket: "cached_app"
+count: 1
+}
+post_boot_oom_adjuster_transition_counts_by_oom_adj_reason {
+name: "executingService"
+src_bucket: "foreground_app"
+dest_bucket: "cached_app"
+count: 2
+}
+post_boot_oom_adjuster_transition_counts_by_oom_adj_reason {
+name: "activityChange"
+src_bucket: "previous_app"
+dest_bucket: "cached_app"
+count: 2
+}
+post_boot_oom_adjuster_transition_counts_by_oom_adj_reason {
+name: "processBegin"
+dest_bucket: "foreground_app"
+count: 21
+}
+post_boot_oom_adjuster_transition_counts_by_oom_adj_reason {
+name: "getProvider"
+src_bucket: "cached_app"
+dest_bucket: "foreground_app"
+count: 1
+}
+post_boot_oom_adjuster_transition_counts_by_oom_adj_reason {
+name: "startReceiver"
+src_bucket: "cached_app"
+dest_bucket: "foreground_app"
+count: 2
+}
+post_boot_oom_adjuster_transition_counts_by_oom_adj_reason {
+name: "startService"
+src_bucket: "cached_app"
+dest_bucket: "foreground_app"
+count: 1
+}
+post_boot_oom_adjuster_transition_counts_by_oom_adj_reason {
+name: "startService"
+src_bucket: "perceptible_app"
+dest_bucket: "foreground_app"
+count: 1
+}
+post_boot_oom_adjuster_transition_counts_by_oom_adj_reason {
+name: "unbindService"
+src_bucket: "perceptible_low_app"
+dest_bucket: "foreground_app"
+count: 2
+}
+post_boot_oom_adjuster_transition_counts_by_oom_adj_reason {
+name: "getProvider"
+src_bucket: "previous_app"
+dest_bucket: "foreground_app"
+count: 1
+}
+post_boot_oom_adjuster_transition_counts_by_oom_adj_reason {
+name: "bindService"
+src_bucket: "visible_app"
+dest_bucket: "foreground_app"
+count: 1
+}
+post_boot_oom_adjuster_transition_counts_by_oom_adj_reason {
+name: "getProvider"
+src_bucket: "visible_app"
+dest_bucket: "foreground_app"
+count: 1
+}
+post_boot_oom_adjuster_transition_counts_by_oom_adj_reason {
+name: "startService"
+src_bucket: "visible_app"
+dest_bucket: "foreground_app"
+count: 1
+}
+post_boot_oom_adjuster_transition_counts_by_oom_adj_reason {
+name: "executingService"
+dest_bucket: "native"
+count: 4
+}
+post_boot_oom_adjuster_transition_counts_by_oom_adj_reason {
+name: "executingService"
+src_bucket: "foreground_app"
+dest_bucket: "perceptible_app"
+count: 2
+}
+post_boot_oom_adjuster_transition_counts_by_oom_adj_reason {
+name: "executingService"
+src_bucket: "foreground_app"
+dest_bucket: "perceptible_low_app"
+count: 3
+}
+post_boot_oom_adjuster_transition_counts_by_oom_adj_reason {
+name: "startService"
+src_bucket: "foreground_app"
+dest_bucket: "persistent_service"
+count: 1
+}
+post_boot_oom_adjuster_transition_counts_by_oom_adj_reason {
+name: "executingService"
+src_bucket: "foreground_app"
+dest_bucket: "visible_app"
+count: 3
+}
+post_boot_oom_adjuster_transition_counts_by_oom_adj_reason {
+name: "finishReceiver"
+src_bucket: "foreground_app"
+dest_bucket: "visible_app"
+count: 1
+}
+post_boot_oom_adj_bucket_duration_agg_global {
+bucket: "cached_app"
+total_dur: 10419196404
+}
+post_boot_oom_adj_bucket_duration_agg_global {
+bucket: "foreground_app"
+total_dur: 23641055480
+}
+post_boot_oom_adj_bucket_duration_agg_global {
+bucket: "native"
+total_dur: 101590616
+}
+post_boot_oom_adj_bucket_duration_agg_global {
+bucket: "perceptible_app"
+total_dur: 940071238
+}
+post_boot_oom_adj_bucket_duration_agg_global {
+bucket: "perceptible_low_app"
+total_dur: 199288116
+}
+post_boot_oom_adj_bucket_duration_agg_global {
+bucket: "persistent_service"
+total_dur: 1546461864
+}
+post_boot_oom_adj_bucket_duration_agg_global {
+bucket: "visible_app"
+total_dur: 2769744731
+}
+post_boot_oom_adj_bucket_duration_agg_by_process {
+name: "/system/bin/apexd"
+bucket: "native"
+total_dur: 25397654
+}
+post_boot_oom_adj_bucket_duration_agg_by_process {
+name: "<pre-initialized>"
+bucket: "native"
+total_dur: 50795308
+}
+post_boot_oom_adj_bucket_duration_agg_by_process {
+name: "android.process.acore"
+bucket: "foreground_app"
+total_dur: 823432282
+}
+post_boot_oom_adj_bucket_duration_agg_by_process {
+name: "com.amazon.mp3"
+bucket: "foreground_app"
+total_dur: 1322230378
+}
+post_boot_oom_adj_bucket_duration_agg_by_process {
+name: "com.android.keychain"
+bucket: "foreground_app"
+total_dur: 196867340
+}
+post_boot_oom_adj_bucket_duration_agg_by_process {
+name: "com.android.printspooler"
+bucket: "foreground_app"
+total_dur: 343904124
+}
+post_boot_oom_adj_bucket_duration_agg_by_process {
+name: "com.android.providers.calendar"
+bucket: "foreground_app"
+total_dur: 281386139
+}
+post_boot_oom_adj_bucket_duration_agg_by_process {
+name: "com.android.settings"
+bucket: "cached_app"
+total_dur: 967052521
+}
+post_boot_oom_adj_bucket_duration_agg_by_process {
+name: "com.apple.android.music"
+bucket: "foreground_app"
+total_dur: 1268519522
+}
+post_boot_oom_adj_bucket_duration_agg_by_process {
+name: "com.blinkslabs.blinkist.android"
+bucket: "foreground_app"
+total_dur: 1250421540
+}
+post_boot_oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.apps.books"
+bucket: "foreground_app"
+total_dur: 1076963328
+}
+post_boot_oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.apps.dreamliner"
+bucket: "cached_app"
+total_dur: 1086183503
+}
+post_boot_oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.apps.dreamliner"
+bucket: "foreground_app"
+total_dur: 402609904
+}
+post_boot_oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.apps.dreamliner"
+bucket: "perceptible_low_app"
+total_dur: 7658935
+}
+post_boot_oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.apps.messaging"
+bucket: "native"
+total_dur: 25397654
+}
+post_boot_oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.apps.messaging:rcs"
+bucket: "foreground_app"
+total_dur: 109047811
+}
+post_boot_oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.apps.messaging:rcs"
+bucket: "perceptible_app"
+total_dur: 940071238
+}
+post_boot_oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.apps.nexuslauncher"
+bucket: "foreground_app"
+total_dur: 1661132477
+}
+post_boot_oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.apps.wallpaper"
+bucket: "foreground_app"
+total_dur: 177919993
+}
+post_boot_oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.apps.wellbeing"
+bucket: "cached_app"
+total_dur: 816703328
+}
+post_boot_oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.apps.wellbeing"
+bucket: "foreground_app"
+total_dur: 884970246
+}
+post_boot_oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.apps.work.clouddpc"
+bucket: "cached_app"
+total_dur: 1695452790
+}
+post_boot_oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.apps.youtube.music"
+bucket: "foreground_app"
+total_dur: 1558635529
+}
+post_boot_oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.as.oss"
+bucket: "cached_app"
+total_dur: 621111806
+}
+post_boot_oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.as.oss"
+bucket: "foreground_app"
+total_dur: 303909018
+}
+post_boot_oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.as.oss"
+bucket: "perceptible_low_app"
+total_dur: 9431030
+}
+post_boot_oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.calendar"
+bucket: "foreground_app"
+total_dur: 1573637523
+}
+post_boot_oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.carrier"
+bucket: "cached_app"
+total_dur: 870239329
+}
+post_boot_oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.cellbroadcastreceiver"
+bucket: "cached_app"
+total_dur: 870311514
+}
+post_boot_oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.deskclock"
+bucket: "foreground_app"
+total_dur: 1674669994
+}
+post_boot_oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.devicelockcontroller"
+bucket: "cached_app"
+total_dur: 11603068
+}
+post_boot_oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.devicelockcontroller"
+bucket: "foreground_app"
+total_dur: 115664632
+}
+post_boot_oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.devicelockcontroller"
+bucket: "visible_app"
+total_dur: 1568444611
+}
+post_boot_oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.dialer"
+bucket: "cached_app"
+total_dur: 1701572215
+}
+post_boot_oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.gms"
+bucket: "cached_app"
+total_dur: 38437012
+}
+post_boot_oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.gms"
+bucket: "foreground_app"
+total_dur: 1259805736
+}
+post_boot_oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.gms"
+bucket: "visible_app"
+total_dur: 397030029
+}
+post_boot_oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.googlequicksearchbox:interactor"
+bucket: "visible_app"
+total_dur: 804270091
+}
+post_boot_oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.googlequicksearchbox:search"
+bucket: "foreground_app"
+total_dur: 1467442984
+}
+post_boot_oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.keep"
+bucket: "foreground_app"
+total_dur: 576153188
+}
+post_boot_oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.permissioncontroller"
+bucket: "cached_app"
+total_dur: 870388947
+}
+post_boot_oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.providers.media.module"
+bucket: "persistent_service"
+total_dur: 1546461864
+}
+post_boot_oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.youtube"
+bucket: "foreground_app"
+total_dur: 1450400991
+}
+post_boot_oom_adj_bucket_duration_agg_by_process {
+name: "com.google.ar.core"
+bucket: "foreground_app"
+total_dur: 733794515
+}
+post_boot_oom_adj_bucket_duration_agg_by_process {
+name: "com.google.ar.core"
+bucket: "perceptible_low_app"
+total_dur: 182198151
+}
+post_boot_oom_adj_bucket_duration_agg_by_process {
+name: "com.google.euiccpixel"
+bucket: "cached_app"
+total_dur: 870140371
+}
+post_boot_oom_adj_bucket_duration_agg_by_process {
+name: "com.overdrive.mobile.android.libby"
+bucket: "foreground_app"
+total_dur: 889039012
+}
+post_boot_oom_adj_bucket_duration_agg_by_process {
+name: "com.patreon.android"
+bucket: "foreground_app"
+total_dur: 608530141
+}
+post_boot_oom_adj_bucket_duration_agg_by_process {
+name: "com.spotify.music"
+bucket: "foreground_app"
+total_dur: 566682160
+}
+post_boot_oom_adj_bucket_duration_agg_by_process {
+name: "org.thoughtcrime.securesms"
+bucket: "foreground_app"
+total_dur: 701758738
+}
+post_boot_oom_adj_bucket_duration_agg_by_process {
+name: "org.videolan.vlc"
+bucket: "foreground_app"
+total_dur: 361526235
+}
+post_boot_oom_adj_duration_agg {
+min_oom_adj_dur: 266887
+max_oom_adj_dur: 7292196
+avg_oom_adj_dur: 6121311.166666667
+oom_adj_event_count: 2
+oom_adj_reason: "activityChange"
+}
+post_boot_oom_adj_duration_agg {
+min_oom_adj_dur: 67180
+max_oom_adj_dur: 225871
+avg_oom_adj_dur: 160115.83333333334
+oom_adj_event_count: 5
+oom_adj_reason: "bindService"
+}
+post_boot_oom_adj_duration_agg {
+min_oom_adj_dur: 64657
+max_oom_adj_dur: 565674
+avg_oom_adj_dur: 232660.2142857143
+oom_adj_event_count: 11
+oom_adj_reason: "executingService"
+}
+post_boot_oom_adj_duration_agg {
+min_oom_adj_dur: 198120
+max_oom_adj_dur: 198120
+avg_oom_adj_dur: 198120.0
+oom_adj_event_count: 1
+oom_adj_reason: "finishReceiver"
+}
+post_boot_oom_adj_duration_agg {
+min_oom_adj_dur: 193766
+max_oom_adj_dur: 626302
+avg_oom_adj_dur: 383901.0
+oom_adj_event_count: 4
+oom_adj_reason: "getProvider"
+}
+post_boot_oom_adj_duration_agg {
+min_oom_adj_dur: 92366
+max_oom_adj_dur: 1461914
+avg_oom_adj_dur: 257078.14285714287
+oom_adj_event_count: 21
+oom_adj_reason: "processBegin"
+}
+post_boot_oom_adj_duration_agg {
+min_oom_adj_dur: 238525
+max_oom_adj_dur: 5160522
+avg_oom_adj_dur: 883870.1111111111
+oom_adj_event_count: 4
+oom_adj_reason: "startReceiver"
+}
+post_boot_oom_adj_duration_agg {
+min_oom_adj_dur: 68645
+max_oom_adj_dur: 2294270
+avg_oom_adj_dur: 747080.5
+oom_adj_event_count: 4
+oom_adj_reason: "startService"
+}
+post_boot_oom_adj_duration_agg {
+min_oom_adj_dur: 165242
+max_oom_adj_dur: 380290
+avg_oom_adj_dur: 272766.0
+oom_adj_event_count: 2
+oom_adj_reason: "unbindService"
+}
+}
diff --git a/test/trace_processor/diff_tests/metrics/android/android_oom_adjuster.out b/test/trace_processor/diff_tests/metrics/android/android_oom_adjuster.out
new file mode 100644
index 000000000..e7135d00c
--- /dev/null
+++ b/test/trace_processor/diff_tests/metrics/android/android_oom_adjuster.out
@@ -0,0 +1,2745 @@
+android_oom_adjuster {
+oom_adjuster_transition_counts_global {
+dest_bucket: "cached_app"
+count: 7
+}
+oom_adjuster_transition_counts_global {
+src_bucket: "cached_app"
+dest_bucket: "cached_app"
+count: 27
+}
+oom_adjuster_transition_counts_global {
+src_bucket: "cached_app_lmk_first"
+dest_bucket: "cached_app"
+count: 5
+}
+oom_adjuster_transition_counts_global {
+src_bucket: "foreground_app"
+dest_bucket: "cached_app"
+count: 3
+}
+oom_adjuster_transition_counts_global {
+src_bucket: "previous_app"
+dest_bucket: "cached_app"
+count: 2
+}
+oom_adjuster_transition_counts_global {
+dest_bucket: "cached_app_lmk_first"
+count: 2
+}
+oom_adjuster_transition_counts_global {
+src_bucket: "cached_app"
+dest_bucket: "cached_app_lmk_first"
+count: 2
+}
+oom_adjuster_transition_counts_global {
+src_bucket: "cached_app_lmk_first"
+dest_bucket: "cached_app_lmk_first"
+count: 2
+}
+oom_adjuster_transition_counts_global {
+src_bucket: "previous_app"
+dest_bucket: "cached_app_lmk_first"
+count: 1
+}
+oom_adjuster_transition_counts_global {
+dest_bucket: "foreground_app"
+count: 27
+}
+oom_adjuster_transition_counts_global {
+src_bucket: "cached_app"
+dest_bucket: "foreground_app"
+count: 4
+}
+oom_adjuster_transition_counts_global {
+src_bucket: "perceptible_app"
+dest_bucket: "foreground_app"
+count: 1
+}
+oom_adjuster_transition_counts_global {
+src_bucket: "perceptible_low_app"
+dest_bucket: "foreground_app"
+count: 2
+}
+oom_adjuster_transition_counts_global {
+src_bucket: "previous_app"
+dest_bucket: "foreground_app"
+count: 1
+}
+oom_adjuster_transition_counts_global {
+src_bucket: "visible_app"
+dest_bucket: "foreground_app"
+count: 4
+}
+oom_adjuster_transition_counts_global {
+dest_bucket: "logcat"
+count: 1
+}
+oom_adjuster_transition_counts_global {
+dest_bucket: "native"
+count: 121
+}
+oom_adjuster_transition_counts_global {
+src_bucket: "foreground_app"
+dest_bucket: "perceptible_app"
+count: 2
+}
+oom_adjuster_transition_counts_global {
+src_bucket: "foreground_app"
+dest_bucket: "perceptible_low_app"
+count: 3
+}
+oom_adjuster_transition_counts_global {
+dest_bucket: "persistent_proc"
+count: 17
+}
+oom_adjuster_transition_counts_global {
+dest_bucket: "persistent_service"
+count: 2
+}
+oom_adjuster_transition_counts_global {
+src_bucket: "foreground_app"
+dest_bucket: "persistent_service"
+count: 1
+}
+oom_adjuster_transition_counts_global {
+dest_bucket: "previous_app"
+count: 5
+}
+oom_adjuster_transition_counts_global {
+dest_bucket: "service"
+count: 1
+}
+oom_adjuster_transition_counts_global {
+dest_bucket: "system"
+count: 1
+}
+oom_adjuster_transition_counts_global {
+dest_bucket: "unknown_native"
+count: 1
+}
+oom_adjuster_transition_counts_global {
+dest_bucket: "visible_app"
+count: 6
+}
+oom_adjuster_transition_counts_global {
+src_bucket: "foreground_app"
+dest_bucket: "visible_app"
+count: 4
+}
+oom_adjuster_transition_counts_by_process {
+name: ".ShannonImsService"
+dest_bucket: "foreground_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/apex/com.android.adbd/bin/adbd"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/apex/com.android.hardware.cas/bin/hw/android.hardware.cas-service.example"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/apex/com.android.os.statsd/bin/statsd"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/apex/com.google.android.hardware.biometrics.face/bin/hw/android.hardware.biometrics.face-service.22.pixel"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/apex/com.google.android.widevine/bin/hw/android.hardware.drm-service.widevine"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/apex/com.google.pixel.camera.hal/bin/hw/android.hardware.camera.provider@2.7-service-google"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/apex/com.google.pixel.camera.hal/bin/rlsservice"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/apex/com.google.pixel.wifi.ext/bin/hw/vendor.google.wifi_ext-service-vendor"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/system/bin/apexd"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/system/bin/audioserver"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/system/bin/cameraserver"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/system/bin/credstore"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/system/bin/drmserver"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/system/bin/gatekeeperd"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/system/bin/gpuservice"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/system/bin/hw/android.system.suspend-service"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/system/bin/incidentd"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/system/bin/init"
+dest_bucket: "native"
+count: 2
+}
+oom_adjuster_transition_counts_by_process {
+name: "/system/bin/installd"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/system/bin/ip6tables-restore"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/system/bin/iptables-restore"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/system/bin/keystore2"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/system/bin/lmkd"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/system/bin/logd"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/system/bin/mediaserver"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/system/bin/netd"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/system/bin/prng_seeder"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/system/bin/servicemanager"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/system/bin/storaged"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/system/bin/surfaceflinger"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/system/bin/tombstoned"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/system/bin/traced"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/system/bin/traced_probes"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/system/bin/ueventd"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/system/bin/update_engine"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/system/bin/virtual_camera"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/system/bin/vold"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/system/bin/wificond"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/system/system_ext/bin/hw/android.hidl.allocator@1.0-service"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/system/system_ext/bin/hwservicemanager"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/system/vendor/bin/wfc-pkt-router"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/system_ext/bin/gs_watchdogd"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/system_ext/bin/hw/vendor.google.edgetpu_app_service@1.0-service"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/aocd"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/bipchmgr"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/cbd"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/chre"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/dmd"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/hw/android.hardware.audio.service"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/hw/android.hardware.authsecret-service.citadel"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/hw/android.hardware.biometrics.fingerprint-service.goodix"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/hw/android.hardware.bluetooth-service.bcmbtlinux"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/hw/android.hardware.boot-service.default-pixel"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/hw/android.hardware.cas@1.2-service"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/hw/android.hardware.composer.hwc3-service.pixel"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/hw/android.hardware.confirmationui-service.trusty.vendor"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/hw/android.hardware.contexthub-service.generic"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/hw/android.hardware.drm-service.clearkey"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/hw/android.hardware.dumpstate-service"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/hw/android.hardware.edgetpu.logging@service-edgetpu-logging"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/hw/android.hardware.gatekeeper-service.trusty"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/hw/android.hardware.gnss@2.1-service-brcm"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/hw/android.hardware.graphics.allocator-V2-service"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/hw/android.hardware.gxp.logging@service-gxp-logging"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/hw/android.hardware.health-service.gs201"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/hw/android.hardware.identity@1.0-service.citadel"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/hw/android.hardware.input.processor-service"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/hw/android.hardware.memtrack-service.pixel"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/hw/android.hardware.neuralnetworks@service-darwinn-aidl"
+dest_bucket: "native"
+count: 2
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/hw/android.hardware.nfc-service.st"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/hw/android.hardware.oemlock-service.citadel"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/hw/android.hardware.power-service.pixel-libperfmgr"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/hw/android.hardware.power.stats-service.pixel"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/hw/android.hardware.secure_element@1.2-service-gto"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/hw/android.hardware.secure_element@1.2-service-gto-ese2"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/hw/android.hardware.secure_element@1.2-uicc-service"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/hw/android.hardware.security.keymint-service.citadel"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/hw/android.hardware.security.keymint-service.trusty"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/hw/android.hardware.sensors-service.multihal"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/hw/android.hardware.thermal-service.pixel"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/hw/android.hardware.usb-service"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/hw/android.hardware.usb.gadget-service"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/hw/android.hardware.vibrator-service.cs40l26"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/hw/android.hardware.weaver-service.citadel"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/hw/battery_mitigation"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/hw/citadeld"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/hw/google.hardware.media.c2@1.0-service"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/hw/gpsd"
+dest_bucket: "unknown_native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/hw/lhd"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/hw/rild_exynos"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/hw/samsung.hardware.media.c2@1.0-service"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/hw/scd"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/hw/vendor.dolby.media.c2@1.0-service"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/hw/vendor.google.audiometricext@1.0-service-vendor"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/hw/vendor.google.google_battery-service"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/hw/vendor.google.radioext@1.0-service"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/hw/vendor.google.wireless_charger-default"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/hw/wpa_supplicant"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/modem_svc_sit"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/pixelstats-vendor"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/ramdump"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/rfsd"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/sced"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/securedpud.slider"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/sscoredump"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/storageproxyd"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/trusty_metricsd"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/twoshay"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/vcd"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "/vendor/bin/vndservicemanager"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "<pre-initialized>"
+dest_bucket: "native"
+count: 2
+}
+oom_adjuster_transition_counts_by_process {
+name: "android.process.acore"
+src_bucket: "previous_app"
+dest_bucket: "foreground_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "android.process.acore"
+dest_bucket: "previous_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.amazon.mp3"
+dest_bucket: "foreground_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.android.hbmsvmanager"
+dest_bucket: "persistent_proc"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.android.keychain"
+dest_bucket: "foreground_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.android.networkstack.process"
+dest_bucket: "persistent_proc"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.android.nfc"
+dest_bucket: "persistent_proc"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.android.phone"
+dest_bucket: "persistent_proc"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.android.pixellogger"
+dest_bucket: "persistent_proc"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.android.printspooler"
+dest_bucket: "foreground_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.android.providers.calendar"
+dest_bucket: "foreground_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.android.qns"
+dest_bucket: "visible_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.android.se"
+dest_bucket: "persistent_proc"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.android.settings"
+src_bucket: "foreground_app"
+dest_bucket: "cached_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.android.settings"
+dest_bucket: "foreground_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.android.systemui"
+dest_bucket: "persistent_proc"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.apple.android.music"
+dest_bucket: "foreground_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.blinkslabs.blinkist.android"
+dest_bucket: "foreground_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.SSRestartDetector"
+dest_bucket: "persistent_proc"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.apps.books"
+dest_bucket: "foreground_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.apps.cbrsnetworkmonitor"
+dest_bucket: "persistent_proc"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.apps.dreamliner"
+src_bucket: "foreground_app"
+dest_bucket: "cached_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.apps.dreamliner"
+dest_bucket: "foreground_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.apps.dreamliner"
+src_bucket: "perceptible_low_app"
+dest_bucket: "foreground_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.apps.dreamliner"
+src_bucket: "foreground_app"
+dest_bucket: "perceptible_low_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.apps.messaging"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.apps.messaging:rcs"
+dest_bucket: "foreground_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.apps.messaging:rcs"
+src_bucket: "perceptible_app"
+dest_bucket: "foreground_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.apps.messaging:rcs"
+src_bucket: "foreground_app"
+dest_bucket: "perceptible_app"
+count: 2
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.apps.nexuslauncher"
+src_bucket: "visible_app"
+dest_bucket: "foreground_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.apps.nexuslauncher"
+dest_bucket: "visible_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.apps.scone"
+dest_bucket: "service"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.apps.wallpaper"
+dest_bucket: "foreground_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.apps.wellbeing"
+dest_bucket: "cached_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.apps.wellbeing"
+src_bucket: "cached_app"
+dest_bucket: "cached_app"
+count: 8
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.apps.wellbeing"
+src_bucket: "cached_app"
+dest_bucket: "foreground_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.apps.work.clouddpc"
+dest_bucket: "cached_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.apps.work.clouddpc"
+src_bucket: "cached_app"
+dest_bucket: "cached_app"
+count: 2
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.apps.work.clouddpc"
+src_bucket: "cached_app_lmk_first"
+dest_bucket: "cached_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.apps.work.clouddpc"
+src_bucket: "cached_app"
+dest_bucket: "cached_app_lmk_first"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.apps.youtube.music"
+dest_bucket: "foreground_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.as"
+dest_bucket: "foreground_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.as.oss"
+src_bucket: "foreground_app"
+dest_bucket: "cached_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.as.oss"
+dest_bucket: "foreground_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.as.oss"
+src_bucket: "perceptible_low_app"
+dest_bucket: "foreground_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.as.oss"
+src_bucket: "foreground_app"
+dest_bucket: "perceptible_low_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.bluetooth"
+dest_bucket: "persistent_service"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.calendar"
+dest_bucket: "foreground_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.carrier"
+src_bucket: "previous_app"
+dest_bucket: "cached_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.carrier"
+dest_bucket: "previous_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.cellbroadcastreceiver"
+dest_bucket: "cached_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.cellbroadcastreceiver"
+src_bucket: "cached_app"
+dest_bucket: "cached_app"
+count: 3
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.connectivitythermalpowermanager"
+dest_bucket: "persistent_proc"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.deskclock"
+src_bucket: "cached_app_lmk_first"
+dest_bucket: "cached_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.deskclock"
+dest_bucket: "cached_app_lmk_first"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.deskclock"
+src_bucket: "cached_app_lmk_first"
+dest_bucket: "cached_app_lmk_first"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.deskclock"
+src_bucket: "cached_app"
+dest_bucket: "foreground_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.devicelockcontroller"
+dest_bucket: "cached_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.devicelockcontroller"
+src_bucket: "cached_app"
+dest_bucket: "cached_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.devicelockcontroller"
+src_bucket: "cached_app_lmk_first"
+dest_bucket: "cached_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.devicelockcontroller"
+src_bucket: "cached_app"
+dest_bucket: "cached_app_lmk_first"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.devicelockcontroller"
+src_bucket: "cached_app"
+dest_bucket: "foreground_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.devicelockcontroller"
+src_bucket: "visible_app"
+dest_bucket: "foreground_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.devicelockcontroller"
+src_bucket: "foreground_app"
+dest_bucket: "visible_app"
+count: 2
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.dialer"
+dest_bucket: "cached_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.dialer"
+src_bucket: "cached_app"
+dest_bucket: "cached_app"
+count: 7
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.euicc"
+dest_bucket: "visible_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.ext.services"
+dest_bucket: "visible_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.flipendo"
+src_bucket: "cached_app_lmk_first"
+dest_bucket: "cached_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.flipendo"
+src_bucket: "previous_app"
+dest_bucket: "cached_app_lmk_first"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.flipendo"
+dest_bucket: "previous_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.gms"
+dest_bucket: "cached_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.gms"
+src_bucket: "cached_app"
+dest_bucket: "cached_app"
+count: 5
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.gms"
+src_bucket: "cached_app"
+dest_bucket: "foreground_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.gms"
+src_bucket: "visible_app"
+dest_bucket: "foreground_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.gms"
+src_bucket: "foreground_app"
+dest_bucket: "visible_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.gms.persistent"
+src_bucket: "visible_app"
+dest_bucket: "foreground_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.gms.persistent"
+dest_bucket: "visible_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.googlequicksearchbox:interactor"
+dest_bucket: "foreground_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.googlequicksearchbox:interactor"
+src_bucket: "foreground_app"
+dest_bucket: "visible_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.googlequicksearchbox:search"
+dest_bucket: "foreground_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.grilservice"
+dest_bucket: "persistent_proc"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.ims"
+dest_bucket: "persistent_service"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.iwlan"
+dest_bucket: "visible_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.keep"
+dest_bucket: "foreground_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.permissioncontroller"
+dest_bucket: "cached_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.permissioncontroller"
+src_bucket: "cached_app"
+dest_bucket: "cached_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.pixelsystemservice"
+dest_bucket: "persistent_proc"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.providers.media.module"
+dest_bucket: "foreground_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.providers.media.module"
+src_bucket: "foreground_app"
+dest_bucket: "persistent_service"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.repairmode"
+src_bucket: "cached_app_lmk_first"
+dest_bucket: "cached_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.repairmode"
+dest_bucket: "cached_app_lmk_first"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.repairmode"
+src_bucket: "cached_app_lmk_first"
+dest_bucket: "cached_app_lmk_first"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.android.youtube"
+dest_bucket: "foreground_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.ar.core"
+dest_bucket: "foreground_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.ar.core"
+src_bucket: "foreground_app"
+dest_bucket: "perceptible_low_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.euiccpixel"
+src_bucket: "previous_app"
+dest_bucket: "cached_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.euiccpixel"
+dest_bucket: "previous_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.google.pixel.camera.services"
+dest_bucket: "persistent_proc"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.overdrive.mobile.android.libby"
+dest_bucket: "foreground_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.patreon.android"
+dest_bucket: "foreground_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.samsung.slsi.telephony.oem.oemrilhookservice"
+dest_bucket: "persistent_proc"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.samsung.slsi.telephony.oemril"
+dest_bucket: "persistent_proc"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.shannon.imsservice"
+dest_bucket: "persistent_proc"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.shannon.rcsservice"
+dest_bucket: "persistent_proc"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.shannon.rcsservice:shannonrcsservice"
+dest_bucket: "previous_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "com.spotify.music"
+dest_bucket: "foreground_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "logcat"
+dest_bucket: "logcat"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "logcat"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "media.extractor"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "media.metrics"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "media.swcodec"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "org.thoughtcrime.securesms"
+dest_bucket: "foreground_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "org.videolan.vlc"
+dest_bucket: "foreground_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "perfetto"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "system_server"
+dest_bucket: "system"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "webview_zygote"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_process {
+name: "zygote64"
+dest_bucket: "native"
+count: 1
+}
+oom_adjuster_transition_counts_by_oom_adj_reason {
+dest_bucket: "cached_app"
+count: 7
+}
+oom_adjuster_transition_counts_by_oom_adj_reason {
+name: "activityChange"
+src_bucket: "cached_app"
+dest_bucket: "cached_app"
+count: 8
+}
+oom_adjuster_transition_counts_by_oom_adj_reason {
+name: "bindService"
+src_bucket: "cached_app"
+dest_bucket: "cached_app"
+count: 8
+}
+oom_adjuster_transition_counts_by_oom_adj_reason {
+name: "getProvider"
+src_bucket: "cached_app"
+dest_bucket: "cached_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_oom_adj_reason {
+name: "startReceiver"
+src_bucket: "cached_app"
+dest_bucket: "cached_app"
+count: 7
+}
+oom_adjuster_transition_counts_by_oom_adj_reason {
+name: "uiVisibility"
+src_bucket: "cached_app"
+dest_bucket: "cached_app"
+count: 3
+}
+oom_adjuster_transition_counts_by_oom_adj_reason {
+name: "uiVisibility"
+src_bucket: "cached_app_lmk_first"
+dest_bucket: "cached_app"
+count: 5
+}
+oom_adjuster_transition_counts_by_oom_adj_reason {
+name: "activityChange"
+src_bucket: "foreground_app"
+dest_bucket: "cached_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_oom_adj_reason {
+name: "executingService"
+src_bucket: "foreground_app"
+dest_bucket: "cached_app"
+count: 2
+}
+oom_adjuster_transition_counts_by_oom_adj_reason {
+name: "activityChange"
+src_bucket: "previous_app"
+dest_bucket: "cached_app"
+count: 2
+}
+oom_adjuster_transition_counts_by_oom_adj_reason {
+dest_bucket: "cached_app_lmk_first"
+count: 2
+}
+oom_adjuster_transition_counts_by_oom_adj_reason {
+name: "activityChange"
+src_bucket: "cached_app"
+dest_bucket: "cached_app_lmk_first"
+count: 2
+}
+oom_adjuster_transition_counts_by_oom_adj_reason {
+name: "activityChange"
+src_bucket: "cached_app_lmk_first"
+dest_bucket: "cached_app_lmk_first"
+count: 2
+}
+oom_adjuster_transition_counts_by_oom_adj_reason {
+name: "activityChange"
+src_bucket: "previous_app"
+dest_bucket: "cached_app_lmk_first"
+count: 1
+}
+oom_adjuster_transition_counts_by_oom_adj_reason {
+dest_bucket: "foreground_app"
+count: 2
+}
+oom_adjuster_transition_counts_by_oom_adj_reason {
+name: "activityChange"
+dest_bucket: "foreground_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_oom_adj_reason {
+name: "processBegin"
+dest_bucket: "foreground_app"
+count: 24
+}
+oom_adjuster_transition_counts_by_oom_adj_reason {
+name: "getProvider"
+src_bucket: "cached_app"
+dest_bucket: "foreground_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_oom_adj_reason {
+name: "startReceiver"
+src_bucket: "cached_app"
+dest_bucket: "foreground_app"
+count: 2
+}
+oom_adjuster_transition_counts_by_oom_adj_reason {
+name: "startService"
+src_bucket: "cached_app"
+dest_bucket: "foreground_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_oom_adj_reason {
+name: "startService"
+src_bucket: "perceptible_app"
+dest_bucket: "foreground_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_oom_adj_reason {
+name: "unbindService"
+src_bucket: "perceptible_low_app"
+dest_bucket: "foreground_app"
+count: 2
+}
+oom_adjuster_transition_counts_by_oom_adj_reason {
+name: "getProvider"
+src_bucket: "previous_app"
+dest_bucket: "foreground_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_oom_adj_reason {
+name: "bindService"
+src_bucket: "visible_app"
+dest_bucket: "foreground_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_oom_adj_reason {
+name: "getProvider"
+src_bucket: "visible_app"
+dest_bucket: "foreground_app"
+count: 1
+}
+oom_adjuster_transition_counts_by_oom_adj_reason {
+name: "startService"
+src_bucket: "visible_app"
+dest_bucket: "foreground_app"
+count: 2
+}
+oom_adjuster_transition_counts_by_oom_adj_reason {
+dest_bucket: "logcat"
+count: 1
+}
+oom_adjuster_transition_counts_by_oom_adj_reason {
+dest_bucket: "native"
+count: 117
+}
+oom_adjuster_transition_counts_by_oom_adj_reason {
+name: "executingService"
+dest_bucket: "native"
+count: 4
+}
+oom_adjuster_transition_counts_by_oom_adj_reason {
+name: "executingService"
+src_bucket: "foreground_app"
+dest_bucket: "perceptible_app"
+count: 2
+}
+oom_adjuster_transition_counts_by_oom_adj_reason {
+name: "executingService"
+src_bucket: "foreground_app"
+dest_bucket: "perceptible_low_app"
+count: 3
+}
+oom_adjuster_transition_counts_by_oom_adj_reason {
+dest_bucket: "persistent_proc"
+count: 13
+}
+oom_adjuster_transition_counts_by_oom_adj_reason {
+name: "processBegin"
+dest_bucket: "persistent_proc"
+count: 4
+}
+oom_adjuster_transition_counts_by_oom_adj_reason {
+dest_bucket: "persistent_service"
+count: 2
+}
+oom_adjuster_transition_counts_by_oom_adj_reason {
+name: "startService"
+src_bucket: "foreground_app"
+dest_bucket: "persistent_service"
+count: 1
+}
+oom_adjuster_transition_counts_by_oom_adj_reason {
+dest_bucket: "previous_app"
+count: 5
+}
+oom_adjuster_transition_counts_by_oom_adj_reason {
+dest_bucket: "service"
+count: 1
+}
+oom_adjuster_transition_counts_by_oom_adj_reason {
+dest_bucket: "system"
+count: 1
+}
+oom_adjuster_transition_counts_by_oom_adj_reason {
+dest_bucket: "unknown_native"
+count: 1
+}
+oom_adjuster_transition_counts_by_oom_adj_reason {
+dest_bucket: "visible_app"
+count: 6
+}
+oom_adjuster_transition_counts_by_oom_adj_reason {
+name: "executingService"
+src_bucket: "foreground_app"
+dest_bucket: "visible_app"
+count: 3
+}
+oom_adjuster_transition_counts_by_oom_adj_reason {
+name: "finishReceiver"
+src_bucket: "foreground_app"
+dest_bucket: "visible_app"
+count: 1
+}
+oom_adj_bucket_duration_agg_global {
+bucket: "cached_app"
+total_dur: 20351484924
+}
+oom_adj_bucket_duration_agg_global {
+bucket: "cached_app_lmk_first"
+total_dur: 544761924
+}
+oom_adj_bucket_duration_agg_global {
+bucket: "foreground_app"
+total_dur: 33803455234
+}
+oom_adj_bucket_duration_agg_global {
+bucket: "logcat"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_global {
+bucket: "native"
+total_dur: 263503709570
+}
+oom_adj_bucket_duration_agg_global {
+bucket: "perceptible_app"
+total_dur: 940071238
+}
+oom_adj_bucket_duration_agg_global {
+bucket: "perceptible_low_app"
+total_dur: 199288116
+}
+oom_adj_bucket_duration_agg_global {
+bucket: "persistent_proc"
+total_dur: 36752485443
+}
+oom_adj_bucket_duration_agg_global {
+bucket: "persistent_service"
+total_dur: 6049062188
+}
+oom_adj_bucket_duration_agg_global {
+bucket: "previous_app"
+total_dur: 6572285720
+}
+oom_adj_bucket_duration_agg_global {
+bucket: "service"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_global {
+bucket: "system"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_global {
+bucket: "unknown_native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_global {
+bucket: "visible_app"
+total_dur: 12579222928
+}
+oom_adj_bucket_duration_agg_by_process {
+name: ".ShannonImsService"
+bucket: "foreground_app"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/apex/com.android.adbd/bin/adbd"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/apex/com.android.hardware.cas/bin/hw/android.hardware.cas-service.example"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/apex/com.android.os.statsd/bin/statsd"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/apex/com.google.android.hardware.biometrics.face/bin/hw/android.hardware.biometrics.face-service.22.pixel"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/apex/com.google.android.widevine/bin/hw/android.hardware.drm-service.widevine"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/apex/com.google.pixel.camera.hal/bin/hw/android.hardware.camera.provider@2.7-service-google"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/apex/com.google.pixel.camera.hal/bin/rlsservice"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/apex/com.google.pixel.wifi.ext/bin/hw/vendor.google.wifi_ext-service-vendor"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/system/bin/apexd"
+bucket: "native"
+total_dur: 25397654
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/system/bin/audioserver"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/system/bin/cameraserver"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/system/bin/credstore"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/system/bin/drmserver"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/system/bin/gatekeeperd"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/system/bin/gpuservice"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/system/bin/hw/android.system.suspend-service"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/system/bin/incidentd"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/system/bin/init"
+bucket: "native"
+total_dur: 4502600324
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/system/bin/installd"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/system/bin/ip6tables-restore"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/system/bin/iptables-restore"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/system/bin/keystore2"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/system/bin/lmkd"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/system/bin/logd"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/system/bin/mediaserver"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/system/bin/netd"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/system/bin/prng_seeder"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/system/bin/servicemanager"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/system/bin/storaged"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/system/bin/surfaceflinger"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/system/bin/tombstoned"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/system/bin/traced"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/system/bin/traced_probes"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/system/bin/ueventd"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/system/bin/update_engine"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/system/bin/virtual_camera"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/system/bin/vold"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/system/bin/wificond"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/system/system_ext/bin/hw/android.hidl.allocator@1.0-service"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/system/system_ext/bin/hwservicemanager"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/system/vendor/bin/wfc-pkt-router"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/system_ext/bin/gs_watchdogd"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/system_ext/bin/hw/vendor.google.edgetpu_app_service@1.0-service"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/aocd"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/bipchmgr"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/cbd"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/chre"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/dmd"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/hw/android.hardware.audio.service"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/hw/android.hardware.authsecret-service.citadel"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/hw/android.hardware.biometrics.fingerprint-service.goodix"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/hw/android.hardware.bluetooth-service.bcmbtlinux"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/hw/android.hardware.boot-service.default-pixel"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/hw/android.hardware.cas@1.2-service"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/hw/android.hardware.composer.hwc3-service.pixel"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/hw/android.hardware.confirmationui-service.trusty.vendor"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/hw/android.hardware.contexthub-service.generic"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/hw/android.hardware.drm-service.clearkey"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/hw/android.hardware.dumpstate-service"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/hw/android.hardware.edgetpu.logging@service-edgetpu-logging"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/hw/android.hardware.gatekeeper-service.trusty"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/hw/android.hardware.gnss@2.1-service-brcm"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/hw/android.hardware.graphics.allocator-V2-service"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/hw/android.hardware.gxp.logging@service-gxp-logging"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/hw/android.hardware.health-service.gs201"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/hw/android.hardware.identity@1.0-service.citadel"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/hw/android.hardware.input.processor-service"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/hw/android.hardware.memtrack-service.pixel"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/hw/android.hardware.neuralnetworks@service-darwinn-aidl"
+bucket: "native"
+total_dur: 4502600324
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/hw/android.hardware.nfc-service.st"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/hw/android.hardware.oemlock-service.citadel"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/hw/android.hardware.power-service.pixel-libperfmgr"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/hw/android.hardware.power.stats-service.pixel"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/hw/android.hardware.secure_element@1.2-service-gto"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/hw/android.hardware.secure_element@1.2-service-gto-ese2"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/hw/android.hardware.secure_element@1.2-uicc-service"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/hw/android.hardware.security.keymint-service.citadel"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/hw/android.hardware.security.keymint-service.trusty"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/hw/android.hardware.sensors-service.multihal"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/hw/android.hardware.thermal-service.pixel"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/hw/android.hardware.usb-service"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/hw/android.hardware.usb.gadget-service"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/hw/android.hardware.vibrator-service.cs40l26"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/hw/android.hardware.weaver-service.citadel"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/hw/battery_mitigation"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/hw/citadeld"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/hw/google.hardware.media.c2@1.0-service"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/hw/gpsd"
+bucket: "unknown_native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/hw/lhd"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/hw/rild_exynos"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/hw/samsung.hardware.media.c2@1.0-service"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/hw/scd"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/hw/vendor.dolby.media.c2@1.0-service"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/hw/vendor.google.audiometricext@1.0-service-vendor"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/hw/vendor.google.google_battery-service"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/hw/vendor.google.radioext@1.0-service"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/hw/vendor.google.wireless_charger-default"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/hw/wpa_supplicant"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/modem_svc_sit"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/pixelstats-vendor"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/ramdump"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/rfsd"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/sced"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/securedpud.slider"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/sscoredump"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/storageproxyd"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/trusty_metricsd"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/twoshay"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/vcd"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "/vendor/bin/vndservicemanager"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "<pre-initialized>"
+bucket: "native"
+total_dur: 50795308
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "android.process.acore"
+bucket: "foreground_app"
+total_dur: 823432282
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "android.process.acore"
+bucket: "previous_app"
+total_dur: 1427867880
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.amazon.mp3"
+bucket: "foreground_app"
+total_dur: 1322230378
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.android.hbmsvmanager"
+bucket: "persistent_proc"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.android.keychain"
+bucket: "foreground_app"
+total_dur: 196867340
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.android.networkstack.process"
+bucket: "persistent_proc"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.android.nfc"
+bucket: "persistent_proc"
+total_dur: 1883581900
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.android.phone"
+bucket: "persistent_proc"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.android.pixellogger"
+bucket: "persistent_proc"
+total_dur: 1852027416
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.android.printspooler"
+bucket: "foreground_app"
+total_dur: 343904124
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.android.providers.calendar"
+bucket: "foreground_app"
+total_dur: 281386139
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.android.qns"
+bucket: "visible_app"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.android.se"
+bucket: "persistent_proc"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.android.settings"
+bucket: "cached_app"
+total_dur: 967052521
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.android.settings"
+bucket: "foreground_app"
+total_dur: 1284247641
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.android.systemui"
+bucket: "persistent_proc"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.apple.android.music"
+bucket: "foreground_app"
+total_dur: 1268519522
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.blinkslabs.blinkist.android"
+bucket: "foreground_app"
+total_dur: 1250421540
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.SSRestartDetector"
+bucket: "persistent_proc"
+total_dur: 1885479849
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.apps.books"
+bucket: "foreground_app"
+total_dur: 1076963328
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.apps.cbrsnetworkmonitor"
+bucket: "persistent_proc"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.apps.dreamliner"
+bucket: "cached_app"
+total_dur: 1086183503
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.apps.dreamliner"
+bucket: "foreground_app"
+total_dur: 402609904
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.apps.dreamliner"
+bucket: "perceptible_low_app"
+total_dur: 7658935
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.apps.messaging"
+bucket: "native"
+total_dur: 25397654
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.apps.messaging:rcs"
+bucket: "foreground_app"
+total_dur: 991455486
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.apps.messaging:rcs"
+bucket: "perceptible_app"
+total_dur: 940071238
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.apps.nexuslauncher"
+bucket: "foreground_app"
+total_dur: 1661132477
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.apps.nexuslauncher"
+bucket: "visible_app"
+total_dur: 590167685
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.apps.scone"
+bucket: "service"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.apps.wallpaper"
+bucket: "foreground_app"
+total_dur: 177919993
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.apps.wellbeing"
+bucket: "cached_app"
+total_dur: 1366329916
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.apps.wellbeing"
+bucket: "foreground_app"
+total_dur: 884970246
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.apps.work.clouddpc"
+bucket: "cached_app"
+total_dur: 2194632641
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.apps.work.clouddpc"
+bucket: "cached_app_lmk_first"
+total_dur: 56667521
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.apps.youtube.music"
+bucket: "foreground_app"
+total_dur: 1558635529
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.as"
+bucket: "foreground_app"
+total_dur: 2003700552
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.as.oss"
+bucket: "cached_app"
+total_dur: 621111806
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.as.oss"
+bucket: "foreground_app"
+total_dur: 303909018
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.as.oss"
+bucket: "perceptible_low_app"
+total_dur: 9431030
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.bluetooth"
+bucket: "persistent_service"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.calendar"
+bucket: "foreground_app"
+total_dur: 1573637523
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.carrier"
+bucket: "cached_app"
+total_dur: 870239329
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.carrier"
+bucket: "previous_app"
+total_dur: 1381060833
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.cellbroadcastreceiver"
+bucket: "cached_app"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.connectivitythermalpowermanager"
+bucket: "persistent_proc"
+total_dur: 1864494172
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.deskclock"
+bucket: "cached_app"
+total_dur: 389354777
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.deskclock"
+bucket: "cached_app_lmk_first"
+total_dur: 187275391
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.deskclock"
+bucket: "foreground_app"
+total_dur: 1674669994
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.devicelockcontroller"
+bucket: "cached_app"
+total_dur: 510497641
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.devicelockcontroller"
+bucket: "cached_app_lmk_first"
+total_dur: 56693278
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.devicelockcontroller"
+bucket: "foreground_app"
+total_dur: 115664632
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.devicelockcontroller"
+bucket: "visible_app"
+total_dur: 1568444611
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.dialer"
+bucket: "cached_app"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.euicc"
+bucket: "visible_app"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.ext.services"
+bucket: "visible_app"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.flipendo"
+bucket: "cached_app"
+total_dur: 2063730581
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.flipendo"
+bucket: "cached_app_lmk_first"
+total_dur: 56672527
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.flipendo"
+bucket: "previous_app"
+total_dur: 130897054
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.gms"
+bucket: "cached_app"
+total_dur: 594464397
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.gms"
+bucket: "foreground_app"
+total_dur: 1259805736
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.gms"
+bucket: "visible_app"
+total_dur: 397030029
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.gms.persistent"
+bucket: "foreground_app"
+total_dur: 2037190298
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.gms.persistent"
+bucket: "visible_app"
+total_dur: 214109864
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.googlequicksearchbox:interactor"
+bucket: "foreground_app"
+total_dur: 1185413249
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.googlequicksearchbox:interactor"
+bucket: "visible_app"
+total_dur: 804270091
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.googlequicksearchbox:search"
+bucket: "foreground_app"
+total_dur: 1467442984
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.grilservice"
+bucket: "persistent_proc"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.ims"
+bucket: "persistent_service"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.iwlan"
+bucket: "visible_app"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.keep"
+bucket: "foreground_app"
+total_dur: 576153188
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.permissioncontroller"
+bucket: "cached_app"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.pixelsystemservice"
+bucket: "persistent_proc"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.providers.media.module"
+bucket: "foreground_app"
+total_dur: 518140177
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.providers.media.module"
+bucket: "persistent_service"
+total_dur: 1546461864
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.repairmode"
+bucket: "cached_app"
+total_dur: 2063846955
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.repairmode"
+bucket: "cached_app_lmk_first"
+total_dur: 187453207
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.android.youtube"
+bucket: "foreground_app"
+total_dur: 1450400991
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.ar.core"
+bucket: "foreground_app"
+total_dur: 733794515
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.ar.core"
+bucket: "perceptible_low_app"
+total_dur: 182198151
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.euiccpixel"
+bucket: "cached_app"
+total_dur: 870140371
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.euiccpixel"
+bucket: "previous_app"
+total_dur: 1381159791
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.google.pixel.camera.services"
+bucket: "persistent_proc"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.overdrive.mobile.android.libby"
+bucket: "foreground_app"
+total_dur: 889039012
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.patreon.android"
+bucket: "foreground_app"
+total_dur: 608530141
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.samsung.slsi.telephony.oem.oemrilhookservice"
+bucket: "persistent_proc"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.samsung.slsi.telephony.oemril"
+bucket: "persistent_proc"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.shannon.imsservice"
+bucket: "persistent_proc"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.shannon.rcsservice"
+bucket: "persistent_proc"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.shannon.rcsservice:shannonrcsservice"
+bucket: "previous_app"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "com.spotify.music"
+bucket: "foreground_app"
+total_dur: 566682160
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "logcat"
+bucket: "logcat"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "logcat"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "media.extractor"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "media.metrics"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "media.swcodec"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "org.thoughtcrime.securesms"
+bucket: "foreground_app"
+total_dur: 701758738
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "org.videolan.vlc"
+bucket: "foreground_app"
+total_dur: 361526235
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "perfetto"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "system_server"
+bucket: "system"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "webview_zygote"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_bucket_duration_agg_by_process {
+name: "zygote64"
+bucket: "native"
+total_dur: 2251300162
+}
+oom_adj_duration_agg {
+oom_adj_event_count: 0
+}
+oom_adj_duration_agg {
+min_oom_adj_dur: 263550
+max_oom_adj_dur: 7292196
+avg_oom_adj_dur: 3888985.6470588236
+oom_adj_event_count: 4
+oom_adj_reason: "activityChange"
+}
+oom_adj_duration_agg {
+min_oom_adj_dur: 67180
+max_oom_adj_dur: 225871
+avg_oom_adj_dur: 171359.88888888888
+oom_adj_event_count: 6
+oom_adj_reason: "bindService"
+}
+oom_adj_duration_agg {
+min_oom_adj_dur: 64657
+max_oom_adj_dur: 565674
+avg_oom_adj_dur: 232660.2142857143
+oom_adj_event_count: 11
+oom_adj_reason: "executingService"
+}
+oom_adj_duration_agg {
+min_oom_adj_dur: 198120
+max_oom_adj_dur: 198120
+avg_oom_adj_dur: 198120.0
+oom_adj_event_count: 1
+oom_adj_reason: "finishReceiver"
+}
+oom_adj_duration_agg {
+min_oom_adj_dur: 193766
+max_oom_adj_dur: 626302
+avg_oom_adj_dur: 383901.0
+oom_adj_event_count: 4
+oom_adj_reason: "getProvider"
+}
+oom_adj_duration_agg {
+min_oom_adj_dur: 92366
+max_oom_adj_dur: 1461914
+avg_oom_adj_dur: 330079.5714285714
+oom_adj_event_count: 28
+oom_adj_reason: "processBegin"
+}
+oom_adj_duration_agg {
+min_oom_adj_dur: 238525
+max_oom_adj_dur: 5160522
+avg_oom_adj_dur: 883870.1111111111
+oom_adj_event_count: 4
+oom_adj_reason: "startReceiver"
+}
+oom_adj_duration_agg {
+min_oom_adj_dur: 68645
+max_oom_adj_dur: 2294270
+avg_oom_adj_dur: 618880.2
+oom_adj_event_count: 5
+oom_adj_reason: "startService"
+}
+oom_adj_duration_agg {
+min_oom_adj_dur: 10092
+max_oom_adj_dur: 10092
+avg_oom_adj_dur: 10092.0
+oom_adj_event_count: 1
+oom_adj_reason: "uiVisibility"
+}
+oom_adj_duration_agg {
+min_oom_adj_dur: 165242
+max_oom_adj_dur: 380290
+avg_oom_adj_dur: 272766.0
+oom_adj_event_count: 2
+oom_adj_reason: "unbindService"
+}
+}
diff --git a/test/trace_processor/diff_tests/metrics/android/tests.py b/test/trace_processor/diff_tests/metrics/android/tests.py
index 06cbe59c9..da5559ea3 100644
--- a/test/trace_processor/diff_tests/metrics/android/tests.py
+++ b/test/trace_processor/diff_tests/metrics/android/tests.py
@@ -303,3 +303,9 @@ class AndroidMetrics(TestSuite):
}
}
"""))
+ def test_android_oom_adjuster(self):
+ return DiffTestBlueprint(
+ trace=DataPath('android_postboot_unlock.pftrace'),
+ query=Metric("android_oom_adjuster"),
+ out=Path('android_oom_adjuster.out')
+ )
diff --git a/test/trace_processor/diff_tests/metrics/chrome/tests_scroll_jank.py b/test/trace_processor/diff_tests/metrics/chrome/tests_scroll_jank.py
index 70dec5c7e..fa52426ec 100644
--- a/test/trace_processor/diff_tests/metrics/chrome/tests_scroll_jank.py
+++ b/test/trace_processor/diff_tests/metrics/chrome/tests_scroll_jank.py
@@ -429,32 +429,18 @@ class ChromeScrollJankMetrics(TestSuite):
query=Metric('chrome_scroll_jank_v3'),
out=TextProto(r"""
[perfetto.protos.chrome_scroll_jank_v3] {
- trace_num_frames: 291
- trace_num_janky_frames: 3
- trace_scroll_jank_percentage: 1.0309278350515463
- vsync_interval_ms: 16.368
+ trace_num_frames: 354
+ trace_num_janky_frames: 1
+ trace_scroll_jank_percentage: 0.2824858757062147
+ vsync_interval_ms: 10.483
scrolls {
- num_frames: 105
- num_janky_frames: 2
- scroll_jank_percentage: 1.9047619047619047
- max_delay_since_last_frame: 6.126221896383187
- scroll_jank_causes {
- cause: "RendererCompositorQueueingDelay"
- delay_since_last_frame: 2.044354838709678
- }
- scroll_jank_causes {
- cause: "RendererCompositorFinishedToBeginImplFrame"
- delay_since_last_frame: 6.126221896383187
- }
- }
- scrolls {
- num_frames: 84
+ num_frames: 122
num_janky_frames: 1
- scroll_jank_percentage: 1.1904761904761905
- max_delay_since_last_frame: 2.040811339198436
+ scroll_jank_percentage: 0.819672131147541
+ max_delay_since_last_frame: 2.13021081751407
scroll_jank_causes {
cause: "RendererCompositorQueueingDelay"
- delay_since_last_frame: 2.040811339198436
+ delay_since_last_frame: 2.13021081751407
}
}
}
@@ -469,8 +455,8 @@ class ChromeScrollJankMetrics(TestSuite):
INCLUDE PERFETTO MODULE chrome.scroll_jank.scroll_jank_v3;
SELECT
- _HAS_DESCENDANT_SLICE_WITH_NAME(
- (SELECT id from slice where dur = 46046000),
+ HAS_DESCENDANT_SLICE_WITH_NAME(
+ (SELECT id from slice where dur = 60156000),
'SwapEndToPresentationCompositorFrame') AS has_descendant;
""",
out=Csv("""
@@ -487,8 +473,8 @@ class ChromeScrollJankMetrics(TestSuite):
INCLUDE PERFETTO MODULE chrome.scroll_jank.scroll_jank_v3;
SELECT
- _HAS_DESCENDANT_SLICE_WITH_NAME(
- (SELECT id from slice where dur = 11666000),
+ HAS_DESCENDANT_SLICE_WITH_NAME(
+ (SELECT id from slice where dur = 77247000),
'SwapEndToPresentationCompositorFrame') AS has_descendant;
""",
out=Csv("""
@@ -506,7 +492,7 @@ class ChromeScrollJankMetrics(TestSuite):
SELECT
_DESCENDANT_SLICE_END(
- (SELECT id from slice where dur = 11666000),
+ (SELECT id from slice where dur = 77247000),
'SwapEndToPresentationCompositorFrame') AS end_ts;
""",
out=Csv("""
@@ -524,10 +510,10 @@ class ChromeScrollJankMetrics(TestSuite):
SELECT
_DESCENDANT_SLICE_END(
- (SELECT id from slice where dur = 46046000),
+ (SELECT id from slice where dur = 60156000),
'SwapEndToPresentationCompositorFrame') AS end_ts;
""",
out=Csv("""
"end_ts"
- 174797566610797
+ 1035869424631926
""")) \ No newline at end of file
diff --git a/test/trace_processor/diff_tests/parser/android/protolog_packet_loss.textproto b/test/trace_processor/diff_tests/parser/android/protolog_packet_loss.textproto
new file mode 100644
index 000000000..ab6fda5f6
--- /dev/null
+++ b/test/trace_processor/diff_tests/parser/android/protolog_packet_loss.textproto
@@ -0,0 +1,118 @@
+packet {
+ trusted_uid: 1000
+ trusted_packet_sequence_id: 3
+ sequence_flags: 1
+ previous_packet_dropped: true
+ trusted_pid: 1716
+ first_packet_on_sequence: true
+}
+packet {
+ trusted_uid: 1000
+ trusted_packet_sequence_id: 3
+ interned_data {
+ protolog_string_args {
+ iid: 1
+ str: "MyTestString"
+ }
+ }
+ trusted_pid: 1716
+}
+packet {
+ trusted_uid: 1000
+ trusted_packet_sequence_id: 3
+ interned_data {
+ protolog_string_args {
+ iid: 2
+ str: "MyOtherTestString"
+ }
+ }
+ trusted_pid: 1716
+}
+packet {
+ trusted_uid: 1000
+ trusted_packet_sequence_id: 3
+ sequence_flags: 2
+ trusted_pid: 1716
+ timestamp: 857384130
+ protolog_message {
+ message_id: 6924537961316301726
+ str_param_iids: 1
+ str_param_iids: 1
+ }
+}
+packet {
+ trusted_uid: 1000
+ trusted_packet_sequence_id: 3
+ sequence_flags: 2
+ trusted_pid: 1716
+ timestamp: 857384130
+ protolog_message {
+ message_id: 6924537961316301726
+ str_param_iids: 1
+ str_param_iids: 2
+ }
+ previous_packet_dropped: true
+}
+packet {
+ trusted_uid: 1000
+ trusted_packet_sequence_id: 3
+ sequence_flags: 2
+ trusted_pid: 1716
+ timestamp: 857384130
+ protolog_message {
+ message_id: 6924537961316301726
+ str_param_iids: 2
+ str_param_iids: 2
+ }
+}
+packet {
+ trusted_uid: 1000
+ trusted_packet_sequence_id: 4
+ sequence_flags: 1
+ previous_packet_dropped: true
+ trusted_pid: 1716
+ first_packet_on_sequence: true
+}
+packet {
+ trusted_uid: 1000
+ trusted_packet_sequence_id: 4
+ interned_data {
+ protolog_string_args {
+ iid: 1
+ str: "MyNextTestString"
+ }
+ }
+ trusted_pid: 1716
+}
+packet {
+ trusted_uid: 1000
+ trusted_packet_sequence_id: 4
+ sequence_flags: 2
+ trusted_pid: 1716
+ timestamp: 857384130
+ protolog_message {
+ message_id: 6924537961316301726
+ str_param_iids: 1
+ str_param_iids: 1
+ }
+}
+packet {
+ trusted_uid: 10224
+ trusted_packet_sequence_id: 10
+ previous_packet_dropped: true
+ trusted_pid: 2063
+ first_packet_on_sequence: true
+ protolog_viewer_config {
+ messages {
+ message_id: 6924537961316301726
+ message: "Test message with two strings: %s and %s"
+ level: PROTOLOG_LEVEL_DEBUG
+ group_id: 1
+ }
+ groups {
+ id: 1
+ name: "MY_FIRST_GROUP"
+ tag: "MyFirstGroup"
+ }
+ }
+} \ No newline at end of file
diff --git a/test/trace_processor/diff_tests/parser/android/tests_protolog.py b/test/trace_processor/diff_tests/parser/android/tests_protolog.py
index 404f34e1a..358fe5125 100644
--- a/test/trace_processor/diff_tests/parser/android/tests_protolog.py
+++ b/test/trace_processor/diff_tests/parser/android/tests_protolog.py
@@ -31,3 +31,13 @@ class ProtoLog(TestSuite):
1,857384110,"WARN","MySecondGroup","Test message with different int formats: 1776, 0o3360, 0x6f0, 888.000000, 8.880000e+02.","[NULL]"
2,857384130,"ERROR","MyThirdGroup","Message re-using interned string 'MyOtherTestString' == 'MyOtherTestString', but 'SomeOtherTestString' != 'MyOtherTestString'","[NULL]"
"""))
+
+ def test_handles_packet_loss(self):
+ return DiffTestBlueprint(
+ trace=Path('protolog_packet_loss.textproto'),
+ query="SELECT id, ts, level, tag, message, stacktrace FROM protolog;",
+ out=Csv("""
+ "id","ts","level","tag","message","stacktrace"
+ 0,857384130,"DEBUG","MyFirstGroup","Test message with two strings: MyTestString and MyTestString","[NULL]"
+ 1,857384130,"DEBUG","MyFirstGroup","Test message with two strings: MyNextTestString and MyNextTestString","[NULL]"
+ """))
diff --git a/test/trace_processor/diff_tests/parser/parsing/chrome_metadata.out b/test/trace_processor/diff_tests/parser/parsing/chrome_metadata.out
index 5635f53e9..7c32d9a38 100644
--- a/test/trace_processor/diff_tests/parser/parsing/chrome_metadata.out
+++ b/test/trace_processor/diff_tests/parser/parsing/chrome_metadata.out
@@ -1,5 +1,5 @@
"id","type","name","key_type","int_value","str_value"
-0,"metadata","trace_uuid","single","[NULL]","00000000-0000-0000-afe8-083bd6899d9d"
+0,"metadata","trace_uuid","single","[NULL]","00000000-0000-0000-7f42-b235fa358661"
1,"metadata","trace_time_clock_id","single",6,"[NULL]"
2,"metadata","cr-a-playstore_version_code","single",101,"[NULL]"
3,"metadata","cr-a-enabled_categories","single","[NULL]","cat1,cat2,cat3"
@@ -7,5 +7,5 @@
5,"metadata","cr-background_tracing_metadata","single","[NULL]","CgUlDsAbXx2RziSz"
6,"metadata","cr-scenario_name_hash","single",3005533841,"[NULL]"
7,"metadata","cr-triggered_rule_name_hash","single",1595654158,"[NULL]"
-8,"metadata","trace_size_bytes","single",78,"[NULL]"
+8,"metadata","trace_size_bytes","single",95,"[NULL]"
9,"metadata","trace_type","single","[NULL]","proto"
diff --git a/test/trace_processor/diff_tests/parser/parsing/tests.py b/test/trace_processor/diff_tests/parser/parsing/tests.py
index 505bdf9fa..91d2f6e46 100644
--- a/test/trace_processor/diff_tests/parser/parsing/tests.py
+++ b/test/trace_processor/diff_tests/parser/parsing/tests.py
@@ -632,6 +632,13 @@ class Parsing(TestSuite):
timestamp: 101000002
}
packet {
+ chrome_trigger {
+ trigger_name_hash: 1595654158
+ }
+ trusted_packet_sequence_id: 1
+ timestamp: 101000002
+ }
+ packet {
trusted_packet_sequence_id: 1
timestamp: 101000002
chrome_metadata {
diff --git a/test/trace_processor/diff_tests/stdlib/OWNERS b/test/trace_processor/diff_tests/stdlib/OWNERS
index 0479e4ba2..21e80a40c 100644
--- a/test/trace_processor/diff_tests/stdlib/OWNERS
+++ b/test/trace_processor/diff_tests/stdlib/OWNERS
@@ -1,3 +1,3 @@
-# These can't be added inside chrome/ bacause the whole directory is
+# These can't be added inside chrome/ because the whole directory is
# blown away on every import.
per-file chrome/* = file://protos/third_party/CHROMIUM_OWNERS
diff --git a/test/trace_processor/diff_tests/stdlib/android/frames_tests.py b/test/trace_processor/diff_tests/stdlib/android/frames_tests.py
index eb6e6d015..83297fd22 100644
--- a/test/trace_processor/diff_tests/stdlib/android/frames_tests.py
+++ b/test/trace_processor/diff_tests/stdlib/android/frames_tests.py
@@ -30,22 +30,22 @@ class Frames(TestSuite):
SELECT * FROM android_frames_choreographer_do_frame;
""",
out=Csv("""
- "id","frame_id","ui_thread_utid"
- 2,10,2
- 15,20,2
- 22,30,2
- 35,40,2
- 46,60,2
- 55,90,2
- 63,100,2
- 73,110,2
- 79,120,2
- 87,130,2
- 93,140,2
- 99,145,2
- 102,150,2
- 108,160,2
- 140,1000,2
+ "id","frame_id","ui_thread_utid","upid"
+ 2,10,2,2
+ 15,20,2,2
+ 22,30,2,2
+ 35,40,2,2
+ 46,60,2,2
+ 55,90,2,2
+ 63,100,2,2
+ 73,110,2,2
+ 79,120,2,2
+ 87,130,2,2
+ 93,140,2,2
+ 99,145,2,2
+ 102,150,2,2
+ 108,160,2,2
+ 140,1000,2,2
"""))
def test_android_frames_draw_frame(self):
@@ -57,24 +57,24 @@ class Frames(TestSuite):
SELECT * FROM android_frames_draw_frame;
""",
out=Csv("""
- "id","frame_id","render_thread_utid"
- 8,10,4
- 16,20,4
- 23,30,4
- 41,40,4
- 50,60,4
- 57,90,4
- 60,90,4
- 66,100,4
- 69,100,4
- 74,110,4
- 80,120,4
- 89,130,4
- 95,140,4
- 100,145,4
- 105,150,4
- 109,160,4
- 146,1000,4
+ "id","frame_id","render_thread_utid","upid"
+ 8,10,4,2
+ 16,20,4,2
+ 23,30,4,2
+ 41,40,4,2
+ 50,60,4,2
+ 57,90,4,2
+ 60,90,4,2
+ 66,100,4,2
+ 69,100,4,2
+ 74,110,4,2
+ 80,120,4,2
+ 89,130,4,2
+ 95,140,4,2
+ 100,145,4,2
+ 105,150,4,2
+ 109,160,4,2
+ 146,1000,4,2
"""))
def test_android_frames(self):
diff --git a/test/trace_processor/diff_tests/stdlib/android/startups_tests.py b/test/trace_processor/diff_tests/stdlib/android/startups_tests.py
index 14d002ad7..197ac2a58 100644
--- a/test/trace_processor/diff_tests/stdlib/android/startups_tests.py
+++ b/test/trace_processor/diff_tests/stdlib/android/startups_tests.py
@@ -35,26 +35,26 @@ class Startups(TestSuite):
def test_warm_startups(self):
return DiffTestBlueprint(
- trace=DataPath('api32_startup_warm.perfetto-trace'),
+ trace=DataPath('api31_startup_warm.perfetto-trace'),
query="""
INCLUDE PERFETTO MODULE android.startup.startups;
SELECT * FROM android_startups;
""",
out=Csv("""
"startup_id","ts","ts_end","dur","package","startup_type"
- 28,157479786566030,157479943081777,156515747,"androidx.benchmark.integration.macrobenchmark.target","[NULL]"
+ 1,186982050780778,186982115528805,64748027,"androidx.benchmark.integration.macrobenchmark.target","[NULL]"
"""))
def test_cold_startups(self):
return DiffTestBlueprint(
- trace=DataPath('api34_startup_cold.perfetto-trace'),
+ trace=DataPath('api31_startup_cold.perfetto-trace'),
query="""
INCLUDE PERFETTO MODULE android.startup.startups;
SELECT * FROM android_startups;
""",
out=Csv("""
"startup_id","ts","ts_end","dur","package","startup_type"
- 61,17806781251694,17806891032171,109780477,"com.android.systemui.people","warm"
+ 1,186974938196632,186975083989042,145792410,"androidx.benchmark.integration.macrobenchmark.target","[NULL]"
"""))
def test_hot_startups_maxsdk28(self):
@@ -66,8 +66,8 @@ class Startups(TestSuite):
""",
out=Csv("""
"startup_id","ts","ts_end","dur","package","startup_type"
- 1,779860286416,779893485322,33198906,"com.google.android.googlequicksearchbox","[NULL]"
- 2,780778904571,780813944498,35039927,"androidx.benchmark.integration.macrobenchmark.target","[NULL]"
+ 1,779860286416,779893485322,33198906,"com.google.android.googlequicksearchbox","hot"
+ 2,780778904571,780813944498,35039927,"androidx.benchmark.integration.macrobenchmark.target","hot"
"""))
def test_warm_startups_maxsdk28(self):
@@ -79,7 +79,7 @@ class Startups(TestSuite):
""",
out=Csv("""
"startup_id","ts","ts_end","dur","package","startup_type"
- 1,799979565075,800014194731,34629656,"com.google.android.googlequicksearchbox","[NULL]"
+ 1,799979565075,800014194731,34629656,"com.google.android.googlequicksearchbox","hot"
2,800868511677,800981929562,113417885,"androidx.benchmark.integration.macrobenchmark.target","[NULL]"
"""))
@@ -94,3 +94,77 @@ class Startups(TestSuite):
"startup_id","ts","ts_end","dur","package","startup_type"
1,791231114368,791501060868,269946500,"androidx.benchmark.integration.macrobenchmark.target","[NULL]"
"""))
+
+ def test_android_startup_time_to_display_hot_maxsdk28(self):
+ return DiffTestBlueprint(
+ trace=DataPath('api24_startup_hot.perfetto-trace'),
+ query="""
+ INCLUDE PERFETTO MODULE android.startup.time_to_display;
+ SELECT * FROM android_startup_time_to_display;
+ """,
+ out=Csv("""
+ "startup_id","time_to_inital_display","time_to_full_display","ttid_frame_id","ttfd_frame_id","upid"
+ 1,33198906,"[NULL]",1,"[NULL]",355
+ 2,35039927,537343160,4,5,383
+ """))
+
+ def test_android_startup_time_to_display_warm_maxsdk28(self):
+ return DiffTestBlueprint(
+ trace=DataPath('api24_startup_warm.perfetto-trace'),
+ query="""
+ INCLUDE PERFETTO MODULE android.startup.time_to_display;
+ SELECT * FROM android_startup_time_to_display;
+ """,
+ out=Csv("""
+ "startup_id","time_to_inital_display","time_to_full_display","ttid_frame_id","ttfd_frame_id","upid"
+ 1,34629656,"[NULL]",1,"[NULL]",355
+ 2,108563770,581026583,4,5,388
+ """))
+
+ def test_android_startup_time_to_display_cold_maxsdk28(self):
+ return DiffTestBlueprint(
+ trace=DataPath('api24_startup_cold.perfetto-trace'),
+ query="""
+ INCLUDE PERFETTO MODULE android.startup.time_to_display;
+ SELECT * FROM android_startup_time_to_display;
+ """,
+ out=Csv("""
+ "startup_id","time_to_inital_display","time_to_full_display","ttid_frame_id","ttfd_frame_id","upid"
+ 1,264869885,715406822,65,66,396
+ """))
+
+ def test_android_startup_time_to_display_hot(self):
+ return DiffTestBlueprint(
+ trace=DataPath('api31_startup_hot.perfetto-trace'),
+ query="""
+ INCLUDE PERFETTO MODULE android.startup.time_to_display;
+ SELECT * FROM android_startup_time_to_display;
+ """,
+ out=Csv("""
+ "startup_id","time_to_inital_display","time_to_full_display","ttid_frame_id","ttfd_frame_id","upid"
+ 1,40534066,542222554,5872867,5872953,184
+ """))
+
+ def test_android_startup_time_to_display_warm(self):
+ return DiffTestBlueprint(
+ trace=DataPath('api31_startup_warm.perfetto-trace'),
+ query="""
+ INCLUDE PERFETTO MODULE android.startup.time_to_display;
+ SELECT * FROM android_startup_time_to_display;
+ """,
+ out=Csv("""
+ "startup_id","time_to_inital_display","time_to_full_display","ttid_frame_id","ttfd_frame_id","upid"
+ 1,62373965,555968701,5873800,5873889,185
+ """))
+
+ def test_android_startup_time_to_display_cold(self):
+ return DiffTestBlueprint(
+ trace=DataPath('api31_startup_cold.perfetto-trace'),
+ query="""
+ INCLUDE PERFETTO MODULE android.startup.time_to_display;
+ SELECT * FROM android_startup_time_to_display;
+ """,
+ out=Csv("""
+ "startup_id","time_to_inital_display","time_to_full_display","ttid_frame_id","ttfd_frame_id","upid"
+ 1,143980066,620815843,5873276,5873353,229
+ """))
diff --git a/test/trace_processor/diff_tests/stdlib/chrome/tests_scroll_jank.py b/test/trace_processor/diff_tests/stdlib/chrome/tests_scroll_jank.py
index ae641d440..bdde2422f 100755
--- a/test/trace_processor/diff_tests/stdlib/chrome/tests_scroll_jank.py
+++ b/test/trace_processor/diff_tests/stdlib/chrome/tests_scroll_jank.py
@@ -40,7 +40,7 @@ class ChromeScrollJankStdlib(TestSuite):
def test_chrome_scrolls(self):
return DiffTestBlueprint(
- trace=Path('chrome_scroll_check.py'),
+ trace=DataPath('chrome_input_with_frame_view.pftrace'),
query="""
INCLUDE PERFETTO MODULE chrome.chrome_scrolls;
@@ -55,15 +55,15 @@ class ChromeScrollJankStdlib(TestSuite):
""",
out=Csv("""
"id","ts","dur","gesture_scroll_begin_ts","gesture_scroll_end_ts"
- 5678,0,55000000,0,45000000
- 5679,60000000,40000000,60000000,90000000
- 5680,80000000,30000000,80000000,100000000
- 5681,120000000,70000000,120000000,"[NULL]"
+ 4328,1035865535981926,1255745000,1035865535981926,1035866753550926
+ 4471,1035866799527926,1358505000,1035866799527926,1035868108723926
+ 4620,1035868146266926,111786000,1035868146266926,1035868230937926
+ 4652,1035868607429926,1517121000,1035868607429926,1035870086449926
"""))
def test_chrome_scroll_intervals(self):
return DiffTestBlueprint(
- trace=Path('chrome_scroll_check.py'),
+ trace=DataPath('chrome_input_with_frame_view.pftrace'),
query="""
INCLUDE PERFETTO MODULE chrome.chrome_scrolls;
@@ -76,14 +76,14 @@ class ChromeScrollJankStdlib(TestSuite):
""",
out=Csv("""
"id","ts","dur"
- 1,0,55000000
- 2,60000000,50000000
- 3,120000000,70000000
+ 1,1035865535981926,1255745000
+ 2,1035866799527926,1458525000
+ 3,1035868607429926,1517121000
"""))
def test_chrome_scroll_input_offsets(self):
return DiffTestBlueprint(
- trace=DataPath('scroll_offsets.pftrace'),
+ trace=DataPath('scroll_offsets_trace_2.pftrace'),
query="""
INCLUDE PERFETTO MODULE chrome.scroll_jank.scroll_offsets;
@@ -91,18 +91,19 @@ class ChromeScrollJankStdlib(TestSuite):
scroll_update_id,
ts,
delta_y,
- offset_y
+ relative_offset_y
FROM chrome_scroll_input_offsets
+ WHERE scroll_update_id IS NOT NULL
ORDER by ts
LIMIT 5;
""",
out=Csv("""
- "scroll_update_id","ts","delta_y","offset_y"
- 1983,4687296612739,-36.999939,-36.999939
- 1983,4687307175845,-39.000092,-76.000031
- 1987,4687313206739,-35.999969,-112.000000
- 1987,4687323152462,-35.000000,-147.000000
- 1991,4687329240739,-28.999969,-175.999969
+ "scroll_update_id","ts","delta_y","relative_offset_y"
+ 130,1349914859791,-6.932281,-308.342704
+ 132,1349923327791,-32.999954,-341.342659
+ 134,1349931893791,-39.999954,-381.342613
+ 140,1349956886791,-51.000046,-432.342659
+ 147,1349982489791,-89.808540,-522.151199
"""))
def test_chrome_janky_event_latencies_v3(self):
@@ -167,10 +168,10 @@ class ChromeScrollJankStdlib(TestSuite):
""",
out=Csv("""
"scroll_id","missed_vsyncs","frame_count","presented_frame_count","janky_frame_count","janky_frame_percent"
- 4328,"[NULL]",109,110,0,0.000000
- 4471,"[NULL]",117,118,0,0.000000
- 4620,"[NULL]",5,4,0,0.000000
- 4652,1,122,122,1,0.820000
+ 4328,"[NULL]",110,110,0,0.000000
+ 4471,"[NULL]",118,118,0,0.000000
+ 4620,"[NULL]",6,4,0,0.000000
+ 4652,1,123,122,1,0.820000
"""))
def test_chrome_scroll_jank_intervals_v3(self):
@@ -192,7 +193,7 @@ class ChromeScrollJankStdlib(TestSuite):
"""))
def test_chrome_presented_scroll_offsets(self):
return DiffTestBlueprint(
- trace=DataPath('scroll_offsets.pftrace'),
+ trace=DataPath('scroll_offsets_trace_2.pftrace'),
query="""
INCLUDE PERFETTO MODULE chrome.scroll_jank.scroll_offsets;
@@ -200,18 +201,19 @@ class ChromeScrollJankStdlib(TestSuite):
scroll_update_id,
ts,
delta_y,
- offset_y
+ relative_offset_y
FROM chrome_presented_scroll_offsets
+ WHERE scroll_update_id IS NOT NULL
ORDER by ts
LIMIT 5;
""",
out=Csv("""
- "scroll_update_id","ts","delta_y","offset_y"
- 1983,4687341817739,"[NULL]",0
- 1987,4687352950739,-50,-50
- 1991,4687364083739,-50,-100
- 1993,4687375224739,-81,-181
- 1996,4687386343739,-66,-247
+ "scroll_update_id","ts","delta_y","relative_offset_y"
+ 130,1349963342791,-6.932281,-6.932281
+ 132,1349985554791,-16.573090,-23.505371
+ 134,1349996680791,-107.517273,-131.022644
+ 140,1350007850791,-158.728424,-289.751068
+ 147,1350018935791,-89.808540,-379.559608
"""))
def test_scroll_jank_cause_map(self):
diff --git a/test/trace_processor/diff_tests/stdlib/graphs/partition_tests.py b/test/trace_processor/diff_tests/stdlib/graphs/partition_tests.py
index 5dab71800..0ad780e0c 100644
--- a/test/trace_processor/diff_tests/stdlib/graphs/partition_tests.py
+++ b/test/trace_processor/diff_tests/stdlib/graphs/partition_tests.py
@@ -38,7 +38,8 @@ class GraphPartitionTests(TestSuite):
(4, 2, 2),
(5, 4, 1),
(6, 4, 3),
- (7, 4, 2)
+ (7, 4, 2),
+ (8, 4, 1)
)
SELECT * FROM data;
@@ -55,4 +56,5 @@ class GraphPartitionTests(TestSuite):
5,2,1
6,"[NULL]",3
7,4,2
+ 8,2,1
"""))
diff --git a/test/trace_processor/diff_tests/stdlib/memory/heap_graph_dominator_tree_tests.py b/test/trace_processor/diff_tests/stdlib/memory/heap_graph_dominator_tree_tests.py
index 52bb465e1..0aa0f6489 100644
--- a/test/trace_processor/diff_tests/stdlib/memory/heap_graph_dominator_tree_tests.py
+++ b/test/trace_processor/diff_tests/stdlib/memory/heap_graph_dominator_tree_tests.py
@@ -65,7 +65,7 @@ class HeapGraphDominatorTree(TestSuite):
19,14,1,128,4,"U"
20,14,1,256,4,"V"
21,14,1,512,4,"W"
- 23,25,1,1024,1,"java.lang.ref.FinalizerReference"
+ 23,25,1,1024,1,"sun.misc.Cleaner"
"""))
def test_heap_graph_super_root_fn(self):
diff --git a/test/trace_processor/diff_tests/stdlib/memory/heap_graph_for_dominator_tree.textproto b/test/trace_processor/diff_tests/stdlib/memory/heap_graph_for_dominator_tree.textproto
index b0de0723a..940b38fde 100644
--- a/test/trace_processor/diff_tests/stdlib/memory/heap_graph_for_dominator_tree.textproto
+++ b/test/trace_processor/diff_tests/stdlib/memory/heap_graph_for_dominator_tree.textproto
@@ -26,7 +26,7 @@ packet {
# 2. a synthetic tree whose dominator tree is itself. It's drawn below with
# each object represented by it's class name. Number in the bracket is the
# size of each node in bytes.
- # S[1] java.lang.ref.FinalizerReference[1024]
+ # S[1] sun.misc.Cleaner[1024]
# / \ /
# M[2] N[4]
# / \ / \
@@ -191,7 +191,7 @@ packet {
}
objects {
id: 0x18
- type_id: 24 # "java.lang.ref.FinalizerReference"
+ type_id: 24 # "sun.misc.Cleaner"
self_size: 1024
reference_object_id: 0x0e
}
@@ -335,9 +335,15 @@ packet {
}
types {
id: 24
- class_name: "java.lang.ref.FinalizerReference"
+ class_name: "sun.misc.Cleaner"
+ kind: KIND_PHANTOM_REFERENCE
+ reference_field_id: 123
location_id: 1
}
+ field_names {
+ iid: 123
+ str: "java.lang.ref.Reference.referent"
+ }
continued: false
index: 1
}
diff --git a/test/trace_processor/diff_tests/stdlib/slices/tests.py b/test/trace_processor/diff_tests/stdlib/slices/tests.py
index 053dabf30..747ffb2e3 100644
--- a/test/trace_processor/diff_tests/stdlib/slices/tests.py
+++ b/test/trace_processor/diff_tests/stdlib/slices/tests.py
@@ -76,21 +76,23 @@ class Slices(TestSuite):
SELECT e.name, e.ts, e.dur, e.depth
FROM _slice_flattened e
- JOIN thread_track ON e.track_id = thread_track.id
- JOIN thread USING(utid)
- WHERE thread.tid = 30944;
+ JOIN thread_track ON e.track_id = thread_track.id
+ JOIN thread USING(utid)
+ WHERE thread.tid = 30196
+ LIMIT 10;
""",
out=Csv("""
"name","ts","dur","depth"
- "ThreadControllerImpl::RunTask",174793737042797,3937000,0
- "ThreadControllerImpl::RunTask",174793741016797,5930000,0
- "ThreadControllerImpl::RunTask",174793747000797,47000,0
- "Receive mojo message",174793747047797,136000,1
- "ThreadControllerImpl::RunTask",174793747183797,17000,0
- "Looper.dispatch: android.os.Handler(Kx3@57873a8)",174793747546797,119000,0
- "ThreadControllerImpl::RunTask",174796099970797,186000,0
- "Looper.dispatch: jy3(null)",174800056530797,1368000,0
- "ThreadControllerImpl::RunTask",174800107962797,132000,0
+ "EventForwarder::OnTouchEvent",1035865509936036,211000,0
+ "EventForwarder::OnTouchEvent",1035865510234036,48000,0
+ "EventForwarder::OnTouchEvent",1035865510673036,10000,0
+ "GestureProvider::OnTouchEvent",1035865510147036,87000,1
+ "RenderWidgetHostImpl::ForwardTouchEvent",1035865510282036,41000,1
+ "RenderWidgetHostImpl::ForwardTouchEvent",1035865510331036,16000,1
+ "RenderWidgetHostImpl::ForwardTouchEvent",1035865510670036,3000,1
+ "LatencyInfo.Flow",1035865510323036,8000,2
+ "PassthroughTouchEventQueue::QueueEvent",1035865510347036,30000,2
+ "PassthroughTouchEventQueue::QueueEvent",1035865510666036,4000,2
"""))
def test_thread_slice_cpu_time(self):
diff --git a/test/vts/OWNERS b/test/vts/OWNERS
index 10fd9fe67..0a7edbb54 100644
--- a/test/vts/OWNERS
+++ b/test/vts/OWNERS
@@ -1,6 +1,5 @@
# Bug component: 323270
ddiproietto@google.com
-hjd@google.com
lalitm@google.com
primiano@google.com
rsavitski@google.com
diff --git a/tools/cpu_profile b/tools/cpu_profile
index 17f0996bb..ece509564 100755
--- a/tools/cpu_profile
+++ b/tools/cpu_profile
@@ -219,6 +219,9 @@ import os
import platform
import subprocess
import sys
+import threading
+
+DOWNLOAD_LOCK = threading.Lock()
def download_or_get_cached(file_name, url, sha256):
@@ -235,28 +238,36 @@ def download_or_get_cached(file_name, url, sha256):
sha256_path = os.path.join(dir, file_name + '.sha256')
needs_download = True
- # Avoid recomputing the SHA-256 on each invocation. The SHA-256 of the last
- # download is cached into file_name.sha256, just check if that matches.
- if os.path.exists(bin_path) and os.path.exists(sha256_path):
- with open(sha256_path, 'rb') as f:
- digest = f.read().decode()
- if digest == sha256:
- needs_download = False
-
- if needs_download:
- # Either the filed doesn't exist or the SHA256 doesn't match.
- tmp_path = bin_path + '.tmp'
- print('Downloading ' + url)
- subprocess.check_call(['curl', '-f', '-L', '-#', '-o', tmp_path, url])
- with open(tmp_path, 'rb') as fd:
- actual_sha256 = hashlib.sha256(fd.read()).hexdigest()
- if actual_sha256 != sha256:
- raise Exception('Checksum mismatch for %s (actual: %s, expected: %s)' %
- (url, actual_sha256, sha256))
- os.chmod(tmp_path, 0o755)
- os.replace(tmp_path, bin_path)
- with open(sha256_path, 'w') as f:
- f.write(sha256)
+ try:
+ # In BatchTraceProcessor, many threads can be trying to execute the below
+ # code in parallel. For this reason, protect the whole operation with a
+ # lock.
+ DOWNLOAD_LOCK.acquire()
+
+ # Avoid recomputing the SHA-256 on each invocation. The SHA-256 of the last
+ # download is cached into file_name.sha256, just check if that matches.
+ if os.path.exists(bin_path) and os.path.exists(sha256_path):
+ with open(sha256_path, 'rb') as f:
+ digest = f.read().decode()
+ if digest == sha256:
+ needs_download = False
+
+ if needs_download:
+ # Either the filed doesn't exist or the SHA256 doesn't match.
+ tmp_path = bin_path + '.tmp'
+ print('Downloading ' + url)
+ subprocess.check_call(['curl', '-f', '-L', '-#', '-o', tmp_path, url])
+ with open(tmp_path, 'rb') as fd:
+ actual_sha256 = hashlib.sha256(fd.read()).hexdigest()
+ if actual_sha256 != sha256:
+ raise Exception('Checksum mismatch for %s (actual: %s, expected: %s)' %
+ (url, actual_sha256, sha256))
+ os.chmod(tmp_path, 0o755)
+ os.replace(tmp_path, bin_path)
+ with open(sha256_path, 'w') as f:
+ f.write(sha256)
+ finally:
+ DOWNLOAD_LOCK.release()
return bin_path
diff --git a/tools/gen_android_bp b/tools/gen_android_bp
index c8db4ec00..c8a2b1a3f 100755
--- a/tools/gen_android_bp
+++ b/tools/gen_android_bp
@@ -121,13 +121,19 @@ target_product_available = [
# Proto target groups which will be made public.
proto_groups = {
- 'trace': [
- '//protos/perfetto/trace:non_minimal_source_set',
- '//protos/perfetto/trace:minimal_source_set'
- ],
- 'config': [
- '//protos/perfetto/config:source_set',
- ],
+ 'trace': {
+ 'types': ['filegroup', 'lite'],
+ 'targets': [
+ '//protos/perfetto/trace:non_minimal_source_set',
+ '//protos/perfetto/trace:minimal_source_set',
+ ]
+ },
+ 'config': {
+ 'types': ['lite'],
+ 'targets': [
+ '//protos/perfetto/config:source_set',
+ ]
+ },
}
needs_libfts = [
@@ -762,7 +768,6 @@ def create_proto_modules(blueprint: Blueprint, gn: GnParser,
# The .proto filegroup will be added to `tool_files` of rdeps so that the
# genrules can be sandboxed.
- tool_files = set()
for proto_dep in target.proto_deps().union(target.transitive_proto_deps()):
tool_files.add(":" + label_to_module_name(proto_dep.name))
@@ -956,23 +961,34 @@ def create_gen_version_module(blueprint: Blueprint, target: GnParser.Target,
def create_proto_group_modules(blueprint, gn: GnParser, module_name: str,
- target_names):
- # TODO(lalitm): today, we're only adding a Java lite module because that's
- # the only one used in practice. In the future, if we need other target types
- # (e.g. C++, Java full etc.) add them here.
- bp_module_name = label_to_module_name(module_name) + '_java_protos'
- module = Module('java_library', bp_module_name, bp_module_name)
- module.comment = f'''GN: [{', '.join(target_names)}]'''
- module.proto = {'type': 'lite', 'canonical_path_from_root': False}
+ group):
+ target_names = group['targets']
+ module_types = group['types']
+ module_sources = set()
for name in target_names:
target = gn.get_target(name)
- module.srcs.update(gn_utils.label_to_path(src) for src in target.sources)
+ module_sources.update(gn_utils.label_to_path(src) for src in target.sources)
for dep_label in target.transitive_proto_deps():
dep = gn.get_target(dep_label.name)
- module.srcs.update(gn_utils.label_to_path(src) for src in dep.sources)
-
- blueprint.add_module(module)
+ module_sources.update(gn_utils.label_to_path(src) for src in dep.sources)
+
+ for type in module_types:
+ if type == 'filegroup':
+ name = label_to_module_name(module_name) + '_filegroup_proto'
+ module = Module('filegroup', name, name)
+ module.comment = f'''GN: [{', '.join(target_names)}]'''
+ module.srcs = module_sources
+ blueprint.add_module(module)
+ elif type == 'lite':
+ name = label_to_module_name(module_name) + '_java_protos'
+ module = Module('java_library', name, name)
+ module.comment = f'''GN: [{', '.join(target_names)}]'''
+ module.proto = {'type': 'lite', 'canonical_path_from_root': False}
+ module.srcs = module_sources
+ blueprint.add_module(module)
+ else:
+ raise Error('Unhandled proto group type: {}'.format(group.type))
def _get_cflags(target: GnParser.Target):
@@ -1228,8 +1244,8 @@ def main():
# checker = gn_utils.ODRChecker(gn, target_name)
# Add any proto groups to the blueprint.
- for l_name, t_names in proto_groups.items():
- create_proto_group_modules(blueprint, gn, l_name, t_names)
+ for name, group in proto_groups.items():
+ create_proto_group_modules(blueprint, gn, name, group)
output = [
"""// Copyright (C) 2017 The Android Open Source Project
diff --git a/tools/gen_tp_table_headers.py b/tools/gen_tp_table_headers.py
index f15245ef8..fd44c1994 100755
--- a/tools/gen_tp_table_headers.py
+++ b/tools/gen_tp_table_headers.py
@@ -66,13 +66,16 @@ def main():
def get_header_path(in_path: str):
return os.path.join(args.import_prefix, get_relout_path(in_path))
+ def get_relin_path_from_module_path(module_path: str):
+ return module_path[module_path.rfind('/src') + 1:]
+
modules = [
os.path.splitext(get_relin_path(i).replace(os.sep, '.'))[0]
for i in args.inputs
]
headers: Dict[str, Header] = {}
for table in parse_tables_from_modules(modules):
- input_path = os.path.relpath(table.table.python_module, ROOT_DIR)
+ input_path = get_relin_path_from_module_path(table.table.python_module)
header = headers.get(input_path, Header([]))
header.tables.append(table)
headers[input_path] = header
@@ -86,7 +89,7 @@ def main():
header_relout_deps: Set[str] = set()
for table in header.tables:
header_relout_deps = header_relout_deps.union([
- get_header_path(os.path.relpath(c.python_module, ROOT_DIR))
+ get_header_path(get_relin_path_from_module_path(c.python_module))
for c in find_table_deps(table.table)
])
header_relout_deps.discard(relout_path)
diff --git a/tools/gn_utils.py b/tools/gn_utils.py
index d0417d72f..904760ec5 100644
--- a/tools/gn_utils.py
+++ b/tools/gn_utils.py
@@ -529,9 +529,8 @@ class GnParser(object):
return metadata.get('exports', [])
def get_proto_paths(self, proto_desc):
- # import_dirs in metadata will be available for source_set targets.
metadata = proto_desc.get('metadata', {})
- return metadata.get('import_dirs', [])
+ return metadata.get('proto_import_dirs', [])
def get_proto_target_type(self, target: Target
) -> Tuple[Optional[str], Optional[Dict]]:
diff --git a/tools/heap_profile b/tools/heap_profile
index 700cfeb69..eec95087e 100755
--- a/tools/heap_profile
+++ b/tools/heap_profile
@@ -216,6 +216,9 @@ import os
import platform
import subprocess
import sys
+import threading
+
+DOWNLOAD_LOCK = threading.Lock()
def download_or_get_cached(file_name, url, sha256):
@@ -232,28 +235,36 @@ def download_or_get_cached(file_name, url, sha256):
sha256_path = os.path.join(dir, file_name + '.sha256')
needs_download = True
- # Avoid recomputing the SHA-256 on each invocation. The SHA-256 of the last
- # download is cached into file_name.sha256, just check if that matches.
- if os.path.exists(bin_path) and os.path.exists(sha256_path):
- with open(sha256_path, 'rb') as f:
- digest = f.read().decode()
- if digest == sha256:
- needs_download = False
-
- if needs_download:
- # Either the filed doesn't exist or the SHA256 doesn't match.
- tmp_path = bin_path + '.tmp'
- print('Downloading ' + url)
- subprocess.check_call(['curl', '-f', '-L', '-#', '-o', tmp_path, url])
- with open(tmp_path, 'rb') as fd:
- actual_sha256 = hashlib.sha256(fd.read()).hexdigest()
- if actual_sha256 != sha256:
- raise Exception('Checksum mismatch for %s (actual: %s, expected: %s)' %
- (url, actual_sha256, sha256))
- os.chmod(tmp_path, 0o755)
- os.replace(tmp_path, bin_path)
- with open(sha256_path, 'w') as f:
- f.write(sha256)
+ try:
+ # In BatchTraceProcessor, many threads can be trying to execute the below
+ # code in parallel. For this reason, protect the whole operation with a
+ # lock.
+ DOWNLOAD_LOCK.acquire()
+
+ # Avoid recomputing the SHA-256 on each invocation. The SHA-256 of the last
+ # download is cached into file_name.sha256, just check if that matches.
+ if os.path.exists(bin_path) and os.path.exists(sha256_path):
+ with open(sha256_path, 'rb') as f:
+ digest = f.read().decode()
+ if digest == sha256:
+ needs_download = False
+
+ if needs_download:
+ # Either the filed doesn't exist or the SHA256 doesn't match.
+ tmp_path = bin_path + '.tmp'
+ print('Downloading ' + url)
+ subprocess.check_call(['curl', '-f', '-L', '-#', '-o', tmp_path, url])
+ with open(tmp_path, 'rb') as fd:
+ actual_sha256 = hashlib.sha256(fd.read()).hexdigest()
+ if actual_sha256 != sha256:
+ raise Exception('Checksum mismatch for %s (actual: %s, expected: %s)' %
+ (url, actual_sha256, sha256))
+ os.chmod(tmp_path, 0o755)
+ os.replace(tmp_path, bin_path)
+ with open(sha256_path, 'w') as f:
+ f.write(sha256)
+ finally:
+ DOWNLOAD_LOCK.release()
return bin_path
diff --git a/tools/record_android_trace b/tools/record_android_trace
index ceb0808ab..f2a48c9f5 100755
--- a/tools/record_android_trace
+++ b/tools/record_android_trace
@@ -201,6 +201,9 @@ import os
import platform
import subprocess
import sys
+import threading
+
+DOWNLOAD_LOCK = threading.Lock()
def download_or_get_cached(file_name, url, sha256):
@@ -217,28 +220,36 @@ def download_or_get_cached(file_name, url, sha256):
sha256_path = os.path.join(dir, file_name + '.sha256')
needs_download = True
- # Avoid recomputing the SHA-256 on each invocation. The SHA-256 of the last
- # download is cached into file_name.sha256, just check if that matches.
- if os.path.exists(bin_path) and os.path.exists(sha256_path):
- with open(sha256_path, 'rb') as f:
- digest = f.read().decode()
- if digest == sha256:
- needs_download = False
-
- if needs_download:
- # Either the filed doesn't exist or the SHA256 doesn't match.
- tmp_path = bin_path + '.tmp'
- print('Downloading ' + url)
- subprocess.check_call(['curl', '-f', '-L', '-#', '-o', tmp_path, url])
- with open(tmp_path, 'rb') as fd:
- actual_sha256 = hashlib.sha256(fd.read()).hexdigest()
- if actual_sha256 != sha256:
- raise Exception('Checksum mismatch for %s (actual: %s, expected: %s)' %
- (url, actual_sha256, sha256))
- os.chmod(tmp_path, 0o755)
- os.replace(tmp_path, bin_path)
- with open(sha256_path, 'w') as f:
- f.write(sha256)
+ try:
+ # In BatchTraceProcessor, many threads can be trying to execute the below
+ # code in parallel. For this reason, protect the whole operation with a
+ # lock.
+ DOWNLOAD_LOCK.acquire()
+
+ # Avoid recomputing the SHA-256 on each invocation. The SHA-256 of the last
+ # download is cached into file_name.sha256, just check if that matches.
+ if os.path.exists(bin_path) and os.path.exists(sha256_path):
+ with open(sha256_path, 'rb') as f:
+ digest = f.read().decode()
+ if digest == sha256:
+ needs_download = False
+
+ if needs_download:
+ # Either the filed doesn't exist or the SHA256 doesn't match.
+ tmp_path = bin_path + '.tmp'
+ print('Downloading ' + url)
+ subprocess.check_call(['curl', '-f', '-L', '-#', '-o', tmp_path, url])
+ with open(tmp_path, 'rb') as fd:
+ actual_sha256 = hashlib.sha256(fd.read()).hexdigest()
+ if actual_sha256 != sha256:
+ raise Exception('Checksum mismatch for %s (actual: %s, expected: %s)' %
+ (url, actual_sha256, sha256))
+ os.chmod(tmp_path, 0o755)
+ os.replace(tmp_path, bin_path)
+ with open(sha256_path, 'w') as f:
+ f.write(sha256)
+ finally:
+ DOWNLOAD_LOCK.release()
return bin_path
diff --git a/tools/trace_processor b/tools/trace_processor
index 0917f546c..972c9afbb 100755
--- a/tools/trace_processor
+++ b/tools/trace_processor
@@ -212,6 +212,9 @@ import os
import platform
import subprocess
import sys
+import threading
+
+DOWNLOAD_LOCK = threading.Lock()
def download_or_get_cached(file_name, url, sha256):
@@ -228,28 +231,36 @@ def download_or_get_cached(file_name, url, sha256):
sha256_path = os.path.join(dir, file_name + '.sha256')
needs_download = True
- # Avoid recomputing the SHA-256 on each invocation. The SHA-256 of the last
- # download is cached into file_name.sha256, just check if that matches.
- if os.path.exists(bin_path) and os.path.exists(sha256_path):
- with open(sha256_path, 'rb') as f:
- digest = f.read().decode()
- if digest == sha256:
- needs_download = False
+ try:
+ # In BatchTraceProcessor, many threads can be trying to execute the below
+ # code in parallel. For this reason, protect the whole operation with a
+ # lock.
+ DOWNLOAD_LOCK.acquire()
+
+ # Avoid recomputing the SHA-256 on each invocation. The SHA-256 of the last
+ # download is cached into file_name.sha256, just check if that matches.
+ if os.path.exists(bin_path) and os.path.exists(sha256_path):
+ with open(sha256_path, 'rb') as f:
+ digest = f.read().decode()
+ if digest == sha256:
+ needs_download = False
- if needs_download:
- # Either the filed doesn't exist or the SHA256 doesn't match.
- tmp_path = bin_path + '.tmp'
- print('Downloading ' + url)
- subprocess.check_call(['curl', '-f', '-L', '-#', '-o', tmp_path, url])
- with open(tmp_path, 'rb') as fd:
- actual_sha256 = hashlib.sha256(fd.read()).hexdigest()
- if actual_sha256 != sha256:
- raise Exception('Checksum mismatch for %s (actual: %s, expected: %s)' %
- (url, actual_sha256, sha256))
- os.chmod(tmp_path, 0o755)
- os.replace(tmp_path, bin_path)
- with open(sha256_path, 'w') as f:
- f.write(sha256)
+ if needs_download:
+ # Either the filed doesn't exist or the SHA256 doesn't match.
+ tmp_path = bin_path + '.tmp'
+ print('Downloading ' + url)
+ subprocess.check_call(['curl', '-f', '-L', '-#', '-o', tmp_path, url])
+ with open(tmp_path, 'rb') as fd:
+ actual_sha256 = hashlib.sha256(fd.read()).hexdigest()
+ if actual_sha256 != sha256:
+ raise Exception('Checksum mismatch for %s (actual: %s, expected: %s)' %
+ (url, actual_sha256, sha256))
+ os.chmod(tmp_path, 0o755)
+ os.replace(tmp_path, bin_path)
+ with open(sha256_path, 'w') as f:
+ f.write(sha256)
+ finally:
+ DOWNLOAD_LOCK.release()
return bin_path
diff --git a/tools/tracebox b/tools/tracebox
index a9a89e6d0..144df3a5e 100755
--- a/tools/tracebox
+++ b/tools/tracebox
@@ -198,6 +198,9 @@ import os
import platform
import subprocess
import sys
+import threading
+
+DOWNLOAD_LOCK = threading.Lock()
def download_or_get_cached(file_name, url, sha256):
@@ -214,28 +217,36 @@ def download_or_get_cached(file_name, url, sha256):
sha256_path = os.path.join(dir, file_name + '.sha256')
needs_download = True
- # Avoid recomputing the SHA-256 on each invocation. The SHA-256 of the last
- # download is cached into file_name.sha256, just check if that matches.
- if os.path.exists(bin_path) and os.path.exists(sha256_path):
- with open(sha256_path, 'rb') as f:
- digest = f.read().decode()
- if digest == sha256:
- needs_download = False
+ try:
+ # In BatchTraceProcessor, many threads can be trying to execute the below
+ # code in parallel. For this reason, protect the whole operation with a
+ # lock.
+ DOWNLOAD_LOCK.acquire()
+
+ # Avoid recomputing the SHA-256 on each invocation. The SHA-256 of the last
+ # download is cached into file_name.sha256, just check if that matches.
+ if os.path.exists(bin_path) and os.path.exists(sha256_path):
+ with open(sha256_path, 'rb') as f:
+ digest = f.read().decode()
+ if digest == sha256:
+ needs_download = False
- if needs_download:
- # Either the filed doesn't exist or the SHA256 doesn't match.
- tmp_path = bin_path + '.tmp'
- print('Downloading ' + url)
- subprocess.check_call(['curl', '-f', '-L', '-#', '-o', tmp_path, url])
- with open(tmp_path, 'rb') as fd:
- actual_sha256 = hashlib.sha256(fd.read()).hexdigest()
- if actual_sha256 != sha256:
- raise Exception('Checksum mismatch for %s (actual: %s, expected: %s)' %
- (url, actual_sha256, sha256))
- os.chmod(tmp_path, 0o755)
- os.replace(tmp_path, bin_path)
- with open(sha256_path, 'w') as f:
- f.write(sha256)
+ if needs_download:
+ # Either the filed doesn't exist or the SHA256 doesn't match.
+ tmp_path = bin_path + '.tmp'
+ print('Downloading ' + url)
+ subprocess.check_call(['curl', '-f', '-L', '-#', '-o', tmp_path, url])
+ with open(tmp_path, 'rb') as fd:
+ actual_sha256 = hashlib.sha256(fd.read()).hexdigest()
+ if actual_sha256 != sha256:
+ raise Exception('Checksum mismatch for %s (actual: %s, expected: %s)' %
+ (url, actual_sha256, sha256))
+ os.chmod(tmp_path, 0o755)
+ os.replace(tmp_path, bin_path)
+ with open(sha256_path, 'w') as f:
+ f.write(sha256)
+ finally:
+ DOWNLOAD_LOCK.release()
return bin_path
diff --git a/tools/traceconv b/tools/traceconv
index 8ba71142c..817022c98 100755
--- a/tools/traceconv
+++ b/tools/traceconv
@@ -212,6 +212,9 @@ import os
import platform
import subprocess
import sys
+import threading
+
+DOWNLOAD_LOCK = threading.Lock()
def download_or_get_cached(file_name, url, sha256):
@@ -228,28 +231,36 @@ def download_or_get_cached(file_name, url, sha256):
sha256_path = os.path.join(dir, file_name + '.sha256')
needs_download = True
- # Avoid recomputing the SHA-256 on each invocation. The SHA-256 of the last
- # download is cached into file_name.sha256, just check if that matches.
- if os.path.exists(bin_path) and os.path.exists(sha256_path):
- with open(sha256_path, 'rb') as f:
- digest = f.read().decode()
- if digest == sha256:
- needs_download = False
+ try:
+ # In BatchTraceProcessor, many threads can be trying to execute the below
+ # code in parallel. For this reason, protect the whole operation with a
+ # lock.
+ DOWNLOAD_LOCK.acquire()
+
+ # Avoid recomputing the SHA-256 on each invocation. The SHA-256 of the last
+ # download is cached into file_name.sha256, just check if that matches.
+ if os.path.exists(bin_path) and os.path.exists(sha256_path):
+ with open(sha256_path, 'rb') as f:
+ digest = f.read().decode()
+ if digest == sha256:
+ needs_download = False
- if needs_download:
- # Either the filed doesn't exist or the SHA256 doesn't match.
- tmp_path = bin_path + '.tmp'
- print('Downloading ' + url)
- subprocess.check_call(['curl', '-f', '-L', '-#', '-o', tmp_path, url])
- with open(tmp_path, 'rb') as fd:
- actual_sha256 = hashlib.sha256(fd.read()).hexdigest()
- if actual_sha256 != sha256:
- raise Exception('Checksum mismatch for %s (actual: %s, expected: %s)' %
- (url, actual_sha256, sha256))
- os.chmod(tmp_path, 0o755)
- os.replace(tmp_path, bin_path)
- with open(sha256_path, 'w') as f:
- f.write(sha256)
+ if needs_download:
+ # Either the filed doesn't exist or the SHA256 doesn't match.
+ tmp_path = bin_path + '.tmp'
+ print('Downloading ' + url)
+ subprocess.check_call(['curl', '-f', '-L', '-#', '-o', tmp_path, url])
+ with open(tmp_path, 'rb') as fd:
+ actual_sha256 = hashlib.sha256(fd.read()).hexdigest()
+ if actual_sha256 != sha256:
+ raise Exception('Checksum mismatch for %s (actual: %s, expected: %s)' %
+ (url, actual_sha256, sha256))
+ os.chmod(tmp_path, 0o755)
+ os.replace(tmp_path, bin_path)
+ with open(sha256_path, 'w') as f:
+ f.write(sha256)
+ finally:
+ DOWNLOAD_LOCK.release()
return bin_path
diff --git a/ui/OWNERS b/ui/OWNERS
index aaf587fa6..d6b4b3d36 100644
--- a/ui/OWNERS
+++ b/ui/OWNERS
@@ -1,4 +1,3 @@
-hjd@google.com
primiano@google.com
stevegolton@google.com
diff --git a/ui/package.json b/ui/package.json
index 9dea540cd..8f1e15d4c 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -61,7 +61,7 @@
"pixelmatch": "^5.3.0",
"pngjs": "^6.0.0",
"prettier": "^2.8.8",
- "puppeteer": "^20.7.3",
+ "puppeteer": "^22.6.0",
"rollup": "^2.79.1",
"rollup-plugin-re": "^1.0.7",
"rollup-plugin-sourcemaps": "^0.6.3",
diff --git a/ui/pnpm-lock.yaml b/ui/pnpm-lock.yaml
index d1e575620..e2beb57fe 100644
--- a/ui/pnpm-lock.yaml
+++ b/ui/pnpm-lock.yaml
@@ -164,8 +164,8 @@ devDependencies:
specifier: ^2.8.8
version: 2.8.8
puppeteer:
- specifier: ^20.7.3
- version: 20.7.3(typescript@5.0.4)
+ specifier: ^22.6.0
+ version: 22.6.0(typescript@5.0.4)
rollup:
specifier: ^2.79.1
version: 2.79.1
@@ -1019,24 +1019,19 @@ packages:
resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==}
dev: false
- /@puppeteer/browsers@1.4.2(typescript@5.0.4):
- resolution: {integrity: sha512-5MLU1RFaJh1Beb9FH6raowtZErcsZ0ojYJvdG3OWXfnc3wZiDAa0PgXU2QOKtbW2S+Z731K/2n3YczGA3KbLbQ==}
- engines: {node: '>=16.3.0'}
+ /@puppeteer/browsers@2.2.0:
+ resolution: {integrity: sha512-MC7LxpcBtdfTbzwARXIkqGZ1Osn3nnZJlm+i0+VqHl72t//Xwl9wICrXT8BwtgC6s1xJNHsxOpvzISUqe92+sw==}
+ engines: {node: '>=18'}
hasBin: true
- peerDependencies:
- typescript: '>= 4.7.4'
- peerDependenciesMeta:
- typescript:
- optional: true
dependencies:
debug: 4.3.4
extract-zip: 2.0.1
progress: 2.0.3
- proxy-agent: 6.2.1
- tar-fs: 3.0.2
- typescript: 5.0.4
+ proxy-agent: 6.4.0
+ semver: 7.6.0
+ tar-fs: 3.0.5
unbzip2-stream: 1.4.3
- yargs: 17.7.1
+ yargs: 17.7.2
transitivePeerDependencies:
- supports-color
dev: true
@@ -1121,6 +1116,10 @@ packages:
engines: {node: '>= 6'}
dev: true
+ /@tootallnate/quickjs-emscripten@0.23.0:
+ resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==}
+ dev: true
+
/@types/babel__core@7.20.1:
resolution: {integrity: sha512-aACu/U/omhdk15O4Nfb+fHgH/z3QsfQzpnvRZhYhThms83ZnAOZz7zZAWO7mn2yyNQaA4xTO8GLK3uqFU4bYYw==}
dependencies:
@@ -1466,11 +1465,6 @@ packages:
engines: {node: '>=0.4.0'}
dev: true
- /acorn-walk@8.2.0:
- resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==}
- engines: {node: '>=0.4.0'}
- dev: true
-
/acorn@7.4.1:
resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==}
engines: {node: '>=0.4.0'}
@@ -1690,6 +1684,37 @@ packages:
/balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
+ /bare-events@2.2.2:
+ resolution: {integrity: sha512-h7z00dWdG0PYOQEvChhOSWvOfkIKsdZGkWr083FgN/HyoQuebSew/cgirYqh9SCuy/hRvxc5Vy6Fw8xAmYHLkQ==}
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /bare-fs@2.2.2:
+ resolution: {integrity: sha512-X9IqgvyB0/VA5OZJyb5ZstoN62AzD7YxVGog13kkfYWYqJYcK0kcqLZ6TrmH5qr4/8//ejVcX4x/a0UvaogXmA==}
+ requiresBuild: true
+ dependencies:
+ bare-events: 2.2.2
+ bare-os: 2.2.1
+ bare-path: 2.1.0
+ streamx: 2.15.0
+ dev: true
+ optional: true
+
+ /bare-os@2.2.1:
+ resolution: {integrity: sha512-OwPyHgBBMkhC29Hl3O4/YfxW9n7mdTr2+SsO29XBWKKJsbgj3mnorDB80r5TiCQgQstgE5ga1qNYrpes6NvX2w==}
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /bare-path@2.1.0:
+ resolution: {integrity: sha512-DIIg7ts8bdRKwJRJrUMy/PICEaQZaPGZ26lsSx9MJSwIhSrcdHn7/C8W+XmnG/rKi6BaRcz+JO00CjZteybDtw==}
+ requiresBuild: true
+ dependencies:
+ bare-os: 2.2.1
+ dev: true
+ optional: true
+
/base64-js@1.5.1:
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
dev: true
@@ -1888,13 +1913,15 @@ packages:
fsevents: 2.3.3
dev: false
- /chromium-bidi@0.4.12(devtools-protocol@0.0.1135028):
- resolution: {integrity: sha512-yl0ngMHtYUGJa2G0lkcbPvbnUZ9WMQyMNSfYmlrGD1nHRNyI9KOGw3dOaofFugXHHToneUaSmF9iUdgCBamCjA==}
+ /chromium-bidi@0.5.13(devtools-protocol@0.0.1262051):
+ resolution: {integrity: sha512-OHbYCetDxdW/xmlrafgOiLsIrw4Sp1BEeolbZ1UGJO5v/nekQOJBj/Kzyw6sqKcAVabUTo0GS3cTYgr6zIf00g==}
peerDependencies:
devtools-protocol: '*'
dependencies:
- devtools-protocol: 0.0.1135028
- mitt: 3.0.0
+ devtools-protocol: 0.0.1262051
+ mitt: 3.0.1
+ urlpattern-polyfill: 10.0.0
+ zod: 3.22.4
dev: true
/ci-info@2.0.0:
@@ -2022,28 +2049,26 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
- /cosmiconfig@8.2.0:
- resolution: {integrity: sha512-3rTMnFJA1tCOPwRxtgF4wd7Ab2qvDbL8jX+3smjIbS4HlZBagTlpERbdN7iAbWlrfxE3M8c27kTwTawQ7st+OQ==}
+ /cosmiconfig@9.0.0(typescript@5.0.4):
+ resolution: {integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==}
engines: {node: '>=14'}
+ peerDependencies:
+ typescript: '>=4.9.5'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
dependencies:
+ env-paths: 2.2.1
import-fresh: 3.3.0
js-yaml: 4.1.0
parse-json: 5.2.0
- path-type: 4.0.0
+ typescript: 5.0.4
dev: true
/crelt@1.0.6:
resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==}
dev: false
- /cross-fetch@3.1.6:
- resolution: {integrity: sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==}
- dependencies:
- node-fetch: 2.6.11
- transitivePeerDependencies:
- - encoding
- dev: true
-
/cross-spawn@6.0.5:
resolution: {integrity: sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==}
engines: {node: '>=4.8'}
@@ -2291,14 +2316,13 @@ packages:
isobject: 3.0.1
dev: true
- /degenerator@4.0.3:
- resolution: {integrity: sha512-2wY8vmCfxrQpe2PKGYdiWRre5HQRwsAXbAAWRbC+z2b80MEpnWc8A3a9k4TwqwN3Z/Fm3uhNm5vYUZIbMhyRxQ==}
+ /degenerator@5.0.1:
+ resolution: {integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==}
engines: {node: '>= 14'}
dependencies:
ast-types: 0.13.4
- escodegen: 1.14.3
+ escodegen: 2.1.0
esprima: 4.0.1
- vm2: 3.9.19
dev: true
/delaunator@5.0.0:
@@ -2317,14 +2341,14 @@ packages:
engines: {node: '>=8'}
dev: true
- /devtools-protocol@0.0.1135028:
- resolution: {integrity: sha512-jEcNGrh6lOXNRJvZb9RjeevtZGrgugPKSMJZxfyxWQnhlKawMPhMtk/dfC+Z/6xNXExlzTKlY5LzIAK/fRpQIw==}
- dev: true
-
/devtools-protocol@0.0.1159816:
resolution: {integrity: sha512-2cZlHxC5IlgkIWe2pSDmCrDiTzbSJWywjbDDnupOImEBcG31CQgBLV8wWE+5t+C4rimcjHsbzy7CBzf9oFjboA==}
dev: false
+ /devtools-protocol@0.0.1262051:
+ resolution: {integrity: sha512-YJe4CT5SA8on3Spa+UDtNhEqtuV6Epwz3OZ4HQVLhlRccpZ9/PAYk0/cy/oKxFKRrZPBUPyxympQci4yWNWZ9g==}
+ dev: true
+
/diff-sequences@26.6.2:
resolution: {integrity: sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==}
engines: {node: '>= 10.14.2'}
@@ -2377,6 +2401,11 @@ packages:
resolution: {integrity: sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==}
dev: false
+ /env-paths@2.2.1:
+ resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==}
+ engines: {node: '>=6'}
+ dev: true
+
/error-ex@1.3.2:
resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==}
dependencies:
@@ -2622,6 +2651,7 @@ packages:
optionator: 0.8.3
optionalDependencies:
source-map: 0.6.1
+ dev: false
/escodegen@2.0.0:
resolution: {integrity: sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==}
@@ -2636,6 +2666,18 @@ packages:
source-map: 0.6.1
dev: true
+ /escodegen@2.1.0:
+ resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==}
+ engines: {node: '>=6.0'}
+ hasBin: true
+ dependencies:
+ esprima: 4.0.1
+ estraverse: 5.3.0
+ esutils: 2.0.3
+ optionalDependencies:
+ source-map: 0.6.1
+ dev: true
+
/eslint-config-google@0.14.0(eslint@8.43.0):
resolution: {integrity: sha512-WsbX4WbjuMvTdeVL6+J3rK1RGhCTqjsFjX7UMSMgZiyxxaNLkoJENbrGExzERFeoTpGw3F3FypTiWAP9ZXzkEw==}
engines: {node: '>=0.10.0'}
@@ -3150,6 +3192,7 @@ packages:
/growly@1.3.0:
resolution: {integrity: sha512-+xGQY0YyAWCnqy7Cd++hc2JqMYzlm0dG30Jd0beaA64sROr8C4nt8Yc9V5Ro3avlSUDTN0ulqP/VBKi1/lLygw==}
+ requiresBuild: true
dev: true
optional: true
@@ -3246,8 +3289,8 @@ packages:
- supports-color
dev: true
- /http-proxy-agent@7.0.0:
- resolution: {integrity: sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==}
+ /http-proxy-agent@7.0.2:
+ resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==}
engines: {node: '>= 14'}
dependencies:
agent-base: 7.1.0
@@ -3266,8 +3309,8 @@ packages:
- supports-color
dev: true
- /https-proxy-agent@7.0.0:
- resolution: {integrity: sha512-0euwPCRyAPSgGdzD1IVN9nJYHtBhJwb6XPfbpQcYbPCwrBidX6GzxmchnaF4sfF/jPb74Ojx5g4yTg3sixlyPw==}
+ /https-proxy-agent@7.0.4:
+ resolution: {integrity: sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==}
engines: {node: '>= 14'}
dependencies:
agent-base: 7.1.0
@@ -3348,10 +3391,6 @@ packages:
engines: {node: '>=12'}
dev: false
- /ip@1.1.8:
- resolution: {integrity: sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==}
- dev: true
-
/ip@2.0.0:
resolution: {integrity: sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==}
dev: true
@@ -3454,6 +3493,7 @@ packages:
resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==}
engines: {node: '>=8'}
hasBin: true
+ requiresBuild: true
dev: true
optional: true
@@ -3565,6 +3605,7 @@ packages:
/is-wsl@2.2.0:
resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==}
engines: {node: '>=8'}
+ requiresBuild: true
dependencies:
is-docker: 2.2.1
dev: true
@@ -4461,8 +4502,8 @@ packages:
ospec: 4.0.0
dev: false
- /mitt@3.0.0:
- resolution: {integrity: sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ==}
+ /mitt@3.0.1:
+ resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==}
dev: true
/mixin-deep@1.3.2:
@@ -4473,10 +4514,6 @@ packages:
is-extendable: 1.0.1
dev: true
- /mkdirp-classic@0.5.3:
- resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==}
- dev: true
-
/mkdirp@1.0.4:
resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==}
engines: {node: '>=10'}
@@ -4537,6 +4574,7 @@ packages:
optional: true
dependencies:
whatwg-url: 5.0.0
+ dev: false
/node-int64@0.4.0:
resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==}
@@ -4714,27 +4752,27 @@ packages:
engines: {node: '>=6'}
dev: true
- /pac-proxy-agent@6.0.3:
- resolution: {integrity: sha512-5Hr1KgPDoc21Vn3rsXBirwwDnF/iac1jN/zkpsOYruyT+ZgsUhUOgVwq3v9+ukjZd/yGm/0nzO1fDfl7rkGoHQ==}
+ /pac-proxy-agent@7.0.1:
+ resolution: {integrity: sha512-ASV8yU4LLKBAjqIPMbrgtaKIvxQri/yh2OpI+S6hVa9JRkUI3Y3NPFbfngDtY7oFtSMD3w31Xns89mDa3Feo5A==}
engines: {node: '>= 14'}
dependencies:
+ '@tootallnate/quickjs-emscripten': 0.23.0
agent-base: 7.1.0
debug: 4.3.4
get-uri: 6.0.1
- http-proxy-agent: 7.0.0
- https-proxy-agent: 7.0.0
- pac-resolver: 6.0.1
- socks-proxy-agent: 8.0.1
+ http-proxy-agent: 7.0.2
+ https-proxy-agent: 7.0.4
+ pac-resolver: 7.0.1
+ socks-proxy-agent: 8.0.2
transitivePeerDependencies:
- supports-color
dev: true
- /pac-resolver@6.0.1:
- resolution: {integrity: sha512-dg497MhVT7jZegPRuOScQ/z0aV/5WR0gTdRu1md+Irs9J9o+ls5jIuxjo1WfaTG+eQQkxyn5HMGvWK+w7EIBkQ==}
+ /pac-resolver@7.0.1:
+ resolution: {integrity: sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==}
engines: {node: '>= 14'}
dependencies:
- degenerator: 4.0.3
- ip: 1.1.8
+ degenerator: 5.0.1
netmask: 2.0.2
dev: true
@@ -4914,18 +4952,18 @@ packages:
long: 5.2.3
dev: false
- /proxy-agent@6.2.1:
- resolution: {integrity: sha512-OIbBKlRAT+ycCm6wAYIzMwPejzRtjy8F3QiDX0eKOA3e4pe3U9F/IvzcHP42bmgQxVv97juG+J8/gx+JIeCX/Q==}
+ /proxy-agent@6.4.0:
+ resolution: {integrity: sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==}
engines: {node: '>= 14'}
dependencies:
agent-base: 7.1.0
debug: 4.3.4
- http-proxy-agent: 7.0.0
- https-proxy-agent: 7.0.0
+ http-proxy-agent: 7.0.2
+ https-proxy-agent: 7.0.4
lru-cache: 7.18.3
- pac-proxy-agent: 6.0.3
+ pac-proxy-agent: 7.0.1
proxy-from-env: 1.1.0
- socks-proxy-agent: 8.0.1
+ socks-proxy-agent: 8.0.2
transitivePeerDependencies:
- supports-color
dev: true
@@ -4950,40 +4988,33 @@ packages:
engines: {node: '>=6'}
dev: true
- /puppeteer-core@20.7.3(typescript@5.0.4):
- resolution: {integrity: sha512-OraI71GPPfUMosLqaOsDGbp/ZLoxLTm0BAda0uE6G+H3onmljfoaJcIPm8X5y1LMq1K1HF1bipcCI7hWGkd3bQ==}
- engines: {node: '>=16.3.0'}
- peerDependencies:
- typescript: '>= 4.7.4'
- peerDependenciesMeta:
- typescript:
- optional: true
+ /puppeteer-core@22.6.0:
+ resolution: {integrity: sha512-xclyGFhxHfZ9l62uXFm+JpgtJHLIZ1qHc7iR4eaIqBNKA5Dg2sDr8yvylfCx5bMN89QWIaxpV6IHsy0qUynK/g==}
+ engines: {node: '>=18'}
dependencies:
- '@puppeteer/browsers': 1.4.2(typescript@5.0.4)
- chromium-bidi: 0.4.12(devtools-protocol@0.0.1135028)
- cross-fetch: 3.1.6
+ '@puppeteer/browsers': 2.2.0
+ chromium-bidi: 0.5.13(devtools-protocol@0.0.1262051)
debug: 4.3.4
- devtools-protocol: 0.0.1135028
- typescript: 5.0.4
- ws: 8.13.0
+ devtools-protocol: 0.0.1262051
+ ws: 8.16.0
transitivePeerDependencies:
- bufferutil
- - encoding
- supports-color
- utf-8-validate
dev: true
- /puppeteer@20.7.3(typescript@5.0.4):
- resolution: {integrity: sha512-3tw12ykFRLvzTRc9PyUOE5xeHQhhLEcKEOVjSfNtRmZqlAnvfhAP8ue+mjojy8NJ1LIfF6fps7OKzSc4JSJSlA==}
- engines: {node: '>=16.3.0'}
+ /puppeteer@22.6.0(typescript@5.0.4):
+ resolution: {integrity: sha512-TYeza4rl1YXfxqUVw/0hWUWYX5cicnf6qu5kkDV+t7QrESCjMoSNnva4ZA/MRGQ03HnB9BOFw9nxs/SKek5KDA==}
+ engines: {node: '>=18'}
+ hasBin: true
requiresBuild: true
dependencies:
- '@puppeteer/browsers': 1.4.2(typescript@5.0.4)
- cosmiconfig: 8.2.0
- puppeteer-core: 20.7.3(typescript@5.0.4)
+ '@puppeteer/browsers': 2.2.0
+ cosmiconfig: 9.0.0(typescript@5.0.4)
+ devtools-protocol: 0.0.1262051
+ puppeteer-core: 22.6.0
transitivePeerDependencies:
- bufferutil
- - encoding
- supports-color
- typescript
- utf-8-validate
@@ -4999,6 +5030,7 @@ packages:
/queue-tick@1.0.1:
resolution: {integrity: sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==}
+ requiresBuild: true
dev: true
/react-is@17.0.2:
@@ -5236,6 +5268,14 @@ packages:
dependencies:
lru-cache: 6.0.0
+ /semver@7.6.0:
+ resolution: {integrity: sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==}
+ engines: {node: '>=10'}
+ hasBin: true
+ dependencies:
+ lru-cache: 6.0.0
+ dev: true
+
/set-blocking@2.0.0:
resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
dev: true
@@ -5276,6 +5316,7 @@ packages:
/shellwords@0.1.1:
resolution: {integrity: sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==}
+ requiresBuild: true
dev: true
optional: true
@@ -5329,8 +5370,8 @@ packages:
- supports-color
dev: true
- /socks-proxy-agent@8.0.1:
- resolution: {integrity: sha512-59EjPbbgg8U3x62hhKOFVAmySQUcfRQ4C7Q/D5sEHnZTQRrQlNKINks44DMR1gwXp0p4LaVIeccX2KHTTcHVqQ==}
+ /socks-proxy-agent@8.0.2:
+ resolution: {integrity: sha512-8zuqoLv1aP/66PHF5TqwJ7Czm3Yv32urJQHrVyhD7mmA6d61Zv8cIXQYPTWwmg6qlupnPvs/QKDmfa4P/qct2g==}
engines: {node: '>= 14'}
dependencies:
agent-base: 7.1.0
@@ -5532,16 +5573,18 @@ packages:
resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
dev: true
- /tar-fs@3.0.2:
- resolution: {integrity: sha512-mLQ5iTTCv2tt3a4BwvD8QX1YFVBL/94/Nd+U2il38wt2+zaJSusp1VwJSNkBmB48FeTdOqptf1DAUIosXQBRrQ==}
+ /tar-fs@3.0.5:
+ resolution: {integrity: sha512-JOgGAmZyMgbqpLwct7ZV8VzkEB6pxXFBVErLtb+XCOqzc6w1xiWKI9GVd6bwk68EX7eJ4DWmfXVmq8K2ziZTGg==}
dependencies:
- mkdirp-classic: 0.5.3
pump: 3.0.0
- tar-stream: 3.1.4
+ tar-stream: 3.1.7
+ optionalDependencies:
+ bare-fs: 2.2.2
+ bare-path: 2.1.0
dev: true
- /tar-stream@3.1.4:
- resolution: {integrity: sha512-IlHr7ZOW6XaVBCrSCokUJG4IqUuRcWW76B8XbrtCotbaDh6zVGE7WPCzaSz1CN+acFmWiwoa+cE4RZsom0RzXg==}
+ /tar-stream@3.1.7:
+ resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==}
dependencies:
b4a: 1.6.4
fast-fifo: 1.2.0
@@ -5642,6 +5685,7 @@ packages:
/tr46@0.0.3:
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
+ dev: false
/tr46@2.1.0:
resolution: {integrity: sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==}
@@ -5795,6 +5839,10 @@ packages:
requires-port: 1.0.0
dev: true
+ /urlpattern-polyfill@10.0.0:
+ resolution: {integrity: sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==}
+ dev: true
+
/use@3.1.1:
resolution: {integrity: sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==}
engines: {node: '>=0.10.0'}
@@ -5813,6 +5861,7 @@ packages:
/uuid@8.3.2:
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
hasBin: true
+ requiresBuild: true
dev: true
optional: true
@@ -6185,15 +6234,6 @@ packages:
resolution: {integrity: sha512-DRibZL6DsNhIgYQ+wNdWDL2SL3bKPlVrRiBqV5yuMm++op8W4kGFtaQfCs4KEJn0wBZcHVHJ3eoywX8983k1ow==}
dev: true
- /vm2@3.9.19:
- resolution: {integrity: sha512-J637XF0DHDMV57R6JyVsTak7nIL8gy5KH4r1HiwWLf/4GBbb5MKL5y7LpmF4A8E2nR6XmzpmMFQ7V7ppPTmUQg==}
- engines: {node: '>=6.0'}
- hasBin: true
- dependencies:
- acorn: 8.9.0
- acorn-walk: 8.2.0
- dev: true
-
/w3c-hr-time@1.0.2:
resolution: {integrity: sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==}
deprecated: Use your platform's native performance.now() and performance.timeOrigin.
@@ -6220,6 +6260,7 @@ packages:
/webidl-conversions@3.0.1:
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
+ dev: false
/webidl-conversions@5.0.0:
resolution: {integrity: sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==}
@@ -6246,6 +6287,7 @@ packages:
dependencies:
tr46: 0.0.3
webidl-conversions: 3.0.1
+ dev: false
/whatwg-url@8.7.0:
resolution: {integrity: sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==}
@@ -6333,8 +6375,8 @@ packages:
optional: true
dev: true
- /ws@8.13.0:
- resolution: {integrity: sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==}
+ /ws@8.16.0:
+ resolution: {integrity: sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==}
engines: {node: '>=10.0.0'}
peerDependencies:
bufferutil: ^4.0.1
@@ -6402,19 +6444,6 @@ packages:
yargs-parser: 18.1.3
dev: true
- /yargs@17.7.1:
- resolution: {integrity: sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==}
- engines: {node: '>=12'}
- dependencies:
- cliui: 8.0.1
- escalade: 3.1.1
- get-caller-file: 2.0.5
- require-directory: 2.1.1
- string-width: 4.2.3
- y18n: 5.0.8
- yargs-parser: 21.1.1
- dev: true
-
/yargs@17.7.2:
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
engines: {node: '>=12'}
@@ -6426,7 +6455,6 @@ packages:
string-width: 4.2.3
y18n: 5.0.8
yargs-parser: 21.1.1
- dev: false
/yauzl@2.10.0:
resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==}
@@ -6440,8 +6468,11 @@ packages:
engines: {node: '>=10'}
dev: true
+ /zod@3.22.4:
+ resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==}
+ dev: true
+
file:src/base/utils:
resolution: {directory: src/base/utils, type: directory}
name: custom_utils
- version: 0.0.1
dev: false
diff --git a/ui/release/OWNERS b/ui/release/OWNERS
index 418a82a48..410876411 100644
--- a/ui/release/OWNERS
+++ b/ui/release/OWNERS
@@ -1,6 +1,5 @@
set noparent
eseckler@google.com
-hjd@google.com
lalitm@google.com
primiano@google.com
skyostil@google.com
diff --git a/ui/release/channels.json b/ui/release/channels.json
index 5f4dd4d02..c87709e1d 100644
--- a/ui/release/channels.json
+++ b/ui/release/channels.json
@@ -6,7 +6,7 @@
},
{
"name": "canary",
- "rev": "58439240cbad83689a1fbafd8234a379a56be7c1"
+ "rev": "9945c86178de704ff44bef7f64c020ff79aedd28"
},
{
"name": "autopush",
diff --git a/ui/src/assets/details.scss b/ui/src/assets/details.scss
index d09690a2c..ff66708ff 100644
--- a/ui/src/assets/details.scss
+++ b/ui/src/assets/details.scss
@@ -446,220 +446,51 @@
header.stale {
color: grey;
}
-
- .rows {
- position: relative;
- direction: ltr;
- width: 100%;
-
- .row {
- @include transition();
- position: absolute;
- width: 100%;
- height: 20px;
- line-height: 20px;
- background-color: hsl(214, 22%, 100%);
-
- &.D {
- color: hsl(122, 20%, 40%);
- }
- &.V {
- color: hsl(122, 20%, 30%);
- }
- &.I {
- color: hsl(0, 0%, 20%);
- }
- &.W {
- color: hsl(45, 60%, 45%);
- }
- &.E {
- color: hsl(4, 90%, 58%);
- }
- &.F {
- color: hsl(291, 64%, 42%);
- }
- &.stale {
- color: #aaa;
- }
- &:nth-child(even) {
- background-color: hsl(214, 22%, 95%);
- }
- &:hover {
- background-color: $table-hover-color;
- }
- .cell {
- font-size: 11px;
- font-family: var(--monospace-font);
- white-space: nowrap;
- overflow: scroll;
- padding-left: 10px;
- padding-right: 10px;
- display: inline-block;
- &:first-child {
- padding-left: 5px;
- }
- &:last-child {
- padding-right: 5px;
- }
- &:only-child {
- width: 100%;
- }
-
- // The following children will be used as columns in the table showing
- // Android logs.
-
- // 1.Timestamp
- &:nth-child(1) {
- width: 7rem;
- text-overflow: clip;
- text-align: right;
- }
- // 2.Level
- &:nth-child(2) {
- width: 4rem;
- }
- // 3.Tag
- &:nth-child(3) {
- width: 13rem;
- }
-
- &.with-process {
- // 4.Process name
- &:nth-child(4) {
- width: 18rem;
- }
- // 5.Message - a long string, will take most of the display space.
- &:nth-child(5) {
- width: calc(100% - 42rem);
- }
- }
-
- &.no-process {
- // 4.Message - a long string, will take most of the display space.
- &:nth-child(4) {
- width: calc(100% - 24rem);
- }
- }
-
- &.row-header {
- text-align: left;
- font-weight: bold;
- font-size: 13px;
- }
-
- &.row-header:first-child {
- padding-left: 15px;
- }
- }
- }
- }
}
-.pf-details-table {
- margin: 10px;
+.pf-ftrace-explorer {
+ height: 100%;
+ font-size: 11px;
+ font-family: var(--monospace-font);
+
+ .colour {
+ display: inline-block;
+ height: 10px;
+ width: 10px;
+ margin-right: 4px;
+ }
}
-.ftrace-panel {
- display: contents;
+.pf-android-logs-table {
+ height: 100%;
+ font-size: 11px;
+ font-family: var(--monospace-font);
- .sticky {
- position: sticky;
- top: 0;
- left: 0;
- z-index: 1;
- background-color: white;
- color: #3c4b5d;
- padding: 5px 10px;
- display: grid;
- grid-template-columns: auto auto;
- justify-content: space-between;
+ .D {
+ color: hsl(122, 20%, 40%);
}
-
- .ftrace-rows-label {
- display: flex;
- align-items: center;
+ .V {
+ color: hsl(122, 20%, 30%);
}
-
- header.stale {
- color: grey;
+ .I {
+ color: hsl(0, 0%, 20%);
}
-
- .rows {
- position: relative;
- direction: ltr;
- min-width: 100%;
- font-size: 12px;
-
- .row {
- @include transition();
- position: absolute;
- min-width: 100%;
- line-height: 20px;
- background-color: hsl(214, 22%, 100%);
- white-space: nowrap;
-
- &:nth-child(even) {
- background-color: hsl(214, 22%, 95%);
- }
-
- &:hover {
- background-color: $table-hover-color;
- }
-
- .cell {
- font-family: var(--monospace-font);
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- margin-right: 8px;
- display: inline-block;
-
- .colour {
- display: inline-block;
- height: 10px;
- width: 10px;
- margin-right: 4px;
- }
-
- &:first-child {
- margin-left: 8px;
- }
-
- &:last-child {
- margin-right: 8px;
- }
-
- &:only-child {
- width: 100%;
- }
-
- // Timestamp
- &:nth-child(1) {
- width: 13em;
- // text-align: right;
- }
-
- // Name
- &:nth-child(2) {
- width: 24em;
- }
-
- // CPU
- &:nth-child(3) {
- width: 3em;
- }
-
- // Process
- &:nth-child(4) {
- width: 24em;
- }
-
- &.row-header {
- font-weight: bold;
- }
- }
- }
+ .W {
+ color: hsl(45, 60%, 45%);
+ }
+ .E {
+ color: hsl(4, 90%, 58%);
}
+ .F {
+ color: hsl(291, 64%, 42%);
+ }
+ .pf-highlighted {
+ background: #d2efe0;
+ }
+}
+
+.pf-details-table {
+ margin: 10px;
}
.screenshot-panel {
diff --git a/ui/src/assets/perfetto.scss b/ui/src/assets/perfetto.scss
index 8bb1cdc78..567deae7e 100644
--- a/ui/src/assets/perfetto.scss
+++ b/ui/src/assets/perfetto.scss
@@ -56,3 +56,4 @@
@import "widgets/hotkey";
@import "widgets/text_paragraph";
@import "widgets/treetable";
+@import "widgets/virtual_table";
diff --git a/ui/src/assets/widgets/details_shell.scss b/ui/src/assets/widgets/details_shell.scss
index a71f237b7..4888f6b80 100644
--- a/ui/src/assets/widgets/details_shell.scss
+++ b/ui/src/assets/widgets/details_shell.scss
@@ -63,6 +63,8 @@
&.pf-fill-parent {
height: 100%;
+ overflow-y: hidden;
+
.pf-content {
overflow-y: auto;
}
diff --git a/ui/src/assets/widgets/tree.scss b/ui/src/assets/widgets/tree.scss
index 83073c961..0f6604bf8 100644
--- a/ui/src/assets/widgets/tree.scss
+++ b/ui/src/assets/widgets/tree.scss
@@ -2,66 +2,60 @@
$chevron-svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='8' width='8'%3E%3Cline x1='2' y1='0' x2='6' y2='4' stroke='black'/%3E%3Cline x1='6' y1='4' x2='2' y2='8' stroke='black'/%3E%3C/svg%3E");
-@mixin grid {
+.pf-tree {
+ font-family: $pf-font;
display: grid;
- grid-template-columns: [gutter]auto [left]auto [right]1fr;
+ grid-template-columns: [left]auto [right]1fr;
row-gap: 5px;
-}
-.pf-tree {
- font-family: $pf-font;
- @include grid;
.pf-tree-node {
display: contents;
- .pf-tree-content {
- display: contents;
- &:hover {
- background: $table-hover-color;
- }
- .pf-tree-left {
- background: inherit;
- min-width: max-content;
- border-radius: $pf-border-radius 0 0 $pf-border-radius;
- font-weight: bolder;
- }
- .pf-tree-right {
- background: inherit;
- padding: 0 0 0 15px;
- border-radius: 0 $pf-border-radius $pf-border-radius 0;
- word-break: break-all;
- white-space: pre-wrap;
- }
+
+ &:hover {
+ background: $table-hover-color;
}
+
+ .pf-tree-left {
+ grid-column: left;
+ background: inherit;
+ min-width: max-content;
+ border-radius: $pf-border-radius 0 0 $pf-border-radius;
+ font-weight: bolder;
+ }
+
+ .pf-tree-right {
+ grid-column: right;
+ background: inherit;
+ padding: 0 0 0 15px;
+ border-radius: 0 $pf-border-radius $pf-border-radius 0;
+ word-break: break-all;
+ white-space: pre-wrap;
+ }
+
.pf-tree-gutter {
- display: block;
+ display: inline-flex;
position: relative;
- }
- &.pf-collapsed > .pf-tree-gutter {
- cursor: pointer;
width: 16px;
- display: flex;
justify-content: center;
align-items: center;
+ }
+
+ &.pf-collapsed > .pf-tree-left > .pf-tree-gutter {
+ cursor: pointer;
+
&::after {
content: $chevron-svg;
}
}
- &.pf-expanded > .pf-tree-gutter {
+ &.pf-expanded > .pf-tree-left > .pf-tree-gutter {
cursor: pointer;
- width: 16px;
- display: flex;
- justify-content: center;
- align-items: center;
&::after {
content: $chevron-svg;
rotate: 90deg;
}
}
- &.pf-loading > .pf-tree-gutter {
- width: 16px;
- display: flex;
- justify-content: center;
- align-items: center;
+
+ &.pf-loading > .pf-tree-left > .pf-tree-gutter {
&::after {
content: "";
border: solid 1px lightgray;
@@ -76,19 +70,19 @@ $chevron-svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'
display: block;
position: relative;
}
- .pf-tree-children {
- grid-column: 2 / span 2;
- @include grid;
- .pf-tree-gutter {
- // Nested gutters are always present, to provide indentation
- width: 16px;
- }
- }
- &.pf-collapsed > .pf-tree-children {
- display: none;
- }
- &.pf-collapsed > .pf-tree-indent-gutter {
+
+ &.pf-collapsed + .pf-tree-children {
display: none;
}
}
+
+ .pf-tree-children {
+ display: grid;
+ grid-column: 1 / span 2;
+ grid-template-columns: subgrid;
+ row-gap: 5px;
+ border-left: solid rgba(0, 0, 0, 0.2) 1px;
+ margin-left: 6px;
+ padding-left: 6px;
+ }
}
diff --git a/ui/src/assets/widgets/virtual_table.scss b/ui/src/assets/widgets/virtual_table.scss
new file mode 100644
index 000000000..acd22d52f
--- /dev/null
+++ b/ui/src/assets/widgets/virtual_table.scss
@@ -0,0 +1,89 @@
+// 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.
+
+@use "sass:math";
+@import "theme";
+
+// Adding these to a new layer makes other rules take precedence
+@layer widgets {
+ .pf-vtable {
+ overflow: auto;
+ font-family: $pf-font;
+ position: relative;
+ background: white; // Performance tweak - see b/335451611
+
+ .pf-vtable-content {
+ display: inline-flex;
+ flex-direction: column;
+ min-width: 100%;
+
+ .pf-vtable-header {
+ font-weight: bold;
+ position: sticky;
+ top: 0;
+ z-index: 1;
+ background: white;
+ white-space: nowrap;
+ padding-inline: 4px;
+
+ // A shadow improves distinction between header and content
+ box-shadow: #0001 0px 0px 8px;
+ }
+
+ .pf-vtable-data {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ margin-right: 8px;
+ display: inline-block;
+ }
+
+ .pf-vtable-slider {
+ overflow: hidden;
+
+ // Necessary trig because we have a 45deg stripes
+ $pattern-density: 1px * math.sqrt(2);
+ $pattern-col: #ddd;
+ overflow: hidden;
+
+ background: repeating-linear-gradient(
+ -45deg,
+ $pattern-col,
+ $pattern-col $pattern-density,
+ white $pattern-density,
+ white $pattern-density * 2
+ );
+
+ .pf-vtable-puck {
+ .pf-vtable-row {
+ white-space: nowrap;
+ padding-inline: 4px;
+
+ &:nth-child(odd) {
+ background-color: hsl(214, 22%, 95%);
+ }
+
+ &:nth-child(even) {
+ background-color: white;
+ }
+
+ &:hover {
+ background-color: $table-hover-color;
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/ui/src/base/geom.ts b/ui/src/base/geom.ts
index f5694006b..5a200235f 100644
--- a/ui/src/base/geom.ts
+++ b/ui/src/base/geom.ts
@@ -24,6 +24,11 @@ export interface Size {
readonly height: number;
}
+export interface Vector {
+ readonly x: number;
+ readonly y: number;
+}
+
export function intersectRects(a: Rect, b: Rect): Rect {
return {
top: Math.max(a.top, b.top),
@@ -57,3 +62,28 @@ export function rectSize(r: Rect): Size {
height: r.bottom - r.top,
};
}
+
+/**
+ * Return true if rect a contains rect b.
+ *
+ * @param a A rect.
+ * @param b Another rect.
+ * @returns True if rect a contains rect b, false otherwise.
+ */
+export function containsRect(a: Rect, b: Rect): boolean {
+ return !(
+ b.top < a.top ||
+ b.bottom > a.bottom ||
+ b.left < a.left ||
+ b.right > a.right
+ );
+}
+
+export function translateRect(a: Rect, b: Vector): Rect {
+ return {
+ top: a.top + b.y,
+ left: a.left + b.x,
+ bottom: a.bottom + b.y,
+ right: a.right + b.x,
+ };
+}
diff --git a/ui/src/base/logging.ts b/ui/src/base/logging.ts
index 04e0bc706..05b288d0b 100644
--- a/ui/src/base/logging.ts
+++ b/ui/src/base/logging.ts
@@ -60,16 +60,36 @@ export function reportError(err: ErrorEvent | PromiseRejectionEvent | {}) {
if (err instanceof ErrorEvent) {
errType = 'ERROR';
- errMsg = `${err.error}`;
- errorObj = err.error;
+ // In nominal cases the error is set in err.error{message,stack} and
+ // a toString() of the error object returns a meaningful one-line
+ // description. However, in the case of wasm errors, emscripten seems to
+ // wrap the error in an unusual way: err.error is null but err.message
+ // contains the whole one-line + stack trace.
+ if (err.error === null || err.error === undefined) {
+ // Wasm case.
+ const errLines = `${err.message}`.split('\n');
+ errMsg = errLines[0];
+ errorObj = {stack: errLines.slice(1).join('\n')};
+ } else {
+ // Standard JS case.
+ errMsg = `${err.error}`;
+ errorObj = err.error;
+ }
} else if (err instanceof PromiseRejectionEvent) {
errType = 'PROMISE_REJ';
- errMsg = `PromiseRejection: ${err.reason}`;
+ errMsg = `${err.reason}`;
errorObj = err.reason;
} else {
errType = 'OTHER';
- errMsg = `Err: ${err}`;
+ errMsg = `${err}`;
}
+
+ // Remove useless "Uncaught Error:" or "Error:" prefixes which just create
+ // noise in the bug tracker without adding any meaningful value.
+ errMsg = errMsg.replace(/^Uncaught Error:/, '');
+ errMsg = errMsg.replace(/^Error:/, '');
+ errMsg = errMsg.trim();
+
if (errorObj !== undefined && errorObj !== null) {
const maybeStack = (errorObj as {stack?: string}).stack;
let errStack = maybeStack !== undefined ? `${maybeStack}` : '';
@@ -113,6 +133,15 @@ export function reportError(err: ErrorEvent | PromiseRejectionEvent | {}) {
entryLocation = entryLocation.replace(`/${VERSION}/`, '');
}
stack.push({name: entryName, location: entryLocation});
+ } // for (line in stack)
+
+ // Beautify the Wasm error message if possible. Most Wasm errors are of the
+ // form RuntimeError: unreachable or RuntimeError: abort. Those lead to bug
+ // titles that are undistinguishable from each other. Instead try using the
+ // first entry of the stack that contains a perfetto:: function name.
+ const wasmFunc = stack.find((e) => e.name.includes('perfetto::'))?.name;
+ if (errMsg.includes('RuntimeError') && wasmFunc) {
+ errMsg += ` @ ${wasmFunc.trim()}`;
}
}
// Invoke all the handlers registered through addErrorHandler.
diff --git a/ui/src/common/actions.ts b/ui/src/common/actions.ts
index 4910cbdec..92cad9733 100644
--- a/ui/src/common/actions.ts
+++ b/ui/src/common/actions.ts
@@ -1162,13 +1162,6 @@ export const StateActions = {
);
},
- setPivotTableArgumentNames(
- state: StateDraft,
- args: {argumentNames: string[]},
- ) {
- state.nonSerializableState.pivotTable.argumentNames = args.argumentNames;
- },
-
changePivotTablePivotOrder(
state: StateDraft,
args: {from: number; to: number; direction: DropDirection},
diff --git a/ui/src/common/empty_state.ts b/ui/src/common/empty_state.ts
index 5bf1f5aee..f86691493 100644
--- a/ui/src/common/empty_state.ts
+++ b/ui/src/common/empty_state.ts
@@ -83,7 +83,6 @@ export function createEmptyNonSerializableState(): NonSerializableState {
],
constrainToArea: true,
queryRequested: false,
- argumentNames: [],
},
};
}
diff --git a/ui/src/common/plugins.ts b/ui/src/common/plugins.ts
index ba602703e..0750e2e66 100644
--- a/ui/src/common/plugins.ts
+++ b/ui/src/common/plugins.ts
@@ -299,11 +299,20 @@ class PluginContextTraceImpl implements PluginContextTrace, Disposable {
},
get tracks(): TrackRef[] {
- return Object.values(globals.state.tracks).map((trackState) => {
+ const tracks = Object.values(globals.state.tracks);
+ const pinnedTracks = globals.state.pinnedTracks;
+ const groups = globals.state.trackGroups;
+ return tracks.map((trackState) => {
+ const group = trackState.trackGroup
+ ? groups[trackState.trackGroup]
+ : undefined;
return {
displayName: trackState.name,
uri: trackState.uri,
params: trackState.params,
+ key: trackState.key,
+ groupName: group?.name,
+ isPinned: pinnedTracks.includes(trackState.key),
};
});
},
diff --git a/ui/src/common/recordingV2/recording_config_utils.ts b/ui/src/common/recordingV2/recording_config_utils.ts
index 334ad251c..133d60677 100644
--- a/ui/src/common/recordingV2/recording_config_utils.ts
+++ b/ui/src/common/recordingV2/recording_config_utils.ts
@@ -769,7 +769,7 @@ export function genTraceConfig(
) {
const ds = new TraceConfig.DataSource();
ds.config = new DataSourceConfig();
- ds.config.name = 'windows.etw';
+ ds.config.name = 'org.chromium.etw_system';
ds.config.etwConfig = new EtwConfig();
const kernelFlags: EtwConfig.KernelFlag[] = [];
diff --git a/ui/src/common/search_data.ts b/ui/src/common/search_data.ts
index b81dfb227..7209c048f 100644
--- a/ui/src/common/search_data.ts
+++ b/ui/src/common/search_data.ts
@@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+export type SearchSource = 'cpu' | 'log' | 'slice' | 'track';
+
export interface SearchSummary {
tsStarts: BigInt64Array;
tsEnds: BigInt64Array;
@@ -19,10 +21,10 @@ export interface SearchSummary {
}
export interface CurrentSearchResults {
- sliceIds: Float64Array;
- tsStarts: BigInt64Array;
+ eventIds: Float64Array;
+ tses: BigInt64Array;
utids: Float64Array;
trackKeys: string[];
- sources: string[];
+ sources: SearchSource[];
totalResults: number;
}
diff --git a/ui/src/common/state.ts b/ui/src/common/state.ts
index 3b27e166a..d0af73b61 100644
--- a/ui/src/common/state.ts
+++ b/ui/src/common/state.ts
@@ -424,9 +424,6 @@ export interface PivotTableState {
// Set to true by frontend to request controller to perform the query to
// acquire the necessary data from the engine.
queryRequested: boolean;
-
- // Argument names in the current trace, used for autocompletion purposes.
- argumentNames: string[];
}
export interface LoadedConfigNone {
diff --git a/ui/src/common/thread_state.ts b/ui/src/common/thread_state.ts
index 6ef6188b3..d94625cdb 100644
--- a/ui/src/common/thread_state.ts
+++ b/ui/src/common/thread_state.ts
@@ -34,9 +34,18 @@ export function translateState(
ioWait: boolean | undefined = undefined,
) {
if (state === undefined) return '';
- if (state === 'Running') {
- return state;
+
+ // Self describing states
+ switch (state) {
+ case 'Running':
+ case 'Initialized':
+ case 'Deferred Ready':
+ case 'Transition':
+ case 'Stand By':
+ case 'Waiting':
+ return state;
}
+
if (state === null) {
return 'Unknown';
}
diff --git a/ui/src/controller/aggregation/counter_aggregation_controller.ts b/ui/src/controller/aggregation/counter_aggregation_controller.ts
index 704add75d..20afd9eb2 100644
--- a/ui/src/controller/aggregation/counter_aggregation_controller.ts
+++ b/ui/src/controller/aggregation/counter_aggregation_controller.ts
@@ -39,33 +39,72 @@ export class CounterAggregationController extends AggregationController {
const duration = area.end - area.start;
const durationSec = Duration.toSeconds(duration);
- const query = `create view ${this.kind} as select
- name,
- count(1) as count,
- round(sum(weighted_value)/${duration}, 2) as avg_value,
- last as last_value,
- first as first_value,
- max(last) - min(first) as delta_value,
- round((max(last) - min(first))/${durationSec}, 2) as rate,
- min(value) as min_value,
- max(value) as max_value
- from
- (select *,
- (min(ts + dur, ${area.end}) - max(ts,${area.start}))
- * value as weighted_value,
- first_value(value) over
- (partition by track_id order by ts) as first,
- last_value(value) over
- (partition by track_id order by ts
- range between unbounded preceding and unbounded following) as last
- from experimental_counter_dur
- where track_id in (${trackIds})
- and ts + dur >= ${area.start} and
- ts <= ${area.end})
- join counter_track
- on track_id = counter_track.id
- group by track_id`;
-
+ // TODO(lalitm): Rewrite this query in a way that is both simpler and faster
+ let query;
+ if (trackIds.length === 1) {
+ // Optimized query for the special case where there is only 1 track id.
+ query = `CREATE VIEW ${this.kind} AS
+ WITH aggregated AS (
+ SELECT
+ COUNT(1) AS count,
+ ROUND(SUM(
+ (MIN(ts + dur, ${area.end}) - MAX(ts,${area.start}))*value)/${duration},
+ 2
+ ) AS avg_value,
+ (SELECT value FROM experimental_counter_dur WHERE track_id = ${trackIds[0]}
+ AND ts + dur >= ${area.start}
+ AND ts <= ${area.end} ORDER BY ts DESC LIMIT 1)
+ AS last_value,
+ (SELECT value FROM experimental_counter_dur WHERE track_id = ${trackIds[0]}
+ AND ts + dur >= ${area.start}
+ AND ts <= ${area.end} ORDER BY ts ASC LIMIT 1)
+ AS first_value,
+ MIN(value) AS min_value,
+ MAX(value) AS max_value
+ FROM experimental_counter_dur
+ WHERE track_id = ${trackIds[0]}
+ AND ts + dur >= ${area.start}
+ AND ts <= ${area.end})
+ SELECT
+ (SELECT name FROM counter_track WHERE id = ${trackIds[0]}) AS name,
+ *,
+ MAX(last_value) - MIN(first_value) AS delta_value,
+ ROUND((MAX(last_value) - MIN(first_value))/${durationSec}, 2) AS rate
+ FROM aggregated`;
+ } else {
+ // Slower, but general purspose query that can aggregate multiple tracks
+ query = `CREATE VIEW ${this.kind} AS
+ WITH aggregated AS (
+ SELECT track_id,
+ COUNT(1) AS count,
+ ROUND(SUM(
+ (MIN(ts + dur, ${area.end}) - MAX(ts,${area.start}))*value)/${duration},
+ 2
+ ) AS avg_value,
+ value_at_max_ts(-ts, value) AS first,
+ value_at_max_ts(ts, value) AS last,
+ MIN(value) AS min_value,
+ MAX(value) AS max_value
+ FROM experimental_counter_dur
+ WHERE track_id IN (${trackIds})
+ AND ts + dur >= ${area.start} AND
+ ts <= ${area.end}
+ GROUP BY track_id
+ )
+ SELECT
+ name,
+ count,
+ avg_value,
+ last AS last_value,
+ first AS first_value,
+ last - first AS delta_value,
+ ROUND((last - first)/${durationSec}, 2) AS rate,
+ min_value,
+ max_value
+ FROM aggregated JOIN counter_track ON
+ track_id = counter_track.id
+ GROUP BY track_id`;
+ }
await engine.query(query);
return true;
}
diff --git a/ui/src/controller/flamegraph_controller.ts b/ui/src/controller/flamegraph_controller.ts
index b1c00a49a..22160d643 100644
--- a/ui/src/controller/flamegraph_controller.ts
+++ b/ui/src/controller/flamegraph_controller.ts
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {Duration, Time, time} from '../base/time';
+import {Duration, time} from '../base/time';
import {exists} from '../base/utils';
import {Actions} from '../common/actions';
import {
@@ -25,8 +25,8 @@ import {
CallsiteInfo,
FlamegraphState,
FlamegraphStateViewingOption,
- ProfileType,
isHeapGraphDominatorTreeViewingOption,
+ ProfileType,
} from '../common/state';
import {FlamegraphDetails, globals} from '../frontend/globals';
import {publishFlamegraphDetails} from '../frontend/publish';
@@ -106,6 +106,10 @@ class TablesCache {
}
return tableName;
}
+
+ hasQuery(query: string): boolean {
+ return this.cache.get(query) !== undefined;
+ }
}
export class FlamegraphController extends Controller<'main'> {
@@ -116,10 +120,6 @@ export class FlamegraphController extends Controller<'main'> {
private flamegraphDetails: FlamegraphDetails = {};
private areaSelectionHandler: AreaSelectionHandler;
private cache: TablesCache;
- private heapGraphSelected: {upid: number; timestamp: time} = {
- upid: -1,
- timestamp: Time.INVALID,
- };
constructor(private args: FlamegraphControllerArgs) {
super('main');
@@ -215,9 +215,8 @@ export class FlamegraphController extends Controller<'main'> {
const flamegraphData = await this.getFlamegraphData(
key,
/* eslint-disable @typescript-eslint/strict-boolean-expressions */
- selectedFlamegraphState.viewingOption
- ? /* eslint-enable */
- selectedFlamegraphState.viewingOption
+ selectedFlamegraphState.viewingOption /* eslint-enable */
+ ? selectedFlamegraphState.viewingOption
: defaultViewingOption(selectedFlamegraphState.type),
selection.start,
selection.end,
@@ -540,22 +539,18 @@ export class FlamegraphController extends Controller<'main'> {
}
private async loadHeapGraphDominatorTreeQuery(upid: number, timestamp: time) {
- const selectTreeQuery = `
- -- cache invalidate: upid ${upid}, ts ${timestamp}
- SELECT * FROM heap_graph_type_dominated`;
- if (
- this.heapGraphSelected.upid === upid &&
- this.heapGraphSelected.timestamp === timestamp
- ) {
- return selectTreeQuery;
+ const outputTableName = `heap_graph_type_dominated_${upid}_${timestamp}`;
+ const outputQuery = `SELECT * FROM ${outputTableName}`;
+ if (this.cache.hasQuery(outputQuery)) {
+ return outputQuery;
}
- this.heapGraphSelected = {upid, timestamp};
+
this.args.engine.query(`
INCLUDE PERFETTO MODULE memory.heap_graph_dominator_tree;
-- heap graph dominator tree with objects as nodes and all relavant
-- object self stats and dominated stats
- CREATE PERFETTO TABLE heap_graph_object_dominated AS
+ CREATE PERFETTO TABLE _heap_graph_object_dominated AS
SELECT
node.id,
node.idom_id,
@@ -574,21 +569,19 @@ export class FlamegraphController extends Controller<'main'> {
-- calculate for each object node in the dominator tree the
-- HASH(path of type_id's from the super root to the object)
CREATE PERFETTO TABLE _dominator_tree_path_hash AS
- WITH RECURSIVE _tree_visitor(id, path, path_hash) AS (
+ WITH RECURSIVE _tree_visitor(id, path_hash) AS (
SELECT
id,
- CAST(type_id AS text) || '-' || IFNULL(root_type, '') AS path,
HASH(
- CAST(type_id AS text) || '-' || IFNULL(root_type, '')
+ CAST(type_id AS TEXT) || '-' || IFNULL(root_type, '')
) AS path_hash
- FROM heap_graph_object_dominated
+ FROM _heap_graph_object_dominated
WHERE depth = 1
UNION ALL
SELECT
child.id,
- parent.path || '/' || CAST(type_id AS text) AS path,
- HASH(parent.path || '/' || CAST(type_id AS text)) AS path_hash
- FROM heap_graph_object_dominated child
+ HASH(CAST(parent.path_hash AS TEXT) || '/' || CAST(type_id AS TEXT)) AS path_hash
+ FROM _heap_graph_object_dominated child
JOIN _tree_visitor parent ON child.idom_id = parent.id
)
SELECT * from _tree_visitor
@@ -597,7 +590,7 @@ export class FlamegraphController extends Controller<'main'> {
-- merge object nodes with the same path into one "class type node", so the
-- end result is a tree where nodes are identified by their types and the
-- dominator relationships are preserved.
- CREATE PERFETTO TABLE heap_graph_type_dominated AS
+ CREATE PERFETTO TABLE ${outputTableName} AS
SELECT
map.path_hash as id,
COALESCE(cls.deobfuscated_name, cls.name, '[NULL]') || IIF(
@@ -615,13 +608,18 @@ export class FlamegraphController extends Controller<'main'> {
-1 as line_number,
sum(self_size) AS size,
count(*) AS count
- FROM heap_graph_object_dominated node
+ FROM _heap_graph_object_dominated node
JOIN _dominator_tree_path_hash map USING(id)
LEFT JOIN _dominator_tree_path_hash parent_map ON node.idom_id = parent_map.id
JOIN heap_graph_class cls ON node.type_id = cls.id
- GROUP BY map.path_hash, name, parent_id, depth, map_name, source_file, line_number;`);
+ GROUP BY map.path_hash, name, parent_id, depth, map_name, source_file, line_number;
+
+ -- These are intermediates and not needed
+ DROP TABLE _heap_graph_object_dominated;
+ DROP TABLE _dominator_tree_path_hash;
+ `);
- return selectTreeQuery;
+ return outputQuery;
}
getMinSizeDisplayed(
diff --git a/ui/src/controller/pivot_table_controller.ts b/ui/src/controller/pivot_table_controller.ts
index b1eb386bd..663fdf481 100644
--- a/ui/src/controller/pivot_table_controller.ts
+++ b/ui/src/controller/pivot_table_controller.ts
@@ -31,7 +31,7 @@ import {
} from '../frontend/pivot_table_query_generator';
import {Aggregation, PivotTree} from '../frontend/pivot_table_types';
import {Engine} from '../trace_processor/engine';
-import {ColumnType, STR} from '../trace_processor/query_result';
+import {ColumnType} from '../trace_processor/query_result';
import {Controller} from './controller';
@@ -189,7 +189,6 @@ export class PivotTableController extends Controller<{}> {
engine: Engine;
lastQueryAreaId = '';
lastQueryAreaTracks = new Set<string>();
- requestedArgumentNames = false;
constructor(args: {engine: Engine}) {
super({});
@@ -272,31 +271,11 @@ export class PivotTableController extends Controller<{}> {
);
}
- async requestArgumentNames() {
- this.requestedArgumentNames = true;
- const result = await this.engine.query(`
- select distinct flat_key from args
- `);
- const it = result.iter({flat_key: STR});
-
- const argumentNames = [];
- while (it.valid()) {
- argumentNames.push(it.flat_key);
- it.next();
- }
-
- globals.dispatch(Actions.setPivotTableArgumentNames({argumentNames}));
- }
-
run() {
if (!PIVOT_TABLE_REDUX_FLAG.get()) {
return;
}
- if (!this.requestedArgumentNames) {
- this.requestArgumentNames();
- }
-
const pivotTableState = globals.state.nonSerializableState.pivotTable;
const selection = getLegacySelection(globals.state);
diff --git a/ui/src/controller/search_controller.ts b/ui/src/controller/search_controller.ts
index 5caff6ead..61ada9330 100644
--- a/ui/src/controller/search_controller.ts
+++ b/ui/src/controller/search_controller.ts
@@ -15,7 +15,11 @@
import {sqliteString} from '../base/string_utils';
import {Duration, duration, Span, time, Time, TimeSpan} from '../base/time';
import {exists} from '../base/utils';
-import {CurrentSearchResults, SearchSummary} from '../common/search_data';
+import {
+ CurrentSearchResults,
+ SearchSource,
+ SearchSummary,
+} from '../common/search_data';
import {OmniboxState} from '../common/state';
import {globals} from '../frontend/globals';
import {publishSearch, publishSearchResult} from '../frontend/publish';
@@ -100,8 +104,8 @@ export class SearchController extends Controller<'main'> {
count: new Uint8Array(0),
});
publishSearchResult({
- sliceIds: new Float64Array(0),
- tsStarts: new BigInt64Array(0),
+ eventIds: new Float64Array(0),
+ tses: new BigInt64Array(0),
utids: new Float64Array(0),
sources: [],
trackKeys: [],
@@ -203,11 +207,9 @@ export class SearchController extends Controller<'main'> {
// easier once the track table has entries for all the tracks.
const cpuToTrackId = new Map();
for (const track of Object.values(globals.state.tracks)) {
- if (exists(track?.uri)) {
- const trackInfo = globals.trackManager.resolveTrackInfo(track.uri);
- if (trackInfo?.kind === CPU_SLICE_TRACK_KIND) {
- exists(trackInfo.cpu) && cpuToTrackId.set(trackInfo.cpu, track.key);
- }
+ const trackInfo = globals.trackManager.resolveTrackInfo(track.uri);
+ if (trackInfo?.kind === CPU_SLICE_TRACK_KIND) {
+ exists(trackInfo.cpu) && cpuToTrackId.set(trackInfo.cpu, track.key);
}
}
@@ -220,60 +222,83 @@ export class SearchController extends Controller<'main'> {
utids.push(it.utid);
}
- const queryRes = await this.query(`
- select
- id as sliceId,
- ts,
- 'cpu' as source,
- cpu as sourceId,
- utid
- from sched where utid in (${utids.join(',')})
- union
- select
- slice_id as sliceId,
- ts,
- 'track' as source,
- track_id as sourceId,
- 0 as utid
- from slice
- where slice.name glob ${searchLiteral}
- or (
- 0 != CAST(${sqliteString(search)} AS INT) and
- sliceId = CAST(${sqliteString(search)} AS INT)
- )
- union
- select
- slice_id as sliceId,
- ts,
- 'track' as source,
- track_id as sourceId,
- 0 as utid
- from slice
- join args using(arg_set_id)
- where string_value glob ${searchLiteral} or key glob ${searchLiteral}
- union
- select
- id as sliceId,
- ts,
- 'log' as source,
- 0 as sourceId,
- utid
- from android_logs where msg glob ${searchLiteral}
- order by ts
-
+ const res = await this.query(`
+ select
+ id as sliceId,
+ ts,
+ 'cpu' as source,
+ cpu as sourceId,
+ utid
+ from sched where utid in (${utids.join(',')})
+ union all
+ select *
+ from (
+ select
+ slice_id as sliceId,
+ ts,
+ 'slice' as source,
+ track_id as sourceId,
+ 0 as utid
+ from slice
+ where slice.name glob ${searchLiteral}
+ or (
+ 0 != CAST(${sqliteString(search)} AS INT) and
+ sliceId = CAST(${sqliteString(search)} AS INT)
+ )
+ union
+ select
+ slice_id as sliceId,
+ ts,
+ 'slice' as source,
+ track_id as sourceId,
+ 0 as utid
+ from slice
+ join args using(arg_set_id)
+ where string_value glob ${searchLiteral} or key glob ${searchLiteral}
+ )
+ union all
+ select
+ id as sliceId,
+ ts,
+ 'log' as source,
+ 0 as sourceId,
+ utid
+ from android_logs where msg glob ${searchLiteral}
+ order by ts
`);
- const rows = queryRes.numRows();
const searchResults: CurrentSearchResults = {
- sliceIds: new Float64Array(rows),
- tsStarts: new BigInt64Array(rows),
- utids: new Float64Array(rows),
- trackKeys: [],
+ eventIds: new Float64Array(0),
+ tses: new BigInt64Array(0),
+ utids: new Float64Array(0),
sources: [],
+ trackKeys: [],
totalResults: 0,
};
- const it = queryRes.iter({
+ const lowerSearch = search.toLowerCase();
+ for (const track of Object.values(globals.state.tracks)) {
+ if (track.name.toLowerCase().indexOf(lowerSearch) === -1) {
+ continue;
+ }
+ searchResults.totalResults++;
+ searchResults.sources.push('track');
+ searchResults.trackKeys.push(track.key);
+ }
+
+ const rows = res.numRows();
+ searchResults.eventIds = new Float64Array(
+ searchResults.totalResults + rows,
+ );
+ searchResults.tses = new BigInt64Array(searchResults.totalResults + rows);
+ searchResults.utids = new Float64Array(searchResults.totalResults + rows);
+ for (let i = 0; i < searchResults.totalResults; ++i) {
+ searchResults.eventIds[i] = -1;
+ searchResults.tses[i] = -1n;
+ searchResults.utids[i] = -1;
+ }
+
+ const it = res.iter({
sliceId: NUM,
ts: LONG,
source: STR,
@@ -284,7 +309,7 @@ export class SearchController extends Controller<'main'> {
let trackId = undefined;
if (it.source === 'cpu') {
trackId = cpuToTrackId.get(it.sourceId);
- } else if (it.source === 'track') {
+ } else if (it.source === 'slice') {
trackId = globals.trackManager.trackKeyByTrackId.get(it.sourceId);
} else if (it.source === 'log') {
const logTracks = Object.values(globals.state.tracks).filter(
@@ -305,9 +330,9 @@ export class SearchController extends Controller<'main'> {
const i = searchResults.totalResults++;
searchResults.trackKeys.push(trackId);
- searchResults.sources.push(it.source);
- searchResults.sliceIds[i] = it.sliceId;
- searchResults.tsStarts[i] = it.ts;
+ searchResults.sources.push(it.source as SearchSource);
+ searchResults.eventIds[i] = it.sliceId;
+ searchResults.tses[i] = it.ts;
searchResults.utids[i] = it.utid;
}
return searchResults;
diff --git a/ui/src/controller/trace_controller.ts b/ui/src/controller/trace_controller.ts
index 519421518..c1f0d6e74 100644
--- a/ui/src/controller/trace_controller.ts
+++ b/ui/src/controller/trace_controller.ts
@@ -201,20 +201,14 @@ function showJsonWarning() {
// ensure it's only run once.
async function defineMaxLayoutDepthSqlFunction(engine: Engine): Promise<void> {
await engine.query(`
- create or replace perfetto table __max_layout_depth_state as
- select track_id, max(depth) as max_depth
- from slice
- group by track_id
- order by track_id;
-
create perfetto function __max_layout_depth(track_count INT, track_ids STRING)
returns INT AS
select iif(
$track_count = 1,
(
select max_depth
- from __max_layout_depth_state
- where track_id = cast($track_ids AS int)
+ from _slice_track_summary
+ where id = cast($track_ids AS int)
),
(
select max(layout_depth)
@@ -517,6 +511,7 @@ export class TraceController extends Controller<States> {
// Make sure the helper views are available before we start adding tracks.
await this.initialiseHelperViews();
+ await this.includeSummaryTables();
await defineMaxLayoutDepthSqlFunction(engine);
@@ -1097,6 +1092,22 @@ export class TraceController extends Controller<States> {
}
}
+ async includeSummaryTables() {
+ const engine = assertExists<Engine>(this.engine);
+
+ this.updateStatus('Creating slice summaries');
+ await engine.query(`include perfetto module viz.summary.slices;`);
+
+ this.updateStatus('Creating thread summaries');
+ await engine.query(`include perfetto module viz.summary.threads;`);
+
+ this.updateStatus('Creating processes summaries');
+ await engine.query(`include perfetto module viz.summary.processes;`);
+
+ this.updateStatus('Creating track summaries');
+ await engine.query(`include perfetto module viz.summary.tracks;`);
+ }
+
private updateStatus(msg: string): void {
globals.dispatch(
Actions.updateStatus({
diff --git a/ui/src/controller/track_decider.ts b/ui/src/controller/track_decider.ts
index d8f42fb3a..b6dd3881e 100644
--- a/ui/src/controller/track_decider.ts
+++ b/ui/src/controller/track_decider.ts
@@ -167,64 +167,35 @@ class TrackDecider {
async addGlobalAsyncTracks(engine: EngineProxy): Promise<void> {
const rawGlobalAsyncTracks = await engine.query(`
- with tracks_with_slices as materialized (
- select distinct track_id
- from slice
- ),
- global_tracks as (
- select
- track.parent_id as parent_id,
- track.id as track_id,
- track.name as name
- from track
- join tracks_with_slices on tracks_with_slices.track_id = track.id
- where
- track.type = "track"
- or track.type = "gpu_track"
- or track.type = "cpu_track"
- ),
- global_tracks_grouped as (
- select
- parent_id,
- name,
- group_concat(track_id) as trackIds,
- count(track_id) as trackCount
- from global_tracks track
- group by parent_id, name
+ with global_tracks_grouped as (
+ select distinct t.parent_id, t.name
+ from track t
+ join _slice_track_summary using (id)
+ where t.type in ('track', 'gpu_track', 'cpu_track')
)
select
- t.parent_id as parentId,
- p.name as parentName,
t.name as name,
- t.trackIds as trackIds,
- __max_layout_depth(t.trackCount, t.trackIds) as maxDepth
+ t.parent_id as parentId,
+ p.name as parentName
from global_tracks_grouped AS t
left join track p on (t.parent_id = p.id)
- order by p.name, t.name;
+ order by p.name, t.name
`);
const it = rawGlobalAsyncTracks.iter({
name: STR_NULL,
- parentName: STR_NULL,
parentId: NUM_NULL,
- maxDepth: NUM_NULL,
+ parentName: STR_NULL,
});
const parentIdToGroupId = new Map<number, string>();
-
for (; it.valid(); it.next()) {
const kind = ASYNC_SLICE_TRACK_KIND;
const rawName = it.name === null ? undefined : it.name;
const rawParentName = it.parentName === null ? undefined : it.parentName;
const name = getTrackName({name: rawName, kind});
const parentTrackId = it.parentId;
- const maxDepth = it.maxDepth;
let trackGroup = SCROLLING_TRACK_GROUP;
- // If there are no slices in this track, skip it.
- if (maxDepth === null) {
- continue;
- }
-
if (parentTrackId !== null) {
const groupId = parentIdToGroupId.get(parentTrackId);
if (groupId === undefined) {
@@ -261,11 +232,11 @@ class TrackDecider {
// Only add a gpu freq track if we have
// gpu freq data.
const freqExistsResult = await engine.query(`
- select *
- from gpu_counter_track
- where name = 'gpufreq' and gpu_id = ${gpu}
- limit 1;
- `);
+ select *
+ from gpu_counter_track
+ where name = 'gpufreq' and gpu_id = ${gpu}
+ limit 1;
+ `);
if (freqExistsResult.numRows() > 0) {
this.tracksToAdd.push({
uri: `perfetto.Counter#gpu_freq${gpu}`,
@@ -638,14 +609,14 @@ class TrackDecider {
async addThreadStateTracks(engine: EngineProxy): Promise<void> {
const result = await engine.query(`
- with ts_distinct as materialized (select distinct utid from thread_state)
select
utid,
upid,
tid,
thread.name as threadName
from thread
- where utid != 0 and utid in ts_distinct`);
+ join _sched_summary using (utid)
+ `);
const it = result.iter({
utid: NUM,
@@ -726,16 +697,16 @@ class TrackDecider {
async addThreadCounterTracks(engine: EngineProxy): Promise<void> {
const result = await engine.query(`
- select
- thread_counter_track.name as trackName,
- utid,
- upid,
- tid,
- thread.name as threadName,
- thread_counter_track.id as trackId
- from thread_counter_track
- join thread using(utid)
- where thread_counter_track.name != 'thread_time'
+ select
+ thread_counter_track.name as trackName,
+ utid,
+ upid,
+ tid,
+ thread.name as threadName,
+ thread_counter_track.id as trackId
+ from thread_counter_track
+ join thread using(utid)
+ where thread_counter_track.name != 'thread_time'
`);
const it = result.iter({
@@ -776,27 +747,15 @@ class TrackDecider {
async addProcessAsyncSliceTracks(engine: EngineProxy): Promise<void> {
const result = await engine.query(`
- with process_async_tracks as materialized (
- select
- process_track.upid as upid,
- process_track.name as trackName,
- process.name as processName,
- process.pid as pid,
- group_concat(process_track.id) as trackIds,
- count(1) as trackCount
- from process_track
- join process using(upid)
- where
- process_track.name is null or
- process_track.name not like "% Timeline"
- group by
- process_track.upid,
- process_track.name
- )
select
- t.*,
- __max_layout_depth(t.trackCount, t.trackIds) as maxDepth
- from process_async_tracks t;
+ upid,
+ t.name as trackName,
+ t.track_ids as trackIds,
+ process.name as processName,
+ process.pid as pid
+ from _process_track_summary_by_upid_and_name t
+ join process using(upid)
+ where t.name is null or t.name not glob "* Timeline"
`);
const it = result.iter({
@@ -805,7 +764,6 @@ class TrackDecider {
trackIds: STR,
processName: STR_NULL,
pid: NUM_NULL,
- maxDepth: NUM_NULL,
});
for (; it.valid(); it.next()) {
const upid = it.upid;
@@ -813,14 +771,8 @@ class TrackDecider {
const rawTrackIds = it.trackIds;
const processName = it.processName;
const pid = it.pid;
- const maxDepth = it.maxDepth;
- if (maxDepth === null) {
- // If there are no slices in this track, skip it.
- continue;
- }
-
- const uuid = this.getUuid(0, upid);
+ const uuid = this.getUuid(null, upid);
const name = getTrackName({
name: trackName,
upid,
@@ -840,37 +792,18 @@ class TrackDecider {
async addUserAsyncSliceTracks(engine: EngineProxy): Promise<void> {
const result = await engine.query(`
- with tracks_with_slices as materialized (
- select distinct track_id
- from slice
- ),
- global_tracks as (
- select
- uid_track.name,
- uid_track.uid,
- group_concat(uid_track.id) as trackIds,
- count(uid_track.id) as trackCount
- from uid_track
- join tracks_with_slices
- where tracks_with_slices.track_id == uid_track.id
- group by uid_track.uid
- )
select
t.name as name,
t.uid as uid,
- package_list.package_name as package_name,
- t.trackIds as trackIds,
- __max_layout_depth(t.trackCount, t.trackIds) as maxDepth
- from global_tracks t
- join package_list
- where t.uid = package_list.uid
- group by t.uid
- `);
+ package_list.package_name as packageName
+ from _uid_track_track_summary_by_uid_and_name t
+ join package_list using (uid)
+ `);
const it = result.iter({
name: STR_NULL,
uid: NUM_NULL,
- package_name: STR_NULL,
+ packageName: STR_NULL,
});
// Map From [name] -> [uuid, key]
@@ -882,8 +815,7 @@ class TrackDecider {
}
const rawName = it.name;
const uid = it.uid === null ? undefined : it.uid;
- const userName =
- it.package_name === null ? `UID: ${uid}` : it.package_name;
+ const userName = it.packageName === null ? `UID: ${uid}` : it.packageName;
const groupUuid = `uid-track-group${rawName}`;
if (groupMap.get(rawName) === undefined) {
@@ -910,48 +842,29 @@ class TrackDecider {
async addActualFramesTracks(engine: EngineProxy): Promise<void> {
const result = await engine.query(`
- with process_async_tracks as materialized (
- select
- process_track.upid as upid,
- process_track.name as trackName,
- process.name as processName,
- process.pid as pid,
- group_concat(process_track.id) as trackIds,
- count(1) as trackCount
- from process_track
- join process using(upid)
- where process_track.name = "Actual Timeline"
- group by
- process_track.upid,
- process_track.name
- )
select
- t.*,
- __max_layout_depth(t.trackCount, t.trackIds) as maxDepth
- from process_async_tracks t;
- `);
+ upid,
+ t.name as trackName,
+ process.name as processName,
+ process.pid as pid
+ from _process_track_summary_by_upid_and_name t
+ join process using(upid)
+ where t.name = "Actual Timeline"
+ `);
const it = result.iter({
upid: NUM,
trackName: STR_NULL,
processName: STR_NULL,
pid: NUM_NULL,
- maxDepth: NUM_NULL,
});
for (; it.valid(); it.next()) {
const upid = it.upid;
const trackName = it.trackName;
const processName = it.processName;
const pid = it.pid;
- const maxDepth = it.maxDepth;
-
- if (maxDepth === null) {
- // If there are no slices in this track, skip it.
- continue;
- }
-
- const uuid = this.getUuid(0, upid);
+ const uuid = this.getUuid(null, upid);
const kind = ACTUAL_FRAMES_SLICE_TRACK_KIND;
const name = getTrackName({
name: trackName,
@@ -972,33 +885,21 @@ class TrackDecider {
async addExpectedFramesTracks(engine: EngineProxy): Promise<void> {
const result = await engine.query(`
- with process_async_tracks as materialized (
- select
- process_track.upid as upid,
- process_track.name as trackName,
- process.name as processName,
- process.pid as pid,
- group_concat(process_track.id) as trackIds,
- count(1) as trackCount
- from process_track
- join process using(upid)
- where process_track.name = "Expected Timeline"
- group by
- process_track.upid,
- process_track.name
- )
select
- t.*,
- __max_layout_depth(t.trackCount, t.trackIds) as maxDepth
- from process_async_tracks t;
- `);
+ upid,
+ t.name as trackName,
+ process.name as processName,
+ process.pid as pid
+ from _process_track_summary_by_upid_and_name t
+ join process using(upid)
+ where t.name = "Expected Timeline"
+ `);
const it = result.iter({
upid: NUM,
trackName: STR_NULL,
processName: STR_NULL,
pid: NUM_NULL,
- maxDepth: NUM_NULL,
});
for (; it.valid(); it.next()) {
@@ -1006,15 +907,8 @@ class TrackDecider {
const trackName = it.trackName;
const processName = it.processName;
const pid = it.pid;
- const maxDepth = it.maxDepth;
-
- if (maxDepth === null) {
- // If there are no slices in this track, skip it.
- continue;
- }
-
- const uuid = this.getUuid(0, upid);
+ const uuid = this.getUuid(null, upid);
const kind = EXPECTED_FRAMES_SLICE_TRACK_KIND;
const name = getTrackName({
name: trackName,
@@ -1035,7 +929,6 @@ class TrackDecider {
async addThreadSliceTracks(engine: EngineProxy): Promise<void> {
const result = await engine.query(`
- with slice_track as materialized (select distinct track_id from slice)
select
thread_track.utid as utid,
thread_track.id as trackId,
@@ -1046,8 +939,8 @@ class TrackDecider {
thread.name as threadName,
thread.upid as upid
from thread_track
+ join _slice_track_summary using (id)
join thread using(utid)
- join slice_track on thread_track.id = slice_track.track_id
`);
const it = result.iter({
@@ -1091,14 +984,14 @@ class TrackDecider {
async addProcessCounterTracks(engine: EngineProxy): Promise<void> {
const result = await engine.query(`
- select
- process_counter_track.id as trackId,
- process_counter_track.name as trackName,
- upid,
- process.pid,
- process.name as processName
- from process_counter_track
- join process using(upid);
+ select
+ process_counter_track.id as trackId,
+ process_counter_track.name as trackName,
+ upid,
+ process.pid,
+ process.name as processName
+ from process_counter_track
+ join process using(upid);
`);
const it = result.iter({
trackId: NUM,
@@ -1113,7 +1006,7 @@ class TrackDecider {
const trackId = it.trackId;
const trackName = it.trackName;
const processName = it.processName;
- const uuid = this.getUuid(0, upid);
+ const uuid = this.getUuid(null, upid);
const name = getTrackName({
name: trackName,
upid,
@@ -1135,13 +1028,13 @@ class TrackDecider {
async addProcessHeapProfileTracks(engine: EngineProxy): Promise<void> {
const result = await engine.query(`
- select distinct(upid) from heap_profile_allocation
- union
- select distinct(upid) from heap_graph_object
+ select upid
+ from _process_available_info_summary
+ where allocation_count > 0 or graph_object_count > 0
`);
for (const it = result.iter({upid: NUM}); it.valid(); it.next()) {
const upid = it.upid;
- const uuid = this.getUuid(0, upid);
+ const uuid = this.getUuid(null, upid);
this.tracksToAdd.push({
uri: `perfetto.HeapProfile#${upid}`,
trackSortKey: PrimaryTrackSortKey.HEAP_PROFILE_TRACK,
@@ -1153,14 +1046,15 @@ class TrackDecider {
async addProcessPerfSamplesTracks(engine: EngineProxy): Promise<void> {
const result = await engine.query(`
- select distinct upid, pid
- from perf_sample join thread using (utid) join process using (upid)
- where callsite_id is not null
+ select upid, pid
+ from _process_available_info_summary
+ join process using (upid)
+ where perf_sample_count > 0
`);
for (const it = result.iter({upid: NUM, pid: NUM}); it.valid(); it.next()) {
const upid = it.upid;
const pid = it.pid;
- const uuid = this.getUuid(0, upid);
+ const uuid = this.getUuid(null, upid);
this.tracksToAdd.push({
uri: `perfetto.PerfSamplesProfile#${upid}`,
trackSortKey: PrimaryTrackSortKey.PERF_SAMPLES_PROFILE_TRACK,
@@ -1170,22 +1064,22 @@ class TrackDecider {
}
}
- getUuidUnchecked(utid: number, upid: number | null) {
+ getUuidUnchecked(utid: number | null, upid: number | null) {
return upid === null
- ? this.utidToUuid.get(utid)
+ ? this.utidToUuid.get(utid!)
: this.upidToUuid.get(upid);
}
- getUuid(utid: number, upid: number | null) {
+ getUuid(utid: number | null, upid: number | null) {
return assertExists(this.getUuidUnchecked(utid, upid));
}
- getOrCreateUuid(utid: number, upid: number | null) {
+ getOrCreateUuid(utid: number | null, upid: number | null) {
let uuid = this.getUuidUnchecked(utid, upid);
if (uuid === undefined) {
uuid = uuidv4();
if (upid === null) {
- this.utidToUuid.set(utid, uuid);
+ this.utidToUuid.set(utid!, uuid);
} else {
this.upidToUuid.set(upid, uuid);
}
@@ -1274,181 +1168,155 @@ class TrackDecider {
// thread name
// utid
const result = await engine.query(`
- with candidateThreadsAndProcesses as materialized (
- select upid, 0 as utid from process_track
- union
- select upid, 0 as utid from process_counter_track
- union
- select upid, utid from thread_counter_track join thread using(utid)
- union
- select upid, utid from thread_track join thread using(utid)
- union
- select upid, utid from (
- select distinct utid from sched
- ) join thread using(utid) group by utid
- union
- select upid, 0 as utid from (
- select distinct utid from perf_sample where callsite_id is not null
- ) join thread using (utid)
- union
- select upid, utid from (
- select distinct utid from cpu_profile_stack_sample
- ) join thread using(utid)
- union
- select upid as upid, 0 as utid from heap_profile_allocation
- union
- select upid as upid, 0 as utid from heap_graph_object
- ),
- schedSum as materialized (
- select upid, sum(thread_total_dur) as total_dur
+ with processGroups as (
+ select
+ upid,
+ process.pid as pid,
+ process.name as processName,
+ sum_running_dur as sumRunningDur,
+ thread_slice_count + process_slice_count as sliceCount,
+ perf_sample_count as perfSampleCount,
+ allocation_count as heapProfileAllocationCount,
+ graph_object_count as heapGraphObjectCount,
+ (
+ select group_concat(string_value)
+ from args
+ where
+ process.arg_set_id is not null and
+ arg_set_id = process.arg_set_id and
+ flat_key = 'chrome.process_label'
+ ) chromeProcessLabels,
+ case process.name
+ when 'Browser' then 3
+ when 'Gpu' then 2
+ when 'Renderer' then 1
+ else 0
+ end as chromeProcessRank
+ from _process_available_info_summary
+ join process using(upid)
+ ),
+ threadGroups as (
+ select
+ utid,
+ tid,
+ thread.name as threadName,
+ sum_running_dur as sumRunningDur,
+ slice_count as sliceCount,
+ perf_sample_count as perfSampleCount
+ from _thread_available_info_summary
+ join thread using (utid)
+ where upid is null
+ )
+ select *
from (
- select utid, sum(dur) as thread_total_dur
- from sched where dur != -1 and utid != 0
- group by utid
+ select
+ upid,
+ null as utid,
+ pid,
+ null as tid,
+ processName,
+ null as threadName,
+ sumRunningDur > 0 as hasSched,
+ heapProfileAllocationCount > 0
+ or heapGraphObjectCount > 0 as hasHeapInfo,
+ ifnull(chromeProcessLabels, '') as chromeProcessLabels
+ from processGroups
+ order by
+ chromeProcessRank desc,
+ heapProfileAllocationCount desc,
+ heapGraphObjectCount desc,
+ perfSampleCount desc,
+ sumRunningDur desc,
+ sliceCount desc,
+ processName asc,
+ upid asc
)
- join thread using (utid)
- group by upid
- ),
- sliceSum as materialized (
- select
- process.upid as upid,
- sum(cnt) as sliceCount
- from (select track_id, count(*) as cnt from slice group by track_id)
- left join thread_track on track_id = thread_track.id
- left join thread on thread_track.utid = thread.utid
- left join process_track on track_id = process_track.id
- join process on process.upid = thread.upid
- or process_track.upid = process.upid
- where process.upid is not null
- group by process.upid
- )
- select
- the_tracks.upid,
- the_tracks.utid,
- total_dur as hasSched,
- hasHeapProfiles,
- process.pid as pid,
- thread.tid as tid,
- process.name as processName,
- thread.name as threadName,
- package_list.debuggable as isDebuggable,
- ifnull((
- select group_concat(string_value)
- from args
- where
- process.arg_set_id is not null and
- arg_set_id = process.arg_set_id and
- flat_key = 'chrome.process_label'
- ), '') AS chromeProcessLabels,
- (case process.name
- when 'Browser' then 3
- when 'Gpu' then 2
- when 'Renderer' then 1
- else 0
- end) as chromeProcessRank
- from candidateThreadsAndProcesses the_tracks
- left join schedSum using(upid)
- left join (
- select
- distinct(upid) as upid,
- true as hasHeapProfiles
- from heap_profile_allocation
- union
- select
- distinct(upid) as upid,
- true as hasHeapProfiles
- from heap_graph_object
- ) using (upid)
- left join (
- select
- thread.upid as upid,
- sum(cnt) as perfSampleCount
+ union all
+ select *
from (
- select utid, count(*) as cnt
- from perf_sample where callsite_id is not null
- group by utid
- ) join thread using (utid)
- group by thread.upid
- ) using (upid)
- left join sliceSum using (upid)
- left join thread using(utid)
- left join process using(upid)
- left join package_list using(uid)
- order by
- chromeProcessRank desc,
- hasHeapProfiles desc,
- perfSampleCount desc,
- total_dur desc,
- sliceCount desc,
- processName asc nulls last,
- the_tracks.upid asc nulls last,
- threadName asc nulls last,
- the_tracks.utid asc nulls last;
+ select
+ null,
+ utid,
+ null as pid,
+ tid,
+ null as processName,
+ threadName,
+ sumRunningDur > 0 as hasSched,
+ 0 as hasHeapInfo,
+ '' as chromeProcessLabels
+ from threadGroups
+ order by
+ perfSampleCount desc,
+ sumRunningDur desc,
+ sliceCount desc,
+ threadName asc,
+ utid asc
+ )
`);
const it = result.iter({
- utid: NUM,
upid: NUM_NULL,
- tid: NUM_NULL,
+ utid: NUM_NULL,
pid: NUM_NULL,
- threadName: STR_NULL,
+ tid: NUM_NULL,
processName: STR_NULL,
+ threadName: STR_NULL,
hasSched: NUM_NULL,
- hasHeapProfiles: NUM_NULL,
+ hasHeapInfo: NUM_NULL,
chromeProcessLabels: STR,
});
for (; it.valid(); it.next()) {
const utid = it.utid;
- const tid = it.tid;
const upid = it.upid;
const pid = it.pid;
+ const tid = it.tid;
const threadName = it.threadName;
const processName = it.processName;
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
const hasSched = !!it.hasSched;
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
- const hasHeapProfiles = !!it.hasHeapProfiles;
-
- // Group by upid if present else by utid.
- let pUuid =
- upid === null ? this.utidToUuid.get(utid) : this.upidToUuid.get(upid);
- // These should only happen once for each track group.
- if (pUuid === undefined) {
- pUuid = this.getOrCreateUuid(utid, upid);
- const summaryTrackKey = uuidv4();
- const type = hasSched ? 'schedule' : 'summary';
- const uri = `perfetto.ProcessScheduling#${upid}.${utid}.${type}`;
-
- this.tracksToAdd.push({
- uri,
- key: summaryTrackKey,
- trackSortKey: hasSched
- ? PrimaryTrackSortKey.PROCESS_SCHEDULING_TRACK
- : PrimaryTrackSortKey.PROCESS_SUMMARY_TRACK,
- name: `${upid === null ? tid : pid} summary`,
- labels: it.chromeProcessLabels.split(','),
- });
+ const hasHeapInfo = !!it.hasHeapInfo;
- const name = getTrackName({
- utid,
- processName,
- pid,
- threadName,
- tid,
- upid,
- });
- const addTrackGroup = Actions.addTrackGroup({
- summaryTrackKey,
- name,
- id: pUuid,
- // Perf profiling tracks remain collapsed, otherwise we would have too
- // many expanded process tracks for some perf traces, leading to
- // jankyness.
- collapsed: !hasHeapProfiles,
- });
+ const summaryTrackKey = uuidv4();
+ const type = hasSched ? 'schedule' : 'summary';
+ const uri = `perfetto.ProcessScheduling#${upid}.${utid}.${type}`;
- this.addTrackGroupActions.push(addTrackGroup);
+ // If previous groupings (e.g. kernel threads) picked up there tracks,
+ // don't try to regroup them.
+ const pUuid =
+ upid === null ? this.utidToUuid.get(utid!) : this.upidToUuid.get(upid);
+ if (pUuid !== undefined) {
+ continue;
}
+
+ this.tracksToAdd.push({
+ uri,
+ key: summaryTrackKey,
+ trackSortKey: hasSched
+ ? PrimaryTrackSortKey.PROCESS_SCHEDULING_TRACK
+ : PrimaryTrackSortKey.PROCESS_SUMMARY_TRACK,
+ name: `${upid === null ? tid : pid} summary`,
+ labels: it.chromeProcessLabels.split(','),
+ });
+
+ const name = getTrackName({
+ utid,
+ processName,
+ pid,
+ threadName,
+ tid,
+ upid,
+ });
+ const addTrackGroup = Actions.addTrackGroup({
+ summaryTrackKey,
+ name,
+ id: this.getOrCreateUuid(utid, upid),
+ // Perf profiling tracks remain collapsed, otherwise we would have too
+ // many expanded process tracks for some perf traces, leading to
+ // jankyness.
+ collapsed: !hasHeapInfo,
+ });
+ this.addTrackGroupActions.push(addTrackGroup);
}
}
diff --git a/ui/src/core/colorizer.ts b/ui/src/core/colorizer.ts
index 3850e1ea1..3f0d1b369 100644
--- a/ui/src/core/colorizer.ts
+++ b/ui/src/core/colorizer.ts
@@ -182,6 +182,8 @@ export function colorForState(state: string): ColorScheme {
return DESAT_RED;
}
return ORANGE;
+ } else if (state.includes('Dead')) {
+ return GRAY;
} else if (state.includes('Sleeping') || state.includes('Idle')) {
return TRANSPARENT_WHITE;
}
diff --git a/ui/src/core/default_plugins.ts b/ui/src/core/default_plugins.ts
index 583e6bc32..a9f8395fd 100644
--- a/ui/src/core/default_plugins.ts
+++ b/ui/src/core/default_plugins.ts
@@ -32,6 +32,7 @@ export const defaultPlugins = [
'dev.perfetto.BookmarkletApi',
'dev.perfetto.CoreCommands',
'dev.perfetto.LargeScreensPerf',
+ 'dev.perfetto.RestorePinnedTrack',
'perfetto.AndroidLog',
'perfetto.Annotation',
'perfetto.AsyncSlices',
@@ -48,7 +49,6 @@ export const defaultPlugins = [
'perfetto.Frames',
'perfetto.FtraceRaw',
'perfetto.HeapProfile',
- 'perfetto.NullTrack',
'perfetto.PerfSamplesProfile',
'perfetto.PivotTable',
'perfetto.ProcessSummary',
diff --git a/ui/src/core/timeline_cache.ts b/ui/src/core/timeline_cache.ts
index 30d1cd3b4..a3ce96a85 100644
--- a/ui/src/core/timeline_cache.ts
+++ b/ui/src/core/timeline_cache.ts
@@ -13,7 +13,6 @@
// limitations under the License.
import {BigintMath} from '../base/bigint_math';
-import {assertTrue} from '../base/logging';
import {duration, time, Time} from '../base/time';
export const BUCKETS_PER_PIXEL = 2;
@@ -63,7 +62,12 @@ export class CacheKey {
static create(startNs: time, endNs: time, windowSizePx: number): CacheKey {
const bucketNs =
(endNs - startNs) / BigInt(Math.round(windowSizePx * BUCKETS_PER_PIXEL));
- return new CacheKey(startNs, endNs, bucketNs, windowSizePx);
+ return new CacheKey(
+ startNs,
+ endNs,
+ BigintMath.max(1n, bucketNs),
+ windowSizePx,
+ );
}
private constructor(
@@ -123,63 +127,3 @@ export class CacheKey {
return `CacheKey<${start}, ${end}, ${bucket}, ${size}>`;
}
}
-
-interface CacheItem<T> {
- t: T;
- lastAccessId: number;
-}
-
-// LRU cache for the timeline.
-// T is all the data needed for a displaying the track in a given
-// CacheKey area - generally an array of slices.
-export class TimelineCache<T> {
- private cacheSize: number;
- private cache: Map<string, CacheItem<T>>;
- private lastAccessId: number;
-
- constructor(cacheSize: number) {
- assertTrue(cacheSize >= 2);
- this.cacheSize = cacheSize;
- this.cache = new Map();
- this.lastAccessId = 0;
- }
-
- invalidate() {
- this.cache.clear();
- }
-
- insert(cacheKey: CacheKey, t: T): void {
- assertTrue(cacheKey.isNormalized());
- const key = cacheKey.toString();
- this.cache.set(key, {
- t,
- lastAccessId: this.lastAccessId++,
- });
- this.updateLru();
- }
-
- lookup(cacheKey: CacheKey): undefined | T {
- assertTrue(cacheKey.isNormalized());
- const key = cacheKey.toString();
- const item = this.cache.get(key);
- if (item) {
- item.lastAccessId = this.lastAccessId++;
- this.updateLru();
- }
- return item === undefined ? undefined : item.t;
- }
-
- private updateLru(): void {
- while (this.cache.size > this.cacheSize) {
- let oldestKey = '';
- let oldestAccessId = Number.MAX_SAFE_INTEGER;
- for (const [k, v] of this.cache.entries()) {
- if (v.lastAccessId < oldestAccessId) {
- oldestAccessId = v.lastAccessId;
- oldestKey = k;
- }
- }
- this.cache.delete(oldestKey);
- }
- }
-}
diff --git a/ui/src/core/timeline_cache_unittest.ts b/ui/src/core/timeline_cache_unittest.ts
index 227c19d8c..c41fb6443 100644
--- a/ui/src/core/timeline_cache_unittest.ts
+++ b/ui/src/core/timeline_cache_unittest.ts
@@ -14,7 +14,7 @@
import {Time} from '../base/time';
-import {CacheKey, TimelineCache} from './timeline_cache';
+import {CacheKey} from './timeline_cache';
test('cacheKeys', () => {
const k = CacheKey.create(Time.fromRaw(201n), Time.fromRaw(302n), 123);
@@ -29,62 +29,3 @@ test('cacheKeys', () => {
expect(n.bucketSize).toBeGreaterThanOrEqual(k.bucketSize);
expect(Math.abs(n.windowSizePx - k.windowSizePx)).toBeLessThanOrEqual(200);
});
-
-test('cache', () => {
- const key1 = CacheKey.create(
- Time.fromRaw(1000n),
- Time.fromRaw(1100n),
- 100,
- ).normalize();
- const key2 = CacheKey.create(
- Time.fromRaw(2000n),
- Time.fromRaw(2100n),
- 100,
- ).normalize();
- const key3 = CacheKey.create(
- Time.fromRaw(3000n),
- Time.fromRaw(3100n),
- 100,
- ).normalize();
- const key4 = CacheKey.create(
- Time.fromRaw(4000n),
- Time.fromRaw(4100n),
- 100,
- ).normalize();
- const key5 = CacheKey.create(
- Time.fromRaw(5000n),
- Time.fromRaw(5100n),
- 100,
- ).normalize();
- const key6 = CacheKey.create(
- Time.fromRaw(6000n),
- Time.fromRaw(6100n),
- 100,
- ).normalize();
- const key7 = CacheKey.create(
- Time.fromRaw(7000n),
- Time.fromRaw(7100n),
- 100,
- ).normalize();
- const cache = new TimelineCache<string>(5);
-
- cache.insert(key1, 'v1');
- expect(cache.lookup(key1)).toEqual('v1');
-
- cache.insert(key2, 'v2');
- cache.insert(key3, 'v3');
- cache.insert(key4, 'v4');
- cache.insert(key5, 'v5');
-
- // Should push key1/v1 out of the cache:
- cache.insert(key6, 'v6');
- expect(cache.lookup(key1)).toEqual(undefined);
-
- // Access key2 then add one more entry:
- expect(cache.lookup(key2)).toEqual('v2');
- cache.insert(key7, 'v7');
-
- // key2/v2 should still be present but key3/v3 should be discarded:
- expect(cache.lookup(key2)).toEqual('v2');
- expect(cache.lookup(key3)).toEqual(undefined);
-});
diff --git a/ui/src/frontend/app.ts b/ui/src/frontend/app.ts
index 0f11c5972..dc65d19d0 100644
--- a/ui/src/frontend/app.ts
+++ b/ui/src/frontend/app.ts
@@ -414,7 +414,7 @@ export class App implements m.ClassComponent {
},
{
id: 'perfetto.ShowSliceTable',
- name: 'Show slice table',
+ name: 'Open new slice table tab',
callback: () => {
addSqlTableTab({
table: SqlTables.slice,
@@ -452,13 +452,13 @@ export class App implements m.ClassComponent {
},
{
id: 'perfetto.OpenCommandPalette',
- name: 'Open Command Palette',
+ name: 'Open command palette',
callback: () => this.enterCommandMode(),
defaultHotkey: '!Mod+Shift+P',
},
{
id: 'perfetto.RunQuery',
- name: 'Run Query',
+ name: 'Run query',
callback: () => this.enterQueryMode(),
defaultHotkey: '!Mod+O',
},
@@ -545,7 +545,7 @@ export class App implements m.ClassComponent {
},
{
id: 'perfetto.FocusSelection',
- name: 'Focus selection',
+ name: 'Focus current selection',
callback: () => findCurrentSelection(),
defaultHotkey: 'F',
},
diff --git a/ui/src/frontend/base_counter_track.ts b/ui/src/frontend/base_counter_track.ts
index 999678265..91b0939ec 100644
--- a/ui/src/frontend/base_counter_track.ts
+++ b/ui/src/frontend/base_counter_track.ts
@@ -17,7 +17,7 @@ import m from 'mithril';
import {searchSegment} from '../base/binary_search';
import {Disposable, NullDisposable} from '../base/disposable';
import {assertTrue, assertUnreachable} from '../base/logging';
-import {duration, Time, time} from '../base/time';
+import {Time, time} from '../base/time';
import {drawTrackHoverTooltip} from '../common/canvas_utils';
import {raf} from '../core/raf_scheduler';
import {EngineProxy, LONG, NUM, Track} from '../public';
@@ -27,10 +27,10 @@ import {MenuItem, MenuDivider, PopupMenu2} from '../widgets/menu';
import {checkerboardExcept} from './checkerboard';
import {globals} from './globals';
import {PanelSize} from './panel';
-import {constraintsToQuerySuffix} from './sql_utils';
import {NewTrackArgs} from './track';
-import {CacheKey, TimelineCache} from '../core/timeline_cache';
+import {CacheKey} from '../core/timeline_cache';
import {featureFlags} from '../core/feature_flags';
+import {uuidv4Sql} from '../base/uuid';
export const COUNTER_DEBUG_MENU_ITEMS = featureFlags.register({
id: 'counterDebugMenuItems',
@@ -128,8 +128,6 @@ class RangeSharer {
interface CounterData {
timestamps: BigInt64Array;
- counts: Uint32Array;
- avgValues: Float64Array;
minDisplayValues: Float64Array;
maxDisplayValues: Float64Array;
lastDisplayValues: Float64Array;
@@ -142,13 +140,10 @@ const MARGIN_TOP = 3.5;
interface CounterLimits {
maxDisplayValue: number;
minDisplayValue: number;
- maxDurNs: duration;
}
interface CounterTooltipState {
lastDisplayValue: number;
- avgValue: number;
- count: number;
ts: time;
tsEnd?: time;
}
@@ -201,22 +196,19 @@ export type BaseCounterTrackArgs = NewTrackArgs & {
export abstract class BaseCounterTrack implements Track {
protected engine: EngineProxy;
protected trackKey: string;
+ protected trackUuid = uuidv4Sql();
// This is the over-skirted cached bounds:
private countersKey: CacheKey = CacheKey.zero();
private counters: CounterData = {
timestamps: new BigInt64Array(0),
- counts: new Uint32Array(0),
- avgValues: new Float64Array(0),
minDisplayValues: new Float64Array(0),
maxDisplayValues: new Float64Array(0),
lastDisplayValues: new Float64Array(0),
displayValueRange: [0, 0],
};
- private cache: TimelineCache<CounterData> = new TimelineCache(5);
-
// Cleanup hook for onInit.
private initState?: Disposable;
@@ -427,12 +419,9 @@ export abstract class BaseCounterTrack implements Track {
protected invalidate() {
this.limits = undefined;
- this.cache.invalidate();
this.countersKey = CacheKey.zero();
this.counters = {
timestamps: new BigInt64Array(0),
- counts: new Uint32Array(0),
- avgValues: new Float64Array(0),
minDisplayValues: new Float64Array(0),
maxDisplayValues: new Float64Array(0),
lastDisplayValues: new Float64Array(0),
@@ -462,6 +451,33 @@ export abstract class BaseCounterTrack implements Track {
async onCreate(): Promise<void> {
this.initState = await this.onInit();
+
+ const displayValueQuery = await this.engine.query(`
+ create virtual table ${this.getTableName()}
+ using __intrinsic_counter_mipmap((
+ SELECT
+ ts,
+ ${this.getValueExpression()} as value
+ FROM (${this.getSqlSource()})
+ ));
+
+ select
+ min_value as minDisplayValue,
+ max_value as maxDisplayValue
+ from ${this.getTableName()}(
+ trace_start(), trace_end(), trace_dur()
+ );
+ `);
+
+ const {minDisplayValue, maxDisplayValue} = displayValueQuery.firstRow({
+ minDisplayValue: NUM,
+ maxDisplayValue: NUM,
+ });
+
+ this.limits = {
+ minDisplayValue,
+ maxDisplayValue,
+ };
}
async onUpdate(): Promise<void> {
@@ -479,10 +495,7 @@ export abstract class BaseCounterTrack implements Track {
}
render(ctx: CanvasRenderingContext2D, size: PanelSize) {
- const {
- visibleTimeScale: timeScale,
- // visibleWindowTime: vizTime,
- } = globals.timeline;
+ const {visibleTimeScale: timeScale} = globals.timeline;
// In any case, draw whatever we have (which might be stale/incomplete).
@@ -490,11 +503,17 @@ export abstract class BaseCounterTrack implements Track {
const data = this.counters;
if (data.timestamps.length === 0 || limits === undefined) {
+ checkerboardExcept(
+ ctx,
+ this.getHeight(),
+ 0,
+ size.width,
+ timeScale.timeToPx(this.countersKey.start),
+ timeScale.timeToPx(this.countersKey.end),
+ );
return;
}
- assertTrue(data.timestamps.length === data.counts.length);
- assertTrue(data.timestamps.length === data.avgValues.length);
assertTrue(data.timestamps.length === data.minDisplayValues.length);
assertTrue(data.timestamps.length === data.maxDisplayValues.length);
assertTrue(data.timestamps.length === data.lastDisplayValues.length);
@@ -520,6 +539,7 @@ export abstract class BaseCounterTrack implements Track {
zeroY = effectiveHeight * (yMax / (yMax - yMin)) + MARGIN_TOP;
}
+ // Use hue to differentiate the scale of the counter value
const exp = Math.ceil(Math.log10(Math.max(yMax, 1)));
const expCapped = Math.min(exp - 3, 9);
const hue = (180 - Math.floor(expCapped * (180 / 6)) + 360) % 360;
@@ -540,11 +560,11 @@ export abstract class BaseCounterTrack implements Track {
ctx.beginPath();
const timestamp = Time.fromRaw(timestamps[0]);
- ctx.moveTo(calculateX(timestamp), zeroY);
+ ctx.moveTo(Math.max(0, calculateX(timestamp)), zeroY);
let lastDrawnY = zeroY;
for (let i = 0; i < timestamps.length; i++) {
const timestamp = Time.fromRaw(timestamps[i]);
- const x = calculateX(timestamp);
+ const x = Math.max(0, calculateX(timestamp));
const minY = calculateY(minValues[i]);
const maxY = calculateY(maxValues[i]);
const lastY = calculateY(lastValues[i]);
@@ -581,7 +601,7 @@ export abstract class BaseCounterTrack implements Track {
const hover = this.hover;
if (hover !== undefined) {
- let text = `${hover.avgValue.toLocaleString()}`;
+ let text = `${hover.lastDisplayValue.toLocaleString()}`;
const unit = this.unit;
switch (options.yMode) {
@@ -599,14 +619,11 @@ export abstract class BaseCounterTrack implements Track {
break;
}
- if (hover.count > 1) {
- text += ` (avg of ${hover.count})`;
- }
-
ctx.fillStyle = `hsl(${hue}, 45%, 75%)`;
ctx.strokeStyle = `hsl(${hue}, 45%, 45%)`;
- const xStart = Math.floor(timeScale.timeToPx(hover.ts));
+ const rawXStart = calculateX(hover.ts);
+ const xStart = Math.max(0, rawXStart);
const xEnd =
hover.tsEnd === undefined
? endPx
@@ -626,17 +643,19 @@ export abstract class BaseCounterTrack implements Track {
ctx.stroke();
ctx.lineWidth = 1;
- // Draw change marker.
- ctx.beginPath();
- ctx.arc(
- xStart,
- y,
- 3 /* r*/,
- 0 /* start angle*/,
- 2 * Math.PI /* end angle*/,
- );
- ctx.fill();
- ctx.stroke();
+ // Draw change marker if it would be visible.
+ if (rawXStart >= -6) {
+ ctx.beginPath();
+ ctx.arc(
+ xStart,
+ y,
+ 3 /* r*/,
+ 0 /* start angle*/,
+ 2 * Math.PI /* end angle*/,
+ );
+ ctx.fill();
+ ctx.stroke();
+ }
// Draw the tooltip.
drawTrackHoverTooltip(ctx, this.mousePos, this.getHeight(), text);
@@ -644,11 +663,11 @@ export abstract class BaseCounterTrack implements Track {
// Write the Y scale on the top left corner.
ctx.fillStyle = 'rgba(255, 255, 255, 0.6)';
- ctx.fillRect(0, 0, 42, 16);
+ ctx.fillRect(0, 0, 42, 13);
ctx.fillStyle = '#666';
ctx.textAlign = 'left';
ctx.textBaseline = 'alphabetic';
- ctx.fillText(`${yLabel}`, 5, 14);
+ ctx.fillText(`${yLabel}`, 5, 11);
// TODO(hjd): Refactor this into checkerboardExcept
{
@@ -690,14 +709,10 @@ export abstract class BaseCounterTrack implements Track {
const tsEnd =
right === -1 ? undefined : Time.fromRaw(data.timestamps[right]);
const lastDisplayValue = data.lastDisplayValues[left];
- const count = data.counts[left];
- const avgValue = data.avgValues[left];
this.hover = {
ts,
tsEnd,
lastDisplayValue,
- count,
- avgValue,
};
}
@@ -705,11 +720,14 @@ export abstract class BaseCounterTrack implements Track {
this.hover = undefined;
}
- onDestroy(): void {
+ async onDestroy(): Promise<void> {
if (this.initState) {
this.initState.dispose();
this.initState = undefined;
}
+ if (this.engine.isAlive) {
+ await this.engine.query(`drop table if exists ${this.getTableName()}`);
+ }
}
// Compute the range of values to display and range label.
@@ -784,7 +802,6 @@ export abstract class BaseCounterTrack implements Track {
break;
default:
assertUnreachable(options.yMode);
- break;
}
if (options.yDisplay === 'log') {
@@ -800,11 +817,10 @@ export abstract class BaseCounterTrack implements Track {
}
// The underlying table has `ts` and `value` columns.
- private getSqlPreamble(): string {
+ private getValueExpression(): string {
const options = this.getCounterOptions();
let valueExpr;
-
switch (options.yMode) {
case 'value':
valueExpr = 'value';
@@ -818,64 +834,20 @@ export abstract class BaseCounterTrack implements Track {
break;
default:
assertUnreachable(options.yMode);
- break;
}
- let displayValueExpr = valueExpr;
if (options.yDisplay === 'log') {
- displayValueExpr = `ifnull(ln(${displayValueExpr}), 0)`;
+ return `ifnull(ln(${valueExpr}), 0)`;
+ } else {
+ return valueExpr;
}
+ }
- return `
- WITH data AS (
- SELECT
- ts,
- ${valueExpr} as value,
- ${displayValueExpr} as displayValue
- FROM (${this.getSqlSource()})
- )
- `;
+ private getTableName(): string {
+ return `counter_${this.trackUuid}`;
}
private async maybeRequestData(rawCountersKey: CacheKey) {
- let limits = this.limits;
- if (limits === undefined) {
- const maxDurQuery = await this.engine.query(`
- ${this.getSqlPreamble()}
- SELECT
- max(dur) as maxDur
- FROM (
- SELECT
- lead(ts, 1, ts) over (order by ts) - ts as dur
- FROM data
- )
- `);
- const maxDurRow = maxDurQuery.firstRow({
- maxDur: LONG,
- });
- const maxDurNs = maxDurRow.maxDur;
-
- const displayValueQuery = await this.engine.query(`
- ${this.getSqlPreamble()}
- SELECT
- max(displayValue) as maxDisplayValue,
- min(displayValue) as minDisplayValue
- FROM data
- `);
- const displayValueRow = displayValueQuery.firstRow({
- minDisplayValue: NUM,
- maxDisplayValue: NUM,
- });
-
- const minDisplayValue = displayValueRow.minDisplayValue;
- const maxDisplayValue = displayValueRow.maxDisplayValue;
- limits = this.limits = {
- minDisplayValue,
- maxDisplayValue,
- maxDurNs,
- };
- }
-
if (rawCountersKey.isCoveredBy(this.countersKey)) {
return; // We have the data already, no need to re-query.
}
@@ -887,42 +859,21 @@ export abstract class BaseCounterTrack implements Track {
);
}
- const maybeCachedCounters = this.cache.lookup(countersKey);
- if (maybeCachedCounters) {
- this.countersKey = countersKey;
- this.counters = maybeCachedCounters;
- return;
- }
-
- const bucketNs = countersKey.bucketSize;
-
- const constraint = constraintsToQuerySuffix({
- filters: [
- `ts >= ${countersKey.start} - ${limits.maxDurNs}`,
- `ts <= ${countersKey.end}`,
- `value is not null`,
- ],
- groupBy: ['tsq'],
- orderBy: ['tsq'],
- });
-
const queryRes = await this.engine.query(`
- ${this.getSqlPreamble()}
SELECT
- (ts + ${bucketNs / 2n}) / ${bucketNs} * ${bucketNs} as tsq,
- count(value) as count,
- avg(value) as avgValue,
- min(displayValue) as minDisplayValue,
- max(displayValue) as maxDisplayValue,
- value_at_max_ts(ts, displayValue) as lastDisplayValue
- FROM data
- ${constraint}
+ min_value as minDisplayValue,
+ max_value as maxDisplayValue,
+ last_ts as ts,
+ last_value as lastDisplayValue
+ FROM ${this.getTableName()}(
+ ${countersKey.start},
+ ${countersKey.end},
+ ${countersKey.bucketSize}
+ );
`);
const it = queryRes.iter({
- tsq: LONG,
- count: NUM,
- avgValue: NUM,
+ ts: LONG,
minDisplayValue: NUM,
maxDisplayValue: NUM,
lastDisplayValue: NUM,
@@ -931,8 +882,6 @@ export abstract class BaseCounterTrack implements Track {
const numRows = queryRes.numRows();
const data: CounterData = {
timestamps: new BigInt64Array(numRows),
- counts: new Uint32Array(numRows),
- avgValues: new Float64Array(numRows),
minDisplayValues: new Float64Array(numRows),
maxDisplayValues: new Float64Array(numRows),
lastDisplayValues: new Float64Array(numRows),
@@ -942,10 +891,7 @@ export abstract class BaseCounterTrack implements Track {
let min = 0;
let max = 0;
for (let row = 0; it.valid(); it.next(), row++) {
- const ts = Time.fromRaw(it.tsq);
- data.timestamps[row] = ts;
- data.counts[row] = it.count;
- data.avgValues[row] = it.avgValue;
+ data.timestamps[row] = Time.fromRaw(it.ts);
data.minDisplayValues[row] = it.minDisplayValue;
data.maxDisplayValues[row] = it.maxDisplayValue;
data.lastDisplayValues[row] = it.lastDisplayValue;
@@ -955,7 +901,6 @@ export abstract class BaseCounterTrack implements Track {
data.displayValueRange = [min, max];
- this.cache.insert(countersKey, data);
this.countersKey = countersKey;
this.counters = data;
diff --git a/ui/src/frontend/base_slice_track.ts b/ui/src/frontend/base_slice_track.ts
index ef3b60bf2..329e44179 100644
--- a/ui/src/frontend/base_slice_track.ts
+++ b/ui/src/frontend/base_slice_track.ts
@@ -15,7 +15,7 @@
import {Disposable, NullDisposable} from '../base/disposable';
import {assertExists} from '../base/logging';
import {clamp, floatEqual} from '../base/math_utils';
-import {duration, Time, time} from '../base/time';
+import {Time, time} from '../base/time';
import {exists} from '../base/utils';
import {Actions} from '../common/actions';
import {
@@ -39,13 +39,9 @@ import {checkerboardExcept} from './checkerboard';
import {globals} from './globals';
import {PanelSize} from './panel';
import {DEFAULT_SLICE_LAYOUT, SliceLayout} from './slice_layout';
-import {constraintsToQuerySuffix} from './sql_utils';
import {NewTrackArgs} from './track';
-import {
- BUCKETS_PER_PIXEL,
- CacheKey,
- TimelineCache,
-} from '../core/timeline_cache';
+import {BUCKETS_PER_PIXEL, CacheKey} from '../core/timeline_cache';
+import {uuidv4Sql} from '../base/uuid';
// The common class that underpins all tracks drawing slices.
@@ -125,13 +121,11 @@ function filterVisibleSlices<S extends Slice>(
// to the right).
// Since the slices are sorted by startS we can check this easily:
const maybeFirstSlice: S | undefined = slices[0];
- if (exists(maybeFirstSlice) && maybeFirstSlice.startNsQ > end) {
+ if (exists(maybeFirstSlice) && maybeFirstSlice.startNs > end) {
return [];
}
- return slices.filter(
- (slice) => slice.startNsQ <= end && slice.endNsQ >= start,
- );
+ return slices.filter((slice) => slice.startNs <= end && slice.endNs >= start);
}
export const filterVisibleSlicesForTesting = filterVisibleSlices;
@@ -145,13 +139,11 @@ export const filterVisibleSlicesForTesting = filterVisibleSlices;
// merges several tracks into one visual track.
export const BASE_ROW = {
id: NUM, // The slice ID, for selection / lookups.
- ts: LONG, // Start time in nanoseconds.
- dur: LONG, // Duration in nanoseconds. -1 = incomplete, 0 = instant.
+ ts: LONG, // True ts in nanoseconds.
+ dur: LONG, // True duration in nanoseconds. -1 = incomplete, 0 = instant.
+ tsQ: LONG, // Quantized start time in nanoseconds.
+ durQ: LONG, // Quantized duration in nanoseconds.
depth: NUM, // Vertical depth.
-
- // These are computed by the base class:
- tsq: LONG, // Quantized |ts|. This class owns the quantization logic.
- tsqEnd: LONG, // Quantized |ts+dur|. The end bucket.
};
export type BaseRow = typeof BASE_ROW;
@@ -185,6 +177,7 @@ export abstract class BaseSliceTrack<
protected sliceLayout: SliceLayout = {...DEFAULT_SLICE_LAYOUT};
protected engine: EngineProxy;
protected trackKey: string;
+ protected trackUuid = uuidv4Sql();
// This is the over-skirted cached bounds:
private slicesKey: CacheKey = CacheKey.zero();
@@ -192,17 +185,11 @@ export abstract class BaseSliceTrack<
// This is the currently 'cached' slices:
private slices = new Array<CastInternal<T['slice']>>();
- // This is the slices cache:
- private cache: TimelineCache<Array<CastInternal<T['slice']>>> =
- new TimelineCache(5);
-
- private hasOneOffData: boolean = false;
// Incomplete slices (dur = -1). Rather than adding a lot of logic to
// the SQL queries to handle this case we materialise them one off
// then unconditionally render them. This should be efficient since
// there are at most |depth| slices.
private incomplete = new Array<CastInternal<T['slice']>>();
- private maxDurNs: duration = 0n;
// The currently selected slice.
// TODO(hjd): We should fetch this from the underlying data rather
@@ -324,8 +311,71 @@ export abstract class BaseSliceTrack<
return `${size}px Roboto Condensed`;
}
+ private getTableName(): string {
+ return `slice_${this.trackUuid}`;
+ }
+
async onCreate(): Promise<void> {
this.initState = await this.onInit();
+
+ // TODO(hjd): Consider case below:
+ // raw:
+ // 0123456789
+ // [A did not end)
+ // [B ]
+ //
+ //
+ // quantised:
+ // 0123456789
+ // [A did not end)
+ // [ B ]
+ // Does it lead to odd results?
+ const extraCols = this.extraSqlColumns.join(',');
+ let queryRes;
+ if (CROP_INCOMPLETE_SLICE_FLAG.get()) {
+ queryRes = await this.engine.query(`
+ select
+ ${this.depthColumn()},
+ ts as tsQ,
+ ts,
+ -1 as durQ,
+ -1 as dur,
+ id
+ ${extraCols ? ',' + extraCols : ''}
+ from (${this.getSqlSource()})
+ where dur = -1;
+ `);
+ } else {
+ queryRes = await this.engine.query(`
+ select
+ ${this.depthColumn()},
+ max(ts) as tsQ,
+ ts,
+ -1 as durQ,
+ -1 as dur,
+ id
+ ${extraCols ? ',' + extraCols : ''}
+ from (${this.getSqlSource()})
+ where dur = -1
+ group by 1
+ `);
+ }
+ const incomplete = new Array<CastInternal<T['slice']>>(queryRes.numRows());
+ const it = queryRes.iter(this.getRowSpec());
+ for (let i = 0; it.valid(); it.next(), ++i) {
+ incomplete[i] = this.rowToSliceInternal(it);
+ }
+ this.onUpdatedSlices(incomplete);
+ this.incomplete = incomplete;
+
+ await this.engine.query(`
+ create virtual table ${this.getTableName()}
+ using __intrinsic_slice_mipmap((
+ select id, ts, dur, ${this.depthColumn()}
+ from (${this.getSqlSource()})
+ where dur != -1
+ ));
+ `);
}
async onUpdate(): Promise<void> {
@@ -394,8 +444,8 @@ export abstract class BaseSliceTrack<
// partially visible. This might end up with a negative x if the
// slice starts before the visible time or with a width that overflows
// pxEnd.
- slice.x = timeScale.timeToPx(slice.startNsQ);
- slice.w = timeScale.durationToPx(slice.durNsQ);
+ slice.x = timeScale.timeToPx(slice.startNs);
+ slice.w = timeScale.durationToPx(slice.durNs);
if (slice.flags & SLICE_FLAGS_INSTANT) {
// In the case of an instant slice, set the slice geometry on the
@@ -611,82 +661,20 @@ export abstract class BaseSliceTrack<
} // if (hoveredSlice)
}
- onDestroy() {
+ async onDestroy(): Promise<void> {
if (this.initState) {
this.initState.dispose();
this.initState = undefined;
}
+ if (this.engine.isAlive) {
+ await this.engine.execute(`drop table ${this.getTableName()}`);
+ }
}
// This method figures out if the visible window is outside the bounds of
// the cached data and if so issues new queries (i.e. sorta subsumes the
// onBoundsChange).
private async maybeRequestData(rawSlicesKey: CacheKey) {
- if (!this.hasOneOffData) {
- // TODO(hjd): This could be done in onInit maybe?
- const queryRes = await this.engine.query(`select
- ifnull(max(dur), 0) as maxDur, count(1) as rowCount
- from (${this.getSqlSource()})`);
- const row = queryRes.firstRow({maxDur: LONG, rowCount: NUM});
- this.maxDurNs = row.maxDur;
-
- {
- // TODO(hjd): Consider case below:
- // raw:
- // 0123456789
- // [A did not end)
- // [B ]
- //
- //
- // quantised:
- // 0123456789
- // [A did not end)
- // [ B ]
- // Does it lead to odd results?
- const extraCols = this.extraSqlColumns.join(',');
- let queryRes;
- if (CROP_INCOMPLETE_SLICE_FLAG.get()) {
- queryRes = await this.engine.query(`
- select
- ${this.depthColumn()},
- ts as tsq,
- ts as tsqEnd,
- ts,
- -1 as dur,
- id
- ${extraCols ? ',' + extraCols : ''}
- from (${this.getSqlSource()})
- where dur = -1;
- `);
- } else {
- queryRes = await this.engine.query(`
- select
- ${this.depthColumn()},
- max(ts) as tsq,
- max(ts) as tsqEnd,
- max(ts) as ts,
- -1 as dur,
- id
- ${extraCols ? ',' + extraCols : ''}
- from (${this.getSqlSource()})
- group by 1
- having dur = -1;
- `);
- }
- const incomplete = new Array<CastInternal<T['slice']>>(
- queryRes.numRows(),
- );
- const it = queryRes.iter(this.getRowSpec());
- for (let i = 0; it.valid(); it.next(), ++i) {
- incomplete[i] = this.rowToSliceInternal(it);
- }
- this.onUpdatedSlices(incomplete);
- this.incomplete = incomplete;
- }
-
- this.hasOneOffData = true;
- }
-
if (rawSlicesKey.isCoveredBy(this.slicesKey)) {
return; // We have the data already, no need to re-query
}
@@ -699,52 +687,22 @@ export abstract class BaseSliceTrack<
);
}
- const maybeCachedSlices = this.cache.lookup(slicesKey);
- if (maybeCachedSlices) {
- this.slicesKey = slicesKey;
- this.onUpdatedSlices(maybeCachedSlices);
- this.slices = maybeCachedSlices;
- return;
- }
-
- const bucketNs = slicesKey.bucketSize;
- let queryTsq;
- let queryTsqEnd;
- // When we're zoomed into the level of single ns there is no point
- // doing quantization (indeed it causes bad artifacts) so instead
- // we use ts / ts+dur directly.
- if (bucketNs === 1n) {
- queryTsq = 'ts';
- queryTsqEnd = 'ts + dur';
- } else {
- queryTsq = `(ts + ${bucketNs / 2n}) / ${bucketNs} * ${bucketNs}`;
- queryTsqEnd = `(ts + dur + ${bucketNs / 2n}) / ${bucketNs} * ${bucketNs}`;
- }
-
const extraCols = this.extraSqlColumns.join(',');
- const maybeDepth = this.isFlat() ? undefined : 'depth';
-
- const constraint = constraintsToQuerySuffix({
- filters: [
- `ts >= ${slicesKey.start - this.maxDurNs}`,
- `ts <= ${slicesKey.end}`,
- ],
- groupBy: [maybeDepth, 'tsq'],
- orderBy: [maybeDepth, 'tsq'],
- });
-
- // TODO(hjd): Count and expose the number of slices summarized in
- // each bucket?
const queryRes = await this.engine.query(`
SELECT
- ${queryTsq} AS tsq,
- ${queryTsqEnd} AS tsqEnd,
- ts,
- MAX(dur) AS dur,
- id,
- ${this.depthColumn()}
+ (z.ts / ${rawSlicesKey.bucketSize}) * ${rawSlicesKey.bucketSize} as tsQ,
+ max(z.dur, ${rawSlicesKey.bucketSize}) as durQ,
+ s.ts as ts,
+ s.dur as dur,
+ s.id,
+ z.depth
${extraCols ? ',' + extraCols : ''}
- FROM (${this.getSqlSource()}) ${constraint}
+ FROM ${this.getTableName()}(
+ ${slicesKey.start},
+ ${slicesKey.end},
+ ${slicesKey.bucketSize}
+ ) z
+ CROSS JOIN (${this.getSqlSource()}) s using (id)
`);
// Here convert each row to a Slice. We do what we can do
@@ -768,7 +726,6 @@ export abstract class BaseSliceTrack<
}
this.maxDataDepth = maxDataDepth;
this.onUpdatedSlices(slices);
- this.cache.insert(slicesKey, slices);
this.slices = slices;
raf.scheduleRedraw();
@@ -789,11 +746,6 @@ export abstract class BaseSliceTrack<
}
rowToSlice(row: T['row']): T['slice'] {
- const startNsQ = Time.fromRaw(row.tsq);
- const endNsQ = Time.fromRaw(row.tsqEnd);
- const ts = Time.fromRaw(row.ts);
- const dur: duration = row.dur;
-
let flags = 0;
if (row.dur === -1n) {
flags |= SLICE_FLAGS_INCOMPLETE;
@@ -803,11 +755,11 @@ export abstract class BaseSliceTrack<
return {
id: row.id,
- startNsQ,
- endNsQ,
- durNsQ: endNsQ - startNsQ,
- ts,
- dur,
+ startNs: Time.fromRaw(row.tsQ),
+ endNs: Time.fromRaw(row.tsQ + row.durQ),
+ durNs: row.durQ,
+ ts: Time.fromRaw(row.ts),
+ dur: row.dur,
flags,
depth: row.depth,
title: '',
@@ -846,7 +798,7 @@ export abstract class BaseSliceTrack<
for (const slice of this.incomplete) {
const visibleTimeScale = globals.timeline.visibleTimeScale;
const startPx = CROP_INCOMPLETE_SLICE_FLAG.get()
- ? visibleTimeScale.timeToPx(slice.startNsQ)
+ ? visibleTimeScale.timeToPx(slice.startNs)
: slice.x;
const cropUnfinishedSlicesCondition = CROP_INCOMPLETE_SLICE_FLAG.get()
? startPx + INCOMPLETE_SLICE_WIDTH_PX >= x
diff --git a/ui/src/frontend/base_slice_track_unittest.ts b/ui/src/frontend/base_slice_track_unittest.ts
index 196cc2ea5..4e8fd5182 100644
--- a/ui/src/frontend/base_slice_track_unittest.ts
+++ b/ui/src/frontend/base_slice_track_unittest.ts
@@ -19,16 +19,16 @@ import {Slice} from '../public';
import {filterVisibleSlicesForTesting as filterVisibleSlices} from './base_slice_track';
function slice(start: number, duration: number, depth: number = 0): Slice {
- const startNsQ = Time.fromRaw(BigInt(start));
- const durNsQ = Time.fromRaw(BigInt(duration));
- const endNsQ = Time.fromRaw(startNsQ + durNsQ);
+ const startNs = Time.fromRaw(BigInt(start));
+ const durNs = Time.fromRaw(BigInt(duration));
+ const endNs = Time.fromRaw(startNs + durNs);
return {
id: 42,
- startNsQ,
- endNsQ,
- durNsQ,
- ts: startNsQ,
- dur: durNsQ,
+ startNs,
+ endNs,
+ durNs,
+ ts: startNs,
+ dur: durNs,
depth,
flags: 0,
title: '',
diff --git a/ui/src/frontend/css_constants.ts b/ui/src/frontend/css_constants.ts
index 1a37c1e7f..f75613633 100644
--- a/ui/src/frontend/css_constants.ts
+++ b/ui/src/frontend/css_constants.ts
@@ -23,7 +23,6 @@ export let SELECTION_STROKE_COLOR = '#00344596';
export let SELECTION_FILL_COLOR = '#8398e64d';
export let OVERVIEW_TIMELINE_NON_VISIBLE_COLOR = '#c8c8c8cc';
export let DEFAULT_DETAILS_CONTENT_HEIGHT = 280;
-export const SELECTED_LOG_ROWS_COLOR = '#D2EFE0';
export let BACKGROUND_COLOR = '#ffffff';
export let FOREGROUND_COLOR = '#222';
export let COLLAPSED_BACKGROUND = '#ffffff';
diff --git a/ui/src/frontend/error_dialog.ts b/ui/src/frontend/error_dialog.ts
index dd9a4a9c6..f2cea3770 100644
--- a/ui/src/frontend/error_dialog.ts
+++ b/ui/src/frontend/error_dialog.ts
@@ -36,6 +36,8 @@ export function maybeShowErrorDialog(err: ErrorDetails) {
// Here we rely on the exception message from onCannotGrowMemory function
if (
err.message.includes('Cannot enlarge memory') ||
+ err.stack.some((entry) => entry.name.includes('_emscripten_resize_heap')) ||
+ err.stack.some((entry) => entry.name.includes('sbrk')) ||
/^out of memory$/m.exec(err.message)
) {
showOutOfMemoryDialog();
diff --git a/ui/src/frontend/globals.ts b/ui/src/frontend/globals.ts
index 8fc392d9e..68e16a559 100644
--- a/ui/src/frontend/globals.ts
+++ b/ui/src/frontend/globals.ts
@@ -276,8 +276,8 @@ class Globals {
private _publishRedraw?: () => void = undefined;
private _currentSearchResults: CurrentSearchResults = {
- sliceIds: new Float64Array(0),
- tsStarts: new BigInt64Array(0),
+ eventIds: new Float64Array(0),
+ tses: new BigInt64Array(0),
utids: new Float64Array(0),
trackKeys: [],
sources: [],
@@ -648,8 +648,8 @@ class Globals {
this._numQueriesQueued = 0;
this._metricResult = undefined;
this._currentSearchResults = {
- sliceIds: new Float64Array(0),
- tsStarts: new BigInt64Array(0),
+ eventIds: new Float64Array(0),
+ tses: new BigInt64Array(0),
utids: new Float64Array(0),
trackKeys: [],
sources: [],
diff --git a/ui/src/frontend/pivot_table_argument_popup.ts b/ui/src/frontend/pivot_table_argument_popup.ts
index faa15796f..949200ee3 100644
--- a/ui/src/frontend/pivot_table_argument_popup.ts
+++ b/ui/src/frontend/pivot_table_argument_popup.ts
@@ -20,21 +20,6 @@ import {raf} from '../core/raf_scheduler';
interface ArgumentPopupArgs {
onArgumentChange: (arg: string) => void;
- knownArguments: string[];
-}
-
-function longestString(array: string[]): string {
- if (array.length === 0) {
- return '';
- }
-
- let answer = array[0];
- for (let i = 1; i < array.length; i++) {
- if (array[i].length > answer.length) {
- answer = array[i];
- }
- }
- return answer;
}
// Component rendering popup for entering an argument name to use as a pivot.
@@ -47,41 +32,6 @@ export class ArgumentPopup implements m.ClassComponent<ArgumentPopupArgs> {
raf.scheduleFullRedraw();
}
- renderMatches(attrs: ArgumentPopupArgs): m.Child[] {
- const result: m.Child[] = [];
-
- for (const option of attrs.knownArguments) {
- // Would be great to have smarter fuzzy matching, but in the meantime
- // simple substring check should work fine.
- const index = option.indexOf(this.argument);
-
- if (index === -1) {
- continue;
- }
-
- if (result.length === 10) {
- break;
- }
-
- result.push(
- m(
- 'div',
- {
- onclick: () => {
- this.setArgument(attrs, option);
- },
- },
- option.substring(0, index),
- // Highlight the matching part with bold font
- m('strong', this.argument),
- option.substring(index + this.argument.length),
- ),
- );
- }
-
- return result;
- }
-
view({attrs}: m.Vnode<ArgumentPopupArgs>): m.Child {
return m(
'.name-completion',
@@ -94,8 +44,6 @@ export class ArgumentPopup implements m.ClassComponent<ArgumentPopupArgs> {
},
value: this.argument,
}),
- m('.arguments-popup-sizer', longestString(attrs.knownArguments)),
- this.renderMatches(attrs),
);
}
}
diff --git a/ui/src/frontend/search_handler.ts b/ui/src/frontend/search_handler.ts
index d9e3f0df2..3fcc1783c 100644
--- a/ui/src/frontend/search_handler.ts
+++ b/ui/src/frontend/search_handler.ts
@@ -13,8 +13,10 @@
// limitations under the License.
import {searchSegment} from '../base/binary_search';
+import {assertUnreachable} from '../base/logging';
import {Actions} from '../common/actions';
import {globals} from './globals';
+import {verticalScrollToTrack} from './scroll_helper';
function setToPrevious(current: number) {
let index = current - 1;
@@ -34,7 +36,7 @@ export function executeSearch(reverse = false) {
const vizWindow = globals.stateVisibleTime();
const startNs = vizWindow.start;
const endNs = vizWindow.end;
- const currentTs = globals.currentSearchResults.tsStarts[index];
+ const currentTs = globals.currentSearchResults.tses[index];
// If the value of |globals.currentSearchResults.totalResults| is 0,
// it means that the query is in progress or no results are found.
@@ -44,12 +46,12 @@ export function executeSearch(reverse = false) {
// If this is a new search or the currentTs is not in the viewport,
// select the first/last item in the viewport.
- if (index === -1 || currentTs < startNs || currentTs > endNs) {
+ if (
+ index === -1 ||
+ (currentTs !== -1n && (currentTs < startNs || currentTs > endNs))
+ ) {
if (reverse) {
- const [smaller] = searchSegment(
- globals.currentSearchResults.tsStarts,
- endNs,
- );
+ const [smaller] = searchSegment(globals.currentSearchResults.tses, endNs);
// If there is no item in the viewport just go to the previous.
if (smaller === -1) {
setToPrevious(index);
@@ -58,7 +60,7 @@ export function executeSearch(reverse = false) {
}
} else {
const [, larger] = searchSegment(
- globals.currentSearchResults.tsStarts,
+ globals.currentSearchResults.tses,
startNs,
);
// If there is no item in the viewport just go to the next.
@@ -82,52 +84,61 @@ export function executeSearch(reverse = false) {
function selectCurrentSearchResult() {
const searchIndex = globals.state.searchIndex;
const source = globals.currentSearchResults.sources[searchIndex];
- const currentId = globals.currentSearchResults.sliceIds[searchIndex];
+ const currentId = globals.currentSearchResults.eventIds[searchIndex];
const trackKey = globals.currentSearchResults.trackKeys[searchIndex];
if (currentId === undefined) return;
- if (source === 'cpu') {
- globals.setLegacySelection(
- {
- kind: 'SLICE',
- id: currentId,
- trackKey,
- },
- {
- clearSearch: false,
- pendingScrollId: currentId,
- switchToCurrentSelectionTab: true,
- },
- );
- } else if (source === 'log') {
- globals.setLegacySelection(
- {
- kind: 'LOG',
- id: currentId,
- trackKey,
- },
- {
- clearSearch: false,
- pendingScrollId: currentId,
- switchToCurrentSelectionTab: true,
- },
- );
- } else {
- // Search results only include slices from the slice table for now.
- // When we include annotations we need to pass the correct table.
- globals.setLegacySelection(
- {
- kind: 'CHROME_SLICE',
- id: currentId,
- trackKey,
- table: 'slice',
- },
- {
- clearSearch: false,
- pendingScrollId: currentId,
- switchToCurrentSelectionTab: true,
- },
- );
+ switch (source) {
+ case 'track':
+ verticalScrollToTrack(trackKey, true);
+ break;
+ case 'cpu':
+ globals.setLegacySelection(
+ {
+ kind: 'SLICE',
+ id: currentId,
+ trackKey,
+ },
+ {
+ clearSearch: false,
+ pendingScrollId: currentId,
+ switchToCurrentSelectionTab: true,
+ },
+ );
+ break;
+ case 'log':
+ globals.setLegacySelection(
+ {
+ kind: 'LOG',
+ id: currentId,
+ trackKey,
+ },
+ {
+ clearSearch: false,
+ pendingScrollId: currentId,
+ switchToCurrentSelectionTab: true,
+ },
+ );
+ break;
+ case 'slice':
+ // Search results only include slices from the slice table for now.
+ // When we include annotations we need to pass the correct table.
+ globals.setLegacySelection(
+ {
+ kind: 'CHROME_SLICE',
+ id: currentId,
+ trackKey,
+ table: 'slice',
+ },
+ {
+ clearSearch: false,
+ pendingScrollId: currentId,
+ switchToCurrentSelectionTab: true,
+ },
+ );
+ break;
+ default:
+ assertUnreachable(source);
}
}
diff --git a/ui/src/frontend/simple_counter_track.ts b/ui/src/frontend/simple_counter_track.ts
index 084c14fd4..361480b2d 100644
--- a/ui/src/frontend/simple_counter_track.ts
+++ b/ui/src/frontend/simple_counter_track.ts
@@ -17,6 +17,7 @@ import {EngineProxy, TrackContext} from '../public';
import {BaseCounterTrack, CounterOptions} from './base_counter_track';
import {CounterColumns, SqlDataSource} from './debug_tracks';
import {Disposable, DisposableCallback} from '../base/disposable';
+import {uuidv4Sql} from '../base/uuid';
export type SimpleCounterTrackConfig = {
data: SqlDataSource;
@@ -39,7 +40,7 @@ export class SimpleCounterTrack extends BaseCounterTrack {
options: config.options,
});
this.config = config;
- this.sqlTableName = `__simple_counter_${this.trackKey}`;
+ this.sqlTableName = `__simple_counter_${uuidv4Sql()}`;
}
async onInit(): Promise<Disposable> {
@@ -74,7 +75,7 @@ export class SimpleCounterTrack extends BaseCounterTrack {
private async dropTrackTable(): Promise<void> {
if (this.engine.isAlive) {
- this.engine.query(`drop table if exists ${this.sqlTableName}`);
+ await this.engine.query(`drop table if exists ${this.sqlTableName}`);
}
}
}
diff --git a/ui/src/frontend/simple_slice_track.ts b/ui/src/frontend/simple_slice_track.ts
index baa907d4a..6d287b341 100644
--- a/ui/src/frontend/simple_slice_track.ts
+++ b/ui/src/frontend/simple_slice_track.ts
@@ -87,13 +87,12 @@ export class SimpleSliceTrack extends CustomSqlTableSliceTrack<NamedSliceTrackTy
// TODO(altimin): Support removing this table when the track is closed.
const dur = sliceColumns.dur === '0' ? 0 : sliceColumns.dur;
await this.engine.query(`
- create table ${this.sqlTableName} as
+ create perfetto table ${this.sqlTableName} as
with data${dataColumns} as (
${data.sqlSource}
),
prepared_data as (
select
- row_number() over () as id,
${sliceColumns.ts} as ts,
ifnull(cast(${dur} as int), -1) as dur,
printf('%s', ${sliceColumns.name}) as name
@@ -102,6 +101,7 @@ export class SimpleSliceTrack extends CustomSqlTableSliceTrack<NamedSliceTrackTy
from data
)
select
+ row_number() over (order by ts) as id,
*
from prepared_data
order by ts;`);
diff --git a/ui/src/frontend/tables/attribute_modal_holder.ts b/ui/src/frontend/tables/attribute_modal_holder.ts
index 562a92296..d154ab1b4 100644
--- a/ui/src/frontend/tables/attribute_modal_holder.ts
+++ b/ui/src/frontend/tables/attribute_modal_holder.ts
@@ -15,7 +15,6 @@
import m from 'mithril';
import {showModal} from '../../widgets/modal';
-import {globals} from '../globals';
import {ArgumentPopup} from '../pivot_table_argument_popup';
export class AttributeModalHolder {
@@ -45,8 +44,6 @@ export class AttributeModalHolder {
private renderModalContents() {
return m(ArgumentPopup, {
- knownArguments:
- globals.state.nonSerializableState.pivotTable.argumentNames,
onArgumentChange: (arg) => {
this.typedArgument = arg;
},
diff --git a/ui/src/frontend/tickmark_panel.ts b/ui/src/frontend/tickmark_panel.ts
index 270372089..90f3e4cbc 100644
--- a/ui/src/frontend/tickmark_panel.ts
+++ b/ui/src/frontend/tickmark_panel.ts
@@ -84,19 +84,21 @@ export class TickmarkPanel implements Panel {
);
}
const index = globals.state.searchIndex;
- if (index !== -1 && index < globals.currentSearchResults.tsStarts.length) {
- const start = globals.currentSearchResults.tsStarts[index];
- const triangleStart =
- Math.max(visibleTimeScale.timeToPx(Time.fromRaw(start)), 0) +
- TRACK_SHELL_WIDTH;
- ctx.fillStyle = '#000';
- ctx.beginPath();
- ctx.moveTo(triangleStart, size.height);
- ctx.lineTo(triangleStart - 3, 0);
- ctx.lineTo(triangleStart + 3, 0);
- ctx.lineTo(triangleStart, size.height);
- ctx.fill();
- ctx.closePath();
+ if (index !== -1 && index < globals.currentSearchResults.tses.length) {
+ const start = globals.currentSearchResults.tses[index];
+ if (start !== -1n) {
+ const triangleStart =
+ Math.max(visibleTimeScale.timeToPx(Time.fromRaw(start)), 0) +
+ TRACK_SHELL_WIDTH;
+ ctx.fillStyle = '#000';
+ ctx.beginPath();
+ ctx.moveTo(triangleStart, size.height);
+ ctx.lineTo(triangleStart - 3, 0);
+ ctx.lineTo(triangleStart + 3, 0);
+ ctx.lineTo(triangleStart, size.height);
+ ctx.fill();
+ ctx.closePath();
+ }
}
ctx.restore();
diff --git a/ui/src/frontend/track_panel.ts b/ui/src/frontend/track_panel.ts
index d2cc66b14..806b49ce4 100644
--- a/ui/src/frontend/track_panel.ts
+++ b/ui/src/frontend/track_panel.ts
@@ -100,6 +100,7 @@ export class CrashButton implements m.ClassComponent<CrashButtonAttrs> {
{
trigger: m(Button, {
icon: Icons.Crashed,
+ compact: true,
}),
},
this.renderErrorMessage(attrs.error),
diff --git a/ui/src/frontend/widgets_page.ts b/ui/src/frontend/widgets_page.ts
index ea80972ff..98fc400d7 100644
--- a/ui/src/frontend/widgets_page.ts
+++ b/ui/src/frontend/widgets_page.ts
@@ -50,6 +50,11 @@ import {PopupMenuButton} from './popup_menu';
import {TableShowcase} from './tables/table_showcase';
import {TreeTable, TreeTableAttrs} from './widgets/treetable';
import {Intent} from '../widgets/common';
+import {
+ VirtualTable,
+ VirtualTableAttrs,
+ VirtualTableRow,
+} from '../widgets/virtual_table';
const DATA_ENGLISH_LETTER_FREQUENCY = {
table: [
@@ -366,6 +371,17 @@ interface WidgetTitleAttrs {
label: string;
}
+function recursiveTreeNode(): m.Children {
+ return m(LazyTreeNode, {
+ left: 'Recursive',
+ right: '...',
+ fetchData: async () => {
+ // await new Promise((r) => setTimeout(r, 1000));
+ return () => recursiveTreeNode();
+ },
+ });
+}
+
class WidgetTitle implements m.ClassComponent<WidgetTitleAttrs> {
view({attrs}: m.CVnode<WidgetTitleAttrs>) {
const {label} = attrs;
@@ -569,6 +585,11 @@ const files: File[] = [
},
];
+let virtualTableData: {offset: number; rows: VirtualTableRow[]} = {
+ offset: 0,
+ rows: [],
+};
+
export const WidgetsPage = createPage({
view() {
return m(
@@ -986,6 +1007,7 @@ export const WidgetsPage = createPage({
return () => m(TreeNode, {left: 'foo'});
},
}),
+ recursiveTreeNode(),
),
wide: true,
}),
@@ -1154,10 +1176,38 @@ export const WidgetsPage = createPage({
return m(TreeTable<File>, attrs);
},
}),
+ m(WidgetShowcase, {
+ label: 'VirtualTable',
+ description: `Virtualized table for efficient rendering of large datasets`,
+ renderWidget: () => {
+ const attrs: VirtualTableAttrs = {
+ columns: [
+ {header: 'x', width: '4em'},
+ {header: 'x^2', width: '8em'},
+ ],
+ rows: virtualTableData.rows,
+ firstRowOffset: virtualTableData.offset,
+ rowHeight: 20,
+ numRows: 500_000,
+ style: {height: '200px'},
+ onReload: (rowOffset, rowCount) => {
+ const rows = [];
+ for (let i = rowOffset; i < rowOffset + rowCount; i++) {
+ rows.push({id: i, cells: [i, i ** 2]});
+ }
+ virtualTableData = {
+ offset: rowOffset,
+ rows,
+ };
+ raf.scheduleFullRedraw();
+ },
+ };
+ return m(VirtualTable, attrs);
+ },
+ }),
);
},
});
-
class ModalShowcase implements m.ClassComponent {
private static counter = 0;
diff --git a/ui/src/plugins/dev.perfetto.AndroidCujs/index.ts b/ui/src/plugins/dev.perfetto.AndroidCujs/index.ts
index 5fb1a1a71..aaef4842d 100644
--- a/ui/src/plugins/dev.perfetto.AndroidCujs/index.ts
+++ b/ui/src/plugins/dev.perfetto.AndroidCujs/index.ts
@@ -19,6 +19,7 @@ import {Plugin, PluginContextTrace, PluginDescriptor} from '../../public';
const JANK_CUJ_QUERY_PRECONDITIONS = `
SELECT RUN_METRIC('android/android_jank_cuj.sql');
SELECT RUN_METRIC('android/jank/internal/counters.sql');
+ INCLUDE PERFETTO MODULE android.critical_blocking_calls;
`;
const JANK_CUJ_QUERY = `
@@ -64,15 +65,17 @@ const JANK_CUJ_QUERY = `
sf_callback_missed_frames,
hwui_callback_missed_frames,
cuj_layer.layer_name,
- cuj.ts,
- cuj.dur,
+ /* Boundaries table doesn't contain ts and dur when a CUJ didn't complete successfully.
+ In that case we still want to show that it was canceled, so let's take the slice timestamps. */
+ CASE WHEN boundaries.ts IS NOT NULL THEN boundaries.ts ELSE cuj.ts END AS ts,
+ CASE WHEN boundaries.dur IS NOT NULL THEN boundaries.dur ELSE cuj.dur END AS dur,
cuj.track_id,
cuj.slice_id
FROM slice AS cuj
- JOIN process_track AS pt
- ON cuj.track_id = pt.id
+ JOIN process_track AS pt ON cuj.track_id = pt.id
LEFT JOIN android_jank_cuj jc
ON pt.upid = jc.upid AND cuj.name = jc.cuj_slice_name AND cuj.ts = jc.ts
+ LEFT JOIN android_jank_cuj_main_thread_cuj_boundary boundaries using (cuj_id)
LEFT JOIN android_jank_cuj_layer_name cuj_layer USING (cuj_id)
LEFT JOIN android_jank_cuj_counter_metrics USING (cuj_id)
WHERE cuj.name GLOB 'J<*>'
@@ -125,11 +128,47 @@ const LATENCY_CUJ_QUERY = `
`;
const LATENCY_COLUMNS = ['name', 'dur_ms', 'ts', 'dur', 'track_id', 'slice_id'];
+
+const BLOCKING_CALLS_DURING_CUJS_QUERY = `
+ SELECT
+ s.id AS slice_id,
+ s.name,
+ max(s.ts, cuj.ts) AS ts,
+ min(s.ts + s.dur, cuj.ts_end) as ts_end,
+ min(s.ts + s.dur, cuj.ts_end) - max(s.ts, cuj.ts) AS dur,
+ cuj.cuj_id,
+ cuj.cuj_name,
+ s.process_name,
+ s.upid,
+ s.utid,
+ 'slice' AS table_name
+ FROM _android_critical_blocking_calls s
+ JOIN android_jank_cuj cuj
+ -- only when there is an overlap
+ ON s.ts + s.dur > cuj.ts AND s.ts < cuj.ts_end
+ -- and are from the same process
+ AND s.upid = cuj.upid
+`;
+
+const BLOCKING_CALLS_DURING_CUJS_COLUMNS = [
+ 'slice_id',
+ 'name',
+ 'ts',
+ 'cuj_ts',
+ 'dur',
+ 'cuj_id',
+ 'cuj_name',
+ 'process_name',
+ 'upid',
+ 'utid',
+ 'table_name',
+];
+
class AndroidCujs implements Plugin {
async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
ctx.registerCommand({
id: 'dev.perfetto.AndroidCujs#PinJankCUJs',
- name: 'Pin: Android Jank CUJs',
+ name: 'Add track: Android jank CUJs',
callback: () => {
runQuery(JANK_CUJ_QUERY_PRECONDITIONS, ctx.engine).then(() => {
addDebugSliceTrack(
@@ -140,7 +179,7 @@ class AndroidCujs implements Plugin {
},
'Jank CUJs',
{ts: 'ts', dur: 'dur', name: 'name'},
- [],
+ JANK_COLUMNS,
);
});
},
@@ -148,7 +187,7 @@ class AndroidCujs implements Plugin {
ctx.registerCommand({
id: 'dev.perfetto.AndroidCujs#ListJankCUJs',
- name: 'Run query: Android Jank CUJs',
+ name: 'Run query: Android jank CUJs',
callback: () => {
runQuery(JANK_CUJ_QUERY_PRECONDITIONS, ctx.engine).then(() =>
ctx.tabs.openQuery(JANK_CUJ_QUERY, 'Android Jank CUJs'),
@@ -158,7 +197,7 @@ class AndroidCujs implements Plugin {
ctx.registerCommand({
id: 'dev.perfetto.AndroidCujs#PinLatencyCUJs',
- name: 'Pin: Android Latency CUJs',
+ name: 'Add track: Android latency CUJs',
callback: () => {
addDebugSliceTrack(
ctx.engine,
@@ -179,6 +218,25 @@ class AndroidCujs implements Plugin {
callback: () =>
ctx.tabs.openQuery(LATENCY_CUJ_QUERY, 'Android Latency CUJs'),
});
+
+ ctx.registerCommand({
+ id: 'dev.perfetto.AndroidCujs#PinBlockingCalls',
+ name: 'Add track: Android Blocking calls during CUJs',
+ callback: () => {
+ runQuery(JANK_CUJ_QUERY_PRECONDITIONS, ctx.engine).then(() =>
+ addDebugSliceTrack(
+ ctx.engine,
+ {
+ sqlSource: BLOCKING_CALLS_DURING_CUJS_QUERY,
+ columns: BLOCKING_CALLS_DURING_CUJS_COLUMNS,
+ },
+ 'Blocking calls during CUJs',
+ {ts: 'ts', dur: 'dur', name: 'name'},
+ BLOCKING_CALLS_DURING_CUJS_COLUMNS,
+ ),
+ );
+ },
+ });
}
}
diff --git a/ui/src/plugins/dev.perfetto.AndroidNetwork/index.ts b/ui/src/plugins/dev.perfetto.AndroidNetwork/index.ts
index 0eb7e0f28..9cb840508 100644
--- a/ui/src/plugins/dev.perfetto.AndroidNetwork/index.ts
+++ b/ui/src/plugins/dev.perfetto.AndroidNetwork/index.ts
@@ -41,7 +41,7 @@ class AndroidNetwork implements Plugin {
async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
ctx.registerCommand({
id: 'dev.perfetto.AndroidNetwork#batteryEvents',
- name: 'Run query: Pin battery events',
+ name: 'Add track: battery events',
callback: async (track) => {
if (track === undefined) {
track = prompt('Battery Track', '');
@@ -62,7 +62,7 @@ class AndroidNetwork implements Plugin {
ctx.registerCommand({
id: 'dev.perfetto.AndroidNetwork#activityTrack',
- name: 'Run query: Visualize Network Activity',
+ name: 'Add track: network activity',
callback: async (groupby, filter, trackName) => {
if (groupby === undefined) {
groupby = prompt('Group by', 'package_name');
diff --git a/ui/src/plugins/dev.perfetto.CoreCommands/index.ts b/ui/src/plugins/dev.perfetto.CoreCommands/index.ts
index 8dc6a6ff8..54cd458f0 100644
--- a/ui/src/plugins/dev.perfetto.CoreCommands/index.ts
+++ b/ui/src/plugins/dev.perfetto.CoreCommands/index.ts
@@ -19,7 +19,6 @@ import {
PluginContext,
PluginContextTrace,
PluginDescriptor,
- addDebugSliceTrack,
} from '../../public';
const SQL_STATS = `
@@ -107,7 +106,7 @@ class CoreCommandsPlugin implements Plugin {
async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
ctx.registerCommand({
id: 'dev.perfetto.CoreCommands#RunQueryAllProcesses',
- name: 'Run query: all processes',
+ name: 'Run query: All processes',
callback: () => {
ctx.tabs.openQuery(ALL_PROCESSES_QUERY, 'All Processes');
},
@@ -138,7 +137,7 @@ class CoreCommandsPlugin implements Plugin {
callback: () => {
ctx.tabs.openQuery(
CPU_TIME_BY_CPU_BY_PROCESS,
- 'CPU Time by CPU by process',
+ 'CPU time by CPU by process',
);
},
});
@@ -163,18 +162,8 @@ class CoreCommandsPlugin implements Plugin {
});
ctx.registerCommand({
- id: 'dev.perfetto.CoreCommands#PinFtraceTracks',
- name: 'Pin ftrace tracks',
- callback: () => {
- ctx.timeline.pinTracksByPredicate((tags) => {
- return !!tags.name?.startsWith('Ftrace Events Cpu ');
- });
- },
- });
-
- ctx.registerCommand({
id: 'dev.perfetto.CoreCommands#UnpinAllTracks',
- name: 'Unpin all tracks',
+ name: 'Unpin all pinned tracks',
callback: () => {
ctx.timeline.unpinTracksByPredicate((_) => {
return true;
@@ -184,7 +173,7 @@ class CoreCommandsPlugin implements Plugin {
ctx.registerCommand({
id: 'dev.perfetto.CoreCommands#ExpandAllGroups',
- name: 'Expand all groups',
+ name: 'Expand all track groups',
callback: () => {
ctx.timeline.expandGroupsByPredicate((_) => {
return true;
@@ -194,7 +183,7 @@ class CoreCommandsPlugin implements Plugin {
ctx.registerCommand({
id: 'dev.perfetto.CoreCommands#CollapseAllGroups',
- name: 'Collapse all groups',
+ name: 'Collapse all track groups',
callback: () => {
ctx.timeline.collapseGroupsByPredicate((_) => {
return true;
@@ -204,7 +193,7 @@ class CoreCommandsPlugin implements Plugin {
ctx.registerCommand({
id: 'dev.perfetto.CoreCommands#PanToTimestamp',
- name: 'Pan To Timestamp',
+ name: 'Pan to timestamp',
callback: (tsRaw: unknown) => {
if (exists(tsRaw)) {
if (typeof tsRaw !== 'bigint') {
@@ -222,29 +211,8 @@ class CoreCommandsPlugin implements Plugin {
});
ctx.registerCommand({
- id: 'test',
- name: 'Make Test Debug Track',
- callback: () => {
- addDebugSliceTrack(
- ctx.engine,
- {
- sqlSource: `
- SELECT *
- FROM slice
- WHERE name like 'a%'
- LIMIT 10000
- `,
- },
- 'Track Name',
- {ts: 'ts', dur: 'dur', name: 'name'},
- [],
- );
- },
- });
-
- ctx.registerCommand({
id: 'dev.perfetto.CoreCommands#ShowCurrentSelectionTab',
- name: 'Show Current Selection Tab',
+ name: 'Show current selection tab',
callback: () => {
ctx.tabs.showTab('current_selection');
},
diff --git a/ui/src/plugins/dev.perfetto.RestorePinnedTracks/OWNERS b/ui/src/plugins/dev.perfetto.RestorePinnedTracks/OWNERS
new file mode 100644
index 000000000..987684ddc
--- /dev/null
+++ b/ui/src/plugins/dev.perfetto.RestorePinnedTracks/OWNERS
@@ -0,0 +1,2 @@
+nicomazz@google.com
+nickchameyev@google.com
diff --git a/ui/src/plugins/dev.perfetto.RestorePinnedTracks/index.ts b/ui/src/plugins/dev.perfetto.RestorePinnedTracks/index.ts
new file mode 100644
index 000000000..81036db20
--- /dev/null
+++ b/ui/src/plugins/dev.perfetto.RestorePinnedTracks/index.ts
@@ -0,0 +1,135 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import {
+ Plugin,
+ PluginContext,
+ PluginContextTrace,
+ PluginDescriptor,
+ TrackRef,
+} from '../../public';
+
+const PLUGIN_ID = 'dev.perfetto.RestorePinnedTrack';
+const SAVED_TRACKS_KEY = `${PLUGIN_ID}#savedPerfettoTracks`;
+
+/**
+ * Fuzzy save and restore of pinned tracks.
+ *
+ * Tries to persist pinned tracks. Uses full string matching between track name
+ * and group name. When no match is found for a saved track, it tries again
+ * without numbers.
+ */
+class RestorePinnedTrack implements Plugin {
+ onActivate(_ctx: PluginContext): void {}
+
+ private ctx!: PluginContextTrace;
+
+ async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
+ this.ctx = ctx;
+ ctx.registerCommand({
+ id: `${PLUGIN_ID}#save`,
+ name: 'Save: Pinned tracks',
+ callback: () => {
+ this.saveTracks();
+ },
+ });
+ ctx.registerCommand({
+ id: `${PLUGIN_ID}#restore`,
+ name: 'Restore: Pinned tracks',
+ callback: () => {
+ this.restoreTracks();
+ },
+ });
+ }
+
+ private saveTracks() {
+ const pinnedTracks = this.ctx.timeline.tracks.filter(
+ (trackRef) => trackRef.isPinned,
+ );
+ const tracksToSave: SavedPinnedTrack[] = pinnedTracks.map((trackRef) => ({
+ groupName: trackRef.groupName,
+ trackName: trackRef.displayName,
+ }));
+ window.localStorage.setItem(SAVED_TRACKS_KEY, JSON.stringify(tracksToSave));
+ }
+
+ private restoreTracks() {
+ const savedTracks = window.localStorage.getItem(SAVED_TRACKS_KEY);
+ if (!savedTracks) {
+ alert('No saved tracks. Use the Save command first');
+ return;
+ }
+ const tracksToRestore: SavedPinnedTrack[] = JSON.parse(savedTracks);
+ const tracks: TrackRef[] = this.ctx.timeline.tracks;
+ tracksToRestore.forEach((trackToRestore) => {
+ // Check for an exact match
+ const exactMatch = tracks.find((track) => {
+ return (
+ track.key &&
+ trackToRestore.trackName === track.displayName &&
+ trackToRestore.groupName === track.groupName
+ );
+ });
+
+ if (exactMatch) {
+ this.ctx.timeline.pinTrack(exactMatch.key!);
+ } else {
+ // We attempt a match after removing numbers to potentially pin a
+ // "similar" track from a different trace. Removing numbers allows
+ // flexibility; for instance, with multiple 'sysui' processes (e.g.
+ // track group name: "com.android.systemui 123") without this approach,
+ // any could be mistakenly pinned. The goal is to restore specific
+ // tracks within the same trace, ensuring that a previously pinned track
+ // is pinned again.
+ // If the specific process with that PID is unavailable, pinning any
+ // other process matching the package name is attempted.
+ const fuzzyMatch = tracks.find((track) => {
+ return (
+ track.key &&
+ this.removeNumbers(trackToRestore.trackName) ===
+ this.removeNumbers(track.displayName) &&
+ this.removeNumbers(trackToRestore.groupName) ===
+ this.removeNumbers(track.groupName)
+ );
+ });
+
+ if (fuzzyMatch) {
+ this.ctx.timeline.pinTrack(fuzzyMatch.key!);
+ } else {
+ console.warn(
+ '[RestorePinnedTracks] No track found that matches',
+ trackToRestore,
+ );
+ }
+ }
+ });
+ }
+
+ private removeNumbers(inputString?: string): string | undefined {
+ return inputString?.replace(/\d+/g, '');
+ }
+}
+
+interface SavedPinnedTrack {
+ // Optional: group name for the track. Usually matches with process name.
+ groupName?: string;
+
+ // Track name to restore.
+ trackName: string;
+}
+
+export const plugin: PluginDescriptor = {
+ pluginId: PLUGIN_ID,
+ plugin: RestorePinnedTrack,
+};
diff --git a/ui/src/plugins/org.kernel.LinuxKernelDevices/index.ts b/ui/src/plugins/org.kernel.LinuxKernelDevices/index.ts
index d7a37d8d7..b9127f0ce 100644
--- a/ui/src/plugins/org.kernel.LinuxKernelDevices/index.ts
+++ b/ui/src/plugins/org.kernel.LinuxKernelDevices/index.ts
@@ -27,23 +27,11 @@ import {AsyncSliceTrackV2} from '../../tracks/async_slices/async_slice_track_v2'
class LinuxKernelDevices implements Plugin {
async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
const result = await ctx.engine.query(`
- with
- slices_tracks as materialized (
- select distinct track_id
- from slice
- ),
- tracks as (
- select
- linux_device_track.id as track_id,
- linux_device_track.name
- from linux_device_track
- join slices_tracks on
- slices_tracks.track_id = linux_device_track.id
- )
select
- t.name,
- t.track_id as trackId
- from tracks as t
+ t.id as trackId,
+ t.name
+ from linux_device_track t
+ join _slice_track_summary using (id)
order by t.name;
`);
diff --git a/ui/src/public/index.ts b/ui/src/public/index.ts
index a8d29499f..f3e433853 100644
--- a/ui/src/public/index.ts
+++ b/ui/src/public/index.ts
@@ -45,9 +45,9 @@ export interface Slice {
// These properties are updated only once per query result when the Slice
// object is created and don't change afterwards.
readonly id: number;
- readonly startNsQ: time;
- readonly endNsQ: time;
- readonly durNsQ: duration;
+ readonly startNs: time;
+ readonly endNs: time;
+ readonly durNs: duration;
readonly ts: time;
readonly dur: duration;
readonly depth: number;
@@ -482,6 +482,12 @@ export interface TrackRef {
// Optional: Add tracks to a group with this name.
groupName?: string;
+
+ // Optional: Track key
+ key?: string;
+
+ // Optional: Whether the track is pinned
+ isPinned?: boolean;
}
// A predicate for selecting a subset of tracks.
diff --git a/ui/src/public/utils.ts b/ui/src/public/utils.ts
index 60c4487f5..f5d3d7a3c 100644
--- a/ui/src/public/utils.ts
+++ b/ui/src/public/utils.ts
@@ -22,7 +22,7 @@ import {DetailsPanel, Tab} from '.';
export function getTrackName(
args: Partial<{
name: string | null;
- utid: number;
+ utid: number | null;
processName: string | null;
pid: number | null;
threadName: string | null;
diff --git a/ui/src/tracks/android_log/index.ts b/ui/src/tracks/android_log/index.ts
index 72473763a..53e6f69c6 100644
--- a/ui/src/tracks/android_log/index.ts
+++ b/ui/src/tracks/android_log/index.ts
@@ -84,7 +84,7 @@ class AndroidLog implements Plugin {
ctx.registerCommand({
id: 'perfetto.AndroidLog#ShowLogsTab',
- name: 'Show Android Logs Tab',
+ name: 'Show android logs tab',
callback: () => {
ctx.tabs.showTab(androidLogsTabUri);
},
diff --git a/ui/src/tracks/android_log/logs_panel.ts b/ui/src/tracks/android_log/logs_panel.ts
index 51e0889e2..3970190db 100644
--- a/ui/src/tracks/android_log/logs_panel.ts
+++ b/ui/src/tracks/android_log/logs_panel.ts
@@ -18,12 +18,10 @@ import {duration, Span, time, Time, TimeSpan} from '../../base/time';
import {Actions} from '../../common/actions';
import {raf} from '../../core/raf_scheduler';
import {DetailsShell} from '../../widgets/details_shell';
-import {VirtualScrollContainer} from '../../widgets/virtual_scroll_container';
-import {SELECTED_LOG_ROWS_COLOR} from '../../frontend/css_constants';
import {globals} from '../../frontend/globals';
import {Timestamp} from '../../frontend/widgets/timestamp';
-import {createStore, EngineProxy, LONG, NUM, Store, STR} from '../../public';
+import {EngineProxy, LONG, NUM, NUM_NULL, Store, STR} from '../../public';
import {Monitor} from '../../base/monitor';
import {AsyncLimiter} from '../../base/async_limiter';
import {escapeGlob, escapeQuery} from '../../trace_processor/query_utils';
@@ -31,6 +29,8 @@ import {Select} from '../../widgets/select';
import {Button} from '../../widgets/button';
import {TextInput} from '../../widgets/text_input';
import {Intent} from '../../widgets/common';
+import {VirtualTable, VirtualTableRow} from '../../widgets/virtual_table';
+import {classNames} from '../../base/classnames';
const ROW_H = 20;
@@ -63,15 +63,12 @@ interface LogEntries {
}
export class LogPanel implements m.ClassComponent<LogPanelAttrs> {
- private readonly SKIRT_SIZE = 50;
private entries?: LogEntries;
- private isStale = true;
- private viewportBounds = {top: 0, bottom: 0};
- private readonly paginationStore = createStore<Pagination>({
+ private pagination: Pagination = {
offset: 0,
count: 0,
- });
+ };
private readonly rowsMonitor: Monitor;
private readonly filterMonitor: Monitor;
private readonly queryLimiter = new AsyncLimiter();
@@ -81,7 +78,6 @@ export class LogPanel implements m.ClassComponent<LogPanelAttrs> {
() => attrs.filterStore.state,
() => globals.state.frontendLocalState.visibleState.start,
() => globals.state.frontendLocalState.visibleState.end,
- () => this.paginationStore.state,
]);
this.filterMonitor = new Monitor([() => attrs.filterStore.state]);
@@ -89,148 +85,104 @@ export class LogPanel implements m.ClassComponent<LogPanelAttrs> {
view({attrs}: m.CVnode<LogPanelAttrs>) {
if (this.rowsMonitor.ifStateChanged()) {
- this.queryLimiter.schedule(async () => {
- this.isStale = true;
- raf.scheduleFullRedraw();
-
- const visibleState = globals.state.frontendLocalState.visibleState;
- const visibleSpan = new TimeSpan(visibleState.start, visibleState.end);
-
- if (this.filterMonitor.ifStateChanged()) {
- await updateLogView(attrs.engine, attrs.filterStore.state);
- }
-
- this.entries = await updateLogEntries(
- attrs.engine,
- visibleSpan,
- this.paginationStore.state,
- );
-
- raf.scheduleFullRedraw();
- this.isStale = false;
- });
+ this.reloadData(attrs);
}
const hasProcessNames =
this.entries &&
this.entries.processName.filter((name) => name).length > 0;
+ const totalEvents = this.entries?.totalEvents ?? 0;
- const rows: m.Children = [];
- rows.push(
- m(
- `.row`,
- m('.cell.row-header', 'Timestamp'),
- m('.cell.row-header', 'Level'),
- m('.cell.row-header', 'Tag'),
- hasProcessNames
- ? m('.cell.with-process.row-header', 'Process name')
- : undefined,
- hasProcessNames
- ? m('.cell.with-process.row-header', 'Message')
- : m('.cell.no-process.row-header', 'Message'),
- m('br'),
- ),
+ return m(
+ DetailsShell,
+ {
+ title: 'Android Logs',
+ description: `Total messages: ${totalEvents}`,
+ buttons: m(LogsFilters, {store: attrs.filterStore}),
+ },
+ m(VirtualTable, {
+ className: 'pf-android-logs-table',
+ columns: [
+ {header: 'Timestamp', width: '7rem'},
+ {header: 'Level', width: '4rem'},
+ {header: 'Tag', width: '13rem'},
+ ...(hasProcessNames ? [{header: 'Process', width: '18rem'}] : []),
+ {header: 'Message', width: '42rem'},
+ ],
+ rows: this.renderRows(hasProcessNames),
+ firstRowOffset: this.entries?.offset ?? 0,
+ numRows: this.entries?.totalEvents ?? 0,
+ rowHeight: ROW_H,
+ onReload: (offset, count) => {
+ this.pagination = {offset, count};
+ this.reloadData(attrs);
+ },
+ onRowHover: (id) => {
+ const timestamp = this.entries?.timestamps[id];
+ if (timestamp !== undefined) {
+ globals.dispatch(Actions.setHoverCursorTimestamp({ts: timestamp}));
+ }
+ },
+ onRowOut: () => {
+ globals.dispatch(Actions.setHoverCursorTimestamp({ts: Time.INVALID}));
+ },
+ }),
);
- if (this.entries) {
- const offset = this.entries.offset;
- const timestamps = this.entries.timestamps;
- const priorities = this.entries.priorities;
- const tags = this.entries.tags;
- const messages = this.entries.messages;
- const processNames = this.entries.processName;
- const totalEvents = this.entries.totalEvents;
-
- for (let i = 0; i < this.entries.timestamps.length; i++) {
- const priorityLetter = LOG_PRIORITIES[priorities[i]][0];
- const ts = timestamps[i];
- const prioClass = priorityLetter || '';
- const style: {top: string; backgroundColor?: string} = {
- // 1.5 is for the width of the header
- top: `${(offset + i + 1.5) * ROW_H}px`,
- };
- if (this.entries.isHighlighted[i]) {
- style.backgroundColor = SELECTED_LOG_ROWS_COLOR;
- }
-
- rows.push(
- m(
- `.row.${prioClass}`,
- {
- class: this.isStale ? 'stale' : '',
- style,
- onmouseover: () => {
- globals.dispatch(Actions.setHoverCursorTimestamp({ts}));
- },
- onmouseout: () => {
- globals.dispatch(
- Actions.setHoverCursorTimestamp({ts: Time.INVALID}),
- );
- },
- },
- m('.cell', m(Timestamp, {ts})),
- m('.cell', priorityLetter || '?'),
- m('.cell', tags[i]),
- hasProcessNames
- ? m('.cell.with-process', processNames[i])
- : undefined,
- hasProcessNames
- ? m('.cell.with-process', messages[i])
- : m('.cell.no-process', messages[i]),
- m('br'),
- ),
- );
+ }
+
+ private reloadData(attrs: LogPanelAttrs) {
+ this.queryLimiter.schedule(async () => {
+ const visibleState = globals.state.frontendLocalState.visibleState;
+ const visibleSpan = new TimeSpan(visibleState.start, visibleState.end);
+
+ if (this.filterMonitor.ifStateChanged()) {
+ await updateLogView(attrs.engine, attrs.filterStore.state);
}
- return m(
- DetailsShell,
- {
- title: 'Android Logs',
- description: `[${this.viewportBounds.top}, ${this.viewportBounds.bottom}] / ${totalEvents}`,
- buttons: m(LogsFilters, {store: attrs.filterStore}),
- },
- m(
- VirtualScrollContainer,
- {
- onScroll: (scrollContainer: HTMLElement) => {
- this.recomputeVisibleRowsAndUpdate(scrollContainer);
- raf.scheduleFullRedraw();
- },
- },
- m(
- '.log-panel',
- m('.rows', {style: {height: `${totalEvents * ROW_H}px`}}, rows),
- ),
- ),
+ this.entries = await updateLogEntries(
+ attrs.engine,
+ visibleSpan,
+ this.pagination,
);
- }
- return null;
+ raf.scheduleFullRedraw();
+ });
}
- recomputeVisibleRowsAndUpdate(scrollContainer: HTMLElement) {
- const viewportTop = Math.floor(scrollContainer.scrollTop / ROW_H);
- const viewportHeight = Math.ceil(scrollContainer.clientHeight / ROW_H);
- const viewportBottom = viewportTop + viewportHeight;
-
- this.viewportBounds = {
- top: viewportTop,
- bottom: viewportBottom,
- };
-
- const curPage = this.paginationStore.state;
-
- if (
- viewportTop < curPage.offset ||
- viewportBottom >= curPage.offset + curPage.count
- ) {
- this.paginationStore.edit((draft) => {
- const offset = Math.max(0, viewportTop - this.SKIRT_SIZE);
- // Make it even so alternating coloured rows line up
- const offsetEven = Math.floor(offset / 2) * 2;
- draft.offset = offsetEven;
- draft.count = viewportHeight + this.SKIRT_SIZE * 2;
+ private renderRows(hasProcessNames: boolean | undefined): VirtualTableRow[] {
+ if (!this.entries) {
+ return [];
+ }
+
+ const timestamps = this.entries.timestamps;
+ const priorities = this.entries.priorities;
+ const tags = this.entries.tags;
+ const messages = this.entries.messages;
+ const processNames = this.entries.processName;
+
+ const rows: VirtualTableRow[] = [];
+ for (let i = 0; i < this.entries.timestamps.length; i++) {
+ const priorityLetter = LOG_PRIORITIES[priorities[i]][0];
+ const ts = timestamps[i];
+ const prioClass = priorityLetter || '';
+
+ rows.push({
+ id: i,
+ className: classNames(
+ prioClass,
+ this.entries.isHighlighted[i] && 'pf-highlighted',
+ ),
+ cells: [
+ m(Timestamp, {ts}),
+ priorityLetter || '?',
+ tags[i],
+ ...(hasProcessNames ? [processNames[i]] : []),
+ messages[i],
+ ],
});
}
+
+ return rows;
}
}
@@ -460,7 +412,7 @@ async function updateLogEntries(
prio: NUM,
tag: STR,
msg: STR,
- isMsgHighlighted: NUM,
+ isMsgHighlighted: NUM_NULL,
isProcessHighlighted: NUM,
processName: STR,
});
diff --git a/ui/src/tracks/async_slices/index.ts b/ui/src/tracks/async_slices/index.ts
index 2e8b3d5b3..18883d9d4 100644
--- a/ui/src/tracks/async_slices/index.ts
+++ b/ui/src/tracks/async_slices/index.ts
@@ -30,77 +30,41 @@ class AsyncSlicePlugin implements Plugin {
async addGlobalAsyncTracks(ctx: PluginContextTrace): Promise<void> {
const {engine} = ctx;
const rawGlobalAsyncTracks = await engine.query(`
- with tracks_with_slices as materialized (
- select distinct track_id
- from slice
- ),
- global_tracks as (
- select
- track.parent_id as parent_id,
- track.id as track_id,
- track.name as name
- from track
- join tracks_with_slices on tracks_with_slices.track_id = track.id
- where
- track.type = "track"
- or track.type = "gpu_track"
- or track.type = "cpu_track"
- ),
- global_tracks_grouped as (
+ with global_tracks_grouped as (
select
parent_id,
name,
- group_concat(track_id) as trackIds,
- count(track_id) as trackCount
- from global_tracks track
+ group_concat(id) as trackIds,
+ count() as trackCount
+ from track t
+ join _slice_track_summary using (id)
+ where t.type in ('track', 'gpu_track', 'cpu_track')
group by parent_id, name
)
select
- t.parent_id as parentId,
- p.name as parentName,
t.name as name,
+ t.parent_id as parentId,
t.trackIds as trackIds,
__max_layout_depth(t.trackCount, t.trackIds) as maxDepth
- from global_tracks_grouped AS t
- left join track p on (t.parent_id = p.id)
- order by p.name, t.name;
+ from global_tracks_grouped t
`);
const it = rawGlobalAsyncTracks.iter({
name: STR_NULL,
- parentName: STR_NULL,
parentId: NUM_NULL,
trackIds: STR,
- maxDepth: NUM_NULL,
+ maxDepth: NUM,
});
- // let scrollJankRendered = false;
-
for (; it.valid(); it.next()) {
const rawName = it.name === null ? undefined : it.name;
- // const rawParentName = it.parentName === null ? undefined :
- // it.parentName;
const displayName = getTrackName({
name: rawName,
kind: ASYNC_SLICE_TRACK_KIND,
});
const rawTrackIds = it.trackIds;
const trackIds = rawTrackIds.split(',').map((v) => Number(v));
- // const parentTrackId = it.parentId;
const maxDepth = it.maxDepth;
- // If there are no slices in this track, skip it.
- if (maxDepth === null) {
- continue;
- }
-
- // if (ENABLE_SCROLL_JANK_PLUGIN_V2.get() && !scrollJankRendered &&
- // name.includes(INPUT_LATENCY_TRACK)) {
- // // This ensures that the scroll jank tracks render above the tracks
- // // for GestureScrollUpdate.
- // await this.addScrollJankTracks(this.engine);
- // scrollJankRendered = true;
- // }
-
ctx.registerTrack({
uri: `perfetto.AsyncSlices#${rawName}.${it.parentId}`,
displayName,
@@ -115,27 +79,16 @@ class AsyncSlicePlugin implements Plugin {
async addProcessAsyncSliceTracks(ctx: PluginContextTrace): Promise<void> {
const result = await ctx.engine.query(`
- with process_async_tracks as materialized (
- select
- process_track.upid as upid,
- process_track.name as trackName,
- process.name as processName,
- process.pid as pid,
- group_concat(process_track.id) as trackIds,
- count(1) as trackCount
- from process_track
- join process using(upid)
- where
- process_track.name is null or
- process_track.name not like "% Timeline"
- group by
- process_track.upid,
- process_track.name
- )
select
- t.*,
- __max_layout_depth(t.trackCount, t.trackIds) as maxDepth
- from process_async_tracks t;
+ upid,
+ t.name as trackName,
+ t.track_ids as trackIds,
+ process.name as processName,
+ process.pid as pid,
+ __max_layout_depth(t.track_count, t.track_ids) as maxDepth
+ from _process_track_summary_by_upid_and_name t
+ join process using(upid)
+ where t.name is null or t.name not glob "* Timeline"
`);
const it = result.iter({
@@ -144,7 +97,7 @@ class AsyncSlicePlugin implements Plugin {
trackIds: STR,
processName: STR_NULL,
pid: NUM_NULL,
- maxDepth: NUM_NULL,
+ maxDepth: NUM,
});
for (; it.valid(); it.next()) {
const upid = it.upid;
@@ -155,11 +108,6 @@ class AsyncSlicePlugin implements Plugin {
const pid = it.pid;
const maxDepth = it.maxDepth;
- if (maxDepth === null) {
- // If there are no slices in this track, skip it.
- continue;
- }
-
const kind = ASYNC_SLICE_TRACK_KIND;
const displayName = getTrackName({
name: trackName,
@@ -188,37 +136,20 @@ class AsyncSlicePlugin implements Plugin {
async addUserAsyncSliceTracks(ctx: PluginContextTrace): Promise<void> {
const {engine} = ctx;
const result = await engine.query(`
- with tracks_with_slices as materialized (
- select distinct track_id
- from slice
- ),
- global_tracks as (
- select
- uid_track.name,
- uid_track.uid,
- group_concat(uid_track.id) as trackIds,
- count(uid_track.id) as trackCount
- from uid_track
- join tracks_with_slices
- where tracks_with_slices.track_id == uid_track.id
- group by uid_track.uid
- )
select
t.name as name,
t.uid as uid,
- package_list.package_name as package_name,
- t.trackIds as trackIds,
- __max_layout_depth(t.trackCount, t.trackIds) as maxDepth
- from global_tracks t
- join package_list
- where t.uid = package_list.uid
- group by t.uid
- `);
+ package_list.package_name as packageName,
+ t.track_ids as trackIds,
+ __max_layout_depth(t.track_count, t.track_ids) as maxDepth
+ from _uid_track_track_summary_by_uid_and_name t
+ join package_list using (uid)
+ `);
const it = result.iter({
name: STR_NULL,
uid: NUM_NULL,
- package_name: STR_NULL,
+ packageName: STR_NULL,
trackIds: STR,
maxDepth: NUM_NULL,
});
@@ -226,7 +157,7 @@ class AsyncSlicePlugin implements Plugin {
for (; it.valid(); it.next()) {
const kind = ASYNC_SLICE_TRACK_KIND;
const rawName = it.name === null ? undefined : it.name;
- const userName = it.package_name === null ? undefined : it.package_name;
+ const userName = it.packageName === null ? undefined : it.packageName;
const uid = it.uid === null ? undefined : it.uid;
const rawTrackIds = it.trackIds;
const trackIds = rawTrackIds.split(',').map((v) => Number(v));
diff --git a/ui/src/tracks/chrome_critical_user_interactions/index.ts b/ui/src/tracks/chrome_critical_user_interactions/index.ts
index 427acc735..faa863b3f 100644
--- a/ui/src/tracks/chrome_critical_user_interactions/index.ts
+++ b/ui/src/tracks/chrome_critical_user_interactions/index.ts
@@ -284,7 +284,7 @@ class CriticalUserInteractionPlugin implements Plugin {
onActivate(ctx: PluginContext): void {
ctx.registerCommand({
id: 'perfetto.CriticalUserInteraction.AddInteractionTrack',
- name: 'Add Chrome Interactions track',
+ name: 'Add track: Chrome interactions',
callback: () => addCriticalUserInteractionTrack(),
});
}
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 85a2a703d..11cc6d159 100644
--- a/ui/src/tracks/chrome_scroll_jank/event_latency_track.ts
+++ b/ui/src/tracks/chrome_scroll_jank/event_latency_track.ts
@@ -53,8 +53,8 @@ export class EventLatencyTrack extends CustomSqlTableSliceTrack<EventLatencyTrac
});
}
- onDestroy() {
- super.onDestroy();
+ async onDestroy(): Promise<void> {
+ await super.onDestroy();
ScrollJankPluginState.getInstance().unregisterTrack(EventLatencyTrack.kind);
}
diff --git a/ui/src/tracks/chrome_scroll_jank/scroll_delta_graph.ts b/ui/src/tracks/chrome_scroll_jank/scroll_delta_graph.ts
index 0bd3abe0f..eedf3be58 100644
--- a/ui/src/tracks/chrome_scroll_jank/scroll_delta_graph.ts
+++ b/ui/src/tracks/chrome_scroll_jank/scroll_delta_graph.ts
@@ -16,7 +16,7 @@ import m from 'mithril';
import {duration, Time, time} from '../../base/time';
import {EngineProxy} from '../../trace_processor/engine';
-import {LONG, NUM, STR} from '../../trace_processor/query_result';
+import {LONG, NUM} from '../../trace_processor/query_result';
import {VegaView} from '../../widgets/vega_view';
const USER_CATEGORY = 'User';
@@ -27,14 +27,14 @@ interface ScrollDeltaPlotDatum {
// to denote the color of the data point.
category: string;
offset: number;
- scrollUpdateIds: string;
+ scrollUpdateId: number;
ts: number;
delta: number;
}
export interface ScrollDeltaDetails {
ts: time;
- scrollUpdateIds: string;
+ scrollUpdateId: number;
scrollDelta: number;
scrollOffset: number;
}
@@ -54,9 +54,9 @@ export async function getUserScrollDeltas(
SELECT
ts,
- IFNULL(scroll_update_id, "") AS scrollUpdateId,
+ IFNULL(scroll_update_id, 0) AS scrollUpdateId,
delta_y AS deltaY,
- offset_y AS offsetY
+ relative_offset_y AS offsetY
FROM chrome_scroll_input_offsets
WHERE ts >= ${startTs} AND ts <= ${startTs + dur};
`);
@@ -72,7 +72,7 @@ export async function getUserScrollDeltas(
for (; it.valid(); it.next()) {
deltas.push({
ts: Time.fromRaw(it.ts),
- scrollUpdateIds: it.scrollUpdateId.toString(),
+ scrollUpdateId: it.scrollUpdateId,
scrollOffset: it.offsetY,
scrollDelta: it.deltaY,
});
@@ -87,30 +87,21 @@ export async function getAppliedScrollDeltas(
dur: duration,
): Promise<ScrollDeltaDetails[]> {
const queryResult = await engine.query(`
- INCLUDE PERFETTO MODULE chrome.scroll_jank.scroll_offsets;
+ INCLUDE PERFETTO MODULE chrome.scroll_jank.scroll_offsets
- WITH scroll_update_ids AS (
- SELECT DISTINCT
- ts,
- GROUP_CONCAT(scroll_update_id, ', ')
- OVER (PARTITION BY ts) AS scroll_update_ids
- FROM chrome_presented_scroll_offsets
- )
SELECT
ts,
- IFNULL(scroll_update_ids, "") AS scrollUpdateIds,
+ IFNULL(scroll_update_id, 0) AS scrollUpdateId,
delta_y AS deltaY,
- offset_y AS offsetY
+ relative_offset_y AS offsetY
FROM chrome_presented_scroll_offsets
- LEFT JOIN scroll_update_ids
- USING(ts)
WHERE ts >= ${startTs} AND ts <= ${startTs + dur}
AND delta_y IS NOT NULL;
`);
const it = queryResult.iter({
ts: LONG,
- scrollUpdateIds: STR,
+ scrollUpdateId: NUM,
deltaY: NUM,
offsetY: NUM,
});
@@ -122,7 +113,7 @@ export async function getAppliedScrollDeltas(
deltas.push({
ts: Time.fromRaw(it.ts),
- scrollUpdateIds: it.scrollUpdateIds,
+ scrollUpdateId: it.scrollUpdateId,
scrollOffset: offset,
scrollDelta: it.deltaY,
});
@@ -242,7 +233,11 @@ export function buildScrollOffsetsGraph(
},
"tooltip": [
{"field": "delta", "type": "quantitative", "title": "Delta"},
- {"field": "scrollUpdateIds", "type": "nominal", "title": "Trace Ids"}
+ {
+ "field": "scrollUpdateId",
+ "type": "quantititive",
+ "title": "Trace Id"
+ }
]
}
}
@@ -262,7 +257,7 @@ function buildOffsetData(
plotData.push({
category: category,
ts: Number(delta.ts) / 10e8,
- scrollUpdateIds: delta.scrollUpdateIds,
+ scrollUpdateId: delta.scrollUpdateId,
offset: delta.scrollOffset,
delta: delta.scrollDelta,
});
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 ba44b9b65..fdb5ad1c0 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
@@ -76,8 +76,8 @@ export class ScrollJankV3Track extends CustomSqlTableSliceTrack<NamedSliceTrackT
};
}
- onDestroy() {
- super.onDestroy();
+ async onDestroy(): Promise<void> {
+ await super.onDestroy();
ScrollJankPluginState.getInstance().unregisterTrack(ScrollJankV3Track.kind);
}
diff --git a/ui/src/tracks/chrome_scroll_jank/scroll_track.ts b/ui/src/tracks/chrome_scroll_jank/scroll_track.ts
index 2967b97ba..8b475694e 100644
--- a/ui/src/tracks/chrome_scroll_jank/scroll_track.ts
+++ b/ui/src/tracks/chrome_scroll_jank/scroll_track.ts
@@ -62,8 +62,8 @@ export class TopLevelScrollTrack extends CustomSqlTableSliceTrack<NamedSliceTrac
});
}
- onDestroy() {
- super.onDestroy();
+ async onDestroy(): Promise<void> {
+ await super.onDestroy();
ScrollJankPluginState.getInstance().unregisterTrack(
TopLevelScrollTrack.kind,
);
diff --git a/ui/src/tracks/counter/index.ts b/ui/src/tracks/counter/index.ts
index a459c8ab2..55773cd87 100644
--- a/ui/src/tracks/counter/index.ts
+++ b/ui/src/tracks/counter/index.ts
@@ -136,23 +136,31 @@ export class TraceProcessorCounterTrack extends BaseCounterTrack {
const time = visibleTimeScale.pxToHpTime(x).toTime('floor');
const query = `
- SELECT
+ select
id,
ts as leftTs,
- min(ts) OVER (ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) as rightTs
- FROM ${this.rootTable}
- WHERE
- track_id = ${this.trackId} AND
- ts < ${time}
- ORDER BY ts DESC
- LIMIT 1
+ (
+ select ts
+ from ${this.rootTable}
+ where
+ track_id = ${this.trackId}
+ and ts >= ${time}
+ order by ts
+ limit 1
+ ) as rightTs
+ from ${this.rootTable}
+ where
+ track_id = ${this.trackId}
+ and ts < ${time}
+ order by ts DESC
+ limit 1
`;
this.engine.query(query).then((result) => {
const it = result.iter({
id: NUM,
leftTs: LONG,
- rightTs: LONG,
+ rightTs: LONG_NULL,
});
if (!it.valid()) {
return;
@@ -160,7 +168,11 @@ export class TraceProcessorCounterTrack extends BaseCounterTrack {
const trackKey = this.trackKey;
const id = it.id;
const leftTs = Time.fromRaw(it.leftTs);
- const rightTs = Time.fromRaw(it.rightTs);
+
+ // TODO(stevegolton): Don't try to guess times and durations here, make it
+ // obvious to the user that this counter sample has no duration as it's
+ // the last one in the series
+ const rightTs = Time.fromRaw(it.rightTs ?? leftTs);
globals.makeSelection(
Actions.selectCounter({
diff --git a/ui/src/tracks/cpu_freq/index.ts b/ui/src/tracks/cpu_freq/index.ts
index 52708e9b7..b5fa5fa4b 100644
--- a/ui/src/tracks/cpu_freq/index.ts
+++ b/ui/src/tracks/cpu_freq/index.ts
@@ -12,13 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {v4 as uuidv4} from 'uuid';
-
import {BigintMath as BIMath} from '../../base/bigint_math';
import {searchSegment} from '../../base/binary_search';
import {assertTrue} from '../../base/logging';
import {duration, time, Time} from '../../base/time';
-import {calcCachedBucketSize} from '../../common/cache_utils';
import {drawTrackHoverTooltip} from '../../common/canvas_utils';
import {colorForCpu} from '../../core/colorizer';
import {TrackData} from '../../common/track_data';
@@ -33,20 +30,12 @@ import {
PluginDescriptor,
Track,
} from '../../public';
-import {
- LONG,
- LONG_NULL,
- NUM,
- NUM_NULL,
- QueryResult,
-} from '../../trace_processor/query_result';
+import {LONG, NUM, NUM_NULL} from '../../trace_processor/query_result';
+import {uuidv4Sql} from '../../base/uuid';
export const CPU_FREQ_TRACK_KIND = 'CpuFreqTrack';
export interface Data extends TrackData {
- maximumValue: number;
- maxTsEnd: time;
-
timestamps: BigInt64Array;
minFreqKHz: Uint32Array;
maxFreqKHz: Uint32Array;
@@ -58,8 +47,7 @@ interface Config {
cpu: number;
freqTrackId: number;
idleTrackId?: number;
- maximumValue?: number;
- minimumValue?: number;
+ maximumValue: number;
}
// 0.5 Makes the horizontal lines sharp.
@@ -74,79 +62,72 @@ class CpuFreqTrack implements Track {
private hoveredIdle: number | undefined = undefined;
private fetcher = new TimelineFetcher<Data>(this.onBoundsChange.bind(this));
- private maxDur: duration = 0n;
- private maxTsEnd: time = Time.ZERO;
- private maximumValueSeen = 0;
- private cachedBucketSize = BIMath.INT64_MAX;
-
- // This unique ID is just used to create the table names.
- // In the future we should probably use the track instance ID, but for now we
- // don't have access to it.
- private uuid = uuidv4();
-
private engine: EngineProxy;
private config: Config;
+ private trackUuid = uuidv4Sql();
constructor(config: Config, engine: EngineProxy) {
this.config = config;
this.engine = engine;
}
- // Returns a valid SQL table name with the given prefix that should be unique
- // for each track.
- private tableName(prefix: string) {
- // Derive table name from, since that is unique for each track.
- // Track ID can be UUID but '-' is not valid for sql table name.
- const idSuffix = this.uuid.split('-').join('_');
- return `${prefix}_${idSuffix}`;
- }
-
async onCreate() {
- await this.createFreqIdleViews();
-
- this.maximumValueSeen = await this.queryMaxFrequency();
- this.maxDur = await this.queryMaxSourceDur();
-
- const iter = (
- await this.engine.query(`
- select max(ts) as maxTs, dur, count(1) as rowCount
- from ${this.tableName('freq_idle')}
- `)
- ).firstRow({maxTs: LONG_NULL, dur: LONG_NULL, rowCount: NUM});
- if (iter.maxTs === null || iter.dur === null) {
- // We shoulnd't really hit this because trackDecider shouldn't create
- // the track in the first place if there are no entries. But could happen
- // if only one cpu has no cpufreq data.
- return;
- }
- this.maxTsEnd = Time.add(Time.fromRaw(iter.maxTs), iter.dur);
+ if (this.config.idleTrackId === undefined) {
+ await this.engine.execute(`
+ create view raw_freq_idle_${this.trackUuid} as
+ select ts, dur, value as freqValue, -1 as idleValue
+ from experimental_counter_dur c
+ where track_id = ${this.config.freqTrackId}
+ `);
+ } else {
+ await this.engine.execute(`
+ create view raw_freq_${this.trackUuid} as
+ select ts, dur, value as freqValue
+ from experimental_counter_dur c
+ where track_id = ${this.config.freqTrackId};
+
+ create view raw_idle_${this.trackUuid} as
+ select
+ ts,
+ dur,
+ iif(value = 4294967295, -1, cast(value as int)) as idleValue
+ from experimental_counter_dur c
+ where track_id = ${this.config.idleTrackId};
- const rowCount = iter.rowCount;
- const bucketSize = calcCachedBucketSize(rowCount);
- if (bucketSize === undefined) {
- return;
+ create virtual table raw_freq_idle_${this.trackUuid}
+ using span_join(raw_freq_${this.trackUuid}, raw_idle_${this.trackUuid});
+ `);
}
- await this.engine.query(`
- create table ${this.tableName('freq_idle_cached')} as
- select
- (ts + ${bucketSize / 2n}) / ${bucketSize} * ${bucketSize} as cachedTsq,
- min(freqValue) as minFreq,
- max(freqValue) as maxFreq,
- value_at_max_ts(ts, freqValue) as lastFreq,
- value_at_max_ts(ts, idleValue) as lastIdleValue
- from ${this.tableName('freq_idle')}
- group by cachedTsq
- order by cachedTsq
+ await this.engine.execute(`
+ create virtual table cpu_freq_${this.trackUuid}
+ using __intrinsic_counter_mipmap((
+ select ts, freqValue as value
+ from raw_freq_idle_${this.trackUuid}
+ ));
+
+ create virtual table cpu_idle_${this.trackUuid}
+ using __intrinsic_counter_mipmap((
+ select ts, idleValue as value
+ from raw_freq_idle_${this.trackUuid}
+ ));
`);
-
- this.cachedBucketSize = bucketSize;
}
async onUpdate() {
await this.fetcher.requestDataForCurrentTime();
}
+ async onDestroy(): Promise<void> {
+ if (this.engine.isAlive) {
+ await this.engine.query(`drop table cpu_freq_${this.trackUuid}`);
+ await this.engine.query(`drop table cpu_idle_${this.trackUuid}`);
+ await this.engine.query(`drop table raw_freq_idle_${this.trackUuid}`);
+ await this.engine.query(`drop view if exists raw_freq_${this.trackUuid}`);
+ await this.engine.query(`drop view if exists raw_idle_${this.trackUuid}`);
+ }
+ }
+
async onBoundsChange(
start: time,
end: time,
@@ -156,164 +137,62 @@ class CpuFreqTrack implements Track {
// function to make sense.
assertTrue(BIMath.popcount(resolution) === 1, `${resolution} not pow of 2`);
- const freqResult = await this.queryData(start, end, resolution);
- assertTrue(freqResult.isComplete());
+ const freqResult = await this.engine.query(`
+ SELECT
+ min_value as minFreq,
+ max_value as maxFreq,
+ last_ts as ts,
+ last_value as lastFreq
+ FROM cpu_freq_${this.trackUuid}(
+ ${start},
+ ${end},
+ ${resolution}
+ );
+ `);
+ const idleResult = await this.engine.query(`
+ SELECT last_value as lastIdle
+ FROM cpu_idle_${this.trackUuid}(
+ ${start},
+ ${end},
+ ${resolution}
+ );
+ `);
+
+ const freqRows = freqResult.numRows();
+ const idleRows = idleResult.numRows();
+ assertTrue(freqRows == idleRows);
- const numRows = freqResult.numRows();
const data: Data = {
start,
end,
resolution,
- length: numRows,
- maximumValue: this.maximumValue(),
- maxTsEnd: this.maxTsEnd,
- timestamps: new BigInt64Array(numRows),
- minFreqKHz: new Uint32Array(numRows),
- maxFreqKHz: new Uint32Array(numRows),
- lastFreqKHz: new Uint32Array(numRows),
- lastIdleValues: new Int8Array(numRows),
+ length: freqRows,
+ timestamps: new BigInt64Array(freqRows),
+ minFreqKHz: new Uint32Array(freqRows),
+ maxFreqKHz: new Uint32Array(freqRows),
+ lastFreqKHz: new Uint32Array(freqRows),
+ lastIdleValues: new Int8Array(freqRows),
};
- const it = freqResult.iter({
- tsq: LONG,
+ const freqIt = freqResult.iter({
+ ts: LONG,
minFreq: NUM,
maxFreq: NUM,
lastFreq: NUM,
- lastIdleValue: NUM,
});
- for (let i = 0; it.valid(); ++i, it.next()) {
- data.timestamps[i] = it.tsq;
- data.minFreqKHz[i] = it.minFreq;
- data.maxFreqKHz[i] = it.maxFreq;
- data.lastFreqKHz[i] = it.lastFreq;
- data.lastIdleValues[i] = it.lastIdleValue;
+ const idleIt = idleResult.iter({
+ lastIdle: NUM,
+ });
+ for (let i = 0; freqIt.valid(); ++i, freqIt.next(), idleIt.next()) {
+ data.timestamps[i] = freqIt.ts;
+ data.minFreqKHz[i] = freqIt.minFreq;
+ data.maxFreqKHz[i] = freqIt.maxFreq;
+ data.lastFreqKHz[i] = freqIt.lastFreq;
+ data.lastIdleValues[i] = idleIt.lastIdle;
}
-
return data;
}
- private async queryData(
- start: time,
- end: time,
- bucketSize: duration,
- ): Promise<QueryResult> {
- const isCached = this.cachedBucketSize <= bucketSize;
-
- if (isCached) {
- return this.engine.query(`
- select
- cachedTsq / ${bucketSize} * ${bucketSize} as tsq,
- min(minFreq) as minFreq,
- max(maxFreq) as maxFreq,
- value_at_max_ts(cachedTsq, lastFreq) as lastFreq,
- value_at_max_ts(cachedTsq, lastIdleValue) as lastIdleValue
- from ${this.tableName('freq_idle_cached')}
- where
- cachedTsq >= ${start - this.maxDur} and
- cachedTsq <= ${end}
- group by tsq
- order by tsq
- `);
- }
- const minTsFreq = await this.engine.query(`
- select ifnull(max(ts), 0) as minTs from ${this.tableName('freq')}
- where ts < ${start}
- `);
-
- let minTs = minTsFreq.iter({minTs: NUM}).minTs;
- if (this.config.idleTrackId !== undefined) {
- const minTsIdle = await this.engine.query(`
- select ifnull(max(ts), 0) as minTs from ${this.tableName('idle')}
- where ts < ${start}
- `);
- minTs = Math.min(minTsIdle.iter({minTs: NUM}).minTs, minTs);
- }
-
- const geqConstraint =
- this.config.idleTrackId === undefined
- ? `ts >= ${minTs}`
- : `source_geq(ts, ${minTs})`;
- return this.engine.query(`
- select
- (ts + ${bucketSize / 2n}) / ${bucketSize} * ${bucketSize} as tsq,
- min(freqValue) as minFreq,
- max(freqValue) as maxFreq,
- value_at_max_ts(ts, freqValue) as lastFreq,
- value_at_max_ts(ts, idleValue) as lastIdleValue
- from ${this.tableName('freq_idle')}
- where
- ${geqConstraint} and
- ts <= ${end}
- group by tsq
- order by tsq
- `);
- }
-
- private async queryMaxFrequency(): Promise<number> {
- const result = await this.engine.query(`
- select max(freqValue) as maxFreq
- from ${this.tableName('freq')}
- `);
- return result.firstRow({maxFreq: NUM_NULL}).maxFreq ?? 0;
- }
-
- private async queryMaxSourceDur(): Promise<duration> {
- const maxDurFreqResult = await this.engine.query(
- `select ifnull(max(dur), 0) as maxDur from ${this.tableName('freq')}`,
- );
- const maxDur = maxDurFreqResult.firstRow({maxDur: LONG}).maxDur;
- if (this.config.idleTrackId === undefined) {
- return maxDur;
- }
-
- const maxDurIdleResult = await this.engine.query(
- `select ifnull(max(dur), 0) as maxDur from ${this.tableName('idle')}`,
- );
- return BIMath.max(maxDur, maxDurIdleResult.firstRow({maxDur: LONG}).maxDur);
- }
-
- private async createFreqIdleViews() {
- await this.engine.query(`create view ${this.tableName('freq')} as
- select
- ts,
- dur,
- value as freqValue
- from experimental_counter_dur c
- where track_id = ${this.config.freqTrackId};
- `);
-
- if (this.config.idleTrackId === undefined) {
- await this.engine.query(`create view ${this.tableName('freq_idle')} as
- select
- ts,
- dur,
- -1 as idleValue,
- freqValue
- from ${this.tableName('freq')};
- `);
- return;
- }
-
- await this.engine.query(`
- create view ${this.tableName('idle')} as
- select
- ts,
- dur,
- iif(value = 4294967295, -1, cast(value as int)) as idleValue
- from experimental_counter_dur c
- where track_id = ${this.config.idleTrackId};
- `);
-
- await this.engine.query(`
- create virtual table ${this.tableName('freq_idle')}
- using span_join(${this.tableName('freq')}, ${this.tableName('idle')});
- `);
- }
-
- private maximumValue() {
- return Math.max(this.config.maximumValue ?? 0, this.maximumValueSeen);
- }
-
getHeight() {
return MARGIN_TOP + RECT_HEIGHT;
}
@@ -337,7 +216,7 @@ class CpuFreqTrack implements Track {
const zeroY = MARGIN_TOP + RECT_HEIGHT;
// Quantize the Y axis to quarters of powers of tens (7.5K, 10K, 12.5K).
- let yMax = data.maximumValue;
+ let yMax = this.config.maximumValue;
const kUnits = ['', 'K', 'M', 'G', 'T', 'E'];
const exp = Math.ceil(Math.log10(Math.max(yMax, 1)));
const pow10 = Math.pow(10, exp);
@@ -347,7 +226,6 @@ class CpuFreqTrack implements Track {
// The values we have for cpufreq are in kHz so +1 to unitGroup.
const yLabel = `${num} ${kUnits[unitGroup + 1]}Hz`;
- // Draw the CPU frequency graph.
const color = colorForCpu(this.config.cpu);
let saturation = 45;
if (globals.state.hoveredUtid !== -1) {
@@ -366,69 +244,69 @@ class CpuFreqTrack implements Track {
const start = visibleWindowTime.start;
const end = visibleWindowTime.end;
+
const [rawStartIdx] = searchSegment(data.timestamps, start.toTime());
const startIdx = rawStartIdx === -1 ? 0 : rawStartIdx;
const [, rawEndIdx] = searchSegment(data.timestamps, end.toTime());
const endIdx = rawEndIdx === -1 ? data.timestamps.length : rawEndIdx;
- ctx.beginPath();
- const timestamp = Time.fromRaw(data.timestamps[startIdx]);
- ctx.moveTo(Math.max(calculateX(timestamp), 0), zeroY);
-
- let lastDrawnY = zeroY;
- for (let i = startIdx; i < endIdx; i++) {
- const timestamp = Time.fromRaw(data.timestamps[i]);
- const x = calculateX(timestamp);
-
- const minY = calculateY(data.minFreqKHz[i]);
- const maxY = calculateY(data.maxFreqKHz[i]);
- const lastY = calculateY(data.lastFreqKHz[i]);
-
- ctx.lineTo(x, lastDrawnY);
- if (minY === maxY) {
- assertTrue(lastY === minY);
- ctx.lineTo(x, lastY);
- } else {
- ctx.lineTo(x, minY);
- ctx.lineTo(x, maxY);
- ctx.lineTo(x, lastY);
+ // Draw the CPU frequency graph.
+ {
+ ctx.beginPath();
+ const timestamp = Time.fromRaw(data.timestamps[startIdx]);
+ ctx.moveTo(Math.max(calculateX(timestamp), 0), zeroY);
+
+ let lastDrawnY = zeroY;
+ for (let i = startIdx; i < endIdx; i++) {
+ const timestamp = Time.fromRaw(data.timestamps[i]);
+ const x = Math.max(0, calculateX(timestamp));
+ const minY = calculateY(data.minFreqKHz[i]);
+ const maxY = calculateY(data.maxFreqKHz[i]);
+ const lastY = calculateY(data.lastFreqKHz[i]);
+
+ ctx.lineTo(x, lastDrawnY);
+ if (minY === maxY) {
+ assertTrue(lastY === minY);
+ ctx.lineTo(x, lastY);
+ } else {
+ ctx.lineTo(x, minY);
+ ctx.lineTo(x, maxY);
+ ctx.lineTo(x, lastY);
+ }
+ lastDrawnY = lastY;
}
- lastDrawnY = lastY;
+ ctx.lineTo(endPx, lastDrawnY);
+ ctx.lineTo(endPx, zeroY);
+ ctx.closePath();
+ ctx.fill();
+ ctx.stroke();
}
- // Find the end time for the last frequency event and then draw
- // down to zero to show that we do not have data after that point.
- const finalX = Math.min(calculateX(data.maxTsEnd), endPx);
- ctx.lineTo(finalX, lastDrawnY);
- ctx.lineTo(finalX, zeroY);
- ctx.lineTo(endPx, zeroY);
- ctx.closePath();
- ctx.fill();
- ctx.stroke();
// Draw CPU idle rectangles that overlay the CPU freq graph.
ctx.fillStyle = `rgba(240, 240, 240, 1)`;
-
- for (let i = startIdx; i < endIdx; i++) {
- if (data.lastIdleValues[i] < 0) {
- continue;
+ {
+ for (let i = startIdx; i < endIdx; i++) {
+ if (data.lastIdleValues[i] < 0) {
+ continue;
+ }
+
+ // We intentionally don't use the floor function here when computing x
+ // coordinates. Instead we use floating point which prevents flickering as
+ // we pan and zoom; this relies on the browser anti-aliasing pixels
+ // correctly.
+ const timestamp = Time.fromRaw(data.timestamps[i]);
+ const x = visibleTimeScale.timeToPx(timestamp);
+ const xEnd =
+ i === data.lastIdleValues.length - 1
+ ? endPx
+ : visibleTimeScale.timeToPx(Time.fromRaw(data.timestamps[i + 1]));
+
+ const width = xEnd - x;
+ const height = calculateY(data.lastFreqKHz[i]) - zeroY;
+
+ ctx.fillRect(x, zeroY, width, height);
}
-
- // We intentionally don't use the floor function here when computing x
- // coordinates. Instead we use floating point which prevents flickering as
- // we pan and zoom; this relies on the browser anti-aliasing pixels
- // correctly.
- const timestamp = Time.fromRaw(data.timestamps[i]);
- const x = visibleTimeScale.timeToPx(timestamp);
- const xEnd =
- i === data.lastIdleValues.length - 1
- ? finalX
- : visibleTimeScale.timeToPx(Time.fromRaw(data.timestamps[i + 1]));
-
- const width = xEnd - x;
- const height = calculateY(data.lastFreqKHz[i]) - zeroY;
-
- ctx.fillRect(x, zeroY, width, height);
}
ctx.font = '10px Roboto Condensed';
@@ -504,6 +382,7 @@ class CpuFreqTrack implements Track {
const time = visibleTimeScale.pxToHpTime(pos.x);
const [left, right] = searchSegment(data.timestamps, time.toTime());
+
this.hoveredTs =
left === -1 ? undefined : Time.fromRaw(data.timestamps[left]);
this.hoveredTsEnd =
@@ -529,16 +408,13 @@ class CpuFreq implements Plugin {
const maxCpuFreqResult = await engine.query(`
select ifnull(max(value), 0) as freq
from counter c
- inner join cpu_counter_track t on c.track_id = t.id
+ join cpu_counter_track t on c.track_id = t.id
where name = 'cpufreq';
`);
const maxCpuFreq = maxCpuFreqResult.firstRow({freq: NUM}).freq;
for (const cpu of cpus) {
- // Only add a cpu freq track if we have
- // cpu freq data.
- // TODO(hjd): Find a way to display cpu idle
- // events even if there are no cpu freq events.
+ // Only add a cpu freq track if we have cpu freq data.
const cpuFreqIdleResult = await engine.query(`
select
id as cpuFreqId,
diff --git a/ui/src/tracks/cpu_slices/index.ts b/ui/src/tracks/cpu_slices/index.ts
index 412ee6ac0..5c042dc9d 100644
--- a/ui/src/tracks/cpu_slices/index.ts
+++ b/ui/src/tracks/cpu_slices/index.ts
@@ -12,14 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {v4 as uuidv4} from 'uuid';
-
import {BigintMath as BIMath} from '../../base/bigint_math';
import {search, searchEq, searchSegment} from '../../base/binary_search';
import {assertExists, assertTrue} from '../../base/logging';
import {Duration, duration, Time, time} from '../../base/time';
import {Actions} from '../../common/actions';
-import {calcCachedBucketSize} from '../../common/cache_utils';
import {getLegacySelection} from '../../common/state';
import {
cropText,
@@ -43,6 +40,7 @@ import {
Track,
} from '../../public';
import {LONG, NUM, STR_NULL} from '../../trace_processor/query_result';
+import {uuidv4Sql} from '../../base/uuid';
export const CPU_SLICE_TRACK_KIND = 'CpuSliceTrack';
@@ -68,13 +66,11 @@ class CpuSliceTrack implements Track {
private utidHoveredInThisTrack = -1;
private fetcher = new TimelineFetcher<Data>(this.onBoundsChange.bind(this));
- private uuid = uuidv4();
- private cachedBucketSize = BIMath.INT64_MAX;
- private maxDur: duration = 0n;
private lastRowId = -1;
private engine: EngineProxy;
private cpu: number;
private trackKey: string;
+ private trackUuid = uuidv4Sql();
constructor(engine: EngineProxy, trackKey: string, cpu: number) {
this.engine = engine;
@@ -82,62 +78,25 @@ class CpuSliceTrack implements Track {
this.cpu = cpu;
}
- // Returns a valid SQL table name with the given prefix that should be unique
- // for each track.
- private tableName(prefix: string) {
- // Derive table name from, since that is unique for each track.
- // Track ID can be UUID but '-' is not valid for sql table name.
- const idSuffix = this.uuid.split('-').join('_');
- return `${prefix}_${idSuffix}`;
- }
-
async onCreate() {
await this.engine.query(`
- create view ${this.tableName('sched')} as
- select
- ts,
- dur,
- utid,
- id,
- dur = -1 as isIncomplete,
- (case when priority < 100 then 1 else 0 end) as isRealtime
+ create virtual table cpu_slice_${this.trackUuid}
+ using __intrinsic_slice_mipmap((
+ select
+ id,
+ ts,
+ iif(dur = -1, lead(ts, 1, trace_end()) over (order by ts) - ts, dur),
+ 0 as depth
+ from sched
+ where cpu = ${this.cpu} and utid != 0
+ ));
+ `);
+ const it = await this.engine.query(`
+ select coalesce(max(id), -1) as lastRowId
from sched
where cpu = ${this.cpu} and utid != 0
`);
-
- const queryRes = await this.engine.query(`
- select ifnull(max(dur), 0) as maxDur, count(1) as rowCount
- from ${this.tableName('sched')}
- `);
-
- const queryLastSlice = await this.engine.query(`
- select ifnull(max(id), -1) as lastSliceId from ${this.tableName('sched')}
- `);
- this.lastRowId = queryLastSlice.firstRow({lastSliceId: NUM}).lastSliceId;
-
- const row = queryRes.firstRow({maxDur: LONG, rowCount: NUM});
- this.maxDur = row.maxDur;
- const rowCount = row.rowCount;
- const bucketSize = calcCachedBucketSize(rowCount);
- if (bucketSize === undefined) {
- return;
- }
-
- await this.engine.query(`
- create table ${this.tableName('sched_cached')} as
- select
- (ts + ${bucketSize / 2n}) / ${bucketSize} * ${bucketSize} as cached_tsq,
- ts,
- max(dur) as dur,
- utid,
- id,
- isIncomplete,
- isRealtime
- from ${this.tableName('sched')}
- group by cached_tsq, isIncomplete
- order by cached_tsq
- `);
- this.cachedBucketSize = bucketSize;
+ this.lastRowId = it.firstRow({lastRowId: NUM}).lastRowId;
}
async onUpdate() {
@@ -151,30 +110,16 @@ class CpuSliceTrack implements Track {
): Promise<Data> {
assertTrue(BIMath.popcount(resolution) === 1, `${resolution} not pow of 2`);
- const isCached = this.cachedBucketSize <= resolution;
- const queryTsq = isCached
- ? `cached_tsq / ${resolution} * ${resolution}`
- : `(ts + ${resolution / 2n}) / ${resolution} * ${resolution}`;
- const queryTable = isCached
- ? this.tableName('sched_cached')
- : this.tableName('sched');
- const constraintColumn = isCached ? 'cached_tsq' : 'ts';
-
const queryRes = await this.engine.query(`
select
- ${queryTsq} as tsq,
- ts,
- max(dur) as dur,
- utid,
- id,
- isIncomplete,
- isRealtime
- from ${queryTable}
- where
- ${constraintColumn} >= ${start - this.maxDur} and
- ${constraintColumn} <= ${end}
- group by tsq, isIncomplete
- order by tsq
+ (z.ts / ${resolution}) * ${resolution} as ts,
+ max(z.dur, ${resolution}) as dur,
+ s.utid,
+ s.id,
+ s.dur = -1 as isIncomplete,
+ ifnull(s.priority < 100, 0) as isRealtime
+ from cpu_slice_${this.trackUuid}(${start}, ${end}, ${resolution}) z
+ cross join sched s using (id)
`);
const numRows = queryRes.numRows();
@@ -192,7 +137,6 @@ class CpuSliceTrack implements Track {
};
const it = queryRes.iter({
- tsq: LONG,
ts: LONG,
dur: LONG,
utid: NUM,
@@ -201,19 +145,11 @@ class CpuSliceTrack implements Track {
isRealtime: NUM,
});
for (let row = 0; it.valid(); it.next(), row++) {
- const startQ = it.tsq;
const start = it.ts;
const dur = it.dur;
- const end = start + dur;
-
- // If the slice is incomplete, the end calculated later.
- if (!it.isIncomplete) {
- const minEnd = startQ + resolution;
- const endQ = BIMath.max(BIMath.quant(end, resolution), minEnd);
- slices.ends[row] = endQ;
- }
- slices.starts[row] = startQ;
+ slices.starts[row] = start;
+ slices.ends[row] = start + dur;
slices.utids[row] = it.utid;
slices.ids[row] = it.id;
@@ -225,26 +161,13 @@ class CpuSliceTrack implements Track {
slices.flags[row] |= CPU_SLICE_FLAGS_REALTIME;
}
}
-
- // If the slice is incomplete and it is the last slice in the track, the end
- // of the slice would be the end of the visible window. Otherwise we end the
- // slice with the beginning the next one.
- for (let row = 0; row < slices.length; row++) {
- if (!(slices.flags[row] & CPU_SLICE_FLAGS_INCOMPLETE)) {
- continue;
- }
- const endTime = row === slices.length - 1 ? end : slices.starts[row + 1];
- const minEnd = slices.starts[row] + resolution;
- const endQ = BIMath.max(BIMath.quant(endTime, resolution), minEnd);
- slices.ends[row] = endQ;
- }
return slices;
}
async onDestroy() {
if (this.engine.isAlive) {
await this.engine.query(
- `drop table if exists ${this.tableName('sched_cached')}`,
+ `drop table if exists cpu_slice_${this.trackUuid}`,
);
}
this.fetcher.dispose();
diff --git a/ui/src/tracks/debug/slice_track.ts b/ui/src/tracks/debug/slice_track.ts
index 4ea16dc6d..a53e4dcf1 100644
--- a/ui/src/tracks/debug/slice_track.ts
+++ b/ui/src/tracks/debug/slice_track.ts
@@ -90,13 +90,12 @@ export class DebugTrackV2 extends CustomSqlTableSliceTrack<NamedSliceTrackTypes>
// TODO(altimin): Support removing this table when the track is closed.
const dur = sliceColumns.dur === '0' ? 0 : sliceColumns.dur;
await this.engine.query(`
- create table ${this.sqlTableName} as
+ create perfetto table ${this.sqlTableName} as
with data${dataColumns} as (
${data.sqlSource}
),
prepared_data as (
select
- row_number() over () as id,
${sliceColumns.ts} as ts,
ifnull(cast(${dur} as int), -1) as dur,
printf('%s', ${sliceColumns.name}) as name
@@ -105,6 +104,7 @@ export class DebugTrackV2 extends CustomSqlTableSliceTrack<NamedSliceTrackTypes>
from data
)
select
+ row_number() over (order by ts) as id,
*
from prepared_data
order by ts;`);
diff --git a/ui/src/tracks/frames/index.ts b/ui/src/tracks/frames/index.ts
index 47e88ff41..2cc877f29 100644
--- a/ui/src/tracks/frames/index.ts
+++ b/ui/src/tracks/frames/index.ts
@@ -31,26 +31,17 @@ class FramesPlugin implements Plugin {
async addExpectedFrames(ctx: PluginContextTrace): Promise<void> {
const {engine} = ctx;
const result = await engine.query(`
- with process_async_tracks as materialized (
- select
- process_track.upid as upid,
- process_track.name as trackName,
- process.name as processName,
- process.pid as pid,
- group_concat(process_track.id) as trackIds,
- count(1) as trackCount
- from process_track
- join process using(upid)
- where process_track.name = "Expected Timeline"
- group by
- process_track.upid,
- process_track.name
- )
select
- t.*,
- __max_layout_depth(t.trackCount, t.trackIds) as maxDepth
- from process_async_tracks t;
- `);
+ upid,
+ t.name as trackName,
+ t.track_ids as trackIds,
+ process.name as processName,
+ process.pid as pid,
+ __max_layout_depth(t.track_count, t.track_ids) as maxDepth
+ from _process_track_summary_by_upid_and_name t
+ join process using(upid)
+ where t.name = "Expected Timeline"
+ `);
const it = result.iter({
upid: NUM,
@@ -58,7 +49,7 @@ class FramesPlugin implements Plugin {
trackIds: STR,
processName: STR_NULL,
pid: NUM_NULL,
- maxDepth: NUM_NULL,
+ maxDepth: NUM,
});
for (; it.valid(); it.next()) {
@@ -70,11 +61,6 @@ class FramesPlugin implements Plugin {
const pid = it.pid;
const maxDepth = it.maxDepth;
- if (maxDepth === null) {
- // If there are no slices in this track, skip it.
- continue;
- }
-
const displayName = getTrackName({
name: trackName,
upid,
@@ -103,26 +89,17 @@ class FramesPlugin implements Plugin {
async addActualFrames(ctx: PluginContextTrace): Promise<void> {
const {engine} = ctx;
const result = await engine.query(`
- with process_async_tracks as materialized (
- select
- process_track.upid as upid,
- process_track.name as trackName,
- process.name as processName,
- process.pid as pid,
- group_concat(process_track.id) as trackIds,
- count(1) as trackCount
- from process_track
- join process using(upid)
- where process_track.name = "Actual Timeline"
- group by
- process_track.upid,
- process_track.name
- )
select
- t.*,
- __max_layout_depth(t.trackCount, t.trackIds) as maxDepth
- from process_async_tracks t;
- `);
+ upid,
+ t.name as trackName,
+ t.track_ids as trackIds,
+ process.name as processName,
+ process.pid as pid,
+ __max_layout_depth(t.track_count, t.track_ids) as maxDepth
+ from _process_track_summary_by_upid_and_name t
+ join process using(upid)
+ where t.name = "Actual Timeline"
+ `);
const it = result.iter({
upid: NUM,
diff --git a/ui/src/tracks/ftrace/ftrace_explorer.ts b/ui/src/tracks/ftrace/ftrace_explorer.ts
index 9481b864e..5e7eb3273 100644
--- a/ui/src/tracks/ftrace/ftrace_explorer.ts
+++ b/ui/src/tracks/ftrace/ftrace_explorer.ts
@@ -24,27 +24,18 @@ import {
PopupMultiSelect,
} from '../../widgets/multiselect';
import {PopupPosition} from '../../widgets/popup';
-import {VirtualScrollContainer} from '../../widgets/virtual_scroll_container';
import {globals} from '../../frontend/globals';
import {Timestamp} from '../../frontend/widgets/timestamp';
import {FtraceFilter, FtraceStat} from './common';
-import {
- createStore,
- EngineProxy,
- LONG,
- NUM,
- Store,
- STR,
- STR_NULL,
-} from '../../public';
+import {EngineProxy, LONG, NUM, Store, STR, STR_NULL} from '../../public';
import {raf} from '../../core/raf_scheduler';
import {AsyncLimiter} from '../../base/async_limiter';
import {Monitor} from '../../base/monitor';
import {Button} from '../../widgets/button';
+import {VirtualTable, VirtualTableRow} from '../../widgets/virtual_table';
const ROW_H = 20;
-const PAGE_SIZE = 250;
interface FtraceExplorerAttrs {
cache: FtraceExplorerCache;
@@ -69,8 +60,8 @@ interface FtracePanelData {
}
interface Pagination {
- page: number;
- pageCount: number;
+ offset: number;
+ count: number;
}
export interface FtraceExplorerCache {
@@ -104,10 +95,10 @@ async function getFtraceCounters(engine: EngineProxy): Promise<FtraceStat[]> {
}
export class FtraceExplorer implements m.ClassComponent<FtraceExplorerAttrs> {
- private readonly paginationStore = createStore<Pagination>({
- page: 0,
- pageCount: 0,
- });
+ private pagination: Pagination = {
+ offset: 0,
+ count: 0,
+ };
private readonly monitor: Monitor;
private readonly queryLimiter = new AsyncLimiter();
@@ -119,7 +110,6 @@ export class FtraceExplorer implements m.ClassComponent<FtraceExplorerAttrs> {
() => globals.state.frontendLocalState.visibleState.start,
() => globals.state.frontendLocalState.visibleState.end,
() => attrs.filterStore.state,
- () => this.paginationStore.state,
]);
if (attrs.cache.state === 'blank') {
@@ -136,60 +126,85 @@ export class FtraceExplorer implements m.ClassComponent<FtraceExplorerAttrs> {
}
view({attrs}: m.CVnode<FtraceExplorerAttrs>) {
- this.monitor.ifStateChanged(() =>
- this.queryLimiter.schedule(async () => {
- this.data = await lookupFtraceEvents(
- attrs.engine,
- this.paginationStore.state.page * PAGE_SIZE,
- this.paginationStore.state.pageCount * PAGE_SIZE,
- attrs.filterStore.state,
- );
- raf.scheduleFullRedraw();
- }),
- );
+ this.monitor.ifStateChanged(() => {
+ this.reloadData(attrs);
+ });
return m(
DetailsShell,
{
title: this.renderTitle(),
buttons: this.renderFilterPanel(attrs),
+ fillParent: true,
},
- m(
- VirtualScrollContainer,
- {
- onScroll: this.onScroll.bind(this),
+ m(VirtualTable, {
+ className: 'pf-ftrace-explorer',
+ columns: [
+ {header: 'ID', width: '5em'},
+ {header: 'Timestamp', width: '13em'},
+ {header: 'Name', width: '24em'},
+ {header: 'CPU', width: '3em'},
+ {header: 'Process', width: '24em'},
+ {header: 'Args', width: '200em'},
+ ],
+ firstRowOffset: this.data?.offset ?? 0,
+ numRows: this.data?.numEvents ?? 0,
+ rowHeight: ROW_H,
+ rows: this.renderData(),
+ onReload: (offset, count) => {
+ this.pagination = {offset, count};
+ this.reloadData(attrs);
},
- m('.ftrace-panel', this.renderRows()),
- ),
+ onRowHover: this.onRowOver.bind(this),
+ onRowOut: this.onRowOut.bind(this),
+ }),
);
}
- onScroll(scrollContainer: HTMLElement) {
- const paginationState = this.paginationStore.state;
- const prevPage = paginationState.page;
- const prevPageCount = paginationState.pageCount;
-
- const visibleRowOffset = Math.floor(scrollContainer.scrollTop / ROW_H);
- const visibleRowCount = Math.ceil(scrollContainer.clientHeight / ROW_H);
-
- // Work out which "page" we're on
- const page = Math.max(0, Math.floor(visibleRowOffset / PAGE_SIZE) - 1);
- const pageCount = Math.ceil(visibleRowCount / PAGE_SIZE) + 2;
-
- if (page !== prevPage || pageCount !== prevPageCount) {
- this.paginationStore.edit((draft) => {
- draft.page = page;
- draft.pageCount = pageCount;
- });
+ private reloadData(attrs: FtraceExplorerAttrs): void {
+ this.queryLimiter.schedule(async () => {
+ this.data = await lookupFtraceEvents(
+ attrs.engine,
+ this.pagination.offset,
+ this.pagination.count,
+ attrs.filterStore.state,
+ );
raf.scheduleFullRedraw();
+ });
+ }
+
+ private renderData(): VirtualTableRow[] {
+ if (!this.data) {
+ return [];
}
+
+ return this.data.events.map((event) => {
+ const {ts, name, cpu, process, args, id} = event;
+ const timestamp = m(Timestamp, {ts});
+ const color = colorForFtrace(name).base.cssString;
+
+ return {
+ id,
+ cells: [
+ id,
+ timestamp,
+ m('', m('span.colour', {style: {background: color}}), name),
+ cpu,
+ process,
+ args,
+ ],
+ };
+ });
}
- onRowOver(ts: time) {
- globals.dispatch(Actions.setHoverCursorTimestamp({ts}));
+ private onRowOver(id: number) {
+ const event = this.data?.events.find((event) => event.id === id);
+ if (event) {
+ globals.dispatch(Actions.setHoverCursorTimestamp({ts: event.ts}));
+ }
}
- onRowOut() {
+ private onRowOut() {
globals.dispatch(Actions.setHoverCursorTimestamp({ts: Time.INVALID}));
}
@@ -242,55 +257,6 @@ export class FtraceExplorer implements m.ClassComponent<FtraceExplorerAttrs> {
},
});
}
-
- // Render all the rows including the first title row
- private renderRows() {
- const data = this.data;
- const rows: m.Children = [];
-
- rows.push(
- m(
- `.row`,
- m('.cell.row-header', 'Timestamp'),
- m('.cell.row-header', 'Name'),
- m('.cell.row-header', 'CPU'),
- m('.cell.row-header', 'Process'),
- m('.cell.row-header', 'Args'),
- ),
- );
-
- if (data) {
- const {events, offset, numEvents} = data;
- for (let i = 0; i < events.length; i++) {
- const {ts, name, cpu, process, args} = events[i];
-
- const timestamp = m(Timestamp, {ts});
-
- const rank = i + offset;
-
- const color = colorForFtrace(name).base.cssString;
-
- rows.push(
- m(
- `.row`,
- {
- style: {top: `${(rank + 1.0) * ROW_H}px`},
- onmouseover: this.onRowOver.bind(this, ts),
- onmouseout: this.onRowOut.bind(this),
- },
- m('.cell', timestamp),
- m('.cell', m('span.colour', {style: {background: color}}), name),
- m('.cell', cpu),
- m('.cell', process),
- m('.cell', args),
- ),
- );
- }
- return m('.rows', {style: {height: `${numEvents * ROW_H}px`}}, rows);
- } else {
- return m('.rows', rows);
- }
- }
}
async function lookupFtraceEvents(
diff --git a/ui/src/tracks/ftrace/ftrace_track.ts b/ui/src/tracks/ftrace/ftrace_track.ts
index 26c3f3828..7b9def953 100644
--- a/ui/src/tracks/ftrace/ftrace_track.ts
+++ b/ui/src/tracks/ftrace/ftrace_track.ts
@@ -74,7 +74,6 @@ export class FtraceRawTrack implements Track {
const queryRes = await this.engine.query(`
select
cast(ts / ${resolution} as integer) * ${resolution} as tsQuant,
- type,
name
from ftrace_event
where
@@ -93,7 +92,7 @@ export class FtraceRawTrack implements Track {
names: [],
};
- const it = queryRes.iter({tsQuant: LONG, type: STR, name: STR});
+ const it = queryRes.iter({tsQuant: LONG, name: STR});
for (let row = 0; it.valid(); it.next(), row++) {
result.timestamps[row] = it.tsQuant;
result.names[row] = it.name;
diff --git a/ui/src/tracks/ftrace/index.ts b/ui/src/tracks/ftrace/index.ts
index 754223267..3cc06162b 100644
--- a/ui/src/tracks/ftrace/index.ts
+++ b/ui/src/tracks/ftrace/index.ts
@@ -97,7 +97,7 @@ class FtraceRawPlugin implements Plugin {
ctx.registerCommand({
id: 'perfetto.FtraceRaw#ShowFtraceTab',
- name: 'Show Ftrace Tab',
+ name: 'Show ftrace tab',
callback: () => {
ctx.tabs.showTab(ftraceTabUri);
},
diff --git a/ui/src/tracks/process_summary/index.ts b/ui/src/tracks/process_summary/index.ts
index 47a93b01d..ea6845f31 100644
--- a/ui/src/tracks/process_summary/index.ts
+++ b/ui/src/tracks/process_summary/index.ts
@@ -12,17 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {v4 as uuidv4} from 'uuid';
-
import {Plugin, PluginContextTrace, PluginDescriptor} from '../../public';
-import {
- LONG_NULL,
- NUM,
- NUM_NULL,
- STR,
- STR_NULL,
-} from '../../trace_processor/query_result';
-import {assertExists} from '../../base/logging';
+import {NUM, NUM_NULL} from '../../trace_processor/query_result';
import {
Config as ProcessSchedulingTrackConfig,
@@ -37,226 +28,101 @@ import {
// This plugin now manages both process "scheduling" and "summary" tracks.
class ProcessSummaryPlugin implements Plugin {
- private upidToUuid = new Map<number, string>();
- private utidToUuid = new Map<number, string>();
-
async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
await this.addProcessTrackGroups(ctx);
await this.addKernelThreadSummary(ctx);
}
private async addProcessTrackGroups(ctx: PluginContextTrace): Promise<void> {
- this.upidToUuid.clear();
- this.utidToUuid.clear();
-
- // We want to create groups of tracks in a specific order.
- // The tracks should be grouped:
- // by upid
- // or (if upid is null) by utid
- // the groups should be sorted by:
- // Chrome-based process rank based on process names (e.g. Browser)
- // has a heap profile or not
- // total cpu time *for the whole parent process*
- // process name
- // upid
- // thread name
- // utid
const result = await ctx.engine.query(`
- with candidateThreadsAndProcesses as materialized (
- select upid, 0 as utid from process_track
- union
- select upid, 0 as utid from process_counter_track
- union
- select upid, utid from thread_counter_track join thread using(utid)
- union
- select upid, utid from thread_track join thread using(utid)
- union
- select upid, utid from (
- select distinct utid from sched
- ) join thread using(utid) group by utid
- union
- select upid, 0 as utid from (
- select distinct utid from perf_sample where callsite_id is not null
- ) join thread using (utid)
- union
- select upid, utid from (
- select distinct utid from cpu_profile_stack_sample
- ) join thread using(utid)
- union
- select upid as upid, 0 as utid from heap_profile_allocation
- union
- select upid as upid, 0 as utid from heap_graph_object
- ),
- schedSummary as materialized (
- select
- upid,
- sum(thread_total_dur) as total_dur,
- max(thread_max_dur) as total_max_dur,
- sum(thread_event_count) as total_event_count
+ select *
from (
select
- utid,
- sum(dur) as thread_total_dur,
- max(dur) as thread_max_dur,
- count() as thread_event_count
- from sched where dur != -1 and utid != 0
- group by utid
+ _process_available_info_summary.upid,
+ null as utid,
+ pid,
+ null as tid,
+ process.name as processName,
+ null as threadName,
+ sum_running_dur > 0 as hasSched,
+ android_process_metadata.debuggable as isDebuggable
+ from _process_available_info_summary
+ join process using(upid)
+ left join android_process_metadata using(upid)
)
- join thread using (utid)
- group by upid
- ),
- sliceSum as materialized (
- select
- process.upid as upid,
- sum(cnt) as sliceCount
- from (select track_id, count(*) as cnt from slice group by track_id)
- left join thread_track on track_id = thread_track.id
- left join thread on thread_track.utid = thread.utid
- left join process_track on track_id = process_track.id
- join process on process.upid = thread.upid
- or process_track.upid = process.upid
- where process.upid is not null
- group by process.upid
- )
- select
- the_tracks.upid,
- the_tracks.utid,
- total_dur as hasSched,
- total_max_dur as schedMaxDur,
- total_event_count as schedEventCount,
- hasHeapProfiles,
- process.pid as pid,
- thread.tid as tid,
- process.name as processName,
- thread.name as threadName,
- package_list.debuggable as isDebuggable,
- ifnull((
- select group_concat(string_value)
- from args
- where
- process.arg_set_id is not null and
- arg_set_id = process.arg_set_id and
- flat_key = 'chrome.process_label'
- ), '') AS chromeProcessLabels,
- (case process.name
- when 'Browser' then 3
- when 'Gpu' then 2
- when 'Renderer' then 1
- else 0
- end) as chromeProcessRank
- from candidateThreadsAndProcesses the_tracks
- left join schedSummary using(upid)
- left join (
- select
- distinct(upid) as upid,
- true as hasHeapProfiles
- from heap_profile_allocation
- union
- select
- distinct(upid) as upid,
- true as hasHeapProfiles
- from heap_graph_object
- ) using (upid)
- left join (
- select
- thread.upid as upid,
- sum(cnt) as perfSampleCount
+ union all
+ select *
from (
- select utid, count(*) as cnt
- from perf_sample where callsite_id is not null
- group by utid
- ) join thread using (utid)
- group by thread.upid
- ) using (upid)
- left join sliceSum using (upid)
- left join thread using(utid)
- left join process using(upid)
- left join package_list using(uid)
- order by
- chromeProcessRank desc,
- hasHeapProfiles desc,
- perfSampleCount desc,
- total_dur desc,
- sliceCount desc,
- processName asc nulls last,
- the_tracks.upid asc nulls last,
- threadName asc nulls last,
- the_tracks.utid asc nulls last;
+ select
+ null,
+ utid,
+ null as pid,
+ tid,
+ null as processName,
+ thread.name threadName,
+ sum_running_dur > 0 as hasSched,
+ 0 as isDebuggable
+ from _thread_available_info_summary
+ join thread using (utid)
+ where upid is null
+ )
`);
const it = result.iter({
- utid: NUM,
upid: NUM_NULL,
- tid: NUM_NULL,
+ utid: NUM_NULL,
pid: NUM_NULL,
- threadName: STR_NULL,
- processName: STR_NULL,
+ tid: NUM_NULL,
hasSched: NUM_NULL,
- schedMaxDur: LONG_NULL,
- schedEventCount: NUM_NULL,
- hasHeapProfiles: NUM_NULL,
isDebuggable: NUM_NULL,
- chromeProcessLabels: STR,
});
for (; it.valid(); it.next()) {
- const utid = it.utid;
- const tid = it.tid;
const upid = it.upid;
+ const utid = it.utid;
const pid = it.pid;
+ const tid = it.tid;
const hasSched = Boolean(it.hasSched);
- const schedMaxDur = it.schedMaxDur;
- const schedEventCount = it.schedEventCount;
const isDebuggable = Boolean(it.isDebuggable);
// Group by upid if present else by utid.
- let pUuid =
- upid === null ? this.utidToUuid.get(utid) : this.upidToUuid.get(upid);
- // These should only happen once for each track group.
- if (pUuid === undefined) {
- pUuid = this.getOrCreateUuid(utid, upid);
- const pidForColor = pid ?? tid ?? upid ?? utid ?? 0;
- const type = hasSched ? 'schedule' : 'summary';
- const uri = `perfetto.ProcessScheduling#${upid}.${utid}.${type}`;
-
- if (hasSched) {
- const config: ProcessSchedulingTrackConfig = {
- pidForColor,
- upid,
- utid,
- };
-
- ctx.registerTrack({
- uri,
- displayName: `${upid === null ? tid : pid} schedule`,
- kind: PROCESS_SCHEDULING_TRACK_KIND,
- tags: {
- isDebuggable,
- },
- trackFactory: () =>
- new ProcessSchedulingTrack(
- ctx.engine,
- config,
- assertExists(schedMaxDur),
- assertExists(schedEventCount),
- ),
- });
- } else {
- const config: ProcessSummaryTrackConfig = {
- pidForColor,
- upid,
- utid,
- };
-
- ctx.registerTrack({
- uri,
- displayName: `${upid === null ? tid : pid} summary`,
- kind: PROCESS_SUMMARY_TRACK,
- tags: {
- isDebuggable,
- },
- trackFactory: () => new ProcessSummaryTrack(ctx.engine, config),
- });
- }
+ const pidForColor = pid ?? tid ?? upid ?? utid ?? 0;
+ const type = hasSched ? 'schedule' : 'summary';
+ const uri = `perfetto.ProcessScheduling#${upid}.${utid}.${type}`;
+
+ if (hasSched) {
+ const config: ProcessSchedulingTrackConfig = {
+ pidForColor,
+ upid,
+ utid,
+ };
+
+ ctx.registerTrack({
+ uri,
+ displayName: `${upid === null ? tid : pid} schedule`,
+ kind: PROCESS_SCHEDULING_TRACK_KIND,
+ tags: {
+ isDebuggable,
+ },
+ trackFactory: () => {
+ return new ProcessSchedulingTrack(ctx.engine, config);
+ },
+ });
+ } else {
+ const config: ProcessSummaryTrackConfig = {
+ pidForColor,
+ upid,
+ utid,
+ };
+
+ ctx.registerTrack({
+ uri,
+ displayName: `${upid === null ? tid : pid} summary`,
+ kind: PROCESS_SUMMARY_TRACK,
+ tags: {
+ isDebuggable,
+ },
+ trackFactory: () => new ProcessSummaryTrack(ctx.engine, config),
+ });
}
}
}
@@ -313,25 +179,6 @@ class ProcessSummaryPlugin implements Plugin {
trackFactory: () => new ProcessSummaryTrack(ctx.engine, config),
});
}
-
- private getOrCreateUuid(utid: number, upid: number | null) {
- let uuid = this.getUuidUnchecked(utid, upid);
- if (uuid === undefined) {
- uuid = uuidv4();
- if (upid === null) {
- this.utidToUuid.set(utid, uuid);
- } else {
- this.upidToUuid.set(upid, uuid);
- }
- }
- return uuid;
- }
-
- getUuidUnchecked(utid: number, upid: number | null) {
- return upid === null
- ? this.utidToUuid.get(utid)
- : this.upidToUuid.get(upid);
- }
}
export const plugin: PluginDescriptor = {
diff --git a/ui/src/tracks/process_summary/process_scheduling_track.ts b/ui/src/tracks/process_summary/process_scheduling_track.ts
index 07400ca7c..9725c2914 100644
--- a/ui/src/tracks/process_summary/process_scheduling_track.ts
+++ b/ui/src/tracks/process_summary/process_scheduling_track.ts
@@ -12,14 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {v4 as uuidv4} from 'uuid';
-
import {BigintMath as BIMath} from '../../base/bigint_math';
import {searchEq, searchRange} from '../../base/binary_search';
-import {assertTrue} from '../../base/logging';
+import {assertExists, assertTrue} from '../../base/logging';
import {duration, time, Time} from '../../base/time';
import {Actions} from '../../common/actions';
-import {calcCachedBucketSize} from '../../common/cache_utils';
import {drawTrackHoverTooltip} from '../../common/canvas_utils';
import {Color} from '../../core/color';
import {colorForThread} from '../../core/colorizer';
@@ -30,6 +27,7 @@ import {globals} from '../../frontend/globals';
import {PanelSize} from '../../frontend/panel';
import {EngineProxy, Track} from '../../public';
import {LONG, NUM, QueryResult} from '../../trace_processor/query_result';
+import {uuidv4Sql} from '../../base/uuid';
export const PROCESS_SCHEDULING_TRACK_KIND = 'ProcessSchedulingTrack';
@@ -50,8 +48,8 @@ interface Data extends TrackData {
export interface Config {
pidForColor: number;
- upid: null | number;
- utid: number;
+ upid: number | null;
+ utid: number | null;
}
export class ProcessSchedulingTrack implements Track {
@@ -59,60 +57,60 @@ export class ProcessSchedulingTrack implements Track {
private utidHoveredInThisTrack = -1;
private fetcher = new TimelineFetcher(this.onBoundsChange.bind(this));
private maxCpu = 0;
- private maxDur;
- private eventCount;
- private cachedBucketSize = BIMath.INT64_MAX;
private engine: EngineProxy;
- private uuid = uuidv4();
+ private trackUuid = uuidv4Sql();
private config: Config;
- constructor(
- engine: EngineProxy,
- config: Config,
- maxDur: duration,
- eventCount: number,
- ) {
+ constructor(engine: EngineProxy, config: Config) {
this.engine = engine;
this.config = config;
- this.maxDur = maxDur;
- this.eventCount = eventCount;
- }
-
- // Returns a valid SQL table name with the given prefix that should be unique
- // for each track.
- private tableName(prefix: string) {
- // Derive table name from, since that is unique for each track.
- // Track ID can be UUID but '-' is not valid for sql table name.
- const idSuffix = this.uuid.split('-').join('_');
- return `${prefix}_${idSuffix}`;
}
async onCreate(): Promise<void> {
- await this.createSchedView();
-
const cpus = await this.engine.getCpus();
// A process scheduling track should only exist in a trace that has cpus.
assertTrue(cpus.length > 0);
this.maxCpu = Math.max(...cpus) + 1;
- const bucketSize = calcCachedBucketSize(this.eventCount);
- if (bucketSize === undefined) {
- return;
+ if (this.config.upid !== null) {
+ await this.engine.query(`
+ create virtual table process_scheduling_${this.trackUuid}
+ using __intrinsic_slice_mipmap((
+ select
+ id,
+ ts,
+ iif(
+ dur = -1,
+ lead(ts, 1, trace_end()) over (partition by cpu order by ts) - ts,
+ dur
+ ) as dur,
+ cpu as depth
+ from experimental_sched_upid
+ where
+ utid != 0 and
+ upid = ${this.config.upid}
+ ));
+ `);
+ } else {
+ assertExists(this.config.utid);
+ await this.engine.query(`
+ create virtual table process_scheduling_${this.trackUuid}
+ using __intrinsic_slice_mipmap((
+ select
+ id,
+ ts,
+ iif(
+ dur = -1,
+ lead(ts, 1, trace_end()) over (partition by cpu order by ts) - ts,
+ dur
+ ) as dur,
+ cpu as depth
+ from sched
+ where utid = ${this.config.utid}
+ ));
+ `);
}
- await this.engine.query(`
- create table ${this.tableName('process_sched_cached')} as
- select
- (ts + ${bucketSize / 2n}) / ${bucketSize} * ${bucketSize} as cached_tsq,
- ts,
- max(dur) as dur,
- cpu,
- utid
- from ${this.tableName('process_sched')}
- group by cached_tsq, cpu
- order by cached_tsq, cpu
- `);
- this.cachedBucketSize = bucketSize;
}
async onUpdate(): Promise<void> {
@@ -121,6 +119,11 @@ export class ProcessSchedulingTrack implements Track {
async onDestroy(): Promise<void> {
this.fetcher.dispose();
+ if (this.engine.isAlive) {
+ await this.engine.query(`
+ drop table process_scheduling_${this.trackUuid}
+ `);
+ }
}
async onBoundsChange(
@@ -128,8 +131,6 @@ export class ProcessSchedulingTrack implements Track {
end: time,
resolution: duration,
): Promise<Data> {
- assertTrue(this.config.upid !== null);
-
// Resolution must always be a power of 2 for this logic to work
assertTrue(BIMath.popcount(resolution) === 1, `${resolution} not pow of 2`);
@@ -149,7 +150,6 @@ export class ProcessSchedulingTrack implements Track {
};
const it = queryRes.iter({
- tsq: LONG,
ts: LONG,
dur: LONG,
cpu: NUM,
@@ -157,61 +157,35 @@ export class ProcessSchedulingTrack implements Track {
});
for (let row = 0; it.valid(); it.next(), row++) {
- const startQ = Time.fromRaw(it.tsq);
const start = Time.fromRaw(it.ts);
const dur = it.dur;
const end = Time.add(start, dur);
- const minEnd = Time.add(startQ, resolution);
- const endQ = Time.max(Time.quant(end, resolution), minEnd);
- slices.starts[row] = startQ;
- slices.ends[row] = endQ;
+ slices.starts[row] = start;
+ slices.ends[row] = end;
slices.cpus[row] = it.cpu;
slices.utids[row] = it.utid;
- slices.end = Time.max(endQ, slices.end);
+ slices.end = Time.max(end, slices.end);
}
return slices;
}
- private queryData(
+ private async queryData(
start: time,
end: time,
bucketSize: duration,
): Promise<QueryResult> {
- const isCached = this.cachedBucketSize <= bucketSize;
- const tsq = isCached
- ? `cached_tsq / ${bucketSize} * ${bucketSize}`
- : `(ts + ${bucketSize / 2n}) / ${bucketSize} * ${bucketSize}`;
- const queryTable = isCached
- ? this.tableName('process_sched_cached')
- : this.tableName('process_sched');
- const constraintColumn = isCached ? 'cached_tsq' : 'ts';
-
- // The mouse move handler depends on slices being sorted by cpu then tsq
return this.engine.query(`
select
- ${tsq} as tsq,
- ts,
- max(dur) as dur,
- cpu,
+ (z.ts / ${bucketSize}) * ${bucketSize} as ts,
+ iif(s.dur = -1, s.dur, max(z.dur, ${bucketSize})) as dur,
+ s.id,
+ z.depth as cpu,
utid
- from ${queryTable}
- where
- ${constraintColumn} >= ${start - this.maxDur} and
- ${constraintColumn} <= ${end}
- group by tsq, cpu
- order by cpu, tsq
- `);
- }
-
- private async createSchedView() {
- await this.engine.query(`
- create view ${this.tableName('process_sched')} as
- select ts, dur, cpu, utid
- from experimental_sched_upid
- where
- utid != 0 and
- upid = ${this.config.upid}
+ from process_scheduling_${this.trackUuid}(
+ ${start}, ${end}, ${bucketSize}
+ ) z
+ cross join sched s using (id)
`);
}
@@ -252,10 +226,9 @@ export class ProcessSchedulingTrack implements Track {
const utid = data.utids[i];
const cpu = data.cpus[i];
- const rectStart = visibleTimeScale.timeToPx(tStart);
- const rectEnd = visibleTimeScale.timeToPx(tEnd);
- const rectWidth = rectEnd - rectStart;
- if (rectWidth < 0.3) continue;
+ const rectStart = Math.floor(visibleTimeScale.timeToPx(tStart));
+ const rectEnd = Math.floor(visibleTimeScale.timeToPx(tEnd));
+ const rectWidth = Math.max(1, rectEnd - rectStart);
const threadInfo = globals.threads.get(utid);
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
@@ -277,7 +250,7 @@ export class ProcessSchedulingTrack implements Track {
}
ctx.fillStyle = color.cssString;
const y = MARGIN_TOP + cpuTrackHeight * cpu + cpu;
- ctx.fillRect(rectStart, y, rectEnd - rectStart, cpuTrackHeight);
+ ctx.fillRect(rectStart, y, rectWidth, cpuTrackHeight);
}
const hoveredThread = globals.threads.get(this.utidHoveredInThisTrack);
diff --git a/ui/src/tracks/process_summary/process_summary_track.ts b/ui/src/tracks/process_summary/process_summary_track.ts
index 6a8e6879e..7acf36bf8 100644
--- a/ui/src/tracks/process_summary/process_summary_track.ts
+++ b/ui/src/tracks/process_summary/process_summary_track.ts
@@ -15,7 +15,7 @@
import {v4 as uuidv4} from 'uuid';
import {BigintMath} from '../../base/bigint_math';
-import {assertFalse} from '../../base/logging';
+import {assertExists, assertFalse} from '../../base/logging';
import {duration, Time, time} from '../../base/time';
import {colorForTid} from '../../core/colorizer';
import {LIMIT, TrackData} from '../../common/track_data';
@@ -37,7 +37,7 @@ interface Data extends TrackData {
export interface Config {
pidForColor: number;
upid: number | null;
- utid: number;
+ utid: number | null;
}
const MARGIN_TOP = 5;
@@ -70,7 +70,7 @@ export class ProcessSummaryTrack implements Track {
`create virtual table ${this.tableName('window')} using window;`,
);
- let utids = [this.config.utid];
+ let utids: number[];
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
if (this.config.upid) {
const threadQuery = await this.engine.query(
@@ -80,6 +80,8 @@ export class ProcessSummaryTrack implements Track {
for (const it = threadQuery.iter({utid: NUM}); it.valid(); it.next()) {
utids.push(it.utid);
}
+ } else {
+ utids = [assertExists(this.config.utid)];
}
const trackQuery = await this.engine.query(
diff --git a/ui/src/tracks/sched/index.ts b/ui/src/tracks/sched/index.ts
index a8b4b7614..ccebaa1e6 100644
--- a/ui/src/tracks/sched/index.ts
+++ b/ui/src/tracks/sched/index.ts
@@ -44,18 +44,18 @@ class SchedPlugin implements Plugin {
onActivate(ctx: PluginContext): void {
ctx.registerCommand({
id: 'dev.perfetto.Sched.AddRunnableThreadCountTrackCommand',
- name: 'Add runnable thread count track',
+ name: 'Add track: runnable thread count',
callback: () => addRunnableThreadCountTrack(),
});
ctx.registerCommand({
id: 'dev.perfetto.Sched.AddActiveCPUCountTrackCommand',
- name: 'Add active CPU count track',
+ name: 'Add track: active CPU count',
callback: () => addActiveCPUCountTrack(),
});
for (const cpuType of ['big', 'little', 'mid']) {
ctx.registerCommand({
id: `dev.perfetto.Sched.AddActiveCPUCountTrackCommand.${cpuType}`,
- name: `Add active ${cpuType} CPU count track`,
+ name: `Add track: active ${cpuType} CPU count`,
callback: () => addActiveCPUCountTrack(cpuType),
});
}
diff --git a/ui/src/tracks/thread_state/index.ts b/ui/src/tracks/thread_state/index.ts
index b38eb1d1e..73e2faf1c 100644
--- a/ui/src/tracks/thread_state/index.ts
+++ b/ui/src/tracks/thread_state/index.ts
@@ -32,14 +32,14 @@ class ThreadState implements Plugin {
async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
const {engine} = ctx;
const result = await engine.query(`
- with ts_distinct as materialized (select distinct utid from thread_state)
select
utid,
upid,
tid,
thread.name as threadName
from thread
- where utid != 0 and utid in ts_distinct`);
+ join _sched_summary using (utid)
+ `);
const it = result.iter({
utid: NUM,
diff --git a/ui/src/tracks/thread_state/thread_state_v2.ts b/ui/src/tracks/thread_state/thread_state_v2.ts
index 4ab000f42..1d0b31f2f 100644
--- a/ui/src/tracks/thread_state/thread_state_v2.ts
+++ b/ui/src/tracks/thread_state/thread_state_v2.ts
@@ -55,12 +55,7 @@ export class ThreadStateTrack extends BaseSliceTrack<ThreadStateTrackTypes> {
}
getSqlSource(): string {
- // Do not display states:
- // 'x' (dead), 'S' (sleeping), 'I' (idle kernel thread).
- // Note: Thread state tracks V1 basically ignores incomplete slices, faking
- // their duration as 1 instead. Let's just do this here as well for now to
- // achieve feature parity with tracks V1 and tackle the issue of overlapping
- // incomplete slices later.
+ // Do not display states: 'S' (sleeping), 'I' (idle kernel thread).
return `
select
id,
@@ -73,7 +68,7 @@ export class ThreadStateTrack extends BaseSliceTrack<ThreadStateTrackTypes> {
from thread_state
where
utid = ${this.utid} and
- state not in ('x', 'S', 'I')
+ state not in ('S', 'I')
`;
}
diff --git a/ui/src/widgets/details_shell.ts b/ui/src/widgets/details_shell.ts
index 159d016f6..e323d3706 100644
--- a/ui/src/widgets/details_shell.ts
+++ b/ui/src/widgets/details_shell.ts
@@ -20,7 +20,8 @@ interface DetailsShellAttrs {
title: m.Children;
description?: m.Children;
buttons?: m.Children;
- // Stretch/shrink the content to fill the parent vertically.
+
+ // Vertically fill parent container and disable scrolling
fillParent?: boolean;
}
diff --git a/ui/src/widgets/tree.ts b/ui/src/widgets/tree.ts
index 68dcbe1e3..0df2f58a7 100644
--- a/ui/src/widgets/tree.ts
+++ b/ui/src/widgets/tree.ts
@@ -82,24 +82,27 @@ export class TreeNode implements m.ClassComponent<TreeNodeAttrs> {
attrs,
attrs: {left, onCollapseChanged = () => {}},
} = vnode;
- return m(
- '.pf-tree-node',
- {
- class: classNames(this.getClassNameForNode(vnode)),
- },
- m('span.pf-tree-gutter', {
- onclick: () => {
- this.collapsed = !this.isCollapsed(vnode);
- onCollapseChanged(this.collapsed, attrs);
- scheduleFullRedraw();
+ return [
+ m(
+ '.pf-tree-node',
+ {
+ class: classNames(this.getClassNameForNode(vnode)),
},
- }),
- m('.pf-tree-content', m('.pf-tree-left', left), this.renderRight(vnode)),
- hasChildren(vnode) && [
- m('span.pf-tree-indent-gutter'),
- m('.pf-tree-children', children),
- ],
- );
+ m(
+ '.pf-tree-left',
+ m('span.pf-tree-gutter', {
+ onclick: () => {
+ this.collapsed = !this.isCollapsed(vnode);
+ onCollapseChanged(this.collapsed, attrs);
+ scheduleFullRedraw();
+ },
+ }),
+ left,
+ ),
+ this.renderRight(vnode),
+ ),
+ hasChildren(vnode) && m('.pf-tree-children', children),
+ ];
}
private getClassNameForNode(vnode: m.CVnode<TreeNodeAttrs>) {
diff --git a/ui/src/widgets/virtual_scroll_container.ts b/ui/src/widgets/virtual_scroll_container.ts
deleted file mode 100644
index f6bd05238..000000000
--- a/ui/src/widgets/virtual_scroll_container.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright (C) 2023 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-import m from 'mithril';
-
-import {findRef, toHTMLElement} from '../base/dom_utils';
-
-interface VirtualScrollContainerAttrs {
- // Called when the scrolling element is created, updates, or scrolls.
- onScroll?: (dom: HTMLElement) => void;
-}
-
-export class VirtualScrollContainer
- implements m.ClassComponent<VirtualScrollContainerAttrs>
-{
- private readonly REF = 'virtual-scroll-container';
- view({attrs, children}: m.Vnode<VirtualScrollContainerAttrs>) {
- const {onScroll = () => {}} = attrs;
-
- return m(
- '.pf-virtual-scroll-container',
- {
- ref: this.REF,
- onscroll: (e: Event) => onScroll(e.target as HTMLElement),
- },
- children,
- );
- }
-
- oncreate({dom, attrs}: m.VnodeDOM<VirtualScrollContainerAttrs, this>) {
- const {onScroll = () => {}} = attrs;
-
- const element = findRef(dom, this.REF);
- if (element) {
- onScroll(toHTMLElement(element));
- }
- }
-}
diff --git a/ui/src/widgets/virtual_scroll_helper.ts b/ui/src/widgets/virtual_scroll_helper.ts
new file mode 100644
index 000000000..4fbe5c104
--- /dev/null
+++ b/ui/src/widgets/virtual_scroll_helper.ts
@@ -0,0 +1,150 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import {Trash} from '../base/disposable';
+import * as Geometry from '../base/geom';
+
+export interface VirtualScrollHelperOpts {
+ overdrawPx: number;
+
+ // How close we can get to undrawn regions before updating
+ tolerancePx: number;
+
+ callback: (r: Geometry.Rect) => void;
+}
+
+export interface Data {
+ opts: VirtualScrollHelperOpts;
+ rect?: Geometry.Rect;
+}
+
+export class VirtualScrollHelper {
+ private readonly _trash = new Trash();
+ private readonly _data: Data[] = [];
+
+ constructor(
+ sliderElement: HTMLElement,
+ containerElement: Element,
+ opts: VirtualScrollHelperOpts[] = [],
+ ) {
+ this._data = opts.map((opts) => {
+ return {opts};
+ });
+
+ const recalculateRects = () => {
+ this._data.forEach((data) =>
+ recalculatePuckRect(sliderElement, containerElement, data),
+ );
+ };
+
+ containerElement.addEventListener('scroll', recalculateRects, {
+ passive: true,
+ });
+ this._trash.addCallback(() =>
+ containerElement.removeEventListener('scroll', recalculateRects),
+ );
+
+ // Resize observer callbacks are called once immediately
+ const resizeObserver = new ResizeObserver(() => {
+ recalculateRects();
+ });
+
+ resizeObserver.observe(containerElement);
+ resizeObserver.observe(sliderElement);
+ this._trash.addCallback(() => {
+ resizeObserver.disconnect();
+ });
+ }
+
+ dispose() {
+ this._trash.dispose();
+ }
+}
+
+function recalculatePuckRect(
+ sliderElement: HTMLElement,
+ containerElement: Element,
+ data: Data,
+): void {
+ const {tolerancePx, overdrawPx, callback} = data.opts;
+ if (!data.rect) {
+ const targetPuckRect = getTargetPuckRect(
+ sliderElement,
+ containerElement,
+ overdrawPx,
+ );
+ callback(targetPuckRect);
+ data.rect = targetPuckRect;
+ } else {
+ const viewportRect = containerElement.getBoundingClientRect();
+
+ // Expand the viewportRect by the tolerance
+ const viewportExpandedRect = Geometry.expandRect(viewportRect, tolerancePx);
+
+ const sliderClientRect = sliderElement.getBoundingClientRect();
+ const viewportClamped = Geometry.intersectRects(
+ viewportExpandedRect,
+ sliderClientRect,
+ );
+
+ // Translate the puck rect into client space (currently in slider space)
+ const puckClientRect = Geometry.translateRect(data.rect, {
+ x: sliderClientRect.x,
+ y: sliderClientRect.y,
+ });
+
+ // Check if the tolerance rect entirely contains the expanded viewport rect
+ // If not, request an update
+ if (!Geometry.containsRect(puckClientRect, viewportClamped)) {
+ const targetPuckRect = getTargetPuckRect(
+ sliderElement,
+ containerElement,
+ overdrawPx,
+ );
+ callback(targetPuckRect);
+ data.rect = targetPuckRect;
+ }
+ }
+}
+
+// Returns what the puck rect should look like
+function getTargetPuckRect(
+ sliderElement: HTMLElement,
+ containerElement: Element,
+ overdrawPx: number,
+) {
+ const sliderElementRect = sliderElement.getBoundingClientRect();
+ const containerRect = containerElement.getBoundingClientRect();
+
+ // Calculate the intersection of the container's viewport and the target
+ const intersection = Geometry.intersectRects(
+ containerRect,
+ sliderElementRect,
+ );
+
+ // Pad the intersection by the overdraw amount
+ const intersectionExpanded = Geometry.expandRect(intersection, overdrawPx);
+
+ // Intersect with the original target rect unless we want to avoid resizes
+ const targetRect = Geometry.intersectRects(
+ intersectionExpanded,
+ sliderElementRect,
+ );
+
+ return Geometry.rebaseRect(
+ targetRect,
+ sliderElementRect.x,
+ sliderElementRect.y,
+ );
+}
diff --git a/ui/src/widgets/virtual_table.ts b/ui/src/widgets/virtual_table.ts
new file mode 100644
index 000000000..0b97b9653
--- /dev/null
+++ b/ui/src/widgets/virtual_table.ts
@@ -0,0 +1,262 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import m from 'mithril';
+import {Trash} from '../base/disposable';
+import {findRef, toHTMLElement} from '../base/dom_utils';
+import {Rect} from '../base/geom';
+import {assertExists} from '../base/logging';
+import {Style} from './common';
+import {scheduleFullRedraw} from './raf';
+import {VirtualScrollHelper} from './virtual_scroll_helper';
+
+/**
+ * The |VirtualTable| widget can be useful when attempting to render a large
+ * amount of tabular data - i.e. dumping the entire contents of a database
+ * table.
+ *
+ * A naive approach would be to load the entire dataset from the table and
+ * render it into the DOM. However, this has a number of disadvantages:
+ * - The query could potentially be very slow on large enough datasets.
+ * - The amount of data pulled could be larger than the available memory.
+ * - Rendering thousands of DOM elements using Mithril can get be slow.
+ * - Asking the browser to create and update thousands of elements on the DOM
+ * can also be slow.
+ *
+ * This implementation takes advantage of the fact that computer monitors are
+ * only so tall, so most will only be able to display a small subset of rows at
+ * a given time, and the user will have to scroll to reveal more data.
+ *
+ * Thus, this widgets operates in such a way as to only render the DOM elements
+ * that are visible within the given scrolling container's viewport. To avoid
+ * spamming render updates, we render a few more rows above and below the
+ * current viewport, and only trigger an update once the user scrolls too close
+ * to the edge of the rendered data. These margins and tolerances are
+ * configurable with the |renderOverdrawPx| and |renderTolerancePx| attributes.
+ *
+ * When it comes to loading data, it's often more performant to run fewer large
+ * queries compared to more frequent smaller queries. Running a new query every
+ * time we want to update the DOM is usually too frequent, and results in
+ * flickering as the data is usually not loaded at the time the relevant row
+ * scrolls into view.
+ *
+ * Thus, this implementation employs two sets of limits, one to refresh the DOM
+ * and one larger one to re-query the data. The latter may be configured using
+ * the |queryOverdrawPx| and |queryTolerancePx| attributes.
+ *
+ * The smaller DOM refreshes and handled internally, but the user must be called
+ * to invoke a new query update. When new data is required, the |onReload|
+ * callback is called with the row offset and count.
+ *
+ * The data must be passed in the |data| attribute which contains the offset of
+ * the currently loaded data and a number of rows.
+ *
+ * Row and column content is flexible as m.Children are accepted and passed
+ * straight to mithril.
+ *
+ * The widget is quite opinionated in terms of its styling, but the entire
+ * widget and each row may be tweaked using |className| and |style| attributes
+ * which behave in the same way as they do on other Mithril components.
+ */
+
+export interface VirtualTableAttrs {
+ // A list of columns containing the header row content and column widths
+ columns: VirtualTableColumn[];
+
+ // Row height in px (each row must have the same height)
+ rowHeight: number;
+
+ // Offset of the first row
+ firstRowOffset: number;
+
+ // Total number of rows
+ numRows: number;
+
+ // The row data to render
+ rows: VirtualTableRow[];
+
+ // Optional: Called when we need to reload data
+ onReload?: (rowOffset: number, rowCount: number) => void;
+
+ // Additional class name applied to the table container element
+ className?: string;
+
+ // Additional styles applied to the table container element
+ style?: Style;
+
+ // Optional: Called when a row is hovered, passing the hovered row's id
+ onRowHover?: (id: number) => void;
+
+ // Optional: Called when a row is un-hovered, passing the un-hovered row's id
+ onRowOut?: (id: number) => void;
+
+ // Optional: Number of pixels equivalent of rows to overdraw above and below
+ // the viewport
+ // Defaults to a sensible value
+ renderOverdrawPx?: number;
+
+ // Optional: How close we can get to the edge before triggering a DOM redraw
+ // Defaults to a sensible value
+ renderTolerancePx?: number;
+
+ // Optional: Number of pixels equivalent of rows to query above and below the
+ // viewport
+ // Defaults to a sensible value
+ queryOverdrawPx?: number;
+
+ // Optional: How close we can get to the edge if the loaded data before we
+ // trigger another query
+ // Defaults to a sensible value
+ queryTolerancePx?: number;
+}
+
+export interface VirtualTableColumn {
+ // Content to render in the header row
+ header: m.Children;
+
+ // CSS width e.g. 12px, 4em, etc...
+ width: string;
+}
+
+export interface VirtualTableRow {
+ // Id for this row (must be unique within this dataset)
+ // Used for callbacks and as a Mithril key.
+ id: number;
+
+ // Data for each column in this row - must match number of elements in columns
+ cells: m.Children[];
+
+ // Optional: Additional class name applied to the row element
+ className?: string;
+}
+
+export class VirtualTable implements m.ClassComponent<VirtualTableAttrs> {
+ private readonly CONTAINER_REF = 'CONTAINER';
+ private readonly SLIDER_REF = 'SLIDER';
+ private readonly trash = new Trash();
+ private renderBounds = {rowStart: 0, rowEnd: 0};
+
+ view({attrs}: m.Vnode<VirtualTableAttrs>): m.Children {
+ const {columns, className, numRows, rowHeight, style} = attrs;
+ return m(
+ '.pf-vtable',
+ {className, style, ref: this.CONTAINER_REF},
+ m(
+ '.pf-vtable-content',
+ m(
+ '.pf-vtable-header',
+ columns.map((col) =>
+ m('.pf-vtable-data', {style: {width: col.width}}, col.header),
+ ),
+ ),
+ m(
+ '.pf-vtable-slider',
+ {ref: this.SLIDER_REF, style: {height: `${rowHeight * numRows}px`}},
+ m(
+ '.pf-vtable-puck',
+ {
+ style: {
+ transform: `translateY(${
+ this.renderBounds.rowStart * rowHeight
+ }px)`,
+ },
+ },
+ this.renderContent(attrs),
+ ),
+ ),
+ ),
+ );
+ }
+
+ private renderContent(attrs: VirtualTableAttrs): m.Children {
+ const rows: m.ChildArray = [];
+ for (
+ let i = this.renderBounds.rowStart;
+ i < this.renderBounds.rowEnd;
+ ++i
+ ) {
+ rows.push(this.renderRow(attrs, i));
+ }
+ return rows;
+ }
+
+ private renderRow(attrs: VirtualTableAttrs, i: number): m.Children {
+ const {rows, firstRowOffset, rowHeight, columns, onRowHover, onRowOut} =
+ attrs;
+ if (i >= firstRowOffset && i < firstRowOffset + rows.length) {
+ // Render the row...
+ const index = i - firstRowOffset;
+ const rowData = rows[index];
+ return m(
+ '.pf-vtable-row',
+ {
+ className: rowData.className,
+ style: {height: `${rowHeight}px`},
+ onmouseover: () => {
+ onRowHover?.(rowData.id);
+ },
+ onmouseout: () => {
+ onRowOut?.(rowData.id);
+ },
+ },
+ rowData.cells.map((data, colIndex) =>
+ m('.pf-vtable-data', {style: {width: columns[colIndex].width}}, data),
+ ),
+ );
+ } else {
+ // Render a placeholder div with the same height as a row but a
+ // transparent background
+ return m('', {style: {height: `${rowHeight}px`}});
+ }
+ }
+
+ oncreate({dom, attrs}: m.VnodeDOM<VirtualTableAttrs>) {
+ const {
+ renderOverdrawPx = 200,
+ renderTolerancePx = 100,
+ queryOverdrawPx = 10_000,
+ queryTolerancePx = 5_000,
+ } = attrs;
+
+ const sliderEl = toHTMLElement(assertExists(findRef(dom, this.SLIDER_REF)));
+ const containerEl = assertExists(findRef(dom, this.CONTAINER_REF));
+ const virtualScrollHelper = new VirtualScrollHelper(sliderEl, containerEl, [
+ {
+ overdrawPx: renderOverdrawPx,
+ tolerancePx: renderTolerancePx,
+ callback: ({top, bottom}: Rect) => {
+ const height = bottom - top;
+ const rowStart = Math.floor(top / attrs.rowHeight / 2) * 2;
+ const rowCount = Math.ceil(height / attrs.rowHeight / 2) * 2;
+ this.renderBounds = {rowStart, rowEnd: rowStart + rowCount};
+ scheduleFullRedraw();
+ },
+ },
+ {
+ overdrawPx: queryOverdrawPx,
+ tolerancePx: queryTolerancePx,
+ callback: ({top, bottom}: Rect) => {
+ const rowStart = Math.floor(top / attrs.rowHeight / 2) * 2;
+ const rowEnd = Math.ceil(bottom / attrs.rowHeight);
+ attrs.onReload?.(rowStart, rowEnd - rowStart);
+ },
+ },
+ ]);
+ this.trash.add(virtualScrollHelper);
+ }
+
+ onremove(_: m.VnodeDOM<VirtualTableAttrs>) {
+ this.trash.dispose();
+ }
+}