diff options
author | Steve Golton <stevegolton@google.com> | 2024-05-01 18:27:21 +0100 |
---|---|---|
committer | Steve Golton <stevegolton@google.com> | 2024-05-01 18:27:21 +0100 |
commit | 6ebf16051501bd2fe77fe2e7705a7956077c638a (patch) | |
tree | ab871a4821b12af61cfc8d23f2d46d72d4987c9f | |
parent | a9b411b46dcbb0c08772b06f72eec7b8390210a6 (diff) | |
download | perfetto-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
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", @@ -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", @@ -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 @@ -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 (RcoldStartDur™ ProcessStartAggregation& @@ -59,7 +66,24 @@ $full_trace_process_start_aggregation (2:.perfetto.protos.AndroidBootMetric. explicit_gc_count (RexplicitGcCount$ alloc_gc_count (RallocGcCount$ -mb_per_ms_of_gc (RmbPerMsOfGc +mb_per_ms_of_gc (RmbPerMsOfGc‡ +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 (RminOomAdjDur% +max_oom_adj_dur (RmaxOomAdjDur% +avg_oom_adj_dur (RavgOomAdjDur- +oom_adj_event_count (RoomAdjEventCount$ +oom_adj_reason ( RoomAdjReason Ž ?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 (RminOomAdjDur% +max_oom_adj_dur (RmaxOomAdjDur% +avg_oom_adj_dur (RavgOomAdjDur- +oom_adj_event_count (RoomAdjEventCount$ +oom_adj_reason ( RoomAdjReason +»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.AndroidBatteryMetricRandroidBattB 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 @@ -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(); + } +} |