From 0a53e685b5a2744119b2a7f86cb98bd6c5674480 Mon Sep 17 00:00:00 2001 From: Steve Golton Date: Wed, 28 Aug 2024 15:13:38 +0100 Subject: [PATCH] ui: Merge canary -> stable Change-Id: I8d6728ef08495e3e3dfc111b85c5c423511c23f4 --- .bazelignore | 1 + .bazelrc | 1 + .gitignore | 8 +- Android.bp | 603 +- Android.bp.extras | 35 +- BUILD | 462 +- BUILD.gn | 4 + CHANGELOG | 55 +- LICENSE | 27 + OWNERS | 1 - PRESUBMIT.py | 3 +- TEST_MAPPING | 10 + WORKSPACE | 2 +- bazel/deps.bzl | 1 + bazel/proto_gen.bzl | 12 +- buildtools/.gitignore | 87 +- buildtools/BUILD.gn | 517 +- buildtools/grpc/BUILD.gn | 4155 ++++++--- .../google/protobuf/compiler/csharp/names.h | 22 + .../protobuf/compiler/objectivec/names.h | 22 + docs/analysis/perfetto-sql-syntax.md | 54 + docs/case-studies/memory.md | 40 +- docs/contributing/build-instructions.md | 16 +- docs/contributing/getting-started.md | 13 +- docs/data-sources/native-heap-profiler.md | 11 +- docs/design-docs/continuous-integration.md | 6 +- .../images/java-heap-graph-dominated-size.png | Bin 0 -> 119910 bytes docs/images/java-heap-graph-focus.png | Bin 126643 -> 303622 bytes docs/images/java-heap-graph.png | Bin 85885 -> 231736 bytes docs/instrumentation/tracing-sdk.md | 2 +- docs/reference/perfetto-cli.md | 2 +- docs/reference/synthetic-track-event.md | 1 + .../deep-linking-to-perfetto-ui.md | 2 +- examples/sdk/README.md | 2 +- .../shared_lib/example_shlib_track_event.c | 8 +- gn/BUILD.gn | 10 +- gn/perfetto.gni | 4 + gn/perfetto_integrationtests.gni | 1 + gn/perfetto_unittests.gni | 1 - gn/proto_library.gni | 5 +- gn/standalone/BUILD.gn | 2 +- gn/standalone/protoc.py | 39 +- include/perfetto/base/build_config.h | 2 +- include/perfetto/base/compiler.h | 10 - include/perfetto/base/status.h | 2 +- include/perfetto/ext/base/getopt.h | 2 +- include/perfetto/ext/base/sys_types.h | 2 +- include/perfetto/ext/base/unix_socket.h | 2 +- include/perfetto/ext/base/unix_task_runner.h | 2 +- include/perfetto/ext/base/utils.h | 4 + .../perfetto/ext/traced/sys_stats_counters.h | 5 + include/perfetto/profiling/pprof_builder.h | 3 +- include/perfetto/public/abi/producer_abi.h | 39 +- .../perfetto/public/abi/track_event_hl_abi.h | 89 +- include/perfetto/public/compiler.h | 10 + include/perfetto/public/data_source.h | 3 +- include/perfetto/public/pb_macros.h | 112 +- include/perfetto/public/pb_utils.h | 13 + include/perfetto/public/producer.h | 25 +- .../public/protos/common/builtin_clock.pzc.h | 1 + .../protos/config/data_source_config.pzc.h | 6 + .../public/protos/config/trace_config.pzc.h | 18 + .../trace/android/android_track_event.pzc.h | 47 + .../public/protos/trace/clock_snapshot.pzc.h | 84 + .../trace/interned_data/interned_data.pzc.h | 20 + .../public/protos/trace/trace_packet.pzc.h | 30 +- .../trace/track_event/track_descriptor.pzc.h | 5 + .../trace/track_event/track_event.pzc.h | 6 + include/perfetto/public/te_macros.h | 433 +- include/perfetto/trace_processor/BUILD.gn | 2 +- include/perfetto/trace_processor/read_trace.h | 3 +- .../perfetto/trace_processor/ref_counted.h | 4 +- .../tracing/internal/data_source_internal.h | 4 + .../internal/track_event_data_source.h | 31 +- .../tracing/internal/track_event_macros.h | 4 +- include/perfetto/tracing/tracing.h | 4 +- include/perfetto/tracing/track_event.h | 4 +- infra/ci/Makefile | 31 +- infra/ci/common_utils.py | 106 +- infra/ci/config.py | 39 +- infra/ci/controller/.gcloudignore | 5 + infra/ci/controller/Makefile | 18 +- infra/ci/controller/app.yaml | 10 +- infra/ci/controller/cron.yaml | 26 +- infra/ci/controller/index.yaml | 12 +- .../ci/controller/{controller.py => main.py} | 483 +- infra/ci/controller/queue.yaml | 28 - infra/ci/controller/requirements.txt | 7 + infra/ci/controller/stackdriver_metrics.py | 10 +- infra/ci/frontend/.gcloudignore | 5 + infra/ci/frontend/app.yaml | 6 +- infra/ci/frontend/frontend.py | 77 - infra/ci/frontend/main.py | 98 + infra/ci/frontend/requirements.txt | 3 + infra/ci/frontend/static/script.js | 4 +- infra/ci/sandbox/Dockerfile | 17 +- infra/ci/worker/Dockerfile | 3 +- infra/ci/worker/gce-startup-script.sh | 26 +- infra/ci/worker/run_job.py | 4 +- infra/ci/worker/worker.py | 8 +- infra/luci/PRESUBMIT.py | 9 +- infra/perfetto.dev/build.js | 3 +- meson.build | 2 +- perfetto.rc | 2 +- protos/perfetto/bigtrace/BUILD.gn | 47 + protos/perfetto/bigtrace/orchestrator.proto | 43 + protos/perfetto/bigtrace/worker.proto | 44 + .../perfetto/common/observable_events.proto | 3 + .../perfetto/common/sys_stats_counters.proto | 5 + protos/perfetto/config/android/BUILD.gn | 1 + .../android/android_input_event_config.proto | 178 +- .../config/android/windowmanager_config.proto | 48 + .../perfetto/config/data_source_config.proto | 9 +- .../config/ftrace/ftrace_config.proto | 6 +- protos/perfetto/config/perfetto_config.proto | 295 +- .../config/sys_stats/sys_stats_config.proto | 4 + protos/perfetto/config/trace_config.proto | 47 +- protos/perfetto/ipc/OWNERS | 1 - protos/perfetto/metrics/android/BUILD.gn | 2 + .../android/android_blocking_call.proto | 4 + .../android_blocking_calls_unagg.proto | 15 +- .../metrics/android/android_boot.proto | 37 +- .../android/android_broadcasts_metric.proto | 40 + .../android/android_oom_adjuster_metric.proto | 21 +- .../metrics/android/dma_heap_metric.proto | 11 + .../perfetto/metrics/android/io_metric.proto | 4 +- .../android/java_heap_class_stats.proto | 5 +- .../metrics/android/process_metadata.proto | 8 +- .../metrics/android/startup_metric.proto | 27 +- .../metrics/android/wattson_app_startup.proto | 36 + protos/perfetto/metrics/metrics.proto | 25 +- .../metrics/perfetto_merged_metrics.proto | 201 +- protos/perfetto/trace/android/BUILD.gn | 54 +- .../trace/android/android_input_event.proto | 41 +- .../trace/android/android_track_event.proto | 34 + .../trace/android/app/statusbarmanager.proto | 33 + .../android/app/window_configuration.proto | 35 + .../trace/android/content/activityinfo.proto | 41 + .../trace/android/content/configuration.proto | 84 + .../trace/android/content/locale.proto | 28 + protos/perfetto/trace/android/privacy.proto | 63 + .../android/server/animationadapter.proto | 70 + .../android/server/surfaceanimator.proto | 29 + .../server/windowcontainerthumbnail.proto | 29 + .../android/server/windowmanagerservice.proto | 590 ++ .../trace/android/surfaceflinger_common.proto | 6 + .../trace/android/surfaceflinger_layers.proto | 2 + .../android/surfaceflinger_transactions.proto | 3 + .../trace/android/view/displayinfo.proto | 35 + .../perfetto/trace/android/view/enums.proto | 89 + .../view/remote_animation_target.proto | 45 + .../perfetto/trace/android/view/surface.proto | 28 + .../perfetto/trace/android/viewcapture.proto | 52 + .../trace/android/windowmanager.proto | 58 + protos/perfetto/trace/android/winscope.proto | 4 + .../trace/android/winscope_extensions.proto | 2 +- .../android/winscope_extensions_impl.proto | 6 + protos/perfetto/trace/ftrace/all_protos.gni | 3 + protos/perfetto/trace/ftrace/bcl_exynos.proto | 18 + protos/perfetto/trace/ftrace/dcvsh.proto | 11 + .../perfetto/trace/ftrace/ftrace_event.proto | 42 + protos/perfetto/trace/ftrace/kgsl.proto | 11 + protos/perfetto/trace/ftrace/mali.proto | 115 + .../trace/interned_data/interned_data.proto | 8 +- protos/perfetto/trace/perfetto_trace.proto | 682 +- protos/perfetto/trace/ps/process_stats.proto | 1 + .../perfetto/trace/sys_stats/sys_stats.proto | 8 + protos/perfetto/trace/system_info.proto | 6 + .../perfetto/trace/system_info/cpu_info.proto | 4 + protos/perfetto/trace/trace_packet.proto | 12 +- .../track_event/chrome_frame_reporter.proto | 11 +- .../trace/track_event/track_event.proto | 5 +- .../trace/translation/translation_table.proto | 6 + .../chromium/chrome_track_event.proto | 38 +- protos/third_party/pprof/BUILD.gn | 1 + .../third_party/simpleperf/BUILD.gn | 8 +- .../third_party/simpleperf/record_file.proto | 76 + python/BUILD | 10 + python/BUILD.gn | 8 + python/generators/diff_tests/runner.py | 12 +- python/generators/diff_tests/testing.py | 1 + .../generators/sql_processing/docs_parse.py | 15 +- python/generators/sql_processing/utils.py | 29 +- .../trace_processor_table/serialize.py | 4 +- python/perfetto/bigtrace/api.py | 62 + .../perfetto/bigtrace/orchestrator_pb2.py | 33 + .../perfetto/bigtrace/orchestrator_pb2.pyi | 23 + .../bigtrace/orchestrator_pb2_grpc.py | 91 + .../protos/perfetto/common/descriptor_pb2.py | 52 + .../protos/perfetto/common/descriptor_pb2.pyi | 193 + .../metatrace_categories_pb2.py | 27 + .../metatrace_categories_pb2.pyi | 22 + .../trace_processor/trace_processor_pb2.py | 84 + .../trace_processor/trace_processor_pb2.pyi | 231 + .../perfetto/common/exceptions.py | 9 +- .../perfetto/common/query_result_iterator.py | 161 + .../manifests/trace_processor_shell.py | 62 +- .../perfetto/prebuilts/manifests/tracebox.py | 56 +- .../perfetto/prebuilts/manifests/traceconv.py | 62 +- .../perfetto/prebuilts/perfetto_prebuilts.py | 58 +- python/perfetto/trace_processor/api.py | 155 +- .../trace_processor/metrics.descriptor | 139 +- python/run_tests.py | 14 +- python/test/bigtrace_api_integrationtest.py | 84 + ...t.py => query_result_iterator_unittest.py} | 62 +- python/test/stdlib_unittest.py | 4 +- python/tools/check_imports.py | 15 + python/tools/check_ratchet.py | 2 +- python/tools/heap_profile.py | 5 +- python/tools/record_android_trace.py | 44 +- src/android_internal/statsd_logging.cc | 8 +- src/android_stats/perfetto_atoms.h | 5 +- src/android_stats/statsd_logging_helper.cc | 10 +- src/android_stats/statsd_logging_helper.h | 1 + src/base/http/http_server.cc | 7 - src/base/task_runner_unittest.cc | 2 + src/base/unix_task_runner.cc | 4 +- src/base/utils.cc | 36 + src/base/watchdog_posix.cc | 2 +- src/bigtrace/compose.yaml | 31 + src/bigtrace/orchestrator-deployment.yaml | 35 + src/bigtrace/orchestrator-service.yaml | 27 + src/bigtrace/orchestrator/BUILD.gn | 40 + src/bigtrace/orchestrator/Dockerfile | 29 + .../orchestrator/orchestrator_impl.cc | 88 + src/bigtrace/orchestrator/orchestrator_impl.h | 57 + .../orchestrator/orchestrator_main.cc | 213 + src/bigtrace/worker-deployment.yaml | 34 + src/bigtrace/worker-service.yaml | 26 + src/bigtrace/worker/BUILD.gn | 39 + src/bigtrace/worker/Dockerfile | 26 + src/bigtrace/worker/worker_impl.cc | 54 + src/bigtrace/worker/worker_impl.h | 34 + src/bigtrace/worker/worker_main.cc | 78 + src/perfetto_cmd/perfetto_cmd.cc | 123 +- src/perfetto_cmd/perfetto_cmd.h | 12 +- .../protoc_plugin/protozero_c_plugin.cc | 137 +- .../test/example_proto/test_messages.proto | 4 +- src/shared_lib/producer.cc | 28 +- src/shared_lib/test/api_integrationtest.cc | 420 +- src/shared_lib/test/benchmark.cc | 120 +- src/shared_lib/test/protos/BUILD.gn | 1 + src/shared_lib/test/protos/extensions.pzc.h | 68 + src/shared_lib/track_event.cc | 228 +- src/tools/ftrace_proto_gen/event_list | 26 + .../ftrace_proto_gen/ftrace_proto_gen.cc | 3 +- src/trace_processor/BUILD.gn | 8 + src/trace_processor/containers/bit_vector.cc | 26 + src/trace_processor/containers/bit_vector.h | 5 + .../containers/bit_vector_unittest.cc | 38 +- .../containers/interval_tree.h | 224 +- .../containers/interval_tree_unittest.cc | 29 +- src/trace_processor/containers/row_map.cc | 6 + src/trace_processor/containers/row_map.h | 12 +- src/trace_processor/containers/string_pool.h | 51 +- src/trace_processor/db/column.h | 37 +- src/trace_processor/db/column/BUILD.gn | 1 + .../db/column/arrangement_overlay.cc | 38 +- .../db/column/arrangement_overlay.h | 17 +- .../db/column/arrangement_overlay_unittest.cc | 34 +- src/trace_processor/db/column/data_layer.cc | 51 + src/trace_processor/db/column/data_layer.h | 56 +- .../db/column/dense_null_overlay.cc | 74 +- .../db/column/dense_null_overlay.h | 13 +- .../db/column/dense_null_overlay_unittest.cc | 47 +- .../db/column/dummy_storage.cc | 22 +- src/trace_processor/db/column/dummy_storage.h | 13 +- src/trace_processor/db/column/fake_storage.cc | 50 +- src/trace_processor/db/column/fake_storage.h | 14 +- .../db/column/fake_storage_unittest.cc | 43 - src/trace_processor/db/column/id_storage.cc | 59 +- src/trace_processor/db/column/id_storage.h | 16 +- .../db/column/id_storage_unittest.cc | 10 +- src/trace_processor/db/column/null_overlay.cc | 85 +- src/trace_processor/db/column/null_overlay.h | 13 +- .../db/column/null_overlay_unittest.cc | 55 +- .../db/column/numeric_storage.cc | 134 +- .../db/column/numeric_storage.h | 39 +- .../db/column/numeric_storage_unittest.cc | 10 +- .../db/column/range_overlay.cc | 66 +- src/trace_processor/db/column/range_overlay.h | 13 +- .../db/column/range_overlay_unittest.cc | 19 +- .../db/column/selector_overlay.cc | 62 +- .../db/column/selector_overlay.h | 13 +- .../db/column/selector_overlay_unittest.cc | 49 +- .../db/column/set_id_storage.cc | 66 +- .../db/column/set_id_storage.h | 13 +- .../db/column/set_id_storage_unittest.cc | 10 +- .../db/column/string_storage.cc | 187 +- .../db/column/string_storage.h | 13 +- .../db/column/string_storage_unittest.cc | 20 +- src/trace_processor/db/column/types.h | 10 +- src/trace_processor/db/column/utils.cc | 3 +- src/trace_processor/db/column/utils.h | 3 +- src/trace_processor/db/column_storage.h | 16 + src/trace_processor/db/query_executor.cc | 129 +- .../db/query_executor_benchmark.cc | 38 +- src/trace_processor/db/runtime_table.cc | 208 +- src/trace_processor/db/runtime_table.h | 24 +- src/trace_processor/db/table.cc | 1 + src/trace_processor/db/table.h | 90 + src/trace_processor/export_json_unittest.cc | 21 +- .../forwarding_trace_parser.cc | 272 +- src/trace_processor/forwarding_trace_parser.h | 24 +- .../forwarding_trace_parser_unittest.cc | 1 + .../importers/android_bugreport/BUILD.gn | 38 +- .../android_bugreport_parser.cc | 266 - .../android_bugreport_parser.h | 63 - .../android_bugreport_reader.cc | 179 + .../android_bugreport_reader.h | 67 + .../android_dumpstate_reader.cc | 124 + .../android_dumpstate_reader.h | 52 + .../android_bugreport/android_log_event.cc | 47 + .../android_bugreport/android_log_event.h | 55 + .../android_log_event_parser_impl.cc | 42 + .../android_log_event_parser_impl.h | 43 + .../android_bugreport/android_log_parser.cc | 251 - .../android_bugreport/android_log_parser.h | 82 - .../android_log_parser_unittest.cc | 192 - .../android_bugreport/android_log_reader.cc | 261 + .../android_bugreport/android_log_reader.h | 129 + .../android_bugreport/android_log_unittest.cc | 244 + .../android_bugreport/chunked_line_reader.cc | 109 + .../android_bugreport/chunked_line_reader.h | 52 + src/trace_processor/importers/common/BUILD.gn | 11 + .../importers/common/address_range.h | 127 +- .../common/address_range_unittest.cc | 116 +- .../importers/common/args_tracker.cc | 85 +- .../importers/common/args_tracker.h | 23 + .../async_track_set_tracker_unittest.cc | 3 + .../importers/common/chunked_trace_reader.h | 16 +- .../importers/common/clock_tracker.h | 37 +- .../common/clock_tracker_unittest.cc | 114 + .../importers/common/cpu_tracker.cc | 64 + .../importers/common/cpu_tracker.h | 73 + .../importers/common/event_tracker.cc | 17 +- .../common/event_tracker_unittest.cc | 4 + .../importers/common/global_args_tracker.h | 3 +- .../importers/common/mapping_tracker.cc | 21 +- .../importers/common/mapping_tracker.h | 6 + .../common/process_track_translation_table.cc | 25 + .../common/process_track_translation_table.h | 55 + ...rocess_track_translation_table_unittest.cc | 40 + .../importers/common/process_tracker.cc | 11 + .../importers/common/sched_event_tracker.h | 8 +- .../common/scoped_active_trace_file.cc | 52 + .../common/scoped_active_trace_file.h | 77 + .../importers/common/thread_state_tracker.cc | 5 +- .../common/thread_state_tracker_unittest.cc | 10 +- .../importers/common/trace_file_tracker.cc | 74 + .../importers/common/trace_file_tracker.h | 61 + .../importers/common/trace_parser.cc | 1 + .../importers/common/trace_parser.h | 18 +- .../importers/common/track_tracker.cc | 31 +- .../importers/common/track_tracker.h | 5 +- .../importers/ftrace/ftrace_descriptors.cc | 266 +- .../importers/ftrace/ftrace_parser.cc | 164 +- .../importers/ftrace/ftrace_parser.h | 14 + .../ftrace/ftrace_sched_event_tracker.cc | 12 +- .../ftrace_sched_event_tracker_unittest.cc | 6 +- .../importers/ftrace/ftrace_tokenizer.cc | 9 +- .../importers/ftrace/ftrace_tokenizer.h | 7 +- .../ftrace/mali_gpu_event_tracker.cc | 84 +- .../importers/ftrace/mali_gpu_event_tracker.h | 30 +- .../importers/ftrace/thermal_tracker.cc | 8 +- .../fuchsia/fuchsia_parser_unittest.cc | 20 +- .../fuchsia/fuchsia_trace_tokenizer.cc | 10 +- .../importers/gzip/gzip_trace_parser.h | 15 +- .../importers/ninja/ninja_log_parser.h | 13 +- src/trace_processor/importers/perf/BUILD.gn | 56 +- .../importers/perf/attrs_section_reader.cc | 79 + .../importers/perf/attrs_section_reader.h | 60 + .../importers/perf/dso_tracker.cc | 134 + .../importers/perf/dso_tracker.h | 78 + .../importers/perf/features.cc | 296 + src/trace_processor/importers/perf/features.h | 134 + .../importers/perf/mmap_record.cc | 66 + .../importers/perf/mmap_record.h | 81 + .../importers/perf/perf_counter.cc | 37 + .../importers/perf/perf_counter.h | 51 + .../importers/perf/perf_data_parser.cc | 132 - .../importers/perf/perf_data_parser.h | 51 - .../importers/perf/perf_data_reader.cc | 79 - .../importers/perf/perf_data_reader.h | 187 - .../perf/perf_data_reader_unittest.cc | 234 - .../importers/perf/perf_data_tokenizer.cc | 526 +- .../importers/perf/perf_data_tokenizer.h | 88 +- .../importers/perf/perf_data_tracker.cc | 182 - .../importers/perf/perf_data_tracker.h | 114 - .../perf/perf_data_tracker_unittest.cc | 243 - .../importers/perf/perf_event.h | 3 +- .../importers/perf/perf_event_attr.cc | 39 +- .../importers/perf/perf_event_attr.h | 46 +- .../importers/perf/perf_session.cc | 93 +- .../importers/perf/perf_session.h | 76 +- .../importers/perf/perf_session_unittest.cc | 46 +- src/trace_processor/importers/perf/reader.h | 46 + src/trace_processor/importers/perf/record.h | 2 +- .../importers/perf/record_parser.cc | 349 + .../importers/perf/record_parser.h | 77 + src/trace_processor/importers/perf/sample.cc | 258 + src/trace_processor/importers/perf/sample.h | 71 + src/trace_processor/importers/proto/BUILD.gn | 17 + .../importers/proto/additional_modules.cc | 2 + .../importers/proto/args_parser.cc | 130 + .../winscope_args_parser.h => args_parser.h} | 32 +- .../proto/chrome_system_probes_parser.h | 2 +- .../importers/proto/gpu_event_parser.cc | 21 +- .../importers/proto/network_trace_module.cc | 142 +- .../importers/proto/network_trace_module.h | 16 +- .../proto/network_trace_module_unittest.cc | 12 +- .../importers/proto/perf_sample_tracker.cc | 10 +- .../importers/proto/perf_sample_tracker.h | 15 +- .../proto/perf_sample_tracker_unittest.cc | 8 +- .../importers/proto/pigweed_detokenizer.cc | 313 + .../importers/proto/pigweed_detokenizer.h | 111 + .../importers/proto/pixel_modem_module.cc | 115 + .../importers/proto/pixel_modem_module.h | 53 + .../importers/proto/pixel_modem_parser.cc | 124 + .../importers/proto/pixel_modem_parser.h | 49 + .../importers/proto/profile_module.cc | 15 +- .../proto/proto_trace_parser_impl.cc | 118 +- .../importers/proto/proto_trace_parser_impl.h | 1 - .../proto/proto_trace_parser_impl_unittest.cc | 69 +- .../importers/proto/proto_trace_reader.cc | 266 +- .../importers/proto/proto_trace_reader.h | 18 +- .../proto/proto_trace_reader_unittest.cc | 212 + .../importers/proto/proto_trace_tokenizer.h | 267 +- .../proto/proto_trace_tokenizer_unittest.cc | 149 + .../importers/proto/statsd_module.cc | 108 +- .../importers/proto/system_probes_parser.cc | 139 +- .../importers/proto/system_probes_parser.h | 3 +- .../importers/proto/track_event_parser.cc | 118 +- .../importers/proto/track_event_tracker.cc | 5 +- .../proto/translation_table_module.cc | 15 + .../proto/translation_table_module.h | 1 + .../importers/proto/v8_sequence_state.h | 3 +- .../importers/proto/v8_tracker.cc | 11 +- .../importers/proto/v8_tracker.h | 8 +- .../importers/proto/winscope/BUILD.gn | 10 +- .../winscope/android_input_event_parser.cc | 141 + .../winscope/android_input_event_parser.h | 49 + .../winscope/protolog_message_decoder.cc | 136 + .../proto/winscope/protolog_message_decoder.h | 92 + .../winscope/protolog_messages_tracker.cc | 5 + .../winscope/protolog_messages_tracker.h | 1 + .../proto/winscope/protolog_parser.cc | 273 +- .../proto/winscope/protolog_parser.h | 16 +- .../winscope/shell_transitions_parser.cc | 4 +- .../winscope/surfaceflinger_layers_parser.cc | 9 +- .../winscope/surfaceflinger_layers_parser.h | 3 +- .../surfaceflinger_transactions_parser.cc | 4 +- .../proto/winscope/viewcapture_args_parser.cc | 128 + .../proto/winscope/viewcapture_args_parser.h | 50 + .../proto/winscope/winscope_args_parser.cc | 121 - .../proto/winscope/winscope_module.cc | 68 +- .../proto/winscope/winscope_module.h | 13 +- src/trace_processor/importers/zip/BUILD.gn | 32 + .../importers/zip/zip_trace_reader.cc | 140 + .../importers/zip/zip_trace_reader.h | 77 + src/trace_processor/metrics/metrics.h | 3 + .../metrics/sql/android/BUILD.gn | 2 + .../android_blocking_calls_cuj_metric.sql | 14 +- .../android/android_blocking_calls_unagg.sql | 4 + .../metrics/sql/android/android_boot.sql | 100 +- .../sql/android/android_broadcasts.sql | 88 + .../metrics/sql/android/android_camera.sql | 4 +- .../metrics/sql/android/android_dma_heap.sql | 56 +- .../sql/android/android_lmk_reason.sql | 4 +- .../sql/android/android_oom_adjuster.sql | 53 +- .../metrics/sql/android/android_startup.sql | 2 +- .../metrics/sql/android/jank/frames.sql | 11 +- .../metrics/sql/android/jank/params.sql | 3 +- .../sql/android/java_heap_class_stats.sql | 55 +- .../sql/android/network_activity_template.sql | 7 +- .../metrics/sql/android/process_mem.sql | 99 +- .../metrics/sql/android/process_metadata.sql | 48 +- .../android/startup/slow_start_reasons.sql | 769 +- .../sql/android/wattson_app_startup.sql | 221 + .../experimental/chrome_dropped_frames.sql | 4 +- .../metrics/sql/trace_metadata.sql | 12 - .../engine/perfetto_sql_engine.cc | 189 +- .../perfetto_sql/engine/perfetto_sql_engine.h | 106 +- .../engine/perfetto_sql_engine_unittest.cc | 32 +- .../engine/perfetto_sql_parser.cc | 126 +- .../perfetto_sql/engine/perfetto_sql_parser.h | 27 +- .../engine/perfetto_sql_parser_unittest.cc | 1 + .../engine/perfetto_sql_preprocessor.cc | 583 +- .../engine/perfetto_sql_preprocessor.h | 53 +- .../perfetto_sql_preprocessor_unittest.cc | 270 +- .../engine/perfetto_sql_test_utils.h | 11 + .../engine/runtime_table_function.cc | 4 +- .../engine/table_pointer_module.cc | 5 +- .../intrinsics/functions/BUILD.gn | 11 +- .../intrinsics/functions/clock_functions.h | 2 +- .../perfetto_sql/intrinsics/functions/dfs.cc | 104 - .../perfetto_sql/intrinsics/functions/dfs.h | 54 - .../intrinsics/functions/graph_scan.cc | 661 ++ .../intrinsics/functions/graph_scan.h | 32 + .../intrinsics/functions/graph_traversal.cc | 176 + .../intrinsics/functions/graph_traversal.h | 39 + .../functions/interval_intersect.cc | 355 + .../intrinsics/functions/interval_intersect.h | 32 + .../intrinsics/functions/pprof_functions.cc | 10 +- .../functions/structural_tree_partition.cc | 8 +- .../intrinsics/functions/tables.py | 9 +- .../intrinsics/functions/to_ftrace.cc | 6 +- .../intrinsics/functions/type_builders.cc | 404 + .../intrinsics/functions/type_builders.h | 41 + .../intrinsics/operators/BUILD.gn | 2 + .../operators/interval_intersect_operator.cc | 568 ++ .../operators/interval_intersect_operator.h | 139 + .../operators/span_join_operator.cc | 14 +- .../intrinsics/table_functions/BUILD.gn | 2 - .../table_functions/interval_intersect.cc | 233 - .../table_functions/interval_intersect.h | 83 - .../intrinsics/table_functions/tables.py | 20 +- .../perfetto_sql/intrinsics/types/BUILD.gn | 35 + .../perfetto_sql/intrinsics/types/array.h | 32 + .../perfetto_sql/intrinsics/types/node.h | 32 + .../intrinsics/types/partitioned_intervals.h | 47 + .../intrinsics/types/row_dataframe.h | 58 + .../perfetto_sql/intrinsics/types/struct.h | 37 + .../perfetto_sql/intrinsics/types/value.h | 45 + .../perfetto_sql/prelude/views.sql | 5 + .../perfetto_sql/stdlib/BUILD.gn | 7 +- .../perfetto_sql/stdlib/android/BUILD.gn | 4 + .../perfetto_sql/stdlib/android/auto/BUILD.gn | 4 +- .../stdlib/android/auto/multiuser.sql | 2 +- .../perfetto_sql/stdlib/android/binder.sql | 15 +- .../stdlib/android/broadcasts.sql | 49 +- .../perfetto_sql/stdlib/android/cpu/BUILD.gn | 19 + .../stdlib/android/cpu/cluster_type.sql | 83 + .../perfetto_sql/stdlib/android/dvfs.sql | 4 +- .../stdlib/android/frames/BUILD.gn | 1 + .../stdlib/android/frames/jank_type.sql | 38 + .../android/frames/timeline_maxsdk28.sql | 12 +- .../perfetto_sql/stdlib/android/freezer.sql | 2 +- .../stdlib/android/garbage_collection.sql | 10 +- .../perfetto_sql/stdlib/android/gpu/BUILD.gn | 22 + .../stdlib/android/gpu/frequency.sql | 42 + .../{cpu/cpus.sql => android/gpu/memory.sql} | 27 +- .../perfetto_sql/stdlib/android/input.sql | 73 +- .../stdlib/android/memory/BUILD.gn | 26 + .../stdlib/android/memory/dmabuf.sql | 100 + .../stdlib/android/memory/heap_graph/BUILD.gn | 27 + .../android/memory/heap_graph/class_tree.sql | 50 + .../heap_graph/dominator_class_tree.sql | 34 + .../memory/heap_graph/dominator_tree.sql | 112 + .../memory/heap_graph/excluded_refs.sql | 33 + .../heap_graph_class_aggregation.sql | 139 + .../android/memory/heap_graph/helpers.sql | 145 + .../memory/heap_graph/raw_dominator_tree.sql | 60 + .../android/memory/heap_profile/BUILD.gn | 19 + .../memory/heap_profile/callstacks.sql | 47 + .../stdlib/android/memory/process.sql | 99 + .../stdlib/android/monitor_contention.sql | 172 +- .../stdlib/android/network_packets.sql | 44 +- .../stdlib/android/oom_adjuster.sql | 131 +- .../stdlib/android/power_rails.sql | 54 +- .../stdlib/android/process_metadata.sql | 6 +- .../stdlib/android/startup/startup_events.sql | 8 +- .../stdlib/android/startup/startups.sql | 64 +- .../android/startup/startups_maxsdk28.sql | 29 +- .../android/startup/startups_minsdk29.sql | 7 +- .../android/startup/startups_minsdk33.sql | 4 - .../perfetto_sql/stdlib/android/suspend.sql | 4 +- .../{common/cpus.sql => android/version.sql} | 11 +- .../stdlib/android/winscope/BUILD.gn | 2 + .../winscope/viewcapture.sql} | 22 +- .../stdlib/android/winscope/windowmanager.sql | 29 + .../stdlib/{memory => callstacks}/BUILD.gn | 4 +- .../stdlib/callstacks/stack_profile.sql | 114 + .../stdlib/chrome/chrome_scrolls.sql | 68 +- .../stdlib/chrome/event_latency.sql | 159 + .../stdlib/chrome/interactions.sql | 11 +- .../stdlib/chrome/perfetto_sql_files.gni | 5 + .../stdlib/chrome/scroll_interactions.sql | 66 + .../chrome/scroll_jank/predictor_error.sql | 147 + .../scroll_jank/scroll_jank_intervals.sql | 23 +- .../chrome/scroll_jank/scroll_jank_v3.sql | 154 +- .../chrome/scroll_jank/scroll_offsets.sql | 37 +- .../stdlib/chrome/scroll_jank/utils.sql | 19 +- .../stdlib/chrome/speedometer.sql | 234 +- .../stdlib/chrome/speedometer_2_1.sql | 220 + .../stdlib/chrome/speedometer_3.sql | 206 + .../perfetto_sql/stdlib/chrome/tasks.sql | 21 +- .../perfetto_sql/stdlib/common/BUILD.gn | 2 - .../stdlib/counters/intervals.sql | 18 +- .../perfetto_sql/stdlib/cpu/size.sql | 55 - .../stdlib/deprecated/v42/common/BUILD.gn | 2 - .../deprecated/v42/common/thread_states.sql | 66 - .../perfetto_sql/stdlib/export/BUILD.gn | 24 + .../stdlib/export/to_firefox_profile.sql | 448 + .../perfetto_sql/stdlib/graphs/BUILD.gn | 3 + .../stdlib/graphs/critical_path.sql | 190 + .../perfetto_sql/stdlib/graphs/hierarchy.sql | 33 + .../perfetto_sql/stdlib/graphs/scan.sql | 149 + .../perfetto_sql/stdlib/graphs/search.sql | 81 +- .../stdlib/intervals/intersect.sql | 117 +- .../perfetto_sql/stdlib/intervals/overlap.sql | 142 +- .../perfetto_sql/stdlib/linux/BUILD.gn | 7 +- .../perfetto_sql/stdlib/linux/cpu/BUILD.gn | 12 +- .../{cpu/freq.sql => linux/cpu/frequency.sql} | 5 +- .../stdlib/{ => linux}/cpu/idle.sql | 52 +- .../{sched => linux/cpu}/utilization/BUILD.gn | 2 +- .../cpu}/utilization/general.sql | 27 +- .../cpu}/utilization/process.sql | 42 +- .../cpu}/utilization/system.sql | 78 +- .../cpu}/utilization/thread.sql | 38 +- .../perfetto_sql/stdlib/linux/cpu_idle.sql | 81 - .../perfetto_sql/stdlib/linux/memory/BUILD.gn | 23 + .../stdlib/linux/memory/general.sql | 30 + .../stdlib/linux/memory/high_watermark.sql | 67 + .../stdlib/linux/memory/process.sql | 134 + .../perfetto_sql/stdlib/linux/perf/BUILD.gn | 19 + .../stdlib/linux/perf/samples.sql | 41 + .../memory/heap_graph_dominator_tree.sql | 166 - .../stdlib/{cpu => metasql}/BUILD.gn | 8 +- .../stdlib/metasql/column_list.sql | 34 + .../stdlib/metasql/table_list.sql | 33 + .../perfetto_sql/stdlib/prelude/BUILD.gn | 1 + .../stdlib/prelude/tables_views.sql | 379 + .../perfetto_sql/stdlib/sched/BUILD.gn | 1 - .../stdlib/sched/thread_executing_span.sql | 568 +- .../thread_executing_span_with_slice.sql | 277 +- .../stdlib/sched/thread_level_parallelism.sql | 33 - .../stdlib/sched/time_in_state.sql | 5 +- .../perfetto_sql/stdlib/slices/BUILD.gn | 2 + .../stdlib/slices/flat_slices.sql | 81 +- .../perfetto_sql/stdlib/slices/flow.sql | 44 + .../perfetto_sql/stdlib/slices/hierarchy.sql | 94 + .../perfetto_sql/stdlib/viz/BUILD.gn | 20 + .../perfetto_sql/stdlib/viz/flamegraph.sql | 407 + .../perfetto_sql/stdlib/viz/summary/BUILD.gn | 1 + .../cpus.sql => viz/summary/counters.sql} | 14 +- .../perfetto_sql/stdlib/wattson/BUILD.gn | 7 +- .../perfetto_sql/stdlib/wattson/cpu_freq.sql | 92 - .../perfetto_sql/stdlib/wattson/cpu_idle.sql | 124 +- .../perfetto_sql/stdlib/wattson/cpu_split.sql | 220 + .../stdlib/wattson/curves/device.sql | 578 ++ .../stdlib/wattson/curves/grouped.sql | 66 + .../stdlib/wattson/curves/ungrouped.sql | 236 + .../stdlib/wattson/curves/utils.sql | 93 + .../stdlib/wattson/device_infos.sql | 120 + .../stdlib/wattson/system_state.sql | 43 +- src/trace_processor/read_trace.cc | 2 +- src/trace_processor/read_trace_internal.cc | 4 +- src/trace_processor/rpc/rpc.cc | 29 +- src/trace_processor/sorter/BUILD.gn | 2 + src/trace_processor/sorter/trace_sorter.cc | 48 +- src/trace_processor/sorter/trace_sorter.h | 47 +- src/trace_processor/sqlite/BUILD.gn | 1 - src/trace_processor/sqlite/bindings/BUILD.gn | 6 + .../sqlite/bindings/sqlite_bind.h | 50 + .../sqlite/bindings/sqlite_column.h | 71 + .../sqlite/bindings/sqlite_function.h | 51 + .../sqlite/bindings/sqlite_result.h | 13 +- .../sqlite/bindings/sqlite_stmt.h | 33 + .../sqlite/bindings/sqlite_type.h | 34 + .../sqlite/bindings/sqlite_value.h | 57 + src/trace_processor/sqlite/db_sqlite_table.cc | 95 +- src/trace_processor/sqlite/db_sqlite_table.h | 4 +- .../sqlite/module_lifecycle_manager.h | 5 + src/trace_processor/sqlite/sql_source.cc | 4 +- src/trace_processor/sqlite/sqlite_utils.h | 48 +- src/trace_processor/sqlite/sqlite_value.h | 41 - src/trace_processor/storage/metadata.h | 2 + src/trace_processor/storage/stats.h | 41 +- src/trace_processor/storage/trace_storage.h | 73 + src/trace_processor/tables/android_tables.py | 93 +- src/trace_processor/tables/counter_tables.py | 18 +- src/trace_processor/tables/metadata_tables.py | 128 +- src/trace_processor/tables/profiler_tables.py | 53 +- src/trace_processor/tables/sched_tables.py | 55 +- src/trace_processor/tables/slice_tables.py | 91 +- .../tables/table_destructors.cc | 10 +- src/trace_processor/tables/track_tables.py | 43 +- src/trace_processor/tables/winscope_tables.py | 49 +- .../trace_database_integrationtest.cc | 51 +- .../trace_processor_context.cc | 18 + src/trace_processor/trace_processor_impl.cc | 350 +- src/trace_processor/trace_processor_impl.h | 2 +- src/trace_processor/trace_processor_shell.cc | 52 +- .../trace_processor_storage_impl.cc | 35 +- .../trace_processor_storage_impl.h | 6 + src/trace_processor/trace_reader_registry.cc | 72 + src/trace_processor/trace_reader_registry.h | 74 + src/trace_processor/types/BUILD.gn | 1 + .../types/trace_processor_context.h | 47 +- src/trace_processor/util/BUILD.gn | 27 +- src/trace_processor/util/file_buffer.cc | 119 - src/trace_processor/util/file_buffer.h | 89 - src/trace_processor/util/gzip_utils.cc | 37 +- src/trace_processor/util/gzip_utils.h | 14 +- src/trace_processor/util/profile_builder.cc | 6 +- src/trace_processor/util/sql_argument.cc | 18 +- .../util/trace_blob_view_reader.cc | 122 + .../util/trace_blob_view_reader.h | 91 + ....cc => trace_blob_view_reader_unittest.cc} | 44 +- src/trace_processor/util/trace_type.cc | 215 + src/trace_processor/util/trace_type.h | 48 + src/trace_processor/util/zip_reader.cc | 401 +- src/trace_processor/util/zip_reader.h | 47 +- .../util/zip_reader_unittest.cc | 62 +- src/trace_redaction/BUILD.gn | 74 +- ...oardphase_packet_filter_integrationtest.cc | 135 + .../broadphase_packet_filter.cc | 104 + .../broadphase_packet_filter.h | 55 + .../broadphase_packet_filter_unittest.cc | 207 + src/trace_redaction/collect_frame_cookies.cc | 47 +- src/trace_redaction/collect_frame_cookies.h | 12 +- .../collect_frame_cookies_integrationtest.cc | 115 + .../collect_frame_cookies_unittest.cc | 499 +- src/trace_redaction/collect_system_info.cc | 44 +- src/trace_redaction/collect_system_info.h | 4 + .../collect_system_info_unittest.cc | 12 +- .../collect_timeline_events.cc | 6 +- .../collect_timeline_events_unittest.cc | 240 +- .../filter_ftrace_using_allowlist.cc | 51 - .../filter_ftrace_using_allowlist.h | 49 - ..._ftrace_using_allowlist_integrationtest.cc | 163 - .../filter_ftrace_using_allowlist_unittest.cc | 245 - .../filter_packet_using_allowlist.h | 40 - .../filter_packet_using_allowlist_unittest.cc | 72 - src/trace_redaction/filter_print_events.cc | 61 - src/trace_redaction/filter_print_events.h | 46 - .../filter_sched_waking_events.cc | 82 - .../filter_sched_waking_events.h | 61 - ...ter_sched_waking_events_integrationtest.cc | 43 +- .../filter_sched_waking_events_unittest.cc | 254 +- src/trace_redaction/filter_task_rename.cc | 74 - .../filter_task_rename_integrationtest.cc | 101 +- .../filter_task_rename_unittest.cc | 155 - ...packet_using_allowlist.cc => filtering.cc} | 29 +- src/trace_redaction/filtering.h | 58 + src/trace_redaction/find_package_uid.cc | 35 +- .../find_package_uid_unittest.cc | 4 +- src/trace_redaction/main.cc | 83 +- src/trace_redaction/merge_threads.cc | 48 + .../{filter_task_rename.h => merge_threads.h} | 24 +- src/trace_redaction/modify.cc | 58 + src/trace_redaction/modify.h | 73 + src/trace_redaction/modify_process_trees.cc | 114 - src/trace_redaction/modify_process_trees.h | 70 - src/trace_redaction/populate_allow_lists.cc | 134 +- .../process_thread_timeline.cc | 137 +- src/trace_redaction/process_thread_timeline.h | 10 +- ...process_thread_timeline_integrationtest.cc | 108 + .../process_thread_timeline_unittest.cc | 160 +- src/trace_redaction/prune_package_list.cc | 78 +- src/trace_redaction/prune_package_list.h | 5 + .../prune_package_list_integrationtest.cc | 22 +- src/trace_redaction/redact_ftrace_event.cc | 106 - src/trace_redaction/redact_ftrace_event.h | 80 - src/trace_redaction/redact_ftrace_events.cc | 184 + src/trace_redaction/redact_ftrace_events.h | 79 + src/trace_redaction/redact_process_events.cc | 452 + src/trace_redaction/redact_process_events.h | 120 + .../redact_process_events_unittest.cc | 821 ++ src/trace_redaction/redact_process_free.cc | 77 - src/trace_redaction/redact_process_free.h | 41 - .../redact_process_free_unittest.cc | 100 - src/trace_redaction/redact_process_trees.cc | 197 + src/trace_redaction/redact_process_trees.h | 90 + .../redact_process_trees_integrationtest.cc | 245 + src/trace_redaction/redact_sched_events.cc | 626 ++ src/trace_redaction/redact_sched_events.h | 121 + ...=> redact_sched_events_integrationtest.cc} | 110 +- .../redact_sched_events_unittest.cc | 801 ++ src/trace_redaction/redact_sched_switch.cc | 134 - src/trace_redaction/redact_sched_switch.h | 41 - .../redact_sched_switch_unittest.cc | 202 - src/trace_redaction/redact_task_newtask.cc | 114 - src/trace_redaction/redact_task_newtask.h | 41 - .../redact_task_newtask_unittest.cc | 167 - .../remap_scheduling_events.cc | 265 - src/trace_redaction/remap_scheduling_events.h | 137 - ...remap_scheduling_events_integrationtest.cc | 292 - .../remap_scheduling_events_unittest.cc | 441 - src/trace_redaction/scrub_ftrace_events.cc | 107 - src/trace_redaction/scrub_ftrace_events.h | 80 - .../scrub_ftrace_events_integrationtest.cc | 144 - src/trace_redaction/scrub_process_stats.cc | 82 +- src/trace_redaction/scrub_process_stats.h | 21 + .../scrub_process_stats_integrationtest.cc | 13 +- src/trace_redaction/scrub_process_trees.cc | 111 - src/trace_redaction/scrub_process_trees.h | 50 - .../scrub_process_trees_integrationtest.cc | 103 - src/trace_redaction/scrub_trace_packet.cc | 71 - src/trace_redaction/scrub_trace_packet.h | 55 - src/trace_redaction/suspend_resume.cc | 65 - src/trace_redaction/suspend_resume.h | 43 - .../suspend_resume_unittest.cc | 149 - .../trace_processor_integrationtest.cc | 305 + .../trace_redaction_framework.h | 122 +- .../trace_redaction_integration_fixture.cc | 18 +- .../trace_redaction_integration_fixture.h | 12 +- src/trace_redaction/trace_redactor.cc | 135 + src/trace_redaction/trace_redactor.h | 9 + src/trace_redaction/verify_integrity.cc | 174 + src/trace_redaction/verify_integrity.h | 46 + .../verify_integrity_unittest.cc | 347 + src/traceconv/BUILD.gn | 21 +- src/traceconv/main.cc | 42 +- src/traceconv/pprof_builder.cc | 234 +- src/traceconv/pprof_reader.cc | 181 + src/traceconv/pprof_reader.h | 66 + src/traceconv/symbolize_profile.cc | 2 +- src/traceconv/trace_to_firefox.cc | 74 + src/traceconv/trace_to_firefox.h | 33 + .../trace_to_pprof_integrationtest.cc | 174 + src/traceconv/trace_to_profile.cc | 15 + src/traceconv/trace_to_profile.h | 7 + src/traceconv/trace_to_text.cc | 5 +- ...st.cc => trace_to_text_integrationtest.cc} | 0 src/traced/probes/ftrace/OWNERS | 1 - src/traced/probes/ftrace/atrace_wrapper.cc | 28 +- src/traced/probes/ftrace/atrace_wrapper.h | 2 + src/traced/probes/ftrace/cpu_reader.cc | 9 +- .../probes/ftrace/cpu_reader_benchmark.cc | 2 + src/traced/probes/ftrace/cpu_reader_fuzzer.cc | 1 + .../probes/ftrace/cpu_reader_unittest.cc | 5 +- src/traced/probes/ftrace/event_info.cc | 428 + .../probes/ftrace/event_info_constants.cc | 2 + .../probes/ftrace/event_info_constants.h | 3 + .../probes/ftrace/ftrace_config_muxer.cc | 116 +- .../probes/ftrace/ftrace_config_muxer.h | 31 +- .../ftrace/ftrace_config_muxer_unittest.cc | 101 +- .../ftrace/ftrace_controller_unittest.cc | 24 +- src/traced/probes/ftrace/ftrace_procfs.cc | 8 +- .../probes/ftrace/proto_translation_table.cc | 18 +- .../events/bcl_exynos/bcl_irq_trigger/format | 19 + .../synthetic/events/dcvsh/dcvsh_freq/format | 12 + .../events/kgsl/gpu_frequency/format | 12 + .../format | 13 + .../mali_PM_MCU_HCTL_CORES_NOTIFY_PEND/format | 13 + .../format | 13 + .../mali_PM_MCU_HCTL_MCU_ON_RECHECK/format | 13 + .../format | 13 + .../mali_PM_MCU_HCTL_SHADERS_PEND_OFF/format | 13 + .../mali_PM_MCU_HCTL_SHADERS_PEND_ON/format | 13 + .../mali_PM_MCU_HCTL_SHADERS_READY_OFF/format | 13 + .../events/mali/mali_PM_MCU_IN_SLEEP/format | 13 + .../events/mali/mali_PM_MCU_OFF/format | 13 + .../events/mali/mali_PM_MCU_ON/format | 13 + .../format | 13 + .../mali_PM_MCU_ON_GLB_REINIT_PEND/format | 13 + .../events/mali/mali_PM_MCU_ON_HALT/format | 13 + .../mali/mali_PM_MCU_ON_HWCNT_DISABLE/format | 13 + .../mali/mali_PM_MCU_ON_HWCNT_ENABLE/format | 13 + .../mali/mali_PM_MCU_ON_PEND_HALT/format | 13 + .../mali/mali_PM_MCU_ON_PEND_SLEEP/format | 13 + .../mali/mali_PM_MCU_ON_SLEEP_INITIATE/format | 13 + .../events/mali/mali_PM_MCU_PEND_OFF/format | 13 + .../mali/mali_PM_MCU_PEND_ON_RELOAD/format | 13 + .../events/mali/mali_PM_MCU_POWER_DOWN/format | 13 + .../events/mali/mali_PM_MCU_RESET_WAIT/format | 13 + .../probes/ps/process_stats_data_source.cc | 6 + .../probes/ps/process_stats_data_source.h | 1 + .../probes/sys_stats/sys_stats_data_source.cc | 118 +- .../probes/sys_stats/sys_stats_data_source.h | 13 +- .../sys_stats_data_source_unittest.cc | 78 +- .../system_info/system_info_data_source.cc | 13 + .../system_info_data_source_unittest.cc | 16 + src/traced/service/builtin_producer.cc | 3 +- src/traced_relay/relay_service.cc | 20 + src/traced_relay/relay_service.h | 6 + src/traced_relay/relay_service_main.cc | 43 +- src/tracing/OWNERS | 1 - src/tracing/internal/tracing_muxer_impl.cc | 7 +- .../ipc/producer/producer_ipc_client_impl.cc | 2 +- src/tracing/service/tracing_service_impl.cc | 152 +- src/tracing/service/tracing_service_impl.h | 11 +- .../service/tracing_service_impl_unittest.cc | 365 +- src/tracing/test/mock_consumer.cc | 14 +- src/tracing/test/mock_consumer.h | 10 +- src/websocket_bridge/websocket_bridge.cc | 8 +- test/.gitignore | 12 + test/ci/android_tests.sh | 8 +- test/ci/bazel_tests.sh | 7 + test/ci/common.sh | 10 +- test/ci/fuzzer_tests.sh | 7 + test/ci/linux_tests.sh | 19 +- test/ci/ui_tests.sh | 1 - test/cts/heapprofd_java_test_cts.cc | 11 +- test/cts/reporter/Android.bp | 1 + .../data/android_calculator_startup.pb.sha256 | 1 + ...e_input_with_frame_view_new.pftrace.sha256 | 1 + test/data/chrome/cpu_powerups_1.pb.sha256 | 2 +- .../scroll_jank_with_pinch.pftrace.sha256 | 1 + ...> speedometer_21.perfetto_trace.gz.sha256} | 0 .../speedometer_3.perfetto_trace.gz.sha256 | 1 + test/data/heap_graph/heap_graph.pb.sha256 | 1 + .../heap_graph/heap_graph.textproto.sha256 | 1 + .../heap_graph/heap_graph_branching.pb.sha256 | 1 + .../heap_graph_branching.textproto.sha256 | 1 + .../heap_graph/heap_graph_huge_size.pb.sha256 | 1 + .../heap_graph_huge_size.textproto.sha256 | 1 + test/data/multi_machine_trace.pb.sha256 | 1 + .../linux_perf_with_symbols.zip.sha256 | 1 + test/data/simpleperf/perf.data.sha256 | 1 + .../perf_with_add_counter.data.sha256 | 1 + .../perf_with_synthetic_events.data.sha256 | 1 + ...trace-redaction-api-capture.pftrace.sha256 | 1 + ...android_trace_30s_expand_camera.png.sha256 | 2 +- .../ui-android_trace_30s_load.png.sha256 | 2 +- ...chrome_missing_track_names_load.png.sha256 | 2 +- ...ing_desktop_expand_browser_proc.png.sha256 | 2 +- ...i-chrome_rendering_desktop_load.png.sha256 | 2 +- ...desktop_select_slice_with_flows.png.sha256 | 2 +- ...-features_track_debuggable_chip.png.sha256 | 1 + .../ui-modal_dialog_dismiss_1.png.sha256 | 2 +- .../ui-modal_dialog_dismiss_2.png.sha256 | 2 +- .../ui-modal_dialog_show_dialog_1.png.sha256 | 2 +- .../ui-modal_dialog_show_dialog_2.png.sha256 | 2 +- ...al_dialog_switch_page_no_dialog.png.sha256 | 2 +- ...igate_navigate_back_and_forward.png.sha256 | 2 +- ...ng_navigate_open_trace_from_url.png.sha256 | 2 +- ...n_invalid_trace_from_blank_page.png.sha256 | 2 +- ...ace_and_go_back_to_landing_page.png.sha256 | 2 +- ...ack_access_subpage_then_go_back.png.sha256 | 2 +- ..._back_open_first_trace_from_url.png.sha256 | 2 +- ...back_open_second_trace_from_url.png.sha256 | 2 +- ...no_trace_go_back_to_first_trace.png.sha256 | 2 +- ..._trace_go_to_page_with_no_trace.png.sha256 | 2 +- ...rom_no_trace_open_invalid_trace.png.sha256 | 2 +- ...from_no_trace_open_second_trace.png.sha256 | 2 +- ...start_from_no_trace_open_trace_.png.sha256 | 2 +- ...ing_start_from_no_trace_refresh.png.sha256 | 2 +- test/data/zip/perf_track_sym.zip.sha256 | 1 + test/gtest_and_gmock.h | 1 + test/trace_processor/PRESUBMIT.py | 2 +- .../diff_tests/include_index.py | 57 +- .../metrics/android/ad_services_metric.py | 6 +- .../android/android_auto_multiuser.textproto | 72 +- .../metrics/android/android_binder_metric.out | 260 + .../android_blocking_calls_cuj_metric.py | 1 + ...roid_blocking_calls_on_jank_cuj_metric.out | 10 - .../android/android_blocking_calls_unagg.out | 66 + .../metrics/android/android_boot.out | 300 + .../metrics/android/android_broadcasts.out | 302 + .../diff_tests/metrics/android/tests.py | 143 +- .../metrics/chrome/tests_scroll_jank.py | 53 +- .../metrics/graphics/android_jank_cuj.out | 10 - .../metrics/memory/android_lmk_reason.out | 10 - .../diff_tests/metrics/memory/tests.py | 101 +- .../profiling/java_heap_class_stats.out | 5 + .../metrics/startup/android_startup.out | 30 +- .../startup/android_startup_attribution.out | 69 +- .../startup/android_startup_attribution.py | 2 +- .../android_startup_attribution_slow.out | 88 +- .../android_startup_attribution_slow.py | 2 +- .../startup/android_startup_breakdown.out | 128 +- .../android_startup_breakdown_slow.out | 109 +- .../android_startup_broadcast_multiple.out | 62 +- .../android_startup_installd_dex2oat.out | 52 +- .../android_startup_installd_dex2oat_slow.out | 91 +- .../android_startup_lock_contention.out | 5 - .../android_startup_lock_contention_slow.out | 54 +- .../startup/android_startup_minsdk33.out | 29 +- .../startup/android_startup_process_track.out | 56 +- .../metrics/startup/android_startup_slow.out | 86 +- .../startup/android_startup_unlock.out | 18 +- .../metrics/startup/ttid_and_ttfd.out | 2 +- .../android/android_bugreport_logs_test.out | 400 +- .../android/input_event_trace.textproto | 1584 ++++ .../parser/android/protolog.textproto | 49 + .../android/tests_android_input_event.py | 284 + .../parser/android/tests_bugreport.py | 16 + .../parser/android/tests_protolog.py | 1 + .../parser/android/tests_viewcapture.py | 81 + .../parser/android/tests_windowmanager.py | 65 + .../parser/android/viewcapture.textproto | 128 + .../parser/android/windowmanager.textproto | 8113 +++++++++++++++++ .../diff_tests/parser/json/tests.py | 57 +- .../parsing/print_systrace_unsigned.out | 2 +- .../parser/parsing/systrace_html.out | 4 +- .../diff_tests/parser/parsing/tests.py | 161 +- .../parser/parsing/tests_traced_stats.py | 104 + .../parser/power/tests_power_rails.py | 2 +- .../process_tracking/process_tracking_exec.py | 11 +- .../synth_process_tracking.py | 2 +- .../diff_tests/parser/sched/tests.py | 31 +- .../parser/simpleperf/clocks_align_test.sql | 99 + .../parser/simpleperf/perf_test.sql | 45 + .../simpleperf/perf_with_add_counter_test.sql | 67 + .../parser/simpleperf/stacks_test.sql | 48 + .../diff_tests/parser/simpleperf/tests.py | 247 + .../parser/track_event/legacy_async_event.out | 10 +- .../diff_tests/parser/track_event/tests.py | 2 +- .../process_track_name.textproto | 100 + .../parser/translated_args/tests.py | 19 + .../diff_tests/parser/zip/tests.py | 76 + .../stdlib/android/cpu_cluster_tests.py | 314 + .../diff_tests/stdlib/android/gpu.py | 58 + .../heap_graph_for_aggregation.textproto | 83 + .../heap_graph_for_dominator_tree.textproto | 0 .../heap_graph_tests.py} | 32 +- .../diff_tests/stdlib/android/memory.py | 144 + .../stdlib/android/startups_tests.py | 32 +- .../diff_tests/stdlib/android/tests.py | 20 +- .../stdlib/chrome/chrome_speedometer.out | 11 - .../stdlib/chrome/scroll_jank_v3.out | 7 +- .../stdlib/chrome/scroll_jank_v3_new.out | 9 + .../chrome/scroll_jank_v3_percentage.out | 2 +- .../diff_tests/stdlib/chrome/tests.py | 95 +- .../stdlib/chrome/tests_scroll_jank.py | 131 +- .../diff_tests/stdlib/common/tests.py | 26 - .../diff_tests/stdlib/counters/tests.py | 12 +- .../diff_tests/stdlib/cpu/tests.py | 84 - .../diff_tests/stdlib/dynamic_tables/tests.py | 7 +- .../stdlib/export/firefox_profile.out | 2 + .../diff_tests/stdlib/export/tests.py | 29 + .../stdlib/graphs/critical_path_tests.py | 104 + .../diff_tests/stdlib/graphs/scan_tests.py | 227 + .../diff_tests/stdlib/graphs/search_tests.py | 132 +- .../stdlib/intervals/intersect_tests.py | 456 +- .../diff_tests/stdlib/intervals/tests.py | 326 +- .../diff_tests/stdlib/linux/cpu.py | 326 + .../diff_tests/stdlib/linux/memory.py | 68 + .../diff_tests/stdlib/linux/tests.py | 99 - .../diff_tests/stdlib/metasql/column_list.py | 38 + .../diff_tests/stdlib/metasql/table_list.py | 80 + .../diff_tests/stdlib/sched/tests.py | 102 - .../diff_tests/stdlib/slices/tests.py | 53 +- .../diff_tests/stdlib/slices/trace.py | 4 +- .../diff_tests/stdlib/wattson/tests.py | 260 +- .../diff_tests/syntax/filtering_tests.py | 43 + .../diff_tests/syntax/macro_tests.py | 20 + .../diff_tests/syntax/table_tests.py | 117 +- .../diff_tests/tables/tests.py | 24 +- .../diff_tests/tables/tests_counters.py | 58 +- .../diff_tests/tables/tests_sched.py | 706 +- tools/bisect_ui_releases | 116 + tools/check_sql_metrics.py | 43 +- tools/check_sql_modules.py | 19 +- tools/cpu_profile | 120 +- tools/diff_test_trace_processor.py | 11 +- tools/gen_android_bp | 5 +- tools/gen_bigtrace_grpc_protos.py | 62 + tools/gen_c_protos | 3 + tools/gen_grpc_build_gn.py | 58 +- tools/gn_utils.py | 17 +- tools/heap_profile | 125 +- tools/install-build-deps | 21 +- tools/install_test_reporter_app | 58 +- tools/pnpm | 5 +- tools/record_android_trace | 158 +- tools/run_buildtools_binary.py | 6 +- tools/serialize_test_trace.py | 8 +- tools/setup_minikube_cluster.sh | 29 + tools/ssh_into_gce_vm | 88 + tools/trace_processor | 118 +- tools/tracebox | 112 +- tools/traceconv | 118 +- ui/.eslintignore | 8 - ui/.eslintrc.js | 16 +- ui/.prettierignore | 5 +- ui/PRESUBMIT.py | 67 +- ui/build.js | 31 +- ui/config/JestJsdomEnv.js | 30 + ui/config/integrationtest_env.js | 2 +- ui/config/jest.unittest.config.js | 6 +- ui/config/rollup.config.js | 49 +- ui/eslint.config.js | 132 + ui/format-sources | 131 + ui/npm | 3 + ui/package.json | 89 +- ui/pnpm | 3 + ui/pnpm-lock.yaml | 4552 +++++---- ui/release/OWNERS | 1 - ui/release/channels.json | 4 +- ui/src/assets/brand.png | Bin 4000 -> 3260 bytes ui/src/assets/common.scss | 17 +- ui/src/assets/details.scss | 12 - ui/src/assets/favicon.png | Bin 2238 -> 1966 bytes ui/src/assets/index.html | 9 +- ui/src/assets/logo-128.png | Bin 14930 -> 12078 bytes ui/src/assets/logo-3d.png | Bin 48137 -> 18376 bytes ui/src/assets/modal.scss | 3 +- ui/src/assets/perfetto.scss | 18 +- ui/src/assets/rec_atrace.png | Bin 43695 -> 12307 bytes ui/src/assets/rec_battery_counters.png | Bin 27414 -> 10237 bytes ui/src/assets/rec_board_voltage.png | Bin 28263 -> 9027 bytes ui/src/assets/rec_cpu_coarse.png | Bin 53670 -> 15696 bytes ui/src/assets/rec_cpu_fine.png | Bin 84341 -> 23575 bytes ui/src/assets/rec_cpu_freq.png | Bin 29285 -> 9035 bytes ui/src/assets/rec_cpu_voltage.png | Bin 24619 -> 8265 bytes ui/src/assets/rec_frame_timeline.png | Bin 25975 -> 11159 bytes ui/src/assets/rec_ftrace.png | Bin 22382 -> 7418 bytes ui/src/assets/rec_gpu_mem_total.png | Bin 52638 -> 16501 bytes ui/src/assets/rec_java_heap_dump.png | Bin 62897 -> 12633 bytes ui/src/assets/rec_lmk.png | Bin 43733 -> 12519 bytes ui/src/assets/rec_logcat.png | Bin 42205 -> 13216 bytes ui/src/assets/rec_long_trace.png | Bin 21705 -> 9940 bytes ui/src/assets/rec_mem_hifreq.png | Bin 16988 -> 6043 bytes ui/src/assets/rec_meminfo.png | Bin 58545 -> 16605 bytes ui/src/assets/rec_native_heap_profiler.png | Bin 65155 -> 12943 bytes ui/src/assets/rec_one_shot.png | Bin 23210 -> 9445 bytes ui/src/assets/rec_profiling.png | Bin 65111 -> 17686 bytes ui/src/assets/rec_ps_stats.png | Bin 72069 -> 19929 bytes ui/src/assets/rec_ring_buf.png | Bin 24968 -> 10420 bytes ui/src/assets/rec_syscalls.png | Bin 29115 -> 12154 bytes ui/src/assets/rec_vmstat.png | Bin 49966 -> 14651 bytes ui/src/assets/record.scss | 8 +- ui/src/assets/scheduling_latency.png | Bin 6172 -> 5446 bytes ui/src/assets/sidebar.scss | 3 +- ui/src/assets/topbar.scss | 22 +- ui/src/assets/track_panel.scss | 11 +- ui/src/assets/typefaces.scss | 12 +- ui/src/assets/widgets/anchor.scss | 4 +- ui/src/assets/widgets/button.scss | 4 +- ui/src/assets/widgets/checkbox.scss | 6 +- ui/src/assets/widgets/flamegraph.scss | 88 + ui/src/assets/widgets/popup.scss | 4 + ui/src/assets/widgets/select.scss | 4 +- ui/src/assets/widgets/tag_input.scss | 73 + ui/src/assets/widgets/text_input.scss | 4 +- ui/src/assets/widgets/tree.scss | 3 +- ui/src/base/disposable.ts | 86 - .../index.js => disposable_polyfill.ts} | 11 +- ui/src/base/disposable_stack.ts | 159 + ui/src/base/disposable_unittest.ts | 45 +- ui/src/base/errors.ts | 8 +- ui/src/base/hotkeys.ts | 5 +- ui/src/base/http_utils.ts | 24 + ui/src/base/logging.ts | 3 +- ui/src/base/registry.ts | 4 +- ui/src/base/resize_observer.ts | 4 +- ui/src/base/semantic_icons.ts | 1 + ui/src/base/shared_disposable.ts | 133 + ui/src/base/shared_disposable_unittest.ts | 97 + ui/src/base/store.ts | 65 +- ui/src/base/store_unittest.ts | 8 +- ui/src/base/string_utils.ts | 64 +- ui/src/base/string_utils_unittest.ts | 39 +- ui/src/base/time.ts | 40 +- ui/src/base/time_unittest.ts | 73 +- ui/src/base/utils.ts | 8 + ui/src/base/utils/index.d.ts | 4 - ui/src/base/utils/package.json | 8 - ui/src/base/validators.ts | 309 - ui/src/base/validators_unittest.ts | 136 - .../chrome_tracing_controller.ts | 7 +- ui/src/chrome_extension/index.ts | 2 +- ui/src/chrome_extension/manifest.json | 11 +- ui/src/common/actions.ts | 358 +- ui/src/common/actions_unittest.ts | 89 +- ui/src/common/addEphemeralTab.ts | 30 + ui/src/common/arg_types.ts | 2 +- ui/src/common/cache_manager.ts | 10 +- ui/src/common/cache_utils.ts | 3 +- ui/src/common/canvas_utils.ts | 36 +- ui/src/common/canvas_utils_unittest.ts | 41 - ui/src/common/channels.ts | 2 +- ui/src/common/commands.ts | 1 - ui/src/common/empty_state.ts | 14 +- ui/src/common/gcs_uploader.ts | 207 + ui/src/common/high_precision_time.ts | 412 +- ui/src/common/high_precision_time_span.ts | 203 + .../high_precision_time_span_unittest.ts | 156 + ui/src/common/high_precision_time_unittest.ts | 352 +- ...ttest.ts => legacy_flamegraph_unittest.ts} | 3 +- ...raph_util.ts => legacy_flamegraph_util.ts} | 81 +- ui/src/common/metatracing.ts | 52 +- ui/src/common/plugins.ts | 121 +- ui/src/common/queries.ts | 77 +- .../common/recordingV2/adb_connection_impl.ts | 9 +- .../adb_connection_over_websocket.ts | 14 +- .../recordingV2/adb_connection_over_webusb.ts | 22 +- ui/src/common/recordingV2/adb_file_handler.ts | 10 +- .../recordingV2/recording_interfaces_v2.ts | 4 +- ui/src/common/recordingV2/recording_utils.ts | 6 + .../target_factories/chrome_target_factory.ts | 8 +- .../host_os_target_factory.ts | 2 +- .../targets/android_virtual_target.ts | 5 +- .../recordingV2/targets/chrome_target.ts | 2 +- .../recordingV2/traced_tracing_session.ts | 50 +- ui/src/common/resolution.ts | 40 + ui/src/common/state.ts | 173 +- ui/src/common/state_serialization.ts | 281 + ui/src/common/state_serialization_schema.ts | 88 + ui/src/common/state_unittest.ts | 34 +- ui/src/common/tab_registry.ts | 43 +- ui/src/common/track_cache.ts | 265 +- ui/src/common/track_cache_unittest.ts | 155 +- ui/src/common/track_helper.ts | 16 +- ui/src/common/upload_utils.ts | 198 - ui/src/common/upload_utils_unittest.ts | 104 - ui/src/controller/adb.ts | 22 +- ui/src/controller/adb_jsdomtest.ts | 5 +- ui/src/controller/adb_shell_controller.ts | 8 +- ui/src/controller/adb_socket_controller.ts | 12 +- .../aggregation/aggregation_controller.ts | 70 +- .../counter_aggregation_controller.ts | 6 +- .../aggregation/cpu_aggregation_controller.ts | 6 +- .../cpu_by_process_aggregation_controller.ts | 6 +- .../frame_aggregation_controller.ts | 7 +- .../slice_aggregation_controller.ts | 16 +- .../thread_aggregation_controller.ts | 6 +- .../estimate_aggregation_controller.ts | 127 + .../wattson/package_aggregation_controller.ts | 192 + .../wattson/process_aggregation_controller.ts | 188 + .../wattson/thread_aggregation_controller.ts | 191 + ui/src/controller/app_controller.ts | 5 +- ui/src/controller/area_selection_handler.ts | 61 - .../area_selection_handler_unittest.ts | 222 - ui/src/controller/cpu_profile_controller.ts | 7 +- ui/src/controller/flamegraph_controller.ts | 690 -- ui/src/controller/flow_events_controller.ts | 72 +- ui/src/controller/permalink_controller.ts | 269 - ui/src/controller/pivot_table_controller.ts | 32 +- ui/src/controller/record_config_types.ts | 221 +- ui/src/controller/record_controller.ts | 3 +- ui/src/controller/search_controller.ts | 139 +- ui/src/controller/selection_controller.ts | 214 +- ui/src/controller/trace_controller.ts | 275 +- ui/src/controller/track_decider.ts | 273 +- ui/src/core/default_plugins.ts | 14 +- ui/src/core/feature_flags.ts | 11 +- ui/src/core/legacy_flamegraph_cache.ts | 52 + ui/src/core/live_reload.ts | 7 +- ui/src/core/query_flamegraph.ts | 400 + ui/src/core/selection_manager.ts | 58 +- ui/src/core/trace_config_utils.ts | 33 +- ui/src/core/trace_stream.ts | 3 +- ui/src/core/track_kinds.ts | 26 + ui/src/core_plugins/android_log/index.ts | 7 +- ui/src/core_plugins/android_log/logs_panel.ts | 88 +- ui/src/core_plugins/android_log/logs_track.ts | 24 +- ui/src/core_plugins/annotation/index.ts | 31 +- ...slice_track_v2.ts => async_slice_track.ts} | 34 +- ui/src/core_plugins/async_slices/index.ts | 41 +- .../critical_user_interaction_track.ts | 167 + .../index.ts | 175 +- .../page_load_details_panel.ts | 7 +- .../chrome_tasks_scroll_jank_track.ts | 39 +- .../core_plugins/chrome_scroll_jank/common.ts | 2 +- .../chrome_scroll_jank/event_latency_track.ts | 17 +- .../core_plugins/chrome_scroll_jank/index.ts | 106 +- .../chrome_scroll_jank/scroll_delta_graph.ts | 132 +- .../scroll_details_panel.ts | 43 +- .../scroll_jank_cause_link_utils.ts | 8 +- .../chrome_scroll_jank/scroll_jank_slice.ts | 6 +- .../scroll_jank_v3_details_panel.ts | 11 +- .../scroll_jank_v3_track.ts | 9 +- .../chrome_scroll_jank/scroll_track.ts | 5 +- ui/src/core_plugins/chrome_tasks/details.ts | 70 + ui/src/core_plugins/chrome_tasks/index.ts | 138 + ui/src/core_plugins/chrome_tasks/table.ts | 92 + ui/src/core_plugins/chrome_tasks/track.ts | 51 + .../counter/counter_details_panel.ts | 165 + ui/src/core_plugins/counter/index.ts | 230 +- .../counter/trace_processor_counter_track.ts | 87 + ui/src/core_plugins/cpu_freq/index.ts | 83 +- .../cpu_profile/cpu_profile_track.ts | 248 + ui/src/core_plugins/cpu_profile/index.ts | 262 +- .../cpu_slices/cpu_slice_track.ts | 475 + ui/src/core_plugins/cpu_slices/index.ts | 511 +- ui/src/core_plugins/debug/counter_track.ts | 74 - ui/src/core_plugins/debug/index.ts | 83 +- ui/src/core_plugins/debug/slice_track.ts | 118 - ...mes_track_v2.ts => actual_frames_track.ts} | 16 +- ...s_track_v2.ts => expected_frames_track.ts} | 12 +- ui/src/core_plugins/frames/index.ts | 42 +- ui/src/core_plugins/ftrace/ftrace_explorer.ts | 6 +- ui/src/core_plugins/ftrace/ftrace_track.ts | 22 +- ui/src/core_plugins/ftrace/index.ts | 21 +- .../heap_profile/heap_profile_track.ts | 132 + ui/src/core_plugins/heap_profile/index.ts | 514 +- .../perf_samples_profile/index.ts | 452 +- .../perf_samples_profile_track.ts | 119 + ui/src/core_plugins/process_summary/index.ts | 52 +- .../process_scheduling_track.ts | 64 +- .../process_summary/process_summary_track.ts | 226 +- ui/src/core_plugins/sched/active_cpu_count.ts | 67 +- ui/src/core_plugins/sched/index.ts | 74 +- .../sched/runnable_thread_count.ts | 31 +- ui/src/core_plugins/screenshots/index.ts | 58 +- .../screenshots/screenshots_track.ts | 41 + .../{chrome_slices => thread_slice}/index.ts | 74 +- ui/src/core_plugins/thread_state/index.ts | 27 +- ...read_state_v2.ts => thread_state_track.ts} | 25 +- ui/src/core_plugins/track_utils/index.ts | 98 + ui/src/core_plugins/visualised_args/index.ts | 50 - ui/src/core_plugins/wattson/OWNERS | 3 + ui/src/core_plugins/wattson/index.ts | 107 + ui/src/frontend/aggregation_panel.ts | 19 +- ui/src/frontend/aggregation_tab.ts | 202 +- ui/src/frontend/app.ts | 441 +- ui/src/frontend/app_context.ts | 31 + ui/src/frontend/base_counter_track.ts | 215 +- ui/src/frontend/base_slice_track.ts | 187 +- ui/src/frontend/charts/histogram/state.ts | 95 + ui/src/frontend/charts/histogram/tab.ts | 151 + ui/src/frontend/counter_panel.ts | 73 - ui/src/frontend/cpu_profile_panel.ts | 3 +- ui/src/frontend/css_constants.ts | 21 +- ui/src/frontend/debug_tracks.ts | 219 - .../debug_tracks}/add_debug_track_menu.ts | 37 +- ui/src/frontend/debug_tracks/counter_track.ts | 33 + ui/src/frontend/debug_tracks/debug_tracks.ts | 267 + .../debug_tracks}/details_tab.ts | 36 +- ui/src/frontend/debug_tracks/slice_track.ts | 50 + ui/src/frontend/drag/border_drag_strategy.ts | 14 +- ui/src/frontend/drag/drag_strategy.ts | 13 +- ui/src/frontend/drag/inner_drag_strategy.ts | 5 +- ui/src/frontend/drag/outer_drag_strategy.ts | 7 +- ui/src/frontend/drag_gesture_handler.ts | 4 +- ui/src/frontend/drag_handle.ts | 8 +- ui/src/frontend/error_dialog.ts | 26 +- ui/src/frontend/flamegraph_panel.ts | 426 - ui/src/frontend/flow_events_panel.ts | 12 +- ui/src/frontend/flow_events_renderer.ts | 56 +- ui/src/frontend/frontend_local_state.ts | 304 - ui/src/frontend/generic_slice_details_tab.ts | 11 +- ui/src/frontend/globals.ts | 393 +- ui/src/frontend/gridline_helper.ts | 111 +- ui/src/frontend/gridline_helper_unittest.ts | 15 +- ui/src/frontend/help_modal.ts | 100 +- ui/src/frontend/index.ts | 128 +- ui/src/frontend/insights_page.ts | 2 + ui/src/frontend/keyboard_event_handler.ts | 22 +- .../{flamegraph.ts => legacy_flamegraph.ts} | 11 +- ui/src/frontend/legacy_flamegraph_panel.ts | 902 ++ ...ttest.ts => legacy_flamegraph_unittest.ts} | 2 +- ui/src/frontend/legacy_trace_viewer.ts | 6 +- ui/src/frontend/metrics_page.ts | 4 +- ui/src/frontend/named_slice_track.ts | 28 +- ui/src/frontend/notes.ts | 57 +- ui/src/frontend/notes_manager.ts | 64 + ui/src/frontend/notes_panel.ts | 187 +- ui/src/frontend/omnibox_manager.ts | 156 + ui/src/frontend/overview_timeline_panel.ts | 35 +- ui/src/frontend/pan_and_zoom_handler.ts | 12 +- ui/src/frontend/panel_container.ts | 118 +- ui/src/frontend/permalink.ts | 292 + ui/src/frontend/pivot_table.ts | 16 +- .../frontend/pivot_table_query_generator.ts | 7 +- ui/src/frontend/pivot_table_types.ts | 2 +- ui/src/frontend/popup_menu.ts | 2 +- ui/src/frontend/post_message_handler.ts | 26 +- ui/src/frontend/publish.ts | 32 +- ui/src/frontend/query_history.ts | 25 +- ui/src/frontend/query_page.ts | 3 +- ui/src/frontend/query_result_tab.ts | 2 +- ui/src/frontend/query_table.ts | 2 +- ui/src/frontend/record_config.ts | 54 +- ui/src/frontend/record_page.ts | 57 +- ui/src/frontend/record_page_v2.ts | 12 +- ui/src/frontend/record_widgets.ts | 8 +- ui/src/frontend/recording/android_settings.ts | 2 +- ui/src/frontend/recording/memory_settings.ts | 2 - ui/src/frontend/router.ts | 133 +- ui/src/frontend/scroll_helper.ts | 147 +- ui/src/frontend/search_handler.ts | 6 +- ui/src/frontend/search_overview_track.ts | 237 + ui/src/frontend/service_worker_controller.ts | 17 +- ui/src/frontend/sidebar.ts | 42 +- ui/src/frontend/simple_counter_track.ts | 40 +- ui/src/frontend/simple_slice_track.ts | 41 +- ui/src/frontend/slice_args.ts | 96 +- ui/src/frontend/slice_details.ts | 4 +- ui/src/frontend/slice_details_panel.ts | 6 +- ui/src/frontend/sql/args.ts | 2 +- ui/src/frontend/sql/slice.ts | 11 +- ui/src/frontend/sql/thread_state.ts | 13 +- .../{sql_table/tab.ts => sql_table_tab.ts} | 50 +- ui/src/frontend/sql_utils_unittest.ts | 5 +- ui/src/frontend/tab_panel.ts | 62 +- ui/src/frontend/thread_and_process_info.ts | 98 +- ...ils_tab.ts => thread_slice_details_tab.ts} | 88 +- .../thread_slice_track.ts} | 71 +- ui/src/frontend/thread_state.ts | 33 +- ui/src/frontend/thread_state_tab.ts | 72 +- ui/src/frontend/tickmark_panel.ts | 83 +- ui/src/frontend/time_axis_panel.ts | 56 +- ui/src/frontend/time_scale.ts | 75 +- ui/src/frontend/time_scale_unittest.ts | 42 +- ui/src/frontend/time_selection_panel.ts | 113 +- ui/src/frontend/timeline.ts | 124 + ui/src/frontend/topbar.ts | 51 +- ui/src/frontend/trace_attrs.ts | 8 +- ui/src/frontend/trace_context.ts | 41 + ui/src/frontend/trace_converter.ts | 3 +- ui/src/frontend/trace_info_page.ts | 331 +- ui/src/frontend/trace_url_handler.ts | 7 +- ui/src/frontend/track_group_panel.ts | 150 +- ui/src/frontend/track_panel.ts | 315 +- .../tracks/custom_sql_table_slice_track.ts} | 84 +- ui/src/frontend/value.ts | 2 +- ui/src/frontend/vertical_line_helper.ts | 3 +- ui/src/frontend/viewer_page.ts | 160 +- ui/src/frontend/virtual_canvas.ts | 12 +- .../visualized_args_track.ts | 67 +- ui/src/frontend/visualized_args_tracks.ts | 111 +- ...own_tables.ts => well_known_sql_tables.ts} | 2 +- ui/src/frontend/widgets/README | 14 + .../{ => widgets}/sql/details/details.ts | 42 +- .../sql/details/well_known_types.ts | 26 +- .../sql/table}/argument_selector.ts | 12 +- .../sql/table}/column.ts | 2 +- .../sql/table}/column_unittest.ts | 0 .../sql/table}/render_cell.ts | 37 +- .../{sql_table => widgets/sql/table}/state.ts | 16 +- .../sql/table}/state_unittest.ts | 3 +- .../{sql_table => widgets/sql/table}/table.ts | 45 +- .../sql/table}/table_description.ts | 0 ui/src/frontend/widgets_page.ts | 75 +- ui/src/plugins/com.example.Skeleton/index.ts | 8 +- ui/src/plugins/com.google.PixelMemory/OWNERS | 1 + .../plugins/com.google.PixelMemory/index.ts | 66 + .../dev.perfetto.AndroidClientServer/index.ts | 141 +- .../plugins/dev.perfetto.AndroidCujs/index.ts | 82 +- .../dev.perfetto.AndroidCujs/trackUtils.ts | 207 + .../index.ts | 362 +- .../dev.perfetto.AndroidNetwork/index.ts | 11 +- .../plugins/dev.perfetto.AndroidPerf/index.ts | 25 +- .../index.ts | 7 +- .../dev.perfetto.AndroidStartup/index.ts | 4 +- ui/src/plugins/dev.perfetto.Chaos/index.ts | 2 +- .../dev.perfetto.ExampleState/index.ts | 5 +- .../plugins/dev.perfetto.GpuByProcess/OWNERS | 1 + .../dev.perfetto.GpuByProcess/index.ts | 101 + .../dev.perfetto.LargeScreensPerf/index.ts | 22 +- .../dev.perfetto.PinAndroidPerfMetrics/OWNERS | 3 + .../handlers/fullTraceJankMetricHandler.ts | 143 + .../fullTraceJankMetricHandler_unittest.ts | 70 + .../handlers/handlerRegistry.ts} | 15 +- .../handlers/metricUtils.ts | 121 + .../handlers/pinBlockingCall.ts | 117 + .../handlers/pinBlockingCall_unittest.ts | 82 + .../handlers/pinCujScoped.ts | 179 + .../handlers/pinCujScoped_unittest.ts | 76 + .../index.ts | 122 + .../pluginId.ts} | 8 +- .../dev.perfetto.PinSysUITracks/OWNERS | 3 + .../dev.perfetto.PinSysUITracks/index.ts | 76 + .../dev.perfetto.RestorePinnedTracks/index.ts | 6 +- .../plugins/dev.perfetto.TimelineSync/OWNERS | 1 + .../dev.perfetto.TimelineSync/index.ts | 472 + .../plugins/dev.perfetto.TraceMetadata/OWNERS | 1 + .../dev.perfetto.TraceMetadata/index.ts | 50 + .../org.kernel.LinuxKernelDevices/index.ts | 16 +- ui/src/protos/index.ts | 4 - ui/src/public/index.ts | 223 +- ui/src/public/tracks.ts | 233 + ui/src/public/utils.ts | 28 +- ui/src/test/perfetto_ui_test_helper.ts | 8 +- ui/src/test/ui_integrationtest.ts | 29 +- ui/src/trace_processor/engine.ts | 201 +- ui/src/trace_processor/http_rpc_engine.ts | 20 +- ui/src/trace_processor/query_result.ts | 29 +- .../sql_utils.ts | 124 +- ui/src/widgets/basic_table.ts | 1 - ui/src/widgets/common.ts | 55 +- ui/src/widgets/flamegraph.ts | 866 ++ ui/src/widgets/modal.ts | 11 +- ui/src/widgets/tag_input.ts | 155 + ui/src/widgets/vega_view.ts | 46 +- ui/src/widgets/virtual_scroll_helper.ts | 10 +- ui/src/widgets/virtual_table.ts | 7 +- ui/tsconfig.base.json | 2 +- ui/tsconfig.json | 3 +- 1465 files changed, 85042 insertions(+), 34297 deletions(-) create mode 100644 .bazelignore create mode 100644 .bazelrc create mode 100644 buildtools/grpc/protobuf_include/google/protobuf/compiler/csharp/names.h create mode 100644 buildtools/grpc/protobuf_include/google/protobuf/compiler/objectivec/names.h create mode 100644 docs/images/java-heap-graph-dominated-size.png create mode 100644 include/perfetto/public/protos/trace/android/android_track_event.pzc.h create mode 100644 include/perfetto/public/protos/trace/clock_snapshot.pzc.h create mode 100644 infra/ci/controller/.gcloudignore rename infra/ci/controller/{controller.py => main.py} (54%) delete mode 100644 infra/ci/controller/queue.yaml create mode 100644 infra/ci/controller/requirements.txt create mode 100644 infra/ci/frontend/.gcloudignore delete mode 100644 infra/ci/frontend/frontend.py create mode 100644 infra/ci/frontend/main.py create mode 100644 infra/ci/frontend/requirements.txt create mode 100644 protos/perfetto/bigtrace/BUILD.gn create mode 100644 protos/perfetto/bigtrace/orchestrator.proto create mode 100644 protos/perfetto/bigtrace/worker.proto create mode 100644 protos/perfetto/config/android/windowmanager_config.proto create mode 100644 protos/perfetto/metrics/android/android_broadcasts_metric.proto create mode 100644 protos/perfetto/metrics/android/wattson_app_startup.proto create mode 100644 protos/perfetto/trace/android/android_track_event.proto create mode 100644 protos/perfetto/trace/android/app/statusbarmanager.proto create mode 100644 protos/perfetto/trace/android/app/window_configuration.proto create mode 100644 protos/perfetto/trace/android/content/activityinfo.proto create mode 100644 protos/perfetto/trace/android/content/configuration.proto create mode 100644 protos/perfetto/trace/android/content/locale.proto create mode 100644 protos/perfetto/trace/android/privacy.proto create mode 100644 protos/perfetto/trace/android/server/animationadapter.proto create mode 100644 protos/perfetto/trace/android/server/surfaceanimator.proto create mode 100644 protos/perfetto/trace/android/server/windowcontainerthumbnail.proto create mode 100644 protos/perfetto/trace/android/server/windowmanagerservice.proto create mode 100644 protos/perfetto/trace/android/view/displayinfo.proto create mode 100644 protos/perfetto/trace/android/view/enums.proto create mode 100644 protos/perfetto/trace/android/view/remote_animation_target.proto create mode 100644 protos/perfetto/trace/android/view/surface.proto create mode 100644 protos/perfetto/trace/android/viewcapture.proto create mode 100644 protos/perfetto/trace/android/windowmanager.proto create mode 100644 protos/perfetto/trace/ftrace/bcl_exynos.proto create mode 100644 protos/perfetto/trace/ftrace/dcvsh.proto create mode 100644 protos/perfetto/trace/ftrace/kgsl.proto rename ui/prettier-all => protos/third_party/simpleperf/BUILD.gn (79%) mode change 100755 => 100644 create mode 100644 protos/third_party/simpleperf/record_file.proto create mode 100644 python/perfetto/bigtrace/api.py create mode 100644 python/perfetto/bigtrace/protos/perfetto/bigtrace/orchestrator_pb2.py create mode 100644 python/perfetto/bigtrace/protos/perfetto/bigtrace/orchestrator_pb2.pyi create mode 100644 python/perfetto/bigtrace/protos/perfetto/bigtrace/orchestrator_pb2_grpc.py create mode 100644 python/perfetto/bigtrace/protos/perfetto/common/descriptor_pb2.py create mode 100644 python/perfetto/bigtrace/protos/perfetto/common/descriptor_pb2.pyi create mode 100644 python/perfetto/bigtrace/protos/perfetto/trace_processor/metatrace_categories_pb2.py create mode 100644 python/perfetto/bigtrace/protos/perfetto/trace_processor/metatrace_categories_pb2.pyi create mode 100644 python/perfetto/bigtrace/protos/perfetto/trace_processor/trace_processor_pb2.py create mode 100644 python/perfetto/bigtrace/protos/perfetto/trace_processor/trace_processor_pb2.pyi rename infra/ci/controller/appengine_config.py => python/perfetto/common/exceptions.py (78%) create mode 100644 python/perfetto/common/query_result_iterator.py create mode 100644 python/test/bigtrace_api_integrationtest.py rename python/test/{api_unittest.py => query_result_iterator_unittest.py} (85%) create mode 100644 src/bigtrace/compose.yaml create mode 100644 src/bigtrace/orchestrator-deployment.yaml create mode 100644 src/bigtrace/orchestrator-service.yaml create mode 100644 src/bigtrace/orchestrator/BUILD.gn create mode 100644 src/bigtrace/orchestrator/Dockerfile create mode 100644 src/bigtrace/orchestrator/orchestrator_impl.cc create mode 100644 src/bigtrace/orchestrator/orchestrator_impl.h create mode 100644 src/bigtrace/orchestrator/orchestrator_main.cc create mode 100644 src/bigtrace/worker-deployment.yaml create mode 100644 src/bigtrace/worker-service.yaml create mode 100644 src/bigtrace/worker/BUILD.gn create mode 100644 src/bigtrace/worker/Dockerfile create mode 100644 src/bigtrace/worker/worker_impl.cc create mode 100644 src/bigtrace/worker/worker_impl.h create mode 100644 src/bigtrace/worker/worker_main.cc create mode 100644 src/shared_lib/test/protos/extensions.pzc.h delete mode 100644 src/trace_processor/importers/android_bugreport/android_bugreport_parser.cc delete mode 100644 src/trace_processor/importers/android_bugreport/android_bugreport_parser.h create mode 100644 src/trace_processor/importers/android_bugreport/android_bugreport_reader.cc create mode 100644 src/trace_processor/importers/android_bugreport/android_bugreport_reader.h create mode 100644 src/trace_processor/importers/android_bugreport/android_dumpstate_reader.cc create mode 100644 src/trace_processor/importers/android_bugreport/android_dumpstate_reader.h create mode 100644 src/trace_processor/importers/android_bugreport/android_log_event.cc create mode 100644 src/trace_processor/importers/android_bugreport/android_log_event.h create mode 100644 src/trace_processor/importers/android_bugreport/android_log_event_parser_impl.cc create mode 100644 src/trace_processor/importers/android_bugreport/android_log_event_parser_impl.h delete mode 100644 src/trace_processor/importers/android_bugreport/android_log_parser.cc delete mode 100644 src/trace_processor/importers/android_bugreport/android_log_parser.h delete mode 100644 src/trace_processor/importers/android_bugreport/android_log_parser_unittest.cc create mode 100644 src/trace_processor/importers/android_bugreport/android_log_reader.cc create mode 100644 src/trace_processor/importers/android_bugreport/android_log_reader.h create mode 100644 src/trace_processor/importers/android_bugreport/android_log_unittest.cc create mode 100644 src/trace_processor/importers/android_bugreport/chunked_line_reader.cc create mode 100644 src/trace_processor/importers/android_bugreport/chunked_line_reader.h create mode 100644 src/trace_processor/importers/common/cpu_tracker.cc create mode 100644 src/trace_processor/importers/common/cpu_tracker.h create mode 100644 src/trace_processor/importers/common/process_track_translation_table.cc create mode 100644 src/trace_processor/importers/common/process_track_translation_table.h create mode 100644 src/trace_processor/importers/common/process_track_translation_table_unittest.cc create mode 100644 src/trace_processor/importers/common/scoped_active_trace_file.cc create mode 100644 src/trace_processor/importers/common/scoped_active_trace_file.h create mode 100644 src/trace_processor/importers/common/trace_file_tracker.cc create mode 100644 src/trace_processor/importers/common/trace_file_tracker.h create mode 100644 src/trace_processor/importers/perf/attrs_section_reader.cc create mode 100644 src/trace_processor/importers/perf/attrs_section_reader.h create mode 100644 src/trace_processor/importers/perf/dso_tracker.cc create mode 100644 src/trace_processor/importers/perf/dso_tracker.h create mode 100644 src/trace_processor/importers/perf/features.cc create mode 100644 src/trace_processor/importers/perf/features.h create mode 100644 src/trace_processor/importers/perf/mmap_record.cc create mode 100644 src/trace_processor/importers/perf/mmap_record.h create mode 100644 src/trace_processor/importers/perf/perf_counter.cc create mode 100644 src/trace_processor/importers/perf/perf_counter.h delete mode 100644 src/trace_processor/importers/perf/perf_data_parser.cc delete mode 100644 src/trace_processor/importers/perf/perf_data_parser.h delete mode 100644 src/trace_processor/importers/perf/perf_data_reader.cc delete mode 100644 src/trace_processor/importers/perf/perf_data_reader.h delete mode 100644 src/trace_processor/importers/perf/perf_data_reader_unittest.cc delete mode 100644 src/trace_processor/importers/perf/perf_data_tracker.cc delete mode 100644 src/trace_processor/importers/perf/perf_data_tracker.h delete mode 100644 src/trace_processor/importers/perf/perf_data_tracker_unittest.cc create mode 100644 src/trace_processor/importers/perf/record_parser.cc create mode 100644 src/trace_processor/importers/perf/record_parser.h create mode 100644 src/trace_processor/importers/perf/sample.cc create mode 100644 src/trace_processor/importers/perf/sample.h create mode 100644 src/trace_processor/importers/proto/args_parser.cc rename src/trace_processor/importers/proto/{winscope/winscope_args_parser.h => args_parser.h} (61%) create mode 100644 src/trace_processor/importers/proto/pigweed_detokenizer.cc create mode 100644 src/trace_processor/importers/proto/pigweed_detokenizer.h create mode 100644 src/trace_processor/importers/proto/pixel_modem_module.cc create mode 100644 src/trace_processor/importers/proto/pixel_modem_module.h create mode 100644 src/trace_processor/importers/proto/pixel_modem_parser.cc create mode 100644 src/trace_processor/importers/proto/pixel_modem_parser.h create mode 100644 src/trace_processor/importers/proto/proto_trace_reader_unittest.cc create mode 100644 src/trace_processor/importers/proto/proto_trace_tokenizer_unittest.cc create mode 100644 src/trace_processor/importers/proto/winscope/android_input_event_parser.cc create mode 100644 src/trace_processor/importers/proto/winscope/android_input_event_parser.h create mode 100644 src/trace_processor/importers/proto/winscope/protolog_message_decoder.cc create mode 100644 src/trace_processor/importers/proto/winscope/protolog_message_decoder.h create mode 100644 src/trace_processor/importers/proto/winscope/viewcapture_args_parser.cc create mode 100644 src/trace_processor/importers/proto/winscope/viewcapture_args_parser.h delete mode 100644 src/trace_processor/importers/proto/winscope/winscope_args_parser.cc create mode 100644 src/trace_processor/importers/zip/BUILD.gn create mode 100644 src/trace_processor/importers/zip/zip_trace_reader.cc create mode 100644 src/trace_processor/importers/zip/zip_trace_reader.h create mode 100644 src/trace_processor/metrics/sql/android/android_broadcasts.sql create mode 100644 src/trace_processor/metrics/sql/android/wattson_app_startup.sql delete mode 100644 src/trace_processor/perfetto_sql/intrinsics/functions/dfs.cc delete mode 100644 src/trace_processor/perfetto_sql/intrinsics/functions/dfs.h create mode 100644 src/trace_processor/perfetto_sql/intrinsics/functions/graph_scan.cc create mode 100644 src/trace_processor/perfetto_sql/intrinsics/functions/graph_scan.h create mode 100644 src/trace_processor/perfetto_sql/intrinsics/functions/graph_traversal.cc create mode 100644 src/trace_processor/perfetto_sql/intrinsics/functions/graph_traversal.h create mode 100644 src/trace_processor/perfetto_sql/intrinsics/functions/interval_intersect.cc create mode 100644 src/trace_processor/perfetto_sql/intrinsics/functions/interval_intersect.h create mode 100644 src/trace_processor/perfetto_sql/intrinsics/functions/type_builders.cc create mode 100644 src/trace_processor/perfetto_sql/intrinsics/functions/type_builders.h create mode 100644 src/trace_processor/perfetto_sql/intrinsics/operators/interval_intersect_operator.cc create mode 100644 src/trace_processor/perfetto_sql/intrinsics/operators/interval_intersect_operator.h delete mode 100644 src/trace_processor/perfetto_sql/intrinsics/table_functions/interval_intersect.cc delete mode 100644 src/trace_processor/perfetto_sql/intrinsics/table_functions/interval_intersect.h create mode 100644 src/trace_processor/perfetto_sql/intrinsics/types/BUILD.gn create mode 100644 src/trace_processor/perfetto_sql/intrinsics/types/array.h create mode 100644 src/trace_processor/perfetto_sql/intrinsics/types/node.h create mode 100644 src/trace_processor/perfetto_sql/intrinsics/types/partitioned_intervals.h create mode 100644 src/trace_processor/perfetto_sql/intrinsics/types/row_dataframe.h create mode 100644 src/trace_processor/perfetto_sql/intrinsics/types/struct.h create mode 100644 src/trace_processor/perfetto_sql/intrinsics/types/value.h create mode 100644 src/trace_processor/perfetto_sql/stdlib/android/cpu/BUILD.gn create mode 100644 src/trace_processor/perfetto_sql/stdlib/android/cpu/cluster_type.sql create mode 100644 src/trace_processor/perfetto_sql/stdlib/android/frames/jank_type.sql create mode 100644 src/trace_processor/perfetto_sql/stdlib/android/gpu/BUILD.gn create mode 100644 src/trace_processor/perfetto_sql/stdlib/android/gpu/frequency.sql rename src/trace_processor/perfetto_sql/stdlib/{cpu/cpus.sql => android/gpu/memory.sql} (61%) create mode 100644 src/trace_processor/perfetto_sql/stdlib/android/memory/BUILD.gn create mode 100644 src/trace_processor/perfetto_sql/stdlib/android/memory/dmabuf.sql create mode 100644 src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/BUILD.gn create mode 100644 src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/class_tree.sql create mode 100644 src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/dominator_class_tree.sql create mode 100644 src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/dominator_tree.sql create mode 100644 src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/excluded_refs.sql create mode 100644 src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/heap_graph_class_aggregation.sql create mode 100644 src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/helpers.sql create mode 100644 src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/raw_dominator_tree.sql create mode 100644 src/trace_processor/perfetto_sql/stdlib/android/memory/heap_profile/BUILD.gn create mode 100644 src/trace_processor/perfetto_sql/stdlib/android/memory/heap_profile/callstacks.sql create mode 100644 src/trace_processor/perfetto_sql/stdlib/android/memory/process.sql rename src/trace_processor/perfetto_sql/stdlib/{common/cpus.sql => android/version.sql} (66%) rename src/trace_processor/perfetto_sql/stdlib/{common/thread_states.sql => android/winscope/viewcapture.sql} (60%) create mode 100644 src/trace_processor/perfetto_sql/stdlib/android/winscope/windowmanager.sql rename src/trace_processor/perfetto_sql/stdlib/{memory => callstacks}/BUILD.gn (88%) create mode 100644 src/trace_processor/perfetto_sql/stdlib/callstacks/stack_profile.sql create mode 100644 src/trace_processor/perfetto_sql/stdlib/chrome/event_latency.sql create mode 100644 src/trace_processor/perfetto_sql/stdlib/chrome/scroll_interactions.sql create mode 100644 src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/predictor_error.sql create mode 100644 src/trace_processor/perfetto_sql/stdlib/chrome/speedometer_2_1.sql create mode 100644 src/trace_processor/perfetto_sql/stdlib/chrome/speedometer_3.sql delete mode 100644 src/trace_processor/perfetto_sql/stdlib/cpu/size.sql delete mode 100644 src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/thread_states.sql create mode 100644 src/trace_processor/perfetto_sql/stdlib/export/BUILD.gn create mode 100644 src/trace_processor/perfetto_sql/stdlib/export/to_firefox_profile.sql create mode 100644 src/trace_processor/perfetto_sql/stdlib/graphs/critical_path.sql create mode 100644 src/trace_processor/perfetto_sql/stdlib/graphs/hierarchy.sql create mode 100644 src/trace_processor/perfetto_sql/stdlib/graphs/scan.sql rename ui/eslint-all => src/trace_processor/perfetto_sql/stdlib/linux/cpu/BUILD.gn (78%) mode change 100755 => 100644 rename src/trace_processor/perfetto_sql/stdlib/{cpu/freq.sql => linux/cpu/frequency.sql} (94%) rename src/trace_processor/perfetto_sql/stdlib/{ => linux}/cpu/idle.sql (55%) rename src/trace_processor/perfetto_sql/stdlib/{sched => linux/cpu}/utilization/BUILD.gn (93%) rename src/trace_processor/perfetto_sql/stdlib/{sched => linux/cpu}/utilization/general.sql (83%) rename src/trace_processor/perfetto_sql/stdlib/{sched => linux/cpu}/utilization/process.sql (67%) rename src/trace_processor/perfetto_sql/stdlib/{sched => linux/cpu}/utilization/system.sql (50%) rename src/trace_processor/perfetto_sql/stdlib/{sched => linux/cpu}/utilization/thread.sql (68%) delete mode 100644 src/trace_processor/perfetto_sql/stdlib/linux/cpu_idle.sql create mode 100644 src/trace_processor/perfetto_sql/stdlib/linux/memory/BUILD.gn create mode 100644 src/trace_processor/perfetto_sql/stdlib/linux/memory/general.sql create mode 100644 src/trace_processor/perfetto_sql/stdlib/linux/memory/high_watermark.sql create mode 100644 src/trace_processor/perfetto_sql/stdlib/linux/memory/process.sql create mode 100644 src/trace_processor/perfetto_sql/stdlib/linux/perf/BUILD.gn create mode 100644 src/trace_processor/perfetto_sql/stdlib/linux/perf/samples.sql delete mode 100644 src/trace_processor/perfetto_sql/stdlib/memory/heap_graph_dominator_tree.sql rename src/trace_processor/perfetto_sql/stdlib/{cpu => metasql}/BUILD.gn (87%) create mode 100644 src/trace_processor/perfetto_sql/stdlib/metasql/column_list.sql create mode 100644 src/trace_processor/perfetto_sql/stdlib/metasql/table_list.sql create mode 100644 src/trace_processor/perfetto_sql/stdlib/prelude/tables_views.sql create mode 100644 src/trace_processor/perfetto_sql/stdlib/slices/flow.sql create mode 100644 src/trace_processor/perfetto_sql/stdlib/slices/hierarchy.sql create mode 100644 src/trace_processor/perfetto_sql/stdlib/viz/BUILD.gn create mode 100644 src/trace_processor/perfetto_sql/stdlib/viz/flamegraph.sql rename src/trace_processor/perfetto_sql/stdlib/{deprecated/v42/common/cpus.sql => viz/summary/counters.sql} (69%) delete mode 100644 src/trace_processor/perfetto_sql/stdlib/wattson/cpu_freq.sql create mode 100644 src/trace_processor/perfetto_sql/stdlib/wattson/cpu_split.sql create mode 100644 src/trace_processor/perfetto_sql/stdlib/wattson/curves/device.sql create mode 100644 src/trace_processor/perfetto_sql/stdlib/wattson/curves/grouped.sql create mode 100644 src/trace_processor/perfetto_sql/stdlib/wattson/curves/ungrouped.sql create mode 100644 src/trace_processor/perfetto_sql/stdlib/wattson/curves/utils.sql create mode 100644 src/trace_processor/perfetto_sql/stdlib/wattson/device_infos.sql create mode 100644 src/trace_processor/sqlite/bindings/sqlite_bind.h create mode 100644 src/trace_processor/sqlite/bindings/sqlite_column.h create mode 100644 src/trace_processor/sqlite/bindings/sqlite_function.h create mode 100644 src/trace_processor/sqlite/bindings/sqlite_stmt.h create mode 100644 src/trace_processor/sqlite/bindings/sqlite_type.h create mode 100644 src/trace_processor/sqlite/bindings/sqlite_value.h delete mode 100644 src/trace_processor/sqlite/sqlite_value.h create mode 100644 src/trace_processor/trace_reader_registry.cc create mode 100644 src/trace_processor/trace_reader_registry.h delete mode 100644 src/trace_processor/util/file_buffer.cc delete mode 100644 src/trace_processor/util/file_buffer.h create mode 100644 src/trace_processor/util/trace_blob_view_reader.cc create mode 100644 src/trace_processor/util/trace_blob_view_reader.h rename src/trace_processor/util/{file_buffer_unittest.cc => trace_blob_view_reader_unittest.cc} (80%) create mode 100644 src/trace_processor/util/trace_type.cc create mode 100644 src/trace_processor/util/trace_type.h create mode 100644 src/trace_redaction/boardphase_packet_filter_integrationtest.cc create mode 100644 src/trace_redaction/broadphase_packet_filter.cc create mode 100644 src/trace_redaction/broadphase_packet_filter.h create mode 100644 src/trace_redaction/broadphase_packet_filter_unittest.cc create mode 100644 src/trace_redaction/collect_frame_cookies_integrationtest.cc delete mode 100644 src/trace_redaction/filter_ftrace_using_allowlist.cc delete mode 100644 src/trace_redaction/filter_ftrace_using_allowlist.h delete mode 100644 src/trace_redaction/filter_ftrace_using_allowlist_integrationtest.cc delete mode 100644 src/trace_redaction/filter_ftrace_using_allowlist_unittest.cc delete mode 100644 src/trace_redaction/filter_packet_using_allowlist.h delete mode 100644 src/trace_redaction/filter_packet_using_allowlist_unittest.cc delete mode 100644 src/trace_redaction/filter_print_events.cc delete mode 100644 src/trace_redaction/filter_print_events.h delete mode 100644 src/trace_redaction/filter_sched_waking_events.cc delete mode 100644 src/trace_redaction/filter_sched_waking_events.h delete mode 100644 src/trace_redaction/filter_task_rename.cc delete mode 100644 src/trace_redaction/filter_task_rename_unittest.cc rename src/trace_redaction/{filter_packet_using_allowlist.cc => filtering.cc} (50%) create mode 100644 src/trace_redaction/filtering.h create mode 100644 src/trace_redaction/merge_threads.cc rename src/trace_redaction/{filter_task_rename.h => merge_threads.h} (58%) create mode 100644 src/trace_redaction/modify.cc create mode 100644 src/trace_redaction/modify.h delete mode 100644 src/trace_redaction/modify_process_trees.cc delete mode 100644 src/trace_redaction/modify_process_trees.h create mode 100644 src/trace_redaction/process_thread_timeline_integrationtest.cc delete mode 100644 src/trace_redaction/redact_ftrace_event.cc delete mode 100644 src/trace_redaction/redact_ftrace_event.h create mode 100644 src/trace_redaction/redact_ftrace_events.cc create mode 100644 src/trace_redaction/redact_ftrace_events.h create mode 100644 src/trace_redaction/redact_process_events.cc create mode 100644 src/trace_redaction/redact_process_events.h create mode 100644 src/trace_redaction/redact_process_events_unittest.cc delete mode 100644 src/trace_redaction/redact_process_free.cc delete mode 100644 src/trace_redaction/redact_process_free.h delete mode 100644 src/trace_redaction/redact_process_free_unittest.cc create mode 100644 src/trace_redaction/redact_process_trees.cc create mode 100644 src/trace_redaction/redact_process_trees.h create mode 100644 src/trace_redaction/redact_process_trees_integrationtest.cc create mode 100644 src/trace_redaction/redact_sched_events.cc create mode 100644 src/trace_redaction/redact_sched_events.h rename src/trace_redaction/{redact_sched_switch_integrationtest.cc => redact_sched_events_integrationtest.cc} (68%) create mode 100644 src/trace_redaction/redact_sched_events_unittest.cc delete mode 100644 src/trace_redaction/redact_sched_switch.cc delete mode 100644 src/trace_redaction/redact_sched_switch.h delete mode 100644 src/trace_redaction/redact_sched_switch_unittest.cc delete mode 100644 src/trace_redaction/redact_task_newtask.cc delete mode 100644 src/trace_redaction/redact_task_newtask.h delete mode 100644 src/trace_redaction/redact_task_newtask_unittest.cc delete mode 100644 src/trace_redaction/remap_scheduling_events.cc delete mode 100644 src/trace_redaction/remap_scheduling_events.h delete mode 100644 src/trace_redaction/remap_scheduling_events_integrationtest.cc delete mode 100644 src/trace_redaction/remap_scheduling_events_unittest.cc delete mode 100644 src/trace_redaction/scrub_ftrace_events.cc delete mode 100644 src/trace_redaction/scrub_ftrace_events.h delete mode 100644 src/trace_redaction/scrub_ftrace_events_integrationtest.cc delete mode 100644 src/trace_redaction/scrub_process_trees.cc delete mode 100644 src/trace_redaction/scrub_process_trees.h delete mode 100644 src/trace_redaction/scrub_process_trees_integrationtest.cc delete mode 100644 src/trace_redaction/scrub_trace_packet.cc delete mode 100644 src/trace_redaction/scrub_trace_packet.h delete mode 100644 src/trace_redaction/suspend_resume.cc delete mode 100644 src/trace_redaction/suspend_resume.h delete mode 100644 src/trace_redaction/suspend_resume_unittest.cc create mode 100644 src/trace_redaction/trace_processor_integrationtest.cc create mode 100644 src/trace_redaction/verify_integrity.cc create mode 100644 src/trace_redaction/verify_integrity.h create mode 100644 src/trace_redaction/verify_integrity_unittest.cc create mode 100644 src/traceconv/pprof_reader.cc create mode 100644 src/traceconv/pprof_reader.h create mode 100644 src/traceconv/trace_to_firefox.cc create mode 100644 src/traceconv/trace_to_firefox.h create mode 100644 src/traceconv/trace_to_pprof_integrationtest.cc rename src/traceconv/{trace_to_text_unittest.cc => trace_to_text_integrationtest.cc} (100%) create mode 100644 src/traced/probes/ftrace/test/data/synthetic/events/bcl_exynos/bcl_irq_trigger/format create mode 100644 src/traced/probes/ftrace/test/data/synthetic/events/dcvsh/dcvsh_freq/format create mode 100644 src/traced/probes/ftrace/test/data/synthetic/events/kgsl/gpu_frequency/format create mode 100644 src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_HCTL_CORES_DOWN_SCALE_NOTIFY_PEND/format create mode 100644 src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_HCTL_CORES_NOTIFY_PEND/format create mode 100644 src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_HCTL_CORE_INACTIVE_PEND/format create mode 100644 src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_HCTL_MCU_ON_RECHECK/format create mode 100644 src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_HCTL_SHADERS_CORE_OFF_PEND/format create mode 100644 src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_HCTL_SHADERS_PEND_OFF/format create mode 100644 src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_HCTL_SHADERS_PEND_ON/format create mode 100644 src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_HCTL_SHADERS_READY_OFF/format create mode 100644 src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_IN_SLEEP/format create mode 100644 src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_OFF/format create mode 100644 src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_ON/format create mode 100644 src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_ON_CORE_ATTR_UPDATE_PEND/format create mode 100644 src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_ON_GLB_REINIT_PEND/format create mode 100644 src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_ON_HALT/format create mode 100644 src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_ON_HWCNT_DISABLE/format create mode 100644 src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_ON_HWCNT_ENABLE/format create mode 100644 src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_ON_PEND_HALT/format create mode 100644 src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_ON_PEND_SLEEP/format create mode 100644 src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_ON_SLEEP_INITIATE/format create mode 100644 src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_PEND_OFF/format create mode 100644 src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_PEND_ON_RELOAD/format create mode 100644 src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_POWER_DOWN/format create mode 100644 src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_RESET_WAIT/format create mode 100644 test/data/android_calculator_startup.pb.sha256 create mode 100644 test/data/chrome/chrome_input_with_frame_view_new.pftrace.sha256 create mode 100644 test/data/chrome/scroll_jank_with_pinch.pftrace.sha256 rename test/data/chrome/{speedometer.perfetto_trace.gz.sha256 => speedometer_21.perfetto_trace.gz.sha256} (100%) create mode 100644 test/data/chrome/speedometer_3.perfetto_trace.gz.sha256 create mode 100644 test/data/heap_graph/heap_graph.pb.sha256 create mode 100644 test/data/heap_graph/heap_graph.textproto.sha256 create mode 100644 test/data/heap_graph/heap_graph_branching.pb.sha256 create mode 100644 test/data/heap_graph/heap_graph_branching.textproto.sha256 create mode 100644 test/data/heap_graph/heap_graph_huge_size.pb.sha256 create mode 100644 test/data/heap_graph/heap_graph_huge_size.textproto.sha256 create mode 100644 test/data/multi_machine_trace.pb.sha256 create mode 100644 test/data/simpleperf/linux_perf_with_symbols.zip.sha256 create mode 100644 test/data/simpleperf/perf.data.sha256 create mode 100644 test/data/simpleperf/perf_with_add_counter.data.sha256 create mode 100644 test/data/simpleperf/perf_with_synthetic_events.data.sha256 create mode 100644 test/data/trace-redaction-api-capture.pftrace.sha256 create mode 100644 test/data/ui-screenshots/ui-features_track_debuggable_chip.png.sha256 create mode 100644 test/data/zip/perf_track_sym.zip.sha256 create mode 100644 test/trace_processor/diff_tests/metrics/android/android_broadcasts.out create mode 100644 test/trace_processor/diff_tests/parser/android/input_event_trace.textproto create mode 100644 test/trace_processor/diff_tests/parser/android/tests_android_input_event.py create mode 100644 test/trace_processor/diff_tests/parser/android/tests_viewcapture.py create mode 100644 test/trace_processor/diff_tests/parser/android/tests_windowmanager.py create mode 100644 test/trace_processor/diff_tests/parser/android/viewcapture.textproto create mode 100644 test/trace_processor/diff_tests/parser/android/windowmanager.textproto create mode 100644 test/trace_processor/diff_tests/parser/parsing/tests_traced_stats.py create mode 100644 test/trace_processor/diff_tests/parser/simpleperf/clocks_align_test.sql create mode 100644 test/trace_processor/diff_tests/parser/simpleperf/perf_test.sql create mode 100644 test/trace_processor/diff_tests/parser/simpleperf/perf_with_add_counter_test.sql create mode 100644 test/trace_processor/diff_tests/parser/simpleperf/stacks_test.sql create mode 100644 test/trace_processor/diff_tests/parser/simpleperf/tests.py create mode 100644 test/trace_processor/diff_tests/parser/translated_args/process_track_name.textproto create mode 100644 test/trace_processor/diff_tests/parser/zip/tests.py create mode 100644 test/trace_processor/diff_tests/stdlib/android/cpu_cluster_tests.py create mode 100644 test/trace_processor/diff_tests/stdlib/android/gpu.py create mode 100644 test/trace_processor/diff_tests/stdlib/android/heap_graph_for_aggregation.textproto rename test/trace_processor/diff_tests/stdlib/{memory => android}/heap_graph_for_dominator_tree.textproto (100%) rename test/trace_processor/diff_tests/stdlib/{memory/heap_graph_dominator_tree_tests.py => android/heap_graph_tests.py} (68%) create mode 100644 test/trace_processor/diff_tests/stdlib/android/memory.py delete mode 100644 test/trace_processor/diff_tests/stdlib/chrome/chrome_speedometer.out create mode 100644 test/trace_processor/diff_tests/stdlib/chrome/scroll_jank_v3_new.out delete mode 100644 test/trace_processor/diff_tests/stdlib/cpu/tests.py create mode 100644 test/trace_processor/diff_tests/stdlib/export/firefox_profile.out create mode 100644 test/trace_processor/diff_tests/stdlib/export/tests.py create mode 100644 test/trace_processor/diff_tests/stdlib/graphs/critical_path_tests.py create mode 100644 test/trace_processor/diff_tests/stdlib/graphs/scan_tests.py create mode 100644 test/trace_processor/diff_tests/stdlib/linux/cpu.py create mode 100644 test/trace_processor/diff_tests/stdlib/linux/memory.py delete mode 100644 test/trace_processor/diff_tests/stdlib/linux/tests.py create mode 100644 test/trace_processor/diff_tests/stdlib/metasql/column_list.py create mode 100644 test/trace_processor/diff_tests/stdlib/metasql/table_list.py create mode 100644 tools/bisect_ui_releases create mode 100644 tools/gen_bigtrace_grpc_protos.py create mode 100644 tools/setup_minikube_cluster.sh create mode 100644 tools/ssh_into_gce_vm delete mode 100644 ui/.eslintignore create mode 100644 ui/config/JestJsdomEnv.js create mode 100644 ui/eslint.config.js create mode 100644 ui/format-sources create mode 100644 ui/src/assets/widgets/flamegraph.scss create mode 100644 ui/src/assets/widgets/tag_input.scss delete mode 100644 ui/src/base/disposable.ts rename ui/src/base/{utils/index.js => disposable_polyfill.ts} (68%) create mode 100644 ui/src/base/disposable_stack.ts create mode 100644 ui/src/base/shared_disposable.ts create mode 100644 ui/src/base/shared_disposable_unittest.ts delete mode 100644 ui/src/base/utils/index.d.ts delete mode 100644 ui/src/base/utils/package.json delete mode 100644 ui/src/base/validators.ts delete mode 100644 ui/src/base/validators_unittest.ts create mode 100644 ui/src/common/addEphemeralTab.ts delete mode 100644 ui/src/common/canvas_utils_unittest.ts create mode 100644 ui/src/common/gcs_uploader.ts create mode 100644 ui/src/common/high_precision_time_span.ts create mode 100644 ui/src/common/high_precision_time_span_unittest.ts rename ui/src/common/{flamegraph_unittest.ts => legacy_flamegraph_unittest.ts} (99%) rename ui/src/common/{flamegraph_util.ts => legacy_flamegraph_util.ts} (73%) create mode 100644 ui/src/common/resolution.ts create mode 100644 ui/src/common/state_serialization.ts create mode 100644 ui/src/common/state_serialization_schema.ts delete mode 100644 ui/src/common/upload_utils.ts delete mode 100644 ui/src/common/upload_utils_unittest.ts create mode 100644 ui/src/controller/aggregation/wattson/estimate_aggregation_controller.ts create mode 100644 ui/src/controller/aggregation/wattson/package_aggregation_controller.ts create mode 100644 ui/src/controller/aggregation/wattson/process_aggregation_controller.ts create mode 100644 ui/src/controller/aggregation/wattson/thread_aggregation_controller.ts delete mode 100644 ui/src/controller/area_selection_handler.ts delete mode 100644 ui/src/controller/area_selection_handler_unittest.ts delete mode 100644 ui/src/controller/flamegraph_controller.ts delete mode 100644 ui/src/controller/permalink_controller.ts create mode 100644 ui/src/core/legacy_flamegraph_cache.ts create mode 100644 ui/src/core/query_flamegraph.ts create mode 100644 ui/src/core/track_kinds.ts rename ui/src/core_plugins/async_slices/{async_slice_track_v2.ts => async_slice_track.ts} (69%) create mode 100644 ui/src/core_plugins/chrome_critical_user_interactions/critical_user_interaction_track.ts create mode 100644 ui/src/core_plugins/chrome_tasks/details.ts create mode 100644 ui/src/core_plugins/chrome_tasks/index.ts create mode 100644 ui/src/core_plugins/chrome_tasks/table.ts create mode 100644 ui/src/core_plugins/chrome_tasks/track.ts create mode 100644 ui/src/core_plugins/counter/counter_details_panel.ts create mode 100644 ui/src/core_plugins/counter/trace_processor_counter_track.ts create mode 100644 ui/src/core_plugins/cpu_profile/cpu_profile_track.ts create mode 100644 ui/src/core_plugins/cpu_slices/cpu_slice_track.ts delete mode 100644 ui/src/core_plugins/debug/counter_track.ts delete mode 100644 ui/src/core_plugins/debug/slice_track.ts rename ui/src/core_plugins/frames/{actual_frames_track_v2.ts => actual_frames_track.ts} (91%) rename ui/src/core_plugins/frames/{expected_frames_track_v2.ts => expected_frames_track.ts} (88%) create mode 100644 ui/src/core_plugins/heap_profile/heap_profile_track.ts create mode 100644 ui/src/core_plugins/perf_samples_profile/perf_samples_profile_track.ts create mode 100644 ui/src/core_plugins/screenshots/screenshots_track.ts rename ui/src/core_plugins/{chrome_slices => thread_slice}/index.ts (57%) rename ui/src/core_plugins/thread_state/{thread_state_v2.ts => thread_state_track.ts} (80%) create mode 100644 ui/src/core_plugins/track_utils/index.ts delete mode 100644 ui/src/core_plugins/visualised_args/index.ts create mode 100644 ui/src/core_plugins/wattson/OWNERS create mode 100644 ui/src/core_plugins/wattson/index.ts create mode 100644 ui/src/frontend/app_context.ts create mode 100644 ui/src/frontend/charts/histogram/state.ts create mode 100644 ui/src/frontend/charts/histogram/tab.ts delete mode 100644 ui/src/frontend/counter_panel.ts delete mode 100644 ui/src/frontend/debug_tracks.ts rename ui/src/{core_plugins/debug => frontend/debug_tracks}/add_debug_track_menu.ts (84%) create mode 100644 ui/src/frontend/debug_tracks/counter_track.ts create mode 100644 ui/src/frontend/debug_tracks/debug_tracks.ts rename ui/src/{core_plugins/debug => frontend/debug_tracks}/details_tab.ts (88%) create mode 100644 ui/src/frontend/debug_tracks/slice_track.ts delete mode 100644 ui/src/frontend/flamegraph_panel.ts delete mode 100644 ui/src/frontend/frontend_local_state.ts rename ui/src/frontend/{flamegraph.ts => legacy_flamegraph.ts} (98%) create mode 100644 ui/src/frontend/legacy_flamegraph_panel.ts rename ui/src/frontend/{flamegraph_unittest.ts => legacy_flamegraph_unittest.ts} (96%) create mode 100644 ui/src/frontend/notes_manager.ts create mode 100644 ui/src/frontend/omnibox_manager.ts create mode 100644 ui/src/frontend/permalink.ts create mode 100644 ui/src/frontend/search_overview_track.ts rename ui/src/frontend/{sql_table/tab.ts => sql_table_tab.ts} (76%) rename ui/src/frontend/{chrome_slice_details_tab.ts => thread_slice_details_tab.ts} (86%) rename ui/src/{core_plugins/chrome_slices/chrome_slice_track.ts => frontend/thread_slice_track.ts} (54%) create mode 100644 ui/src/frontend/timeline.ts create mode 100644 ui/src/frontend/trace_context.ts rename ui/src/{core_plugins/custom_sql_table_slices/index.ts => frontend/tracks/custom_sql_table_slice_track.ts} (65%) rename ui/src/{core_plugins/visualised_args => frontend}/visualized_args_track.ts (65%) rename ui/src/frontend/{sql_table/well_known_tables.ts => well_known_sql_tables.ts} (97%) create mode 100644 ui/src/frontend/widgets/README rename ui/src/frontend/{ => widgets}/sql/details/details.ts (95%) rename ui/src/frontend/{ => widgets}/sql/details/well_known_types.ts (57%) rename ui/src/frontend/{sql_table => widgets/sql/table}/argument_selector.ts (89%) rename ui/src/frontend/{sql_table => widgets/sql/table}/column.ts (97%) rename ui/src/frontend/{sql_table => widgets/sql/table}/column_unittest.ts (100%) rename ui/src/frontend/{sql_table => widgets/sql/table}/render_cell.ts (87%) rename ui/src/frontend/{sql_table => widgets/sql/table}/state.ts (95%) rename ui/src/frontend/{sql_table => widgets/sql/table}/state_unittest.ts (98%) rename ui/src/frontend/{sql_table => widgets/sql/table}/table.ts (79%) rename ui/src/frontend/{sql_table => widgets/sql/table}/table_description.ts (100%) create mode 100644 ui/src/plugins/com.google.PixelMemory/OWNERS create mode 100644 ui/src/plugins/com.google.PixelMemory/index.ts create mode 100644 ui/src/plugins/dev.perfetto.AndroidCujs/trackUtils.ts create mode 100644 ui/src/plugins/dev.perfetto.GpuByProcess/OWNERS create mode 100644 ui/src/plugins/dev.perfetto.GpuByProcess/index.ts create mode 100644 ui/src/plugins/dev.perfetto.PinAndroidPerfMetrics/OWNERS create mode 100644 ui/src/plugins/dev.perfetto.PinAndroidPerfMetrics/handlers/fullTraceJankMetricHandler.ts create mode 100644 ui/src/plugins/dev.perfetto.PinAndroidPerfMetrics/handlers/fullTraceJankMetricHandler_unittest.ts rename ui/src/{base/utils/index-browser.js => plugins/dev.perfetto.PinAndroidPerfMetrics/handlers/handlerRegistry.ts} (52%) create mode 100644 ui/src/plugins/dev.perfetto.PinAndroidPerfMetrics/handlers/metricUtils.ts create mode 100644 ui/src/plugins/dev.perfetto.PinAndroidPerfMetrics/handlers/pinBlockingCall.ts create mode 100644 ui/src/plugins/dev.perfetto.PinAndroidPerfMetrics/handlers/pinBlockingCall_unittest.ts create mode 100644 ui/src/plugins/dev.perfetto.PinAndroidPerfMetrics/handlers/pinCujScoped.ts create mode 100644 ui/src/plugins/dev.perfetto.PinAndroidPerfMetrics/handlers/pinCujScoped_unittest.ts create mode 100644 ui/src/plugins/dev.perfetto.PinAndroidPerfMetrics/index.ts rename ui/src/{frontend/panel.ts => plugins/dev.perfetto.PinAndroidPerfMetrics/pluginId.ts} (76%) create mode 100644 ui/src/plugins/dev.perfetto.PinSysUITracks/OWNERS create mode 100644 ui/src/plugins/dev.perfetto.PinSysUITracks/index.ts create mode 100644 ui/src/plugins/dev.perfetto.TimelineSync/OWNERS create mode 100644 ui/src/plugins/dev.perfetto.TimelineSync/index.ts create mode 100644 ui/src/plugins/dev.perfetto.TraceMetadata/OWNERS create mode 100644 ui/src/plugins/dev.perfetto.TraceMetadata/index.ts create mode 100644 ui/src/public/tracks.ts rename ui/src/{frontend => trace_processor}/sql_utils.ts (50%) create mode 100644 ui/src/widgets/flamegraph.ts create mode 100644 ui/src/widgets/tag_input.ts diff --git a/.bazelignore b/.bazelignore new file mode 100644 index 0000000000..a35fb26012 --- /dev/null +++ b/.bazelignore @@ -0,0 +1 @@ +ui \ No newline at end of file diff --git a/.bazelrc b/.bazelrc new file mode 100644 index 0000000000..5b3d13f534 --- /dev/null +++ b/.bazelrc @@ -0,0 +1 @@ +build --cxxopt=-std=c++17 diff --git a/.gitignore b/.gitignore index 7e035d04ee..fd1b34ff5f 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ .cproject .deps_sha1 .DS_Store +.env .eslintcache .gclient* .gm-actuals @@ -20,16 +21,17 @@ *.pyc *.sock *.swp +/*.pftrace /bazel-* /compile_commands.json /fuzz_out/ /out* /ui/dist /venv/* -perf.data* -TAGS -/*.pftrace +depot_tools/ examples/sdk/build/ GPATH GRTAGS GTAGS +perf.data* +TAGS diff --git a/Android.bp b/Android.bp index 14e7ab9b4c..065b82a06f 100644 --- a/Android.bp +++ b/Android.bp @@ -386,6 +386,7 @@ cc_library_shared { ], shared_libs: [ "liblog", + "libz", ], static_libs: [ "libasync_safe", @@ -1318,8 +1319,8 @@ filegroup { } // GN: [//protos/perfetto/config:source_set] -java_library { - name: "perfetto_config_java_protos", +filegroup { + name: "perfetto_config_filegroup_proto", srcs: [ "protos/perfetto/common/android_energy_consumer_descriptor.proto", "protos/perfetto/common/android_log_constants.proto", @@ -1350,6 +1351,7 @@ java_library { "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/android/windowmanager_config.proto", "protos/perfetto/config/chrome/chrome_config.proto", "protos/perfetto/config/chrome/scenario_config.proto", "protos/perfetto/config/chrome/v8_config.proto", @@ -1375,10 +1377,6 @@ java_library { "protos/perfetto/config/trace_config.proto", "protos/perfetto/config/track_event/track_event_config.proto", ], - proto: { - type: "lite", - canonical_path_from_root: false, - }, } // GN: //test/cts:perfetto_cts_deps @@ -2350,6 +2348,7 @@ cc_test { ":perfetto_protos_perfetto_trace_translation_lite_gen", ":perfetto_protos_perfetto_trace_translation_zero_gen", ":perfetto_protos_third_party_pprof_zero_gen", + ":perfetto_protos_third_party_simpleperf_zero_gen", ":perfetto_protos_third_party_statsd_config_zero_gen", ":perfetto_src_android_internal_headers", ":perfetto_src_android_internal_lazy_library_loader", @@ -2394,10 +2393,12 @@ cc_test { ":perfetto_src_shared_lib_test_utils", ":perfetto_src_trace_processor_containers_containers", ":perfetto_src_trace_processor_db_column_column", + ":perfetto_src_trace_processor_db_compare", ":perfetto_src_trace_processor_db_db", ":perfetto_src_trace_processor_db_minimal", ":perfetto_src_trace_processor_export_json", ":perfetto_src_trace_processor_importers_android_bugreport_android_bugreport", + ":perfetto_src_trace_processor_importers_android_bugreport_android_log_event", ":perfetto_src_trace_processor_importers_common_common", ":perfetto_src_trace_processor_importers_common_parser_types", ":perfetto_src_trace_processor_importers_common_trace_parser_hdr", @@ -2417,6 +2418,7 @@ cc_test { ":perfetto_src_trace_processor_importers_ninja_ninja", ":perfetto_src_trace_processor_importers_perf_perf", ":perfetto_src_trace_processor_importers_perf_record", + ":perfetto_src_trace_processor_importers_perf_tracker", ":perfetto_src_trace_processor_importers_proto_full", ":perfetto_src_trace_processor_importers_proto_minimal", ":perfetto_src_trace_processor_importers_proto_packet_sequence_state_generation_hdr", @@ -2426,6 +2428,7 @@ cc_test { ":perfetto_src_trace_processor_importers_systrace_full", ":perfetto_src_trace_processor_importers_systrace_systrace_line", ":perfetto_src_trace_processor_importers_systrace_systrace_parser", + ":perfetto_src_trace_processor_importers_zip_full", ":perfetto_src_trace_processor_lib", ":perfetto_src_trace_processor_metatrace", ":perfetto_src_trace_processor_metrics_metrics", @@ -2435,6 +2438,7 @@ cc_test { ":perfetto_src_trace_processor_perfetto_sql_intrinsics_operators_operators", ":perfetto_src_trace_processor_perfetto_sql_intrinsics_table_functions_interface", ":perfetto_src_trace_processor_perfetto_sql_intrinsics_table_functions_table_functions", + ":perfetto_src_trace_processor_perfetto_sql_intrinsics_types_types", ":perfetto_src_trace_processor_sorter_sorter", ":perfetto_src_trace_processor_sqlite_bindings_bindings", ":perfetto_src_trace_processor_sqlite_sqlite", @@ -2457,6 +2461,8 @@ cc_test { ":perfetto_src_trace_processor_util_regex", ":perfetto_src_trace_processor_util_sql_argument", ":perfetto_src_trace_processor_util_stdlib", + ":perfetto_src_trace_processor_util_trace_blob_view_reader", + ":perfetto_src_trace_processor_util_trace_type", ":perfetto_src_trace_processor_util_util", ":perfetto_src_trace_processor_util_zip_reader", ":perfetto_src_traced_probes_android_game_intervention_list_android_game_intervention_list", @@ -2635,8 +2641,10 @@ cc_test { "perfetto_protos_perfetto_trace_translation_lite_gen_headers", "perfetto_protos_perfetto_trace_translation_zero_gen_headers", "perfetto_protos_third_party_pprof_zero_gen_headers", + "perfetto_protos_third_party_simpleperf_zero_gen_headers", "perfetto_protos_third_party_statsd_config_zero_gen_headers", "perfetto_src_base_version_gen_h", + "perfetto_src_trace_processor_importers_proto_gen_cc_android_track_event_descriptor", "perfetto_src_trace_processor_importers_proto_gen_cc_chrome_track_event_descriptor", "perfetto_src_trace_processor_importers_proto_gen_cc_config_descriptor", "perfetto_src_trace_processor_importers_proto_gen_cc_statsd_atoms_descriptor", @@ -2960,6 +2968,7 @@ filegroup { "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/android/windowmanager_config.proto", ], } @@ -2988,6 +2997,7 @@ genrule { "external/perfetto/protos/perfetto/config/android/protolog_config.gen.cc", "external/perfetto/protos/perfetto/config/android/surfaceflinger_layers_config.gen.cc", "external/perfetto/protos/perfetto/config/android/surfaceflinger_transactions_config.gen.cc", + "external/perfetto/protos/perfetto/config/android/windowmanager_config.gen.cc", ], } @@ -3016,6 +3026,7 @@ genrule { "external/perfetto/protos/perfetto/config/android/protolog_config.gen.h", "external/perfetto/protos/perfetto/config/android/surfaceflinger_layers_config.gen.h", "external/perfetto/protos/perfetto/config/android/surfaceflinger_transactions_config.gen.h", + "external/perfetto/protos/perfetto/config/android/windowmanager_config.gen.h", ], export_include_dirs: [ ".", @@ -3039,6 +3050,7 @@ filegroup { "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/android/windowmanager_config.proto", ], } @@ -3066,6 +3078,7 @@ genrule { "external/perfetto/protos/perfetto/config/android/protolog_config.pb.cc", "external/perfetto/protos/perfetto/config/android/surfaceflinger_layers_config.pb.cc", "external/perfetto/protos/perfetto/config/android/surfaceflinger_transactions_config.pb.cc", + "external/perfetto/protos/perfetto/config/android/windowmanager_config.pb.cc", ], } @@ -3093,6 +3106,7 @@ genrule { "external/perfetto/protos/perfetto/config/android/protolog_config.pb.h", "external/perfetto/protos/perfetto/config/android/surfaceflinger_layers_config.pb.h", "external/perfetto/protos/perfetto/config/android/surfaceflinger_transactions_config.pb.h", + "external/perfetto/protos/perfetto/config/android/windowmanager_config.pb.h", ], export_include_dirs: [ ".", @@ -3116,6 +3130,7 @@ filegroup { "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/android/windowmanager_config.proto", ], } @@ -3144,6 +3159,7 @@ genrule { "external/perfetto/protos/perfetto/config/android/protolog_config.pbzero.cc", "external/perfetto/protos/perfetto/config/android/surfaceflinger_layers_config.pbzero.cc", "external/perfetto/protos/perfetto/config/android/surfaceflinger_transactions_config.pbzero.cc", + "external/perfetto/protos/perfetto/config/android/windowmanager_config.pbzero.cc", ], } @@ -3172,6 +3188,7 @@ genrule { "external/perfetto/protos/perfetto/config/android/protolog_config.pbzero.h", "external/perfetto/protos/perfetto/config/android/surfaceflinger_layers_config.pbzero.h", "external/perfetto/protos/perfetto/config/android/surfaceflinger_transactions_config.pbzero.h", + "external/perfetto/protos/perfetto/config/android/windowmanager_config.pbzero.h", ], export_include_dirs: [ ".", @@ -3306,6 +3323,7 @@ genrule { "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/android/windowmanager_config.proto", "protos/perfetto/config/chrome/chrome_config.proto", "protos/perfetto/config/chrome/scenario_config.proto", "protos/perfetto/config/chrome/v8_config.proto", @@ -5231,6 +5249,7 @@ genrule { "protos/perfetto/metrics/android/android_blocking_calls_unagg.proto", "protos/perfetto/metrics/android/android_boot.proto", "protos/perfetto/metrics/android/android_boot_unagg.proto", + "protos/perfetto/metrics/android/android_broadcasts_metric.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", @@ -5285,6 +5304,7 @@ genrule { "protos/perfetto/metrics/android/thread_time_in_state_metric.proto", "protos/perfetto/metrics/android/trace_quality.proto", "protos/perfetto/metrics/android/unsymbolized_frames.proto", + "protos/perfetto/metrics/android/wattson_app_startup.proto", "protos/perfetto/metrics/chrome/all_chrome_metrics.proto", "protos/perfetto/metrics/chrome/args_class_names.proto", "protos/perfetto/metrics/chrome/dropped_frames.proto", @@ -5323,6 +5343,7 @@ genrule { "protos/perfetto/metrics/android/android_blocking_calls_unagg.proto", "protos/perfetto/metrics/android/android_boot.proto", "protos/perfetto/metrics/android/android_boot_unagg.proto", + "protos/perfetto/metrics/android/android_broadcasts_metric.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", @@ -5377,6 +5398,7 @@ genrule { "protos/perfetto/metrics/android/thread_time_in_state_metric.proto", "protos/perfetto/metrics/android/trace_quality.proto", "protos/perfetto/metrics/android/unsymbolized_frames.proto", + "protos/perfetto/metrics/android/wattson_app_startup.proto", "protos/perfetto/metrics/metrics.proto", ], tools: [ @@ -5399,6 +5421,7 @@ genrule { "protos/perfetto/metrics/android/android_blocking_calls_unagg.proto", "protos/perfetto/metrics/android/android_boot.proto", "protos/perfetto/metrics/android/android_boot_unagg.proto", + "protos/perfetto/metrics/android/android_broadcasts_metric.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", @@ -5453,6 +5476,7 @@ genrule { "protos/perfetto/metrics/android/thread_time_in_state_metric.proto", "protos/perfetto/metrics/android/trace_quality.proto", "protos/perfetto/metrics/android/unsymbolized_frames.proto", + "protos/perfetto/metrics/android/wattson_app_startup.proto", "protos/perfetto/metrics/metrics.proto", "protos/perfetto/metrics/webview/all_webview_metrics.proto", "protos/perfetto/metrics/webview/webview_jank_approximation.proto", @@ -5466,12 +5490,54 @@ genrule { ], } +// GN: //protos/perfetto/trace/android:android_track_event_descriptor +genrule { + name: "perfetto_protos_perfetto_trace_android_android_track_event_descriptor", + srcs: [ + "protos/perfetto/trace/android/android_track_event.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", + ], + tools: [ + "aprotoc", + ], + cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --descriptor_set_out=$(out) $(in)", + out: [ + "perfetto_protos_perfetto_trace_android_android_track_event_descriptor.bin", + ], +} + // GN: //protos/perfetto/trace/android:cpp filegroup { name: "perfetto_protos_perfetto_trace_android_cpp", srcs: [ "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", @@ -5501,7 +5567,6 @@ genrule { cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_android_cpp)", out: [ "external/perfetto/protos/perfetto/trace/android/android_game_intervention_list.gen.cc", - "external/perfetto/protos/perfetto/trace/android/android_input_event.gen.cc", "external/perfetto/protos/perfetto/trace/android/android_log.gen.cc", "external/perfetto/protos/perfetto/trace/android/android_system_property.gen.cc", "external/perfetto/protos/perfetto/trace/android/camera_event.gen.cc", @@ -5531,7 +5596,6 @@ genrule { cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_android_cpp)", out: [ "external/perfetto/protos/perfetto/trace/android/android_game_intervention_list.gen.h", - "external/perfetto/protos/perfetto/trace/android/android_input_event.gen.h", "external/perfetto/protos/perfetto/trace/android/android_log.gen.h", "external/perfetto/protos/perfetto/trace/android/android_system_property.gen.h", "external/perfetto/protos/perfetto/trace/android/camera_event.gen.h", @@ -5554,7 +5618,6 @@ filegroup { name: "perfetto_protos_perfetto_trace_android_lite", srcs: [ "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", @@ -5583,7 +5646,6 @@ genrule { cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_android_lite)", out: [ "external/perfetto/protos/perfetto/trace/android/android_game_intervention_list.pb.cc", - "external/perfetto/protos/perfetto/trace/android/android_input_event.pb.cc", "external/perfetto/protos/perfetto/trace/android/android_log.pb.cc", "external/perfetto/protos/perfetto/trace/android/android_system_property.pb.cc", "external/perfetto/protos/perfetto/trace/android/camera_event.pb.cc", @@ -5612,7 +5674,6 @@ genrule { cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_android_lite)", out: [ "external/perfetto/protos/perfetto/trace/android/android_game_intervention_list.pb.h", - "external/perfetto/protos/perfetto/trace/android/android_input_event.pb.h", "external/perfetto/protos/perfetto/trace/android/android_log.pb.h", "external/perfetto/protos/perfetto/trace/android/android_system_property.pb.h", "external/perfetto/protos/perfetto/trace/android/camera_event.pb.h", @@ -5800,14 +5861,25 @@ genrule { "protos/perfetto/common/tracing_service_capabilities.proto", "protos/perfetto/common/tracing_service_state.proto", "protos/perfetto/common/track_event_descriptor.proto", + "protos/perfetto/trace/android/android_input_event.proto", + "protos/perfetto/trace/android/app/statusbarmanager.proto", + "protos/perfetto/trace/android/app/window_configuration.proto", + "protos/perfetto/trace/android/content/activityinfo.proto", + "protos/perfetto/trace/android/content/configuration.proto", + "protos/perfetto/trace/android/content/locale.proto", "protos/perfetto/trace/android/graphics/pixelformat.proto", "protos/perfetto/trace/android/graphics/point.proto", "protos/perfetto/trace/android/graphics/rect.proto", "protos/perfetto/trace/android/inputmethodeditor.proto", "protos/perfetto/trace/android/inputmethodservice/inputmethodservice.proto", "protos/perfetto/trace/android/inputmethodservice/softinputwindow.proto", + "protos/perfetto/trace/android/privacy.proto", "protos/perfetto/trace/android/protolog.proto", + "protos/perfetto/trace/android/server/animationadapter.proto", "protos/perfetto/trace/android/server/inputmethod/inputmethodmanagerservice.proto", + "protos/perfetto/trace/android/server/surfaceanimator.proto", + "protos/perfetto/trace/android/server/windowcontainerthumbnail.proto", + "protos/perfetto/trace/android/server/windowmanagerservice.proto", "protos/perfetto/trace/android/shell_transition.proto", "protos/perfetto/trace/android/surfaceflinger_common.proto", "protos/perfetto/trace/android/surfaceflinger_layers.proto", @@ -5815,6 +5887,8 @@ genrule { "protos/perfetto/trace/android/typedef.proto", "protos/perfetto/trace/android/view/display.proto", "protos/perfetto/trace/android/view/displaycutout.proto", + "protos/perfetto/trace/android/view/displayinfo.proto", + "protos/perfetto/trace/android/view/enums.proto", "protos/perfetto/trace/android/view/imefocuscontroller.proto", "protos/perfetto/trace/android/view/imeinsetssourceconsumer.proto", "protos/perfetto/trace/android/view/inputmethod/editorinfo.proto", @@ -5826,9 +5900,13 @@ genrule { "protos/perfetto/trace/android/view/insetssourceconsumer.proto", "protos/perfetto/trace/android/view/insetssourcecontrol.proto", "protos/perfetto/trace/android/view/insetsstate.proto", + "protos/perfetto/trace/android/view/remote_animation_target.proto", + "protos/perfetto/trace/android/view/surface.proto", "protos/perfetto/trace/android/view/surfacecontrol.proto", "protos/perfetto/trace/android/view/viewrootimpl.proto", "protos/perfetto/trace/android/view/windowlayoutparams.proto", + "protos/perfetto/trace/android/viewcapture.proto", + "protos/perfetto/trace/android/windowmanager.proto", "protos/perfetto/trace/android/winscope.proto", "protos/perfetto/trace/android/winscope_extensions.proto", "protos/perfetto/trace/android/winscope_extensions_impl.proto", @@ -5846,14 +5924,27 @@ genrule { filegroup { name: "perfetto_protos_perfetto_trace_android_winscope_extensions_zero", srcs: [ + "protos/perfetto/trace/android/android_input_event.proto", + "protos/perfetto/trace/android/app/statusbarmanager.proto", + "protos/perfetto/trace/android/app/window_configuration.proto", + "protos/perfetto/trace/android/content/activityinfo.proto", + "protos/perfetto/trace/android/content/configuration.proto", + "protos/perfetto/trace/android/content/locale.proto", "protos/perfetto/trace/android/graphics/pixelformat.proto", "protos/perfetto/trace/android/inputmethodeditor.proto", "protos/perfetto/trace/android/inputmethodservice/inputmethodservice.proto", "protos/perfetto/trace/android/inputmethodservice/softinputwindow.proto", + "protos/perfetto/trace/android/privacy.proto", + "protos/perfetto/trace/android/server/animationadapter.proto", "protos/perfetto/trace/android/server/inputmethod/inputmethodmanagerservice.proto", + "protos/perfetto/trace/android/server/surfaceanimator.proto", + "protos/perfetto/trace/android/server/windowcontainerthumbnail.proto", + "protos/perfetto/trace/android/server/windowmanagerservice.proto", "protos/perfetto/trace/android/typedef.proto", "protos/perfetto/trace/android/view/display.proto", "protos/perfetto/trace/android/view/displaycutout.proto", + "protos/perfetto/trace/android/view/displayinfo.proto", + "protos/perfetto/trace/android/view/enums.proto", "protos/perfetto/trace/android/view/imefocuscontroller.proto", "protos/perfetto/trace/android/view/imeinsetssourceconsumer.proto", "protos/perfetto/trace/android/view/inputmethod/editorinfo.proto", @@ -5865,9 +5956,13 @@ filegroup { "protos/perfetto/trace/android/view/insetssourceconsumer.proto", "protos/perfetto/trace/android/view/insetssourcecontrol.proto", "protos/perfetto/trace/android/view/insetsstate.proto", + "protos/perfetto/trace/android/view/remote_animation_target.proto", + "protos/perfetto/trace/android/view/surface.proto", "protos/perfetto/trace/android/view/surfacecontrol.proto", "protos/perfetto/trace/android/view/viewrootimpl.proto", "protos/perfetto/trace/android/view/windowlayoutparams.proto", + "protos/perfetto/trace/android/viewcapture.proto", + "protos/perfetto/trace/android/windowmanager.proto", "protos/perfetto/trace/android/winscope_extensions_impl.proto", ], } @@ -5886,14 +5981,27 @@ genrule { ], cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --proto_path=external/protobuf/src --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_android_winscope_extensions_zero)", out: [ + "external/perfetto/protos/perfetto/trace/android/android_input_event.pbzero.cc", + "external/perfetto/protos/perfetto/trace/android/app/statusbarmanager.pbzero.cc", + "external/perfetto/protos/perfetto/trace/android/app/window_configuration.pbzero.cc", + "external/perfetto/protos/perfetto/trace/android/content/activityinfo.pbzero.cc", + "external/perfetto/protos/perfetto/trace/android/content/configuration.pbzero.cc", + "external/perfetto/protos/perfetto/trace/android/content/locale.pbzero.cc", "external/perfetto/protos/perfetto/trace/android/graphics/pixelformat.pbzero.cc", "external/perfetto/protos/perfetto/trace/android/inputmethodeditor.pbzero.cc", "external/perfetto/protos/perfetto/trace/android/inputmethodservice/inputmethodservice.pbzero.cc", "external/perfetto/protos/perfetto/trace/android/inputmethodservice/softinputwindow.pbzero.cc", + "external/perfetto/protos/perfetto/trace/android/privacy.pbzero.cc", + "external/perfetto/protos/perfetto/trace/android/server/animationadapter.pbzero.cc", "external/perfetto/protos/perfetto/trace/android/server/inputmethod/inputmethodmanagerservice.pbzero.cc", + "external/perfetto/protos/perfetto/trace/android/server/surfaceanimator.pbzero.cc", + "external/perfetto/protos/perfetto/trace/android/server/windowcontainerthumbnail.pbzero.cc", + "external/perfetto/protos/perfetto/trace/android/server/windowmanagerservice.pbzero.cc", "external/perfetto/protos/perfetto/trace/android/typedef.pbzero.cc", "external/perfetto/protos/perfetto/trace/android/view/display.pbzero.cc", "external/perfetto/protos/perfetto/trace/android/view/displaycutout.pbzero.cc", + "external/perfetto/protos/perfetto/trace/android/view/displayinfo.pbzero.cc", + "external/perfetto/protos/perfetto/trace/android/view/enums.pbzero.cc", "external/perfetto/protos/perfetto/trace/android/view/imefocuscontroller.pbzero.cc", "external/perfetto/protos/perfetto/trace/android/view/imeinsetssourceconsumer.pbzero.cc", "external/perfetto/protos/perfetto/trace/android/view/inputmethod/editorinfo.pbzero.cc", @@ -5905,9 +6013,13 @@ genrule { "external/perfetto/protos/perfetto/trace/android/view/insetssourceconsumer.pbzero.cc", "external/perfetto/protos/perfetto/trace/android/view/insetssourcecontrol.pbzero.cc", "external/perfetto/protos/perfetto/trace/android/view/insetsstate.pbzero.cc", + "external/perfetto/protos/perfetto/trace/android/view/remote_animation_target.pbzero.cc", + "external/perfetto/protos/perfetto/trace/android/view/surface.pbzero.cc", "external/perfetto/protos/perfetto/trace/android/view/surfacecontrol.pbzero.cc", "external/perfetto/protos/perfetto/trace/android/view/viewrootimpl.pbzero.cc", "external/perfetto/protos/perfetto/trace/android/view/windowlayoutparams.pbzero.cc", + "external/perfetto/protos/perfetto/trace/android/viewcapture.pbzero.cc", + "external/perfetto/protos/perfetto/trace/android/windowmanager.pbzero.cc", "external/perfetto/protos/perfetto/trace/android/winscope_extensions_impl.pbzero.cc", ], } @@ -5926,14 +6038,27 @@ genrule { ], cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --proto_path=external/protobuf/src --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_android_winscope_extensions_zero)", out: [ + "external/perfetto/protos/perfetto/trace/android/android_input_event.pbzero.h", + "external/perfetto/protos/perfetto/trace/android/app/statusbarmanager.pbzero.h", + "external/perfetto/protos/perfetto/trace/android/app/window_configuration.pbzero.h", + "external/perfetto/protos/perfetto/trace/android/content/activityinfo.pbzero.h", + "external/perfetto/protos/perfetto/trace/android/content/configuration.pbzero.h", + "external/perfetto/protos/perfetto/trace/android/content/locale.pbzero.h", "external/perfetto/protos/perfetto/trace/android/graphics/pixelformat.pbzero.h", "external/perfetto/protos/perfetto/trace/android/inputmethodeditor.pbzero.h", "external/perfetto/protos/perfetto/trace/android/inputmethodservice/inputmethodservice.pbzero.h", "external/perfetto/protos/perfetto/trace/android/inputmethodservice/softinputwindow.pbzero.h", + "external/perfetto/protos/perfetto/trace/android/privacy.pbzero.h", + "external/perfetto/protos/perfetto/trace/android/server/animationadapter.pbzero.h", "external/perfetto/protos/perfetto/trace/android/server/inputmethod/inputmethodmanagerservice.pbzero.h", + "external/perfetto/protos/perfetto/trace/android/server/surfaceanimator.pbzero.h", + "external/perfetto/protos/perfetto/trace/android/server/windowcontainerthumbnail.pbzero.h", + "external/perfetto/protos/perfetto/trace/android/server/windowmanagerservice.pbzero.h", "external/perfetto/protos/perfetto/trace/android/typedef.pbzero.h", "external/perfetto/protos/perfetto/trace/android/view/display.pbzero.h", "external/perfetto/protos/perfetto/trace/android/view/displaycutout.pbzero.h", + "external/perfetto/protos/perfetto/trace/android/view/displayinfo.pbzero.h", + "external/perfetto/protos/perfetto/trace/android/view/enums.pbzero.h", "external/perfetto/protos/perfetto/trace/android/view/imefocuscontroller.pbzero.h", "external/perfetto/protos/perfetto/trace/android/view/imeinsetssourceconsumer.pbzero.h", "external/perfetto/protos/perfetto/trace/android/view/inputmethod/editorinfo.pbzero.h", @@ -5945,9 +6070,13 @@ genrule { "external/perfetto/protos/perfetto/trace/android/view/insetssourceconsumer.pbzero.h", "external/perfetto/protos/perfetto/trace/android/view/insetssourcecontrol.pbzero.h", "external/perfetto/protos/perfetto/trace/android/view/insetsstate.pbzero.h", + "external/perfetto/protos/perfetto/trace/android/view/remote_animation_target.pbzero.h", + "external/perfetto/protos/perfetto/trace/android/view/surface.pbzero.h", "external/perfetto/protos/perfetto/trace/android/view/surfacecontrol.pbzero.h", "external/perfetto/protos/perfetto/trace/android/view/viewrootimpl.pbzero.h", "external/perfetto/protos/perfetto/trace/android/view/windowlayoutparams.pbzero.h", + "external/perfetto/protos/perfetto/trace/android/viewcapture.pbzero.h", + "external/perfetto/protos/perfetto/trace/android/windowmanager.pbzero.h", "external/perfetto/protos/perfetto/trace/android/winscope_extensions_impl.pbzero.h", ], export_include_dirs: [ @@ -6139,7 +6268,6 @@ filegroup { name: "perfetto_protos_perfetto_trace_android_zero", srcs: [ "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", @@ -6169,7 +6297,6 @@ genrule { cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_android_zero)", out: [ "external/perfetto/protos/perfetto/trace/android/android_game_intervention_list.pbzero.cc", - "external/perfetto/protos/perfetto/trace/android/android_input_event.pbzero.cc", "external/perfetto/protos/perfetto/trace/android/android_log.pbzero.cc", "external/perfetto/protos/perfetto/trace/android/android_system_property.pbzero.cc", "external/perfetto/protos/perfetto/trace/android/camera_event.pbzero.cc", @@ -6199,7 +6326,6 @@ genrule { cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_android_zero)", out: [ "external/perfetto/protos/perfetto/trace/android/android_game_intervention_list.pbzero.h", - "external/perfetto/protos/perfetto/trace/android/android_input_event.pbzero.h", "external/perfetto/protos/perfetto/trace/android/android_log.pbzero.h", "external/perfetto/protos/perfetto/trace/android/android_system_property.pbzero.h", "external/perfetto/protos/perfetto/trace/android/camera_event.pbzero.h", @@ -6416,6 +6542,7 @@ genrule { "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/android/windowmanager_config.proto", "protos/perfetto/config/chrome/chrome_config.proto", "protos/perfetto/config/chrome/scenario_config.proto", "protos/perfetto/config/chrome/v8_config.proto", @@ -6441,7 +6568,6 @@ genrule { "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", @@ -6472,6 +6598,7 @@ genrule { "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/bcl_exynos.proto", "protos/perfetto/trace/ftrace/binder.proto", "protos/perfetto/trace/ftrace/block.proto", "protos/perfetto/trace/ftrace/cgroup.proto", @@ -6480,6 +6607,7 @@ genrule { "protos/perfetto/trace/ftrace/compaction.proto", "protos/perfetto/trace/ftrace/cpuhp.proto", "protos/perfetto/trace/ftrace/cros_ec.proto", + "protos/perfetto/trace/ftrace/dcvsh.proto", "protos/perfetto/trace/ftrace/dma_fence.proto", "protos/perfetto/trace/ftrace/dmabuf_heap.proto", "protos/perfetto/trace/ftrace/dpu.proto", @@ -6504,6 +6632,7 @@ genrule { "protos/perfetto/trace/ftrace/ion.proto", "protos/perfetto/trace/ftrace/ipi.proto", "protos/perfetto/trace/ftrace/irq.proto", + "protos/perfetto/trace/ftrace/kgsl.proto", "protos/perfetto/trace/ftrace/kmem.proto", "protos/perfetto/trace/ftrace/kvm.proto", "protos/perfetto/trace/ftrace/lowmemorykiller.proto", @@ -6897,6 +7026,7 @@ filegroup { name: "perfetto_protos_perfetto_trace_ftrace_cpp", srcs: [ "protos/perfetto/trace/ftrace/android_fs.proto", + "protos/perfetto/trace/ftrace/bcl_exynos.proto", "protos/perfetto/trace/ftrace/binder.proto", "protos/perfetto/trace/ftrace/block.proto", "protos/perfetto/trace/ftrace/cgroup.proto", @@ -6905,6 +7035,7 @@ filegroup { "protos/perfetto/trace/ftrace/compaction.proto", "protos/perfetto/trace/ftrace/cpuhp.proto", "protos/perfetto/trace/ftrace/cros_ec.proto", + "protos/perfetto/trace/ftrace/dcvsh.proto", "protos/perfetto/trace/ftrace/dma_fence.proto", "protos/perfetto/trace/ftrace/dmabuf_heap.proto", "protos/perfetto/trace/ftrace/dpu.proto", @@ -6929,6 +7060,7 @@ filegroup { "protos/perfetto/trace/ftrace/ion.proto", "protos/perfetto/trace/ftrace/ipi.proto", "protos/perfetto/trace/ftrace/irq.proto", + "protos/perfetto/trace/ftrace/kgsl.proto", "protos/perfetto/trace/ftrace/kmem.proto", "protos/perfetto/trace/ftrace/kvm.proto", "protos/perfetto/trace/ftrace/lowmemorykiller.proto", @@ -6983,6 +7115,7 @@ genrule { cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_ftrace_cpp)", out: [ "external/perfetto/protos/perfetto/trace/ftrace/android_fs.gen.cc", + "external/perfetto/protos/perfetto/trace/ftrace/bcl_exynos.gen.cc", "external/perfetto/protos/perfetto/trace/ftrace/binder.gen.cc", "external/perfetto/protos/perfetto/trace/ftrace/block.gen.cc", "external/perfetto/protos/perfetto/trace/ftrace/cgroup.gen.cc", @@ -6991,6 +7124,7 @@ genrule { "external/perfetto/protos/perfetto/trace/ftrace/compaction.gen.cc", "external/perfetto/protos/perfetto/trace/ftrace/cpuhp.gen.cc", "external/perfetto/protos/perfetto/trace/ftrace/cros_ec.gen.cc", + "external/perfetto/protos/perfetto/trace/ftrace/dcvsh.gen.cc", "external/perfetto/protos/perfetto/trace/ftrace/dma_fence.gen.cc", "external/perfetto/protos/perfetto/trace/ftrace/dmabuf_heap.gen.cc", "external/perfetto/protos/perfetto/trace/ftrace/dpu.gen.cc", @@ -7015,6 +7149,7 @@ genrule { "external/perfetto/protos/perfetto/trace/ftrace/ion.gen.cc", "external/perfetto/protos/perfetto/trace/ftrace/ipi.gen.cc", "external/perfetto/protos/perfetto/trace/ftrace/irq.gen.cc", + "external/perfetto/protos/perfetto/trace/ftrace/kgsl.gen.cc", "external/perfetto/protos/perfetto/trace/ftrace/kmem.gen.cc", "external/perfetto/protos/perfetto/trace/ftrace/kvm.gen.cc", "external/perfetto/protos/perfetto/trace/ftrace/lowmemorykiller.gen.cc", @@ -7069,6 +7204,7 @@ genrule { cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_ftrace_cpp)", out: [ "external/perfetto/protos/perfetto/trace/ftrace/android_fs.gen.h", + "external/perfetto/protos/perfetto/trace/ftrace/bcl_exynos.gen.h", "external/perfetto/protos/perfetto/trace/ftrace/binder.gen.h", "external/perfetto/protos/perfetto/trace/ftrace/block.gen.h", "external/perfetto/protos/perfetto/trace/ftrace/cgroup.gen.h", @@ -7077,6 +7213,7 @@ genrule { "external/perfetto/protos/perfetto/trace/ftrace/compaction.gen.h", "external/perfetto/protos/perfetto/trace/ftrace/cpuhp.gen.h", "external/perfetto/protos/perfetto/trace/ftrace/cros_ec.gen.h", + "external/perfetto/protos/perfetto/trace/ftrace/dcvsh.gen.h", "external/perfetto/protos/perfetto/trace/ftrace/dma_fence.gen.h", "external/perfetto/protos/perfetto/trace/ftrace/dmabuf_heap.gen.h", "external/perfetto/protos/perfetto/trace/ftrace/dpu.gen.h", @@ -7101,6 +7238,7 @@ genrule { "external/perfetto/protos/perfetto/trace/ftrace/ion.gen.h", "external/perfetto/protos/perfetto/trace/ftrace/ipi.gen.h", "external/perfetto/protos/perfetto/trace/ftrace/irq.gen.h", + "external/perfetto/protos/perfetto/trace/ftrace/kgsl.gen.h", "external/perfetto/protos/perfetto/trace/ftrace/kmem.gen.h", "external/perfetto/protos/perfetto/trace/ftrace/kvm.gen.h", "external/perfetto/protos/perfetto/trace/ftrace/lowmemorykiller.gen.h", @@ -7151,6 +7289,7 @@ filegroup { name: "perfetto_protos_perfetto_trace_ftrace_lite", srcs: [ "protos/perfetto/trace/ftrace/android_fs.proto", + "protos/perfetto/trace/ftrace/bcl_exynos.proto", "protos/perfetto/trace/ftrace/binder.proto", "protos/perfetto/trace/ftrace/block.proto", "protos/perfetto/trace/ftrace/cgroup.proto", @@ -7159,6 +7298,7 @@ filegroup { "protos/perfetto/trace/ftrace/compaction.proto", "protos/perfetto/trace/ftrace/cpuhp.proto", "protos/perfetto/trace/ftrace/cros_ec.proto", + "protos/perfetto/trace/ftrace/dcvsh.proto", "protos/perfetto/trace/ftrace/dma_fence.proto", "protos/perfetto/trace/ftrace/dmabuf_heap.proto", "protos/perfetto/trace/ftrace/dpu.proto", @@ -7183,6 +7323,7 @@ filegroup { "protos/perfetto/trace/ftrace/ion.proto", "protos/perfetto/trace/ftrace/ipi.proto", "protos/perfetto/trace/ftrace/irq.proto", + "protos/perfetto/trace/ftrace/kgsl.proto", "protos/perfetto/trace/ftrace/kmem.proto", "protos/perfetto/trace/ftrace/kvm.proto", "protos/perfetto/trace/ftrace/lowmemorykiller.proto", @@ -7236,6 +7377,7 @@ genrule { cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_ftrace_lite)", out: [ "external/perfetto/protos/perfetto/trace/ftrace/android_fs.pb.cc", + "external/perfetto/protos/perfetto/trace/ftrace/bcl_exynos.pb.cc", "external/perfetto/protos/perfetto/trace/ftrace/binder.pb.cc", "external/perfetto/protos/perfetto/trace/ftrace/block.pb.cc", "external/perfetto/protos/perfetto/trace/ftrace/cgroup.pb.cc", @@ -7244,6 +7386,7 @@ genrule { "external/perfetto/protos/perfetto/trace/ftrace/compaction.pb.cc", "external/perfetto/protos/perfetto/trace/ftrace/cpuhp.pb.cc", "external/perfetto/protos/perfetto/trace/ftrace/cros_ec.pb.cc", + "external/perfetto/protos/perfetto/trace/ftrace/dcvsh.pb.cc", "external/perfetto/protos/perfetto/trace/ftrace/dma_fence.pb.cc", "external/perfetto/protos/perfetto/trace/ftrace/dmabuf_heap.pb.cc", "external/perfetto/protos/perfetto/trace/ftrace/dpu.pb.cc", @@ -7268,6 +7411,7 @@ genrule { "external/perfetto/protos/perfetto/trace/ftrace/ion.pb.cc", "external/perfetto/protos/perfetto/trace/ftrace/ipi.pb.cc", "external/perfetto/protos/perfetto/trace/ftrace/irq.pb.cc", + "external/perfetto/protos/perfetto/trace/ftrace/kgsl.pb.cc", "external/perfetto/protos/perfetto/trace/ftrace/kmem.pb.cc", "external/perfetto/protos/perfetto/trace/ftrace/kvm.pb.cc", "external/perfetto/protos/perfetto/trace/ftrace/lowmemorykiller.pb.cc", @@ -7321,6 +7465,7 @@ genrule { cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_ftrace_lite)", out: [ "external/perfetto/protos/perfetto/trace/ftrace/android_fs.pb.h", + "external/perfetto/protos/perfetto/trace/ftrace/bcl_exynos.pb.h", "external/perfetto/protos/perfetto/trace/ftrace/binder.pb.h", "external/perfetto/protos/perfetto/trace/ftrace/block.pb.h", "external/perfetto/protos/perfetto/trace/ftrace/cgroup.pb.h", @@ -7329,6 +7474,7 @@ genrule { "external/perfetto/protos/perfetto/trace/ftrace/compaction.pb.h", "external/perfetto/protos/perfetto/trace/ftrace/cpuhp.pb.h", "external/perfetto/protos/perfetto/trace/ftrace/cros_ec.pb.h", + "external/perfetto/protos/perfetto/trace/ftrace/dcvsh.pb.h", "external/perfetto/protos/perfetto/trace/ftrace/dma_fence.pb.h", "external/perfetto/protos/perfetto/trace/ftrace/dmabuf_heap.pb.h", "external/perfetto/protos/perfetto/trace/ftrace/dpu.pb.h", @@ -7353,6 +7499,7 @@ genrule { "external/perfetto/protos/perfetto/trace/ftrace/ion.pb.h", "external/perfetto/protos/perfetto/trace/ftrace/ipi.pb.h", "external/perfetto/protos/perfetto/trace/ftrace/irq.pb.h", + "external/perfetto/protos/perfetto/trace/ftrace/kgsl.pb.h", "external/perfetto/protos/perfetto/trace/ftrace/kmem.pb.h", "external/perfetto/protos/perfetto/trace/ftrace/kvm.pb.h", "external/perfetto/protos/perfetto/trace/ftrace/lowmemorykiller.pb.h", @@ -7403,6 +7550,7 @@ filegroup { name: "perfetto_protos_perfetto_trace_ftrace_zero", srcs: [ "protos/perfetto/trace/ftrace/android_fs.proto", + "protos/perfetto/trace/ftrace/bcl_exynos.proto", "protos/perfetto/trace/ftrace/binder.proto", "protos/perfetto/trace/ftrace/block.proto", "protos/perfetto/trace/ftrace/cgroup.proto", @@ -7411,6 +7559,7 @@ filegroup { "protos/perfetto/trace/ftrace/compaction.proto", "protos/perfetto/trace/ftrace/cpuhp.proto", "protos/perfetto/trace/ftrace/cros_ec.proto", + "protos/perfetto/trace/ftrace/dcvsh.proto", "protos/perfetto/trace/ftrace/dma_fence.proto", "protos/perfetto/trace/ftrace/dmabuf_heap.proto", "protos/perfetto/trace/ftrace/dpu.proto", @@ -7435,6 +7584,7 @@ filegroup { "protos/perfetto/trace/ftrace/ion.proto", "protos/perfetto/trace/ftrace/ipi.proto", "protos/perfetto/trace/ftrace/irq.proto", + "protos/perfetto/trace/ftrace/kgsl.proto", "protos/perfetto/trace/ftrace/kmem.proto", "protos/perfetto/trace/ftrace/kvm.proto", "protos/perfetto/trace/ftrace/lowmemorykiller.proto", @@ -7489,6 +7639,7 @@ genrule { cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_ftrace_zero)", out: [ "external/perfetto/protos/perfetto/trace/ftrace/android_fs.pbzero.cc", + "external/perfetto/protos/perfetto/trace/ftrace/bcl_exynos.pbzero.cc", "external/perfetto/protos/perfetto/trace/ftrace/binder.pbzero.cc", "external/perfetto/protos/perfetto/trace/ftrace/block.pbzero.cc", "external/perfetto/protos/perfetto/trace/ftrace/cgroup.pbzero.cc", @@ -7497,6 +7648,7 @@ genrule { "external/perfetto/protos/perfetto/trace/ftrace/compaction.pbzero.cc", "external/perfetto/protos/perfetto/trace/ftrace/cpuhp.pbzero.cc", "external/perfetto/protos/perfetto/trace/ftrace/cros_ec.pbzero.cc", + "external/perfetto/protos/perfetto/trace/ftrace/dcvsh.pbzero.cc", "external/perfetto/protos/perfetto/trace/ftrace/dma_fence.pbzero.cc", "external/perfetto/protos/perfetto/trace/ftrace/dmabuf_heap.pbzero.cc", "external/perfetto/protos/perfetto/trace/ftrace/dpu.pbzero.cc", @@ -7521,6 +7673,7 @@ genrule { "external/perfetto/protos/perfetto/trace/ftrace/ion.pbzero.cc", "external/perfetto/protos/perfetto/trace/ftrace/ipi.pbzero.cc", "external/perfetto/protos/perfetto/trace/ftrace/irq.pbzero.cc", + "external/perfetto/protos/perfetto/trace/ftrace/kgsl.pbzero.cc", "external/perfetto/protos/perfetto/trace/ftrace/kmem.pbzero.cc", "external/perfetto/protos/perfetto/trace/ftrace/kvm.pbzero.cc", "external/perfetto/protos/perfetto/trace/ftrace/lowmemorykiller.pbzero.cc", @@ -7575,6 +7728,7 @@ genrule { cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_perfetto_trace_ftrace_zero)", out: [ "external/perfetto/protos/perfetto/trace/ftrace/android_fs.pbzero.h", + "external/perfetto/protos/perfetto/trace/ftrace/bcl_exynos.pbzero.h", "external/perfetto/protos/perfetto/trace/ftrace/binder.pbzero.h", "external/perfetto/protos/perfetto/trace/ftrace/block.pbzero.h", "external/perfetto/protos/perfetto/trace/ftrace/cgroup.pbzero.h", @@ -7583,6 +7737,7 @@ genrule { "external/perfetto/protos/perfetto/trace/ftrace/compaction.pbzero.h", "external/perfetto/protos/perfetto/trace/ftrace/cpuhp.pbzero.h", "external/perfetto/protos/perfetto/trace/ftrace/cros_ec.pbzero.h", + "external/perfetto/protos/perfetto/trace/ftrace/dcvsh.pbzero.h", "external/perfetto/protos/perfetto/trace/ftrace/dma_fence.pbzero.h", "external/perfetto/protos/perfetto/trace/ftrace/dmabuf_heap.pbzero.h", "external/perfetto/protos/perfetto/trace/ftrace/dpu.pbzero.h", @@ -7607,6 +7762,7 @@ genrule { "external/perfetto/protos/perfetto/trace/ftrace/ion.pbzero.h", "external/perfetto/protos/perfetto/trace/ftrace/ipi.pbzero.h", "external/perfetto/protos/perfetto/trace/ftrace/irq.pbzero.h", + "external/perfetto/protos/perfetto/trace/ftrace/kgsl.pbzero.h", "external/perfetto/protos/perfetto/trace/ftrace/kmem.pbzero.h", "external/perfetto/protos/perfetto/trace/ftrace/kvm.pbzero.h", "external/perfetto/protos/perfetto/trace/ftrace/lowmemorykiller.pbzero.h", @@ -10389,6 +10545,50 @@ genrule { ], } +// GN: //protos/third_party/simpleperf:zero +filegroup { + name: "perfetto_protos_third_party_simpleperf_zero", + srcs: [ + "protos/third_party/simpleperf/record_file.proto", + ], +} + +// GN: //protos/third_party/simpleperf:zero +genrule { + name: "perfetto_protos_third_party_simpleperf_zero_gen", + srcs: [ + ":perfetto_protos_third_party_simpleperf_zero", + ], + tools: [ + "aprotoc", + "protozero_plugin", + ], + cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_third_party_simpleperf_zero)", + out: [ + "external/perfetto/protos/third_party/simpleperf/record_file.pbzero.cc", + ], +} + +// GN: //protos/third_party/simpleperf:zero +genrule { + name: "perfetto_protos_third_party_simpleperf_zero_gen_headers", + srcs: [ + ":perfetto_protos_third_party_simpleperf_zero", + ], + tools: [ + "aprotoc", + "protozero_plugin", + ], + cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_third_party_simpleperf_zero)", + out: [ + "external/perfetto/protos/third_party/simpleperf/record_file.pbzero.h", + ], + export_include_dirs: [ + ".", + "protos", + ], +} + // GN: //protos/third_party/statsd:config_zero filegroup { name: "perfetto_protos_third_party_statsd_config_zero", @@ -12000,8 +12200,19 @@ genrule { filegroup { name: "perfetto_src_trace_processor_importers_android_bugreport_android_bugreport", srcs: [ - "src/trace_processor/importers/android_bugreport/android_bugreport_parser.cc", - "src/trace_processor/importers/android_bugreport/android_log_parser.cc", + "src/trace_processor/importers/android_bugreport/android_bugreport_reader.cc", + "src/trace_processor/importers/android_bugreport/android_dumpstate_reader.cc", + "src/trace_processor/importers/android_bugreport/android_log_event_parser_impl.cc", + "src/trace_processor/importers/android_bugreport/android_log_reader.cc", + "src/trace_processor/importers/android_bugreport/chunked_line_reader.cc", + ], +} + +// GN: //src/trace_processor/importers/android_bugreport:android_log_event +filegroup { + name: "perfetto_src_trace_processor_importers_android_bugreport_android_log_event", + srcs: [ + "src/trace_processor/importers/android_bugreport/android_log_event.cc", ], } @@ -12009,7 +12220,7 @@ filegroup { filegroup { name: "perfetto_src_trace_processor_importers_android_bugreport_unittests", srcs: [ - "src/trace_processor/importers/android_bugreport/android_log_parser_unittest.cc", + "src/trace_processor/importers/android_bugreport/android_log_unittest.cc", ], } @@ -12022,6 +12233,7 @@ filegroup { "src/trace_processor/importers/common/async_track_set_tracker.cc", "src/trace_processor/importers/common/clock_converter.cc", "src/trace_processor/importers/common/clock_tracker.cc", + "src/trace_processor/importers/common/cpu_tracker.cc", "src/trace_processor/importers/common/deobfuscation_mapping_table.cc", "src/trace_processor/importers/common/event_tracker.cc", "src/trace_processor/importers/common/flow_tracker.cc", @@ -12030,13 +12242,16 @@ filegroup { "src/trace_processor/importers/common/machine_tracker.cc", "src/trace_processor/importers/common/mapping_tracker.cc", "src/trace_processor/importers/common/metadata_tracker.cc", + "src/trace_processor/importers/common/process_track_translation_table.cc", "src/trace_processor/importers/common/process_tracker.cc", "src/trace_processor/importers/common/sched_event_tracker.cc", + "src/trace_processor/importers/common/scoped_active_trace_file.cc", "src/trace_processor/importers/common/slice_tracker.cc", "src/trace_processor/importers/common/slice_translation_table.cc", "src/trace_processor/importers/common/stack_profile_tracker.cc", "src/trace_processor/importers/common/system_info_tracker.cc", "src/trace_processor/importers/common/thread_state_tracker.cc", + "src/trace_processor/importers/common/trace_file_tracker.cc", "src/trace_processor/importers/common/trace_parser.cc", "src/trace_processor/importers/common/track_tracker.cc", "src/trace_processor/importers/common/virtual_memory_mapping.cc", @@ -12065,6 +12280,7 @@ filegroup { "src/trace_processor/importers/common/deobfuscation_mapping_table_unittest.cc", "src/trace_processor/importers/common/event_tracker_unittest.cc", "src/trace_processor/importers/common/flow_tracker_unittest.cc", + "src/trace_processor/importers/common/process_track_translation_table_unittest.cc", "src/trace_processor/importers/common/process_tracker_unittest.cc", "src/trace_processor/importers/common/slice_tracker_unittest.cc", "src/trace_processor/importers/common/slice_translation_table_unittest.cc", @@ -12236,10 +12452,12 @@ filegroup { filegroup { name: "perfetto_src_trace_processor_importers_perf_perf", srcs: [ - "src/trace_processor/importers/perf/perf_data_parser.cc", - "src/trace_processor/importers/perf/perf_data_reader.cc", + "src/trace_processor/importers/perf/attrs_section_reader.cc", + "src/trace_processor/importers/perf/features.cc", + "src/trace_processor/importers/perf/mmap_record.cc", "src/trace_processor/importers/perf/perf_data_tokenizer.cc", - "src/trace_processor/importers/perf/perf_data_tracker.cc", + "src/trace_processor/importers/perf/record_parser.cc", + "src/trace_processor/importers/perf/sample.cc", ], } @@ -12247,17 +12465,24 @@ filegroup { filegroup { name: "perfetto_src_trace_processor_importers_perf_record", srcs: [ + "src/trace_processor/importers/perf/perf_counter.cc", "src/trace_processor/importers/perf/perf_event_attr.cc", "src/trace_processor/importers/perf/perf_session.cc", ], } +// GN: //src/trace_processor/importers/perf:tracker +filegroup { + name: "perfetto_src_trace_processor_importers_perf_tracker", + srcs: [ + "src/trace_processor/importers/perf/dso_tracker.cc", + ], +} + // GN: //src/trace_processor/importers/perf:unittests filegroup { name: "perfetto_src_trace_processor_importers_perf_unittests", srcs: [ - "src/trace_processor/importers/perf/perf_data_reader_unittest.cc", - "src/trace_processor/importers/perf/perf_data_tracker_unittest.cc", "src/trace_processor/importers/perf/perf_session_unittest.cc", "src/trace_processor/importers/perf/reader_unittest.cc", ], @@ -12280,6 +12505,9 @@ filegroup { "src/trace_processor/importers/proto/heap_graph_module.cc", "src/trace_processor/importers/proto/heap_graph_tracker.cc", "src/trace_processor/importers/proto/metadata_module.cc", + "src/trace_processor/importers/proto/pigweed_detokenizer.cc", + "src/trace_processor/importers/proto/pixel_modem_module.cc", + "src/trace_processor/importers/proto/pixel_modem_parser.cc", "src/trace_processor/importers/proto/statsd_module.cc", "src/trace_processor/importers/proto/string_encoding_utils.cc", "src/trace_processor/importers/proto/system_probes_module.cc", @@ -12292,6 +12520,21 @@ filegroup { ], } +// GN: //src/trace_processor/importers/proto:gen_cc_android_track_event_descriptor +genrule { + name: "perfetto_src_trace_processor_importers_proto_gen_cc_android_track_event_descriptor", + srcs: [ + ":perfetto_protos_perfetto_trace_android_android_track_event_descriptor", + ], + cmd: "$(location tools/gen_cc_proto_descriptor.py) --gen_dir=$(genDir) --cpp_out=$(out) $(in)", + out: [ + "src/trace_processor/importers/proto/android_track_event.descriptor.h", + ], + tool_files: [ + "tools/gen_cc_proto_descriptor.py", + ], +} + // GN: //src/trace_processor/importers/proto:gen_cc_chrome_track_event_descriptor genrule { name: "perfetto_src_trace_processor_importers_proto_gen_cc_chrome_track_event_descriptor", @@ -12372,6 +12615,7 @@ filegroup { name: "perfetto_src_trace_processor_importers_proto_minimal", srcs: [ "src/trace_processor/importers/proto/active_chrome_processes_tracker.cc", + "src/trace_processor/importers/proto/args_parser.cc", "src/trace_processor/importers/proto/chrome_string_lookup.cc", "src/trace_processor/importers/proto/chrome_system_probes_module.cc", "src/trace_processor/importers/proto/chrome_system_probes_parser.cc", @@ -12424,6 +12668,8 @@ filegroup { "src/trace_processor/importers/proto/perf_sample_tracker_unittest.cc", "src/trace_processor/importers/proto/profile_packet_sequence_state_unittest.cc", "src/trace_processor/importers/proto/proto_trace_parser_impl_unittest.cc", + "src/trace_processor/importers/proto/proto_trace_reader_unittest.cc", + "src/trace_processor/importers/proto/proto_trace_tokenizer_unittest.cc", "src/trace_processor/importers/proto/string_encoding_utils_unittests.cc", ], } @@ -12432,13 +12678,15 @@ filegroup { filegroup { name: "perfetto_src_trace_processor_importers_proto_winscope_full", srcs: [ + "src/trace_processor/importers/proto/winscope/android_input_event_parser.cc", + "src/trace_processor/importers/proto/winscope/protolog_message_decoder.cc", "src/trace_processor/importers/proto/winscope/protolog_messages_tracker.cc", "src/trace_processor/importers/proto/winscope/protolog_parser.cc", "src/trace_processor/importers/proto/winscope/shell_transitions_parser.cc", "src/trace_processor/importers/proto/winscope/shell_transitions_tracker.cc", "src/trace_processor/importers/proto/winscope/surfaceflinger_layers_parser.cc", "src/trace_processor/importers/proto/winscope/surfaceflinger_transactions_parser.cc", - "src/trace_processor/importers/proto/winscope/winscope_args_parser.cc", + "src/trace_processor/importers/proto/winscope/viewcapture_args_parser.cc", "src/trace_processor/importers/proto/winscope/winscope_module.cc", ], } @@ -12505,6 +12753,14 @@ filegroup { ], } +// GN: //src/trace_processor/importers/zip:full +filegroup { + name: "perfetto_src_trace_processor_importers_zip_full", + srcs: [ + "src/trace_processor/importers/zip/zip_trace_reader.cc", + ], +} + // GN: //src/trace_processor:lib filegroup { name: "perfetto_src_trace_processor_lib", @@ -12591,6 +12847,7 @@ genrule { "src/trace_processor/metrics/sql/android/android_blocking_calls_unagg.sql", "src/trace_processor/metrics/sql/android/android_boot.sql", "src/trace_processor/metrics/sql/android/android_boot_unagg.sql", + "src/trace_processor/metrics/sql/android/android_broadcasts.sql", "src/trace_processor/metrics/sql/android/android_camera.sql", "src/trace_processor/metrics/sql/android/android_camera_unagg.sql", "src/trace_processor/metrics/sql/android/android_cpu.sql", @@ -12703,6 +12960,7 @@ genrule { "src/trace_processor/metrics/sql/android/sysui_notif_shade_list_builder_slices.sql", "src/trace_processor/metrics/sql/android/sysui_update_notif_on_ui_mode_changed_metric.sql", "src/trace_processor/metrics/sql/android/unsymbolized_frames.sql", + "src/trace_processor/metrics/sql/android/wattson_app_startup.sql", "src/trace_processor/metrics/sql/chrome/actual_power_by_category.sql", "src/trace_processor/metrics/sql/chrome/actual_power_by_rail_mode.sql", "src/trace_processor/metrics/sql/chrome/chrome_args_class_names.sql", @@ -12809,9 +13067,11 @@ filegroup { "src/trace_processor/perfetto_sql/intrinsics/functions/base64.cc", "src/trace_processor/perfetto_sql/intrinsics/functions/create_function.cc", "src/trace_processor/perfetto_sql/intrinsics/functions/create_view_function.cc", - "src/trace_processor/perfetto_sql/intrinsics/functions/dfs.cc", "src/trace_processor/perfetto_sql/intrinsics/functions/dominator_tree.cc", + "src/trace_processor/perfetto_sql/intrinsics/functions/graph_scan.cc", + "src/trace_processor/perfetto_sql/intrinsics/functions/graph_traversal.cc", "src/trace_processor/perfetto_sql/intrinsics/functions/import.cc", + "src/trace_processor/perfetto_sql/intrinsics/functions/interval_intersect.cc", "src/trace_processor/perfetto_sql/intrinsics/functions/layout_functions.cc", "src/trace_processor/perfetto_sql/intrinsics/functions/math.cc", "src/trace_processor/perfetto_sql/intrinsics/functions/pprof_functions.cc", @@ -12819,6 +13079,7 @@ filegroup { "src/trace_processor/perfetto_sql/intrinsics/functions/stack_functions.cc", "src/trace_processor/perfetto_sql/intrinsics/functions/structural_tree_partition.cc", "src/trace_processor/perfetto_sql/intrinsics/functions/to_ftrace.cc", + "src/trace_processor/perfetto_sql/intrinsics/functions/type_builders.cc", ], } @@ -12871,6 +13132,7 @@ 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/interval_intersect_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", @@ -12908,7 +13170,6 @@ filegroup { "src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_sched_upid.cc", "src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_slice_layout.cc", "src/trace_processor/perfetto_sql/intrinsics/table_functions/flamegraph_construction_algorithms.cc", - "src/trace_processor/perfetto_sql/intrinsics/table_functions/interval_intersect.cc", "src/trace_processor/perfetto_sql/intrinsics/table_functions/table_info.cc", ], } @@ -12967,6 +13228,11 @@ filegroup { ], } +// GN: //src/trace_processor/perfetto_sql/intrinsics/types:types +filegroup { + name: "perfetto_src_trace_processor_perfetto_sql_intrinsics_types_types", +} + // GN: //src/trace_processor/perfetto_sql/prelude:prelude genrule { name: "perfetto_src_trace_processor_perfetto_sql_prelude_prelude", @@ -12994,17 +13260,31 @@ genrule { "src/trace_processor/perfetto_sql/stdlib/android/battery_stats.sql", "src/trace_processor/perfetto_sql/stdlib/android/binder.sql", "src/trace_processor/perfetto_sql/stdlib/android/broadcasts.sql", + "src/trace_processor/perfetto_sql/stdlib/android/cpu/cluster_type.sql", "src/trace_processor/perfetto_sql/stdlib/android/critical_blocking_calls.sql", "src/trace_processor/perfetto_sql/stdlib/android/device.sql", "src/trace_processor/perfetto_sql/stdlib/android/dvfs.sql", + "src/trace_processor/perfetto_sql/stdlib/android/frames/jank_type.sql", "src/trace_processor/perfetto_sql/stdlib/android/frames/per_frame_metrics.sql", "src/trace_processor/perfetto_sql/stdlib/android/frames/timeline.sql", "src/trace_processor/perfetto_sql/stdlib/android/frames/timeline_maxsdk28.sql", "src/trace_processor/perfetto_sql/stdlib/android/freezer.sql", "src/trace_processor/perfetto_sql/stdlib/android/garbage_collection.sql", + "src/trace_processor/perfetto_sql/stdlib/android/gpu/frequency.sql", + "src/trace_processor/perfetto_sql/stdlib/android/gpu/memory.sql", "src/trace_processor/perfetto_sql/stdlib/android/input.sql", "src/trace_processor/perfetto_sql/stdlib/android/io.sql", "src/trace_processor/perfetto_sql/stdlib/android/job_scheduler.sql", + "src/trace_processor/perfetto_sql/stdlib/android/memory/dmabuf.sql", + "src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/class_tree.sql", + "src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/dominator_class_tree.sql", + "src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/dominator_tree.sql", + "src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/excluded_refs.sql", + "src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/heap_graph_class_aggregation.sql", + "src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/helpers.sql", + "src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/raw_dominator_tree.sql", + "src/trace_processor/perfetto_sql/stdlib/android/memory/heap_profile/callstacks.sql", + "src/trace_processor/perfetto_sql/stdlib/android/memory/process.sql", "src/trace_processor/perfetto_sql/stdlib/android/monitor_contention.sql", "src/trace_processor/perfetto_sql/stdlib/android/network_packets.sql", "src/trace_processor/perfetto_sql/stdlib/android/oom_adjuster.sql", @@ -13022,39 +13302,50 @@ genrule { "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", + "src/trace_processor/perfetto_sql/stdlib/android/version.sql", "src/trace_processor/perfetto_sql/stdlib/android/winscope/inputmethod.sql", + "src/trace_processor/perfetto_sql/stdlib/android/winscope/viewcapture.sql", + "src/trace_processor/perfetto_sql/stdlib/android/winscope/windowmanager.sql", + "src/trace_processor/perfetto_sql/stdlib/callstacks/stack_profile.sql", "src/trace_processor/perfetto_sql/stdlib/chrome/**/*.sql", "src/trace_processor/perfetto_sql/stdlib/common/args.sql", "src/trace_processor/perfetto_sql/stdlib/common/counters.sql", - "src/trace_processor/perfetto_sql/stdlib/common/cpus.sql", "src/trace_processor/perfetto_sql/stdlib/common/metadata.sql", "src/trace_processor/perfetto_sql/stdlib/common/percentiles.sql", "src/trace_processor/perfetto_sql/stdlib/common/slices.sql", - "src/trace_processor/perfetto_sql/stdlib/common/thread_states.sql", "src/trace_processor/perfetto_sql/stdlib/common/timestamps.sql", "src/trace_processor/perfetto_sql/stdlib/counters/intervals.sql", - "src/trace_processor/perfetto_sql/stdlib/cpu/cpus.sql", - "src/trace_processor/perfetto_sql/stdlib/cpu/freq.sql", - "src/trace_processor/perfetto_sql/stdlib/cpu/idle.sql", - "src/trace_processor/perfetto_sql/stdlib/cpu/size.sql", "src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/args.sql", "src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/counters.sql", - "src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/cpus.sql", "src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/metadata.sql", "src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/percentiles.sql", "src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/slices.sql", - "src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/thread_states.sql", "src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/timestamps.sql", + "src/trace_processor/perfetto_sql/stdlib/export/to_firefox_profile.sql", + "src/trace_processor/perfetto_sql/stdlib/graphs/critical_path.sql", "src/trace_processor/perfetto_sql/stdlib/graphs/dominator_tree.sql", + "src/trace_processor/perfetto_sql/stdlib/graphs/hierarchy.sql", "src/trace_processor/perfetto_sql/stdlib/graphs/partition.sql", + "src/trace_processor/perfetto_sql/stdlib/graphs/scan.sql", "src/trace_processor/perfetto_sql/stdlib/graphs/search.sql", "src/trace_processor/perfetto_sql/stdlib/intervals/intersect.sql", "src/trace_processor/perfetto_sql/stdlib/intervals/overlap.sql", - "src/trace_processor/perfetto_sql/stdlib/linux/cpu_idle.sql", - "src/trace_processor/perfetto_sql/stdlib/memory/heap_graph_dominator_tree.sql", + "src/trace_processor/perfetto_sql/stdlib/linux/cpu/frequency.sql", + "src/trace_processor/perfetto_sql/stdlib/linux/cpu/idle.sql", + "src/trace_processor/perfetto_sql/stdlib/linux/cpu/utilization/general.sql", + "src/trace_processor/perfetto_sql/stdlib/linux/cpu/utilization/process.sql", + "src/trace_processor/perfetto_sql/stdlib/linux/cpu/utilization/system.sql", + "src/trace_processor/perfetto_sql/stdlib/linux/cpu/utilization/thread.sql", + "src/trace_processor/perfetto_sql/stdlib/linux/memory/general.sql", + "src/trace_processor/perfetto_sql/stdlib/linux/memory/high_watermark.sql", + "src/trace_processor/perfetto_sql/stdlib/linux/memory/process.sql", + "src/trace_processor/perfetto_sql/stdlib/linux/perf/samples.sql", + "src/trace_processor/perfetto_sql/stdlib/metasql/column_list.sql", + "src/trace_processor/perfetto_sql/stdlib/metasql/table_list.sql", "src/trace_processor/perfetto_sql/stdlib/pkvm/hypervisor.sql", "src/trace_processor/perfetto_sql/stdlib/prelude/casts.sql", "src/trace_processor/perfetto_sql/stdlib/prelude/slices.sql", + "src/trace_processor/perfetto_sql/stdlib/prelude/tables_views.sql", "src/trace_processor/perfetto_sql/stdlib/prelude/trace_bounds.sql", "src/trace_processor/perfetto_sql/stdlib/sched/runnable.sql", "src/trace_processor/perfetto_sql/stdlib/sched/states.sql", @@ -13063,24 +13354,29 @@ genrule { "src/trace_processor/perfetto_sql/stdlib/sched/thread_level_parallelism.sql", "src/trace_processor/perfetto_sql/stdlib/sched/thread_state_flattened.sql", "src/trace_processor/perfetto_sql/stdlib/sched/time_in_state.sql", - "src/trace_processor/perfetto_sql/stdlib/sched/utilization/general.sql", - "src/trace_processor/perfetto_sql/stdlib/sched/utilization/process.sql", - "src/trace_processor/perfetto_sql/stdlib/sched/utilization/system.sql", - "src/trace_processor/perfetto_sql/stdlib/sched/utilization/thread.sql", "src/trace_processor/perfetto_sql/stdlib/slices/cpu_time.sql", "src/trace_processor/perfetto_sql/stdlib/slices/flat_slices.sql", + "src/trace_processor/perfetto_sql/stdlib/slices/flow.sql", + "src/trace_processor/perfetto_sql/stdlib/slices/hierarchy.sql", "src/trace_processor/perfetto_sql/stdlib/slices/slices.sql", "src/trace_processor/perfetto_sql/stdlib/slices/with_context.sql", "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/flamegraph.sql", + "src/trace_processor/perfetto_sql/stdlib/viz/summary/counters.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", + "src/trace_processor/perfetto_sql/stdlib/wattson/cpu_split.sql", + "src/trace_processor/perfetto_sql/stdlib/wattson/curves/device.sql", + "src/trace_processor/perfetto_sql/stdlib/wattson/curves/grouped.sql", + "src/trace_processor/perfetto_sql/stdlib/wattson/curves/ungrouped.sql", + "src/trace_processor/perfetto_sql/stdlib/wattson/curves/utils.sql", + "src/trace_processor/perfetto_sql/stdlib/wattson/device_infos.sql", "src/trace_processor/perfetto_sql/stdlib/wattson/system_state.sql", ], cmd: "$(location tools/gen_amalgamated_sql.py) --namespace=stdlib --cpp-out=$(out) $(in)", @@ -13182,6 +13478,7 @@ filegroup { "src/trace_processor/trace_processor_context.cc", "src/trace_processor/trace_processor_storage.cc", "src/trace_processor/trace_processor_storage_impl.cc", + "src/trace_processor/trace_reader_registry.cc", "src/trace_processor/virtual_destructors.cc", ], } @@ -13359,14 +13656,6 @@ filegroup { ], } -// GN: //src/trace_processor/util:file_buffer -filegroup { - name: "perfetto_src_trace_processor_util_file_buffer", - srcs: [ - "src/trace_processor/util/file_buffer.cc", - ], -} - // GN: //src/trace_processor/util:glob filegroup { name: "perfetto_src_trace_processor_util_glob", @@ -13456,13 +13745,28 @@ filegroup { name: "perfetto_src_trace_processor_util_stdlib", } +// GN: //src/trace_processor/util:trace_blob_view_reader +filegroup { + name: "perfetto_src_trace_processor_util_trace_blob_view_reader", + srcs: [ + "src/trace_processor/util/trace_blob_view_reader.cc", + ], +} + +// GN: //src/trace_processor/util:trace_type +filegroup { + name: "perfetto_src_trace_processor_util_trace_type", + srcs: [ + "src/trace_processor/util/trace_type.cc", + ], +} + // GN: //src/trace_processor/util:unittests filegroup { name: "perfetto_src_trace_processor_util_unittests", srcs: [ "src/trace_processor/util/bump_allocator_unittest.cc", "src/trace_processor/util/debug_annotation_parser_unittest.cc", - "src/trace_processor/util/file_buffer_unittest.cc", "src/trace_processor/util/glob_unittest.cc", "src/trace_processor/util/gzip_utils_unittest.cc", "src/trace_processor/util/proto_profiler_unittest.cc", @@ -13471,6 +13775,7 @@ filegroup { "src/trace_processor/util/protozero_to_text_unittests.cc", "src/trace_processor/util/sql_argument_unittest.cc", "src/trace_processor/util/streaming_line_reader_unittest.cc", + "src/trace_processor/util/trace_blob_view_reader_unittest.cc", "src/trace_processor/util/zip_reader_unittest.cc", ], } @@ -13493,32 +13798,26 @@ filegroup { filegroup { name: "perfetto_src_trace_redaction_trace_redaction", srcs: [ + "src/trace_redaction/broadphase_packet_filter.cc", "src/trace_redaction/collect_frame_cookies.cc", "src/trace_redaction/collect_system_info.cc", "src/trace_redaction/collect_timeline_events.cc", - "src/trace_redaction/filter_ftrace_using_allowlist.cc", - "src/trace_redaction/filter_packet_using_allowlist.cc", - "src/trace_redaction/filter_print_events.cc", - "src/trace_redaction/filter_sched_waking_events.cc", - "src/trace_redaction/filter_task_rename.cc", + "src/trace_redaction/filtering.cc", "src/trace_redaction/find_package_uid.cc", - "src/trace_redaction/modify_process_trees.cc", + "src/trace_redaction/merge_threads.cc", + "src/trace_redaction/modify.cc", "src/trace_redaction/populate_allow_lists.cc", "src/trace_redaction/process_thread_timeline.cc", "src/trace_redaction/proto_util.cc", "src/trace_redaction/prune_package_list.cc", - "src/trace_redaction/redact_ftrace_event.cc", - "src/trace_redaction/redact_process_free.cc", - "src/trace_redaction/redact_sched_switch.cc", - "src/trace_redaction/redact_task_newtask.cc", - "src/trace_redaction/remap_scheduling_events.cc", - "src/trace_redaction/scrub_ftrace_events.cc", + "src/trace_redaction/redact_ftrace_events.cc", + "src/trace_redaction/redact_process_events.cc", + "src/trace_redaction/redact_process_trees.cc", + "src/trace_redaction/redact_sched_events.cc", "src/trace_redaction/scrub_process_stats.cc", - "src/trace_redaction/scrub_process_trees.cc", - "src/trace_redaction/scrub_trace_packet.cc", - "src/trace_redaction/suspend_resume.cc", "src/trace_redaction/trace_redaction_framework.cc", "src/trace_redaction/trace_redactor.cc", + "src/trace_redaction/verify_integrity.cc", ], } @@ -13526,22 +13825,18 @@ filegroup { filegroup { name: "perfetto_src_trace_redaction_unittests", srcs: [ + "src/trace_redaction/broadphase_packet_filter_unittest.cc", "src/trace_redaction/collect_frame_cookies_unittest.cc", "src/trace_redaction/collect_system_info_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", "src/trace_redaction/filter_sched_waking_events_unittest.cc", - "src/trace_redaction/filter_task_rename_unittest.cc", "src/trace_redaction/find_package_uid_unittest.cc", "src/trace_redaction/process_thread_timeline_unittest.cc", "src/trace_redaction/proto_util_unittest.cc", "src/trace_redaction/prune_package_list_unittest.cc", - "src/trace_redaction/redact_process_free_unittest.cc", - "src/trace_redaction/redact_sched_switch_unittest.cc", - "src/trace_redaction/redact_task_newtask_unittest.cc", - "src/trace_redaction/remap_scheduling_events_unittest.cc", - "src/trace_redaction/suspend_resume_unittest.cc", + "src/trace_redaction/redact_process_events_unittest.cc", + "src/trace_redaction/redact_sched_events_unittest.cc", + "src/trace_redaction/verify_integrity_unittest.cc", ], } @@ -13560,12 +13855,28 @@ genrule { ], } +// GN: //src/traceconv:gen_cc_winscope_descriptor +genrule { + name: "perfetto_src_traceconv_gen_cc_winscope_descriptor", + srcs: [ + ":perfetto_protos_perfetto_trace_android_winscope_descriptor", + ], + cmd: "$(location tools/gen_cc_proto_descriptor.py) --gen_dir=$(genDir) --cpp_out=$(out) $(in)", + out: [ + "src/traceconv/winscope.descriptor.h", + ], + tool_files: [ + "tools/gen_cc_proto_descriptor.py", + ], +} + // GN: //src/traceconv:lib filegroup { name: "perfetto_src_traceconv_lib", srcs: [ "src/traceconv/deobfuscate_profile.cc", "src/traceconv/symbolize_profile.cc", + "src/traceconv/trace_to_firefox.cc", "src/traceconv/trace_to_hprof.cc", "src/traceconv/trace_to_json.cc", "src/traceconv/trace_to_profile.cc", @@ -13591,14 +13902,6 @@ filegroup { ], } -// GN: //src/traceconv:unittests -filegroup { - name: "perfetto_src_traceconv_unittests", - srcs: [ - "src/traceconv/trace_to_text_unittest.cc", - ], -} - // GN: //src/traceconv:utils filegroup { name: "perfetto_src_traceconv_utils", @@ -14420,6 +14723,7 @@ java_library { "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/android/windowmanager_config.proto", "protos/perfetto/config/chrome/chrome_config.proto", "protos/perfetto/config/chrome/scenario_config.proto", "protos/perfetto/config/chrome/v8_config.proto", @@ -14445,7 +14749,6 @@ java_library { "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", @@ -14476,6 +14779,7 @@ java_library { "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/bcl_exynos.proto", "protos/perfetto/trace/ftrace/binder.proto", "protos/perfetto/trace/ftrace/block.proto", "protos/perfetto/trace/ftrace/cgroup.proto", @@ -14484,6 +14788,7 @@ java_library { "protos/perfetto/trace/ftrace/compaction.proto", "protos/perfetto/trace/ftrace/cpuhp.proto", "protos/perfetto/trace/ftrace/cros_ec.proto", + "protos/perfetto/trace/ftrace/dcvsh.proto", "protos/perfetto/trace/ftrace/dma_fence.proto", "protos/perfetto/trace/ftrace/dmabuf_heap.proto", "protos/perfetto/trace/ftrace/dpu.proto", @@ -14508,6 +14813,7 @@ java_library { "protos/perfetto/trace/ftrace/ion.proto", "protos/perfetto/trace/ftrace/ipi.proto", "protos/perfetto/trace/ftrace/irq.proto", + "protos/perfetto/trace/ftrace/kgsl.proto", "protos/perfetto/trace/ftrace/kmem.proto", "protos/perfetto/trace/ftrace/kvm.proto", "protos/perfetto/trace/ftrace/lowmemorykiller.proto", @@ -14765,7 +15071,6 @@ cc_test { ":perfetto_include_perfetto_ext_traced_traced", ":perfetto_include_perfetto_ext_tracing_core_core", ":perfetto_include_perfetto_ext_tracing_ipc_ipc", - ":perfetto_include_perfetto_profiling_pprof_builder", ":perfetto_include_perfetto_protozero_protozero", ":perfetto_include_perfetto_public_abi_base", ":perfetto_include_perfetto_public_abi_public", @@ -14889,6 +15194,7 @@ cc_test { ":perfetto_protos_perfetto_trace_translation_lite_gen", ":perfetto_protos_perfetto_trace_translation_zero_gen", ":perfetto_protos_third_party_pprof_zero_gen", + ":perfetto_protos_third_party_simpleperf_zero_gen", ":perfetto_protos_third_party_statsd_config_zero_gen", ":perfetto_src_android_internal_headers", ":perfetto_src_android_internal_lazy_library_loader", @@ -14943,7 +15249,6 @@ cc_test { ":perfetto_src_profiling_perf_producer_unittests", ":perfetto_src_profiling_perf_regs_parsing", ":perfetto_src_profiling_perf_unwinding", - ":perfetto_src_profiling_symbolizer_symbolize_database", ":perfetto_src_profiling_symbolizer_symbolizer", ":perfetto_src_profiling_symbolizer_unittests", ":perfetto_src_profiling_unittests", @@ -14979,6 +15284,7 @@ cc_test { ":perfetto_src_trace_processor_db_unittests", ":perfetto_src_trace_processor_export_json", ":perfetto_src_trace_processor_importers_android_bugreport_android_bugreport", + ":perfetto_src_trace_processor_importers_android_bugreport_android_log_event", ":perfetto_src_trace_processor_importers_android_bugreport_unittests", ":perfetto_src_trace_processor_importers_common_common", ":perfetto_src_trace_processor_importers_common_parser_types", @@ -15003,6 +15309,7 @@ cc_test { ":perfetto_src_trace_processor_importers_ninja_ninja", ":perfetto_src_trace_processor_importers_perf_perf", ":perfetto_src_trace_processor_importers_perf_record", + ":perfetto_src_trace_processor_importers_perf_tracker", ":perfetto_src_trace_processor_importers_perf_unittests", ":perfetto_src_trace_processor_importers_proto_full", ":perfetto_src_trace_processor_importers_proto_minimal", @@ -15016,6 +15323,7 @@ cc_test { ":perfetto_src_trace_processor_importers_systrace_systrace_line", ":perfetto_src_trace_processor_importers_systrace_systrace_parser", ":perfetto_src_trace_processor_importers_systrace_unittests", + ":perfetto_src_trace_processor_importers_zip_full", ":perfetto_src_trace_processor_lib", ":perfetto_src_trace_processor_metatrace", ":perfetto_src_trace_processor_metrics_metrics", @@ -15030,6 +15338,7 @@ cc_test { ":perfetto_src_trace_processor_perfetto_sql_intrinsics_table_functions_interface", ":perfetto_src_trace_processor_perfetto_sql_intrinsics_table_functions_table_functions", ":perfetto_src_trace_processor_perfetto_sql_intrinsics_table_functions_unittests", + ":perfetto_src_trace_processor_perfetto_sql_intrinsics_types_types", ":perfetto_src_trace_processor_rpc_rpc", ":perfetto_src_trace_processor_rpc_unittests", ":perfetto_src_trace_processor_sorter_sorter", @@ -15048,7 +15357,6 @@ cc_test { ":perfetto_src_trace_processor_util_build_id", ":perfetto_src_trace_processor_util_bump_allocator", ":perfetto_src_trace_processor_util_descriptors", - ":perfetto_src_trace_processor_util_file_buffer", ":perfetto_src_trace_processor_util_glob", ":perfetto_src_trace_processor_util_gzip", ":perfetto_src_trace_processor_util_interned_message_view", @@ -15061,15 +15369,13 @@ cc_test { ":perfetto_src_trace_processor_util_regex", ":perfetto_src_trace_processor_util_sql_argument", ":perfetto_src_trace_processor_util_stdlib", + ":perfetto_src_trace_processor_util_trace_blob_view_reader", + ":perfetto_src_trace_processor_util_trace_type", ":perfetto_src_trace_processor_util_unittests", ":perfetto_src_trace_processor_util_util", ":perfetto_src_trace_processor_util_zip_reader", ":perfetto_src_trace_redaction_trace_redaction", ":perfetto_src_trace_redaction_unittests", - ":perfetto_src_traceconv_lib", - ":perfetto_src_traceconv_pprofbuilder", - ":perfetto_src_traceconv_unittests", - ":perfetto_src_traceconv_utils", ":perfetto_src_traced_probes_android_game_intervention_list_android_game_intervention_list", ":perfetto_src_traced_probes_android_game_intervention_list_unittests", ":perfetto_src_traced_probes_android_log_android_log", @@ -15264,6 +15570,7 @@ cc_test { "perfetto_protos_perfetto_trace_translation_lite_gen_headers", "perfetto_protos_perfetto_trace_translation_zero_gen_headers", "perfetto_protos_third_party_pprof_zero_gen_headers", + "perfetto_protos_third_party_simpleperf_zero_gen_headers", "perfetto_protos_third_party_statsd_config_zero_gen_headers", "perfetto_src_base_version_gen_h", "perfetto_src_ipc_test_messages_cpp_gen_headers", @@ -15280,6 +15587,7 @@ cc_test { "perfetto_src_protozero_testing_messages_subpackage_zero_gen_headers", "perfetto_src_protozero_testing_messages_zero_gen_headers", "perfetto_src_trace_processor_gen_cc_test_messages_descriptor", + "perfetto_src_trace_processor_importers_proto_gen_cc_android_track_event_descriptor", "perfetto_src_trace_processor_importers_proto_gen_cc_chrome_track_event_descriptor", "perfetto_src_trace_processor_importers_proto_gen_cc_config_descriptor", "perfetto_src_trace_processor_importers_proto_gen_cc_statsd_atoms_descriptor", @@ -15296,7 +15604,6 @@ cc_test { "perfetto_src_trace_processor_perfetto_sql_stdlib_stdlib", "perfetto_src_trace_processor_tables_py_tables_unittest", "perfetto_src_trace_processor_tables_tables_python", - "perfetto_src_traceconv_gen_cc_trace_descriptor", "perfetto_src_traced_probes_ftrace_test_messages_cpp_gen_headers", "perfetto_src_traced_probes_ftrace_test_messages_lite_gen_headers", "perfetto_src_traced_probes_ftrace_test_messages_zero_gen_headers", @@ -15633,6 +15940,35 @@ cc_library_static { ], } +// GN: //protos/perfetto/trace/android:perfetto_winscope_extensions_zero +cc_library_static { + name: "perfetto_winscope_extensions_zero", + srcs: [ + ":perfetto_include_perfetto_base_base", + ":perfetto_include_perfetto_ext_base_base", + ":perfetto_include_perfetto_protozero_protozero", + ":perfetto_include_perfetto_public_abi_base", + ":perfetto_include_perfetto_public_base", + ":perfetto_include_perfetto_public_protozero", + ":perfetto_protos_perfetto_trace_android_winscope_common_zero_gen", + ":perfetto_protos_perfetto_trace_android_winscope_extensions_zero_gen", + ":perfetto_src_base_base", + ":perfetto_src_protozero_protozero", + ], + host_supported: true, + generated_headers: [ + "perfetto_protos_perfetto_trace_android_winscope_common_zero_gen_headers", + "perfetto_protos_perfetto_trace_android_winscope_extensions_zero_gen_headers", + ], + export_generated_headers: [ + "perfetto_protos_perfetto_trace_android_winscope_common_zero_gen_headers", + "perfetto_protos_perfetto_trace_android_winscope_extensions_zero_gen_headers", + ], + defaults: [ + "perfetto_defaults", + ], +} + // GN: [//protos/perfetto/trace:non_minimal_source_set, //protos/perfetto/trace/android:winscope_extensions_source_set] filegroup { name: "perfetto_winscope_filegroup_proto", @@ -15666,6 +16002,7 @@ filegroup { "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/android/windowmanager_config.proto", "protos/perfetto/config/chrome/chrome_config.proto", "protos/perfetto/config/chrome/scenario_config.proto", "protos/perfetto/config/chrome/v8_config.proto", @@ -15694,7 +16031,12 @@ filegroup { "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/app/statusbarmanager.proto", + "protos/perfetto/trace/android/app/window_configuration.proto", "protos/perfetto/trace/android/camera_event.proto", + "protos/perfetto/trace/android/content/activityinfo.proto", + "protos/perfetto/trace/android/content/configuration.proto", + "protos/perfetto/trace/android/content/locale.proto", "protos/perfetto/trace/android/frame_timeline_event.proto", "protos/perfetto/trace/android/gpu_mem_event.proto", "protos/perfetto/trace/android/graphics/pixelformat.proto", @@ -15708,8 +16050,13 @@ filegroup { "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/privacy.proto", "protos/perfetto/trace/android/protolog.proto", + "protos/perfetto/trace/android/server/animationadapter.proto", "protos/perfetto/trace/android/server/inputmethod/inputmethodmanagerservice.proto", + "protos/perfetto/trace/android/server/surfaceanimator.proto", + "protos/perfetto/trace/android/server/windowcontainerthumbnail.proto", + "protos/perfetto/trace/android/server/windowmanagerservice.proto", "protos/perfetto/trace/android/shell_transition.proto", "protos/perfetto/trace/android/surfaceflinger_common.proto", "protos/perfetto/trace/android/surfaceflinger_layers.proto", @@ -15717,6 +16064,8 @@ filegroup { "protos/perfetto/trace/android/typedef.proto", "protos/perfetto/trace/android/view/display.proto", "protos/perfetto/trace/android/view/displaycutout.proto", + "protos/perfetto/trace/android/view/displayinfo.proto", + "protos/perfetto/trace/android/view/enums.proto", "protos/perfetto/trace/android/view/imefocuscontroller.proto", "protos/perfetto/trace/android/view/imeinsetssourceconsumer.proto", "protos/perfetto/trace/android/view/inputmethod/editorinfo.proto", @@ -15728,9 +16077,13 @@ filegroup { "protos/perfetto/trace/android/view/insetssourceconsumer.proto", "protos/perfetto/trace/android/view/insetssourcecontrol.proto", "protos/perfetto/trace/android/view/insetsstate.proto", + "protos/perfetto/trace/android/view/remote_animation_target.proto", + "protos/perfetto/trace/android/view/surface.proto", "protos/perfetto/trace/android/view/surfacecontrol.proto", "protos/perfetto/trace/android/view/viewrootimpl.proto", "protos/perfetto/trace/android/view/windowlayoutparams.proto", + "protos/perfetto/trace/android/viewcapture.proto", + "protos/perfetto/trace/android/windowmanager.proto", "protos/perfetto/trace/android/winscope_extensions.proto", "protos/perfetto/trace/android/winscope_extensions_impl.proto", "protos/perfetto/trace/chrome/chrome_benchmark_metadata.proto", @@ -15745,6 +16098,7 @@ filegroup { "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/bcl_exynos.proto", "protos/perfetto/trace/ftrace/binder.proto", "protos/perfetto/trace/ftrace/block.proto", "protos/perfetto/trace/ftrace/cgroup.proto", @@ -15753,6 +16107,7 @@ filegroup { "protos/perfetto/trace/ftrace/compaction.proto", "protos/perfetto/trace/ftrace/cpuhp.proto", "protos/perfetto/trace/ftrace/cros_ec.proto", + "protos/perfetto/trace/ftrace/dcvsh.proto", "protos/perfetto/trace/ftrace/dma_fence.proto", "protos/perfetto/trace/ftrace/dmabuf_heap.proto", "protos/perfetto/trace/ftrace/dpu.proto", @@ -15777,6 +16132,7 @@ filegroup { "protos/perfetto/trace/ftrace/ion.proto", "protos/perfetto/trace/ftrace/ipi.proto", "protos/perfetto/trace/ftrace/irq.proto", + "protos/perfetto/trace/ftrace/kgsl.proto", "protos/perfetto/trace/ftrace/kmem.proto", "protos/perfetto/trace/ftrace/kvm.proto", "protos/perfetto/trace/ftrace/lowmemorykiller.proto", @@ -15964,6 +16320,7 @@ cc_binary { ":perfetto_protos_perfetto_trace_track_event_zero_gen", ":perfetto_protos_perfetto_trace_translation_zero_gen", ":perfetto_protos_third_party_pprof_zero_gen", + ":perfetto_protos_third_party_simpleperf_zero_gen", ":perfetto_src_base_base", ":perfetto_src_base_http_http", ":perfetto_src_base_unix_socket", @@ -15976,10 +16333,12 @@ cc_binary { ":perfetto_src_protozero_protozero", ":perfetto_src_trace_processor_containers_containers", ":perfetto_src_trace_processor_db_column_column", + ":perfetto_src_trace_processor_db_compare", ":perfetto_src_trace_processor_db_db", ":perfetto_src_trace_processor_db_minimal", ":perfetto_src_trace_processor_export_json", ":perfetto_src_trace_processor_importers_android_bugreport_android_bugreport", + ":perfetto_src_trace_processor_importers_android_bugreport_android_log_event", ":perfetto_src_trace_processor_importers_common_common", ":perfetto_src_trace_processor_importers_common_parser_types", ":perfetto_src_trace_processor_importers_common_trace_parser_hdr", @@ -15999,6 +16358,7 @@ cc_binary { ":perfetto_src_trace_processor_importers_ninja_ninja", ":perfetto_src_trace_processor_importers_perf_perf", ":perfetto_src_trace_processor_importers_perf_record", + ":perfetto_src_trace_processor_importers_perf_tracker", ":perfetto_src_trace_processor_importers_proto_full", ":perfetto_src_trace_processor_importers_proto_minimal", ":perfetto_src_trace_processor_importers_proto_packet_sequence_state_generation_hdr", @@ -16008,6 +16368,7 @@ cc_binary { ":perfetto_src_trace_processor_importers_systrace_full", ":perfetto_src_trace_processor_importers_systrace_systrace_line", ":perfetto_src_trace_processor_importers_systrace_systrace_parser", + ":perfetto_src_trace_processor_importers_zip_full", ":perfetto_src_trace_processor_lib", ":perfetto_src_trace_processor_metatrace", ":perfetto_src_trace_processor_metrics_metrics", @@ -16017,6 +16378,7 @@ cc_binary { ":perfetto_src_trace_processor_perfetto_sql_intrinsics_operators_operators", ":perfetto_src_trace_processor_perfetto_sql_intrinsics_table_functions_interface", ":perfetto_src_trace_processor_perfetto_sql_intrinsics_table_functions_table_functions", + ":perfetto_src_trace_processor_perfetto_sql_intrinsics_types_types", ":perfetto_src_trace_processor_rpc_httpd", ":perfetto_src_trace_processor_rpc_rpc", ":perfetto_src_trace_processor_rpc_stdiod", @@ -16042,6 +16404,8 @@ cc_binary { ":perfetto_src_trace_processor_util_regex", ":perfetto_src_trace_processor_util_sql_argument", ":perfetto_src_trace_processor_util_stdlib", + ":perfetto_src_trace_processor_util_trace_blob_view_reader", + ":perfetto_src_trace_processor_util_trace_type", ":perfetto_src_trace_processor_util_util", ":perfetto_src_trace_processor_util_zip_reader", "src/trace_processor/trace_processor_shell.cc", @@ -16089,7 +16453,9 @@ cc_binary { "perfetto_protos_perfetto_trace_track_event_zero_gen_headers", "perfetto_protos_perfetto_trace_translation_zero_gen_headers", "perfetto_protos_third_party_pprof_zero_gen_headers", + "perfetto_protos_third_party_simpleperf_zero_gen_headers", "perfetto_src_base_version_gen_h", + "perfetto_src_trace_processor_importers_proto_gen_cc_android_track_event_descriptor", "perfetto_src_trace_processor_importers_proto_gen_cc_chrome_track_event_descriptor", "perfetto_src_trace_processor_importers_proto_gen_cc_config_descriptor", "perfetto_src_trace_processor_importers_proto_gen_cc_statsd_atoms_descriptor", @@ -16203,11 +16569,14 @@ cc_binary { ":perfetto_protos_perfetto_trace_system_info_zero_gen", ":perfetto_protos_perfetto_trace_track_event_zero_gen", ":perfetto_protos_perfetto_trace_translation_zero_gen", + ":perfetto_protos_third_party_simpleperf_zero_gen", ":perfetto_src_base_base", ":perfetto_src_protozero_protozero", ":perfetto_src_trace_processor_containers_containers", ":perfetto_src_trace_processor_db_column_column", + ":perfetto_src_trace_processor_db_compare", ":perfetto_src_trace_processor_db_minimal", + ":perfetto_src_trace_processor_importers_android_bugreport_android_log_event", ":perfetto_src_trace_processor_importers_common_common", ":perfetto_src_trace_processor_importers_common_parser_types", ":perfetto_src_trace_processor_importers_common_trace_parser_hdr", @@ -16216,6 +16585,8 @@ cc_binary { ":perfetto_src_trace_processor_importers_fuchsia_fuchsia_record", ":perfetto_src_trace_processor_importers_json_minimal", ":perfetto_src_trace_processor_importers_memory_tracker_graph_processor", + ":perfetto_src_trace_processor_importers_perf_record", + ":perfetto_src_trace_processor_importers_perf_tracker", ":perfetto_src_trace_processor_importers_proto_minimal", ":perfetto_src_trace_processor_importers_proto_packet_sequence_state_generation_hdr", ":perfetto_src_trace_processor_importers_proto_proto_importer_module", @@ -16236,6 +16607,8 @@ cc_binary { ":perfetto_src_trace_processor_util_proto_to_args_parser", ":perfetto_src_trace_processor_util_protozero_to_text", ":perfetto_src_trace_processor_util_regex", + ":perfetto_src_trace_processor_util_trace_blob_view_reader", + ":perfetto_src_trace_processor_util_trace_type", ":perfetto_src_trace_processor_util_util", ":perfetto_src_trace_redaction_trace_redaction", "src/trace_redaction/main.cc", @@ -16280,6 +16653,8 @@ cc_binary { "perfetto_protos_perfetto_trace_system_info_zero_gen_headers", "perfetto_protos_perfetto_trace_track_event_zero_gen_headers", "perfetto_protos_perfetto_trace_translation_zero_gen_headers", + "perfetto_protos_third_party_simpleperf_zero_gen_headers", + "perfetto_src_trace_processor_importers_proto_gen_cc_android_track_event_descriptor", "perfetto_src_trace_processor_importers_proto_gen_cc_chrome_track_event_descriptor", "perfetto_src_trace_processor_importers_proto_gen_cc_track_event_descriptor", "perfetto_src_trace_processor_tables_tables_python", @@ -16351,6 +16726,7 @@ cc_binary_host { ":perfetto_protos_perfetto_trace_track_event_zero_gen", ":perfetto_protos_perfetto_trace_translation_zero_gen", ":perfetto_protos_third_party_pprof_zero_gen", + ":perfetto_protos_third_party_simpleperf_zero_gen", ":perfetto_src_base_base", ":perfetto_src_base_version", ":perfetto_src_kernel_utils_syscall_table", @@ -16361,10 +16737,12 @@ cc_binary_host { ":perfetto_src_protozero_protozero", ":perfetto_src_trace_processor_containers_containers", ":perfetto_src_trace_processor_db_column_column", + ":perfetto_src_trace_processor_db_compare", ":perfetto_src_trace_processor_db_db", ":perfetto_src_trace_processor_db_minimal", ":perfetto_src_trace_processor_export_json", ":perfetto_src_trace_processor_importers_android_bugreport_android_bugreport", + ":perfetto_src_trace_processor_importers_android_bugreport_android_log_event", ":perfetto_src_trace_processor_importers_common_common", ":perfetto_src_trace_processor_importers_common_parser_types", ":perfetto_src_trace_processor_importers_common_trace_parser_hdr", @@ -16384,6 +16762,7 @@ cc_binary_host { ":perfetto_src_trace_processor_importers_ninja_ninja", ":perfetto_src_trace_processor_importers_perf_perf", ":perfetto_src_trace_processor_importers_perf_record", + ":perfetto_src_trace_processor_importers_perf_tracker", ":perfetto_src_trace_processor_importers_proto_full", ":perfetto_src_trace_processor_importers_proto_minimal", ":perfetto_src_trace_processor_importers_proto_packet_sequence_state_generation_hdr", @@ -16393,6 +16772,7 @@ cc_binary_host { ":perfetto_src_trace_processor_importers_systrace_full", ":perfetto_src_trace_processor_importers_systrace_systrace_line", ":perfetto_src_trace_processor_importers_systrace_systrace_parser", + ":perfetto_src_trace_processor_importers_zip_full", ":perfetto_src_trace_processor_lib", ":perfetto_src_trace_processor_metatrace", ":perfetto_src_trace_processor_metrics_metrics", @@ -16402,6 +16782,7 @@ cc_binary_host { ":perfetto_src_trace_processor_perfetto_sql_intrinsics_operators_operators", ":perfetto_src_trace_processor_perfetto_sql_intrinsics_table_functions_interface", ":perfetto_src_trace_processor_perfetto_sql_intrinsics_table_functions_table_functions", + ":perfetto_src_trace_processor_perfetto_sql_intrinsics_types_types", ":perfetto_src_trace_processor_sorter_sorter", ":perfetto_src_trace_processor_sqlite_bindings_bindings", ":perfetto_src_trace_processor_sqlite_sqlite", @@ -16424,6 +16805,8 @@ cc_binary_host { ":perfetto_src_trace_processor_util_regex", ":perfetto_src_trace_processor_util_sql_argument", ":perfetto_src_trace_processor_util_stdlib", + ":perfetto_src_trace_processor_util_trace_blob_view_reader", + ":perfetto_src_trace_processor_util_trace_type", ":perfetto_src_trace_processor_util_util", ":perfetto_src_trace_processor_util_zip_reader", ":perfetto_src_traceconv_lib", @@ -16476,7 +16859,9 @@ cc_binary_host { "perfetto_protos_perfetto_trace_track_event_zero_gen_headers", "perfetto_protos_perfetto_trace_translation_zero_gen_headers", "perfetto_protos_third_party_pprof_zero_gen_headers", + "perfetto_protos_third_party_simpleperf_zero_gen_headers", "perfetto_src_base_version_gen_h", + "perfetto_src_trace_processor_importers_proto_gen_cc_android_track_event_descriptor", "perfetto_src_trace_processor_importers_proto_gen_cc_chrome_track_event_descriptor", "perfetto_src_trace_processor_importers_proto_gen_cc_config_descriptor", "perfetto_src_trace_processor_importers_proto_gen_cc_statsd_atoms_descriptor", @@ -16493,6 +16878,7 @@ cc_binary_host { "perfetto_src_trace_processor_perfetto_sql_stdlib_stdlib", "perfetto_src_trace_processor_tables_tables_python", "perfetto_src_traceconv_gen_cc_trace_descriptor", + "perfetto_src_traceconv_gen_cc_winscope_descriptor", ], defaults: [ "perfetto_defaults", @@ -17231,7 +17617,7 @@ gensrcs { cmd: "mkdir -p $(genDir)/$(in) " + "&& $(location aprotoc) " + "--plugin=$(location protoc-gen-javastream) " + - "--javastream_opt=include_filter:perfetto.protos.TracePacket,perfetto.protos.ShellTransition,perfetto.protos.ShellHandlerMappings,perfetto.protos.ProtoLogMessage,perfetto.protos.ProtoLogViewerConfig,perfetto.protos.ShellHandlerMapping,perfetto.protos.ShellHandlerMappings,perfetto.protos.ProtoLogGroup,perfetto.protos.ProtoLogConfig,perfetto.protos.DataSourceConfig,perfetto.protos.InternedString,perfetto.protos.InternedData,perfetto.protos.ProtoLogLevel,perfetto.protos.TestEvent,perfetto.protos.TestEvent.TestPayload,perfetto.protos.TestConfig,perfetto.protos.TestConfig.DummyFields,perfetto.protos.WinscopeExtensionsImpl,perfetto.protos.InputMethodClientsTraceProto,perfetto.protos.InputMethodManagerServiceTraceProto,perfetto.protos.InputMethodServiceTraceProto " + + "--javastream_opt=include_filter:perfetto.protos.TracePacket,perfetto.protos.ShellTransition,perfetto.protos.ShellHandlerMappings,perfetto.protos.ProtoLogMessage,perfetto.protos.ProtoLogViewerConfig,perfetto.protos.ShellHandlerMapping,perfetto.protos.ShellHandlerMappings,perfetto.protos.ProtoLogGroup,perfetto.protos.ProtoLogConfig,perfetto.protos.DataSourceConfig,perfetto.protos.InternedString,perfetto.protos.InternedData,perfetto.protos.ProtoLogLevel,perfetto.protos.TestEvent,perfetto.protos.TestEvent.TestPayload,perfetto.protos.TestConfig,perfetto.protos.TestConfig.DummyFields,perfetto.protos.WinscopeExtensionsImpl,perfetto.protos.InputMethodClientsTraceProto,perfetto.protos.InputMethodManagerServiceTraceProto,perfetto.protos.InputMethodServiceTraceProto,perfetto.protos.ViewCapture,perfetto.protos.WindowManagerConfig,perfetto.protos.WindowManagerTraceEntry " + "--javastream_out=$(genDir)/$(in) " + "-Iexternal/protobuf/src " + "-Iexternal/perfetto " + @@ -17244,6 +17630,39 @@ gensrcs { output_extension: "srcjar", } +java_library { + name: "perfetto_config_java_protos", + srcs: [ + ":perfetto_config_filegroup_proto", + ], + static_libs: [ + "libprotobuf-java-lite", + ], + proto: { + type: "lite", + canonical_path_from_root: false, + }, +} + +java_library { + name: "perfetto_config_java_protos_system_server_current", + srcs: [ + ":perfetto_config_filegroup_proto", + ], + static_libs: [ + "libprotobuf-java-lite", + ], + proto: { + type: "lite", + canonical_path_from_root: false, + }, + sdk_version: "system_server_current", + min_sdk_version: "35", + apex_available: [ + "com.android.profiling", + ], +} + prebuilt_etc { name: "perfetto_persistent_cfg.pbtxt", filename: "persistent_cfg.pbtxt", diff --git a/Android.bp.extras b/Android.bp.extras index 75d3a81b79..51a9345313 100644 --- a/Android.bp.extras +++ b/Android.bp.extras @@ -185,7 +185,7 @@ gensrcs { cmd: "mkdir -p $(genDir)/$(in) " + "&& $(location aprotoc) " + "--plugin=$(location protoc-gen-javastream) " + - "--javastream_opt=include_filter:perfetto.protos.TracePacket,perfetto.protos.ShellTransition,perfetto.protos.ShellHandlerMappings,perfetto.protos.ProtoLogMessage,perfetto.protos.ProtoLogViewerConfig,perfetto.protos.ShellHandlerMapping,perfetto.protos.ShellHandlerMappings,perfetto.protos.ProtoLogGroup,perfetto.protos.ProtoLogConfig,perfetto.protos.DataSourceConfig,perfetto.protos.InternedString,perfetto.protos.InternedData,perfetto.protos.ProtoLogLevel,perfetto.protos.TestEvent,perfetto.protos.TestEvent.TestPayload,perfetto.protos.TestConfig,perfetto.protos.TestConfig.DummyFields,perfetto.protos.WinscopeExtensionsImpl,perfetto.protos.InputMethodClientsTraceProto,perfetto.protos.InputMethodManagerServiceTraceProto,perfetto.protos.InputMethodServiceTraceProto " + + "--javastream_opt=include_filter:perfetto.protos.TracePacket,perfetto.protos.ShellTransition,perfetto.protos.ShellHandlerMappings,perfetto.protos.ProtoLogMessage,perfetto.protos.ProtoLogViewerConfig,perfetto.protos.ShellHandlerMapping,perfetto.protos.ShellHandlerMappings,perfetto.protos.ProtoLogGroup,perfetto.protos.ProtoLogConfig,perfetto.protos.DataSourceConfig,perfetto.protos.InternedString,perfetto.protos.InternedData,perfetto.protos.ProtoLogLevel,perfetto.protos.TestEvent,perfetto.protos.TestEvent.TestPayload,perfetto.protos.TestConfig,perfetto.protos.TestConfig.DummyFields,perfetto.protos.WinscopeExtensionsImpl,perfetto.protos.InputMethodClientsTraceProto,perfetto.protos.InputMethodManagerServiceTraceProto,perfetto.protos.InputMethodServiceTraceProto,perfetto.protos.ViewCapture,perfetto.protos.WindowManagerConfig,perfetto.protos.WindowManagerTraceEntry " + "--javastream_out=$(genDir)/$(in) " + "-Iexternal/protobuf/src " + "-Iexternal/perfetto " + @@ -198,6 +198,39 @@ gensrcs { output_extension: "srcjar", } +java_library { + name: "perfetto_config_java_protos", + srcs: [ + ":perfetto_config_filegroup_proto", + ], + static_libs: [ + "libprotobuf-java-lite", + ], + proto: { + type: "lite", + canonical_path_from_root: false, + }, +} + +java_library { + name: "perfetto_config_java_protos_system_server_current", + srcs: [ + ":perfetto_config_filegroup_proto", + ], + static_libs: [ + "libprotobuf-java-lite", + ], + proto: { + type: "lite", + canonical_path_from_root: false, + }, + sdk_version: "system_server_current", + min_sdk_version: "35", + apex_available: [ + "com.android.profiling", + ], +} + prebuilt_etc { name: "perfetto_persistent_cfg.pbtxt", filename: "persistent_cfg.pbtxt", diff --git a/BUILD b/BUILD index 3437d7c165..c2db6572c2 100644 --- a/BUILD +++ b/BUILD @@ -217,10 +217,12 @@ perfetto_cc_library( ":src_kernel_utils_syscall_table", ":src_protozero_proto_ring_buffer", ":src_trace_processor_db_column_column", + ":src_trace_processor_db_compare", ":src_trace_processor_db_db", ":src_trace_processor_db_minimal", ":src_trace_processor_export_json", ":src_trace_processor_importers_android_bugreport_android_bugreport", + ":src_trace_processor_importers_android_bugreport_android_log_event", ":src_trace_processor_importers_common_common", ":src_trace_processor_importers_common_parser_types", ":src_trace_processor_importers_common_trace_parser_hdr", @@ -240,6 +242,7 @@ perfetto_cc_library( ":src_trace_processor_importers_ninja_ninja", ":src_trace_processor_importers_perf_perf", ":src_trace_processor_importers_perf_record", + ":src_trace_processor_importers_perf_tracker", ":src_trace_processor_importers_proto_full", ":src_trace_processor_importers_proto_minimal", ":src_trace_processor_importers_proto_packet_sequence_state_generation_hdr", @@ -249,6 +252,7 @@ perfetto_cc_library( ":src_trace_processor_importers_systrace_full", ":src_trace_processor_importers_systrace_systrace_line", ":src_trace_processor_importers_systrace_systrace_parser", + ":src_trace_processor_importers_zip_full", ":src_trace_processor_lib", ":src_trace_processor_metatrace", ":src_trace_processor_metrics_metrics", @@ -260,6 +264,7 @@ perfetto_cc_library( ":src_trace_processor_perfetto_sql_intrinsics_table_functions_interface", ":src_trace_processor_perfetto_sql_intrinsics_table_functions_table_functions", ":src_trace_processor_perfetto_sql_intrinsics_table_functions_tables", + ":src_trace_processor_perfetto_sql_intrinsics_types_types", ":src_trace_processor_rpc_rpc", ":src_trace_processor_sorter_sorter", ":src_trace_processor_sqlite_bindings_bindings", @@ -284,6 +289,8 @@ perfetto_cc_library( ":src_trace_processor_util_regex", ":src_trace_processor_util_sql_argument", ":src_trace_processor_util_stdlib", + ":src_trace_processor_util_trace_blob_view_reader", + ":src_trace_processor_util_trace_type", ":src_trace_processor_util_util", ":src_trace_processor_util_zip_reader", ], @@ -343,10 +350,12 @@ perfetto_cc_library( ":protos_perfetto_trace_track_event_zero", ":protos_perfetto_trace_translation_zero", ":protos_third_party_pprof_zero", + ":protos_third_party_simpleperf_zero", ":protozero", ":src_base_base", ":src_base_version", ":src_trace_processor_containers_containers", + ":src_trace_processor_importers_proto_gen_cc_android_track_event_descriptor", ":src_trace_processor_importers_proto_gen_cc_chrome_track_event_descriptor", ":src_trace_processor_importers_proto_gen_cc_config_descriptor", ":src_trace_processor_importers_proto_gen_cc_statsd_atoms_descriptor", @@ -884,6 +893,7 @@ perfetto_filegroup( perfetto_filegroup( name = "include_perfetto_trace_processor_storage", srcs = [ + "include/perfetto/trace_processor/ref_counted.h", "include/perfetto/trace_processor/trace_blob.h", "include/perfetto/trace_processor/trace_blob_view.h", "include/perfetto/trace_processor/trace_processor_storage.h", @@ -897,7 +907,6 @@ perfetto_filegroup( "include/perfetto/trace_processor/iterator.h", "include/perfetto/trace_processor/metatrace_config.h", "include/perfetto/trace_processor/read_trace.h", - "include/perfetto/trace_processor/ref_counted.h", "include/perfetto/trace_processor/trace_processor.h", ], ) @@ -1421,6 +1430,14 @@ perfetto_filegroup( ], ) +# GN target: //src/trace_processor/db:compare +perfetto_filegroup( + name = "src_trace_processor_db_compare", + srcs = [ + "src/trace_processor/db/compare.h", + ], +) + # GN target: //src/trace_processor/db:db perfetto_filegroup( name = "src_trace_processor_db_db", @@ -1454,10 +1471,25 @@ perfetto_filegroup( perfetto_filegroup( name = "src_trace_processor_importers_android_bugreport_android_bugreport", srcs = [ - "src/trace_processor/importers/android_bugreport/android_bugreport_parser.cc", - "src/trace_processor/importers/android_bugreport/android_bugreport_parser.h", - "src/trace_processor/importers/android_bugreport/android_log_parser.cc", - "src/trace_processor/importers/android_bugreport/android_log_parser.h", + "src/trace_processor/importers/android_bugreport/android_bugreport_reader.cc", + "src/trace_processor/importers/android_bugreport/android_bugreport_reader.h", + "src/trace_processor/importers/android_bugreport/android_dumpstate_reader.cc", + "src/trace_processor/importers/android_bugreport/android_dumpstate_reader.h", + "src/trace_processor/importers/android_bugreport/android_log_event_parser_impl.cc", + "src/trace_processor/importers/android_bugreport/android_log_event_parser_impl.h", + "src/trace_processor/importers/android_bugreport/android_log_reader.cc", + "src/trace_processor/importers/android_bugreport/android_log_reader.h", + "src/trace_processor/importers/android_bugreport/chunked_line_reader.cc", + "src/trace_processor/importers/android_bugreport/chunked_line_reader.h", + ], +) + +# GN target: //src/trace_processor/importers/android_bugreport:android_log_event +perfetto_filegroup( + name = "src_trace_processor_importers_android_bugreport_android_log_event", + srcs = [ + "src/trace_processor/importers/android_bugreport/android_log_event.cc", + "src/trace_processor/importers/android_bugreport/android_log_event.h", ], ) @@ -1477,6 +1509,8 @@ perfetto_filegroup( "src/trace_processor/importers/common/clock_converter.h", "src/trace_processor/importers/common/clock_tracker.cc", "src/trace_processor/importers/common/clock_tracker.h", + "src/trace_processor/importers/common/cpu_tracker.cc", + "src/trace_processor/importers/common/cpu_tracker.h", "src/trace_processor/importers/common/create_mapping_params.h", "src/trace_processor/importers/common/deobfuscation_mapping_table.cc", "src/trace_processor/importers/common/deobfuscation_mapping_table.h", @@ -1494,11 +1528,15 @@ perfetto_filegroup( "src/trace_processor/importers/common/mapping_tracker.h", "src/trace_processor/importers/common/metadata_tracker.cc", "src/trace_processor/importers/common/metadata_tracker.h", + "src/trace_processor/importers/common/process_track_translation_table.cc", + "src/trace_processor/importers/common/process_track_translation_table.h", "src/trace_processor/importers/common/process_tracker.cc", "src/trace_processor/importers/common/process_tracker.h", "src/trace_processor/importers/common/sched_event_state.h", "src/trace_processor/importers/common/sched_event_tracker.cc", "src/trace_processor/importers/common/sched_event_tracker.h", + "src/trace_processor/importers/common/scoped_active_trace_file.cc", + "src/trace_processor/importers/common/scoped_active_trace_file.h", "src/trace_processor/importers/common/slice_tracker.cc", "src/trace_processor/importers/common/slice_tracker.h", "src/trace_processor/importers/common/slice_translation_table.cc", @@ -1509,6 +1547,8 @@ perfetto_filegroup( "src/trace_processor/importers/common/system_info_tracker.h", "src/trace_processor/importers/common/thread_state_tracker.cc", "src/trace_processor/importers/common/thread_state_tracker.h", + "src/trace_processor/importers/common/trace_file_tracker.cc", + "src/trace_processor/importers/common/trace_file_tracker.h", "src/trace_processor/importers/common/trace_parser.cc", "src/trace_processor/importers/common/track_tracker.cc", "src/trace_processor/importers/common/track_tracker.h", @@ -1702,16 +1742,19 @@ perfetto_filegroup( perfetto_filegroup( name = "src_trace_processor_importers_perf_perf", srcs = [ - "src/trace_processor/importers/perf/perf_data_parser.cc", - "src/trace_processor/importers/perf/perf_data_parser.h", - "src/trace_processor/importers/perf/perf_data_reader.cc", - "src/trace_processor/importers/perf/perf_data_reader.h", + "src/trace_processor/importers/perf/attrs_section_reader.cc", + "src/trace_processor/importers/perf/attrs_section_reader.h", + "src/trace_processor/importers/perf/features.cc", + "src/trace_processor/importers/perf/features.h", + "src/trace_processor/importers/perf/mmap_record.cc", + "src/trace_processor/importers/perf/mmap_record.h", "src/trace_processor/importers/perf/perf_data_tokenizer.cc", "src/trace_processor/importers/perf/perf_data_tokenizer.h", - "src/trace_processor/importers/perf/perf_data_tracker.cc", - "src/trace_processor/importers/perf/perf_data_tracker.h", "src/trace_processor/importers/perf/perf_file.h", - "src/trace_processor/importers/perf/reader.h", + "src/trace_processor/importers/perf/record_parser.cc", + "src/trace_processor/importers/perf/record_parser.h", + "src/trace_processor/importers/perf/sample.cc", + "src/trace_processor/importers/perf/sample.h", ], ) @@ -1719,6 +1762,8 @@ perfetto_filegroup( perfetto_filegroup( name = "src_trace_processor_importers_perf_record", srcs = [ + "src/trace_processor/importers/perf/perf_counter.cc", + "src/trace_processor/importers/perf/perf_counter.h", "src/trace_processor/importers/perf/perf_event.h", "src/trace_processor/importers/perf/perf_event_attr.cc", "src/trace_processor/importers/perf/perf_event_attr.h", @@ -1729,10 +1774,23 @@ perfetto_filegroup( ], ) +# GN target: //src/trace_processor/importers/perf:tracker +perfetto_filegroup( + name = "src_trace_processor_importers_perf_tracker", + srcs = [ + "src/trace_processor/importers/perf/dso_tracker.cc", + "src/trace_processor/importers/perf/dso_tracker.h", + ], +) + # GN target: //src/trace_processor/importers/proto/winscope:full perfetto_filegroup( name = "src_trace_processor_importers_proto_winscope_full", srcs = [ + "src/trace_processor/importers/proto/winscope/android_input_event_parser.cc", + "src/trace_processor/importers/proto/winscope/android_input_event_parser.h", + "src/trace_processor/importers/proto/winscope/protolog_message_decoder.cc", + "src/trace_processor/importers/proto/winscope/protolog_message_decoder.h", "src/trace_processor/importers/proto/winscope/protolog_messages_tracker.cc", "src/trace_processor/importers/proto/winscope/protolog_messages_tracker.h", "src/trace_processor/importers/proto/winscope/protolog_parser.cc", @@ -1745,8 +1803,8 @@ perfetto_filegroup( "src/trace_processor/importers/proto/winscope/surfaceflinger_layers_parser.h", "src/trace_processor/importers/proto/winscope/surfaceflinger_transactions_parser.cc", "src/trace_processor/importers/proto/winscope/surfaceflinger_transactions_parser.h", - "src/trace_processor/importers/proto/winscope/winscope_args_parser.cc", - "src/trace_processor/importers/proto/winscope/winscope_args_parser.h", + "src/trace_processor/importers/proto/winscope/viewcapture_args_parser.cc", + "src/trace_processor/importers/proto/winscope/viewcapture_args_parser.h", "src/trace_processor/importers/proto/winscope/winscope_module.cc", "src/trace_processor/importers/proto/winscope/winscope_module.h", ], @@ -1793,6 +1851,12 @@ perfetto_filegroup( "src/trace_processor/importers/proto/heap_graph_tracker.h", "src/trace_processor/importers/proto/metadata_module.cc", "src/trace_processor/importers/proto/metadata_module.h", + "src/trace_processor/importers/proto/pigweed_detokenizer.cc", + "src/trace_processor/importers/proto/pigweed_detokenizer.h", + "src/trace_processor/importers/proto/pixel_modem_module.cc", + "src/trace_processor/importers/proto/pixel_modem_module.h", + "src/trace_processor/importers/proto/pixel_modem_parser.cc", + "src/trace_processor/importers/proto/pixel_modem_parser.h", "src/trace_processor/importers/proto/statsd_module.cc", "src/trace_processor/importers/proto/statsd_module.h", "src/trace_processor/importers/proto/string_encoding_utils.cc", @@ -1814,6 +1878,17 @@ perfetto_filegroup( ], ) +# GN target: //src/trace_processor/importers/proto:gen_cc_android_track_event_descriptor +perfetto_cc_proto_descriptor( + name = "src_trace_processor_importers_proto_gen_cc_android_track_event_descriptor", + deps = [ + ":protos_perfetto_trace_android_android_track_event_descriptor", + ], + outs = [ + "src/trace_processor/importers/proto/android_track_event.descriptor.h", + ], +) + # GN target: //src/trace_processor/importers/proto:gen_cc_chrome_track_event_descriptor perfetto_cc_proto_descriptor( name = "src_trace_processor_importers_proto_gen_cc_chrome_track_event_descriptor", @@ -1875,6 +1950,8 @@ perfetto_filegroup( srcs = [ "src/trace_processor/importers/proto/active_chrome_processes_tracker.cc", "src/trace_processor/importers/proto/active_chrome_processes_tracker.h", + "src/trace_processor/importers/proto/args_parser.cc", + "src/trace_processor/importers/proto/args_parser.h", "src/trace_processor/importers/proto/chrome_string_lookup.cc", "src/trace_processor/importers/proto/chrome_string_lookup.h", "src/trace_processor/importers/proto/chrome_system_probes_module.cc", @@ -1984,6 +2061,15 @@ perfetto_filegroup( ], ) +# GN target: //src/trace_processor/importers/zip:full +perfetto_filegroup( + name = "src_trace_processor_importers_zip_full", + srcs = [ + "src/trace_processor/importers/zip/zip_trace_reader.cc", + "src/trace_processor/importers/zip/zip_trace_reader.h", + ], +) + # GN target: //src/trace_processor/metrics/sql/android:android perfetto_filegroup( name = "src_trace_processor_metrics_sql_android_android", @@ -1997,6 +2083,7 @@ perfetto_filegroup( "src/trace_processor/metrics/sql/android/android_blocking_calls_unagg.sql", "src/trace_processor/metrics/sql/android/android_boot.sql", "src/trace_processor/metrics/sql/android/android_boot_unagg.sql", + "src/trace_processor/metrics/sql/android/android_broadcasts.sql", "src/trace_processor/metrics/sql/android/android_camera.sql", "src/trace_processor/metrics/sql/android/android_camera_unagg.sql", "src/trace_processor/metrics/sql/android/android_cpu.sql", @@ -2109,6 +2196,7 @@ perfetto_filegroup( "src/trace_processor/metrics/sql/android/sysui_notif_shade_list_builder_slices.sql", "src/trace_processor/metrics/sql/android/sysui_update_notif_on_ui_mode_changed_metric.sql", "src/trace_processor/metrics/sql/android/unsymbolized_frames.sql", + "src/trace_processor/metrics/sql/android/wattson_app_startup.sql", ], ) @@ -2295,12 +2383,16 @@ perfetto_filegroup( "src/trace_processor/perfetto_sql/intrinsics/functions/create_function.h", "src/trace_processor/perfetto_sql/intrinsics/functions/create_view_function.cc", "src/trace_processor/perfetto_sql/intrinsics/functions/create_view_function.h", - "src/trace_processor/perfetto_sql/intrinsics/functions/dfs.cc", - "src/trace_processor/perfetto_sql/intrinsics/functions/dfs.h", "src/trace_processor/perfetto_sql/intrinsics/functions/dominator_tree.cc", "src/trace_processor/perfetto_sql/intrinsics/functions/dominator_tree.h", + "src/trace_processor/perfetto_sql/intrinsics/functions/graph_scan.cc", + "src/trace_processor/perfetto_sql/intrinsics/functions/graph_scan.h", + "src/trace_processor/perfetto_sql/intrinsics/functions/graph_traversal.cc", + "src/trace_processor/perfetto_sql/intrinsics/functions/graph_traversal.h", "src/trace_processor/perfetto_sql/intrinsics/functions/import.cc", "src/trace_processor/perfetto_sql/intrinsics/functions/import.h", + "src/trace_processor/perfetto_sql/intrinsics/functions/interval_intersect.cc", + "src/trace_processor/perfetto_sql/intrinsics/functions/interval_intersect.h", "src/trace_processor/perfetto_sql/intrinsics/functions/layout_functions.cc", "src/trace_processor/perfetto_sql/intrinsics/functions/layout_functions.h", "src/trace_processor/perfetto_sql/intrinsics/functions/math.cc", @@ -2315,6 +2407,8 @@ perfetto_filegroup( "src/trace_processor/perfetto_sql/intrinsics/functions/structural_tree_partition.h", "src/trace_processor/perfetto_sql/intrinsics/functions/to_ftrace.cc", "src/trace_processor/perfetto_sql/intrinsics/functions/to_ftrace.h", + "src/trace_processor/perfetto_sql/intrinsics/functions/type_builders.cc", + "src/trace_processor/perfetto_sql/intrinsics/functions/type_builders.h", "src/trace_processor/perfetto_sql/intrinsics/functions/utils.h", "src/trace_processor/perfetto_sql/intrinsics/functions/window_functions.h", ], @@ -2346,6 +2440,8 @@ perfetto_filegroup( 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/interval_intersect_operator.cc", + "src/trace_processor/perfetto_sql/intrinsics/operators/interval_intersect_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", @@ -2390,8 +2486,6 @@ perfetto_filegroup( "src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_slice_layout.h", "src/trace_processor/perfetto_sql/intrinsics/table_functions/flamegraph_construction_algorithms.cc", "src/trace_processor/perfetto_sql/intrinsics/table_functions/flamegraph_construction_algorithms.h", - "src/trace_processor/perfetto_sql/intrinsics/table_functions/interval_intersect.cc", - "src/trace_processor/perfetto_sql/intrinsics/table_functions/interval_intersect.h", "src/trace_processor/perfetto_sql/intrinsics/table_functions/table_info.cc", "src/trace_processor/perfetto_sql/intrinsics/table_functions/table_info.h", ], @@ -2411,6 +2505,19 @@ perfetto_cc_tp_tables( ], ) +# GN target: //src/trace_processor/perfetto_sql/intrinsics/types:types +perfetto_filegroup( + name = "src_trace_processor_perfetto_sql_intrinsics_types_types", + srcs = [ + "src/trace_processor/perfetto_sql/intrinsics/types/array.h", + "src/trace_processor/perfetto_sql/intrinsics/types/node.h", + "src/trace_processor/perfetto_sql/intrinsics/types/partitioned_intervals.h", + "src/trace_processor/perfetto_sql/intrinsics/types/row_dataframe.h", + "src/trace_processor/perfetto_sql/intrinsics/types/struct.h", + "src/trace_processor/perfetto_sql/intrinsics/types/value.h", + ], +) + # GN target: //src/trace_processor/perfetto_sql/prelude:prelude perfetto_cc_amalgamated_sql( name = "src_trace_processor_perfetto_sql_prelude_prelude", @@ -2440,16 +2547,65 @@ perfetto_filegroup( ], ) +# GN target: //src/trace_processor/perfetto_sql/stdlib/android/cpu:cpu +perfetto_filegroup( + name = "src_trace_processor_perfetto_sql_stdlib_android_cpu_cpu", + srcs = [ + "src/trace_processor/perfetto_sql/stdlib/android/cpu/cluster_type.sql", + ], +) + # GN target: //src/trace_processor/perfetto_sql/stdlib/android/frames:frames perfetto_filegroup( name = "src_trace_processor_perfetto_sql_stdlib_android_frames_frames", srcs = [ + "src/trace_processor/perfetto_sql/stdlib/android/frames/jank_type.sql", "src/trace_processor/perfetto_sql/stdlib/android/frames/per_frame_metrics.sql", "src/trace_processor/perfetto_sql/stdlib/android/frames/timeline.sql", "src/trace_processor/perfetto_sql/stdlib/android/frames/timeline_maxsdk28.sql", ], ) +# GN target: //src/trace_processor/perfetto_sql/stdlib/android/gpu:gpu +perfetto_filegroup( + name = "src_trace_processor_perfetto_sql_stdlib_android_gpu_gpu", + srcs = [ + "src/trace_processor/perfetto_sql/stdlib/android/gpu/frequency.sql", + "src/trace_processor/perfetto_sql/stdlib/android/gpu/memory.sql", + ], +) + +# GN target: //src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph:heap_graph +perfetto_filegroup( + name = "src_trace_processor_perfetto_sql_stdlib_android_memory_heap_graph_heap_graph", + srcs = [ + "src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/class_tree.sql", + "src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/dominator_class_tree.sql", + "src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/dominator_tree.sql", + "src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/excluded_refs.sql", + "src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/heap_graph_class_aggregation.sql", + "src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/helpers.sql", + "src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/raw_dominator_tree.sql", + ], +) + +# GN target: //src/trace_processor/perfetto_sql/stdlib/android/memory/heap_profile:heap_profile +perfetto_filegroup( + name = "src_trace_processor_perfetto_sql_stdlib_android_memory_heap_profile_heap_profile", + srcs = [ + "src/trace_processor/perfetto_sql/stdlib/android/memory/heap_profile/callstacks.sql", + ], +) + +# GN target: //src/trace_processor/perfetto_sql/stdlib/android/memory:memory +perfetto_filegroup( + name = "src_trace_processor_perfetto_sql_stdlib_android_memory_memory", + srcs = [ + "src/trace_processor/perfetto_sql/stdlib/android/memory/dmabuf.sql", + "src/trace_processor/perfetto_sql/stdlib/android/memory/process.sql", + ], +) + # GN target: //src/trace_processor/perfetto_sql/stdlib/android/startup:startup perfetto_filegroup( name = "src_trace_processor_perfetto_sql_stdlib_android_startup_startup", @@ -2468,6 +2624,8 @@ perfetto_filegroup( name = "src_trace_processor_perfetto_sql_stdlib_android_winscope_winscope", srcs = [ "src/trace_processor/perfetto_sql/stdlib/android/winscope/inputmethod.sql", + "src/trace_processor/perfetto_sql/stdlib/android/winscope/viewcapture.sql", + "src/trace_processor/perfetto_sql/stdlib/android/winscope/windowmanager.sql", ], ) @@ -2500,6 +2658,15 @@ perfetto_filegroup( "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", + "src/trace_processor/perfetto_sql/stdlib/android/version.sql", + ], +) + +# GN target: //src/trace_processor/perfetto_sql/stdlib/callstacks:callstacks +perfetto_filegroup( + name = "src_trace_processor_perfetto_sql_stdlib_callstacks_callstacks", + srcs = [ + "src/trace_processor/perfetto_sql/stdlib/callstacks/stack_profile.sql", ], ) @@ -2515,11 +2682,9 @@ perfetto_filegroup( srcs = [ "src/trace_processor/perfetto_sql/stdlib/common/args.sql", "src/trace_processor/perfetto_sql/stdlib/common/counters.sql", - "src/trace_processor/perfetto_sql/stdlib/common/cpus.sql", "src/trace_processor/perfetto_sql/stdlib/common/metadata.sql", "src/trace_processor/perfetto_sql/stdlib/common/percentiles.sql", "src/trace_processor/perfetto_sql/stdlib/common/slices.sql", - "src/trace_processor/perfetto_sql/stdlib/common/thread_states.sql", "src/trace_processor/perfetto_sql/stdlib/common/timestamps.sql", ], ) @@ -2532,38 +2697,36 @@ perfetto_filegroup( ], ) -# GN target: //src/trace_processor/perfetto_sql/stdlib/cpu:cpu -perfetto_filegroup( - name = "src_trace_processor_perfetto_sql_stdlib_cpu_cpu", - srcs = [ - "src/trace_processor/perfetto_sql/stdlib/cpu/cpus.sql", - "src/trace_processor/perfetto_sql/stdlib/cpu/freq.sql", - "src/trace_processor/perfetto_sql/stdlib/cpu/idle.sql", - "src/trace_processor/perfetto_sql/stdlib/cpu/size.sql", - ], -) - # GN target: //src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common:common perfetto_filegroup( name = "src_trace_processor_perfetto_sql_stdlib_deprecated_v42_common_common", srcs = [ "src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/args.sql", "src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/counters.sql", - "src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/cpus.sql", "src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/metadata.sql", "src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/percentiles.sql", "src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/slices.sql", - "src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/thread_states.sql", "src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/timestamps.sql", ], ) +# GN target: //src/trace_processor/perfetto_sql/stdlib/export:export +perfetto_filegroup( + name = "src_trace_processor_perfetto_sql_stdlib_export_export", + srcs = [ + "src/trace_processor/perfetto_sql/stdlib/export/to_firefox_profile.sql", + ], +) + # GN target: //src/trace_processor/perfetto_sql/stdlib/graphs:graphs perfetto_filegroup( name = "src_trace_processor_perfetto_sql_stdlib_graphs_graphs", srcs = [ + "src/trace_processor/perfetto_sql/stdlib/graphs/critical_path.sql", "src/trace_processor/perfetto_sql/stdlib/graphs/dominator_tree.sql", + "src/trace_processor/perfetto_sql/stdlib/graphs/hierarchy.sql", "src/trace_processor/perfetto_sql/stdlib/graphs/partition.sql", + "src/trace_processor/perfetto_sql/stdlib/graphs/scan.sql", "src/trace_processor/perfetto_sql/stdlib/graphs/search.sql", ], ) @@ -2577,19 +2740,55 @@ perfetto_filegroup( ], ) -# GN target: //src/trace_processor/perfetto_sql/stdlib/linux:linux +# GN target: //src/trace_processor/perfetto_sql/stdlib/linux/cpu/utilization:utilization perfetto_filegroup( - name = "src_trace_processor_perfetto_sql_stdlib_linux_linux", + name = "src_trace_processor_perfetto_sql_stdlib_linux_cpu_utilization_utilization", + srcs = [ + "src/trace_processor/perfetto_sql/stdlib/linux/cpu/utilization/general.sql", + "src/trace_processor/perfetto_sql/stdlib/linux/cpu/utilization/process.sql", + "src/trace_processor/perfetto_sql/stdlib/linux/cpu/utilization/system.sql", + "src/trace_processor/perfetto_sql/stdlib/linux/cpu/utilization/thread.sql", + ], +) + +# GN target: //src/trace_processor/perfetto_sql/stdlib/linux/cpu:cpu +perfetto_filegroup( + name = "src_trace_processor_perfetto_sql_stdlib_linux_cpu_cpu", srcs = [ - "src/trace_processor/perfetto_sql/stdlib/linux/cpu_idle.sql", + "src/trace_processor/perfetto_sql/stdlib/linux/cpu/frequency.sql", + "src/trace_processor/perfetto_sql/stdlib/linux/cpu/idle.sql", ], ) -# GN target: //src/trace_processor/perfetto_sql/stdlib/memory:memory +# GN target: //src/trace_processor/perfetto_sql/stdlib/linux/memory:memory perfetto_filegroup( - name = "src_trace_processor_perfetto_sql_stdlib_memory_memory", + name = "src_trace_processor_perfetto_sql_stdlib_linux_memory_memory", srcs = [ - "src/trace_processor/perfetto_sql/stdlib/memory/heap_graph_dominator_tree.sql", + "src/trace_processor/perfetto_sql/stdlib/linux/memory/general.sql", + "src/trace_processor/perfetto_sql/stdlib/linux/memory/high_watermark.sql", + "src/trace_processor/perfetto_sql/stdlib/linux/memory/process.sql", + ], +) + +# GN target: //src/trace_processor/perfetto_sql/stdlib/linux/perf:perf +perfetto_filegroup( + name = "src_trace_processor_perfetto_sql_stdlib_linux_perf_perf", + srcs = [ + "src/trace_processor/perfetto_sql/stdlib/linux/perf/samples.sql", + ], +) + +# GN target: //src/trace_processor/perfetto_sql/stdlib/linux:linux +perfetto_filegroup( + name = "src_trace_processor_perfetto_sql_stdlib_linux_linux", +) + +# GN target: //src/trace_processor/perfetto_sql/stdlib/metasql:metasql +perfetto_filegroup( + name = "src_trace_processor_perfetto_sql_stdlib_metasql_metasql", + srcs = [ + "src/trace_processor/perfetto_sql/stdlib/metasql/column_list.sql", + "src/trace_processor/perfetto_sql/stdlib/metasql/table_list.sql", ], ) @@ -2607,21 +2806,11 @@ perfetto_filegroup( srcs = [ "src/trace_processor/perfetto_sql/stdlib/prelude/casts.sql", "src/trace_processor/perfetto_sql/stdlib/prelude/slices.sql", + "src/trace_processor/perfetto_sql/stdlib/prelude/tables_views.sql", "src/trace_processor/perfetto_sql/stdlib/prelude/trace_bounds.sql", ], ) -# GN target: //src/trace_processor/perfetto_sql/stdlib/sched/utilization:utilization -perfetto_filegroup( - name = "src_trace_processor_perfetto_sql_stdlib_sched_utilization_utilization", - srcs = [ - "src/trace_processor/perfetto_sql/stdlib/sched/utilization/general.sql", - "src/trace_processor/perfetto_sql/stdlib/sched/utilization/process.sql", - "src/trace_processor/perfetto_sql/stdlib/sched/utilization/system.sql", - "src/trace_processor/perfetto_sql/stdlib/sched/utilization/thread.sql", - ], -) - # GN target: //src/trace_processor/perfetto_sql/stdlib/sched:sched perfetto_filegroup( name = "src_trace_processor_perfetto_sql_stdlib_sched_sched", @@ -2642,6 +2831,8 @@ perfetto_filegroup( srcs = [ "src/trace_processor/perfetto_sql/stdlib/slices/cpu_time.sql", "src/trace_processor/perfetto_sql/stdlib/slices/flat_slices.sql", + "src/trace_processor/perfetto_sql/stdlib/slices/flow.sql", + "src/trace_processor/perfetto_sql/stdlib/slices/hierarchy.sql", "src/trace_processor/perfetto_sql/stdlib/slices/slices.sql", "src/trace_processor/perfetto_sql/stdlib/slices/with_context.sql", ], @@ -2675,6 +2866,7 @@ perfetto_filegroup( perfetto_filegroup( name = "src_trace_processor_perfetto_sql_stdlib_viz_summary_summary", srcs = [ + "src/trace_processor/perfetto_sql/stdlib/viz/summary/counters.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", @@ -2682,13 +2874,26 @@ perfetto_filegroup( ], ) +# GN target: //src/trace_processor/perfetto_sql/stdlib/viz:viz +perfetto_filegroup( + name = "src_trace_processor_perfetto_sql_stdlib_viz_viz", + srcs = [ + "src/trace_processor/perfetto_sql/stdlib/viz/flamegraph.sql", + ], +) + # GN target: //src/trace_processor/perfetto_sql/stdlib/wattson:wattson perfetto_filegroup( name = "src_trace_processor_perfetto_sql_stdlib_wattson_wattson", srcs = [ "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", + "src/trace_processor/perfetto_sql/stdlib/wattson/cpu_split.sql", + "src/trace_processor/perfetto_sql/stdlib/wattson/curves/device.sql", + "src/trace_processor/perfetto_sql/stdlib/wattson/curves/grouped.sql", + "src/trace_processor/perfetto_sql/stdlib/wattson/curves/ungrouped.sql", + "src/trace_processor/perfetto_sql/stdlib/wattson/curves/utils.sql", + "src/trace_processor/perfetto_sql/stdlib/wattson/device_infos.sql", "src/trace_processor/perfetto_sql/stdlib/wattson/system_state.sql", ], ) @@ -2699,27 +2904,37 @@ perfetto_cc_amalgamated_sql( deps = [ ":src_trace_processor_perfetto_sql_stdlib_android_android", ":src_trace_processor_perfetto_sql_stdlib_android_auto_auto", + ":src_trace_processor_perfetto_sql_stdlib_android_cpu_cpu", ":src_trace_processor_perfetto_sql_stdlib_android_frames_frames", + ":src_trace_processor_perfetto_sql_stdlib_android_gpu_gpu", + ":src_trace_processor_perfetto_sql_stdlib_android_memory_heap_graph_heap_graph", + ":src_trace_processor_perfetto_sql_stdlib_android_memory_heap_profile_heap_profile", + ":src_trace_processor_perfetto_sql_stdlib_android_memory_memory", ":src_trace_processor_perfetto_sql_stdlib_android_startup_startup", ":src_trace_processor_perfetto_sql_stdlib_android_winscope_winscope", + ":src_trace_processor_perfetto_sql_stdlib_callstacks_callstacks", ":src_trace_processor_perfetto_sql_stdlib_chrome_chrome_sql", ":src_trace_processor_perfetto_sql_stdlib_common_common", ":src_trace_processor_perfetto_sql_stdlib_counters_counters", - ":src_trace_processor_perfetto_sql_stdlib_cpu_cpu", ":src_trace_processor_perfetto_sql_stdlib_deprecated_v42_common_common", + ":src_trace_processor_perfetto_sql_stdlib_export_export", ":src_trace_processor_perfetto_sql_stdlib_graphs_graphs", ":src_trace_processor_perfetto_sql_stdlib_intervals_intervals", + ":src_trace_processor_perfetto_sql_stdlib_linux_cpu_cpu", + ":src_trace_processor_perfetto_sql_stdlib_linux_cpu_utilization_utilization", ":src_trace_processor_perfetto_sql_stdlib_linux_linux", - ":src_trace_processor_perfetto_sql_stdlib_memory_memory", + ":src_trace_processor_perfetto_sql_stdlib_linux_memory_memory", + ":src_trace_processor_perfetto_sql_stdlib_linux_perf_perf", + ":src_trace_processor_perfetto_sql_stdlib_metasql_metasql", ":src_trace_processor_perfetto_sql_stdlib_pkvm_pkvm", ":src_trace_processor_perfetto_sql_stdlib_prelude_prelude", ":src_trace_processor_perfetto_sql_stdlib_sched_sched", - ":src_trace_processor_perfetto_sql_stdlib_sched_utilization_utilization", ":src_trace_processor_perfetto_sql_stdlib_slices_slices", ":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_viz_viz", ":src_trace_processor_perfetto_sql_stdlib_wattson_wattson", ], outs = [ @@ -2772,8 +2987,14 @@ perfetto_filegroup( name = "src_trace_processor_sqlite_bindings_bindings", srcs = [ "src/trace_processor/sqlite/bindings/sqlite_aggregate_function.h", + "src/trace_processor/sqlite/bindings/sqlite_bind.h", + "src/trace_processor/sqlite/bindings/sqlite_column.h", + "src/trace_processor/sqlite/bindings/sqlite_function.h", "src/trace_processor/sqlite/bindings/sqlite_module.h", "src/trace_processor/sqlite/bindings/sqlite_result.h", + "src/trace_processor/sqlite/bindings/sqlite_stmt.h", + "src/trace_processor/sqlite/bindings/sqlite_type.h", + "src/trace_processor/sqlite/bindings/sqlite_value.h", "src/trace_processor/sqlite/bindings/sqlite_window_function.h", ], ) @@ -2796,7 +3017,6 @@ perfetto_filegroup( "src/trace_processor/sqlite/sqlite_tokenizer.h", "src/trace_processor/sqlite/sqlite_utils.cc", "src/trace_processor/sqlite/sqlite_utils.h", - "src/trace_processor/sqlite/sqlite_value.h", "src/trace_processor/sqlite/stats_table.cc", "src/trace_processor/sqlite/stats_table.h", ], @@ -3011,6 +3231,24 @@ perfetto_filegroup( ], ) +# GN target: //src/trace_processor/util:trace_blob_view_reader +perfetto_filegroup( + name = "src_trace_processor_util_trace_blob_view_reader", + srcs = [ + "src/trace_processor/util/trace_blob_view_reader.cc", + "src/trace_processor/util/trace_blob_view_reader.h", + ], +) + +# GN target: //src/trace_processor/util:trace_type +perfetto_filegroup( + name = "src_trace_processor_util_trace_type", + srcs = [ + "src/trace_processor/util/trace_type.cc", + "src/trace_processor/util/trace_type.h", + ], +) + # GN target: //src/trace_processor/util:util perfetto_filegroup( name = "src_trace_processor_util_util", @@ -3092,6 +3330,8 @@ perfetto_filegroup( "src/trace_processor/trace_processor_storage.cc", "src/trace_processor/trace_processor_storage_impl.cc", "src/trace_processor/trace_processor_storage_impl.h", + "src/trace_processor/trace_reader_registry.cc", + "src/trace_processor/trace_reader_registry.h", "src/trace_processor/virtual_destructors.cc", ], ) @@ -3107,6 +3347,17 @@ perfetto_cc_proto_descriptor( ], ) +# GN target: //src/traceconv:gen_cc_winscope_descriptor +perfetto_cc_proto_descriptor( + name = "src_traceconv_gen_cc_winscope_descriptor", + deps = [ + ":protos_perfetto_trace_android_winscope_descriptor", + ], + outs = [ + "src/traceconv/winscope.descriptor.h", + ], +) + # GN target: //src/traceconv:lib perfetto_filegroup( name = "src_traceconv_lib", @@ -3115,6 +3366,8 @@ perfetto_filegroup( "src/traceconv/deobfuscate_profile.h", "src/traceconv/symbolize_profile.cc", "src/traceconv/symbolize_profile.h", + "src/traceconv/trace_to_firefox.cc", + "src/traceconv/trace_to_firefox.h", "src/traceconv/trace_to_hprof.cc", "src/traceconv/trace_to_hprof.h", "src/traceconv/trace_to_json.cc", @@ -3964,6 +4217,7 @@ perfetto_proto_library( "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/android/windowmanager_config.proto", ], visibility = [ PERFETTO_CONFIG.proto_library_visibility, @@ -4493,6 +4747,7 @@ perfetto_proto_library( "protos/perfetto/metrics/android/android_blocking_calls_unagg.proto", "protos/perfetto/metrics/android/android_boot.proto", "protos/perfetto/metrics/android/android_boot_unagg.proto", + "protos/perfetto/metrics/android/android_broadcasts_metric.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", @@ -4547,6 +4802,7 @@ perfetto_proto_library( "protos/perfetto/metrics/android/thread_time_in_state_metric.proto", "protos/perfetto/metrics/android/trace_quality.proto", "protos/perfetto/metrics/android/unsymbolized_frames.proto", + "protos/perfetto/metrics/android/wattson_app_startup.proto", ], visibility = [ PERFETTO_CONFIG.proto_library_visibility, @@ -4660,12 +4916,39 @@ perfetto_proto_library( ], ) +# GN target: //protos/perfetto/trace/android:android_track_event_descriptor +perfetto_proto_descriptor( + name = "protos_perfetto_trace_android_android_track_event_descriptor", + deps = [ + ":protos_perfetto_trace_android_android_track_event_protos", + ], + outs = [ + "protos_perfetto_trace_android_android_track_event_descriptor.bin", + ], +) + +# GN target: //protos/perfetto/trace/android:android_track_event_source_set +perfetto_proto_library( + name = "protos_perfetto_trace_android_android_track_event_protos", + srcs = [ + "protos/perfetto/trace/android/android_track_event.proto", + ], + visibility = [ + PERFETTO_CONFIG.proto_library_visibility, + ], + deps = [ + ":protos_perfetto_trace_track_event_protos", + ], + exports = [ + ":protos_perfetto_trace_track_event_protos", + ], +) + # GN target: //protos/perfetto/trace/android:source_set perfetto_proto_library( name = "protos_perfetto_trace_android_protos", srcs = [ "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", @@ -4724,14 +5007,27 @@ perfetto_proto_descriptor( perfetto_proto_library( name = "protos_perfetto_trace_android_winscope_extensions_protos", srcs = [ + "protos/perfetto/trace/android/android_input_event.proto", + "protos/perfetto/trace/android/app/statusbarmanager.proto", + "protos/perfetto/trace/android/app/window_configuration.proto", + "protos/perfetto/trace/android/content/activityinfo.proto", + "protos/perfetto/trace/android/content/configuration.proto", + "protos/perfetto/trace/android/content/locale.proto", "protos/perfetto/trace/android/graphics/pixelformat.proto", "protos/perfetto/trace/android/inputmethodeditor.proto", "protos/perfetto/trace/android/inputmethodservice/inputmethodservice.proto", "protos/perfetto/trace/android/inputmethodservice/softinputwindow.proto", + "protos/perfetto/trace/android/privacy.proto", + "protos/perfetto/trace/android/server/animationadapter.proto", "protos/perfetto/trace/android/server/inputmethod/inputmethodmanagerservice.proto", + "protos/perfetto/trace/android/server/surfaceanimator.proto", + "protos/perfetto/trace/android/server/windowcontainerthumbnail.proto", + "protos/perfetto/trace/android/server/windowmanagerservice.proto", "protos/perfetto/trace/android/typedef.proto", "protos/perfetto/trace/android/view/display.proto", "protos/perfetto/trace/android/view/displaycutout.proto", + "protos/perfetto/trace/android/view/displayinfo.proto", + "protos/perfetto/trace/android/view/enums.proto", "protos/perfetto/trace/android/view/imefocuscontroller.proto", "protos/perfetto/trace/android/view/imeinsetssourceconsumer.proto", "protos/perfetto/trace/android/view/inputmethod/editorinfo.proto", @@ -4743,9 +5039,13 @@ perfetto_proto_library( "protos/perfetto/trace/android/view/insetssourceconsumer.proto", "protos/perfetto/trace/android/view/insetssourcecontrol.proto", "protos/perfetto/trace/android/view/insetsstate.proto", + "protos/perfetto/trace/android/view/remote_animation_target.proto", + "protos/perfetto/trace/android/view/surface.proto", "protos/perfetto/trace/android/view/surfacecontrol.proto", "protos/perfetto/trace/android/view/viewrootimpl.proto", "protos/perfetto/trace/android/view/windowlayoutparams.proto", + "protos/perfetto/trace/android/viewcapture.proto", + "protos/perfetto/trace/android/windowmanager.proto", "protos/perfetto/trace/android/winscope_extensions_impl.proto", ], visibility = [ @@ -4887,6 +5187,7 @@ perfetto_proto_library( name = "protos_perfetto_trace_ftrace_protos", srcs = [ "protos/perfetto/trace/ftrace/android_fs.proto", + "protos/perfetto/trace/ftrace/bcl_exynos.proto", "protos/perfetto/trace/ftrace/binder.proto", "protos/perfetto/trace/ftrace/block.proto", "protos/perfetto/trace/ftrace/cgroup.proto", @@ -4895,6 +5196,7 @@ perfetto_proto_library( "protos/perfetto/trace/ftrace/compaction.proto", "protos/perfetto/trace/ftrace/cpuhp.proto", "protos/perfetto/trace/ftrace/cros_ec.proto", + "protos/perfetto/trace/ftrace/dcvsh.proto", "protos/perfetto/trace/ftrace/dma_fence.proto", "protos/perfetto/trace/ftrace/dmabuf_heap.proto", "protos/perfetto/trace/ftrace/dpu.proto", @@ -4919,6 +5221,7 @@ perfetto_proto_library( "protos/perfetto/trace/ftrace/ion.proto", "protos/perfetto/trace/ftrace/ipi.proto", "protos/perfetto/trace/ftrace/irq.proto", + "protos/perfetto/trace/ftrace/kgsl.proto", "protos/perfetto/trace/ftrace/kmem.proto", "protos/perfetto/trace/ftrace/kvm.proto", "protos/perfetto/trace/ftrace/lowmemorykiller.proto", @@ -5528,6 +5831,25 @@ perfetto_cc_protozero_library( ], ) +# GN target: //protos/third_party/simpleperf:source_set +perfetto_proto_library( + name = "protos_third_party_simpleperf_protos", + srcs = [ + "protos/third_party/simpleperf/record_file.proto", + ], + visibility = [ + PERFETTO_CONFIG.proto_library_visibility, + ], +) + +# GN target: //protos/third_party/simpleperf:zero +perfetto_cc_protozero_library( + name = "protos_third_party_simpleperf_zero", + deps = [ + ":protos_third_party_simpleperf_protos", + ], +) + # GN target: //protos/third_party/statsd:config_source_set perfetto_proto_library( name = "protos_third_party_statsd_config_protos", @@ -5881,10 +6203,12 @@ perfetto_cc_library( srcs = [ ":src_kernel_utils_syscall_table", ":src_trace_processor_db_column_column", + ":src_trace_processor_db_compare", ":src_trace_processor_db_db", ":src_trace_processor_db_minimal", ":src_trace_processor_export_json", ":src_trace_processor_importers_android_bugreport_android_bugreport", + ":src_trace_processor_importers_android_bugreport_android_log_event", ":src_trace_processor_importers_common_common", ":src_trace_processor_importers_common_parser_types", ":src_trace_processor_importers_common_trace_parser_hdr", @@ -5904,6 +6228,7 @@ perfetto_cc_library( ":src_trace_processor_importers_ninja_ninja", ":src_trace_processor_importers_perf_perf", ":src_trace_processor_importers_perf_record", + ":src_trace_processor_importers_perf_tracker", ":src_trace_processor_importers_proto_full", ":src_trace_processor_importers_proto_minimal", ":src_trace_processor_importers_proto_packet_sequence_state_generation_hdr", @@ -5913,6 +6238,7 @@ perfetto_cc_library( ":src_trace_processor_importers_systrace_full", ":src_trace_processor_importers_systrace_systrace_line", ":src_trace_processor_importers_systrace_systrace_parser", + ":src_trace_processor_importers_zip_full", ":src_trace_processor_lib", ":src_trace_processor_metatrace", ":src_trace_processor_metrics_metrics", @@ -5924,6 +6250,7 @@ perfetto_cc_library( ":src_trace_processor_perfetto_sql_intrinsics_table_functions_interface", ":src_trace_processor_perfetto_sql_intrinsics_table_functions_table_functions", ":src_trace_processor_perfetto_sql_intrinsics_table_functions_tables", + ":src_trace_processor_perfetto_sql_intrinsics_types_types", ":src_trace_processor_sorter_sorter", ":src_trace_processor_sqlite_bindings_bindings", ":src_trace_processor_sqlite_sqlite", @@ -5947,6 +6274,8 @@ perfetto_cc_library( ":src_trace_processor_util_regex", ":src_trace_processor_util_sql_argument", ":src_trace_processor_util_stdlib", + ":src_trace_processor_util_trace_blob_view_reader", + ":src_trace_processor_util_trace_type", ":src_trace_processor_util_util", ":src_trace_processor_util_zip_reader", ], @@ -6007,9 +6336,11 @@ perfetto_cc_library( ":protos_perfetto_trace_track_event_zero", ":protos_perfetto_trace_translation_zero", ":protos_third_party_pprof_zero", + ":protos_third_party_simpleperf_zero", ":protozero", ":src_base_base", ":src_trace_processor_containers_containers", + ":src_trace_processor_importers_proto_gen_cc_android_track_event_descriptor", ":src_trace_processor_importers_proto_gen_cc_chrome_track_event_descriptor", ":src_trace_processor_importers_proto_gen_cc_config_descriptor", ":src_trace_processor_importers_proto_gen_cc_statsd_atoms_descriptor", @@ -6055,10 +6386,12 @@ perfetto_cc_binary( ":src_profiling_symbolizer_symbolizer", ":src_protozero_proto_ring_buffer", ":src_trace_processor_db_column_column", + ":src_trace_processor_db_compare", ":src_trace_processor_db_db", ":src_trace_processor_db_minimal", ":src_trace_processor_export_json", ":src_trace_processor_importers_android_bugreport_android_bugreport", + ":src_trace_processor_importers_android_bugreport_android_log_event", ":src_trace_processor_importers_common_common", ":src_trace_processor_importers_common_parser_types", ":src_trace_processor_importers_common_trace_parser_hdr", @@ -6078,6 +6411,7 @@ perfetto_cc_binary( ":src_trace_processor_importers_ninja_ninja", ":src_trace_processor_importers_perf_perf", ":src_trace_processor_importers_perf_record", + ":src_trace_processor_importers_perf_tracker", ":src_trace_processor_importers_proto_full", ":src_trace_processor_importers_proto_minimal", ":src_trace_processor_importers_proto_packet_sequence_state_generation_hdr", @@ -6087,6 +6421,7 @@ perfetto_cc_binary( ":src_trace_processor_importers_systrace_full", ":src_trace_processor_importers_systrace_systrace_line", ":src_trace_processor_importers_systrace_systrace_parser", + ":src_trace_processor_importers_zip_full", ":src_trace_processor_lib", ":src_trace_processor_metatrace", ":src_trace_processor_metrics_metrics", @@ -6098,6 +6433,7 @@ perfetto_cc_binary( ":src_trace_processor_perfetto_sql_intrinsics_table_functions_interface", ":src_trace_processor_perfetto_sql_intrinsics_table_functions_table_functions", ":src_trace_processor_perfetto_sql_intrinsics_table_functions_tables", + ":src_trace_processor_perfetto_sql_intrinsics_types_types", ":src_trace_processor_rpc_httpd", ":src_trace_processor_rpc_rpc", ":src_trace_processor_rpc_stdiod", @@ -6124,6 +6460,8 @@ perfetto_cc_binary( ":src_trace_processor_util_regex", ":src_trace_processor_util_sql_argument", ":src_trace_processor_util_stdlib", + ":src_trace_processor_util_trace_blob_view_reader", + ":src_trace_processor_util_trace_type", ":src_trace_processor_util_util", ":src_trace_processor_util_zip_reader", "src/trace_processor/trace_processor_shell.cc", @@ -6170,11 +6508,13 @@ perfetto_cc_binary( ":protos_perfetto_trace_track_event_zero", ":protos_perfetto_trace_translation_zero", ":protos_third_party_pprof_zero", + ":protos_third_party_simpleperf_zero", ":protozero", ":src_base_base", ":src_base_http_http", ":src_base_version", ":src_trace_processor_containers_containers", + ":src_trace_processor_importers_proto_gen_cc_android_track_event_descriptor", ":src_trace_processor_importers_proto_gen_cc_chrome_track_event_descriptor", ":src_trace_processor_importers_proto_gen_cc_config_descriptor", ":src_trace_processor_importers_proto_gen_cc_statsd_atoms_descriptor", @@ -6289,10 +6629,12 @@ perfetto_cc_binary( ":src_profiling_symbolizer_symbolizer", ":src_protozero_proto_ring_buffer", ":src_trace_processor_db_column_column", + ":src_trace_processor_db_compare", ":src_trace_processor_db_db", ":src_trace_processor_db_minimal", ":src_trace_processor_export_json", ":src_trace_processor_importers_android_bugreport_android_bugreport", + ":src_trace_processor_importers_android_bugreport_android_log_event", ":src_trace_processor_importers_common_common", ":src_trace_processor_importers_common_parser_types", ":src_trace_processor_importers_common_trace_parser_hdr", @@ -6312,6 +6654,7 @@ perfetto_cc_binary( ":src_trace_processor_importers_ninja_ninja", ":src_trace_processor_importers_perf_perf", ":src_trace_processor_importers_perf_record", + ":src_trace_processor_importers_perf_tracker", ":src_trace_processor_importers_proto_full", ":src_trace_processor_importers_proto_minimal", ":src_trace_processor_importers_proto_packet_sequence_state_generation_hdr", @@ -6321,6 +6664,7 @@ perfetto_cc_binary( ":src_trace_processor_importers_systrace_full", ":src_trace_processor_importers_systrace_systrace_line", ":src_trace_processor_importers_systrace_systrace_parser", + ":src_trace_processor_importers_zip_full", ":src_trace_processor_lib", ":src_trace_processor_metatrace", ":src_trace_processor_metrics_metrics", @@ -6332,6 +6676,7 @@ perfetto_cc_binary( ":src_trace_processor_perfetto_sql_intrinsics_table_functions_interface", ":src_trace_processor_perfetto_sql_intrinsics_table_functions_table_functions", ":src_trace_processor_perfetto_sql_intrinsics_table_functions_tables", + ":src_trace_processor_perfetto_sql_intrinsics_types_types", ":src_trace_processor_sorter_sorter", ":src_trace_processor_sqlite_bindings_bindings", ":src_trace_processor_sqlite_sqlite", @@ -6355,6 +6700,8 @@ perfetto_cc_binary( ":src_trace_processor_util_regex", ":src_trace_processor_util_sql_argument", ":src_trace_processor_util_stdlib", + ":src_trace_processor_util_trace_blob_view_reader", + ":src_trace_processor_util_trace_type", ":src_trace_processor_util_util", ":src_trace_processor_util_zip_reader", ":src_traceconv_lib", @@ -6404,10 +6751,12 @@ perfetto_cc_binary( ":protos_perfetto_trace_track_event_zero", ":protos_perfetto_trace_translation_zero", ":protos_third_party_pprof_zero", + ":protos_third_party_simpleperf_zero", ":protozero", ":src_base_base", ":src_base_version", ":src_trace_processor_containers_containers", + ":src_trace_processor_importers_proto_gen_cc_android_track_event_descriptor", ":src_trace_processor_importers_proto_gen_cc_chrome_track_event_descriptor", ":src_trace_processor_importers_proto_gen_cc_config_descriptor", ":src_trace_processor_importers_proto_gen_cc_statsd_atoms_descriptor", @@ -6421,6 +6770,7 @@ perfetto_cc_binary( ":src_trace_processor_perfetto_sql_prelude_prelude", ":src_trace_processor_perfetto_sql_stdlib_stdlib", ":src_traceconv_gen_cc_trace_descriptor", + ":src_traceconv_gen_cc_winscope_descriptor", ] + PERFETTO_CONFIG.deps.jsoncpp + PERFETTO_CONFIG.deps.sqlite + PERFETTO_CONFIG.deps.sqlite_ext_percentile + diff --git a/BUILD.gn b/BUILD.gn index a6acff19e7..ee45b3c217 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -45,6 +45,10 @@ if (enable_perfetto_platform_services) { } if (enable_perfetto_trace_processor && enable_perfetto_trace_processor_sqlite) { + if (enable_perfetto_grpc) { + all_targets += [ "src/bigtrace/orchestrator:orchestrator_main" ] + all_targets += [ "src/bigtrace/worker:worker_main" ] + } all_targets += [ "src/trace_processor:trace_processor_shell" ] } diff --git a/CHANGELOG b/CHANGELOG index d117e7d343..e3884768ac 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,15 +1,66 @@ Unreleased: Tracing service and probes: * + SQL Standard library: + * Removed `cpu.cpus` and `cpu.size` modules. The functions inside + for guessing core type were inaccurate and often misleading. + There is no replacement for these as there is no accurate data + source available. + * Moved `cpu.utilization` package to `linux.cpu.utilization`. The + functionality inside this package only works properly on Linux + and Linux derived OSes (e.g. Android). + * Moved `cpu.freq` module to `linux.cpu.frequency` and renamed + `cpu_freq_counters` to `cpu_frequency_counters` for the same + reason as above. + * Moved `gpu.frequency` to `android.gpu.frequency` for the same reason as + above. + * Moved `cpu.idle` module to `linux.cpu.idle` for the same + reason as above. + * Moved content of `linux.cpu_idle` into `linux.cpu.idle` to make it + consistent with above changes. + * Moved `memory.android.gpu` to `android.memory.gpu` to make it consistent + with above changes.` + * Moved contents of `memory.linux.process` to `linux.memory.process` and + `android.memory.process` to make it consistent with above changes. + * Moved `memory.linux.high_watermark` to `linux.memory.high_watermark` to + make it consistent with above changes. + * Moved `memory.heap_graph_dominator_tree` to + `android.memory.heap_graph.dominator_tree`. This is to allow for the + addition of more modules related to heap graphs. Trace Processor: - * Added "time to initial display" and "time to full display" metrics to - the Android startup metric. + * Added `CREATE PERFETTO INDEX` to add sqlite-like indexes to Perfetto + tables. Has the same API as `CREATE INDEX`. UI: * SDK: * +v46.0 - 2024-06-13: + SQL Standard library: + * Added megacycles support to CPU package. Added tables: + `cpu_cycles_per_process`, `cpu_cycles_per_thread` and + `cpu_cycles_per_cpu`. + * Improved `memory` package. Added `memory.linux.process`, + `memory.linux.high_watermark` and `memory.android.gpu` modules. + * Created `gpu` package with `gpu.frequency` module. + * Migrated `sched.utilization` package to `cpu.utilization`. + Trace Processor: + * Added "time to initial display" and "time to full display" metrics to + the Android startup metric. + UI: + * Added plugin for synchronizing two instances of the UI - search for + 'Enable timeline sync with other Perfetto UI tabs' in the command palette. + * Add builtin prompt functionality to the plugin API: + I.e. `await PluginContextTrace.prompt('...')` + * Fixed various bits of tech debt. + SDK: + * The TRACE_COUNTER macro and CounterTrack constructor no longer accept + `const char *` track names. In case your code fails to compile, + https://perfetto.dev/docs/instrumentation/track-events#dynamic-event-names + explains how to fix the problem. + + v45.0 - 2024-05-09: Trace Processor: * Optimised single column `DISTINCT` queries. diff --git a/LICENSE b/LICENSE index bb056df223..cd2eba57bf 100644 --- a/LICENSE +++ b/LICENSE @@ -187,3 +187,30 @@ limitations under the License. + Copyright 2015 The Chromium Authors + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/OWNERS b/OWNERS index b2a1331009..13037640ad 100644 --- a/OWNERS +++ b/OWNERS @@ -4,7 +4,6 @@ # Perfetto tracing internals and API/ABI boundaries. primiano@google.com -skyostil@google.com # UI, Ftrace interop, traced_probes, protozero, Android internals. diff --git a/PRESUBMIT.py b/PRESUBMIT.py index 124a502e50..c615ffaafb 100644 --- a/PRESUBMIT.py +++ b/PRESUBMIT.py @@ -24,7 +24,7 @@ def RunAndReportIfLong(func, *args, **kargs): start = time.time() results = func(*args, **kargs) end = time.time() - limit = 0.5 # seconds + limit = 3.0 # seconds name = func.__name__ runtime = end - start if runtime > limit: @@ -52,6 +52,7 @@ def long_line_sources(x): '/perfetto_build_flags.h$', "infra/luci/.*", "^ui/.*\.[jt]s$", # TS/JS handled by eslint + "^ui/pnpm-lock.yaml$", ]) results = [] diff --git a/TEST_MAPPING b/TEST_MAPPING index 9d84571592..0e6a83da7e 100644 --- a/TEST_MAPPING +++ b/TEST_MAPPING @@ -1,4 +1,14 @@ { + "art-mainline-presubmit": [ + { + "name": "CtsPerfettoTestCases", + "options": [ + { + "include-filter": "HeapprofdJavaCtsTest*" + } + ] + } + ], "presubmit": [ { "name": "CtsPerfettoTestCases" diff --git a/WORKSPACE b/WORKSPACE index a933c67cc1..6bf498049c 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -21,7 +21,7 @@ workspace(name = "perfetto") new_local_repository( name = "perfetto_cfg", path = "bazel/standalone", - build_file_content = "" + build_file_content = "", ) load("@perfetto//bazel:deps.bzl", "perfetto_deps") diff --git a/bazel/deps.bzl b/bazel/deps.bzl index ee178560ed..69dbcbbfde 100644 --- a/bazel/deps.bzl +++ b/bazel/deps.bzl @@ -95,3 +95,4 @@ def perfetto_deps(): def _add_repo_if_not_existing(repo_rule, name, **kwargs): if name not in native.existing_rules(): repo_rule(name = name, **kwargs) + diff --git a/bazel/proto_gen.bzl b/bazel/proto_gen.bzl index c5fd127c25..e237ebc3f8 100644 --- a/bazel/proto_gen.bzl +++ b/bazel/proto_gen.bzl @@ -46,13 +46,17 @@ def _proto_gen_impl(ctx): strip_base_path = ctx.label.package + "/" elif ctx.label.workspace_root: # This path is hit when proto targets are built as @perfetto//:xxx - # instead of //:xxx. This happens in embedder builds. In this case, - # workspace_root == "external/perfetto" and we need to rebase the paths - # passed to protoc. + # instead of //:xxx. This happens in embedder builds. proto_path = ctx.label.workspace_root - out_dir += "/" + ctx.label.workspace_root + + # We could be using the sibling repository layout, in which case we do nothing. + if not ctx.label.workspace_root.startswith("../"): + # workspace_root == "external/perfetto" and we need to rebase the paths + # passed to protoc. + out_dir += "/" + ctx.label.workspace_root strip_base_path = ctx.label.workspace_root + "/" + out_files = [] suffix = ctx.attr.suffix for src in proto_src: diff --git a/buildtools/.gitignore b/buildtools/.gitignore index 39840d3fe8..a3e63760f5 100644 --- a/buildtools/.gitignore +++ b/buildtools/.gitignore @@ -1,43 +1,44 @@ -android_sdk/ -android-core/ -android-libbase/ -android-libprocinfo/ -android-logging/ -android-unwinding/ -aosp-*/ -benchmark/ -bionic/ -bloaty/ -catapult_trace_viewer/ -clang_format/ -clang/ -d8/ -debian_sid_arm-sysroot/ -debian_sid_arm64-sysroot/ -debian_sid_armel-sysroot/ -emulator/ -googletest/ -grpc/src -jsoncpp/ -libbacktrace/ -libbase/ -libcxx/ -libcxxabi/ -libfuzzer/ -libunwind/ -linenoise/ -linux/ -linux64/ -llvm-project/ -lzma/ -mac/ -ndk/ -nodejs/ -protobuf/ -sqlite_src/ -sqlite/ -test_data/ -typefaces/ -win/ -zlib/ -zstd/ +/android_sdk/ +/android-core/ +/android-libbase/ +/android-libprocinfo/ +/android-logging/ +/android-unwinding/ +/aosp-*/ +/benchmark/ +/bionic/ +/bloaty/ +/catapult_trace_viewer/ +/clang_format/ +/clang/ +/d8/ +/debian_sid_arm-sysroot/ +/debian_sid_arm64-sysroot/ +/debian_sid_armel-sysroot/ +/emulator/ +/googletest/ +/grpc/src/ +/jsoncpp/ +/libbacktrace/ +/libbase/ +/libcxx/ +/libcxxabi/ +/libfuzzer/ +/libunwind/ +/linenoise/ +/linux/ +/linux64/ +/llvm-project/ +/lzma/ +/mac/ +/ndk/ +/nodejs/ +/pigweed/ +/protobuf/ +/sqlite_src/ +/sqlite/ +/test_data/ +/typefaces/ +/win/ +/zlib/ +/zstd/ diff --git a/buildtools/BUILD.gn b/buildtools/BUILD.gn index ae25789a62..275595890e 100644 --- a/buildtools/BUILD.gn +++ b/buildtools/BUILD.gn @@ -144,7 +144,7 @@ config("protobuf_config") { if (is_mac) { cflags += [ "-Wno-deprecated-declarations" ] } - if (!is_clang && !is_win) { # implies gcc + if (is_gcc) { cflags += [ "-Wno-stringop-overread" ] } if (is_win) { @@ -164,141 +164,147 @@ config("protobuf_full_public_config") { } } +_protobuf_headers = [ + "protobuf/src/google/protobuf/any.h", + "protobuf/src/google/protobuf/any.pb.h", + "protobuf/src/google/protobuf/api.pb.h", + "protobuf/src/google/protobuf/arena_impl.h", + "protobuf/src/google/protobuf/arena.h", + "protobuf/src/google/protobuf/arenastring.h", + "protobuf/src/google/protobuf/arenaz_sampler.h", + "protobuf/src/google/protobuf/compiler/importer.h", + "protobuf/src/google/protobuf/compiler/parser.h", + "protobuf/src/google/protobuf/descriptor_database.h", + "protobuf/src/google/protobuf/descriptor.h", + "protobuf/src/google/protobuf/descriptor.pb.h", + "protobuf/src/google/protobuf/duration.pb.h", + "protobuf/src/google/protobuf/dynamic_message.h", + "protobuf/src/google/protobuf/empty.pb.h", + "protobuf/src/google/protobuf/endian.h", + "protobuf/src/google/protobuf/explicitly_constructed.h", + "protobuf/src/google/protobuf/extension_set_inl.h", + "protobuf/src/google/protobuf/extension_set.h", + "protobuf/src/google/protobuf/field_access_listener.h", + "protobuf/src/google/protobuf/field_mask.pb.h", + "protobuf/src/google/protobuf/generated_enum_reflection.h", + "protobuf/src/google/protobuf/generated_enum_util.h", + "protobuf/src/google/protobuf/generated_message_bases.h", + "protobuf/src/google/protobuf/generated_message_reflection.h", + "protobuf/src/google/protobuf/generated_message_tctable_decl.h", + "protobuf/src/google/protobuf/generated_message_tctable_impl.h", + "protobuf/src/google/protobuf/generated_message_util.h", + "protobuf/src/google/protobuf/has_bits.h", + "protobuf/src/google/protobuf/implicit_weak_message.h", + "protobuf/src/google/protobuf/inlined_string_field.h", + "protobuf/src/google/protobuf/io/coded_stream.h", + "protobuf/src/google/protobuf/io/io_win32.h", + "protobuf/src/google/protobuf/io/printer.h", + "protobuf/src/google/protobuf/io/strtod.h", + "protobuf/src/google/protobuf/io/tokenizer.h", + "protobuf/src/google/protobuf/io/zero_copy_stream_impl_lite.h", + "protobuf/src/google/protobuf/io/zero_copy_stream_impl.h", + "protobuf/src/google/protobuf/io/zero_copy_stream.h", + "protobuf/src/google/protobuf/map_entry_lite.h", + "protobuf/src/google/protobuf/map_entry.h", + "protobuf/src/google/protobuf/map_field_inl.h", + "protobuf/src/google/protobuf/map_field_lite.h", + "protobuf/src/google/protobuf/map_field.h", + "protobuf/src/google/protobuf/map_type_handler.h", + "protobuf/src/google/protobuf/map.h", + "protobuf/src/google/protobuf/message_lite.h", + "protobuf/src/google/protobuf/message.h", + "protobuf/src/google/protobuf/metadata_lite.h", + "protobuf/src/google/protobuf/metadata.h", + "protobuf/src/google/protobuf/parse_context.h", + "protobuf/src/google/protobuf/port_def.inc", + "protobuf/src/google/protobuf/port_undef.inc", + "protobuf/src/google/protobuf/port.h", + "protobuf/src/google/protobuf/reflection_internal.h", + "protobuf/src/google/protobuf/reflection_ops.h", + "protobuf/src/google/protobuf/reflection.h", + "protobuf/src/google/protobuf/repeated_field.h", + "protobuf/src/google/protobuf/repeated_ptr_field.h", + "protobuf/src/google/protobuf/service.h", + "protobuf/src/google/protobuf/source_context.pb.h", + "protobuf/src/google/protobuf/struct.pb.h", + "protobuf/src/google/protobuf/stubs/bytestream.h", + "protobuf/src/google/protobuf/stubs/callback.h", + "protobuf/src/google/protobuf/stubs/casts.h", + "protobuf/src/google/protobuf/stubs/common.h", + "protobuf/src/google/protobuf/stubs/hash.h", + "protobuf/src/google/protobuf/stubs/logging.h", + "protobuf/src/google/protobuf/stubs/macros.h", + "protobuf/src/google/protobuf/stubs/map_util.h", + "protobuf/src/google/protobuf/stubs/mutex.h", + "protobuf/src/google/protobuf/stubs/once.h", + "protobuf/src/google/protobuf/stubs/platform_macros.h", + "protobuf/src/google/protobuf/stubs/port.h", + "protobuf/src/google/protobuf/stubs/status.h", + "protobuf/src/google/protobuf/stubs/stl_util.h", + "protobuf/src/google/protobuf/stubs/stringpiece.h", + "protobuf/src/google/protobuf/stubs/strutil.h", + "protobuf/src/google/protobuf/stubs/template_util.h", + "protobuf/src/google/protobuf/text_format.h", + "protobuf/src/google/protobuf/timestamp.pb.h", + "protobuf/src/google/protobuf/type.pb.h", + "protobuf/src/google/protobuf/unknown_field_set.h", + "protobuf/src/google/protobuf/util/delimited_message_util.h", + "protobuf/src/google/protobuf/util/field_comparator.h", + "protobuf/src/google/protobuf/util/field_mask_util.h", + "protobuf/src/google/protobuf/util/json_util.h", + "protobuf/src/google/protobuf/util/message_differencer.h", + "protobuf/src/google/protobuf/util/time_util.h", + "protobuf/src/google/protobuf/util/type_resolver_util.h", + "protobuf/src/google/protobuf/util/type_resolver.h", + "protobuf/src/google/protobuf/wire_format_lite.h", + "protobuf/src/google/protobuf/wire_format.h", + "protobuf/src/google/protobuf/wrappers.pb.h", +] + source_set("protobuf_lite") { visibility = _buildtools_visibility sources = [ - "protobuf/src/google/protobuf/any.h", - "protobuf/src/google/protobuf/any.pb.h", "protobuf/src/google/protobuf/any_lite.cc", - "protobuf/src/google/protobuf/api.pb.h", "protobuf/src/google/protobuf/arena.cc", - "protobuf/src/google/protobuf/arena.h", - "protobuf/src/google/protobuf/arena_impl.h", "protobuf/src/google/protobuf/arenastring.cc", - "protobuf/src/google/protobuf/arenastring.h", "protobuf/src/google/protobuf/arenaz_sampler.cc", - "protobuf/src/google/protobuf/arenaz_sampler.h", - "protobuf/src/google/protobuf/compiler/importer.h", - "protobuf/src/google/protobuf/compiler/parser.h", - "protobuf/src/google/protobuf/descriptor.h", - "protobuf/src/google/protobuf/descriptor.pb.h", - "protobuf/src/google/protobuf/descriptor_database.h", - "protobuf/src/google/protobuf/duration.pb.h", - "protobuf/src/google/protobuf/dynamic_message.h", - "protobuf/src/google/protobuf/empty.pb.h", - "protobuf/src/google/protobuf/explicitly_constructed.h", "protobuf/src/google/protobuf/extension_set.cc", - "protobuf/src/google/protobuf/extension_set.h", - "protobuf/src/google/protobuf/extension_set_inl.h", - "protobuf/src/google/protobuf/field_access_listener.h", - "protobuf/src/google/protobuf/field_mask.pb.h", - "protobuf/src/google/protobuf/generated_enum_reflection.h", "protobuf/src/google/protobuf/generated_enum_util.cc", - "protobuf/src/google/protobuf/generated_enum_util.h", - "protobuf/src/google/protobuf/generated_message_bases.h", - "protobuf/src/google/protobuf/generated_message_reflection.h", - "protobuf/src/google/protobuf/generated_message_tctable_decl.h", - "protobuf/src/google/protobuf/generated_message_tctable_impl.h", "protobuf/src/google/protobuf/generated_message_tctable_lite.cc", "protobuf/src/google/protobuf/generated_message_util.cc", - "protobuf/src/google/protobuf/generated_message_util.h", - "protobuf/src/google/protobuf/has_bits.h", "protobuf/src/google/protobuf/implicit_weak_message.cc", - "protobuf/src/google/protobuf/implicit_weak_message.h", "protobuf/src/google/protobuf/inlined_string_field.cc", - "protobuf/src/google/protobuf/inlined_string_field.h", "protobuf/src/google/protobuf/io/coded_stream.cc", - "protobuf/src/google/protobuf/io/coded_stream.h", "protobuf/src/google/protobuf/io/io_win32.cc", - "protobuf/src/google/protobuf/io/io_win32.h", - "protobuf/src/google/protobuf/io/printer.h", "protobuf/src/google/protobuf/io/strtod.cc", - "protobuf/src/google/protobuf/io/strtod.h", - "protobuf/src/google/protobuf/io/tokenizer.h", "protobuf/src/google/protobuf/io/zero_copy_stream.cc", - "protobuf/src/google/protobuf/io/zero_copy_stream.h", "protobuf/src/google/protobuf/io/zero_copy_stream_impl.cc", - "protobuf/src/google/protobuf/io/zero_copy_stream_impl.h", "protobuf/src/google/protobuf/io/zero_copy_stream_impl_lite.cc", - "protobuf/src/google/protobuf/io/zero_copy_stream_impl_lite.h", "protobuf/src/google/protobuf/map.cc", - "protobuf/src/google/protobuf/map.h", - "protobuf/src/google/protobuf/map_entry.h", - "protobuf/src/google/protobuf/map_entry_lite.h", - "protobuf/src/google/protobuf/map_field.h", - "protobuf/src/google/protobuf/map_field_inl.h", - "protobuf/src/google/protobuf/map_field_lite.h", - "protobuf/src/google/protobuf/map_type_handler.h", - "protobuf/src/google/protobuf/message.h", "protobuf/src/google/protobuf/message_lite.cc", - "protobuf/src/google/protobuf/message_lite.h", - "protobuf/src/google/protobuf/metadata.h", - "protobuf/src/google/protobuf/metadata_lite.h", "protobuf/src/google/protobuf/parse_context.cc", - "protobuf/src/google/protobuf/parse_context.h", - "protobuf/src/google/protobuf/port.h", - "protobuf/src/google/protobuf/port_def.inc", - "protobuf/src/google/protobuf/port_undef.inc", - "protobuf/src/google/protobuf/reflection.h", - "protobuf/src/google/protobuf/reflection_ops.h", "protobuf/src/google/protobuf/repeated_field.cc", - "protobuf/src/google/protobuf/repeated_field.h", "protobuf/src/google/protobuf/repeated_ptr_field.cc", - "protobuf/src/google/protobuf/repeated_ptr_field.h", - "protobuf/src/google/protobuf/service.h", - "protobuf/src/google/protobuf/source_context.pb.h", "protobuf/src/google/protobuf/string_member_robber.h", - "protobuf/src/google/protobuf/struct.pb.h", "protobuf/src/google/protobuf/stubs/bytestream.cc", - "protobuf/src/google/protobuf/stubs/bytestream.h", - "protobuf/src/google/protobuf/stubs/callback.h", - "protobuf/src/google/protobuf/stubs/casts.h", "protobuf/src/google/protobuf/stubs/common.cc", - "protobuf/src/google/protobuf/stubs/common.h", - "protobuf/src/google/protobuf/stubs/hash.h", "protobuf/src/google/protobuf/stubs/int128.cc", "protobuf/src/google/protobuf/stubs/int128.h", - "protobuf/src/google/protobuf/stubs/logging.h", - "protobuf/src/google/protobuf/stubs/macros.h", - "protobuf/src/google/protobuf/stubs/map_util.h", "protobuf/src/google/protobuf/stubs/mathutil.h", - "protobuf/src/google/protobuf/stubs/mutex.h", - "protobuf/src/google/protobuf/stubs/once.h", - "protobuf/src/google/protobuf/stubs/platform_macros.h", - "protobuf/src/google/protobuf/stubs/port.h", "protobuf/src/google/protobuf/stubs/status.cc", - "protobuf/src/google/protobuf/stubs/status.h", "protobuf/src/google/protobuf/stubs/status_macros.h", "protobuf/src/google/protobuf/stubs/statusor.cc", "protobuf/src/google/protobuf/stubs/statusor.h", - "protobuf/src/google/protobuf/stubs/stl_util.h", "protobuf/src/google/protobuf/stubs/stringpiece.cc", - "protobuf/src/google/protobuf/stubs/stringpiece.h", "protobuf/src/google/protobuf/stubs/stringprintf.cc", "protobuf/src/google/protobuf/stubs/stringprintf.h", "protobuf/src/google/protobuf/stubs/structurally_valid.cc", "protobuf/src/google/protobuf/stubs/strutil.cc", - "protobuf/src/google/protobuf/stubs/strutil.h", - "protobuf/src/google/protobuf/stubs/template_util.h", "protobuf/src/google/protobuf/stubs/time.cc", "protobuf/src/google/protobuf/stubs/time.h", - "protobuf/src/google/protobuf/text_format.h", - "protobuf/src/google/protobuf/timestamp.pb.h", - "protobuf/src/google/protobuf/type.pb.h", - "protobuf/src/google/protobuf/unknown_field_set.h", - "protobuf/src/google/protobuf/util/delimited_message_util.h", - "protobuf/src/google/protobuf/util/field_comparator.h", - "protobuf/src/google/protobuf/util/field_mask_util.h", - "protobuf/src/google/protobuf/util/json_util.h", - "protobuf/src/google/protobuf/util/message_differencer.h", - "protobuf/src/google/protobuf/util/time_util.h", - "protobuf/src/google/protobuf/util/type_resolver.h", - "protobuf/src/google/protobuf/util/type_resolver_util.h", - "protobuf/src/google/protobuf/wire_format.h", "protobuf/src/google/protobuf/wire_format_lite.cc", - "protobuf/src/google/protobuf/wire_format_lite.h", - "protobuf/src/google/protobuf/wrappers.pb.h", ] + sources += _protobuf_headers configs -= [ "//gn/standalone:extra_warnings" ] if (is_win) { # Protobuf has its own #define WIN32_LEAN_AND_MEAN. @@ -317,124 +323,39 @@ source_set("protobuf_full") { ] sources = [ "protobuf/src/google/protobuf/any.cc", - "protobuf/src/google/protobuf/any.h", "protobuf/src/google/protobuf/any.pb.cc", - "protobuf/src/google/protobuf/any.pb.h", "protobuf/src/google/protobuf/api.pb.cc", - "protobuf/src/google/protobuf/api.pb.h", - "protobuf/src/google/protobuf/arena.h", - "protobuf/src/google/protobuf/arena_impl.h", - "protobuf/src/google/protobuf/arenastring.h", - "protobuf/src/google/protobuf/arenaz_sampler.h", "protobuf/src/google/protobuf/compiler/importer.cc", - "protobuf/src/google/protobuf/compiler/importer.h", "protobuf/src/google/protobuf/compiler/parser.cc", - "protobuf/src/google/protobuf/compiler/parser.h", "protobuf/src/google/protobuf/descriptor.cc", - "protobuf/src/google/protobuf/descriptor.h", "protobuf/src/google/protobuf/descriptor.pb.cc", - "protobuf/src/google/protobuf/descriptor.pb.h", "protobuf/src/google/protobuf/descriptor_database.cc", - "protobuf/src/google/protobuf/descriptor_database.h", "protobuf/src/google/protobuf/duration.pb.cc", - "protobuf/src/google/protobuf/duration.pb.h", "protobuf/src/google/protobuf/dynamic_message.cc", - "protobuf/src/google/protobuf/dynamic_message.h", "protobuf/src/google/protobuf/empty.pb.cc", - "protobuf/src/google/protobuf/empty.pb.h", - "protobuf/src/google/protobuf/explicitly_constructed.h", - "protobuf/src/google/protobuf/extension_set.h", "protobuf/src/google/protobuf/extension_set_heavy.cc", - "protobuf/src/google/protobuf/extension_set_inl.h", - "protobuf/src/google/protobuf/field_access_listener.h", "protobuf/src/google/protobuf/field_mask.pb.cc", - "protobuf/src/google/protobuf/field_mask.pb.h", - "protobuf/src/google/protobuf/generated_enum_reflection.h", - "protobuf/src/google/protobuf/generated_enum_util.h", "protobuf/src/google/protobuf/generated_message_bases.cc", - "protobuf/src/google/protobuf/generated_message_bases.h", "protobuf/src/google/protobuf/generated_message_reflection.cc", - "protobuf/src/google/protobuf/generated_message_reflection.h", - "protobuf/src/google/protobuf/generated_message_tctable_decl.h", "protobuf/src/google/protobuf/generated_message_tctable_full.cc", - "protobuf/src/google/protobuf/generated_message_tctable_impl.h", - "protobuf/src/google/protobuf/generated_message_util.h", - "protobuf/src/google/protobuf/has_bits.h", - "protobuf/src/google/protobuf/implicit_weak_message.h", - "protobuf/src/google/protobuf/inlined_string_field.h", - "protobuf/src/google/protobuf/io/coded_stream.h", "protobuf/src/google/protobuf/io/gzip_stream.cc", - "protobuf/src/google/protobuf/io/io_win32.h", "protobuf/src/google/protobuf/io/printer.cc", - "protobuf/src/google/protobuf/io/printer.h", - "protobuf/src/google/protobuf/io/strtod.h", "protobuf/src/google/protobuf/io/tokenizer.cc", - "protobuf/src/google/protobuf/io/tokenizer.h", - "protobuf/src/google/protobuf/io/zero_copy_stream.h", - "protobuf/src/google/protobuf/io/zero_copy_stream_impl.h", - "protobuf/src/google/protobuf/io/zero_copy_stream_impl_lite.h", - "protobuf/src/google/protobuf/map.h", - "protobuf/src/google/protobuf/map_entry.h", - "protobuf/src/google/protobuf/map_entry_lite.h", "protobuf/src/google/protobuf/map_field.cc", - "protobuf/src/google/protobuf/map_field.h", - "protobuf/src/google/protobuf/map_field_inl.h", - "protobuf/src/google/protobuf/map_field_lite.h", - "protobuf/src/google/protobuf/map_type_handler.h", "protobuf/src/google/protobuf/message.cc", - "protobuf/src/google/protobuf/message.h", - "protobuf/src/google/protobuf/message_lite.h", - "protobuf/src/google/protobuf/metadata.h", - "protobuf/src/google/protobuf/metadata_lite.h", - "protobuf/src/google/protobuf/parse_context.h", - "protobuf/src/google/protobuf/port.h", - "protobuf/src/google/protobuf/port_def.inc", - "protobuf/src/google/protobuf/port_undef.inc", - "protobuf/src/google/protobuf/reflection.h", - "protobuf/src/google/protobuf/reflection_internal.h", "protobuf/src/google/protobuf/reflection_ops.cc", - "protobuf/src/google/protobuf/reflection_ops.h", - "protobuf/src/google/protobuf/repeated_field.h", - "protobuf/src/google/protobuf/repeated_ptr_field.h", "protobuf/src/google/protobuf/service.cc", - "protobuf/src/google/protobuf/service.h", "protobuf/src/google/protobuf/source_context.pb.cc", - "protobuf/src/google/protobuf/source_context.pb.h", "protobuf/src/google/protobuf/struct.pb.cc", - "protobuf/src/google/protobuf/struct.pb.h", - "protobuf/src/google/protobuf/stubs/bytestream.h", - "protobuf/src/google/protobuf/stubs/callback.h", - "protobuf/src/google/protobuf/stubs/casts.h", - "protobuf/src/google/protobuf/stubs/common.h", - "protobuf/src/google/protobuf/stubs/hash.h", - "protobuf/src/google/protobuf/stubs/logging.h", - "protobuf/src/google/protobuf/stubs/macros.h", - "protobuf/src/google/protobuf/stubs/map_util.h", - "protobuf/src/google/protobuf/stubs/mutex.h", - "protobuf/src/google/protobuf/stubs/once.h", - "protobuf/src/google/protobuf/stubs/platform_macros.h", - "protobuf/src/google/protobuf/stubs/port.h", - "protobuf/src/google/protobuf/stubs/status.h", - "protobuf/src/google/protobuf/stubs/stl_util.h", - "protobuf/src/google/protobuf/stubs/stringpiece.h", - "protobuf/src/google/protobuf/stubs/strutil.h", "protobuf/src/google/protobuf/stubs/substitute.cc", "protobuf/src/google/protobuf/stubs/substitute.h", - "protobuf/src/google/protobuf/stubs/template_util.h", "protobuf/src/google/protobuf/text_format.cc", - "protobuf/src/google/protobuf/text_format.h", "protobuf/src/google/protobuf/timestamp.pb.cc", - "protobuf/src/google/protobuf/timestamp.pb.h", "protobuf/src/google/protobuf/type.pb.cc", - "protobuf/src/google/protobuf/type.pb.h", "protobuf/src/google/protobuf/unknown_field_set.cc", - "protobuf/src/google/protobuf/unknown_field_set.h", "protobuf/src/google/protobuf/util/delimited_message_util.cc", - "protobuf/src/google/protobuf/util/delimited_message_util.h", "protobuf/src/google/protobuf/util/field_comparator.cc", - "protobuf/src/google/protobuf/util/field_comparator.h", "protobuf/src/google/protobuf/util/field_mask_util.cc", - "protobuf/src/google/protobuf/util/field_mask_util.h", "protobuf/src/google/protobuf/util/internal/constants.h", "protobuf/src/google/protobuf/util/internal/datapiece.cc", "protobuf/src/google/protobuf/util/internal/datapiece.h", @@ -470,20 +391,13 @@ source_set("protobuf_full") { "protobuf/src/google/protobuf/util/internal/utility.cc", "protobuf/src/google/protobuf/util/internal/utility.h", "protobuf/src/google/protobuf/util/json_util.cc", - "protobuf/src/google/protobuf/util/json_util.h", "protobuf/src/google/protobuf/util/message_differencer.cc", - "protobuf/src/google/protobuf/util/message_differencer.h", "protobuf/src/google/protobuf/util/time_util.cc", - "protobuf/src/google/protobuf/util/time_util.h", - "protobuf/src/google/protobuf/util/type_resolver.h", "protobuf/src/google/protobuf/util/type_resolver_util.cc", - "protobuf/src/google/protobuf/util/type_resolver_util.h", "protobuf/src/google/protobuf/wire_format.cc", - "protobuf/src/google/protobuf/wire_format.h", - "protobuf/src/google/protobuf/wire_format_lite.h", "protobuf/src/google/protobuf/wrappers.pb.cc", - "protobuf/src/google/protobuf/wrappers.pb.h", ] + sources += _protobuf_headers configs -= [ "//gn/standalone:extra_warnings" ] if (is_win) { # Protobuf has its own #define WIN32_LEAN_AND_MEAN. @@ -507,39 +421,40 @@ source_set("protoc_lib") { "protobuf/src/google/protobuf/compiler/code_generator.h", "protobuf/src/google/protobuf/compiler/command_line_interface.cc", "protobuf/src/google/protobuf/compiler/command_line_interface.h", - "protobuf/src/google/protobuf/compiler/cpp/cpp_enum.cc", - "protobuf/src/google/protobuf/compiler/cpp/cpp_enum.h", - "protobuf/src/google/protobuf/compiler/cpp/cpp_enum_field.cc", - "protobuf/src/google/protobuf/compiler/cpp/cpp_enum_field.h", - "protobuf/src/google/protobuf/compiler/cpp/cpp_extension.cc", - "protobuf/src/google/protobuf/compiler/cpp/cpp_extension.h", - "protobuf/src/google/protobuf/compiler/cpp/cpp_field.cc", - "protobuf/src/google/protobuf/compiler/cpp/cpp_field.h", - "protobuf/src/google/protobuf/compiler/cpp/cpp_file.cc", - "protobuf/src/google/protobuf/compiler/cpp/cpp_file.h", - "protobuf/src/google/protobuf/compiler/cpp/cpp_generator.cc", "protobuf/src/google/protobuf/compiler/cpp/cpp_generator.h", - "protobuf/src/google/protobuf/compiler/cpp/cpp_helpers.cc", - "protobuf/src/google/protobuf/compiler/cpp/cpp_helpers.h", - "protobuf/src/google/protobuf/compiler/cpp/cpp_map_field.cc", - "protobuf/src/google/protobuf/compiler/cpp/cpp_map_field.h", - "protobuf/src/google/protobuf/compiler/cpp/cpp_message.cc", - "protobuf/src/google/protobuf/compiler/cpp/cpp_message.h", - "protobuf/src/google/protobuf/compiler/cpp/cpp_message_field.cc", - "protobuf/src/google/protobuf/compiler/cpp/cpp_message_field.h", - "protobuf/src/google/protobuf/compiler/cpp/cpp_message_layout_helper.h", - "protobuf/src/google/protobuf/compiler/cpp/cpp_names.h", - "protobuf/src/google/protobuf/compiler/cpp/cpp_options.h", - "protobuf/src/google/protobuf/compiler/cpp/cpp_padding_optimizer.cc", - "protobuf/src/google/protobuf/compiler/cpp/cpp_padding_optimizer.h", - "protobuf/src/google/protobuf/compiler/cpp/cpp_parse_function_generator.cc", - "protobuf/src/google/protobuf/compiler/cpp/cpp_parse_function_generator.h", - "protobuf/src/google/protobuf/compiler/cpp/cpp_primitive_field.cc", - "protobuf/src/google/protobuf/compiler/cpp/cpp_primitive_field.h", - "protobuf/src/google/protobuf/compiler/cpp/cpp_service.cc", - "protobuf/src/google/protobuf/compiler/cpp/cpp_service.h", - "protobuf/src/google/protobuf/compiler/cpp/cpp_string_field.cc", - "protobuf/src/google/protobuf/compiler/cpp/cpp_string_field.h", + "protobuf/src/google/protobuf/compiler/cpp/enum.cc", + "protobuf/src/google/protobuf/compiler/cpp/enum.h", + "protobuf/src/google/protobuf/compiler/cpp/enum_field.cc", + "protobuf/src/google/protobuf/compiler/cpp/enum_field.h", + "protobuf/src/google/protobuf/compiler/cpp/extension.cc", + "protobuf/src/google/protobuf/compiler/cpp/extension.h", + "protobuf/src/google/protobuf/compiler/cpp/field.cc", + "protobuf/src/google/protobuf/compiler/cpp/field.h", + "protobuf/src/google/protobuf/compiler/cpp/file.cc", + "protobuf/src/google/protobuf/compiler/cpp/file.h", + "protobuf/src/google/protobuf/compiler/cpp/generator.cc", + "protobuf/src/google/protobuf/compiler/cpp/generator.h", + "protobuf/src/google/protobuf/compiler/cpp/helpers.cc", + "protobuf/src/google/protobuf/compiler/cpp/helpers.h", + "protobuf/src/google/protobuf/compiler/cpp/map_field.cc", + "protobuf/src/google/protobuf/compiler/cpp/map_field.h", + "protobuf/src/google/protobuf/compiler/cpp/message.cc", + "protobuf/src/google/protobuf/compiler/cpp/message.h", + "protobuf/src/google/protobuf/compiler/cpp/message_field.cc", + "protobuf/src/google/protobuf/compiler/cpp/message_field.h", + "protobuf/src/google/protobuf/compiler/cpp/message_layout_helper.h", + "protobuf/src/google/protobuf/compiler/cpp/names.h", + "protobuf/src/google/protobuf/compiler/cpp/options.h", + "protobuf/src/google/protobuf/compiler/cpp/padding_optimizer.cc", + "protobuf/src/google/protobuf/compiler/cpp/padding_optimizer.h", + "protobuf/src/google/protobuf/compiler/cpp/parse_function_generator.cc", + "protobuf/src/google/protobuf/compiler/cpp/parse_function_generator.h", + "protobuf/src/google/protobuf/compiler/cpp/primitive_field.cc", + "protobuf/src/google/protobuf/compiler/cpp/primitive_field.h", + "protobuf/src/google/protobuf/compiler/cpp/service.cc", + "protobuf/src/google/protobuf/compiler/cpp/service.h", + "protobuf/src/google/protobuf/compiler/cpp/string_field.cc", + "protobuf/src/google/protobuf/compiler/cpp/string_field.h", "protobuf/src/google/protobuf/compiler/csharp/csharp_doc_comment.cc", "protobuf/src/google/protobuf/compiler/csharp/csharp_doc_comment.h", "protobuf/src/google/protobuf/compiler/csharp/csharp_enum.cc", @@ -574,70 +489,67 @@ source_set("protoc_lib") { "protobuf/src/google/protobuf/compiler/csharp/csharp_source_generator_base.h", "protobuf/src/google/protobuf/compiler/csharp/csharp_wrapper_field.cc", "protobuf/src/google/protobuf/compiler/csharp/csharp_wrapper_field.h", - "protobuf/src/google/protobuf/compiler/java/java_context.cc", - "protobuf/src/google/protobuf/compiler/java/java_context.h", - "protobuf/src/google/protobuf/compiler/java/java_doc_comment.cc", - "protobuf/src/google/protobuf/compiler/java/java_doc_comment.h", - "protobuf/src/google/protobuf/compiler/java/java_enum.cc", - "protobuf/src/google/protobuf/compiler/java/java_enum.h", - "protobuf/src/google/protobuf/compiler/java/java_enum_field.cc", - "protobuf/src/google/protobuf/compiler/java/java_enum_field.h", - "protobuf/src/google/protobuf/compiler/java/java_enum_field_lite.cc", - "protobuf/src/google/protobuf/compiler/java/java_enum_field_lite.h", - "protobuf/src/google/protobuf/compiler/java/java_enum_lite.cc", - "protobuf/src/google/protobuf/compiler/java/java_enum_lite.h", - "protobuf/src/google/protobuf/compiler/java/java_extension.cc", - "protobuf/src/google/protobuf/compiler/java/java_extension.h", - "protobuf/src/google/protobuf/compiler/java/java_extension_lite.cc", - "protobuf/src/google/protobuf/compiler/java/java_extension_lite.h", - "protobuf/src/google/protobuf/compiler/java/java_field.cc", - "protobuf/src/google/protobuf/compiler/java/java_field.h", - "protobuf/src/google/protobuf/compiler/java/java_file.cc", - "protobuf/src/google/protobuf/compiler/java/java_file.h", - "protobuf/src/google/protobuf/compiler/java/java_generator.cc", + "protobuf/src/google/protobuf/compiler/java/context.cc", + "protobuf/src/google/protobuf/compiler/java/context.h", + "protobuf/src/google/protobuf/compiler/java/doc_comment.cc", + "protobuf/src/google/protobuf/compiler/java/doc_comment.h", + "protobuf/src/google/protobuf/compiler/java/enum.cc", + "protobuf/src/google/protobuf/compiler/java/enum.h", + "protobuf/src/google/protobuf/compiler/java/enum_field.cc", + "protobuf/src/google/protobuf/compiler/java/enum_field.h", + "protobuf/src/google/protobuf/compiler/java/enum_field_lite.cc", + "protobuf/src/google/protobuf/compiler/java/enum_field_lite.h", + "protobuf/src/google/protobuf/compiler/java/enum_lite.cc", + "protobuf/src/google/protobuf/compiler/java/enum_lite.h", + "protobuf/src/google/protobuf/compiler/java/extension.cc", + "protobuf/src/google/protobuf/compiler/java/extension.h", + "protobuf/src/google/protobuf/compiler/java/extension_lite.cc", + "protobuf/src/google/protobuf/compiler/java/extension_lite.h", + "protobuf/src/google/protobuf/compiler/java/field.cc", + "protobuf/src/google/protobuf/compiler/java/field.h", + "protobuf/src/google/protobuf/compiler/java/file.cc", + "protobuf/src/google/protobuf/compiler/java/file.h", + "protobuf/src/google/protobuf/compiler/java/generator.cc", + "protobuf/src/google/protobuf/compiler/java/generator.h", + "protobuf/src/google/protobuf/compiler/java/generator_factory.cc", + "protobuf/src/google/protobuf/compiler/java/generator_factory.h", + "protobuf/src/google/protobuf/compiler/java/helpers.cc", + "protobuf/src/google/protobuf/compiler/java/helpers.h", "protobuf/src/google/protobuf/compiler/java/java_generator.h", - "protobuf/src/google/protobuf/compiler/java/java_generator_factory.cc", - "protobuf/src/google/protobuf/compiler/java/java_generator_factory.h", - "protobuf/src/google/protobuf/compiler/java/java_helpers.cc", - "protobuf/src/google/protobuf/compiler/java/java_helpers.h", - "protobuf/src/google/protobuf/compiler/java/java_kotlin_generator.cc", - "protobuf/src/google/protobuf/compiler/java/java_kotlin_generator.h", - "protobuf/src/google/protobuf/compiler/java/java_map_field.cc", - "protobuf/src/google/protobuf/compiler/java/java_map_field.h", - "protobuf/src/google/protobuf/compiler/java/java_map_field_lite.cc", - "protobuf/src/google/protobuf/compiler/java/java_map_field_lite.h", - "protobuf/src/google/protobuf/compiler/java/java_message.cc", - "protobuf/src/google/protobuf/compiler/java/java_message.h", - "protobuf/src/google/protobuf/compiler/java/java_message_builder.cc", - "protobuf/src/google/protobuf/compiler/java/java_message_builder.h", - "protobuf/src/google/protobuf/compiler/java/java_message_builder_lite.cc", - "protobuf/src/google/protobuf/compiler/java/java_message_builder_lite.h", - "protobuf/src/google/protobuf/compiler/java/java_message_field.cc", - "protobuf/src/google/protobuf/compiler/java/java_message_field.h", - "protobuf/src/google/protobuf/compiler/java/java_message_field_lite.cc", - "protobuf/src/google/protobuf/compiler/java/java_message_field_lite.h", - "protobuf/src/google/protobuf/compiler/java/java_message_lite.cc", - "protobuf/src/google/protobuf/compiler/java/java_message_lite.h", - "protobuf/src/google/protobuf/compiler/java/java_name_resolver.cc", - "protobuf/src/google/protobuf/compiler/java/java_name_resolver.h", - "protobuf/src/google/protobuf/compiler/java/java_names.h", - "protobuf/src/google/protobuf/compiler/java/java_options.h", - "protobuf/src/google/protobuf/compiler/java/java_primitive_field.cc", - "protobuf/src/google/protobuf/compiler/java/java_primitive_field.h", - "protobuf/src/google/protobuf/compiler/java/java_primitive_field_lite.cc", - "protobuf/src/google/protobuf/compiler/java/java_primitive_field_lite.h", - "protobuf/src/google/protobuf/compiler/java/java_service.cc", - "protobuf/src/google/protobuf/compiler/java/java_service.h", - "protobuf/src/google/protobuf/compiler/java/java_shared_code_generator.cc", - "protobuf/src/google/protobuf/compiler/java/java_shared_code_generator.h", - "protobuf/src/google/protobuf/compiler/java/java_string_field.cc", - "protobuf/src/google/protobuf/compiler/java/java_string_field.h", - "protobuf/src/google/protobuf/compiler/java/java_string_field_lite.cc", - "protobuf/src/google/protobuf/compiler/java/java_string_field_lite.h", - "protobuf/src/google/protobuf/compiler/js/js_generator.cc", - "protobuf/src/google/protobuf/compiler/js/js_generator.h", - "protobuf/src/google/protobuf/compiler/js/well_known_types_embed.cc", - "protobuf/src/google/protobuf/compiler/js/well_known_types_embed.h", + "protobuf/src/google/protobuf/compiler/java/kotlin_generator.cc", + "protobuf/src/google/protobuf/compiler/java/kotlin_generator.h", + "protobuf/src/google/protobuf/compiler/java/map_field.cc", + "protobuf/src/google/protobuf/compiler/java/map_field.h", + "protobuf/src/google/protobuf/compiler/java/map_field_lite.cc", + "protobuf/src/google/protobuf/compiler/java/map_field_lite.h", + "protobuf/src/google/protobuf/compiler/java/message.cc", + "protobuf/src/google/protobuf/compiler/java/message.h", + "protobuf/src/google/protobuf/compiler/java/message_builder.cc", + "protobuf/src/google/protobuf/compiler/java/message_builder.h", + "protobuf/src/google/protobuf/compiler/java/message_builder_lite.cc", + "protobuf/src/google/protobuf/compiler/java/message_builder_lite.h", + "protobuf/src/google/protobuf/compiler/java/message_field.cc", + "protobuf/src/google/protobuf/compiler/java/message_field.h", + "protobuf/src/google/protobuf/compiler/java/message_field_lite.cc", + "protobuf/src/google/protobuf/compiler/java/message_field_lite.h", + "protobuf/src/google/protobuf/compiler/java/message_lite.cc", + "protobuf/src/google/protobuf/compiler/java/message_lite.h", + "protobuf/src/google/protobuf/compiler/java/name_resolver.cc", + "protobuf/src/google/protobuf/compiler/java/name_resolver.h", + "protobuf/src/google/protobuf/compiler/java/names.h", + "protobuf/src/google/protobuf/compiler/java/options.h", + "protobuf/src/google/protobuf/compiler/java/primitive_field.cc", + "protobuf/src/google/protobuf/compiler/java/primitive_field.h", + "protobuf/src/google/protobuf/compiler/java/primitive_field_lite.cc", + "protobuf/src/google/protobuf/compiler/java/primitive_field_lite.h", + "protobuf/src/google/protobuf/compiler/java/service.cc", + "protobuf/src/google/protobuf/compiler/java/service.h", + "protobuf/src/google/protobuf/compiler/java/shared_code_generator.cc", + "protobuf/src/google/protobuf/compiler/java/shared_code_generator.h", + "protobuf/src/google/protobuf/compiler/java/string_field.cc", + "protobuf/src/google/protobuf/compiler/java/string_field.h", + "protobuf/src/google/protobuf/compiler/java/string_field_lite.cc", + "protobuf/src/google/protobuf/compiler/java/string_field_lite.h", "protobuf/src/google/protobuf/compiler/objectivec/objectivec_enum.cc", "protobuf/src/google/protobuf/compiler/objectivec/objectivec_enum.h", "protobuf/src/google/protobuf/compiler/objectivec/objectivec_enum_field.cc", @@ -669,12 +581,13 @@ source_set("protoc_lib") { "protobuf/src/google/protobuf/compiler/plugin.h", "protobuf/src/google/protobuf/compiler/plugin.pb.cc", "protobuf/src/google/protobuf/compiler/plugin.pb.h", - "protobuf/src/google/protobuf/compiler/python/python_generator.cc", + "protobuf/src/google/protobuf/compiler/python/generator.cc", + "protobuf/src/google/protobuf/compiler/python/generator.h", + "protobuf/src/google/protobuf/compiler/python/helpers.cc", + "protobuf/src/google/protobuf/compiler/python/helpers.h", + "protobuf/src/google/protobuf/compiler/python/pyi_generator.cc", + "protobuf/src/google/protobuf/compiler/python/pyi_generator.h", "protobuf/src/google/protobuf/compiler/python/python_generator.h", - "protobuf/src/google/protobuf/compiler/python/python_helpers.cc", - "protobuf/src/google/protobuf/compiler/python/python_helpers.h", - "protobuf/src/google/protobuf/compiler/python/python_pyi_generator.cc", - "protobuf/src/google/protobuf/compiler/python/python_pyi_generator.h", "protobuf/src/google/protobuf/compiler/ruby/ruby_generator.cc", "protobuf/src/google/protobuf/compiler/ruby/ruby_generator.h", "protobuf/src/google/protobuf/compiler/scc.h", @@ -1579,12 +1492,18 @@ if (enable_perfetto_grpc) { config("grpc_absl_config") { visibility = _buildtools_visibility include_dirs = [ "grpc/src/third_party/abseil-cpp" ] - cflags = [ "-Wno-deprecated-builtins" ] + cflags = [ + "-Wno-deprecated-builtins", + "-Wno-deprecated-pragma", + ] } config("grpc_boringssl_config") { visibility = _buildtools_visibility include_dirs = [ "grpc/src/third_party/boringssl-with-bazel/src/include" ] + if (is_gcc) { + cflags = [ "-Wno-stringop-overflow" ] + } } config("grpc_upb_config") { @@ -1604,15 +1523,25 @@ if (enable_perfetto_grpc) { config("grpc_internal_config") { visibility = _buildtools_visibility include_dirs = [ - "grpc/src/include", + "grpc/protobuf_include", "grpc/src", + "grpc/src/include", + "grpc/src/src/core/ext/upb-gen", + "grpc/src/src/core/ext/upbdefs-gen", + "grpc/src/third_party/abseil-cpp", "grpc/src/third_party/address_sorting/include", "grpc/src/third_party/re2", - "grpc/src/src/core/ext/upb-generated", - "grpc/src/src/core/ext/upbdefs-generated", + "grpc/src/third_party/upb", + "grpc/src/third_party/utf8_range", "grpc/src/third_party/xxhash", ] cflags = [ "-DGRPC_ARES=0" ] + if (is_gcc) { + cflags += [ + "-Wno-attributes", + "-Wno-return-type", + ] + } } config("grpc_gen_config") { @@ -1635,4 +1564,12 @@ if (enable_perfetto_grpc) { ] } } + + config("grpc_public_config") { + visibility = _buildtools_visibility + cflags = [ + perfetto_isystem_cflag, + rebase_path("grpc/src/include", root_build_dir), + ] + } } diff --git a/buildtools/grpc/BUILD.gn b/buildtools/grpc/BUILD.gn index a13fb50dcc..fa2a1c825e 100644 --- a/buildtools/grpc/BUILD.gn +++ b/buildtools/grpc/BUILD.gn @@ -19,6 +19,7 @@ source_set("absl_algorithm_algorithm") { public_deps = [ ":absl_base_config" ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_algorithm_container") { @@ -26,10 +27,12 @@ source_set("absl_algorithm_container") { public_deps = [ ":absl_algorithm_algorithm", ":absl_base_core_headers", + ":absl_base_nullability", ":absl_meta_type_traits", ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_base_atomic_hook") { @@ -40,6 +43,7 @@ source_set("absl_base_atomic_hook") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_base_base") { @@ -68,12 +72,14 @@ source_set("absl_base_base") { ":absl_base_cycleclock_internal", ":absl_base_dynamic_annotations", ":absl_base_log_severity", + ":absl_base_nullability", ":absl_base_raw_logging_internal", ":absl_base_spinlock_wait", ":absl_meta_type_traits", ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_base_base_internal") { @@ -90,6 +96,7 @@ source_set("absl_base_base_internal") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_base_config") { @@ -101,13 +108,13 @@ source_set("absl_base_config") { public_deps = [] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_base_core_headers") { sources = [ "src/third_party/abseil-cpp/absl/base/attributes.h", "src/third_party/abseil-cpp/absl/base/const_init.h", - "src/third_party/abseil-cpp/absl/base/internal/thread_annotations.h", "src/third_party/abseil-cpp/absl/base/macros.h", "src/third_party/abseil-cpp/absl/base/optimization.h", "src/third_party/abseil-cpp/absl/base/port.h", @@ -116,6 +123,7 @@ source_set("absl_base_core_headers") { public_deps = [ ":absl_base_config" ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_base_cycleclock_internal") { @@ -129,6 +137,7 @@ source_set("absl_base_cycleclock_internal") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_base_dynamic_annotations") { @@ -142,6 +151,7 @@ source_set("absl_base_dynamic_annotations") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_base_endian") { @@ -153,9 +163,11 @@ source_set("absl_base_endian") { ":absl_base_base", ":absl_base_config", ":absl_base_core_headers", + ":absl_base_nullability", ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_base_errno_saver") { @@ -163,6 +175,7 @@ source_set("absl_base_errno_saver") { public_deps = [ ":absl_base_config" ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_base_fast_type_id") { @@ -170,6 +183,7 @@ source_set("absl_base_fast_type_id") { public_deps = [ ":absl_base_config" ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_base_log_severity") { @@ -183,6 +197,7 @@ source_set("absl_base_log_severity") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_base_malloc_internal") { @@ -201,13 +216,40 @@ source_set("absl_base_malloc_internal") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } -source_set("absl_base_prefetch") { - sources = [ "src/third_party/abseil-cpp/absl/base/internal/prefetch.h" ] +source_set("absl_base_no_destructor") { + sources = [ "src/third_party/abseil-cpp/absl/base/no_destructor.h" ] public_deps = [ ":absl_base_config" ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true +} + +source_set("absl_base_nullability") { + sources = [ + "src/third_party/abseil-cpp/absl/base/internal/nullability_impl.h", + "src/third_party/abseil-cpp/absl/base/nullability.h", + ] + public_deps = [ + ":absl_base_core_headers", + ":absl_meta_type_traits", + ] + public_configs = [ "..:grpc_absl_config" ] + configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true +} + +source_set("absl_base_prefetch") { + sources = [ "src/third_party/abseil-cpp/absl/base/prefetch.h" ] + public_deps = [ + ":absl_base_config", + ":absl_base_core_headers", + ] + public_configs = [ "..:grpc_absl_config" ] + configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_base_pretty_function") { @@ -216,6 +258,7 @@ source_set("absl_base_pretty_function") { public_deps = [] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_base_raw_logging_internal") { @@ -232,6 +275,7 @@ source_set("absl_base_raw_logging_internal") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_base_spinlock_wait") { @@ -250,6 +294,7 @@ source_set("absl_base_spinlock_wait") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_base_strerror") { @@ -264,6 +309,7 @@ source_set("absl_base_strerror") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_base_throw_delegate") { @@ -277,6 +323,7 @@ source_set("absl_base_throw_delegate") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_cleanup_cleanup") { @@ -288,6 +335,7 @@ source_set("absl_cleanup_cleanup") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_cleanup_cleanup_internal") { @@ -299,6 +347,7 @@ source_set("absl_cleanup_cleanup_internal") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_container_btree") { @@ -326,6 +375,7 @@ source_set("absl_container_btree") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_container_common") { @@ -336,6 +386,7 @@ source_set("absl_container_common") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_container_common_policy_traits") { @@ -345,6 +396,7 @@ source_set("absl_container_common_policy_traits") { public_deps = [ ":absl_meta_type_traits" ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_container_compressed_tuple") { @@ -354,6 +406,7 @@ source_set("absl_container_compressed_tuple") { public_deps = [ ":absl_utility_utility" ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_container_container_memory") { @@ -368,6 +421,7 @@ source_set("absl_container_container_memory") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_container_fixed_array") { @@ -383,6 +437,7 @@ source_set("absl_container_fixed_array") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_container_flat_hash_map") { @@ -397,6 +452,7 @@ source_set("absl_container_flat_hash_map") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_container_flat_hash_set") { @@ -411,6 +467,7 @@ source_set("absl_container_flat_hash_set") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_container_hash_function_defaults") { @@ -423,6 +480,7 @@ source_set("absl_container_hash_function_defaults") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_container_hash_policy_traits") { @@ -435,6 +493,7 @@ source_set("absl_container_hash_policy_traits") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_container_hashtable_debug") { @@ -443,6 +502,7 @@ source_set("absl_container_hashtable_debug") { public_deps = [ ":absl_container_hashtable_debug_hooks" ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_container_hashtable_debug_hooks") { @@ -450,6 +510,7 @@ source_set("absl_container_hashtable_debug_hooks") { public_deps = [ ":absl_base_config" ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_container_hashtablez_sampler") { @@ -462,15 +523,18 @@ source_set("absl_container_hashtablez_sampler") { ":absl_base_base", ":absl_base_config", ":absl_base_core_headers", + ":absl_base_raw_logging_internal", ":absl_debugging_stacktrace", ":absl_memory_memory", ":absl_profiling_exponential_biased", ":absl_profiling_sample_recorder", ":absl_synchronization_synchronization", + ":absl_time_time", ":absl_utility_utility", ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_container_inlined_vector") { @@ -485,12 +549,14 @@ source_set("absl_container_inlined_vector") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_container_inlined_vector_internal") { sources = [ "src/third_party/abseil-cpp/absl/container/internal/inlined_vector.h" ] public_deps = [ + ":absl_base_config", ":absl_base_core_headers", ":absl_container_compressed_tuple", ":absl_memory_memory", @@ -499,6 +565,7 @@ source_set("absl_container_inlined_vector_internal") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_container_layout") { @@ -506,6 +573,7 @@ source_set("absl_container_layout") { public_deps = [ ":absl_base_config", ":absl_base_core_headers", + ":absl_debugging_demangle_internal", ":absl_meta_type_traits", ":absl_strings_strings", ":absl_types_span", @@ -513,6 +581,7 @@ source_set("absl_container_layout") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_container_node_hash_map") { @@ -528,6 +597,7 @@ source_set("absl_container_node_hash_map") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_container_node_hash_set") { @@ -542,6 +612,7 @@ source_set("absl_container_node_hash_set") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_container_node_slot_policy") { @@ -551,18 +622,22 @@ source_set("absl_container_node_slot_policy") { public_deps = [ ":absl_base_config" ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_container_raw_hash_map") { sources = [ "src/third_party/abseil-cpp/absl/container/internal/raw_hash_map.h" ] public_deps = [ + ":absl_base_config", + ":absl_base_core_headers", ":absl_base_throw_delegate", ":absl_container_container_memory", ":absl_container_raw_hash_set", ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_container_raw_hash_set") { @@ -573,6 +648,7 @@ source_set("absl_container_raw_hash_set") { public_deps = [ ":absl_base_config", ":absl_base_core_headers", + ":absl_base_dynamic_annotations", ":absl_base_endian", ":absl_base_prefetch", ":absl_base_raw_logging_internal", @@ -582,6 +658,7 @@ source_set("absl_container_raw_hash_set") { ":absl_container_hash_policy_traits", ":absl_container_hashtable_debug_hooks", ":absl_container_hashtablez_sampler", + ":absl_hash_hash", ":absl_memory_memory", ":absl_meta_type_traits", ":absl_numeric_bits", @@ -589,6 +666,7 @@ source_set("absl_container_raw_hash_set") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_crc_cpu_detect") { @@ -602,6 +680,7 @@ source_set("absl_crc_cpu_detect") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_crc_crc32c") { @@ -612,22 +691,23 @@ source_set("absl_crc_crc32c") { "src/third_party/abseil-cpp/absl/crc/internal/crc32c_inline.h", "src/third_party/abseil-cpp/absl/crc/internal/crc_memcpy.h", "src/third_party/abseil-cpp/absl/crc/internal/crc_memcpy_fallback.cc", - "src/third_party/abseil-cpp/absl/crc/internal/crc_memcpy_x86_64.cc", + "src/third_party/abseil-cpp/absl/crc/internal/crc_memcpy_x86_arm_combined.cc", "src/third_party/abseil-cpp/absl/crc/internal/crc_non_temporal_memcpy.cc", ] public_deps = [ ":absl_base_config", ":absl_base_core_headers", - ":absl_base_dynamic_annotations", ":absl_base_endian", ":absl_base_prefetch", ":absl_crc_cpu_detect", ":absl_crc_crc_internal", ":absl_crc_non_temporal_memcpy", + ":absl_strings_str_format", ":absl_strings_strings", ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_crc_crc_cord_state") { @@ -643,6 +723,7 @@ source_set("absl_crc_crc_cord_state") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_crc_crc_internal") { @@ -654,10 +735,8 @@ source_set("absl_crc_crc_internal") { "src/third_party/abseil-cpp/absl/crc/internal/crc_x86_arm_combined.cc", ] public_deps = [ - ":absl_base_base", ":absl_base_config", ":absl_base_core_headers", - ":absl_base_dynamic_annotations", ":absl_base_endian", ":absl_base_prefetch", ":absl_base_raw_logging_internal", @@ -667,6 +746,7 @@ source_set("absl_crc_crc_internal") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_crc_non_temporal_arm_intrinsics") { @@ -674,6 +754,7 @@ source_set("absl_crc_non_temporal_arm_intrinsics") { public_deps = [ ":absl_base_config" ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_crc_non_temporal_memcpy") { @@ -686,6 +767,7 @@ source_set("absl_crc_non_temporal_memcpy") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_debugging_debugging_internal") { @@ -706,6 +788,7 @@ source_set("absl_debugging_debugging_internal") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_debugging_demangle_internal") { @@ -720,6 +803,7 @@ source_set("absl_debugging_demangle_internal") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_debugging_examine_stack") { @@ -736,6 +820,7 @@ source_set("absl_debugging_examine_stack") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_debugging_failure_signal_handler") { @@ -753,6 +838,7 @@ source_set("absl_debugging_failure_signal_handler") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_debugging_leak_check") { @@ -766,6 +852,7 @@ source_set("absl_debugging_leak_check") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_debugging_stacktrace") { @@ -786,11 +873,13 @@ source_set("absl_debugging_stacktrace") { public_deps = [ ":absl_base_config", ":absl_base_core_headers", + ":absl_base_dynamic_annotations", ":absl_base_raw_logging_internal", ":absl_debugging_debugging_internal", ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_debugging_symbolize") { @@ -817,6 +906,7 @@ source_set("absl_debugging_symbolize") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_flags_commandlineflag") { @@ -833,6 +923,7 @@ source_set("absl_flags_commandlineflag") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_flags_commandlineflag_internal") { @@ -846,6 +937,7 @@ source_set("absl_flags_commandlineflag_internal") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_flags_config") { @@ -864,14 +956,13 @@ source_set("absl_flags_config") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_flags_flag") { sources = [ "src/third_party/abseil-cpp/absl/flags/declare.h", - "src/third_party/abseil-cpp/absl/flags/flag.cc", "src/third_party/abseil-cpp/absl/flags/flag.h", - "src/third_party/abseil-cpp/absl/flags/internal/flag_msvc.inc", ] public_deps = [ ":absl_base_base", @@ -884,6 +975,7 @@ source_set("absl_flags_flag") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_flags_flag_internal") { @@ -910,6 +1002,7 @@ source_set("absl_flags_flag_internal") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_flags_marshalling") { @@ -921,12 +1014,14 @@ source_set("absl_flags_marshalling") { ":absl_base_config", ":absl_base_core_headers", ":absl_base_log_severity", + ":absl_numeric_int128", ":absl_strings_str_format", ":absl_strings_strings", ":absl_types_optional", ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_flags_parse") { @@ -954,6 +1049,7 @@ source_set("absl_flags_parse") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_flags_path_util") { @@ -964,6 +1060,7 @@ source_set("absl_flags_path_util") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_flags_private_handle_accessor") { @@ -979,6 +1076,7 @@ source_set("absl_flags_private_handle_accessor") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_flags_program_name") { @@ -995,6 +1093,7 @@ source_set("absl_flags_program_name") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_flags_reflection") { @@ -1006,6 +1105,7 @@ source_set("absl_flags_reflection") { public_deps = [ ":absl_base_config", ":absl_base_core_headers", + ":absl_base_no_destructor", ":absl_container_flat_hash_map", ":absl_flags_commandlineflag", ":absl_flags_commandlineflag_internal", @@ -1016,6 +1116,7 @@ source_set("absl_flags_reflection") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_flags_usage") { @@ -1026,12 +1127,14 @@ source_set("absl_flags_usage") { public_deps = [ ":absl_base_config", ":absl_base_core_headers", + ":absl_base_raw_logging_internal", ":absl_flags_usage_internal", ":absl_strings_strings", ":absl_synchronization_synchronization", ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_flags_usage_internal") { @@ -1042,7 +1145,6 @@ source_set("absl_flags_usage_internal") { public_deps = [ ":absl_base_config", ":absl_base_core_headers", - ":absl_container_flat_hash_map", ":absl_flags_commandlineflag", ":absl_flags_config", ":absl_flags_flag", @@ -1052,9 +1154,11 @@ source_set("absl_flags_usage_internal") { ":absl_flags_program_name", ":absl_flags_reflection", ":absl_strings_strings", + ":absl_synchronization_synchronization", ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_functional_any_invocable") { @@ -1071,6 +1175,7 @@ source_set("absl_functional_any_invocable") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_functional_bind_front") { @@ -1086,6 +1191,7 @@ source_set("absl_functional_bind_front") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_functional_function_ref") { @@ -1096,10 +1202,23 @@ source_set("absl_functional_function_ref") { public_deps = [ ":absl_base_base_internal", ":absl_base_core_headers", + ":absl_functional_any_invocable", ":absl_meta_type_traits", ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true +} + +source_set("absl_functional_overload") { + sources = [ "src/third_party/abseil-cpp/absl/functional/overload.h" ] + public_deps = [ + ":absl_base_config", + ":absl_meta_type_traits", + ] + public_configs = [ "..:grpc_absl_config" ] + configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_hash_city") { @@ -1114,6 +1233,7 @@ source_set("absl_hash_city") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_hash_hash") { @@ -1140,6 +1260,7 @@ source_set("absl_hash_hash") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_hash_low_level_hash") { @@ -1150,10 +1271,12 @@ source_set("absl_hash_low_level_hash") { public_deps = [ ":absl_base_config", ":absl_base_endian", + ":absl_base_prefetch", ":absl_numeric_int128", ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_log_internal_append_truncated") { @@ -1166,6 +1289,7 @@ source_set("absl_log_internal_append_truncated") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_log_internal_check_impl") { @@ -1179,6 +1303,7 @@ source_set("absl_log_internal_check_impl") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_log_internal_check_op") { @@ -1196,6 +1321,7 @@ source_set("absl_log_internal_check_op") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_log_internal_conditions") { @@ -1211,6 +1337,7 @@ source_set("absl_log_internal_conditions") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_log_internal_config") { @@ -1221,6 +1348,7 @@ source_set("absl_log_internal_config") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_log_internal_flags") { @@ -1228,6 +1356,21 @@ source_set("absl_log_internal_flags") { public_deps = [ ":absl_flags_flag" ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true +} + +source_set("absl_log_internal_fnmatch") { + sources = [ + "src/third_party/abseil-cpp/absl/log/internal/fnmatch.cc", + "src/third_party/abseil-cpp/absl/log/internal/fnmatch.h", + ] + public_deps = [ + ":absl_base_config", + ":absl_strings_strings", + ] + public_configs = [ "..:grpc_absl_config" ] + configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_log_internal_format") { @@ -1249,6 +1392,7 @@ source_set("absl_log_internal_format") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_log_internal_globals") { @@ -1266,17 +1410,20 @@ source_set("absl_log_internal_globals") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_log_internal_log_impl") { sources = [ "src/third_party/abseil-cpp/absl/log/internal/log_impl.h" ] public_deps = [ + ":absl_log_absl_vlog_is_on", ":absl_log_internal_conditions", ":absl_log_internal_log_message", ":absl_log_internal_strip", ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_log_internal_log_message") { @@ -1311,6 +1458,7 @@ source_set("absl_log_internal_log_message") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_log_internal_log_sink_set") { @@ -1323,6 +1471,7 @@ source_set("absl_log_internal_log_sink_set") { ":absl_base_config", ":absl_base_core_headers", ":absl_base_log_severity", + ":absl_base_no_destructor", ":absl_base_raw_logging_internal", ":absl_cleanup_cleanup", ":absl_log_globals", @@ -1336,6 +1485,7 @@ source_set("absl_log_internal_log_sink_set") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_log_internal_nullguard") { @@ -1349,6 +1499,7 @@ source_set("absl_log_internal_nullguard") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_log_internal_nullstream") { @@ -1361,6 +1512,7 @@ source_set("absl_log_internal_nullstream") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_log_internal_proto") { @@ -1377,6 +1529,7 @@ source_set("absl_log_internal_proto") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_log_internal_strip") { @@ -1388,6 +1541,7 @@ source_set("absl_log_internal_strip") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_log_internal_structured") { @@ -1399,6 +1553,28 @@ source_set("absl_log_internal_structured") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true +} + +source_set("absl_log_internal_vlog_config") { + sources = [ + "src/third_party/abseil-cpp/absl/log/internal/vlog_config.cc", + "src/third_party/abseil-cpp/absl/log/internal/vlog_config.h", + ] + public_deps = [ + ":absl_base_base", + ":absl_base_config", + ":absl_base_core_headers", + ":absl_base_no_destructor", + ":absl_log_internal_fnmatch", + ":absl_memory_memory", + ":absl_strings_strings", + ":absl_synchronization_synchronization", + ":absl_types_optional", + ] + public_configs = [ "..:grpc_absl_config" ] + configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_log_internal_voidify") { @@ -1406,6 +1582,7 @@ source_set("absl_log_internal_voidify") { public_deps = [ ":absl_base_config" ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_log_absl_check") { @@ -1413,6 +1590,7 @@ source_set("absl_log_absl_check") { public_deps = [ ":absl_log_internal_check_impl" ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_log_absl_log") { @@ -1420,6 +1598,20 @@ source_set("absl_log_absl_log") { public_deps = [ ":absl_log_internal_log_impl" ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true +} + +source_set("absl_log_absl_vlog_is_on") { + sources = [ "src/third_party/abseil-cpp/absl/log/absl_vlog_is_on.h" ] + public_deps = [ + ":absl_base_config", + ":absl_base_core_headers", + ":absl_log_internal_vlog_config", + ":absl_strings_strings", + ] + public_configs = [ "..:grpc_absl_config" ] + configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_log_check") { @@ -1433,6 +1625,7 @@ source_set("absl_log_check") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_log_die_if_null") { @@ -1448,6 +1641,7 @@ source_set("absl_log_die_if_null") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_log_flags") { @@ -1464,10 +1658,12 @@ source_set("absl_log_flags") { ":absl_log_globals", ":absl_log_internal_config", ":absl_log_internal_flags", + ":absl_log_internal_vlog_config", ":absl_strings_strings", ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_log_globals") { @@ -1480,11 +1676,14 @@ source_set("absl_log_globals") { ":absl_base_config", ":absl_base_core_headers", ":absl_base_log_severity", + ":absl_base_raw_logging_internal", ":absl_hash_hash", + ":absl_log_internal_vlog_config", ":absl_strings_strings", ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_log_initialize") { @@ -1500,13 +1699,18 @@ source_set("absl_log_initialize") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_log_log") { sources = [ "src/third_party/abseil-cpp/absl/log/log.h" ] - public_deps = [ ":absl_log_internal_log_impl" ] + public_deps = [ + ":absl_log_internal_log_impl", + ":absl_log_vlog_is_on", + ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_log_log_entry") { @@ -1525,6 +1729,7 @@ source_set("absl_log_log_entry") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_log_log_sink") { @@ -1538,6 +1743,7 @@ source_set("absl_log_log_sink") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_log_log_sink_registry") { @@ -1549,6 +1755,7 @@ source_set("absl_log_log_sink_registry") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_log_log_streamer") { @@ -1564,6 +1771,7 @@ source_set("absl_log_log_streamer") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_log_structured") { @@ -1575,6 +1783,15 @@ source_set("absl_log_structured") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true +} + +source_set("absl_log_vlog_is_on") { + sources = [ "src/third_party/abseil-cpp/absl/log/vlog_is_on.h" ] + public_deps = [ ":absl_log_absl_vlog_is_on" ] + public_configs = [ "..:grpc_absl_config" ] + configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_memory_memory") { @@ -1585,13 +1802,18 @@ source_set("absl_memory_memory") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_meta_type_traits") { sources = [ "src/third_party/abseil-cpp/absl/meta/type_traits.h" ] - public_deps = [ ":absl_base_config" ] + public_deps = [ + ":absl_base_config", + ":absl_base_core_headers", + ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_numeric_bits") { @@ -1605,6 +1827,7 @@ source_set("absl_numeric_bits") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_numeric_int128") { @@ -1621,6 +1844,7 @@ source_set("absl_numeric_int128") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_numeric_representation") { @@ -1629,6 +1853,7 @@ source_set("absl_numeric_representation") { public_deps = [ ":absl_base_config" ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_profiling_exponential_biased") { @@ -1642,6 +1867,7 @@ source_set("absl_profiling_exponential_biased") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_profiling_periodic_sampler") { @@ -1655,6 +1881,7 @@ source_set("absl_profiling_periodic_sampler") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_profiling_sample_recorder") { @@ -1668,6 +1895,7 @@ source_set("absl_profiling_sample_recorder") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_random_internal_distribution_caller") { @@ -1681,6 +1909,7 @@ source_set("absl_random_internal_distribution_caller") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_random_internal_fast_uniform_bits") { @@ -1693,6 +1922,7 @@ source_set("absl_random_internal_fast_uniform_bits") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_random_internal_fastmath") { @@ -1700,6 +1930,7 @@ source_set("absl_random_internal_fastmath") { public_deps = [ ":absl_numeric_bits" ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_random_internal_generate_real") { @@ -1713,6 +1944,7 @@ source_set("absl_random_internal_generate_real") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_random_internal_iostream_state_saver") { @@ -1725,6 +1957,7 @@ source_set("absl_random_internal_iostream_state_saver") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_random_internal_mock_helpers") { @@ -1735,6 +1968,7 @@ source_set("absl_random_internal_mock_helpers") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_random_internal_nanobenchmark") { @@ -1751,6 +1985,7 @@ source_set("absl_random_internal_nanobenchmark") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_random_internal_nonsecure_base") { @@ -1767,6 +2002,7 @@ source_set("absl_random_internal_nonsecure_base") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_random_internal_pcg_engine") { @@ -1781,6 +2017,7 @@ source_set("absl_random_internal_pcg_engine") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_random_internal_platform") { @@ -1792,6 +2029,7 @@ source_set("absl_random_internal_platform") { public_deps = [ ":absl_base_config" ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_random_internal_pool_urbg") { @@ -1813,6 +2051,7 @@ source_set("absl_random_internal_pool_urbg") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_random_internal_randen") { @@ -1828,6 +2067,7 @@ source_set("absl_random_internal_randen") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_random_internal_randen_engine") { @@ -1841,6 +2081,7 @@ source_set("absl_random_internal_randen_engine") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_random_internal_randen_hwaes") { @@ -1856,6 +2097,7 @@ source_set("absl_random_internal_randen_hwaes") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_random_internal_randen_hwaes_impl") { @@ -1871,6 +2113,7 @@ source_set("absl_random_internal_randen_hwaes_impl") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_random_internal_randen_slow") { @@ -1887,6 +2130,7 @@ source_set("absl_random_internal_randen_slow") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_random_internal_salted_seed_seq") { @@ -1901,6 +2145,7 @@ source_set("absl_random_internal_salted_seed_seq") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_random_internal_seed_material") { @@ -1919,6 +2164,7 @@ source_set("absl_random_internal_seed_material") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_random_internal_traits") { @@ -1930,6 +2176,7 @@ source_set("absl_random_internal_traits") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_random_internal_uniform_helper") { @@ -1943,6 +2190,7 @@ source_set("absl_random_internal_uniform_helper") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_random_internal_wide_multiply") { @@ -1956,6 +2204,7 @@ source_set("absl_random_internal_wide_multiply") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_random_bit_gen_ref") { @@ -1970,6 +2219,7 @@ source_set("absl_random_bit_gen_ref") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_random_distributions") { @@ -2006,6 +2256,7 @@ source_set("absl_random_distributions") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_random_random") { @@ -2020,6 +2271,7 @@ source_set("absl_random_random") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_random_seed_gen_exception") { @@ -2030,6 +2282,7 @@ source_set("absl_random_seed_gen_exception") { public_deps = [ ":absl_base_config" ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_random_seed_sequences") { @@ -2047,10 +2300,12 @@ source_set("absl_random_seed_sequences") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_status_status") { sources = [ + "src/third_party/abseil-cpp/absl/status/internal/status_internal.cc", "src/third_party/abseil-cpp/absl/status/internal/status_internal.h", "src/third_party/abseil-cpp/absl/status/status.cc", "src/third_party/abseil-cpp/absl/status/status.h", @@ -2059,20 +2314,26 @@ source_set("absl_status_status") { ] public_deps = [ ":absl_base_atomic_hook", + ":absl_base_config", ":absl_base_core_headers", + ":absl_base_no_destructor", + ":absl_base_nullability", ":absl_base_raw_logging_internal", ":absl_base_strerror", ":absl_container_inlined_vector", ":absl_debugging_stacktrace", ":absl_debugging_symbolize", ":absl_functional_function_ref", + ":absl_memory_memory", ":absl_strings_cord", ":absl_strings_str_format", ":absl_strings_strings", ":absl_types_optional", + ":absl_types_span", ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_status_statusor") { @@ -2083,16 +2344,32 @@ source_set("absl_status_statusor") { ] public_deps = [ ":absl_base_base", + ":absl_base_config", ":absl_base_core_headers", + ":absl_base_nullability", ":absl_base_raw_logging_internal", ":absl_meta_type_traits", ":absl_status_status", + ":absl_strings_has_ostream_operator", + ":absl_strings_str_format", ":absl_strings_strings", ":absl_types_variant", ":absl_utility_utility", ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true +} + +source_set("absl_strings_charset") { + sources = [ "src/third_party/abseil-cpp/absl/strings/charset.h" ] + public_deps = [ + ":absl_base_core_headers", + ":absl_strings_string_view", + ] + public_configs = [ "..:grpc_absl_config" ] + configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_strings_cord") { @@ -2109,9 +2386,10 @@ source_set("absl_strings_cord") { ":absl_base_config", ":absl_base_core_headers", ":absl_base_endian", + ":absl_base_nullability", ":absl_base_raw_logging_internal", - ":absl_container_fixed_array", ":absl_container_inlined_vector", + ":absl_crc_crc32c", ":absl_crc_crc_cord_state", ":absl_functional_function_ref", ":absl_meta_type_traits", @@ -2123,13 +2401,13 @@ source_set("absl_strings_cord") { ":absl_strings_cordz_update_scope", ":absl_strings_cordz_update_tracker", ":absl_strings_internal", - ":absl_strings_str_format", ":absl_strings_strings", ":absl_types_optional", ":absl_types_span", ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_strings_cord_internal") { @@ -2148,9 +2426,6 @@ source_set("absl_strings_cord_internal") { "src/third_party/abseil-cpp/absl/strings/internal/cord_rep_crc.cc", "src/third_party/abseil-cpp/absl/strings/internal/cord_rep_crc.h", "src/third_party/abseil-cpp/absl/strings/internal/cord_rep_flat.h", - "src/third_party/abseil-cpp/absl/strings/internal/cord_rep_ring.cc", - "src/third_party/abseil-cpp/absl/strings/internal/cord_rep_ring.h", - "src/third_party/abseil-cpp/absl/strings/internal/cord_rep_ring_reader.h", ] public_deps = [ ":absl_base_base_internal", @@ -2171,6 +2446,7 @@ source_set("absl_strings_cord_internal") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_strings_cordz_functions") { @@ -2186,6 +2462,7 @@ source_set("absl_strings_cordz_functions") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_strings_cordz_handle") { @@ -2201,6 +2478,7 @@ source_set("absl_strings_cordz_handle") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_strings_cordz_info") { @@ -2221,10 +2499,12 @@ source_set("absl_strings_cordz_info") { ":absl_strings_cordz_statistics", ":absl_strings_cordz_update_tracker", ":absl_synchronization_synchronization", + ":absl_time_time", ":absl_types_span", ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_strings_cordz_sample_token") { @@ -2239,6 +2519,7 @@ source_set("absl_strings_cordz_sample_token") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_strings_cordz_statistics") { @@ -2250,6 +2531,7 @@ source_set("absl_strings_cordz_statistics") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_strings_cordz_update_scope") { @@ -2265,6 +2547,7 @@ source_set("absl_strings_cordz_update_scope") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_strings_cordz_update_tracker") { @@ -2274,11 +2557,19 @@ source_set("absl_strings_cordz_update_tracker") { public_deps = [ ":absl_base_config" ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true +} + +source_set("absl_strings_has_ostream_operator") { + sources = [ "src/third_party/abseil-cpp/absl/strings/has_ostream_operator.h" ] + public_deps = [ ":absl_base_config" ] + public_configs = [ "..:grpc_absl_config" ] + configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_strings_internal") { sources = [ - "src/third_party/abseil-cpp/absl/strings/internal/char_map.h", "src/third_party/abseil-cpp/absl/strings/internal/escaping.cc", "src/third_party/abseil-cpp/absl/strings/internal/escaping.h", "src/third_party/abseil-cpp/absl/strings/internal/ostringstream.cc", @@ -2296,13 +2587,22 @@ source_set("absl_strings_internal") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_strings_str_format") { sources = [ "src/third_party/abseil-cpp/absl/strings/str_format.h" ] - public_deps = [ ":absl_strings_str_format_internal" ] + public_deps = [ + ":absl_base_config", + ":absl_base_core_headers", + ":absl_base_nullability", + ":absl_strings_str_format_internal", + ":absl_strings_string_view", + ":absl_types_span", + ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_strings_str_format_internal") { @@ -2325,6 +2625,8 @@ source_set("absl_strings_str_format_internal") { public_deps = [ ":absl_base_config", ":absl_base_core_headers", + ":absl_container_fixed_array", + ":absl_container_inlined_vector", ":absl_functional_function_ref", ":absl_meta_type_traits", ":absl_numeric_bits", @@ -2337,6 +2639,24 @@ source_set("absl_strings_str_format_internal") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true +} + +source_set("absl_strings_string_view") { + sources = [ + "src/third_party/abseil-cpp/absl/strings/string_view.cc", + "src/third_party/abseil-cpp/absl/strings/string_view.h", + ] + public_deps = [ + ":absl_base_base", + ":absl_base_config", + ":absl_base_core_headers", + ":absl_base_nullability", + ":absl_base_throw_delegate", + ] + public_configs = [ "..:grpc_absl_config" ] + configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_strings_strings") { @@ -2347,6 +2667,7 @@ source_set("absl_strings_strings") { "src/third_party/abseil-cpp/absl/strings/charconv.h", "src/third_party/abseil-cpp/absl/strings/escaping.cc", "src/third_party/abseil-cpp/absl/strings/escaping.h", + "src/third_party/abseil-cpp/absl/strings/has_absl_stringify.h", "src/third_party/abseil-cpp/absl/strings/internal/charconv_bigint.cc", "src/third_party/abseil-cpp/absl/strings/internal/charconv_bigint.h", "src/third_party/abseil-cpp/absl/strings/internal/charconv_parse.cc", @@ -2373,7 +2694,7 @@ source_set("absl_strings_strings") { "src/third_party/abseil-cpp/absl/strings/str_replace.h", "src/third_party/abseil-cpp/absl/strings/str_split.cc", "src/third_party/abseil-cpp/absl/strings/str_split.h", - "src/third_party/abseil-cpp/absl/strings/string_view.cc", + "src/third_party/abseil-cpp/absl/strings/string_view.h", "src/third_party/abseil-cpp/absl/strings/string_view.h", "src/third_party/abseil-cpp/absl/strings/strip.h", "src/third_party/abseil-cpp/absl/strings/substitute.cc", @@ -2384,16 +2705,20 @@ source_set("absl_strings_strings") { ":absl_base_config", ":absl_base_core_headers", ":absl_base_endian", + ":absl_base_nullability", ":absl_base_raw_logging_internal", ":absl_base_throw_delegate", ":absl_memory_memory", ":absl_meta_type_traits", ":absl_numeric_bits", ":absl_numeric_int128", + ":absl_strings_charset", ":absl_strings_internal", + ":absl_strings_string_view", ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_synchronization_graphcycles_internal") { @@ -2411,19 +2736,24 @@ source_set("absl_synchronization_graphcycles_internal") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_synchronization_kernel_timeout_internal") { sources = [ + "src/third_party/abseil-cpp/absl/synchronization/internal/kernel_timeout.cc", "src/third_party/abseil-cpp/absl/synchronization/internal/kernel_timeout.h", ] public_deps = [ + ":absl_base_base", + ":absl_base_config", ":absl_base_core_headers", ":absl_base_raw_logging_internal", ":absl_time_time", ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_synchronization_synchronization") { @@ -2435,10 +2765,21 @@ source_set("absl_synchronization_synchronization") { "src/third_party/abseil-cpp/absl/synchronization/internal/create_thread_identity.cc", "src/third_party/abseil-cpp/absl/synchronization/internal/create_thread_identity.h", "src/third_party/abseil-cpp/absl/synchronization/internal/futex.h", + "src/third_party/abseil-cpp/absl/synchronization/internal/futex_waiter.cc", + "src/third_party/abseil-cpp/absl/synchronization/internal/futex_waiter.h", "src/third_party/abseil-cpp/absl/synchronization/internal/per_thread_sem.cc", "src/third_party/abseil-cpp/absl/synchronization/internal/per_thread_sem.h", - "src/third_party/abseil-cpp/absl/synchronization/internal/waiter.cc", + "src/third_party/abseil-cpp/absl/synchronization/internal/pthread_waiter.cc", + "src/third_party/abseil-cpp/absl/synchronization/internal/pthread_waiter.h", + "src/third_party/abseil-cpp/absl/synchronization/internal/sem_waiter.cc", + "src/third_party/abseil-cpp/absl/synchronization/internal/sem_waiter.h", + "src/third_party/abseil-cpp/absl/synchronization/internal/stdcpp_waiter.cc", + "src/third_party/abseil-cpp/absl/synchronization/internal/stdcpp_waiter.h", "src/third_party/abseil-cpp/absl/synchronization/internal/waiter.h", + "src/third_party/abseil-cpp/absl/synchronization/internal/waiter_base.cc", + "src/third_party/abseil-cpp/absl/synchronization/internal/waiter_base.h", + "src/third_party/abseil-cpp/absl/synchronization/internal/win32_waiter.cc", + "src/third_party/abseil-cpp/absl/synchronization/internal/win32_waiter.h", "src/third_party/abseil-cpp/absl/synchronization/mutex.cc", "src/third_party/abseil-cpp/absl/synchronization/mutex.h", "src/third_party/abseil-cpp/absl/synchronization/notification.cc", @@ -2461,6 +2802,7 @@ source_set("absl_synchronization_synchronization") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_time_internal_cctz_civil_time") { @@ -2472,6 +2814,7 @@ source_set("absl_time_internal_cctz_civil_time") { public_deps = [ ":absl_base_config" ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_time_internal_cctz_time_zone") { @@ -2501,6 +2844,7 @@ source_set("absl_time_internal_cctz_time_zone") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_time_time") { @@ -2529,6 +2873,7 @@ source_set("absl_time_time") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_types_any") { @@ -2543,6 +2888,7 @@ source_set("absl_types_any") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_types_bad_any_cast") { @@ -2553,6 +2899,7 @@ source_set("absl_types_bad_any_cast") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_types_bad_any_cast_impl") { @@ -2566,6 +2913,7 @@ source_set("absl_types_bad_any_cast_impl") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_types_bad_optional_access") { @@ -2579,6 +2927,7 @@ source_set("absl_types_bad_optional_access") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_types_bad_variant_access") { @@ -2592,16 +2941,19 @@ source_set("absl_types_bad_variant_access") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_types_compare") { sources = [ "src/third_party/abseil-cpp/absl/types/compare.h" ] public_deps = [ + ":absl_base_config", ":absl_base_core_headers", ":absl_meta_type_traits", ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_types_optional") { @@ -2613,6 +2965,7 @@ source_set("absl_types_optional") { ":absl_base_base_internal", ":absl_base_config", ":absl_base_core_headers", + ":absl_base_nullability", ":absl_memory_memory", ":absl_meta_type_traits", ":absl_types_bad_optional_access", @@ -2620,6 +2973,7 @@ source_set("absl_types_optional") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_types_span") { @@ -2630,11 +2984,13 @@ source_set("absl_types_span") { public_deps = [ ":absl_algorithm_algorithm", ":absl_base_core_headers", + ":absl_base_nullability", ":absl_base_throw_delegate", ":absl_meta_type_traits", ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_types_variant") { @@ -2652,6 +3008,16 @@ source_set("absl_types_variant") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true +} + +source_set("absl_utility_if_constexpr") { + sources = + [ "src/third_party/abseil-cpp/absl/utility/internal/if_constexpr.h" ] + public_deps = [ ":absl_base_config" ] + public_configs = [ "..:grpc_absl_config" ] + configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("absl_utility_utility") { @@ -2663,186 +3029,44 @@ source_set("absl_utility_utility") { ] public_configs = [ "..:grpc_absl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] -} - -static_library("upb") { - sources = [ - "src/src/core/ext/upb-generated/google/protobuf/descriptor.upb.c", - "src/src/core/ext/upb-generated/google/protobuf/descriptor.upb.h", - "src/src/core/ext/upbdefs-generated/google/protobuf/descriptor.upbdefs.c", - "src/src/core/ext/upbdefs-generated/google/protobuf/descriptor.upbdefs.h", - "src/third_party/upb/third_party/utf8_range/naive.c", - "src/third_party/upb/third_party/utf8_range/range2-neon.c", - "src/third_party/upb/third_party/utf8_range/range2-sse.c", - "src/third_party/upb/third_party/utf8_range/utf8_range.h", - "src/third_party/upb/upb/arena.c", - "src/third_party/upb/upb/arena.h", - "src/third_party/upb/upb/array.c", - "src/third_party/upb/upb/array.h", - "src/third_party/upb/upb/collections.h", - "src/third_party/upb/upb/decode.c", - "src/third_party/upb/upb/decode.h", - "src/third_party/upb/upb/decode_fast.c", - "src/third_party/upb/upb/decode_fast.h", - "src/third_party/upb/upb/def.c", - "src/third_party/upb/upb/def.h", - "src/third_party/upb/upb/def.hpp", - "src/third_party/upb/upb/encode.c", - "src/third_party/upb/upb/encode.h", - "src/third_party/upb/upb/extension_registry.c", - "src/third_party/upb/upb/extension_registry.h", - "src/third_party/upb/upb/internal/decode.h", - "src/third_party/upb/upb/internal/table.h", - "src/third_party/upb/upb/internal/upb.h", - "src/third_party/upb/upb/internal/vsnprintf_compat.h", - "src/third_party/upb/upb/json_decode.c", - "src/third_party/upb/upb/json_decode.h", - "src/third_party/upb/upb/json_encode.c", - "src/third_party/upb/upb/json_encode.h", - "src/third_party/upb/upb/map.c", - "src/third_party/upb/upb/map.h", - "src/third_party/upb/upb/message_value.h", - "src/third_party/upb/upb/mini_table.c", - "src/third_party/upb/upb/mini_table.h", - "src/third_party/upb/upb/mini_table.hpp", - "src/third_party/upb/upb/msg.c", - "src/third_party/upb/upb/msg.h", - "src/third_party/upb/upb/msg_internal.h", - "src/third_party/upb/upb/port_def.inc", - "src/third_party/upb/upb/port_undef.inc", - "src/third_party/upb/upb/reflection.c", - "src/third_party/upb/upb/reflection.h", - "src/third_party/upb/upb/reflection.hpp", - "src/third_party/upb/upb/status.c", - "src/third_party/upb/upb/status.h", - "src/third_party/upb/upb/table.c", - "src/third_party/upb/upb/table_internal.h", - "src/third_party/upb/upb/text_encode.c", - "src/third_party/upb/upb/text_encode.h", - "src/third_party/upb/upb/upb.c", - "src/third_party/upb/upb/upb.h", - "src/third_party/upb/upb/upb.hpp", - ] - public_deps = [] - public_configs = [ "..:grpc_upb_config" ] - configs -= [ "//gn/standalone:extra_warnings" ] -} - -static_library("re2") { - sources = [ - "src/third_party/re2/re2/bitmap256.h", - "src/third_party/re2/re2/bitstate.cc", - "src/third_party/re2/re2/compile.cc", - "src/third_party/re2/re2/dfa.cc", - "src/third_party/re2/re2/filtered_re2.cc", - "src/third_party/re2/re2/filtered_re2.h", - "src/third_party/re2/re2/mimics_pcre.cc", - "src/third_party/re2/re2/nfa.cc", - "src/third_party/re2/re2/onepass.cc", - "src/third_party/re2/re2/parse.cc", - "src/third_party/re2/re2/perl_groups.cc", - "src/third_party/re2/re2/pod_array.h", - "src/third_party/re2/re2/prefilter.cc", - "src/third_party/re2/re2/prefilter.h", - "src/third_party/re2/re2/prefilter_tree.cc", - "src/third_party/re2/re2/prefilter_tree.h", - "src/third_party/re2/re2/prog.cc", - "src/third_party/re2/re2/prog.h", - "src/third_party/re2/re2/re2.cc", - "src/third_party/re2/re2/re2.h", - "src/third_party/re2/re2/regexp.cc", - "src/third_party/re2/re2/regexp.h", - "src/third_party/re2/re2/set.cc", - "src/third_party/re2/re2/set.h", - "src/third_party/re2/re2/simplify.cc", - "src/third_party/re2/re2/sparse_array.h", - "src/third_party/re2/re2/sparse_set.h", - "src/third_party/re2/re2/stringpiece.cc", - "src/third_party/re2/re2/stringpiece.h", - "src/third_party/re2/re2/tostring.cc", - "src/third_party/re2/re2/unicode_casefold.cc", - "src/third_party/re2/re2/unicode_casefold.h", - "src/third_party/re2/re2/unicode_groups.cc", - "src/third_party/re2/re2/unicode_groups.h", - "src/third_party/re2/re2/walker-inl.h", - "src/third_party/re2/util/benchmark.h", - "src/third_party/re2/util/flags.h", - "src/third_party/re2/util/logging.h", - "src/third_party/re2/util/malloc_counter.h", - "src/third_party/re2/util/mix.h", - "src/third_party/re2/util/mutex.h", - "src/third_party/re2/util/pcre.cc", - "src/third_party/re2/util/pcre.h", - "src/third_party/re2/util/rune.cc", - "src/third_party/re2/util/strutil.cc", - "src/third_party/re2/util/strutil.h", - "src/third_party/re2/util/test.h", - "src/third_party/re2/util/utf.h", - "src/third_party/re2/util/util.h", - ] - public_deps = [] - public_configs = [ "..:grpc_re2_config" ] - configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } static_library("boringssl") { sources = [ - "src/third_party/boringssl-with-bazel/err_data.c", - "src/third_party/boringssl-with-bazel/linux-x86_64/crypto/chacha/chacha-x86_64.S", - "src/third_party/boringssl-with-bazel/linux-x86_64/crypto/cipher_extra/aes128gcmsiv-x86_64.S", - "src/third_party/boringssl-with-bazel/linux-x86_64/crypto/cipher_extra/chacha20_poly1305_x86_64.S", - "src/third_party/boringssl-with-bazel/linux-x86_64/crypto/fipsmodule/aesni-gcm-x86_64.S", - "src/third_party/boringssl-with-bazel/linux-x86_64/crypto/fipsmodule/aesni-x86_64.S", - "src/third_party/boringssl-with-bazel/linux-x86_64/crypto/fipsmodule/ghash-ssse3-x86_64.S", - "src/third_party/boringssl-with-bazel/linux-x86_64/crypto/fipsmodule/ghash-x86_64.S", - "src/third_party/boringssl-with-bazel/linux-x86_64/crypto/fipsmodule/md5-x86_64.S", - "src/third_party/boringssl-with-bazel/linux-x86_64/crypto/fipsmodule/p256-x86_64-asm.S", - "src/third_party/boringssl-with-bazel/linux-x86_64/crypto/fipsmodule/p256_beeu-x86_64-asm.S", - "src/third_party/boringssl-with-bazel/linux-x86_64/crypto/fipsmodule/rdrand-x86_64.S", - "src/third_party/boringssl-with-bazel/linux-x86_64/crypto/fipsmodule/rsaz-avx2.S", - "src/third_party/boringssl-with-bazel/linux-x86_64/crypto/fipsmodule/sha1-x86_64.S", - "src/third_party/boringssl-with-bazel/linux-x86_64/crypto/fipsmodule/sha256-x86_64.S", - "src/third_party/boringssl-with-bazel/linux-x86_64/crypto/fipsmodule/sha512-x86_64.S", - "src/third_party/boringssl-with-bazel/linux-x86_64/crypto/fipsmodule/vpaes-x86_64.S", - "src/third_party/boringssl-with-bazel/linux-x86_64/crypto/fipsmodule/x86_64-mont.S", - "src/third_party/boringssl-with-bazel/linux-x86_64/crypto/fipsmodule/x86_64-mont5.S", - "src/third_party/boringssl-with-bazel/linux-x86_64/crypto/test/trampoline-x86_64.S", "src/third_party/boringssl-with-bazel/src/crypto/asn1/a_bitstr.c", "src/third_party/boringssl-with-bazel/src/crypto/asn1/a_bool.c", "src/third_party/boringssl-with-bazel/src/crypto/asn1/a_d2i_fp.c", "src/third_party/boringssl-with-bazel/src/crypto/asn1/a_dup.c", - "src/third_party/boringssl-with-bazel/src/crypto/asn1/a_enum.c", "src/third_party/boringssl-with-bazel/src/crypto/asn1/a_gentm.c", "src/third_party/boringssl-with-bazel/src/crypto/asn1/a_i2d_fp.c", "src/third_party/boringssl-with-bazel/src/crypto/asn1/a_int.c", "src/third_party/boringssl-with-bazel/src/crypto/asn1/a_mbstr.c", "src/third_party/boringssl-with-bazel/src/crypto/asn1/a_object.c", "src/third_party/boringssl-with-bazel/src/crypto/asn1/a_octet.c", - "src/third_party/boringssl-with-bazel/src/crypto/asn1/a_print.c", "src/third_party/boringssl-with-bazel/src/crypto/asn1/a_strex.c", "src/third_party/boringssl-with-bazel/src/crypto/asn1/a_strnid.c", "src/third_party/boringssl-with-bazel/src/crypto/asn1/a_time.c", "src/third_party/boringssl-with-bazel/src/crypto/asn1/a_type.c", "src/third_party/boringssl-with-bazel/src/crypto/asn1/a_utctm.c", - "src/third_party/boringssl-with-bazel/src/crypto/asn1/a_utf8.c", "src/third_party/boringssl-with-bazel/src/crypto/asn1/asn1_lib.c", "src/third_party/boringssl-with-bazel/src/crypto/asn1/asn1_par.c", "src/third_party/boringssl-with-bazel/src/crypto/asn1/asn_pack.c", - "src/third_party/boringssl-with-bazel/src/crypto/asn1/charmap.h", "src/third_party/boringssl-with-bazel/src/crypto/asn1/f_int.c", "src/third_party/boringssl-with-bazel/src/crypto/asn1/f_string.c", "src/third_party/boringssl-with-bazel/src/crypto/asn1/internal.h", + "src/third_party/boringssl-with-bazel/src/crypto/asn1/posix_time.c", "src/third_party/boringssl-with-bazel/src/crypto/asn1/tasn_dec.c", "src/third_party/boringssl-with-bazel/src/crypto/asn1/tasn_enc.c", "src/third_party/boringssl-with-bazel/src/crypto/asn1/tasn_fre.c", "src/third_party/boringssl-with-bazel/src/crypto/asn1/tasn_new.c", "src/third_party/boringssl-with-bazel/src/crypto/asn1/tasn_typ.c", "src/third_party/boringssl-with-bazel/src/crypto/asn1/tasn_utl.c", - "src/third_party/boringssl-with-bazel/src/crypto/asn1/time_support.c", "src/third_party/boringssl-with-bazel/src/crypto/base64/base64.c", "src/third_party/boringssl-with-bazel/src/crypto/bio/bio.c", "src/third_party/boringssl-with-bazel/src/crypto/bio/bio_mem.c", "src/third_party/boringssl-with-bazel/src/crypto/bio/connect.c", + "src/third_party/boringssl-with-bazel/src/crypto/bio/errno.c", "src/third_party/boringssl-with-bazel/src/crypto/bio/fd.c", "src/third_party/boringssl-with-bazel/src/crypto/bio/file.c", "src/third_party/boringssl-with-bazel/src/crypto/bio/hexdump.c", @@ -2865,33 +3089,38 @@ static_library("boringssl") { "src/third_party/boringssl-with-bazel/src/crypto/chacha/internal.h", "src/third_party/boringssl-with-bazel/src/crypto/cipher_extra/cipher_extra.c", "src/third_party/boringssl-with-bazel/src/crypto/cipher_extra/derive_key.c", - "src/third_party/boringssl-with-bazel/src/crypto/cipher_extra/e_aesccm.c", "src/third_party/boringssl-with-bazel/src/crypto/cipher_extra/e_aesctrhmac.c", "src/third_party/boringssl-with-bazel/src/crypto/cipher_extra/e_aesgcmsiv.c", "src/third_party/boringssl-with-bazel/src/crypto/cipher_extra/e_chacha20poly1305.c", + "src/third_party/boringssl-with-bazel/src/crypto/cipher_extra/e_des.c", "src/third_party/boringssl-with-bazel/src/crypto/cipher_extra/e_null.c", "src/third_party/boringssl-with-bazel/src/crypto/cipher_extra/e_rc2.c", "src/third_party/boringssl-with-bazel/src/crypto/cipher_extra/e_rc4.c", "src/third_party/boringssl-with-bazel/src/crypto/cipher_extra/e_tls.c", "src/third_party/boringssl-with-bazel/src/crypto/cipher_extra/internal.h", "src/third_party/boringssl-with-bazel/src/crypto/cipher_extra/tls_cbc.c", - "src/third_party/boringssl-with-bazel/src/crypto/cmac/cmac.c", "src/third_party/boringssl-with-bazel/src/crypto/conf/conf.c", "src/third_party/boringssl-with-bazel/src/crypto/conf/conf_def.h", "src/third_party/boringssl-with-bazel/src/crypto/conf/internal.h", - "src/third_party/boringssl-with-bazel/src/crypto/cpu-aarch64-fuchsia.c", - "src/third_party/boringssl-with-bazel/src/crypto/cpu-aarch64-linux.c", - "src/third_party/boringssl-with-bazel/src/crypto/cpu-aarch64-win.c", - "src/third_party/boringssl-with-bazel/src/crypto/cpu-arm-linux.c", - "src/third_party/boringssl-with-bazel/src/crypto/cpu-arm-linux.h", - "src/third_party/boringssl-with-bazel/src/crypto/cpu-arm.c", - "src/third_party/boringssl-with-bazel/src/crypto/cpu-intel.c", - "src/third_party/boringssl-with-bazel/src/crypto/cpu-ppc64le.c", + "src/third_party/boringssl-with-bazel/src/crypto/cpu_aarch64_apple.c", + "src/third_party/boringssl-with-bazel/src/crypto/cpu_aarch64_fuchsia.c", + "src/third_party/boringssl-with-bazel/src/crypto/cpu_aarch64_linux.c", + "src/third_party/boringssl-with-bazel/src/crypto/cpu_aarch64_openbsd.c", + "src/third_party/boringssl-with-bazel/src/crypto/cpu_aarch64_sysreg.c", + "src/third_party/boringssl-with-bazel/src/crypto/cpu_aarch64_win.c", + "src/third_party/boringssl-with-bazel/src/crypto/cpu_arm_freebsd.c", + "src/third_party/boringssl-with-bazel/src/crypto/cpu_arm_linux.c", + "src/third_party/boringssl-with-bazel/src/crypto/cpu_arm_linux.h", + "src/third_party/boringssl-with-bazel/src/crypto/cpu_intel.c", "src/third_party/boringssl-with-bazel/src/crypto/crypto.c", + "src/third_party/boringssl-with-bazel/src/crypto/curve25519/asm/x25519-asm-arm.S", "src/third_party/boringssl-with-bazel/src/crypto/curve25519/curve25519.c", + "src/third_party/boringssl-with-bazel/src/crypto/curve25519/curve25519_64_adx.c", "src/third_party/boringssl-with-bazel/src/crypto/curve25519/curve25519_tables.h", "src/third_party/boringssl-with-bazel/src/crypto/curve25519/internal.h", "src/third_party/boringssl-with-bazel/src/crypto/curve25519/spake25519.c", + "src/third_party/boringssl-with-bazel/src/crypto/des/des.c", + "src/third_party/boringssl-with-bazel/src/crypto/des/internal.h", "src/third_party/boringssl-with-bazel/src/crypto/dh_extra/dh_asn1.c", "src/third_party/boringssl-with-bazel/src/crypto/dh_extra/params.c", "src/third_party/boringssl-with-bazel/src/crypto/digest_extra/digest_extra.c", @@ -2907,16 +3136,18 @@ static_library("boringssl") { "src/third_party/boringssl-with-bazel/src/crypto/engine/engine.c", "src/third_party/boringssl-with-bazel/src/crypto/err/err.c", "src/third_party/boringssl-with-bazel/src/crypto/err/internal.h", - "src/third_party/boringssl-with-bazel/src/crypto/evp/digestsign.c", "src/third_party/boringssl-with-bazel/src/crypto/evp/evp.c", "src/third_party/boringssl-with-bazel/src/crypto/evp/evp_asn1.c", "src/third_party/boringssl-with-bazel/src/crypto/evp/evp_ctx.c", "src/third_party/boringssl-with-bazel/src/crypto/evp/internal.h", + "src/third_party/boringssl-with-bazel/src/crypto/evp/p_dh.c", + "src/third_party/boringssl-with-bazel/src/crypto/evp/p_dh_asn1.c", "src/third_party/boringssl-with-bazel/src/crypto/evp/p_dsa_asn1.c", "src/third_party/boringssl-with-bazel/src/crypto/evp/p_ec.c", "src/third_party/boringssl-with-bazel/src/crypto/evp/p_ec_asn1.c", "src/third_party/boringssl-with-bazel/src/crypto/evp/p_ed25519.c", "src/third_party/boringssl-with-bazel/src/crypto/evp/p_ed25519_asn1.c", + "src/third_party/boringssl-with-bazel/src/crypto/evp/p_hkdf.c", "src/third_party/boringssl-with-bazel/src/crypto/evp/p_rsa.c", "src/third_party/boringssl-with-bazel/src/crypto/evp/p_rsa_asn1.c", "src/third_party/boringssl-with-bazel/src/crypto/evp/p_x25519.c", @@ -2958,17 +3189,19 @@ static_library("boringssl") { "src/third_party/boringssl-with-bazel/src/crypto/fipsmodule/cipher/aead.c", "src/third_party/boringssl-with-bazel/src/crypto/fipsmodule/cipher/cipher.c", "src/third_party/boringssl-with-bazel/src/crypto/fipsmodule/cipher/e_aes.c", - "src/third_party/boringssl-with-bazel/src/crypto/fipsmodule/cipher/e_des.c", + "src/third_party/boringssl-with-bazel/src/crypto/fipsmodule/cipher/e_aesccm.c", "src/third_party/boringssl-with-bazel/src/crypto/fipsmodule/cipher/internal.h", + "src/third_party/boringssl-with-bazel/src/crypto/fipsmodule/cmac/cmac.c", "src/third_party/boringssl-with-bazel/src/crypto/fipsmodule/delocate.h", - "src/third_party/boringssl-with-bazel/src/crypto/fipsmodule/des/des.c", - "src/third_party/boringssl-with-bazel/src/crypto/fipsmodule/des/internal.h", "src/third_party/boringssl-with-bazel/src/crypto/fipsmodule/dh/check.c", "src/third_party/boringssl-with-bazel/src/crypto/fipsmodule/dh/dh.c", + "src/third_party/boringssl-with-bazel/src/crypto/fipsmodule/dh/internal.h", "src/third_party/boringssl-with-bazel/src/crypto/fipsmodule/digest/digest.c", "src/third_party/boringssl-with-bazel/src/crypto/fipsmodule/digest/digests.c", "src/third_party/boringssl-with-bazel/src/crypto/fipsmodule/digest/internal.h", "src/third_party/boringssl-with-bazel/src/crypto/fipsmodule/digest/md32_common.h", + "src/third_party/boringssl-with-bazel/src/crypto/fipsmodule/digestsign/digestsign.c", + "src/third_party/boringssl-with-bazel/src/crypto/fipsmodule/ec/builtin_curves.h", "src/third_party/boringssl-with-bazel/src/crypto/fipsmodule/ec/ec.c", "src/third_party/boringssl-with-bazel/src/crypto/fipsmodule/ec/ec_key.c", "src/third_party/boringssl-with-bazel/src/crypto/fipsmodule/ec/ec_montgomery.c", @@ -2976,9 +3209,9 @@ static_library("boringssl") { "src/third_party/boringssl-with-bazel/src/crypto/fipsmodule/ec/internal.h", "src/third_party/boringssl-with-bazel/src/crypto/fipsmodule/ec/oct.c", "src/third_party/boringssl-with-bazel/src/crypto/fipsmodule/ec/p224-64.c", - "src/third_party/boringssl-with-bazel/src/crypto/fipsmodule/ec/p256-x86_64-table.h", - "src/third_party/boringssl-with-bazel/src/crypto/fipsmodule/ec/p256-x86_64.c", - "src/third_party/boringssl-with-bazel/src/crypto/fipsmodule/ec/p256-x86_64.h", + "src/third_party/boringssl-with-bazel/src/crypto/fipsmodule/ec/p256-nistz-table.h", + "src/third_party/boringssl-with-bazel/src/crypto/fipsmodule/ec/p256-nistz.c", + "src/third_party/boringssl-with-bazel/src/crypto/fipsmodule/ec/p256-nistz.h", "src/third_party/boringssl-with-bazel/src/crypto/fipsmodule/ec/p256.c", "src/third_party/boringssl-with-bazel/src/crypto/fipsmodule/ec/p256_table.h", "src/third_party/boringssl-with-bazel/src/crypto/fipsmodule/ec/scalar.c", @@ -2990,6 +3223,7 @@ static_library("boringssl") { "src/third_party/boringssl-with-bazel/src/crypto/fipsmodule/ecdsa/ecdsa.c", "src/third_party/boringssl-with-bazel/src/crypto/fipsmodule/ecdsa/internal.h", "src/third_party/boringssl-with-bazel/src/crypto/fipsmodule/fips_shared_support.c", + "src/third_party/boringssl-with-bazel/src/crypto/fipsmodule/hkdf/hkdf.c", "src/third_party/boringssl-with-bazel/src/crypto/fipsmodule/hmac/hmac.c", "src/third_party/boringssl-with-bazel/src/crypto/fipsmodule/md4/md4.c", "src/third_party/boringssl-with-bazel/src/crypto/fipsmodule/md5/internal.h", @@ -3016,19 +3250,23 @@ static_library("boringssl") { "src/third_party/boringssl-with-bazel/src/crypto/fipsmodule/rsa/rsa_impl.c", "src/third_party/boringssl-with-bazel/src/crypto/fipsmodule/self_check/fips.c", "src/third_party/boringssl-with-bazel/src/crypto/fipsmodule/self_check/self_check.c", + "src/third_party/boringssl-with-bazel/src/crypto/fipsmodule/service_indicator/internal.h", + "src/third_party/boringssl-with-bazel/src/crypto/fipsmodule/service_indicator/service_indicator.c", "src/third_party/boringssl-with-bazel/src/crypto/fipsmodule/sha/internal.h", - "src/third_party/boringssl-with-bazel/src/crypto/fipsmodule/sha/sha1-altivec.c", "src/third_party/boringssl-with-bazel/src/crypto/fipsmodule/sha/sha1.c", "src/third_party/boringssl-with-bazel/src/crypto/fipsmodule/sha/sha256.c", "src/third_party/boringssl-with-bazel/src/crypto/fipsmodule/sha/sha512.c", "src/third_party/boringssl-with-bazel/src/crypto/fipsmodule/tls/internal.h", "src/third_party/boringssl-with-bazel/src/crypto/fipsmodule/tls/kdf.c", - "src/third_party/boringssl-with-bazel/src/crypto/hkdf/hkdf.c", "src/third_party/boringssl-with-bazel/src/crypto/hpke/hpke.c", "src/third_party/boringssl-with-bazel/src/crypto/hrss/asm/poly_rq_mul.S", "src/third_party/boringssl-with-bazel/src/crypto/hrss/hrss.c", "src/third_party/boringssl-with-bazel/src/crypto/hrss/internal.h", "src/third_party/boringssl-with-bazel/src/crypto/internal.h", + "src/third_party/boringssl-with-bazel/src/crypto/keccak/internal.h", + "src/third_party/boringssl-with-bazel/src/crypto/keccak/keccak.c", + "src/third_party/boringssl-with-bazel/src/crypto/kyber/internal.h", + "src/third_party/boringssl-with-bazel/src/crypto/kyber/kyber.c", "src/third_party/boringssl-with-bazel/src/crypto/lhash/internal.h", "src/third_party/boringssl-with-bazel/src/crypto/lhash/lhash.c", "src/third_party/boringssl-with-bazel/src/crypto/mem.c", @@ -3053,21 +3291,39 @@ static_library("boringssl") { "src/third_party/boringssl-with-bazel/src/crypto/poly1305/internal.h", "src/third_party/boringssl-with-bazel/src/crypto/poly1305/poly1305.c", "src/third_party/boringssl-with-bazel/src/crypto/poly1305/poly1305_arm.c", + "src/third_party/boringssl-with-bazel/src/crypto/poly1305/poly1305_arm_asm.S", "src/third_party/boringssl-with-bazel/src/crypto/poly1305/poly1305_vec.c", "src/third_party/boringssl-with-bazel/src/crypto/pool/internal.h", "src/third_party/boringssl-with-bazel/src/crypto/pool/pool.c", "src/third_party/boringssl-with-bazel/src/crypto/rand_extra/deterministic.c", "src/third_party/boringssl-with-bazel/src/crypto/rand_extra/forkunsafe.c", - "src/third_party/boringssl-with-bazel/src/crypto/rand_extra/fuchsia.c", + "src/third_party/boringssl-with-bazel/src/crypto/rand_extra/getentropy.c", + "src/third_party/boringssl-with-bazel/src/crypto/rand_extra/ios.c", "src/third_party/boringssl-with-bazel/src/crypto/rand_extra/passive.c", "src/third_party/boringssl-with-bazel/src/crypto/rand_extra/rand_extra.c", + "src/third_party/boringssl-with-bazel/src/crypto/rand_extra/trusty.c", "src/third_party/boringssl-with-bazel/src/crypto/rand_extra/windows.c", "src/third_party/boringssl-with-bazel/src/crypto/rc4/rc4.c", - "src/third_party/boringssl-with-bazel/src/crypto/refcount_c11.c", - "src/third_party/boringssl-with-bazel/src/crypto/refcount_lock.c", + "src/third_party/boringssl-with-bazel/src/crypto/refcount.c", + "src/third_party/boringssl-with-bazel/src/crypto/rsa_extra/internal.h", "src/third_party/boringssl-with-bazel/src/crypto/rsa_extra/rsa_asn1.c", + "src/third_party/boringssl-with-bazel/src/crypto/rsa_extra/rsa_crypt.c", "src/third_party/boringssl-with-bazel/src/crypto/rsa_extra/rsa_print.c", "src/third_party/boringssl-with-bazel/src/crypto/siphash/siphash.c", + "src/third_party/boringssl-with-bazel/src/crypto/spx/address.c", + "src/third_party/boringssl-with-bazel/src/crypto/spx/address.h", + "src/third_party/boringssl-with-bazel/src/crypto/spx/fors.c", + "src/third_party/boringssl-with-bazel/src/crypto/spx/fors.h", + "src/third_party/boringssl-with-bazel/src/crypto/spx/merkle.c", + "src/third_party/boringssl-with-bazel/src/crypto/spx/merkle.h", + "src/third_party/boringssl-with-bazel/src/crypto/spx/params.h", + "src/third_party/boringssl-with-bazel/src/crypto/spx/spx.c", + "src/third_party/boringssl-with-bazel/src/crypto/spx/spx_util.c", + "src/third_party/boringssl-with-bazel/src/crypto/spx/spx_util.h", + "src/third_party/boringssl-with-bazel/src/crypto/spx/thash.c", + "src/third_party/boringssl-with-bazel/src/crypto/spx/thash.h", + "src/third_party/boringssl-with-bazel/src/crypto/spx/wots.c", + "src/third_party/boringssl-with-bazel/src/crypto/spx/wots.h", "src/third_party/boringssl-with-bazel/src/crypto/stack/stack.c", "src/third_party/boringssl-with-bazel/src/crypto/thread.c", "src/third_party/boringssl-with-bazel/src/crypto/thread_none.c", @@ -3084,14 +3340,39 @@ static_library("boringssl") { "src/third_party/boringssl-with-bazel/src/crypto/x509/asn1_gen.c", "src/third_party/boringssl-with-bazel/src/crypto/x509/by_dir.c", "src/third_party/boringssl-with-bazel/src/crypto/x509/by_file.c", + "src/third_party/boringssl-with-bazel/src/crypto/x509/ext_dat.h", "src/third_party/boringssl-with-bazel/src/crypto/x509/i2d_pr.c", "src/third_party/boringssl-with-bazel/src/crypto/x509/internal.h", "src/third_party/boringssl-with-bazel/src/crypto/x509/name_print.c", + "src/third_party/boringssl-with-bazel/src/crypto/x509/policy.c", "src/third_party/boringssl-with-bazel/src/crypto/x509/rsa_pss.c", "src/third_party/boringssl-with-bazel/src/crypto/x509/t_crl.c", "src/third_party/boringssl-with-bazel/src/crypto/x509/t_req.c", "src/third_party/boringssl-with-bazel/src/crypto/x509/t_x509.c", "src/third_party/boringssl-with-bazel/src/crypto/x509/t_x509a.c", + "src/third_party/boringssl-with-bazel/src/crypto/x509/v3_akey.c", + "src/third_party/boringssl-with-bazel/src/crypto/x509/v3_akeya.c", + "src/third_party/boringssl-with-bazel/src/crypto/x509/v3_alt.c", + "src/third_party/boringssl-with-bazel/src/crypto/x509/v3_bcons.c", + "src/third_party/boringssl-with-bazel/src/crypto/x509/v3_bitst.c", + "src/third_party/boringssl-with-bazel/src/crypto/x509/v3_conf.c", + "src/third_party/boringssl-with-bazel/src/crypto/x509/v3_cpols.c", + "src/third_party/boringssl-with-bazel/src/crypto/x509/v3_crld.c", + "src/third_party/boringssl-with-bazel/src/crypto/x509/v3_enum.c", + "src/third_party/boringssl-with-bazel/src/crypto/x509/v3_extku.c", + "src/third_party/boringssl-with-bazel/src/crypto/x509/v3_genn.c", + "src/third_party/boringssl-with-bazel/src/crypto/x509/v3_ia5.c", + "src/third_party/boringssl-with-bazel/src/crypto/x509/v3_info.c", + "src/third_party/boringssl-with-bazel/src/crypto/x509/v3_int.c", + "src/third_party/boringssl-with-bazel/src/crypto/x509/v3_lib.c", + "src/third_party/boringssl-with-bazel/src/crypto/x509/v3_ncons.c", + "src/third_party/boringssl-with-bazel/src/crypto/x509/v3_ocsp.c", + "src/third_party/boringssl-with-bazel/src/crypto/x509/v3_pcons.c", + "src/third_party/boringssl-with-bazel/src/crypto/x509/v3_pmaps.c", + "src/third_party/boringssl-with-bazel/src/crypto/x509/v3_prn.c", + "src/third_party/boringssl-with-bazel/src/crypto/x509/v3_purp.c", + "src/third_party/boringssl-with-bazel/src/crypto/x509/v3_skey.c", + "src/third_party/boringssl-with-bazel/src/crypto/x509/v3_utl.c", "src/third_party/boringssl-with-bazel/src/crypto/x509/x509.c", "src/third_party/boringssl-with-bazel/src/crypto/x509/x509_att.c", "src/third_party/boringssl-with-bazel/src/crypto/x509/x509_cmp.c", @@ -3116,9 +3397,7 @@ static_library("boringssl") { "src/third_party/boringssl-with-bazel/src/crypto/x509/x_attrib.c", "src/third_party/boringssl-with-bazel/src/crypto/x509/x_crl.c", "src/third_party/boringssl-with-bazel/src/crypto/x509/x_exten.c", - "src/third_party/boringssl-with-bazel/src/crypto/x509/x_info.c", "src/third_party/boringssl-with-bazel/src/crypto/x509/x_name.c", - "src/third_party/boringssl-with-bazel/src/crypto/x509/x_pkey.c", "src/third_party/boringssl-with-bazel/src/crypto/x509/x_pubkey.c", "src/third_party/boringssl-with-bazel/src/crypto/x509/x_req.c", "src/third_party/boringssl-with-bazel/src/crypto/x509/x_sig.c", @@ -3126,42 +3405,131 @@ static_library("boringssl") { "src/third_party/boringssl-with-bazel/src/crypto/x509/x_val.c", "src/third_party/boringssl-with-bazel/src/crypto/x509/x_x509.c", "src/third_party/boringssl-with-bazel/src/crypto/x509/x_x509a.c", - "src/third_party/boringssl-with-bazel/src/crypto/x509v3/ext_dat.h", - "src/third_party/boringssl-with-bazel/src/crypto/x509v3/internal.h", - "src/third_party/boringssl-with-bazel/src/crypto/x509v3/pcy_cache.c", - "src/third_party/boringssl-with-bazel/src/crypto/x509v3/pcy_data.c", - "src/third_party/boringssl-with-bazel/src/crypto/x509v3/pcy_lib.c", - "src/third_party/boringssl-with-bazel/src/crypto/x509v3/pcy_map.c", - "src/third_party/boringssl-with-bazel/src/crypto/x509v3/pcy_node.c", - "src/third_party/boringssl-with-bazel/src/crypto/x509v3/pcy_tree.c", - "src/third_party/boringssl-with-bazel/src/crypto/x509v3/v3_akey.c", - "src/third_party/boringssl-with-bazel/src/crypto/x509v3/v3_akeya.c", - "src/third_party/boringssl-with-bazel/src/crypto/x509v3/v3_alt.c", - "src/third_party/boringssl-with-bazel/src/crypto/x509v3/v3_bcons.c", - "src/third_party/boringssl-with-bazel/src/crypto/x509v3/v3_bitst.c", - "src/third_party/boringssl-with-bazel/src/crypto/x509v3/v3_conf.c", - "src/third_party/boringssl-with-bazel/src/crypto/x509v3/v3_cpols.c", - "src/third_party/boringssl-with-bazel/src/crypto/x509v3/v3_crld.c", - "src/third_party/boringssl-with-bazel/src/crypto/x509v3/v3_enum.c", - "src/third_party/boringssl-with-bazel/src/crypto/x509v3/v3_extku.c", - "src/third_party/boringssl-with-bazel/src/crypto/x509v3/v3_genn.c", - "src/third_party/boringssl-with-bazel/src/crypto/x509v3/v3_ia5.c", - "src/third_party/boringssl-with-bazel/src/crypto/x509v3/v3_info.c", - "src/third_party/boringssl-with-bazel/src/crypto/x509v3/v3_int.c", - "src/third_party/boringssl-with-bazel/src/crypto/x509v3/v3_lib.c", - "src/third_party/boringssl-with-bazel/src/crypto/x509v3/v3_ncons.c", - "src/third_party/boringssl-with-bazel/src/crypto/x509v3/v3_ocsp.c", - "src/third_party/boringssl-with-bazel/src/crypto/x509v3/v3_pci.c", - "src/third_party/boringssl-with-bazel/src/crypto/x509v3/v3_pcia.c", - "src/third_party/boringssl-with-bazel/src/crypto/x509v3/v3_pcons.c", - "src/third_party/boringssl-with-bazel/src/crypto/x509v3/v3_pmaps.c", - "src/third_party/boringssl-with-bazel/src/crypto/x509v3/v3_prn.c", - "src/third_party/boringssl-with-bazel/src/crypto/x509v3/v3_purp.c", - "src/third_party/boringssl-with-bazel/src/crypto/x509v3/v3_skey.c", - "src/third_party/boringssl-with-bazel/src/crypto/x509v3/v3_utl.c", + "src/third_party/boringssl-with-bazel/src/gen/bcm/aesni-gcm-x86_64-apple.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/aesni-gcm-x86_64-linux.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/aesni-x86-apple.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/aesni-x86-linux.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/aesni-x86_64-apple.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/aesni-x86_64-linux.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/aesv8-armv7-linux.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/aesv8-armv8-apple.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/aesv8-armv8-linux.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/aesv8-armv8-win.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/aesv8-gcm-armv8-apple.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/aesv8-gcm-armv8-linux.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/aesv8-gcm-armv8-win.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/armv4-mont-linux.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/armv8-mont-apple.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/armv8-mont-linux.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/armv8-mont-win.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/bn-586-apple.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/bn-586-linux.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/bn-armv8-apple.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/bn-armv8-linux.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/bn-armv8-win.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/bsaes-armv7-linux.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/co-586-apple.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/co-586-linux.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/ghash-armv4-linux.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/ghash-neon-armv8-apple.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/ghash-neon-armv8-linux.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/ghash-neon-armv8-win.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/ghash-ssse3-x86-apple.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/ghash-ssse3-x86-linux.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/ghash-ssse3-x86_64-apple.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/ghash-ssse3-x86_64-linux.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/ghash-x86-apple.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/ghash-x86-linux.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/ghash-x86_64-apple.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/ghash-x86_64-linux.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/ghashv8-armv7-linux.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/ghashv8-armv8-apple.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/ghashv8-armv8-linux.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/ghashv8-armv8-win.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/md5-586-apple.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/md5-586-linux.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/md5-x86_64-apple.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/md5-x86_64-linux.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/p256-armv8-asm-apple.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/p256-armv8-asm-linux.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/p256-armv8-asm-win.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/p256-x86_64-asm-apple.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/p256-x86_64-asm-linux.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/p256_beeu-armv8-asm-apple.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/p256_beeu-armv8-asm-linux.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/p256_beeu-armv8-asm-win.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/p256_beeu-x86_64-asm-apple.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/p256_beeu-x86_64-asm-linux.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/rdrand-x86_64-apple.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/rdrand-x86_64-linux.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/rsaz-avx2-apple.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/rsaz-avx2-linux.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/sha1-586-apple.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/sha1-586-linux.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/sha1-armv4-large-linux.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/sha1-armv8-apple.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/sha1-armv8-linux.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/sha1-armv8-win.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/sha1-x86_64-apple.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/sha1-x86_64-linux.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/sha256-586-apple.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/sha256-586-linux.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/sha256-armv4-linux.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/sha256-armv8-apple.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/sha256-armv8-linux.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/sha256-armv8-win.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/sha256-x86_64-apple.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/sha256-x86_64-linux.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/sha512-586-apple.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/sha512-586-linux.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/sha512-armv4-linux.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/sha512-armv8-apple.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/sha512-armv8-linux.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/sha512-armv8-win.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/sha512-x86_64-apple.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/sha512-x86_64-linux.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/vpaes-armv7-linux.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/vpaes-armv8-apple.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/vpaes-armv8-linux.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/vpaes-armv8-win.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/vpaes-x86-apple.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/vpaes-x86-linux.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/vpaes-x86_64-apple.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/vpaes-x86_64-linux.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/x86-mont-apple.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/x86-mont-linux.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/x86_64-mont-apple.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/x86_64-mont-linux.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/x86_64-mont5-apple.S", + "src/third_party/boringssl-with-bazel/src/gen/bcm/x86_64-mont5-linux.S", + "src/third_party/boringssl-with-bazel/src/gen/crypto/aes128gcmsiv-x86_64-apple.S", + "src/third_party/boringssl-with-bazel/src/gen/crypto/aes128gcmsiv-x86_64-linux.S", + "src/third_party/boringssl-with-bazel/src/gen/crypto/chacha-armv4-linux.S", + "src/third_party/boringssl-with-bazel/src/gen/crypto/chacha-armv8-apple.S", + "src/third_party/boringssl-with-bazel/src/gen/crypto/chacha-armv8-linux.S", + "src/third_party/boringssl-with-bazel/src/gen/crypto/chacha-armv8-win.S", + "src/third_party/boringssl-with-bazel/src/gen/crypto/chacha-x86-apple.S", + "src/third_party/boringssl-with-bazel/src/gen/crypto/chacha-x86-linux.S", + "src/third_party/boringssl-with-bazel/src/gen/crypto/chacha-x86_64-apple.S", + "src/third_party/boringssl-with-bazel/src/gen/crypto/chacha-x86_64-linux.S", + "src/third_party/boringssl-with-bazel/src/gen/crypto/chacha20_poly1305_armv8-apple.S", + "src/third_party/boringssl-with-bazel/src/gen/crypto/chacha20_poly1305_armv8-linux.S", + "src/third_party/boringssl-with-bazel/src/gen/crypto/chacha20_poly1305_armv8-win.S", + "src/third_party/boringssl-with-bazel/src/gen/crypto/chacha20_poly1305_x86_64-apple.S", + "src/third_party/boringssl-with-bazel/src/gen/crypto/chacha20_poly1305_x86_64-linux.S", + "src/third_party/boringssl-with-bazel/src/gen/crypto/err_data.c", + "src/third_party/boringssl-with-bazel/src/gen/test_support/trampoline-armv4-linux.S", + "src/third_party/boringssl-with-bazel/src/gen/test_support/trampoline-armv8-apple.S", + "src/third_party/boringssl-with-bazel/src/gen/test_support/trampoline-armv8-linux.S", + "src/third_party/boringssl-with-bazel/src/gen/test_support/trampoline-armv8-win.S", + "src/third_party/boringssl-with-bazel/src/gen/test_support/trampoline-x86-apple.S", + "src/third_party/boringssl-with-bazel/src/gen/test_support/trampoline-x86-linux.S", + "src/third_party/boringssl-with-bazel/src/gen/test_support/trampoline-x86_64-apple.S", + "src/third_party/boringssl-with-bazel/src/gen/test_support/trampoline-x86_64-linux.S", "src/third_party/boringssl-with-bazel/src/include/openssl/aead.h", "src/third_party/boringssl-with-bazel/src/include/openssl/aes.h", "src/third_party/boringssl-with-bazel/src/include/openssl/arm_arch.h", + "src/third_party/boringssl-with-bazel/src/include/openssl/asm_base.h", "src/third_party/boringssl-with-bazel/src/include/openssl/asn1.h", "src/third_party/boringssl-with-bazel/src/include/openssl/asn1_mac.h", "src/third_party/boringssl-with-bazel/src/include/openssl/asn1t.h", @@ -3181,6 +3549,7 @@ static_library("boringssl") { "src/third_party/boringssl-with-bazel/src/include/openssl/conf.h", "src/third_party/boringssl-with-bazel/src/include/openssl/cpu.h", "src/third_party/boringssl-with-bazel/src/include/openssl/crypto.h", + "src/third_party/boringssl-with-bazel/src/include/openssl/ctrdrbg.h", "src/third_party/boringssl-with-bazel/src/include/openssl/curve25519.h", "src/third_party/boringssl-with-bazel/src/include/openssl/des.h", "src/third_party/boringssl-with-bazel/src/include/openssl/dh.h", @@ -3197,11 +3566,14 @@ static_library("boringssl") { "src/third_party/boringssl-with-bazel/src/include/openssl/evp.h", "src/third_party/boringssl-with-bazel/src/include/openssl/evp_errors.h", "src/third_party/boringssl-with-bazel/src/include/openssl/ex_data.h", + "src/third_party/boringssl-with-bazel/src/include/openssl/experimental/kyber.h", + "src/third_party/boringssl-with-bazel/src/include/openssl/experimental/spx.h", "src/third_party/boringssl-with-bazel/src/include/openssl/hkdf.h", "src/third_party/boringssl-with-bazel/src/include/openssl/hmac.h", "src/third_party/boringssl-with-bazel/src/include/openssl/hpke.h", "src/third_party/boringssl-with-bazel/src/include/openssl/hrss.h", "src/third_party/boringssl-with-bazel/src/include/openssl/is_boringssl.h", + "src/third_party/boringssl-with-bazel/src/include/openssl/kdf.h", "src/third_party/boringssl-with-bazel/src/include/openssl/lhash.h", "src/third_party/boringssl-with-bazel/src/include/openssl/md4.h", "src/third_party/boringssl-with-bazel/src/include/openssl/md5.h", @@ -3219,11 +3591,13 @@ static_library("boringssl") { "src/third_party/boringssl-with-bazel/src/include/openssl/pkcs8.h", "src/third_party/boringssl-with-bazel/src/include/openssl/poly1305.h", "src/third_party/boringssl-with-bazel/src/include/openssl/pool.h", + "src/third_party/boringssl-with-bazel/src/include/openssl/posix_time.h", "src/third_party/boringssl-with-bazel/src/include/openssl/rand.h", "src/third_party/boringssl-with-bazel/src/include/openssl/rc4.h", "src/third_party/boringssl-with-bazel/src/include/openssl/ripemd.h", "src/third_party/boringssl-with-bazel/src/include/openssl/rsa.h", "src/third_party/boringssl-with-bazel/src/include/openssl/safestack.h", + "src/third_party/boringssl-with-bazel/src/include/openssl/service_indicator.h", "src/third_party/boringssl-with-bazel/src/include/openssl/sha.h", "src/third_party/boringssl-with-bazel/src/include/openssl/siphash.h", "src/third_party/boringssl-with-bazel/src/include/openssl/span.h", @@ -3231,13 +3605,16 @@ static_library("boringssl") { "src/third_party/boringssl-with-bazel/src/include/openssl/ssl.h", "src/third_party/boringssl-with-bazel/src/include/openssl/ssl3.h", "src/third_party/boringssl-with-bazel/src/include/openssl/stack.h", + "src/third_party/boringssl-with-bazel/src/include/openssl/target.h", "src/third_party/boringssl-with-bazel/src/include/openssl/thread.h", + "src/third_party/boringssl-with-bazel/src/include/openssl/time.h", "src/third_party/boringssl-with-bazel/src/include/openssl/tls1.h", "src/third_party/boringssl-with-bazel/src/include/openssl/trust_token.h", "src/third_party/boringssl-with-bazel/src/include/openssl/type_check.h", "src/third_party/boringssl-with-bazel/src/include/openssl/x509.h", "src/third_party/boringssl-with-bazel/src/include/openssl/x509_vfy.h", "src/third_party/boringssl-with-bazel/src/include/openssl/x509v3.h", + "src/third_party/boringssl-with-bazel/src/include/openssl/x509v3_errors.h", "src/third_party/boringssl-with-bazel/src/ssl/bio_ssl.cc", "src/third_party/boringssl-with-bazel/src/ssl/d1_both.cc", "src/third_party/boringssl-with-bazel/src/ssl/d1_lib.cc", @@ -3260,6 +3637,7 @@ static_library("boringssl") { "src/third_party/boringssl-with-bazel/src/ssl/ssl_buffer.cc", "src/third_party/boringssl-with-bazel/src/ssl/ssl_cert.cc", "src/third_party/boringssl-with-bazel/src/ssl/ssl_cipher.cc", + "src/third_party/boringssl-with-bazel/src/ssl/ssl_credential.cc", "src/third_party/boringssl-with-bazel/src/ssl/ssl_file.cc", "src/third_party/boringssl-with-bazel/src/ssl/ssl_key_share.cc", "src/third_party/boringssl-with-bazel/src/ssl/ssl_lib.cc", @@ -3276,14 +3654,22 @@ static_library("boringssl") { "src/third_party/boringssl-with-bazel/src/ssl/tls13_server.cc", "src/third_party/boringssl-with-bazel/src/ssl/tls_method.cc", "src/third_party/boringssl-with-bazel/src/ssl/tls_record.cc", + "src/third_party/boringssl-with-bazel/src/third_party/fiat/asm/fiat_curve25519_adx_mul.S", + "src/third_party/boringssl-with-bazel/src/third_party/fiat/asm/fiat_curve25519_adx_square.S", + "src/third_party/boringssl-with-bazel/src/third_party/fiat/asm/fiat_p256_adx_mul.S", + "src/third_party/boringssl-with-bazel/src/third_party/fiat/asm/fiat_p256_adx_sqr.S", "src/third_party/boringssl-with-bazel/src/third_party/fiat/curve25519_32.h", "src/third_party/boringssl-with-bazel/src/third_party/fiat/curve25519_64.h", + "src/third_party/boringssl-with-bazel/src/third_party/fiat/curve25519_64_adx.h", + "src/third_party/boringssl-with-bazel/src/third_party/fiat/curve25519_64_msvc.h", "src/third_party/boringssl-with-bazel/src/third_party/fiat/p256_32.h", "src/third_party/boringssl-with-bazel/src/third_party/fiat/p256_64.h", + "src/third_party/boringssl-with-bazel/src/third_party/fiat/p256_64_msvc.h", ] public_deps = [] public_configs = [ "..:grpc_boringssl_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("address_sorting") { @@ -3297,84 +3683,91 @@ source_set("address_sorting") { public_deps = [] public_configs = [ "..:grpc_internal_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("gpr") { sources = [ + "src/src/core/lib/config/config_vars.cc", + "src/src/core/lib/config/config_vars.h", + "src/src/core/lib/config/config_vars_non_generated.cc", + "src/src/core/lib/config/load_config.cc", + "src/src/core/lib/config/load_config.h", "src/src/core/lib/event_engine/thread_local.cc", "src/src/core/lib/event_engine/thread_local.h", "src/src/core/lib/gpr/alloc.cc", "src/src/core/lib/gpr/alloc.h", + "src/src/core/lib/gpr/android/log.cc", "src/src/core/lib/gpr/atm.cc", - "src/src/core/lib/gpr/cpu_iphone.cc", - "src/src/core/lib/gpr/cpu_linux.cc", - "src/src/core/lib/gpr/cpu_posix.cc", - "src/src/core/lib/gpr/cpu_windows.cc", + "src/src/core/lib/gpr/iphone/cpu.cc", + "src/src/core/lib/gpr/linux/cpu.cc", + "src/src/core/lib/gpr/linux/log.cc", "src/src/core/lib/gpr/log.cc", - "src/src/core/lib/gpr/log_android.cc", - "src/src/core/lib/gpr/log_linux.cc", - "src/src/core/lib/gpr/log_posix.cc", - "src/src/core/lib/gpr/log_windows.cc", + "src/src/core/lib/gpr/msys/tmpfile.cc", + "src/src/core/lib/gpr/posix/cpu.cc", + "src/src/core/lib/gpr/posix/log.cc", + "src/src/core/lib/gpr/posix/string.cc", + "src/src/core/lib/gpr/posix/sync.cc", + "src/src/core/lib/gpr/posix/time.cc", + "src/src/core/lib/gpr/posix/tmpfile.cc", "src/src/core/lib/gpr/string.cc", "src/src/core/lib/gpr/string.h", - "src/src/core/lib/gpr/string_posix.cc", - "src/src/core/lib/gpr/string_util_windows.cc", - "src/src/core/lib/gpr/string_windows.cc", "src/src/core/lib/gpr/sync.cc", "src/src/core/lib/gpr/sync_abseil.cc", - "src/src/core/lib/gpr/sync_posix.cc", - "src/src/core/lib/gpr/sync_windows.cc", "src/src/core/lib/gpr/time.cc", - "src/src/core/lib/gpr/time_posix.cc", "src/src/core/lib/gpr/time_precise.cc", "src/src/core/lib/gpr/time_precise.h", - "src/src/core/lib/gpr/time_windows.cc", "src/src/core/lib/gpr/tmpfile.h", - "src/src/core/lib/gpr/tmpfile_msys.cc", - "src/src/core/lib/gpr/tmpfile_posix.cc", - "src/src/core/lib/gpr/tmpfile_windows.cc", "src/src/core/lib/gpr/useful.h", - "src/src/core/lib/gpr/wrap_memcpy.cc", + "src/src/core/lib/gpr/windows/cpu.cc", + "src/src/core/lib/gpr/windows/log.cc", + "src/src/core/lib/gpr/windows/string.cc", + "src/src/core/lib/gpr/windows/string_util.cc", + "src/src/core/lib/gpr/windows/sync.cc", + "src/src/core/lib/gpr/windows/time.cc", + "src/src/core/lib/gpr/windows/tmpfile.cc", "src/src/core/lib/gprpp/construct_destruct.h", "src/src/core/lib/gprpp/crash.cc", "src/src/core/lib/gprpp/crash.h", "src/src/core/lib/gprpp/debug_location.h", "src/src/core/lib/gprpp/env.h", - "src/src/core/lib/gprpp/env_linux.cc", - "src/src/core/lib/gprpp/env_posix.cc", - "src/src/core/lib/gprpp/env_windows.cc", "src/src/core/lib/gprpp/examine_stack.cc", "src/src/core/lib/gprpp/examine_stack.h", "src/src/core/lib/gprpp/fork.cc", "src/src/core/lib/gprpp/fork.h", - "src/src/core/lib/gprpp/global_config.h", - "src/src/core/lib/gprpp/global_config_custom.h", - "src/src/core/lib/gprpp/global_config_env.cc", - "src/src/core/lib/gprpp/global_config_env.h", - "src/src/core/lib/gprpp/global_config_generic.h", "src/src/core/lib/gprpp/host_port.cc", "src/src/core/lib/gprpp/host_port.h", + "src/src/core/lib/gprpp/linux/env.cc", "src/src/core/lib/gprpp/memory.h", "src/src/core/lib/gprpp/mpscq.cc", "src/src/core/lib/gprpp/mpscq.h", "src/src/core/lib/gprpp/no_destruct.h", + "src/src/core/lib/gprpp/posix/env.cc", + "src/src/core/lib/gprpp/posix/stat.cc", + "src/src/core/lib/gprpp/posix/thd.cc", "src/src/core/lib/gprpp/stat.h", - "src/src/core/lib/gprpp/stat_posix.cc", - "src/src/core/lib/gprpp/stat_windows.cc", "src/src/core/lib/gprpp/strerror.cc", "src/src/core/lib/gprpp/strerror.h", "src/src/core/lib/gprpp/sync.h", "src/src/core/lib/gprpp/tchar.cc", "src/src/core/lib/gprpp/tchar.h", "src/src/core/lib/gprpp/thd.h", - "src/src/core/lib/gprpp/thd_posix.cc", - "src/src/core/lib/gprpp/thd_windows.cc", "src/src/core/lib/gprpp/time_util.cc", "src/src/core/lib/gprpp/time_util.h", + "src/src/core/lib/gprpp/windows/env.cc", + "src/src/core/lib/gprpp/windows/stat.cc", + "src/src/core/lib/gprpp/windows/thd.cc", ] public_deps = [ ":absl_base_base", ":absl_base_core_headers", + ":absl_base_log_severity", + ":absl_flags_flag", + ":absl_flags_marshalling", + ":absl_functional_any_invocable", + ":absl_log_check", + ":absl_log_globals", + ":absl_log_log", ":absl_memory_memory", ":absl_random_random", ":absl_status_status", @@ -3388,120 +3781,59 @@ source_set("gpr") { ] public_configs = [ "..:grpc_internal_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("grpc") { sources = [ + "src/src/core/channelz/channel_trace.cc", + "src/src/core/channelz/channel_trace.h", + "src/src/core/channelz/channelz.cc", + "src/src/core/channelz/channelz.h", + "src/src/core/channelz/channelz_registry.cc", + "src/src/core/channelz/channelz_registry.h", + "src/src/core/client_channel/backup_poller.cc", + "src/src/core/client_channel/backup_poller.h", + "src/src/core/client_channel/client_channel_factory.cc", + "src/src/core/client_channel/client_channel_factory.h", + "src/src/core/client_channel/client_channel_filter.cc", + "src/src/core/client_channel/client_channel_filter.h", + "src/src/core/client_channel/client_channel_internal.h", + "src/src/core/client_channel/client_channel_plugin.cc", + "src/src/core/client_channel/client_channel_service_config.cc", + "src/src/core/client_channel/client_channel_service_config.h", + "src/src/core/client_channel/config_selector.cc", + "src/src/core/client_channel/config_selector.h", + "src/src/core/client_channel/connector.h", + "src/src/core/client_channel/dynamic_filters.cc", + "src/src/core/client_channel/dynamic_filters.h", + "src/src/core/client_channel/global_subchannel_pool.cc", + "src/src/core/client_channel/global_subchannel_pool.h", + "src/src/core/client_channel/local_subchannel_pool.cc", + "src/src/core/client_channel/local_subchannel_pool.h", + "src/src/core/client_channel/retry_filter.cc", + "src/src/core/client_channel/retry_filter.h", + "src/src/core/client_channel/retry_filter_legacy_call_data.cc", + "src/src/core/client_channel/retry_filter_legacy_call_data.h", + "src/src/core/client_channel/retry_service_config.cc", + "src/src/core/client_channel/retry_service_config.h", + "src/src/core/client_channel/retry_throttle.cc", + "src/src/core/client_channel/retry_throttle.h", + "src/src/core/client_channel/subchannel.cc", + "src/src/core/client_channel/subchannel.h", + "src/src/core/client_channel/subchannel_interface_internal.h", + "src/src/core/client_channel/subchannel_pool_interface.cc", + "src/src/core/client_channel/subchannel_pool_interface.h", + "src/src/core/client_channel/subchannel_stream_client.cc", + "src/src/core/client_channel/subchannel_stream_client.h", + "src/src/core/ext/filters/backend_metrics/backend_metric_filter.cc", + "src/src/core/ext/filters/backend_metrics/backend_metric_filter.h", + "src/src/core/ext/filters/backend_metrics/backend_metric_provider.h", "src/src/core/ext/filters/census/grpc_context.cc", - "src/src/core/ext/filters/channel_idle/channel_idle_filter.cc", - "src/src/core/ext/filters/channel_idle/channel_idle_filter.h", "src/src/core/ext/filters/channel_idle/idle_filter_state.cc", "src/src/core/ext/filters/channel_idle/idle_filter_state.h", - "src/src/core/ext/filters/client_channel/backend_metric.cc", - "src/src/core/ext/filters/client_channel/backend_metric.h", - "src/src/core/ext/filters/client_channel/backup_poller.cc", - "src/src/core/ext/filters/client_channel/backup_poller.h", - "src/src/core/ext/filters/client_channel/channel_connectivity.cc", - "src/src/core/ext/filters/client_channel/client_channel.cc", - "src/src/core/ext/filters/client_channel/client_channel.h", - "src/src/core/ext/filters/client_channel/client_channel_channelz.cc", - "src/src/core/ext/filters/client_channel/client_channel_channelz.h", - "src/src/core/ext/filters/client_channel/client_channel_factory.cc", - "src/src/core/ext/filters/client_channel/client_channel_factory.h", - "src/src/core/ext/filters/client_channel/client_channel_plugin.cc", - "src/src/core/ext/filters/client_channel/client_channel_service_config.cc", - "src/src/core/ext/filters/client_channel/client_channel_service_config.h", - "src/src/core/ext/filters/client_channel/config_selector.cc", - "src/src/core/ext/filters/client_channel/config_selector.h", - "src/src/core/ext/filters/client_channel/connector.h", - "src/src/core/ext/filters/client_channel/dynamic_filters.cc", - "src/src/core/ext/filters/client_channel/dynamic_filters.h", - "src/src/core/ext/filters/client_channel/global_subchannel_pool.cc", - "src/src/core/ext/filters/client_channel/global_subchannel_pool.h", - "src/src/core/ext/filters/client_channel/health/health_check_client.cc", - "src/src/core/ext/filters/client_channel/health/health_check_client.h", - "src/src/core/ext/filters/client_channel/http_proxy.cc", - "src/src/core/ext/filters/client_channel/http_proxy.h", - "src/src/core/ext/filters/client_channel/lb_call_state_internal.h", - "src/src/core/ext/filters/client_channel/lb_policy/address_filtering.cc", - "src/src/core/ext/filters/client_channel/lb_policy/address_filtering.h", - "src/src/core/ext/filters/client_channel/lb_policy/backend_metric_data.h", - "src/src/core/ext/filters/client_channel/lb_policy/child_policy_handler.cc", - "src/src/core/ext/filters/client_channel/lb_policy/child_policy_handler.h", - "src/src/core/ext/filters/client_channel/lb_policy/grpclb/client_load_reporting_filter.cc", - "src/src/core/ext/filters/client_channel/lb_policy/grpclb/client_load_reporting_filter.h", - "src/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb.cc", - "src/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb.h", - "src/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_balancer_addresses.cc", - "src/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_balancer_addresses.h", - "src/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_client_stats.cc", - "src/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_client_stats.h", - "src/src/core/ext/filters/client_channel/lb_policy/grpclb/load_balancer_api.cc", - "src/src/core/ext/filters/client_channel/lb_policy/grpclb/load_balancer_api.h", - "src/src/core/ext/filters/client_channel/lb_policy/oob_backend_metric.cc", - "src/src/core/ext/filters/client_channel/lb_policy/oob_backend_metric.h", - "src/src/core/ext/filters/client_channel/lb_policy/oob_backend_metric_internal.h", - "src/src/core/ext/filters/client_channel/lb_policy/outlier_detection/outlier_detection.cc", - "src/src/core/ext/filters/client_channel/lb_policy/outlier_detection/outlier_detection.h", - "src/src/core/ext/filters/client_channel/lb_policy/pick_first/pick_first.cc", - "src/src/core/ext/filters/client_channel/lb_policy/priority/priority.cc", - "src/src/core/ext/filters/client_channel/lb_policy/ring_hash/ring_hash.cc", - "src/src/core/ext/filters/client_channel/lb_policy/ring_hash/ring_hash.h", - "src/src/core/ext/filters/client_channel/lb_policy/rls/rls.cc", - "src/src/core/ext/filters/client_channel/lb_policy/round_robin/round_robin.cc", - "src/src/core/ext/filters/client_channel/lb_policy/subchannel_list.h", - "src/src/core/ext/filters/client_channel/lb_policy/weighted_round_robin/static_stride_scheduler.cc", - "src/src/core/ext/filters/client_channel/lb_policy/weighted_round_robin/static_stride_scheduler.h", - "src/src/core/ext/filters/client_channel/lb_policy/weighted_round_robin/weighted_round_robin.cc", - "src/src/core/ext/filters/client_channel/lb_policy/weighted_target/weighted_target.cc", - "src/src/core/ext/filters/client_channel/lb_policy/xds/cds.cc", - "src/src/core/ext/filters/client_channel/lb_policy/xds/xds_attributes.cc", - "src/src/core/ext/filters/client_channel/lb_policy/xds/xds_attributes.h", - "src/src/core/ext/filters/client_channel/lb_policy/xds/xds_channel_args.h", - "src/src/core/ext/filters/client_channel/lb_policy/xds/xds_cluster_impl.cc", - "src/src/core/ext/filters/client_channel/lb_policy/xds/xds_cluster_manager.cc", - "src/src/core/ext/filters/client_channel/lb_policy/xds/xds_cluster_resolver.cc", - "src/src/core/ext/filters/client_channel/lb_policy/xds/xds_override_host.cc", - "src/src/core/ext/filters/client_channel/lb_policy/xds/xds_override_host.h", - "src/src/core/ext/filters/client_channel/lb_policy/xds/xds_wrr_locality.cc", - "src/src/core/ext/filters/client_channel/local_subchannel_pool.cc", - "src/src/core/ext/filters/client_channel/local_subchannel_pool.h", - "src/src/core/ext/filters/client_channel/resolver/binder/binder_resolver.cc", - "src/src/core/ext/filters/client_channel/resolver/dns/c_ares/dns_resolver_ares.cc", - "src/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver.h", - "src/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_posix.cc", - "src/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_windows.cc", - "src/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.cc", - "src/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.h", - "src/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_posix.cc", - "src/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_windows.cc", - "src/src/core/ext/filters/client_channel/resolver/dns/dns_resolver_selection.cc", - "src/src/core/ext/filters/client_channel/resolver/dns/dns_resolver_selection.h", - "src/src/core/ext/filters/client_channel/resolver/dns/native/dns_resolver.cc", - "src/src/core/ext/filters/client_channel/resolver/fake/fake_resolver.cc", - "src/src/core/ext/filters/client_channel/resolver/fake/fake_resolver.h", - "src/src/core/ext/filters/client_channel/resolver/google_c2p/google_c2p_resolver.cc", - "src/src/core/ext/filters/client_channel/resolver/polling_resolver.cc", - "src/src/core/ext/filters/client_channel/resolver/polling_resolver.h", - "src/src/core/ext/filters/client_channel/resolver/sockaddr/sockaddr_resolver.cc", - "src/src/core/ext/filters/client_channel/resolver/xds/xds_resolver.cc", - "src/src/core/ext/filters/client_channel/resolver/xds/xds_resolver.h", - "src/src/core/ext/filters/client_channel/retry_filter.cc", - "src/src/core/ext/filters/client_channel/retry_filter.h", - "src/src/core/ext/filters/client_channel/retry_service_config.cc", - "src/src/core/ext/filters/client_channel/retry_service_config.h", - "src/src/core/ext/filters/client_channel/retry_throttle.cc", - "src/src/core/ext/filters/client_channel/retry_throttle.h", - "src/src/core/ext/filters/client_channel/service_config_channel_arg_filter.cc", - "src/src/core/ext/filters/client_channel/subchannel.cc", - "src/src/core/ext/filters/client_channel/subchannel.h", - "src/src/core/ext/filters/client_channel/subchannel_interface_internal.h", - "src/src/core/ext/filters/client_channel/subchannel_pool_interface.cc", - "src/src/core/ext/filters/client_channel/subchannel_pool_interface.h", - "src/src/core/ext/filters/client_channel/subchannel_stream_client.cc", - "src/src/core/ext/filters/client_channel/subchannel_stream_client.h", - "src/src/core/ext/filters/deadline/deadline_filter.cc", - "src/src/core/ext/filters/deadline/deadline_filter.h", + "src/src/core/ext/filters/channel_idle/legacy_channel_idle_filter.cc", + "src/src/core/ext/filters/channel_idle/legacy_channel_idle_filter.h", "src/src/core/ext/filters/fault_injection/fault_injection_filter.cc", "src/src/core/ext/filters/fault_injection/fault_injection_filter.h", "src/src/core/ext/filters/fault_injection/fault_injection_service_config_parser.cc", @@ -3521,13 +3853,12 @@ source_set("grpc") { "src/src/core/ext/filters/rbac/rbac_filter.h", "src/src/core/ext/filters/rbac/rbac_service_config_parser.cc", "src/src/core/ext/filters/rbac/rbac_service_config_parser.h", - "src/src/core/ext/filters/server_config_selector/server_config_selector.h", - "src/src/core/ext/filters/server_config_selector/server_config_selector_filter.cc", - "src/src/core/ext/filters/server_config_selector/server_config_selector_filter.h", "src/src/core/ext/filters/stateful_session/stateful_session_filter.cc", "src/src/core/ext/filters/stateful_session/stateful_session_filter.h", "src/src/core/ext/filters/stateful_session/stateful_session_service_config_parser.cc", "src/src/core/ext/filters/stateful_session/stateful_session_service_config_parser.h", + "src/src/core/ext/gcp/metadata_query.cc", + "src/src/core/ext/gcp/metadata_query.h", "src/src/core/ext/transport/chttp2/alpn/alpn.cc", "src/src/core/ext/transport/chttp2/alpn/alpn.h", "src/src/core/ext/transport/chttp2/client/chttp2_connector.cc", @@ -3540,12 +3871,12 @@ source_set("grpc") { "src/src/core/ext/transport/chttp2/transport/bin_encoder.h", "src/src/core/ext/transport/chttp2/transport/chttp2_transport.cc", "src/src/core/ext/transport/chttp2/transport/chttp2_transport.h", - "src/src/core/ext/transport/chttp2/transport/context_list.cc", - "src/src/core/ext/transport/chttp2/transport/context_list.h", + "src/src/core/ext/transport/chttp2/transport/context_list_entry.h", "src/src/core/ext/transport/chttp2/transport/decode_huff.cc", "src/src/core/ext/transport/chttp2/transport/decode_huff.h", "src/src/core/ext/transport/chttp2/transport/flow_control.cc", "src/src/core/ext/transport/chttp2/transport/flow_control.h", + "src/src/core/ext/transport/chttp2/transport/frame.cc", "src/src/core/ext/transport/chttp2/transport/frame.h", "src/src/core/ext/transport/chttp2/transport/frame_data.cc", "src/src/core/ext/transport/chttp2/transport/frame_data.h", @@ -3564,6 +3895,8 @@ source_set("grpc") { "src/src/core/ext/transport/chttp2/transport/hpack_encoder.h", "src/src/core/ext/transport/chttp2/transport/hpack_encoder_table.cc", "src/src/core/ext/transport/chttp2/transport/hpack_encoder_table.h", + "src/src/core/ext/transport/chttp2/transport/hpack_parse_result.cc", + "src/src/core/ext/transport/chttp2/transport/hpack_parse_result.h", "src/src/core/ext/transport/chttp2/transport/hpack_parser.cc", "src/src/core/ext/transport/chttp2/transport/hpack_parser.h", "src/src/core/ext/transport/chttp2/transport/hpack_parser_table.cc", @@ -3575,690 +3908,848 @@ source_set("grpc") { "src/src/core/ext/transport/chttp2/transport/huffsyms.cc", "src/src/core/ext/transport/chttp2/transport/huffsyms.h", "src/src/core/ext/transport/chttp2/transport/internal.h", + "src/src/core/ext/transport/chttp2/transport/legacy_frame.h", + "src/src/core/ext/transport/chttp2/transport/max_concurrent_streams_policy.cc", + "src/src/core/ext/transport/chttp2/transport/max_concurrent_streams_policy.h", "src/src/core/ext/transport/chttp2/transport/parsing.cc", + "src/src/core/ext/transport/chttp2/transport/ping_abuse_policy.cc", + "src/src/core/ext/transport/chttp2/transport/ping_abuse_policy.h", + "src/src/core/ext/transport/chttp2/transport/ping_callbacks.cc", + "src/src/core/ext/transport/chttp2/transport/ping_callbacks.h", + "src/src/core/ext/transport/chttp2/transport/ping_rate_policy.cc", + "src/src/core/ext/transport/chttp2/transport/ping_rate_policy.h", "src/src/core/ext/transport/chttp2/transport/stream_lists.cc", - "src/src/core/ext/transport/chttp2/transport/stream_map.cc", - "src/src/core/ext/transport/chttp2/transport/stream_map.h", "src/src/core/ext/transport/chttp2/transport/varint.cc", "src/src/core/ext/transport/chttp2/transport/varint.h", + "src/src/core/ext/transport/chttp2/transport/write_size_policy.cc", + "src/src/core/ext/transport/chttp2/transport/write_size_policy.h", "src/src/core/ext/transport/chttp2/transport/writing.cc", "src/src/core/ext/transport/inproc/inproc_plugin.cc", "src/src/core/ext/transport/inproc/inproc_transport.cc", "src/src/core/ext/transport/inproc/inproc_transport.h", - "src/src/core/ext/upb-generated/envoy/admin/v3/certs.upb.c", - "src/src/core/ext/upb-generated/envoy/admin/v3/certs.upb.h", - "src/src/core/ext/upb-generated/envoy/admin/v3/clusters.upb.c", - "src/src/core/ext/upb-generated/envoy/admin/v3/clusters.upb.h", - "src/src/core/ext/upb-generated/envoy/admin/v3/config_dump.upb.c", - "src/src/core/ext/upb-generated/envoy/admin/v3/config_dump.upb.h", - "src/src/core/ext/upb-generated/envoy/admin/v3/config_dump_shared.upb.c", - "src/src/core/ext/upb-generated/envoy/admin/v3/config_dump_shared.upb.h", - "src/src/core/ext/upb-generated/envoy/admin/v3/init_dump.upb.c", - "src/src/core/ext/upb-generated/envoy/admin/v3/init_dump.upb.h", - "src/src/core/ext/upb-generated/envoy/admin/v3/listeners.upb.c", - "src/src/core/ext/upb-generated/envoy/admin/v3/listeners.upb.h", - "src/src/core/ext/upb-generated/envoy/admin/v3/memory.upb.c", - "src/src/core/ext/upb-generated/envoy/admin/v3/memory.upb.h", - "src/src/core/ext/upb-generated/envoy/admin/v3/metrics.upb.c", - "src/src/core/ext/upb-generated/envoy/admin/v3/metrics.upb.h", - "src/src/core/ext/upb-generated/envoy/admin/v3/mutex_stats.upb.c", - "src/src/core/ext/upb-generated/envoy/admin/v3/mutex_stats.upb.h", - "src/src/core/ext/upb-generated/envoy/admin/v3/server_info.upb.c", - "src/src/core/ext/upb-generated/envoy/admin/v3/server_info.upb.h", - "src/src/core/ext/upb-generated/envoy/admin/v3/tap.upb.c", - "src/src/core/ext/upb-generated/envoy/admin/v3/tap.upb.h", - "src/src/core/ext/upb-generated/envoy/annotations/deprecation.upb.c", - "src/src/core/ext/upb-generated/envoy/annotations/deprecation.upb.h", - "src/src/core/ext/upb-generated/envoy/annotations/resource.upb.c", - "src/src/core/ext/upb-generated/envoy/annotations/resource.upb.h", - "src/src/core/ext/upb-generated/envoy/config/accesslog/v3/accesslog.upb.c", - "src/src/core/ext/upb-generated/envoy/config/accesslog/v3/accesslog.upb.h", - "src/src/core/ext/upb-generated/envoy/config/bootstrap/v3/bootstrap.upb.c", - "src/src/core/ext/upb-generated/envoy/config/bootstrap/v3/bootstrap.upb.h", - "src/src/core/ext/upb-generated/envoy/config/cluster/v3/circuit_breaker.upb.c", - "src/src/core/ext/upb-generated/envoy/config/cluster/v3/circuit_breaker.upb.h", - "src/src/core/ext/upb-generated/envoy/config/cluster/v3/cluster.upb.c", - "src/src/core/ext/upb-generated/envoy/config/cluster/v3/cluster.upb.h", - "src/src/core/ext/upb-generated/envoy/config/cluster/v3/filter.upb.c", - "src/src/core/ext/upb-generated/envoy/config/cluster/v3/filter.upb.h", - "src/src/core/ext/upb-generated/envoy/config/cluster/v3/outlier_detection.upb.c", - "src/src/core/ext/upb-generated/envoy/config/cluster/v3/outlier_detection.upb.h", - "src/src/core/ext/upb-generated/envoy/config/common/matcher/v3/matcher.upb.c", - "src/src/core/ext/upb-generated/envoy/config/common/matcher/v3/matcher.upb.h", - "src/src/core/ext/upb-generated/envoy/config/core/v3/address.upb.c", - "src/src/core/ext/upb-generated/envoy/config/core/v3/address.upb.h", - "src/src/core/ext/upb-generated/envoy/config/core/v3/backoff.upb.c", - "src/src/core/ext/upb-generated/envoy/config/core/v3/backoff.upb.h", - "src/src/core/ext/upb-generated/envoy/config/core/v3/base.upb.c", - "src/src/core/ext/upb-generated/envoy/config/core/v3/base.upb.h", - "src/src/core/ext/upb-generated/envoy/config/core/v3/config_source.upb.c", - "src/src/core/ext/upb-generated/envoy/config/core/v3/config_source.upb.h", - "src/src/core/ext/upb-generated/envoy/config/core/v3/event_service_config.upb.c", - "src/src/core/ext/upb-generated/envoy/config/core/v3/event_service_config.upb.h", - "src/src/core/ext/upb-generated/envoy/config/core/v3/extension.upb.c", - "src/src/core/ext/upb-generated/envoy/config/core/v3/extension.upb.h", - "src/src/core/ext/upb-generated/envoy/config/core/v3/grpc_method_list.upb.c", - "src/src/core/ext/upb-generated/envoy/config/core/v3/grpc_method_list.upb.h", - "src/src/core/ext/upb-generated/envoy/config/core/v3/grpc_service.upb.c", - "src/src/core/ext/upb-generated/envoy/config/core/v3/grpc_service.upb.h", - "src/src/core/ext/upb-generated/envoy/config/core/v3/health_check.upb.c", - "src/src/core/ext/upb-generated/envoy/config/core/v3/health_check.upb.h", - "src/src/core/ext/upb-generated/envoy/config/core/v3/http_uri.upb.c", - "src/src/core/ext/upb-generated/envoy/config/core/v3/http_uri.upb.h", - "src/src/core/ext/upb-generated/envoy/config/core/v3/protocol.upb.c", - "src/src/core/ext/upb-generated/envoy/config/core/v3/protocol.upb.h", - "src/src/core/ext/upb-generated/envoy/config/core/v3/proxy_protocol.upb.c", - "src/src/core/ext/upb-generated/envoy/config/core/v3/proxy_protocol.upb.h", - "src/src/core/ext/upb-generated/envoy/config/core/v3/resolver.upb.c", - "src/src/core/ext/upb-generated/envoy/config/core/v3/resolver.upb.h", - "src/src/core/ext/upb-generated/envoy/config/core/v3/socket_option.upb.c", - "src/src/core/ext/upb-generated/envoy/config/core/v3/socket_option.upb.h", - "src/src/core/ext/upb-generated/envoy/config/core/v3/substitution_format_string.upb.c", - "src/src/core/ext/upb-generated/envoy/config/core/v3/substitution_format_string.upb.h", - "src/src/core/ext/upb-generated/envoy/config/core/v3/udp_socket_config.upb.c", - "src/src/core/ext/upb-generated/envoy/config/core/v3/udp_socket_config.upb.h", - "src/src/core/ext/upb-generated/envoy/config/endpoint/v3/endpoint.upb.c", - "src/src/core/ext/upb-generated/envoy/config/endpoint/v3/endpoint.upb.h", - "src/src/core/ext/upb-generated/envoy/config/endpoint/v3/endpoint_components.upb.c", - "src/src/core/ext/upb-generated/envoy/config/endpoint/v3/endpoint_components.upb.h", - "src/src/core/ext/upb-generated/envoy/config/endpoint/v3/load_report.upb.c", - "src/src/core/ext/upb-generated/envoy/config/endpoint/v3/load_report.upb.h", - "src/src/core/ext/upb-generated/envoy/config/listener/v3/api_listener.upb.c", - "src/src/core/ext/upb-generated/envoy/config/listener/v3/api_listener.upb.h", - "src/src/core/ext/upb-generated/envoy/config/listener/v3/listener.upb.c", - "src/src/core/ext/upb-generated/envoy/config/listener/v3/listener.upb.h", - "src/src/core/ext/upb-generated/envoy/config/listener/v3/listener_components.upb.c", - "src/src/core/ext/upb-generated/envoy/config/listener/v3/listener_components.upb.h", - "src/src/core/ext/upb-generated/envoy/config/listener/v3/quic_config.upb.c", - "src/src/core/ext/upb-generated/envoy/config/listener/v3/quic_config.upb.h", - "src/src/core/ext/upb-generated/envoy/config/listener/v3/udp_listener_config.upb.c", - "src/src/core/ext/upb-generated/envoy/config/listener/v3/udp_listener_config.upb.h", - "src/src/core/ext/upb-generated/envoy/config/metrics/v3/metrics_service.upb.c", - "src/src/core/ext/upb-generated/envoy/config/metrics/v3/metrics_service.upb.h", - "src/src/core/ext/upb-generated/envoy/config/metrics/v3/stats.upb.c", - "src/src/core/ext/upb-generated/envoy/config/metrics/v3/stats.upb.h", - "src/src/core/ext/upb-generated/envoy/config/overload/v3/overload.upb.c", - "src/src/core/ext/upb-generated/envoy/config/overload/v3/overload.upb.h", - "src/src/core/ext/upb-generated/envoy/config/rbac/v3/rbac.upb.c", - "src/src/core/ext/upb-generated/envoy/config/rbac/v3/rbac.upb.h", - "src/src/core/ext/upb-generated/envoy/config/route/v3/route.upb.c", - "src/src/core/ext/upb-generated/envoy/config/route/v3/route.upb.h", - "src/src/core/ext/upb-generated/envoy/config/route/v3/route_components.upb.c", - "src/src/core/ext/upb-generated/envoy/config/route/v3/route_components.upb.h", - "src/src/core/ext/upb-generated/envoy/config/route/v3/scoped_route.upb.c", - "src/src/core/ext/upb-generated/envoy/config/route/v3/scoped_route.upb.h", - "src/src/core/ext/upb-generated/envoy/config/tap/v3/common.upb.c", - "src/src/core/ext/upb-generated/envoy/config/tap/v3/common.upb.h", - "src/src/core/ext/upb-generated/envoy/config/trace/v3/datadog.upb.c", - "src/src/core/ext/upb-generated/envoy/config/trace/v3/datadog.upb.h", - "src/src/core/ext/upb-generated/envoy/config/trace/v3/dynamic_ot.upb.c", - "src/src/core/ext/upb-generated/envoy/config/trace/v3/dynamic_ot.upb.h", - "src/src/core/ext/upb-generated/envoy/config/trace/v3/http_tracer.upb.c", - "src/src/core/ext/upb-generated/envoy/config/trace/v3/http_tracer.upb.h", - "src/src/core/ext/upb-generated/envoy/config/trace/v3/lightstep.upb.c", - "src/src/core/ext/upb-generated/envoy/config/trace/v3/lightstep.upb.h", - "src/src/core/ext/upb-generated/envoy/config/trace/v3/opencensus.upb.c", - "src/src/core/ext/upb-generated/envoy/config/trace/v3/opencensus.upb.h", - "src/src/core/ext/upb-generated/envoy/config/trace/v3/opentelemetry.upb.c", - "src/src/core/ext/upb-generated/envoy/config/trace/v3/opentelemetry.upb.h", - "src/src/core/ext/upb-generated/envoy/config/trace/v3/service.upb.c", - "src/src/core/ext/upb-generated/envoy/config/trace/v3/service.upb.h", - "src/src/core/ext/upb-generated/envoy/config/trace/v3/skywalking.upb.c", - "src/src/core/ext/upb-generated/envoy/config/trace/v3/skywalking.upb.h", - "src/src/core/ext/upb-generated/envoy/config/trace/v3/trace.upb.c", - "src/src/core/ext/upb-generated/envoy/config/trace/v3/trace.upb.h", - "src/src/core/ext/upb-generated/envoy/config/trace/v3/xray.upb.c", - "src/src/core/ext/upb-generated/envoy/config/trace/v3/xray.upb.h", - "src/src/core/ext/upb-generated/envoy/config/trace/v3/zipkin.upb.c", - "src/src/core/ext/upb-generated/envoy/config/trace/v3/zipkin.upb.h", - "src/src/core/ext/upb-generated/envoy/extensions/clusters/aggregate/v3/cluster.upb.c", - "src/src/core/ext/upb-generated/envoy/extensions/clusters/aggregate/v3/cluster.upb.h", - "src/src/core/ext/upb-generated/envoy/extensions/filters/common/fault/v3/fault.upb.c", - "src/src/core/ext/upb-generated/envoy/extensions/filters/common/fault/v3/fault.upb.h", - "src/src/core/ext/upb-generated/envoy/extensions/filters/http/fault/v3/fault.upb.c", - "src/src/core/ext/upb-generated/envoy/extensions/filters/http/fault/v3/fault.upb.h", - "src/src/core/ext/upb-generated/envoy/extensions/filters/http/rbac/v3/rbac.upb.c", - "src/src/core/ext/upb-generated/envoy/extensions/filters/http/rbac/v3/rbac.upb.h", - "src/src/core/ext/upb-generated/envoy/extensions/filters/http/router/v3/router.upb.c", - "src/src/core/ext/upb-generated/envoy/extensions/filters/http/router/v3/router.upb.h", - "src/src/core/ext/upb-generated/envoy/extensions/filters/http/stateful_session/v3/stateful_session.upb.c", - "src/src/core/ext/upb-generated/envoy/extensions/filters/http/stateful_session/v3/stateful_session.upb.h", - "src/src/core/ext/upb-generated/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.upb.c", - "src/src/core/ext/upb-generated/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.upb.h", - "src/src/core/ext/upb-generated/envoy/extensions/http/stateful_session/cookie/v3/cookie.upb.c", - "src/src/core/ext/upb-generated/envoy/extensions/http/stateful_session/cookie/v3/cookie.upb.h", - "src/src/core/ext/upb-generated/envoy/extensions/load_balancing_policies/client_side_weighted_round_robin/v3/client_side_weighted_round_robin.upb.c", - "src/src/core/ext/upb-generated/envoy/extensions/load_balancing_policies/client_side_weighted_round_robin/v3/client_side_weighted_round_robin.upb.h", - "src/src/core/ext/upb-generated/envoy/extensions/load_balancing_policies/common/v3/common.upb.c", - "src/src/core/ext/upb-generated/envoy/extensions/load_balancing_policies/common/v3/common.upb.h", - "src/src/core/ext/upb-generated/envoy/extensions/load_balancing_policies/ring_hash/v3/ring_hash.upb.c", - "src/src/core/ext/upb-generated/envoy/extensions/load_balancing_policies/ring_hash/v3/ring_hash.upb.h", - "src/src/core/ext/upb-generated/envoy/extensions/load_balancing_policies/wrr_locality/v3/wrr_locality.upb.c", - "src/src/core/ext/upb-generated/envoy/extensions/load_balancing_policies/wrr_locality/v3/wrr_locality.upb.h", - "src/src/core/ext/upb-generated/envoy/extensions/transport_sockets/tls/v3/cert.upb.c", - "src/src/core/ext/upb-generated/envoy/extensions/transport_sockets/tls/v3/cert.upb.h", - "src/src/core/ext/upb-generated/envoy/extensions/transport_sockets/tls/v3/common.upb.c", - "src/src/core/ext/upb-generated/envoy/extensions/transport_sockets/tls/v3/common.upb.h", - "src/src/core/ext/upb-generated/envoy/extensions/transport_sockets/tls/v3/secret.upb.c", - "src/src/core/ext/upb-generated/envoy/extensions/transport_sockets/tls/v3/secret.upb.h", - "src/src/core/ext/upb-generated/envoy/extensions/transport_sockets/tls/v3/tls.upb.c", - "src/src/core/ext/upb-generated/envoy/extensions/transport_sockets/tls/v3/tls.upb.h", - "src/src/core/ext/upb-generated/envoy/extensions/transport_sockets/tls/v3/tls_spiffe_validator_config.upb.c", - "src/src/core/ext/upb-generated/envoy/extensions/transport_sockets/tls/v3/tls_spiffe_validator_config.upb.h", - "src/src/core/ext/upb-generated/envoy/service/discovery/v3/ads.upb.c", - "src/src/core/ext/upb-generated/envoy/service/discovery/v3/ads.upb.h", - "src/src/core/ext/upb-generated/envoy/service/discovery/v3/discovery.upb.c", - "src/src/core/ext/upb-generated/envoy/service/discovery/v3/discovery.upb.h", - "src/src/core/ext/upb-generated/envoy/service/load_stats/v3/lrs.upb.c", - "src/src/core/ext/upb-generated/envoy/service/load_stats/v3/lrs.upb.h", - "src/src/core/ext/upb-generated/envoy/service/status/v3/csds.upb.c", - "src/src/core/ext/upb-generated/envoy/service/status/v3/csds.upb.h", - "src/src/core/ext/upb-generated/envoy/type/http/v3/cookie.upb.c", - "src/src/core/ext/upb-generated/envoy/type/http/v3/cookie.upb.h", - "src/src/core/ext/upb-generated/envoy/type/http/v3/path_transformation.upb.c", - "src/src/core/ext/upb-generated/envoy/type/http/v3/path_transformation.upb.h", - "src/src/core/ext/upb-generated/envoy/type/matcher/v3/filter_state.upb.c", - "src/src/core/ext/upb-generated/envoy/type/matcher/v3/filter_state.upb.h", - "src/src/core/ext/upb-generated/envoy/type/matcher/v3/http_inputs.upb.c", - "src/src/core/ext/upb-generated/envoy/type/matcher/v3/http_inputs.upb.h", - "src/src/core/ext/upb-generated/envoy/type/matcher/v3/metadata.upb.c", - "src/src/core/ext/upb-generated/envoy/type/matcher/v3/metadata.upb.h", - "src/src/core/ext/upb-generated/envoy/type/matcher/v3/node.upb.c", - "src/src/core/ext/upb-generated/envoy/type/matcher/v3/node.upb.h", - "src/src/core/ext/upb-generated/envoy/type/matcher/v3/number.upb.c", - "src/src/core/ext/upb-generated/envoy/type/matcher/v3/number.upb.h", - "src/src/core/ext/upb-generated/envoy/type/matcher/v3/path.upb.c", - "src/src/core/ext/upb-generated/envoy/type/matcher/v3/path.upb.h", - "src/src/core/ext/upb-generated/envoy/type/matcher/v3/regex.upb.c", - "src/src/core/ext/upb-generated/envoy/type/matcher/v3/regex.upb.h", - "src/src/core/ext/upb-generated/envoy/type/matcher/v3/status_code_input.upb.c", - "src/src/core/ext/upb-generated/envoy/type/matcher/v3/status_code_input.upb.h", - "src/src/core/ext/upb-generated/envoy/type/matcher/v3/string.upb.c", - "src/src/core/ext/upb-generated/envoy/type/matcher/v3/string.upb.h", - "src/src/core/ext/upb-generated/envoy/type/matcher/v3/struct.upb.c", - "src/src/core/ext/upb-generated/envoy/type/matcher/v3/struct.upb.h", - "src/src/core/ext/upb-generated/envoy/type/matcher/v3/value.upb.c", - "src/src/core/ext/upb-generated/envoy/type/matcher/v3/value.upb.h", - "src/src/core/ext/upb-generated/envoy/type/metadata/v3/metadata.upb.c", - "src/src/core/ext/upb-generated/envoy/type/metadata/v3/metadata.upb.h", - "src/src/core/ext/upb-generated/envoy/type/tracing/v3/custom_tag.upb.c", - "src/src/core/ext/upb-generated/envoy/type/tracing/v3/custom_tag.upb.h", - "src/src/core/ext/upb-generated/envoy/type/v3/hash_policy.upb.c", - "src/src/core/ext/upb-generated/envoy/type/v3/hash_policy.upb.h", - "src/src/core/ext/upb-generated/envoy/type/v3/http.upb.c", - "src/src/core/ext/upb-generated/envoy/type/v3/http.upb.h", - "src/src/core/ext/upb-generated/envoy/type/v3/http_status.upb.c", - "src/src/core/ext/upb-generated/envoy/type/v3/http_status.upb.h", - "src/src/core/ext/upb-generated/envoy/type/v3/percent.upb.c", - "src/src/core/ext/upb-generated/envoy/type/v3/percent.upb.h", - "src/src/core/ext/upb-generated/envoy/type/v3/range.upb.c", - "src/src/core/ext/upb-generated/envoy/type/v3/range.upb.h", - "src/src/core/ext/upb-generated/envoy/type/v3/ratelimit_strategy.upb.c", - "src/src/core/ext/upb-generated/envoy/type/v3/ratelimit_strategy.upb.h", - "src/src/core/ext/upb-generated/envoy/type/v3/ratelimit_unit.upb.c", - "src/src/core/ext/upb-generated/envoy/type/v3/ratelimit_unit.upb.h", - "src/src/core/ext/upb-generated/envoy/type/v3/semantic_version.upb.c", - "src/src/core/ext/upb-generated/envoy/type/v3/semantic_version.upb.h", - "src/src/core/ext/upb-generated/envoy/type/v3/token_bucket.upb.c", - "src/src/core/ext/upb-generated/envoy/type/v3/token_bucket.upb.h", - "src/src/core/ext/upb-generated/google/api/annotations.upb.c", - "src/src/core/ext/upb-generated/google/api/annotations.upb.h", - "src/src/core/ext/upb-generated/google/api/expr/v1alpha1/checked.upb.c", - "src/src/core/ext/upb-generated/google/api/expr/v1alpha1/checked.upb.h", - "src/src/core/ext/upb-generated/google/api/expr/v1alpha1/syntax.upb.c", - "src/src/core/ext/upb-generated/google/api/expr/v1alpha1/syntax.upb.h", - "src/src/core/ext/upb-generated/google/api/http.upb.c", - "src/src/core/ext/upb-generated/google/api/http.upb.h", - "src/src/core/ext/upb-generated/google/api/httpbody.upb.c", - "src/src/core/ext/upb-generated/google/api/httpbody.upb.h", - "src/src/core/ext/upb-generated/google/protobuf/any.upb.c", - "src/src/core/ext/upb-generated/google/protobuf/any.upb.h", - "src/src/core/ext/upb-generated/google/protobuf/descriptor.upb.c", - "src/src/core/ext/upb-generated/google/protobuf/descriptor.upb.h", - "src/src/core/ext/upb-generated/google/protobuf/duration.upb.c", - "src/src/core/ext/upb-generated/google/protobuf/duration.upb.h", - "src/src/core/ext/upb-generated/google/protobuf/empty.upb.c", - "src/src/core/ext/upb-generated/google/protobuf/empty.upb.h", - "src/src/core/ext/upb-generated/google/protobuf/struct.upb.c", - "src/src/core/ext/upb-generated/google/protobuf/struct.upb.h", - "src/src/core/ext/upb-generated/google/protobuf/timestamp.upb.c", - "src/src/core/ext/upb-generated/google/protobuf/timestamp.upb.h", - "src/src/core/ext/upb-generated/google/protobuf/wrappers.upb.c", - "src/src/core/ext/upb-generated/google/protobuf/wrappers.upb.h", - "src/src/core/ext/upb-generated/google/rpc/status.upb.c", - "src/src/core/ext/upb-generated/google/rpc/status.upb.h", - "src/src/core/ext/upb-generated/opencensus/proto/trace/v1/trace_config.upb.c", - "src/src/core/ext/upb-generated/opencensus/proto/trace/v1/trace_config.upb.h", - "src/src/core/ext/upb-generated/src/proto/grpc/gcp/altscontext.upb.c", - "src/src/core/ext/upb-generated/src/proto/grpc/gcp/altscontext.upb.h", - "src/src/core/ext/upb-generated/src/proto/grpc/gcp/handshaker.upb.c", - "src/src/core/ext/upb-generated/src/proto/grpc/gcp/handshaker.upb.h", - "src/src/core/ext/upb-generated/src/proto/grpc/gcp/transport_security_common.upb.c", - "src/src/core/ext/upb-generated/src/proto/grpc/gcp/transport_security_common.upb.h", - "src/src/core/ext/upb-generated/src/proto/grpc/health/v1/health.upb.c", - "src/src/core/ext/upb-generated/src/proto/grpc/health/v1/health.upb.h", - "src/src/core/ext/upb-generated/src/proto/grpc/lb/v1/load_balancer.upb.c", - "src/src/core/ext/upb-generated/src/proto/grpc/lb/v1/load_balancer.upb.h", - "src/src/core/ext/upb-generated/src/proto/grpc/lookup/v1/rls.upb.c", - "src/src/core/ext/upb-generated/src/proto/grpc/lookup/v1/rls.upb.h", - "src/src/core/ext/upb-generated/src/proto/grpc/lookup/v1/rls_config.upb.c", - "src/src/core/ext/upb-generated/src/proto/grpc/lookup/v1/rls_config.upb.h", - "src/src/core/ext/upb-generated/udpa/annotations/migrate.upb.c", - "src/src/core/ext/upb-generated/udpa/annotations/migrate.upb.h", - "src/src/core/ext/upb-generated/udpa/annotations/security.upb.c", - "src/src/core/ext/upb-generated/udpa/annotations/security.upb.h", - "src/src/core/ext/upb-generated/udpa/annotations/sensitive.upb.c", - "src/src/core/ext/upb-generated/udpa/annotations/sensitive.upb.h", - "src/src/core/ext/upb-generated/udpa/annotations/status.upb.c", - "src/src/core/ext/upb-generated/udpa/annotations/status.upb.h", - "src/src/core/ext/upb-generated/udpa/annotations/versioning.upb.c", - "src/src/core/ext/upb-generated/udpa/annotations/versioning.upb.h", - "src/src/core/ext/upb-generated/validate/validate.upb.c", - "src/src/core/ext/upb-generated/validate/validate.upb.h", - "src/src/core/ext/upb-generated/xds/annotations/v3/migrate.upb.c", - "src/src/core/ext/upb-generated/xds/annotations/v3/migrate.upb.h", - "src/src/core/ext/upb-generated/xds/annotations/v3/security.upb.c", - "src/src/core/ext/upb-generated/xds/annotations/v3/security.upb.h", - "src/src/core/ext/upb-generated/xds/annotations/v3/sensitive.upb.c", - "src/src/core/ext/upb-generated/xds/annotations/v3/sensitive.upb.h", - "src/src/core/ext/upb-generated/xds/annotations/v3/status.upb.c", - "src/src/core/ext/upb-generated/xds/annotations/v3/status.upb.h", - "src/src/core/ext/upb-generated/xds/annotations/v3/versioning.upb.c", - "src/src/core/ext/upb-generated/xds/annotations/v3/versioning.upb.h", - "src/src/core/ext/upb-generated/xds/core/v3/authority.upb.c", - "src/src/core/ext/upb-generated/xds/core/v3/authority.upb.h", - "src/src/core/ext/upb-generated/xds/core/v3/cidr.upb.c", - "src/src/core/ext/upb-generated/xds/core/v3/cidr.upb.h", - "src/src/core/ext/upb-generated/xds/core/v3/collection_entry.upb.c", - "src/src/core/ext/upb-generated/xds/core/v3/collection_entry.upb.h", - "src/src/core/ext/upb-generated/xds/core/v3/context_params.upb.c", - "src/src/core/ext/upb-generated/xds/core/v3/context_params.upb.h", - "src/src/core/ext/upb-generated/xds/core/v3/extension.upb.c", - "src/src/core/ext/upb-generated/xds/core/v3/extension.upb.h", - "src/src/core/ext/upb-generated/xds/core/v3/resource.upb.c", - "src/src/core/ext/upb-generated/xds/core/v3/resource.upb.h", - "src/src/core/ext/upb-generated/xds/core/v3/resource_locator.upb.c", - "src/src/core/ext/upb-generated/xds/core/v3/resource_locator.upb.h", - "src/src/core/ext/upb-generated/xds/core/v3/resource_name.upb.c", - "src/src/core/ext/upb-generated/xds/core/v3/resource_name.upb.h", - "src/src/core/ext/upb-generated/xds/data/orca/v3/orca_load_report.upb.c", - "src/src/core/ext/upb-generated/xds/data/orca/v3/orca_load_report.upb.h", - "src/src/core/ext/upb-generated/xds/service/orca/v3/orca.upb.c", - "src/src/core/ext/upb-generated/xds/service/orca/v3/orca.upb.h", - "src/src/core/ext/upb-generated/xds/type/matcher/v3/cel.upb.c", - "src/src/core/ext/upb-generated/xds/type/matcher/v3/cel.upb.h", - "src/src/core/ext/upb-generated/xds/type/matcher/v3/domain.upb.c", - "src/src/core/ext/upb-generated/xds/type/matcher/v3/domain.upb.h", - "src/src/core/ext/upb-generated/xds/type/matcher/v3/http_inputs.upb.c", - "src/src/core/ext/upb-generated/xds/type/matcher/v3/http_inputs.upb.h", - "src/src/core/ext/upb-generated/xds/type/matcher/v3/ip.upb.c", - "src/src/core/ext/upb-generated/xds/type/matcher/v3/ip.upb.h", - "src/src/core/ext/upb-generated/xds/type/matcher/v3/matcher.upb.c", - "src/src/core/ext/upb-generated/xds/type/matcher/v3/matcher.upb.h", - "src/src/core/ext/upb-generated/xds/type/matcher/v3/range.upb.c", - "src/src/core/ext/upb-generated/xds/type/matcher/v3/range.upb.h", - "src/src/core/ext/upb-generated/xds/type/matcher/v3/regex.upb.c", - "src/src/core/ext/upb-generated/xds/type/matcher/v3/regex.upb.h", - "src/src/core/ext/upb-generated/xds/type/matcher/v3/string.upb.c", - "src/src/core/ext/upb-generated/xds/type/matcher/v3/string.upb.h", - "src/src/core/ext/upb-generated/xds/type/v3/cel.upb.c", - "src/src/core/ext/upb-generated/xds/type/v3/cel.upb.h", - "src/src/core/ext/upb-generated/xds/type/v3/range.upb.c", - "src/src/core/ext/upb-generated/xds/type/v3/range.upb.h", - "src/src/core/ext/upb-generated/xds/type/v3/typed_struct.upb.c", - "src/src/core/ext/upb-generated/xds/type/v3/typed_struct.upb.h", - "src/src/core/ext/upbdefs-generated/envoy/admin/v3/certs.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/admin/v3/certs.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/admin/v3/clusters.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/admin/v3/clusters.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/admin/v3/config_dump.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/admin/v3/config_dump.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/admin/v3/config_dump_shared.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/admin/v3/config_dump_shared.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/admin/v3/init_dump.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/admin/v3/init_dump.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/admin/v3/listeners.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/admin/v3/listeners.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/admin/v3/memory.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/admin/v3/memory.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/admin/v3/metrics.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/admin/v3/metrics.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/admin/v3/mutex_stats.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/admin/v3/mutex_stats.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/admin/v3/server_info.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/admin/v3/server_info.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/admin/v3/tap.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/admin/v3/tap.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/annotations/deprecation.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/annotations/deprecation.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/annotations/resource.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/annotations/resource.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/config/accesslog/v3/accesslog.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/config/accesslog/v3/accesslog.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/config/bootstrap/v3/bootstrap.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/config/bootstrap/v3/bootstrap.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/config/cluster/v3/circuit_breaker.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/config/cluster/v3/circuit_breaker.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/config/cluster/v3/cluster.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/config/cluster/v3/cluster.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/config/cluster/v3/filter.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/config/cluster/v3/filter.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/config/cluster/v3/outlier_detection.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/config/cluster/v3/outlier_detection.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/config/common/matcher/v3/matcher.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/config/common/matcher/v3/matcher.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/config/core/v3/address.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/config/core/v3/address.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/config/core/v3/backoff.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/config/core/v3/backoff.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/config/core/v3/base.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/config/core/v3/base.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/config/core/v3/config_source.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/config/core/v3/config_source.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/config/core/v3/event_service_config.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/config/core/v3/event_service_config.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/config/core/v3/extension.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/config/core/v3/extension.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/config/core/v3/grpc_method_list.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/config/core/v3/grpc_method_list.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/config/core/v3/grpc_service.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/config/core/v3/grpc_service.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/config/core/v3/health_check.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/config/core/v3/health_check.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/config/core/v3/http_uri.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/config/core/v3/http_uri.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/config/core/v3/protocol.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/config/core/v3/protocol.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/config/core/v3/proxy_protocol.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/config/core/v3/proxy_protocol.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/config/core/v3/resolver.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/config/core/v3/resolver.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/config/core/v3/socket_option.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/config/core/v3/socket_option.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/config/core/v3/substitution_format_string.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/config/core/v3/substitution_format_string.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/config/core/v3/udp_socket_config.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/config/core/v3/udp_socket_config.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/config/endpoint/v3/endpoint.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/config/endpoint/v3/endpoint.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/config/endpoint/v3/endpoint_components.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/config/endpoint/v3/endpoint_components.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/config/endpoint/v3/load_report.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/config/endpoint/v3/load_report.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/config/listener/v3/api_listener.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/config/listener/v3/api_listener.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/config/listener/v3/listener.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/config/listener/v3/listener.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/config/listener/v3/listener_components.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/config/listener/v3/listener_components.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/config/listener/v3/quic_config.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/config/listener/v3/quic_config.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/config/listener/v3/udp_listener_config.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/config/listener/v3/udp_listener_config.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/config/metrics/v3/metrics_service.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/config/metrics/v3/metrics_service.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/config/metrics/v3/stats.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/config/metrics/v3/stats.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/config/overload/v3/overload.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/config/overload/v3/overload.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/config/rbac/v3/rbac.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/config/rbac/v3/rbac.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/config/route/v3/route.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/config/route/v3/route.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/config/route/v3/route_components.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/config/route/v3/route_components.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/config/route/v3/scoped_route.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/config/route/v3/scoped_route.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/config/tap/v3/common.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/config/tap/v3/common.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/config/trace/v3/datadog.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/config/trace/v3/datadog.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/config/trace/v3/dynamic_ot.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/config/trace/v3/dynamic_ot.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/config/trace/v3/http_tracer.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/config/trace/v3/http_tracer.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/config/trace/v3/lightstep.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/config/trace/v3/lightstep.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/config/trace/v3/opencensus.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/config/trace/v3/opencensus.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/config/trace/v3/opentelemetry.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/config/trace/v3/opentelemetry.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/config/trace/v3/service.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/config/trace/v3/service.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/config/trace/v3/skywalking.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/config/trace/v3/skywalking.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/config/trace/v3/trace.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/config/trace/v3/trace.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/config/trace/v3/xray.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/config/trace/v3/xray.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/config/trace/v3/zipkin.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/config/trace/v3/zipkin.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/extensions/clusters/aggregate/v3/cluster.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/extensions/clusters/aggregate/v3/cluster.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/extensions/filters/common/fault/v3/fault.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/extensions/filters/common/fault/v3/fault.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/extensions/filters/http/fault/v3/fault.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/extensions/filters/http/fault/v3/fault.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/extensions/filters/http/rbac/v3/rbac.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/extensions/filters/http/rbac/v3/rbac.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/extensions/filters/http/router/v3/router.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/extensions/filters/http/router/v3/router.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/extensions/filters/http/stateful_session/v3/stateful_session.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/extensions/filters/http/stateful_session/v3/stateful_session.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/extensions/http/stateful_session/cookie/v3/cookie.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/extensions/http/stateful_session/cookie/v3/cookie.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/extensions/transport_sockets/tls/v3/cert.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/extensions/transport_sockets/tls/v3/cert.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/extensions/transport_sockets/tls/v3/common.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/extensions/transport_sockets/tls/v3/common.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/extensions/transport_sockets/tls/v3/secret.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/extensions/transport_sockets/tls/v3/secret.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/extensions/transport_sockets/tls/v3/tls.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/extensions/transport_sockets/tls/v3/tls.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/extensions/transport_sockets/tls/v3/tls_spiffe_validator_config.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/extensions/transport_sockets/tls/v3/tls_spiffe_validator_config.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/service/discovery/v3/ads.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/service/discovery/v3/ads.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/service/discovery/v3/discovery.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/service/discovery/v3/discovery.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/service/load_stats/v3/lrs.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/service/load_stats/v3/lrs.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/service/status/v3/csds.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/service/status/v3/csds.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/type/http/v3/cookie.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/type/http/v3/cookie.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/type/http/v3/path_transformation.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/type/http/v3/path_transformation.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/type/matcher/v3/filter_state.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/type/matcher/v3/filter_state.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/type/matcher/v3/http_inputs.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/type/matcher/v3/http_inputs.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/type/matcher/v3/metadata.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/type/matcher/v3/metadata.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/type/matcher/v3/node.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/type/matcher/v3/node.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/type/matcher/v3/number.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/type/matcher/v3/number.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/type/matcher/v3/path.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/type/matcher/v3/path.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/type/matcher/v3/regex.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/type/matcher/v3/regex.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/type/matcher/v3/status_code_input.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/type/matcher/v3/status_code_input.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/type/matcher/v3/string.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/type/matcher/v3/string.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/type/matcher/v3/struct.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/type/matcher/v3/struct.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/type/matcher/v3/value.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/type/matcher/v3/value.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/type/metadata/v3/metadata.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/type/metadata/v3/metadata.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/type/tracing/v3/custom_tag.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/type/tracing/v3/custom_tag.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/type/v3/hash_policy.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/type/v3/hash_policy.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/type/v3/http.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/type/v3/http.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/type/v3/http_status.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/type/v3/http_status.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/type/v3/percent.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/type/v3/percent.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/type/v3/range.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/type/v3/range.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/type/v3/ratelimit_strategy.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/type/v3/ratelimit_strategy.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/type/v3/ratelimit_unit.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/type/v3/ratelimit_unit.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/type/v3/semantic_version.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/type/v3/semantic_version.upbdefs.h", - "src/src/core/ext/upbdefs-generated/envoy/type/v3/token_bucket.upbdefs.c", - "src/src/core/ext/upbdefs-generated/envoy/type/v3/token_bucket.upbdefs.h", - "src/src/core/ext/upbdefs-generated/google/api/annotations.upbdefs.c", - "src/src/core/ext/upbdefs-generated/google/api/annotations.upbdefs.h", - "src/src/core/ext/upbdefs-generated/google/api/expr/v1alpha1/checked.upbdefs.c", - "src/src/core/ext/upbdefs-generated/google/api/expr/v1alpha1/checked.upbdefs.h", - "src/src/core/ext/upbdefs-generated/google/api/expr/v1alpha1/syntax.upbdefs.c", - "src/src/core/ext/upbdefs-generated/google/api/expr/v1alpha1/syntax.upbdefs.h", - "src/src/core/ext/upbdefs-generated/google/api/http.upbdefs.c", - "src/src/core/ext/upbdefs-generated/google/api/http.upbdefs.h", - "src/src/core/ext/upbdefs-generated/google/api/httpbody.upbdefs.c", - "src/src/core/ext/upbdefs-generated/google/api/httpbody.upbdefs.h", - "src/src/core/ext/upbdefs-generated/google/protobuf/any.upbdefs.c", - "src/src/core/ext/upbdefs-generated/google/protobuf/any.upbdefs.h", - "src/src/core/ext/upbdefs-generated/google/protobuf/descriptor.upbdefs.c", - "src/src/core/ext/upbdefs-generated/google/protobuf/descriptor.upbdefs.h", - "src/src/core/ext/upbdefs-generated/google/protobuf/duration.upbdefs.c", - "src/src/core/ext/upbdefs-generated/google/protobuf/duration.upbdefs.h", - "src/src/core/ext/upbdefs-generated/google/protobuf/empty.upbdefs.c", - "src/src/core/ext/upbdefs-generated/google/protobuf/empty.upbdefs.h", - "src/src/core/ext/upbdefs-generated/google/protobuf/struct.upbdefs.c", - "src/src/core/ext/upbdefs-generated/google/protobuf/struct.upbdefs.h", - "src/src/core/ext/upbdefs-generated/google/protobuf/timestamp.upbdefs.c", - "src/src/core/ext/upbdefs-generated/google/protobuf/timestamp.upbdefs.h", - "src/src/core/ext/upbdefs-generated/google/protobuf/wrappers.upbdefs.c", - "src/src/core/ext/upbdefs-generated/google/protobuf/wrappers.upbdefs.h", - "src/src/core/ext/upbdefs-generated/google/rpc/status.upbdefs.c", - "src/src/core/ext/upbdefs-generated/google/rpc/status.upbdefs.h", - "src/src/core/ext/upbdefs-generated/opencensus/proto/trace/v1/trace_config.upbdefs.c", - "src/src/core/ext/upbdefs-generated/opencensus/proto/trace/v1/trace_config.upbdefs.h", - "src/src/core/ext/upbdefs-generated/src/proto/grpc/lookup/v1/rls_config.upbdefs.c", - "src/src/core/ext/upbdefs-generated/src/proto/grpc/lookup/v1/rls_config.upbdefs.h", - "src/src/core/ext/upbdefs-generated/udpa/annotations/migrate.upbdefs.c", - "src/src/core/ext/upbdefs-generated/udpa/annotations/migrate.upbdefs.h", - "src/src/core/ext/upbdefs-generated/udpa/annotations/security.upbdefs.c", - "src/src/core/ext/upbdefs-generated/udpa/annotations/security.upbdefs.h", - "src/src/core/ext/upbdefs-generated/udpa/annotations/sensitive.upbdefs.c", - "src/src/core/ext/upbdefs-generated/udpa/annotations/sensitive.upbdefs.h", - "src/src/core/ext/upbdefs-generated/udpa/annotations/status.upbdefs.c", - "src/src/core/ext/upbdefs-generated/udpa/annotations/status.upbdefs.h", - "src/src/core/ext/upbdefs-generated/udpa/annotations/versioning.upbdefs.c", - "src/src/core/ext/upbdefs-generated/udpa/annotations/versioning.upbdefs.h", - "src/src/core/ext/upbdefs-generated/validate/validate.upbdefs.c", - "src/src/core/ext/upbdefs-generated/validate/validate.upbdefs.h", - "src/src/core/ext/upbdefs-generated/xds/annotations/v3/migrate.upbdefs.c", - "src/src/core/ext/upbdefs-generated/xds/annotations/v3/migrate.upbdefs.h", - "src/src/core/ext/upbdefs-generated/xds/annotations/v3/security.upbdefs.c", - "src/src/core/ext/upbdefs-generated/xds/annotations/v3/security.upbdefs.h", - "src/src/core/ext/upbdefs-generated/xds/annotations/v3/sensitive.upbdefs.c", - "src/src/core/ext/upbdefs-generated/xds/annotations/v3/sensitive.upbdefs.h", - "src/src/core/ext/upbdefs-generated/xds/annotations/v3/status.upbdefs.c", - "src/src/core/ext/upbdefs-generated/xds/annotations/v3/status.upbdefs.h", - "src/src/core/ext/upbdefs-generated/xds/annotations/v3/versioning.upbdefs.c", - "src/src/core/ext/upbdefs-generated/xds/annotations/v3/versioning.upbdefs.h", - "src/src/core/ext/upbdefs-generated/xds/core/v3/authority.upbdefs.c", - "src/src/core/ext/upbdefs-generated/xds/core/v3/authority.upbdefs.h", - "src/src/core/ext/upbdefs-generated/xds/core/v3/cidr.upbdefs.c", - "src/src/core/ext/upbdefs-generated/xds/core/v3/cidr.upbdefs.h", - "src/src/core/ext/upbdefs-generated/xds/core/v3/collection_entry.upbdefs.c", - "src/src/core/ext/upbdefs-generated/xds/core/v3/collection_entry.upbdefs.h", - "src/src/core/ext/upbdefs-generated/xds/core/v3/context_params.upbdefs.c", - "src/src/core/ext/upbdefs-generated/xds/core/v3/context_params.upbdefs.h", - "src/src/core/ext/upbdefs-generated/xds/core/v3/extension.upbdefs.c", - "src/src/core/ext/upbdefs-generated/xds/core/v3/extension.upbdefs.h", - "src/src/core/ext/upbdefs-generated/xds/core/v3/resource.upbdefs.c", - "src/src/core/ext/upbdefs-generated/xds/core/v3/resource.upbdefs.h", - "src/src/core/ext/upbdefs-generated/xds/core/v3/resource_locator.upbdefs.c", - "src/src/core/ext/upbdefs-generated/xds/core/v3/resource_locator.upbdefs.h", - "src/src/core/ext/upbdefs-generated/xds/core/v3/resource_name.upbdefs.c", - "src/src/core/ext/upbdefs-generated/xds/core/v3/resource_name.upbdefs.h", - "src/src/core/ext/upbdefs-generated/xds/type/matcher/v3/cel.upbdefs.c", - "src/src/core/ext/upbdefs-generated/xds/type/matcher/v3/cel.upbdefs.h", - "src/src/core/ext/upbdefs-generated/xds/type/matcher/v3/domain.upbdefs.c", - "src/src/core/ext/upbdefs-generated/xds/type/matcher/v3/domain.upbdefs.h", - "src/src/core/ext/upbdefs-generated/xds/type/matcher/v3/http_inputs.upbdefs.c", - "src/src/core/ext/upbdefs-generated/xds/type/matcher/v3/http_inputs.upbdefs.h", - "src/src/core/ext/upbdefs-generated/xds/type/matcher/v3/ip.upbdefs.c", - "src/src/core/ext/upbdefs-generated/xds/type/matcher/v3/ip.upbdefs.h", - "src/src/core/ext/upbdefs-generated/xds/type/matcher/v3/matcher.upbdefs.c", - "src/src/core/ext/upbdefs-generated/xds/type/matcher/v3/matcher.upbdefs.h", - "src/src/core/ext/upbdefs-generated/xds/type/matcher/v3/range.upbdefs.c", - "src/src/core/ext/upbdefs-generated/xds/type/matcher/v3/range.upbdefs.h", - "src/src/core/ext/upbdefs-generated/xds/type/matcher/v3/regex.upbdefs.c", - "src/src/core/ext/upbdefs-generated/xds/type/matcher/v3/regex.upbdefs.h", - "src/src/core/ext/upbdefs-generated/xds/type/matcher/v3/string.upbdefs.c", - "src/src/core/ext/upbdefs-generated/xds/type/matcher/v3/string.upbdefs.h", - "src/src/core/ext/upbdefs-generated/xds/type/v3/cel.upbdefs.c", - "src/src/core/ext/upbdefs-generated/xds/type/v3/cel.upbdefs.h", - "src/src/core/ext/upbdefs-generated/xds/type/v3/range.upbdefs.c", - "src/src/core/ext/upbdefs-generated/xds/type/v3/range.upbdefs.h", - "src/src/core/ext/upbdefs-generated/xds/type/v3/typed_struct.upbdefs.c", - "src/src/core/ext/upbdefs-generated/xds/type/v3/typed_struct.upbdefs.h", - "src/src/core/ext/xds/certificate_provider_store.cc", - "src/src/core/ext/xds/certificate_provider_store.h", - "src/src/core/ext/xds/file_watcher_certificate_provider_factory.cc", - "src/src/core/ext/xds/file_watcher_certificate_provider_factory.h", - "src/src/core/ext/xds/upb_utils.h", - "src/src/core/ext/xds/xds_api.cc", - "src/src/core/ext/xds/xds_api.h", - "src/src/core/ext/xds/xds_bootstrap.cc", - "src/src/core/ext/xds/xds_bootstrap.h", - "src/src/core/ext/xds/xds_bootstrap_grpc.cc", - "src/src/core/ext/xds/xds_bootstrap_grpc.h", - "src/src/core/ext/xds/xds_certificate_provider.cc", - "src/src/core/ext/xds/xds_certificate_provider.h", - "src/src/core/ext/xds/xds_channel_args.h", - "src/src/core/ext/xds/xds_channel_stack_modifier.cc", - "src/src/core/ext/xds/xds_channel_stack_modifier.h", - "src/src/core/ext/xds/xds_client.cc", - "src/src/core/ext/xds/xds_client.h", - "src/src/core/ext/xds/xds_client_grpc.cc", - "src/src/core/ext/xds/xds_client_grpc.h", - "src/src/core/ext/xds/xds_client_stats.cc", - "src/src/core/ext/xds/xds_client_stats.h", - "src/src/core/ext/xds/xds_cluster.cc", - "src/src/core/ext/xds/xds_cluster.h", - "src/src/core/ext/xds/xds_cluster_specifier_plugin.cc", - "src/src/core/ext/xds/xds_cluster_specifier_plugin.h", - "src/src/core/ext/xds/xds_common_types.cc", - "src/src/core/ext/xds/xds_common_types.h", - "src/src/core/ext/xds/xds_endpoint.cc", - "src/src/core/ext/xds/xds_endpoint.h", - "src/src/core/ext/xds/xds_health_status.cc", - "src/src/core/ext/xds/xds_health_status.h", - "src/src/core/ext/xds/xds_http_fault_filter.cc", - "src/src/core/ext/xds/xds_http_fault_filter.h", - "src/src/core/ext/xds/xds_http_filters.cc", - "src/src/core/ext/xds/xds_http_filters.h", - "src/src/core/ext/xds/xds_http_rbac_filter.cc", - "src/src/core/ext/xds/xds_http_rbac_filter.h", - "src/src/core/ext/xds/xds_http_stateful_session_filter.cc", - "src/src/core/ext/xds/xds_http_stateful_session_filter.h", - "src/src/core/ext/xds/xds_lb_policy_registry.cc", - "src/src/core/ext/xds/xds_lb_policy_registry.h", - "src/src/core/ext/xds/xds_listener.cc", - "src/src/core/ext/xds/xds_listener.h", - "src/src/core/ext/xds/xds_resource_type.h", - "src/src/core/ext/xds/xds_resource_type_impl.h", - "src/src/core/ext/xds/xds_route_config.cc", - "src/src/core/ext/xds/xds_route_config.h", - "src/src/core/ext/xds/xds_routing.cc", - "src/src/core/ext/xds/xds_routing.h", - "src/src/core/ext/xds/xds_server_config_fetcher.cc", - "src/src/core/ext/xds/xds_transport.h", - "src/src/core/ext/xds/xds_transport_grpc.cc", - "src/src/core/ext/xds/xds_transport_grpc.h", + "src/src/core/ext/transport/inproc/legacy_inproc_transport.cc", + "src/src/core/ext/transport/inproc/legacy_inproc_transport.h", + "src/src/core/ext/upb-gen/envoy/admin/v3/certs.upb.h", + "src/src/core/ext/upb-gen/envoy/admin/v3/certs.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/admin/v3/certs.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/admin/v3/clusters.upb.h", + "src/src/core/ext/upb-gen/envoy/admin/v3/clusters.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/admin/v3/clusters.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/admin/v3/config_dump.upb.h", + "src/src/core/ext/upb-gen/envoy/admin/v3/config_dump.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/admin/v3/config_dump.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/admin/v3/config_dump_shared.upb.h", + "src/src/core/ext/upb-gen/envoy/admin/v3/config_dump_shared.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/admin/v3/config_dump_shared.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/admin/v3/init_dump.upb.h", + "src/src/core/ext/upb-gen/envoy/admin/v3/init_dump.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/admin/v3/init_dump.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/admin/v3/listeners.upb.h", + "src/src/core/ext/upb-gen/envoy/admin/v3/listeners.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/admin/v3/listeners.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/admin/v3/memory.upb.h", + "src/src/core/ext/upb-gen/envoy/admin/v3/memory.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/admin/v3/memory.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/admin/v3/metrics.upb.h", + "src/src/core/ext/upb-gen/envoy/admin/v3/metrics.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/admin/v3/metrics.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/admin/v3/mutex_stats.upb.h", + "src/src/core/ext/upb-gen/envoy/admin/v3/mutex_stats.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/admin/v3/mutex_stats.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/admin/v3/server_info.upb.h", + "src/src/core/ext/upb-gen/envoy/admin/v3/server_info.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/admin/v3/server_info.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/admin/v3/tap.upb.h", + "src/src/core/ext/upb-gen/envoy/admin/v3/tap.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/admin/v3/tap.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/annotations/deprecation.upb.h", + "src/src/core/ext/upb-gen/envoy/annotations/deprecation.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/annotations/deprecation.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/annotations/resource.upb.h", + "src/src/core/ext/upb-gen/envoy/annotations/resource.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/annotations/resource.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/config/accesslog/v3/accesslog.upb.h", + "src/src/core/ext/upb-gen/envoy/config/accesslog/v3/accesslog.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/config/accesslog/v3/accesslog.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/config/bootstrap/v3/bootstrap.upb.h", + "src/src/core/ext/upb-gen/envoy/config/bootstrap/v3/bootstrap.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/config/bootstrap/v3/bootstrap.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/config/cluster/v3/circuit_breaker.upb.h", + "src/src/core/ext/upb-gen/envoy/config/cluster/v3/circuit_breaker.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/config/cluster/v3/circuit_breaker.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/config/cluster/v3/cluster.upb.h", + "src/src/core/ext/upb-gen/envoy/config/cluster/v3/cluster.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/config/cluster/v3/cluster.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/config/cluster/v3/filter.upb.h", + "src/src/core/ext/upb-gen/envoy/config/cluster/v3/filter.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/config/cluster/v3/filter.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/config/cluster/v3/outlier_detection.upb.h", + "src/src/core/ext/upb-gen/envoy/config/cluster/v3/outlier_detection.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/config/cluster/v3/outlier_detection.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/config/common/matcher/v3/matcher.upb.h", + "src/src/core/ext/upb-gen/envoy/config/common/matcher/v3/matcher.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/config/common/matcher/v3/matcher.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/config/core/v3/address.upb.h", + "src/src/core/ext/upb-gen/envoy/config/core/v3/address.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/config/core/v3/address.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/config/core/v3/backoff.upb.h", + "src/src/core/ext/upb-gen/envoy/config/core/v3/backoff.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/config/core/v3/backoff.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/config/core/v3/base.upb.h", + "src/src/core/ext/upb-gen/envoy/config/core/v3/base.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/config/core/v3/base.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/config/core/v3/config_source.upb.h", + "src/src/core/ext/upb-gen/envoy/config/core/v3/config_source.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/config/core/v3/config_source.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/config/core/v3/event_service_config.upb.h", + "src/src/core/ext/upb-gen/envoy/config/core/v3/event_service_config.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/config/core/v3/event_service_config.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/config/core/v3/extension.upb.h", + "src/src/core/ext/upb-gen/envoy/config/core/v3/extension.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/config/core/v3/extension.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/config/core/v3/grpc_method_list.upb.h", + "src/src/core/ext/upb-gen/envoy/config/core/v3/grpc_method_list.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/config/core/v3/grpc_method_list.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/config/core/v3/grpc_service.upb.h", + "src/src/core/ext/upb-gen/envoy/config/core/v3/grpc_service.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/config/core/v3/grpc_service.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/config/core/v3/health_check.upb.h", + "src/src/core/ext/upb-gen/envoy/config/core/v3/health_check.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/config/core/v3/health_check.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/config/core/v3/http_service.upb.h", + "src/src/core/ext/upb-gen/envoy/config/core/v3/http_service.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/config/core/v3/http_service.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/config/core/v3/http_uri.upb.h", + "src/src/core/ext/upb-gen/envoy/config/core/v3/http_uri.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/config/core/v3/http_uri.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/config/core/v3/protocol.upb.h", + "src/src/core/ext/upb-gen/envoy/config/core/v3/protocol.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/config/core/v3/protocol.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/config/core/v3/proxy_protocol.upb.h", + "src/src/core/ext/upb-gen/envoy/config/core/v3/proxy_protocol.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/config/core/v3/proxy_protocol.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/config/core/v3/resolver.upb.h", + "src/src/core/ext/upb-gen/envoy/config/core/v3/resolver.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/config/core/v3/resolver.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/config/core/v3/socket_option.upb.h", + "src/src/core/ext/upb-gen/envoy/config/core/v3/socket_option.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/config/core/v3/socket_option.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/config/core/v3/substitution_format_string.upb.h", + "src/src/core/ext/upb-gen/envoy/config/core/v3/substitution_format_string.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/config/core/v3/substitution_format_string.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/config/core/v3/udp_socket_config.upb.h", + "src/src/core/ext/upb-gen/envoy/config/core/v3/udp_socket_config.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/config/core/v3/udp_socket_config.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/config/endpoint/v3/endpoint.upb.h", + "src/src/core/ext/upb-gen/envoy/config/endpoint/v3/endpoint.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/config/endpoint/v3/endpoint.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/config/endpoint/v3/endpoint_components.upb.h", + "src/src/core/ext/upb-gen/envoy/config/endpoint/v3/endpoint_components.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/config/endpoint/v3/endpoint_components.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/config/endpoint/v3/load_report.upb.h", + "src/src/core/ext/upb-gen/envoy/config/endpoint/v3/load_report.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/config/endpoint/v3/load_report.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/config/listener/v3/api_listener.upb.h", + "src/src/core/ext/upb-gen/envoy/config/listener/v3/api_listener.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/config/listener/v3/api_listener.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/config/listener/v3/listener.upb.h", + "src/src/core/ext/upb-gen/envoy/config/listener/v3/listener.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/config/listener/v3/listener.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/config/listener/v3/listener_components.upb.h", + "src/src/core/ext/upb-gen/envoy/config/listener/v3/listener_components.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/config/listener/v3/listener_components.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/config/listener/v3/quic_config.upb.h", + "src/src/core/ext/upb-gen/envoy/config/listener/v3/quic_config.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/config/listener/v3/quic_config.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/config/listener/v3/udp_listener_config.upb.h", + "src/src/core/ext/upb-gen/envoy/config/listener/v3/udp_listener_config.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/config/listener/v3/udp_listener_config.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/config/metrics/v3/metrics_service.upb.h", + "src/src/core/ext/upb-gen/envoy/config/metrics/v3/metrics_service.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/config/metrics/v3/metrics_service.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/config/metrics/v3/stats.upb.h", + "src/src/core/ext/upb-gen/envoy/config/metrics/v3/stats.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/config/metrics/v3/stats.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/config/overload/v3/overload.upb.h", + "src/src/core/ext/upb-gen/envoy/config/overload/v3/overload.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/config/overload/v3/overload.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/config/rbac/v3/rbac.upb.h", + "src/src/core/ext/upb-gen/envoy/config/rbac/v3/rbac.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/config/rbac/v3/rbac.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/config/route/v3/route.upb.h", + "src/src/core/ext/upb-gen/envoy/config/route/v3/route.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/config/route/v3/route.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/config/route/v3/route_components.upb.h", + "src/src/core/ext/upb-gen/envoy/config/route/v3/route_components.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/config/route/v3/route_components.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/config/route/v3/scoped_route.upb.h", + "src/src/core/ext/upb-gen/envoy/config/route/v3/scoped_route.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/config/route/v3/scoped_route.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/config/tap/v3/common.upb.h", + "src/src/core/ext/upb-gen/envoy/config/tap/v3/common.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/config/tap/v3/common.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/config/trace/v3/datadog.upb.h", + "src/src/core/ext/upb-gen/envoy/config/trace/v3/datadog.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/config/trace/v3/datadog.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/config/trace/v3/dynamic_ot.upb.h", + "src/src/core/ext/upb-gen/envoy/config/trace/v3/dynamic_ot.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/config/trace/v3/dynamic_ot.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/config/trace/v3/http_tracer.upb.h", + "src/src/core/ext/upb-gen/envoy/config/trace/v3/http_tracer.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/config/trace/v3/http_tracer.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/config/trace/v3/lightstep.upb.h", + "src/src/core/ext/upb-gen/envoy/config/trace/v3/lightstep.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/config/trace/v3/lightstep.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/config/trace/v3/opencensus.upb.h", + "src/src/core/ext/upb-gen/envoy/config/trace/v3/opencensus.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/config/trace/v3/opencensus.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/config/trace/v3/opentelemetry.upb.h", + "src/src/core/ext/upb-gen/envoy/config/trace/v3/opentelemetry.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/config/trace/v3/opentelemetry.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/config/trace/v3/service.upb.h", + "src/src/core/ext/upb-gen/envoy/config/trace/v3/service.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/config/trace/v3/service.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/config/trace/v3/skywalking.upb.h", + "src/src/core/ext/upb-gen/envoy/config/trace/v3/skywalking.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/config/trace/v3/skywalking.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/config/trace/v3/trace.upb.h", + "src/src/core/ext/upb-gen/envoy/config/trace/v3/trace.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/config/trace/v3/trace.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/config/trace/v3/xray.upb.h", + "src/src/core/ext/upb-gen/envoy/config/trace/v3/xray.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/config/trace/v3/xray.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/config/trace/v3/zipkin.upb.h", + "src/src/core/ext/upb-gen/envoy/config/trace/v3/zipkin.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/config/trace/v3/zipkin.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/data/accesslog/v3/accesslog.upb.h", + "src/src/core/ext/upb-gen/envoy/data/accesslog/v3/accesslog.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/data/accesslog/v3/accesslog.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/extensions/clusters/aggregate/v3/cluster.upb.h", + "src/src/core/ext/upb-gen/envoy/extensions/clusters/aggregate/v3/cluster.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/extensions/clusters/aggregate/v3/cluster.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/extensions/filters/common/fault/v3/fault.upb.h", + "src/src/core/ext/upb-gen/envoy/extensions/filters/common/fault/v3/fault.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/extensions/filters/common/fault/v3/fault.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/extensions/filters/http/fault/v3/fault.upb.h", + "src/src/core/ext/upb-gen/envoy/extensions/filters/http/fault/v3/fault.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/extensions/filters/http/fault/v3/fault.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/extensions/filters/http/rbac/v3/rbac.upb.h", + "src/src/core/ext/upb-gen/envoy/extensions/filters/http/rbac/v3/rbac.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/extensions/filters/http/rbac/v3/rbac.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/extensions/filters/http/router/v3/router.upb.h", + "src/src/core/ext/upb-gen/envoy/extensions/filters/http/router/v3/router.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/extensions/filters/http/router/v3/router.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/extensions/filters/http/stateful_session/v3/stateful_session.upb.h", + "src/src/core/ext/upb-gen/envoy/extensions/filters/http/stateful_session/v3/stateful_session.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/extensions/filters/http/stateful_session/v3/stateful_session.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.upb.h", + "src/src/core/ext/upb-gen/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/extensions/http/stateful_session/cookie/v3/cookie.upb.h", + "src/src/core/ext/upb-gen/envoy/extensions/http/stateful_session/cookie/v3/cookie.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/extensions/http/stateful_session/cookie/v3/cookie.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/extensions/load_balancing_policies/client_side_weighted_round_robin/v3/client_side_weighted_round_robin.upb.h", + "src/src/core/ext/upb-gen/envoy/extensions/load_balancing_policies/client_side_weighted_round_robin/v3/client_side_weighted_round_robin.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/extensions/load_balancing_policies/client_side_weighted_round_robin/v3/client_side_weighted_round_robin.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/extensions/load_balancing_policies/common/v3/common.upb.h", + "src/src/core/ext/upb-gen/envoy/extensions/load_balancing_policies/common/v3/common.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/extensions/load_balancing_policies/common/v3/common.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/extensions/load_balancing_policies/pick_first/v3/pick_first.upb.h", + "src/src/core/ext/upb-gen/envoy/extensions/load_balancing_policies/pick_first/v3/pick_first.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/extensions/load_balancing_policies/pick_first/v3/pick_first.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/extensions/load_balancing_policies/ring_hash/v3/ring_hash.upb.h", + "src/src/core/ext/upb-gen/envoy/extensions/load_balancing_policies/ring_hash/v3/ring_hash.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/extensions/load_balancing_policies/ring_hash/v3/ring_hash.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/extensions/load_balancing_policies/wrr_locality/v3/wrr_locality.upb.h", + "src/src/core/ext/upb-gen/envoy/extensions/load_balancing_policies/wrr_locality/v3/wrr_locality.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/extensions/load_balancing_policies/wrr_locality/v3/wrr_locality.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/extensions/transport_sockets/tls/v3/cert.upb.h", + "src/src/core/ext/upb-gen/envoy/extensions/transport_sockets/tls/v3/cert.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/extensions/transport_sockets/tls/v3/cert.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/extensions/transport_sockets/tls/v3/common.upb.h", + "src/src/core/ext/upb-gen/envoy/extensions/transport_sockets/tls/v3/common.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/extensions/transport_sockets/tls/v3/common.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/extensions/transport_sockets/tls/v3/secret.upb.h", + "src/src/core/ext/upb-gen/envoy/extensions/transport_sockets/tls/v3/secret.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/extensions/transport_sockets/tls/v3/secret.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/extensions/transport_sockets/tls/v3/tls.upb.h", + "src/src/core/ext/upb-gen/envoy/extensions/transport_sockets/tls/v3/tls.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/extensions/transport_sockets/tls/v3/tls.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/extensions/transport_sockets/tls/v3/tls_spiffe_validator_config.upb.h", + "src/src/core/ext/upb-gen/envoy/extensions/transport_sockets/tls/v3/tls_spiffe_validator_config.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/extensions/transport_sockets/tls/v3/tls_spiffe_validator_config.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/extensions/upstreams/http/v3/http_protocol_options.upb.h", + "src/src/core/ext/upb-gen/envoy/extensions/upstreams/http/v3/http_protocol_options.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/extensions/upstreams/http/v3/http_protocol_options.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/service/discovery/v3/ads.upb.h", + "src/src/core/ext/upb-gen/envoy/service/discovery/v3/ads.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/service/discovery/v3/ads.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/service/discovery/v3/discovery.upb.h", + "src/src/core/ext/upb-gen/envoy/service/discovery/v3/discovery.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/service/discovery/v3/discovery.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/service/load_stats/v3/lrs.upb.h", + "src/src/core/ext/upb-gen/envoy/service/load_stats/v3/lrs.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/service/load_stats/v3/lrs.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/service/status/v3/csds.upb.h", + "src/src/core/ext/upb-gen/envoy/service/status/v3/csds.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/service/status/v3/csds.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/type/http/v3/cookie.upb.h", + "src/src/core/ext/upb-gen/envoy/type/http/v3/cookie.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/type/http/v3/cookie.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/type/http/v3/path_transformation.upb.h", + "src/src/core/ext/upb-gen/envoy/type/http/v3/path_transformation.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/type/http/v3/path_transformation.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/type/matcher/v3/filter_state.upb.h", + "src/src/core/ext/upb-gen/envoy/type/matcher/v3/filter_state.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/type/matcher/v3/filter_state.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/type/matcher/v3/http_inputs.upb.h", + "src/src/core/ext/upb-gen/envoy/type/matcher/v3/http_inputs.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/type/matcher/v3/http_inputs.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/type/matcher/v3/metadata.upb.h", + "src/src/core/ext/upb-gen/envoy/type/matcher/v3/metadata.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/type/matcher/v3/metadata.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/type/matcher/v3/node.upb.h", + "src/src/core/ext/upb-gen/envoy/type/matcher/v3/node.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/type/matcher/v3/node.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/type/matcher/v3/number.upb.h", + "src/src/core/ext/upb-gen/envoy/type/matcher/v3/number.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/type/matcher/v3/number.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/type/matcher/v3/path.upb.h", + "src/src/core/ext/upb-gen/envoy/type/matcher/v3/path.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/type/matcher/v3/path.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/type/matcher/v3/regex.upb.h", + "src/src/core/ext/upb-gen/envoy/type/matcher/v3/regex.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/type/matcher/v3/regex.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/type/matcher/v3/status_code_input.upb.h", + "src/src/core/ext/upb-gen/envoy/type/matcher/v3/status_code_input.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/type/matcher/v3/status_code_input.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/type/matcher/v3/string.upb.h", + "src/src/core/ext/upb-gen/envoy/type/matcher/v3/string.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/type/matcher/v3/string.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/type/matcher/v3/struct.upb.h", + "src/src/core/ext/upb-gen/envoy/type/matcher/v3/struct.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/type/matcher/v3/struct.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/type/matcher/v3/value.upb.h", + "src/src/core/ext/upb-gen/envoy/type/matcher/v3/value.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/type/matcher/v3/value.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/type/metadata/v3/metadata.upb.h", + "src/src/core/ext/upb-gen/envoy/type/metadata/v3/metadata.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/type/metadata/v3/metadata.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/type/tracing/v3/custom_tag.upb.h", + "src/src/core/ext/upb-gen/envoy/type/tracing/v3/custom_tag.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/type/tracing/v3/custom_tag.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/type/v3/hash_policy.upb.h", + "src/src/core/ext/upb-gen/envoy/type/v3/hash_policy.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/type/v3/hash_policy.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/type/v3/http.upb.h", + "src/src/core/ext/upb-gen/envoy/type/v3/http.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/type/v3/http.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/type/v3/http_status.upb.h", + "src/src/core/ext/upb-gen/envoy/type/v3/http_status.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/type/v3/http_status.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/type/v3/percent.upb.h", + "src/src/core/ext/upb-gen/envoy/type/v3/percent.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/type/v3/percent.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/type/v3/range.upb.h", + "src/src/core/ext/upb-gen/envoy/type/v3/range.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/type/v3/range.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/type/v3/ratelimit_strategy.upb.h", + "src/src/core/ext/upb-gen/envoy/type/v3/ratelimit_strategy.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/type/v3/ratelimit_strategy.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/type/v3/ratelimit_unit.upb.h", + "src/src/core/ext/upb-gen/envoy/type/v3/ratelimit_unit.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/type/v3/ratelimit_unit.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/type/v3/semantic_version.upb.h", + "src/src/core/ext/upb-gen/envoy/type/v3/semantic_version.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/type/v3/semantic_version.upb_minitable.h", + "src/src/core/ext/upb-gen/envoy/type/v3/token_bucket.upb.h", + "src/src/core/ext/upb-gen/envoy/type/v3/token_bucket.upb_minitable.c", + "src/src/core/ext/upb-gen/envoy/type/v3/token_bucket.upb_minitable.h", + "src/src/core/ext/upb-gen/google/api/annotations.upb.h", + "src/src/core/ext/upb-gen/google/api/annotations.upb_minitable.c", + "src/src/core/ext/upb-gen/google/api/annotations.upb_minitable.h", + "src/src/core/ext/upb-gen/google/api/expr/v1alpha1/checked.upb.h", + "src/src/core/ext/upb-gen/google/api/expr/v1alpha1/checked.upb_minitable.c", + "src/src/core/ext/upb-gen/google/api/expr/v1alpha1/checked.upb_minitable.h", + "src/src/core/ext/upb-gen/google/api/expr/v1alpha1/syntax.upb.h", + "src/src/core/ext/upb-gen/google/api/expr/v1alpha1/syntax.upb_minitable.c", + "src/src/core/ext/upb-gen/google/api/expr/v1alpha1/syntax.upb_minitable.h", + "src/src/core/ext/upb-gen/google/api/http.upb.h", + "src/src/core/ext/upb-gen/google/api/http.upb_minitable.c", + "src/src/core/ext/upb-gen/google/api/http.upb_minitable.h", + "src/src/core/ext/upb-gen/google/api/httpbody.upb.h", + "src/src/core/ext/upb-gen/google/api/httpbody.upb_minitable.c", + "src/src/core/ext/upb-gen/google/api/httpbody.upb_minitable.h", + "src/src/core/ext/upb-gen/google/protobuf/any.upb.h", + "src/src/core/ext/upb-gen/google/protobuf/any.upb_minitable.c", + "src/src/core/ext/upb-gen/google/protobuf/any.upb_minitable.h", + "src/src/core/ext/upb-gen/google/protobuf/descriptor.upb.h", + "src/src/core/ext/upb-gen/google/protobuf/descriptor.upb_minitable.c", + "src/src/core/ext/upb-gen/google/protobuf/descriptor.upb_minitable.h", + "src/src/core/ext/upb-gen/google/protobuf/duration.upb.h", + "src/src/core/ext/upb-gen/google/protobuf/duration.upb_minitable.c", + "src/src/core/ext/upb-gen/google/protobuf/duration.upb_minitable.h", + "src/src/core/ext/upb-gen/google/protobuf/empty.upb.h", + "src/src/core/ext/upb-gen/google/protobuf/empty.upb_minitable.c", + "src/src/core/ext/upb-gen/google/protobuf/empty.upb_minitable.h", + "src/src/core/ext/upb-gen/google/protobuf/struct.upb.h", + "src/src/core/ext/upb-gen/google/protobuf/struct.upb_minitable.c", + "src/src/core/ext/upb-gen/google/protobuf/struct.upb_minitable.h", + "src/src/core/ext/upb-gen/google/protobuf/timestamp.upb.h", + "src/src/core/ext/upb-gen/google/protobuf/timestamp.upb_minitable.c", + "src/src/core/ext/upb-gen/google/protobuf/timestamp.upb_minitable.h", + "src/src/core/ext/upb-gen/google/protobuf/wrappers.upb.h", + "src/src/core/ext/upb-gen/google/protobuf/wrappers.upb_minitable.c", + "src/src/core/ext/upb-gen/google/protobuf/wrappers.upb_minitable.h", + "src/src/core/ext/upb-gen/google/rpc/status.upb.h", + "src/src/core/ext/upb-gen/google/rpc/status.upb_minitable.c", + "src/src/core/ext/upb-gen/google/rpc/status.upb_minitable.h", + "src/src/core/ext/upb-gen/opencensus/proto/trace/v1/trace_config.upb.h", + "src/src/core/ext/upb-gen/opencensus/proto/trace/v1/trace_config.upb_minitable.c", + "src/src/core/ext/upb-gen/opencensus/proto/trace/v1/trace_config.upb_minitable.h", + "src/src/core/ext/upb-gen/src/proto/grpc/gcp/altscontext.upb.h", + "src/src/core/ext/upb-gen/src/proto/grpc/gcp/altscontext.upb_minitable.c", + "src/src/core/ext/upb-gen/src/proto/grpc/gcp/altscontext.upb_minitable.h", + "src/src/core/ext/upb-gen/src/proto/grpc/gcp/handshaker.upb.h", + "src/src/core/ext/upb-gen/src/proto/grpc/gcp/handshaker.upb_minitable.c", + "src/src/core/ext/upb-gen/src/proto/grpc/gcp/handshaker.upb_minitable.h", + "src/src/core/ext/upb-gen/src/proto/grpc/gcp/transport_security_common.upb.h", + "src/src/core/ext/upb-gen/src/proto/grpc/gcp/transport_security_common.upb_minitable.c", + "src/src/core/ext/upb-gen/src/proto/grpc/gcp/transport_security_common.upb_minitable.h", + "src/src/core/ext/upb-gen/src/proto/grpc/health/v1/health.upb.h", + "src/src/core/ext/upb-gen/src/proto/grpc/health/v1/health.upb_minitable.c", + "src/src/core/ext/upb-gen/src/proto/grpc/health/v1/health.upb_minitable.h", + "src/src/core/ext/upb-gen/src/proto/grpc/lb/v1/load_balancer.upb.h", + "src/src/core/ext/upb-gen/src/proto/grpc/lb/v1/load_balancer.upb_minitable.c", + "src/src/core/ext/upb-gen/src/proto/grpc/lb/v1/load_balancer.upb_minitable.h", + "src/src/core/ext/upb-gen/src/proto/grpc/lookup/v1/rls.upb.h", + "src/src/core/ext/upb-gen/src/proto/grpc/lookup/v1/rls.upb_minitable.c", + "src/src/core/ext/upb-gen/src/proto/grpc/lookup/v1/rls.upb_minitable.h", + "src/src/core/ext/upb-gen/src/proto/grpc/lookup/v1/rls_config.upb.h", + "src/src/core/ext/upb-gen/src/proto/grpc/lookup/v1/rls_config.upb_minitable.c", + "src/src/core/ext/upb-gen/src/proto/grpc/lookup/v1/rls_config.upb_minitable.h", + "src/src/core/ext/upb-gen/udpa/annotations/migrate.upb.h", + "src/src/core/ext/upb-gen/udpa/annotations/migrate.upb_minitable.c", + "src/src/core/ext/upb-gen/udpa/annotations/migrate.upb_minitable.h", + "src/src/core/ext/upb-gen/udpa/annotations/security.upb.h", + "src/src/core/ext/upb-gen/udpa/annotations/security.upb_minitable.c", + "src/src/core/ext/upb-gen/udpa/annotations/security.upb_minitable.h", + "src/src/core/ext/upb-gen/udpa/annotations/sensitive.upb.h", + "src/src/core/ext/upb-gen/udpa/annotations/sensitive.upb_minitable.c", + "src/src/core/ext/upb-gen/udpa/annotations/sensitive.upb_minitable.h", + "src/src/core/ext/upb-gen/udpa/annotations/status.upb.h", + "src/src/core/ext/upb-gen/udpa/annotations/status.upb_minitable.c", + "src/src/core/ext/upb-gen/udpa/annotations/status.upb_minitable.h", + "src/src/core/ext/upb-gen/udpa/annotations/versioning.upb.h", + "src/src/core/ext/upb-gen/udpa/annotations/versioning.upb_minitable.c", + "src/src/core/ext/upb-gen/udpa/annotations/versioning.upb_minitable.h", + "src/src/core/ext/upb-gen/validate/validate.upb.h", + "src/src/core/ext/upb-gen/validate/validate.upb_minitable.c", + "src/src/core/ext/upb-gen/validate/validate.upb_minitable.h", + "src/src/core/ext/upb-gen/xds/annotations/v3/migrate.upb.h", + "src/src/core/ext/upb-gen/xds/annotations/v3/migrate.upb_minitable.c", + "src/src/core/ext/upb-gen/xds/annotations/v3/migrate.upb_minitable.h", + "src/src/core/ext/upb-gen/xds/annotations/v3/security.upb.h", + "src/src/core/ext/upb-gen/xds/annotations/v3/security.upb_minitable.c", + "src/src/core/ext/upb-gen/xds/annotations/v3/security.upb_minitable.h", + "src/src/core/ext/upb-gen/xds/annotations/v3/sensitive.upb.h", + "src/src/core/ext/upb-gen/xds/annotations/v3/sensitive.upb_minitable.c", + "src/src/core/ext/upb-gen/xds/annotations/v3/sensitive.upb_minitable.h", + "src/src/core/ext/upb-gen/xds/annotations/v3/status.upb.h", + "src/src/core/ext/upb-gen/xds/annotations/v3/status.upb_minitable.c", + "src/src/core/ext/upb-gen/xds/annotations/v3/status.upb_minitable.h", + "src/src/core/ext/upb-gen/xds/annotations/v3/versioning.upb.h", + "src/src/core/ext/upb-gen/xds/annotations/v3/versioning.upb_minitable.c", + "src/src/core/ext/upb-gen/xds/annotations/v3/versioning.upb_minitable.h", + "src/src/core/ext/upb-gen/xds/core/v3/authority.upb.h", + "src/src/core/ext/upb-gen/xds/core/v3/authority.upb_minitable.c", + "src/src/core/ext/upb-gen/xds/core/v3/authority.upb_minitable.h", + "src/src/core/ext/upb-gen/xds/core/v3/cidr.upb.h", + "src/src/core/ext/upb-gen/xds/core/v3/cidr.upb_minitable.c", + "src/src/core/ext/upb-gen/xds/core/v3/cidr.upb_minitable.h", + "src/src/core/ext/upb-gen/xds/core/v3/collection_entry.upb.h", + "src/src/core/ext/upb-gen/xds/core/v3/collection_entry.upb_minitable.c", + "src/src/core/ext/upb-gen/xds/core/v3/collection_entry.upb_minitable.h", + "src/src/core/ext/upb-gen/xds/core/v3/context_params.upb.h", + "src/src/core/ext/upb-gen/xds/core/v3/context_params.upb_minitable.c", + "src/src/core/ext/upb-gen/xds/core/v3/context_params.upb_minitable.h", + "src/src/core/ext/upb-gen/xds/core/v3/extension.upb.h", + "src/src/core/ext/upb-gen/xds/core/v3/extension.upb_minitable.c", + "src/src/core/ext/upb-gen/xds/core/v3/extension.upb_minitable.h", + "src/src/core/ext/upb-gen/xds/core/v3/resource.upb.h", + "src/src/core/ext/upb-gen/xds/core/v3/resource.upb_minitable.c", + "src/src/core/ext/upb-gen/xds/core/v3/resource.upb_minitable.h", + "src/src/core/ext/upb-gen/xds/core/v3/resource_locator.upb.h", + "src/src/core/ext/upb-gen/xds/core/v3/resource_locator.upb_minitable.c", + "src/src/core/ext/upb-gen/xds/core/v3/resource_locator.upb_minitable.h", + "src/src/core/ext/upb-gen/xds/core/v3/resource_name.upb.h", + "src/src/core/ext/upb-gen/xds/core/v3/resource_name.upb_minitable.c", + "src/src/core/ext/upb-gen/xds/core/v3/resource_name.upb_minitable.h", + "src/src/core/ext/upb-gen/xds/data/orca/v3/orca_load_report.upb.h", + "src/src/core/ext/upb-gen/xds/data/orca/v3/orca_load_report.upb_minitable.c", + "src/src/core/ext/upb-gen/xds/data/orca/v3/orca_load_report.upb_minitable.h", + "src/src/core/ext/upb-gen/xds/service/orca/v3/orca.upb.h", + "src/src/core/ext/upb-gen/xds/service/orca/v3/orca.upb_minitable.c", + "src/src/core/ext/upb-gen/xds/service/orca/v3/orca.upb_minitable.h", + "src/src/core/ext/upb-gen/xds/type/matcher/v3/cel.upb.h", + "src/src/core/ext/upb-gen/xds/type/matcher/v3/cel.upb_minitable.c", + "src/src/core/ext/upb-gen/xds/type/matcher/v3/cel.upb_minitable.h", + "src/src/core/ext/upb-gen/xds/type/matcher/v3/domain.upb.h", + "src/src/core/ext/upb-gen/xds/type/matcher/v3/domain.upb_minitable.c", + "src/src/core/ext/upb-gen/xds/type/matcher/v3/domain.upb_minitable.h", + "src/src/core/ext/upb-gen/xds/type/matcher/v3/http_inputs.upb.h", + "src/src/core/ext/upb-gen/xds/type/matcher/v3/http_inputs.upb_minitable.c", + "src/src/core/ext/upb-gen/xds/type/matcher/v3/http_inputs.upb_minitable.h", + "src/src/core/ext/upb-gen/xds/type/matcher/v3/ip.upb.h", + "src/src/core/ext/upb-gen/xds/type/matcher/v3/ip.upb_minitable.c", + "src/src/core/ext/upb-gen/xds/type/matcher/v3/ip.upb_minitable.h", + "src/src/core/ext/upb-gen/xds/type/matcher/v3/matcher.upb.h", + "src/src/core/ext/upb-gen/xds/type/matcher/v3/matcher.upb_minitable.c", + "src/src/core/ext/upb-gen/xds/type/matcher/v3/matcher.upb_minitable.h", + "src/src/core/ext/upb-gen/xds/type/matcher/v3/range.upb.h", + "src/src/core/ext/upb-gen/xds/type/matcher/v3/range.upb_minitable.c", + "src/src/core/ext/upb-gen/xds/type/matcher/v3/range.upb_minitable.h", + "src/src/core/ext/upb-gen/xds/type/matcher/v3/regex.upb.h", + "src/src/core/ext/upb-gen/xds/type/matcher/v3/regex.upb_minitable.c", + "src/src/core/ext/upb-gen/xds/type/matcher/v3/regex.upb_minitable.h", + "src/src/core/ext/upb-gen/xds/type/matcher/v3/string.upb.h", + "src/src/core/ext/upb-gen/xds/type/matcher/v3/string.upb_minitable.c", + "src/src/core/ext/upb-gen/xds/type/matcher/v3/string.upb_minitable.h", + "src/src/core/ext/upb-gen/xds/type/v3/cel.upb.h", + "src/src/core/ext/upb-gen/xds/type/v3/cel.upb_minitable.c", + "src/src/core/ext/upb-gen/xds/type/v3/cel.upb_minitable.h", + "src/src/core/ext/upb-gen/xds/type/v3/range.upb.h", + "src/src/core/ext/upb-gen/xds/type/v3/range.upb_minitable.c", + "src/src/core/ext/upb-gen/xds/type/v3/range.upb_minitable.h", + "src/src/core/ext/upb-gen/xds/type/v3/typed_struct.upb.h", + "src/src/core/ext/upb-gen/xds/type/v3/typed_struct.upb_minitable.c", + "src/src/core/ext/upb-gen/xds/type/v3/typed_struct.upb_minitable.h", + "src/src/core/ext/upbdefs-gen/envoy/admin/v3/certs.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/admin/v3/certs.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/admin/v3/clusters.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/admin/v3/clusters.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/admin/v3/config_dump.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/admin/v3/config_dump.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/admin/v3/config_dump_shared.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/admin/v3/config_dump_shared.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/admin/v3/init_dump.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/admin/v3/init_dump.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/admin/v3/listeners.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/admin/v3/listeners.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/admin/v3/memory.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/admin/v3/memory.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/admin/v3/metrics.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/admin/v3/metrics.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/admin/v3/mutex_stats.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/admin/v3/mutex_stats.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/admin/v3/server_info.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/admin/v3/server_info.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/admin/v3/tap.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/admin/v3/tap.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/annotations/deprecation.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/annotations/deprecation.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/annotations/resource.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/annotations/resource.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/config/accesslog/v3/accesslog.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/config/accesslog/v3/accesslog.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/config/bootstrap/v3/bootstrap.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/config/bootstrap/v3/bootstrap.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/config/cluster/v3/circuit_breaker.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/config/cluster/v3/circuit_breaker.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/config/cluster/v3/cluster.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/config/cluster/v3/cluster.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/config/cluster/v3/filter.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/config/cluster/v3/filter.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/config/cluster/v3/outlier_detection.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/config/cluster/v3/outlier_detection.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/config/common/matcher/v3/matcher.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/config/common/matcher/v3/matcher.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/config/core/v3/address.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/config/core/v3/address.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/config/core/v3/backoff.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/config/core/v3/backoff.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/config/core/v3/base.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/config/core/v3/base.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/config/core/v3/config_source.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/config/core/v3/config_source.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/config/core/v3/event_service_config.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/config/core/v3/event_service_config.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/config/core/v3/extension.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/config/core/v3/extension.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/config/core/v3/grpc_method_list.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/config/core/v3/grpc_method_list.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/config/core/v3/grpc_service.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/config/core/v3/grpc_service.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/config/core/v3/health_check.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/config/core/v3/health_check.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/config/core/v3/http_service.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/config/core/v3/http_service.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/config/core/v3/http_uri.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/config/core/v3/http_uri.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/config/core/v3/protocol.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/config/core/v3/protocol.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/config/core/v3/proxy_protocol.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/config/core/v3/proxy_protocol.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/config/core/v3/resolver.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/config/core/v3/resolver.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/config/core/v3/socket_option.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/config/core/v3/socket_option.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/config/core/v3/substitution_format_string.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/config/core/v3/substitution_format_string.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/config/core/v3/udp_socket_config.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/config/core/v3/udp_socket_config.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/config/endpoint/v3/endpoint.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/config/endpoint/v3/endpoint.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/config/endpoint/v3/endpoint_components.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/config/endpoint/v3/endpoint_components.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/config/endpoint/v3/load_report.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/config/endpoint/v3/load_report.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/config/listener/v3/api_listener.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/config/listener/v3/api_listener.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/config/listener/v3/listener.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/config/listener/v3/listener.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/config/listener/v3/listener_components.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/config/listener/v3/listener_components.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/config/listener/v3/quic_config.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/config/listener/v3/quic_config.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/config/listener/v3/udp_listener_config.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/config/listener/v3/udp_listener_config.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/config/metrics/v3/metrics_service.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/config/metrics/v3/metrics_service.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/config/metrics/v3/stats.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/config/metrics/v3/stats.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/config/overload/v3/overload.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/config/overload/v3/overload.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/config/rbac/v3/rbac.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/config/rbac/v3/rbac.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/config/route/v3/route.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/config/route/v3/route.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/config/route/v3/route_components.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/config/route/v3/route_components.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/config/route/v3/scoped_route.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/config/route/v3/scoped_route.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/config/tap/v3/common.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/config/tap/v3/common.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/config/trace/v3/datadog.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/config/trace/v3/datadog.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/config/trace/v3/dynamic_ot.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/config/trace/v3/dynamic_ot.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/config/trace/v3/http_tracer.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/config/trace/v3/http_tracer.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/config/trace/v3/lightstep.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/config/trace/v3/lightstep.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/config/trace/v3/opencensus.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/config/trace/v3/opencensus.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/config/trace/v3/opentelemetry.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/config/trace/v3/opentelemetry.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/config/trace/v3/service.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/config/trace/v3/service.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/config/trace/v3/skywalking.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/config/trace/v3/skywalking.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/config/trace/v3/trace.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/config/trace/v3/trace.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/config/trace/v3/xray.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/config/trace/v3/xray.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/config/trace/v3/zipkin.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/config/trace/v3/zipkin.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/data/accesslog/v3/accesslog.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/data/accesslog/v3/accesslog.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/extensions/clusters/aggregate/v3/cluster.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/extensions/clusters/aggregate/v3/cluster.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/extensions/filters/common/fault/v3/fault.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/extensions/filters/common/fault/v3/fault.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/extensions/filters/http/fault/v3/fault.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/extensions/filters/http/fault/v3/fault.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/extensions/filters/http/rbac/v3/rbac.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/extensions/filters/http/rbac/v3/rbac.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/extensions/filters/http/router/v3/router.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/extensions/filters/http/router/v3/router.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/extensions/filters/http/stateful_session/v3/stateful_session.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/extensions/filters/http/stateful_session/v3/stateful_session.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/extensions/http/stateful_session/cookie/v3/cookie.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/extensions/http/stateful_session/cookie/v3/cookie.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/extensions/transport_sockets/tls/v3/cert.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/extensions/transport_sockets/tls/v3/cert.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/extensions/transport_sockets/tls/v3/common.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/extensions/transport_sockets/tls/v3/common.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/extensions/transport_sockets/tls/v3/secret.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/extensions/transport_sockets/tls/v3/secret.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/extensions/transport_sockets/tls/v3/tls.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/extensions/transport_sockets/tls/v3/tls.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/extensions/transport_sockets/tls/v3/tls_spiffe_validator_config.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/extensions/transport_sockets/tls/v3/tls_spiffe_validator_config.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/extensions/upstreams/http/v3/http_protocol_options.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/extensions/upstreams/http/v3/http_protocol_options.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/service/discovery/v3/ads.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/service/discovery/v3/ads.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/service/discovery/v3/discovery.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/service/discovery/v3/discovery.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/service/load_stats/v3/lrs.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/service/load_stats/v3/lrs.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/service/status/v3/csds.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/service/status/v3/csds.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/type/http/v3/cookie.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/type/http/v3/cookie.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/type/http/v3/path_transformation.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/type/http/v3/path_transformation.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/type/matcher/v3/filter_state.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/type/matcher/v3/filter_state.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/type/matcher/v3/http_inputs.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/type/matcher/v3/http_inputs.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/type/matcher/v3/metadata.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/type/matcher/v3/metadata.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/type/matcher/v3/node.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/type/matcher/v3/node.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/type/matcher/v3/number.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/type/matcher/v3/number.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/type/matcher/v3/path.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/type/matcher/v3/path.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/type/matcher/v3/regex.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/type/matcher/v3/regex.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/type/matcher/v3/status_code_input.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/type/matcher/v3/status_code_input.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/type/matcher/v3/string.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/type/matcher/v3/string.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/type/matcher/v3/struct.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/type/matcher/v3/struct.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/type/matcher/v3/value.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/type/matcher/v3/value.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/type/metadata/v3/metadata.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/type/metadata/v3/metadata.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/type/tracing/v3/custom_tag.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/type/tracing/v3/custom_tag.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/type/v3/hash_policy.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/type/v3/hash_policy.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/type/v3/http.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/type/v3/http.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/type/v3/http_status.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/type/v3/http_status.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/type/v3/percent.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/type/v3/percent.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/type/v3/range.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/type/v3/range.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/type/v3/ratelimit_strategy.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/type/v3/ratelimit_strategy.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/type/v3/ratelimit_unit.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/type/v3/ratelimit_unit.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/type/v3/semantic_version.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/type/v3/semantic_version.upbdefs.h", + "src/src/core/ext/upbdefs-gen/envoy/type/v3/token_bucket.upbdefs.c", + "src/src/core/ext/upbdefs-gen/envoy/type/v3/token_bucket.upbdefs.h", + "src/src/core/ext/upbdefs-gen/google/api/annotations.upbdefs.c", + "src/src/core/ext/upbdefs-gen/google/api/annotations.upbdefs.h", + "src/src/core/ext/upbdefs-gen/google/api/expr/v1alpha1/checked.upbdefs.c", + "src/src/core/ext/upbdefs-gen/google/api/expr/v1alpha1/checked.upbdefs.h", + "src/src/core/ext/upbdefs-gen/google/api/expr/v1alpha1/syntax.upbdefs.c", + "src/src/core/ext/upbdefs-gen/google/api/expr/v1alpha1/syntax.upbdefs.h", + "src/src/core/ext/upbdefs-gen/google/api/http.upbdefs.c", + "src/src/core/ext/upbdefs-gen/google/api/http.upbdefs.h", + "src/src/core/ext/upbdefs-gen/google/api/httpbody.upbdefs.c", + "src/src/core/ext/upbdefs-gen/google/api/httpbody.upbdefs.h", + "src/src/core/ext/upbdefs-gen/google/protobuf/any.upbdefs.c", + "src/src/core/ext/upbdefs-gen/google/protobuf/any.upbdefs.h", + "src/src/core/ext/upbdefs-gen/google/protobuf/descriptor.upbdefs.c", + "src/src/core/ext/upbdefs-gen/google/protobuf/descriptor.upbdefs.h", + "src/src/core/ext/upbdefs-gen/google/protobuf/duration.upbdefs.c", + "src/src/core/ext/upbdefs-gen/google/protobuf/duration.upbdefs.h", + "src/src/core/ext/upbdefs-gen/google/protobuf/empty.upbdefs.c", + "src/src/core/ext/upbdefs-gen/google/protobuf/empty.upbdefs.h", + "src/src/core/ext/upbdefs-gen/google/protobuf/struct.upbdefs.c", + "src/src/core/ext/upbdefs-gen/google/protobuf/struct.upbdefs.h", + "src/src/core/ext/upbdefs-gen/google/protobuf/timestamp.upbdefs.c", + "src/src/core/ext/upbdefs-gen/google/protobuf/timestamp.upbdefs.h", + "src/src/core/ext/upbdefs-gen/google/protobuf/wrappers.upbdefs.c", + "src/src/core/ext/upbdefs-gen/google/protobuf/wrappers.upbdefs.h", + "src/src/core/ext/upbdefs-gen/google/rpc/status.upbdefs.c", + "src/src/core/ext/upbdefs-gen/google/rpc/status.upbdefs.h", + "src/src/core/ext/upbdefs-gen/opencensus/proto/trace/v1/trace_config.upbdefs.c", + "src/src/core/ext/upbdefs-gen/opencensus/proto/trace/v1/trace_config.upbdefs.h", + "src/src/core/ext/upbdefs-gen/src/proto/grpc/lookup/v1/rls_config.upbdefs.c", + "src/src/core/ext/upbdefs-gen/src/proto/grpc/lookup/v1/rls_config.upbdefs.h", + "src/src/core/ext/upbdefs-gen/udpa/annotations/migrate.upbdefs.c", + "src/src/core/ext/upbdefs-gen/udpa/annotations/migrate.upbdefs.h", + "src/src/core/ext/upbdefs-gen/udpa/annotations/security.upbdefs.c", + "src/src/core/ext/upbdefs-gen/udpa/annotations/security.upbdefs.h", + "src/src/core/ext/upbdefs-gen/udpa/annotations/sensitive.upbdefs.c", + "src/src/core/ext/upbdefs-gen/udpa/annotations/sensitive.upbdefs.h", + "src/src/core/ext/upbdefs-gen/udpa/annotations/status.upbdefs.c", + "src/src/core/ext/upbdefs-gen/udpa/annotations/status.upbdefs.h", + "src/src/core/ext/upbdefs-gen/udpa/annotations/versioning.upbdefs.c", + "src/src/core/ext/upbdefs-gen/udpa/annotations/versioning.upbdefs.h", + "src/src/core/ext/upbdefs-gen/validate/validate.upbdefs.c", + "src/src/core/ext/upbdefs-gen/validate/validate.upbdefs.h", + "src/src/core/ext/upbdefs-gen/xds/annotations/v3/migrate.upbdefs.c", + "src/src/core/ext/upbdefs-gen/xds/annotations/v3/migrate.upbdefs.h", + "src/src/core/ext/upbdefs-gen/xds/annotations/v3/security.upbdefs.c", + "src/src/core/ext/upbdefs-gen/xds/annotations/v3/security.upbdefs.h", + "src/src/core/ext/upbdefs-gen/xds/annotations/v3/sensitive.upbdefs.c", + "src/src/core/ext/upbdefs-gen/xds/annotations/v3/sensitive.upbdefs.h", + "src/src/core/ext/upbdefs-gen/xds/annotations/v3/status.upbdefs.c", + "src/src/core/ext/upbdefs-gen/xds/annotations/v3/status.upbdefs.h", + "src/src/core/ext/upbdefs-gen/xds/annotations/v3/versioning.upbdefs.c", + "src/src/core/ext/upbdefs-gen/xds/annotations/v3/versioning.upbdefs.h", + "src/src/core/ext/upbdefs-gen/xds/core/v3/authority.upbdefs.c", + "src/src/core/ext/upbdefs-gen/xds/core/v3/authority.upbdefs.h", + "src/src/core/ext/upbdefs-gen/xds/core/v3/cidr.upbdefs.c", + "src/src/core/ext/upbdefs-gen/xds/core/v3/cidr.upbdefs.h", + "src/src/core/ext/upbdefs-gen/xds/core/v3/collection_entry.upbdefs.c", + "src/src/core/ext/upbdefs-gen/xds/core/v3/collection_entry.upbdefs.h", + "src/src/core/ext/upbdefs-gen/xds/core/v3/context_params.upbdefs.c", + "src/src/core/ext/upbdefs-gen/xds/core/v3/context_params.upbdefs.h", + "src/src/core/ext/upbdefs-gen/xds/core/v3/extension.upbdefs.c", + "src/src/core/ext/upbdefs-gen/xds/core/v3/extension.upbdefs.h", + "src/src/core/ext/upbdefs-gen/xds/core/v3/resource.upbdefs.c", + "src/src/core/ext/upbdefs-gen/xds/core/v3/resource.upbdefs.h", + "src/src/core/ext/upbdefs-gen/xds/core/v3/resource_locator.upbdefs.c", + "src/src/core/ext/upbdefs-gen/xds/core/v3/resource_locator.upbdefs.h", + "src/src/core/ext/upbdefs-gen/xds/core/v3/resource_name.upbdefs.c", + "src/src/core/ext/upbdefs-gen/xds/core/v3/resource_name.upbdefs.h", + "src/src/core/ext/upbdefs-gen/xds/type/matcher/v3/cel.upbdefs.c", + "src/src/core/ext/upbdefs-gen/xds/type/matcher/v3/cel.upbdefs.h", + "src/src/core/ext/upbdefs-gen/xds/type/matcher/v3/domain.upbdefs.c", + "src/src/core/ext/upbdefs-gen/xds/type/matcher/v3/domain.upbdefs.h", + "src/src/core/ext/upbdefs-gen/xds/type/matcher/v3/http_inputs.upbdefs.c", + "src/src/core/ext/upbdefs-gen/xds/type/matcher/v3/http_inputs.upbdefs.h", + "src/src/core/ext/upbdefs-gen/xds/type/matcher/v3/ip.upbdefs.c", + "src/src/core/ext/upbdefs-gen/xds/type/matcher/v3/ip.upbdefs.h", + "src/src/core/ext/upbdefs-gen/xds/type/matcher/v3/matcher.upbdefs.c", + "src/src/core/ext/upbdefs-gen/xds/type/matcher/v3/matcher.upbdefs.h", + "src/src/core/ext/upbdefs-gen/xds/type/matcher/v3/range.upbdefs.c", + "src/src/core/ext/upbdefs-gen/xds/type/matcher/v3/range.upbdefs.h", + "src/src/core/ext/upbdefs-gen/xds/type/matcher/v3/regex.upbdefs.c", + "src/src/core/ext/upbdefs-gen/xds/type/matcher/v3/regex.upbdefs.h", + "src/src/core/ext/upbdefs-gen/xds/type/matcher/v3/string.upbdefs.c", + "src/src/core/ext/upbdefs-gen/xds/type/matcher/v3/string.upbdefs.h", + "src/src/core/ext/upbdefs-gen/xds/type/v3/cel.upbdefs.c", + "src/src/core/ext/upbdefs-gen/xds/type/v3/cel.upbdefs.h", + "src/src/core/ext/upbdefs-gen/xds/type/v3/range.upbdefs.c", + "src/src/core/ext/upbdefs-gen/xds/type/v3/range.upbdefs.h", + "src/src/core/ext/upbdefs-gen/xds/type/v3/typed_struct.upbdefs.c", + "src/src/core/ext/upbdefs-gen/xds/type/v3/typed_struct.upbdefs.h", + "src/src/core/handshaker/endpoint_info/endpoint_info_handshaker.cc", + "src/src/core/handshaker/endpoint_info/endpoint_info_handshaker.h", + "src/src/core/handshaker/handshaker.cc", + "src/src/core/handshaker/handshaker.h", + "src/src/core/handshaker/handshaker_factory.h", + "src/src/core/handshaker/handshaker_registry.cc", + "src/src/core/handshaker/handshaker_registry.h", + "src/src/core/handshaker/http_connect/http_connect_handshaker.cc", + "src/src/core/handshaker/http_connect/http_connect_handshaker.h", + "src/src/core/handshaker/http_connect/http_proxy_mapper.cc", + "src/src/core/handshaker/http_connect/http_proxy_mapper.h", + "src/src/core/handshaker/proxy_mapper.h", + "src/src/core/handshaker/proxy_mapper_registry.cc", + "src/src/core/handshaker/proxy_mapper_registry.h", + "src/src/core/handshaker/security/secure_endpoint.cc", + "src/src/core/handshaker/security/secure_endpoint.h", + "src/src/core/handshaker/security/security_handshaker.cc", + "src/src/core/handshaker/security/security_handshaker.h", + "src/src/core/handshaker/security/tsi_error.cc", + "src/src/core/handshaker/security/tsi_error.h", + "src/src/core/handshaker/tcp_connect/tcp_connect_handshaker.cc", + "src/src/core/handshaker/tcp_connect/tcp_connect_handshaker.h", "src/src/core/lib/address_utils/parse_address.cc", "src/src/core/lib/address_utils/parse_address.h", "src/src/core/lib/address_utils/sockaddr_utils.cc", @@ -4266,7 +4757,10 @@ source_set("grpc") { "src/src/core/lib/avl/avl.h", "src/src/core/lib/backoff/backoff.cc", "src/src/core/lib/backoff/backoff.h", + "src/src/core/lib/backoff/random_early_detection.cc", + "src/src/core/lib/backoff/random_early_detection.h", "src/src/core/lib/channel/call_finalization.h", + "src/src/core/lib/channel/call_tracer.cc", "src/src/core/lib/channel/call_tracer.h", "src/src/core/lib/channel/channel_args.cc", "src/src/core/lib/channel/channel_args.h", @@ -4279,19 +4773,18 @@ source_set("grpc") { "src/src/core/lib/channel/channel_stack_builder.h", "src/src/core/lib/channel/channel_stack_builder_impl.cc", "src/src/core/lib/channel/channel_stack_builder_impl.h", - "src/src/core/lib/channel/channel_trace.cc", - "src/src/core/lib/channel/channel_trace.h", - "src/src/core/lib/channel/channelz.cc", - "src/src/core/lib/channel/channelz.h", - "src/src/core/lib/channel/channelz_registry.cc", - "src/src/core/lib/channel/channelz_registry.h", + "src/src/core/lib/channel/channel_stack_trace.cc", + "src/src/core/lib/channel/channel_stack_trace.h", "src/src/core/lib/channel/connected_channel.cc", "src/src/core/lib/channel/connected_channel.h", "src/src/core/lib/channel/context.h", + "src/src/core/lib/channel/metrics.cc", + "src/src/core/lib/channel/metrics.h", "src/src/core/lib/channel/promise_based_filter.cc", "src/src/core/lib/channel/promise_based_filter.h", "src/src/core/lib/channel/status_util.cc", "src/src/core/lib/channel/status_util.h", + "src/src/core/lib/channel/tcp_tracer.h", "src/src/core/lib/compression/compression.cc", "src/src/core/lib/compression/compression_internal.cc", "src/src/core/lib/compression/compression_internal.h", @@ -4309,6 +4802,15 @@ source_set("grpc") { "src/src/core/lib/debug/stats_data.h", "src/src/core/lib/debug/trace.cc", "src/src/core/lib/debug/trace.h", + "src/src/core/lib/event_engine/ares_resolver.cc", + "src/src/core/lib/event_engine/ares_resolver.h", + "src/src/core/lib/event_engine/cf_engine/cf_engine.cc", + "src/src/core/lib/event_engine/cf_engine/cf_engine.h", + "src/src/core/lib/event_engine/cf_engine/cfstream_endpoint.cc", + "src/src/core/lib/event_engine/cf_engine/cfstream_endpoint.h", + "src/src/core/lib/event_engine/cf_engine/cftype_unique_ref.h", + "src/src/core/lib/event_engine/cf_engine/dns_service_resolver.cc", + "src/src/core/lib/event_engine/cf_engine/dns_service_resolver.h", "src/src/core/lib/event_engine/channel_args_endpoint_config.cc", "src/src/core/lib/event_engine/channel_args_endpoint_config.h", "src/src/core/lib/event_engine/common_closures.h", @@ -4317,11 +4819,16 @@ source_set("grpc") { "src/src/core/lib/event_engine/default_event_engine_factory.cc", "src/src/core/lib/event_engine/default_event_engine_factory.h", "src/src/core/lib/event_engine/event_engine.cc", - "src/src/core/lib/event_engine/executor/executor.h", + "src/src/core/lib/event_engine/event_engine_context.h", + "src/src/core/lib/event_engine/extensions/can_track_errors.h", + "src/src/core/lib/event_engine/extensions/chaotic_good_extension.h", + "src/src/core/lib/event_engine/extensions/supports_fd.h", "src/src/core/lib/event_engine/forkable.cc", "src/src/core/lib/event_engine/forkable.h", + "src/src/core/lib/event_engine/grpc_polled_fd.h", "src/src/core/lib/event_engine/handle_containers.h", - "src/src/core/lib/event_engine/memory_allocator.cc", + "src/src/core/lib/event_engine/memory_allocator_factory.h", + "src/src/core/lib/event_engine/nameser.h", "src/src/core/lib/event_engine/poller.h", "src/src/core/lib/event_engine/posix.h", "src/src/core/lib/event_engine/posix_engine/ev_epoll1_linux.cc", @@ -4331,10 +4838,13 @@ source_set("grpc") { "src/src/core/lib/event_engine/posix_engine/event_poller.h", "src/src/core/lib/event_engine/posix_engine/event_poller_posix_default.cc", "src/src/core/lib/event_engine/posix_engine/event_poller_posix_default.h", + "src/src/core/lib/event_engine/posix_engine/grpc_polled_fd_posix.h", "src/src/core/lib/event_engine/posix_engine/internal_errqueue.cc", "src/src/core/lib/event_engine/posix_engine/internal_errqueue.h", "src/src/core/lib/event_engine/posix_engine/lockfree_event.cc", "src/src/core/lib/event_engine/posix_engine/lockfree_event.h", + "src/src/core/lib/event_engine/posix_engine/native_posix_dns_resolver.cc", + "src/src/core/lib/event_engine/posix_engine/native_posix_dns_resolver.h", "src/src/core/lib/event_engine/posix_engine/posix_endpoint.cc", "src/src/core/lib/event_engine/posix_engine/posix_endpoint.h", "src/src/core/lib/event_engine/posix_engine/posix_engine.cc", @@ -4361,6 +4871,8 @@ source_set("grpc") { "src/src/core/lib/event_engine/posix_engine/wakeup_fd_posix.h", "src/src/core/lib/event_engine/posix_engine/wakeup_fd_posix_default.cc", "src/src/core/lib/event_engine/posix_engine/wakeup_fd_posix_default.h", + "src/src/core/lib/event_engine/query_extensions.h", + "src/src/core/lib/event_engine/ref_counted_dns_resolver_interface.h", "src/src/core/lib/event_engine/resolved_address.cc", "src/src/core/lib/event_engine/resolved_address_internal.h", "src/src/core/lib/event_engine/shim.cc", @@ -4369,22 +4881,37 @@ source_set("grpc") { "src/src/core/lib/event_engine/slice_buffer.cc", "src/src/core/lib/event_engine/tcp_socket_utils.cc", "src/src/core/lib/event_engine/tcp_socket_utils.h", - "src/src/core/lib/event_engine/thread_pool.cc", - "src/src/core/lib/event_engine/thread_pool.h", + "src/src/core/lib/event_engine/thread_pool/thread_count.cc", + "src/src/core/lib/event_engine/thread_pool/thread_count.h", + "src/src/core/lib/event_engine/thread_pool/thread_pool.h", + "src/src/core/lib/event_engine/thread_pool/thread_pool_factory.cc", + "src/src/core/lib/event_engine/thread_pool/work_stealing_thread_pool.cc", + "src/src/core/lib/event_engine/thread_pool/work_stealing_thread_pool.h", + "src/src/core/lib/event_engine/thready_event_engine/thready_event_engine.cc", + "src/src/core/lib/event_engine/thready_event_engine/thready_event_engine.h", "src/src/core/lib/event_engine/time_util.cc", "src/src/core/lib/event_engine/time_util.h", "src/src/core/lib/event_engine/trace.cc", "src/src/core/lib/event_engine/trace.h", "src/src/core/lib/event_engine/utils.cc", "src/src/core/lib/event_engine/utils.h", + "src/src/core/lib/event_engine/windows/grpc_polled_fd_windows.cc", + "src/src/core/lib/event_engine/windows/grpc_polled_fd_windows.h", "src/src/core/lib/event_engine/windows/iocp.cc", "src/src/core/lib/event_engine/windows/iocp.h", + "src/src/core/lib/event_engine/windows/native_windows_dns_resolver.cc", + "src/src/core/lib/event_engine/windows/native_windows_dns_resolver.h", "src/src/core/lib/event_engine/windows/win_socket.cc", "src/src/core/lib/event_engine/windows/win_socket.h", "src/src/core/lib/event_engine/windows/windows_endpoint.cc", "src/src/core/lib/event_engine/windows/windows_endpoint.h", "src/src/core/lib/event_engine/windows/windows_engine.cc", "src/src/core/lib/event_engine/windows/windows_engine.h", + "src/src/core/lib/event_engine/windows/windows_listener.cc", + "src/src/core/lib/event_engine/windows/windows_listener.h", + "src/src/core/lib/event_engine/work_queue/basic_work_queue.cc", + "src/src/core/lib/event_engine/work_queue/basic_work_queue.h", + "src/src/core/lib/event_engine/work_queue/work_queue.h", "src/src/core/lib/experiments/config.cc", "src/src/core/lib/experiments/config.h", "src/src/core/lib/experiments/experiments.cc", @@ -4394,7 +4921,10 @@ source_set("grpc") { "src/src/core/lib/gprpp/bitset.h", "src/src/core/lib/gprpp/chunked_vector.h", "src/src/core/lib/gprpp/cpp_impl_of.h", + "src/src/core/lib/gprpp/directory_reader.h", + "src/src/core/lib/gprpp/down_cast.h", "src/src/core/lib/gprpp/dual_ref_counted.h", + "src/src/core/lib/gprpp/if_list.h", "src/src/core/lib/gprpp/load_file.cc", "src/src/core/lib/gprpp/load_file.h", "src/src/core/lib/gprpp/manual_constructor.h", @@ -4403,9 +4933,13 @@ source_set("grpc") { "src/src/core/lib/gprpp/orphanable.h", "src/src/core/lib/gprpp/overload.h", "src/src/core/lib/gprpp/packed_table.h", + "src/src/core/lib/gprpp/per_cpu.cc", "src/src/core/lib/gprpp/per_cpu.h", + "src/src/core/lib/gprpp/posix/directory_reader.cc", "src/src/core/lib/gprpp/ref_counted.h", "src/src/core/lib/gprpp/ref_counted_ptr.h", + "src/src/core/lib/gprpp/ref_counted_string.cc", + "src/src/core/lib/gprpp/ref_counted_string.h", "src/src/core/lib/gprpp/single_set_ptr.h", "src/src/core/lib/gprpp/sorted_pack.h", "src/src/core/lib/gprpp/status_helper.cc", @@ -4415,14 +4949,16 @@ source_set("grpc") { "src/src/core/lib/gprpp/time.h", "src/src/core/lib/gprpp/time_averaged_stats.cc", "src/src/core/lib/gprpp/time_averaged_stats.h", + "src/src/core/lib/gprpp/type_list.h", "src/src/core/lib/gprpp/unique_type_name.h", + "src/src/core/lib/gprpp/uuid_v4.cc", + "src/src/core/lib/gprpp/uuid_v4.h", "src/src/core/lib/gprpp/validation_errors.cc", "src/src/core/lib/gprpp/validation_errors.h", + "src/src/core/lib/gprpp/windows/directory_reader.cc", "src/src/core/lib/gprpp/work_serializer.cc", "src/src/core/lib/gprpp/work_serializer.h", - "src/src/core/lib/handshaker/proxy_mapper.h", - "src/src/core/lib/handshaker/proxy_mapper_registry.cc", - "src/src/core/lib/handshaker/proxy_mapper_registry.h", + "src/src/core/lib/gprpp/xxhash_inline.h", "src/src/core/lib/http/format_request.cc", "src/src/core/lib/http/format_request.h", "src/src/core/lib/http/httpcli.cc", @@ -4495,8 +5031,6 @@ source_set("grpc") { "src/src/core/lib/iomgr/iomgr_posix.cc", "src/src/core/lib/iomgr/iomgr_posix_cfstream.cc", "src/src/core/lib/iomgr/iomgr_windows.cc", - "src/src/core/lib/iomgr/load_file.cc", - "src/src/core/lib/iomgr/load_file.h", "src/src/core/lib/iomgr/lockfree_event.cc", "src/src/core/lib/iomgr/lockfree_event.h", "src/src/core/lib/iomgr/nameser.h", @@ -4567,6 +5101,8 @@ source_set("grpc") { "src/src/core/lib/iomgr/unix_sockets_posix.cc", "src/src/core/lib/iomgr/unix_sockets_posix.h", "src/src/core/lib/iomgr/unix_sockets_posix_noop.cc", + "src/src/core/lib/iomgr/vsock.cc", + "src/src/core/lib/iomgr/vsock.h", "src/src/core/lib/iomgr/wakeup_fd_eventfd.cc", "src/src/core/lib/iomgr/wakeup_fd_nospecial.cc", "src/src/core/lib/iomgr/wakeup_fd_pipe.cc", @@ -4579,56 +5115,53 @@ source_set("grpc") { "src/src/core/lib/json/json_object_loader.cc", "src/src/core/lib/json/json_object_loader.h", "src/src/core/lib/json/json_reader.cc", + "src/src/core/lib/json/json_reader.h", "src/src/core/lib/json/json_util.cc", "src/src/core/lib/json/json_util.h", "src/src/core/lib/json/json_writer.cc", - "src/src/core/lib/load_balancing/lb_policy.cc", - "src/src/core/lib/load_balancing/lb_policy.h", - "src/src/core/lib/load_balancing/lb_policy_factory.h", - "src/src/core/lib/load_balancing/lb_policy_registry.cc", - "src/src/core/lib/load_balancing/lb_policy_registry.h", - "src/src/core/lib/load_balancing/subchannel_interface.h", + "src/src/core/lib/json/json_writer.h", "src/src/core/lib/matchers/matchers.cc", "src/src/core/lib/matchers/matchers.h", "src/src/core/lib/promise/activity.cc", "src/src/core/lib/promise/activity.h", + "src/src/core/lib/promise/all_ok.h", "src/src/core/lib/promise/arena_promise.h", + "src/src/core/lib/promise/cancel_callback.h", "src/src/core/lib/promise/context.h", - "src/src/core/lib/promise/detail/basic_join.h", "src/src/core/lib/promise/detail/basic_seq.h", + "src/src/core/lib/promise/detail/join_state.h", "src/src/core/lib/promise/detail/promise_factory.h", "src/src/core/lib/promise/detail/promise_like.h", + "src/src/core/lib/promise/detail/seq_state.h", "src/src/core/lib/promise/detail/status.h", - "src/src/core/lib/promise/detail/switch.h", "src/src/core/lib/promise/exec_ctx_wakeup_scheduler.h", + "src/src/core/lib/promise/for_each.h", "src/src/core/lib/promise/if.h", "src/src/core/lib/promise/interceptor_list.h", - "src/src/core/lib/promise/intra_activity_waiter.h", "src/src/core/lib/promise/latch.h", "src/src/core/lib/promise/loop.h", "src/src/core/lib/promise/map.h", + "src/src/core/lib/promise/party.cc", + "src/src/core/lib/promise/party.h", "src/src/core/lib/promise/pipe.h", "src/src/core/lib/promise/poll.h", + "src/src/core/lib/promise/prioritized_race.h", "src/src/core/lib/promise/promise.h", "src/src/core/lib/promise/race.h", "src/src/core/lib/promise/seq.h", "src/src/core/lib/promise/sleep.cc", "src/src/core/lib/promise/sleep.h", + "src/src/core/lib/promise/status_flag.h", "src/src/core/lib/promise/trace.cc", "src/src/core/lib/promise/trace.h", "src/src/core/lib/promise/try_join.h", "src/src/core/lib/promise/try_seq.h", - "src/src/core/lib/resolver/resolver.cc", - "src/src/core/lib/resolver/resolver.h", - "src/src/core/lib/resolver/resolver_factory.h", - "src/src/core/lib/resolver/resolver_registry.cc", - "src/src/core/lib/resolver/resolver_registry.h", - "src/src/core/lib/resolver/server_address.cc", - "src/src/core/lib/resolver/server_address.h", "src/src/core/lib/resource_quota/api.cc", "src/src/core/lib/resource_quota/api.h", "src/src/core/lib/resource_quota/arena.cc", "src/src/core/lib/resource_quota/arena.h", + "src/src/core/lib/resource_quota/connection_quota.cc", + "src/src/core/lib/resource_quota/connection_quota.h", "src/src/core/lib/resource_quota/memory_quota.cc", "src/src/core/lib/resource_quota/memory_quota.h", "src/src/core/lib/resource_quota/periodic_update.cc", @@ -4639,6 +5172,8 @@ source_set("grpc") { "src/src/core/lib/resource_quota/thread_quota.h", "src/src/core/lib/resource_quota/trace.cc", "src/src/core/lib/resource_quota/trace.h", + "src/src/core/lib/security/authorization/audit_logging.cc", + "src/src/core/lib/security/authorization/audit_logging.h", "src/src/core/lib/security/authorization/authorization_engine.h", "src/src/core/lib/security/authorization/authorization_policy_provider.h", "src/src/core/lib/security/authorization/authorization_policy_provider_vtable.cc", @@ -4652,6 +5187,8 @@ source_set("grpc") { "src/src/core/lib/security/authorization/matchers.h", "src/src/core/lib/security/authorization/rbac_policy.cc", "src/src/core/lib/security/authorization/rbac_policy.h", + "src/src/core/lib/security/authorization/stdout_logger.cc", + "src/src/core/lib/security/authorization/stdout_logger.h", "src/src/core/lib/security/certificate_provider/certificate_provider_factory.h", "src/src/core/lib/security/certificate_provider/certificate_provider_registry.cc", "src/src/core/lib/security/certificate_provider/certificate_provider_registry.h", @@ -4711,12 +5248,15 @@ source_set("grpc") { "src/src/core/lib/security/credentials/ssl/ssl_credentials.h", "src/src/core/lib/security/credentials/tls/grpc_tls_certificate_distributor.cc", "src/src/core/lib/security/credentials/tls/grpc_tls_certificate_distributor.h", + "src/src/core/lib/security/credentials/tls/grpc_tls_certificate_match.cc", "src/src/core/lib/security/credentials/tls/grpc_tls_certificate_provider.cc", "src/src/core/lib/security/credentials/tls/grpc_tls_certificate_provider.h", "src/src/core/lib/security/credentials/tls/grpc_tls_certificate_verifier.cc", "src/src/core/lib/security/credentials/tls/grpc_tls_certificate_verifier.h", "src/src/core/lib/security/credentials/tls/grpc_tls_credentials_options.cc", "src/src/core/lib/security/credentials/tls/grpc_tls_credentials_options.h", + "src/src/core/lib/security/credentials/tls/grpc_tls_crl_provider.cc", + "src/src/core/lib/security/credentials/tls/grpc_tls_crl_provider.h", "src/src/core/lib/security/credentials/tls/tls_credentials.cc", "src/src/core/lib/security/credentials/tls/tls_credentials.h", "src/src/core/lib/security/credentials/tls/tls_utils.cc", @@ -4733,6 +5273,7 @@ source_set("grpc") { "src/src/core/lib/security/security_connector/load_system_roots_fallback.cc", "src/src/core/lib/security/security_connector/load_system_roots_supported.cc", "src/src/core/lib/security/security_connector/load_system_roots_supported.h", + "src/src/core/lib/security/security_connector/load_system_roots_windows.cc", "src/src/core/lib/security/security_connector/local/local_security_connector.cc", "src/src/core/lib/security/security_connector/local/local_security_connector.h", "src/src/core/lib/security/security_connector/security_connector.cc", @@ -4741,29 +5282,13 @@ source_set("grpc") { "src/src/core/lib/security/security_connector/ssl/ssl_security_connector.h", "src/src/core/lib/security/security_connector/ssl_utils.cc", "src/src/core/lib/security/security_connector/ssl_utils.h", - "src/src/core/lib/security/security_connector/ssl_utils_config.cc", - "src/src/core/lib/security/security_connector/ssl_utils_config.h", "src/src/core/lib/security/security_connector/tls/tls_security_connector.cc", "src/src/core/lib/security/security_connector/tls/tls_security_connector.h", "src/src/core/lib/security/transport/auth_filters.h", "src/src/core/lib/security/transport/client_auth_filter.cc", - "src/src/core/lib/security/transport/secure_endpoint.cc", - "src/src/core/lib/security/transport/secure_endpoint.h", - "src/src/core/lib/security/transport/security_handshaker.cc", - "src/src/core/lib/security/transport/security_handshaker.h", "src/src/core/lib/security/transport/server_auth_filter.cc", - "src/src/core/lib/security/transport/tsi_error.cc", - "src/src/core/lib/security/transport/tsi_error.h", "src/src/core/lib/security/util/json_util.cc", "src/src/core/lib/security/util/json_util.h", - "src/src/core/lib/service_config/service_config.h", - "src/src/core/lib/service_config/service_config_call_data.h", - "src/src/core/lib/service_config/service_config_impl.cc", - "src/src/core/lib/service_config/service_config_impl.h", - "src/src/core/lib/service_config/service_config_parser.cc", - "src/src/core/lib/service_config/service_config_parser.h", - "src/src/core/lib/slice/b64.cc", - "src/src/core/lib/slice/b64.h", "src/src/core/lib/slice/percent_encoding.cc", "src/src/core/lib/slice/percent_encoding.h", "src/src/core/lib/slice/slice.cc", @@ -4777,8 +5302,6 @@ source_set("grpc") { "src/src/core/lib/slice/slice_string_helpers.h", "src/src/core/lib/surface/api_trace.cc", "src/src/core/lib/surface/api_trace.h", - "src/src/core/lib/surface/builtins.cc", - "src/src/core/lib/surface/builtins.h", "src/src/core/lib/surface/byte_buffer.cc", "src/src/core/lib/surface/byte_buffer_reader.cc", "src/src/core/lib/surface/call.cc", @@ -4786,13 +5309,13 @@ source_set("grpc") { "src/src/core/lib/surface/call_details.cc", "src/src/core/lib/surface/call_log_batch.cc", "src/src/core/lib/surface/call_test_only.h", - "src/src/core/lib/surface/call_trace.cc", "src/src/core/lib/surface/call_trace.h", "src/src/core/lib/surface/channel.cc", "src/src/core/lib/surface/channel.h", + "src/src/core/lib/surface/channel_create.cc", + "src/src/core/lib/surface/channel_create.h", "src/src/core/lib/surface/channel_init.cc", "src/src/core/lib/surface/channel_init.h", - "src/src/core/lib/surface/channel_ping.cc", "src/src/core/lib/surface/channel_stack_type.cc", "src/src/core/lib/surface/channel_stack_type.h", "src/src/core/lib/surface/completion_queue.cc", @@ -4807,47 +5330,166 @@ source_set("grpc") { "src/src/core/lib/surface/init_internally.h", "src/src/core/lib/surface/lame_client.cc", "src/src/core/lib/surface/lame_client.h", + "src/src/core/lib/surface/legacy_channel.cc", + "src/src/core/lib/surface/legacy_channel.h", "src/src/core/lib/surface/metadata_array.cc", - "src/src/core/lib/surface/server.cc", - "src/src/core/lib/surface/server.h", "src/src/core/lib/surface/validate_metadata.cc", "src/src/core/lib/surface/validate_metadata.h", "src/src/core/lib/surface/version.cc", + "src/src/core/lib/surface/wait_for_cq_end_op.cc", + "src/src/core/lib/surface/wait_for_cq_end_op.h", + "src/src/core/lib/transport/batch_builder.cc", + "src/src/core/lib/transport/batch_builder.h", "src/src/core/lib/transport/bdp_estimator.cc", "src/src/core/lib/transport/bdp_estimator.h", + "src/src/core/lib/transport/call_arena_allocator.cc", + "src/src/core/lib/transport/call_arena_allocator.h", + "src/src/core/lib/transport/call_filters.cc", + "src/src/core/lib/transport/call_filters.h", + "src/src/core/lib/transport/call_final_info.cc", + "src/src/core/lib/transport/call_final_info.h", + "src/src/core/lib/transport/call_spine.cc", + "src/src/core/lib/transport/call_spine.h", "src/src/core/lib/transport/connectivity_state.cc", "src/src/core/lib/transport/connectivity_state.h", + "src/src/core/lib/transport/custom_metadata.h", "src/src/core/lib/transport/error_utils.cc", "src/src/core/lib/transport/error_utils.h", - "src/src/core/lib/transport/handshaker.cc", - "src/src/core/lib/transport/handshaker.h", - "src/src/core/lib/transport/handshaker_factory.h", - "src/src/core/lib/transport/handshaker_registry.cc", - "src/src/core/lib/transport/handshaker_registry.h", "src/src/core/lib/transport/http2_errors.h", - "src/src/core/lib/transport/http_connect_handshaker.cc", - "src/src/core/lib/transport/http_connect_handshaker.h", + "src/src/core/lib/transport/message.cc", + "src/src/core/lib/transport/message.h", + "src/src/core/lib/transport/metadata.cc", + "src/src/core/lib/transport/metadata.h", "src/src/core/lib/transport/metadata_batch.cc", "src/src/core/lib/transport/metadata_batch.h", + "src/src/core/lib/transport/metadata_compression_traits.h", + "src/src/core/lib/transport/metadata_info.cc", + "src/src/core/lib/transport/metadata_info.h", "src/src/core/lib/transport/parsed_metadata.cc", "src/src/core/lib/transport/parsed_metadata.h", - "src/src/core/lib/transport/pid_controller.cc", - "src/src/core/lib/transport/pid_controller.h", + "src/src/core/lib/transport/simple_slice_based_metadata.h", "src/src/core/lib/transport/status_conversion.cc", "src/src/core/lib/transport/status_conversion.h", - "src/src/core/lib/transport/tcp_connect_handshaker.cc", - "src/src/core/lib/transport/tcp_connect_handshaker.h", "src/src/core/lib/transport/timeout_encoding.cc", "src/src/core/lib/transport/timeout_encoding.h", "src/src/core/lib/transport/transport.cc", "src/src/core/lib/transport/transport.h", "src/src/core/lib/transport/transport_fwd.h", - "src/src/core/lib/transport/transport_impl.h", "src/src/core/lib/transport/transport_op_string.cc", "src/src/core/lib/uri/uri_parser.cc", "src/src/core/lib/uri/uri_parser.h", + "src/src/core/load_balancing/address_filtering.cc", + "src/src/core/load_balancing/address_filtering.h", + "src/src/core/load_balancing/backend_metric_data.h", + "src/src/core/load_balancing/backend_metric_parser.cc", + "src/src/core/load_balancing/backend_metric_parser.h", + "src/src/core/load_balancing/child_policy_handler.cc", + "src/src/core/load_balancing/child_policy_handler.h", + "src/src/core/load_balancing/delegating_helper.h", + "src/src/core/load_balancing/endpoint_list.cc", + "src/src/core/load_balancing/endpoint_list.h", + "src/src/core/load_balancing/grpclb/client_load_reporting_filter.cc", + "src/src/core/load_balancing/grpclb/client_load_reporting_filter.h", + "src/src/core/load_balancing/grpclb/grpclb.cc", + "src/src/core/load_balancing/grpclb/grpclb.h", + "src/src/core/load_balancing/grpclb/grpclb_balancer_addresses.cc", + "src/src/core/load_balancing/grpclb/grpclb_balancer_addresses.h", + "src/src/core/load_balancing/grpclb/grpclb_client_stats.cc", + "src/src/core/load_balancing/grpclb/grpclb_client_stats.h", + "src/src/core/load_balancing/grpclb/load_balancer_api.cc", + "src/src/core/load_balancing/grpclb/load_balancer_api.h", + "src/src/core/load_balancing/health_check_client.cc", + "src/src/core/load_balancing/health_check_client.h", + "src/src/core/load_balancing/health_check_client_internal.h", + "src/src/core/load_balancing/lb_policy.cc", + "src/src/core/load_balancing/lb_policy.h", + "src/src/core/load_balancing/lb_policy_factory.h", + "src/src/core/load_balancing/lb_policy_registry.cc", + "src/src/core/load_balancing/lb_policy_registry.h", + "src/src/core/load_balancing/oob_backend_metric.cc", + "src/src/core/load_balancing/oob_backend_metric.h", + "src/src/core/load_balancing/oob_backend_metric_internal.h", + "src/src/core/load_balancing/outlier_detection/outlier_detection.cc", + "src/src/core/load_balancing/outlier_detection/outlier_detection.h", + "src/src/core/load_balancing/pick_first/pick_first.cc", + "src/src/core/load_balancing/pick_first/pick_first.h", + "src/src/core/load_balancing/priority/priority.cc", + "src/src/core/load_balancing/ring_hash/ring_hash.cc", + "src/src/core/load_balancing/ring_hash/ring_hash.h", + "src/src/core/load_balancing/rls/rls.cc", + "src/src/core/load_balancing/rls/rls.h", + "src/src/core/load_balancing/round_robin/round_robin.cc", + "src/src/core/load_balancing/subchannel_interface.h", + "src/src/core/load_balancing/weighted_round_robin/static_stride_scheduler.cc", + "src/src/core/load_balancing/weighted_round_robin/static_stride_scheduler.h", + "src/src/core/load_balancing/weighted_round_robin/weighted_round_robin.cc", + "src/src/core/load_balancing/weighted_target/weighted_target.cc", + "src/src/core/load_balancing/weighted_target/weighted_target.h", + "src/src/core/load_balancing/xds/cds.cc", + "src/src/core/load_balancing/xds/xds_channel_args.h", + "src/src/core/load_balancing/xds/xds_cluster_impl.cc", + "src/src/core/load_balancing/xds/xds_cluster_manager.cc", + "src/src/core/load_balancing/xds/xds_override_host.cc", + "src/src/core/load_balancing/xds/xds_override_host.h", + "src/src/core/load_balancing/xds/xds_wrr_locality.cc", "src/src/core/plugin_registry/grpc_plugin_registry.cc", "src/src/core/plugin_registry/grpc_plugin_registry_extra.cc", + "src/src/core/resolver/binder/binder_resolver.cc", + "src/src/core/resolver/dns/c_ares/dns_resolver_ares.cc", + "src/src/core/resolver/dns/c_ares/dns_resolver_ares.h", + "src/src/core/resolver/dns/c_ares/grpc_ares_ev_driver.h", + "src/src/core/resolver/dns/c_ares/grpc_ares_ev_driver_posix.cc", + "src/src/core/resolver/dns/c_ares/grpc_ares_ev_driver_windows.cc", + "src/src/core/resolver/dns/c_ares/grpc_ares_wrapper.cc", + "src/src/core/resolver/dns/c_ares/grpc_ares_wrapper.h", + "src/src/core/resolver/dns/c_ares/grpc_ares_wrapper_posix.cc", + "src/src/core/resolver/dns/c_ares/grpc_ares_wrapper_windows.cc", + "src/src/core/resolver/dns/dns_resolver_plugin.cc", + "src/src/core/resolver/dns/dns_resolver_plugin.h", + "src/src/core/resolver/dns/event_engine/event_engine_client_channel_resolver.cc", + "src/src/core/resolver/dns/event_engine/event_engine_client_channel_resolver.h", + "src/src/core/resolver/dns/event_engine/service_config_helper.cc", + "src/src/core/resolver/dns/event_engine/service_config_helper.h", + "src/src/core/resolver/dns/native/dns_resolver.cc", + "src/src/core/resolver/dns/native/dns_resolver.h", + "src/src/core/resolver/endpoint_addresses.cc", + "src/src/core/resolver/endpoint_addresses.h", + "src/src/core/resolver/fake/fake_resolver.cc", + "src/src/core/resolver/fake/fake_resolver.h", + "src/src/core/resolver/google_c2p/google_c2p_resolver.cc", + "src/src/core/resolver/polling_resolver.cc", + "src/src/core/resolver/polling_resolver.h", + "src/src/core/resolver/resolver.cc", + "src/src/core/resolver/resolver.h", + "src/src/core/resolver/resolver_factory.h", + "src/src/core/resolver/resolver_registry.cc", + "src/src/core/resolver/resolver_registry.h", + "src/src/core/resolver/server_address.h", + "src/src/core/resolver/sockaddr/sockaddr_resolver.cc", + "src/src/core/resolver/xds/xds_dependency_manager.cc", + "src/src/core/resolver/xds/xds_dependency_manager.h", + "src/src/core/resolver/xds/xds_resolver.cc", + "src/src/core/resolver/xds/xds_resolver_attributes.h", + "src/src/core/resolver/xds/xds_resolver_trace.cc", + "src/src/core/resolver/xds/xds_resolver_trace.h", + "src/src/core/server/server.cc", + "src/src/core/server/server.h", + "src/src/core/server/server_call_tracer_filter.cc", + "src/src/core/server/server_call_tracer_filter.h", + "src/src/core/server/server_config_selector.h", + "src/src/core/server/server_config_selector_filter.cc", + "src/src/core/server/server_config_selector_filter.h", + "src/src/core/server/server_interface.h", + "src/src/core/server/xds_channel_stack_modifier.cc", + "src/src/core/server/xds_channel_stack_modifier.h", + "src/src/core/server/xds_server_config_fetcher.cc", + "src/src/core/service_config/service_config.h", + "src/src/core/service_config/service_config_call_data.h", + "src/src/core/service_config/service_config_channel_arg_filter.cc", + "src/src/core/service_config/service_config_impl.cc", + "src/src/core/service_config/service_config_impl.h", + "src/src/core/service_config/service_config_parser.cc", + "src/src/core/service_config/service_config_parser.h", "src/src/core/tsi/alts/crypt/aes_gcm.cc", "src/src/core/tsi/alts/crypt/gsec.cc", "src/src/core/tsi/alts/crypt/gsec.h", @@ -4906,18 +5548,77 @@ source_set("grpc") { "src/src/core/tsi/transport_security_grpc.cc", "src/src/core/tsi/transport_security_grpc.h", "src/src/core/tsi/transport_security_interface.h", + "src/src/core/xds/grpc/certificate_provider_store.cc", + "src/src/core/xds/grpc/certificate_provider_store.h", + "src/src/core/xds/grpc/file_watcher_certificate_provider_factory.cc", + "src/src/core/xds/grpc/file_watcher_certificate_provider_factory.h", + "src/src/core/xds/grpc/upb_utils.h", + "src/src/core/xds/grpc/xds_audit_logger_registry.cc", + "src/src/core/xds/grpc/xds_audit_logger_registry.h", + "src/src/core/xds/grpc/xds_bootstrap_grpc.cc", + "src/src/core/xds/grpc/xds_bootstrap_grpc.h", + "src/src/core/xds/grpc/xds_certificate_provider.cc", + "src/src/core/xds/grpc/xds_certificate_provider.h", + "src/src/core/xds/grpc/xds_client_grpc.cc", + "src/src/core/xds/grpc/xds_client_grpc.h", + "src/src/core/xds/grpc/xds_cluster.cc", + "src/src/core/xds/grpc/xds_cluster.h", + "src/src/core/xds/grpc/xds_cluster_specifier_plugin.cc", + "src/src/core/xds/grpc/xds_cluster_specifier_plugin.h", + "src/src/core/xds/grpc/xds_common_types.cc", + "src/src/core/xds/grpc/xds_common_types.h", + "src/src/core/xds/grpc/xds_endpoint.cc", + "src/src/core/xds/grpc/xds_endpoint.h", + "src/src/core/xds/grpc/xds_health_status.cc", + "src/src/core/xds/grpc/xds_health_status.h", + "src/src/core/xds/grpc/xds_http_fault_filter.cc", + "src/src/core/xds/grpc/xds_http_fault_filter.h", + "src/src/core/xds/grpc/xds_http_filters.cc", + "src/src/core/xds/grpc/xds_http_filters.h", + "src/src/core/xds/grpc/xds_http_rbac_filter.cc", + "src/src/core/xds/grpc/xds_http_rbac_filter.h", + "src/src/core/xds/grpc/xds_http_stateful_session_filter.cc", + "src/src/core/xds/grpc/xds_http_stateful_session_filter.h", + "src/src/core/xds/grpc/xds_lb_policy_registry.cc", + "src/src/core/xds/grpc/xds_lb_policy_registry.h", + "src/src/core/xds/grpc/xds_listener.cc", + "src/src/core/xds/grpc/xds_listener.h", + "src/src/core/xds/grpc/xds_route_config.cc", + "src/src/core/xds/grpc/xds_route_config.h", + "src/src/core/xds/grpc/xds_routing.cc", + "src/src/core/xds/grpc/xds_routing.h", + "src/src/core/xds/grpc/xds_transport_grpc.cc", + "src/src/core/xds/grpc/xds_transport_grpc.h", + "src/src/core/xds/xds_client/xds_api.cc", + "src/src/core/xds/xds_client/xds_api.h", + "src/src/core/xds/xds_client/xds_bootstrap.cc", + "src/src/core/xds/xds_client/xds_bootstrap.h", + "src/src/core/xds/xds_client/xds_channel_args.h", + "src/src/core/xds/xds_client/xds_client.cc", + "src/src/core/xds/xds_client/xds_client.h", + "src/src/core/xds/xds_client/xds_client_stats.cc", + "src/src/core/xds/xds_client/xds_client_stats.h", + "src/src/core/xds/xds_client/xds_metrics.h", + "src/src/core/xds/xds_client/xds_resource_type.h", + "src/src/core/xds/xds_client/xds_resource_type_impl.h", + "src/src/core/xds/xds_client/xds_transport.h", + "src/third_party/upb/upb/generated_code_support.h", "src/third_party/xxhash/xxhash.h", ] public_deps = [ + ":absl_algorithm_container", + ":absl_base_config", + ":absl_base_no_destructor", ":absl_cleanup_cleanup", ":absl_container_flat_hash_map", ":absl_container_flat_hash_set", ":absl_container_inlined_vector", - ":absl_functional_any_invocable", ":absl_functional_bind_front", ":absl_functional_function_ref", ":absl_hash_hash", ":absl_meta_type_traits", + ":absl_random_bit_gen_ref", + ":absl_random_distributions", ":absl_status_statusor", ":absl_types_span", ":absl_utility_utility", @@ -4925,112 +5626,65 @@ source_set("grpc") { ":boringssl", ":gpr", ":re2", - ":upb", - "../../gn:zlib", + ":upb_json_lib", + ":upb_textformat_lib", + "..:zlib", ] public_configs = [ "..:grpc_internal_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("grpc_unsecure") { sources = [ + "src/src/core/channelz/channel_trace.cc", + "src/src/core/channelz/channel_trace.h", + "src/src/core/channelz/channelz.cc", + "src/src/core/channelz/channelz.h", + "src/src/core/channelz/channelz_registry.cc", + "src/src/core/channelz/channelz_registry.h", + "src/src/core/client_channel/backup_poller.cc", + "src/src/core/client_channel/backup_poller.h", + "src/src/core/client_channel/client_channel_factory.cc", + "src/src/core/client_channel/client_channel_factory.h", + "src/src/core/client_channel/client_channel_filter.cc", + "src/src/core/client_channel/client_channel_filter.h", + "src/src/core/client_channel/client_channel_internal.h", + "src/src/core/client_channel/client_channel_plugin.cc", + "src/src/core/client_channel/client_channel_service_config.cc", + "src/src/core/client_channel/client_channel_service_config.h", + "src/src/core/client_channel/config_selector.cc", + "src/src/core/client_channel/config_selector.h", + "src/src/core/client_channel/connector.h", + "src/src/core/client_channel/dynamic_filters.cc", + "src/src/core/client_channel/dynamic_filters.h", + "src/src/core/client_channel/global_subchannel_pool.cc", + "src/src/core/client_channel/global_subchannel_pool.h", + "src/src/core/client_channel/local_subchannel_pool.cc", + "src/src/core/client_channel/local_subchannel_pool.h", + "src/src/core/client_channel/retry_filter.cc", + "src/src/core/client_channel/retry_filter.h", + "src/src/core/client_channel/retry_filter_legacy_call_data.cc", + "src/src/core/client_channel/retry_filter_legacy_call_data.h", + "src/src/core/client_channel/retry_service_config.cc", + "src/src/core/client_channel/retry_service_config.h", + "src/src/core/client_channel/retry_throttle.cc", + "src/src/core/client_channel/retry_throttle.h", + "src/src/core/client_channel/subchannel.cc", + "src/src/core/client_channel/subchannel.h", + "src/src/core/client_channel/subchannel_interface_internal.h", + "src/src/core/client_channel/subchannel_pool_interface.cc", + "src/src/core/client_channel/subchannel_pool_interface.h", + "src/src/core/client_channel/subchannel_stream_client.cc", + "src/src/core/client_channel/subchannel_stream_client.h", + "src/src/core/ext/filters/backend_metrics/backend_metric_filter.cc", + "src/src/core/ext/filters/backend_metrics/backend_metric_filter.h", + "src/src/core/ext/filters/backend_metrics/backend_metric_provider.h", "src/src/core/ext/filters/census/grpc_context.cc", - "src/src/core/ext/filters/channel_idle/channel_idle_filter.cc", - "src/src/core/ext/filters/channel_idle/channel_idle_filter.h", "src/src/core/ext/filters/channel_idle/idle_filter_state.cc", "src/src/core/ext/filters/channel_idle/idle_filter_state.h", - "src/src/core/ext/filters/client_channel/backend_metric.cc", - "src/src/core/ext/filters/client_channel/backend_metric.h", - "src/src/core/ext/filters/client_channel/backup_poller.cc", - "src/src/core/ext/filters/client_channel/backup_poller.h", - "src/src/core/ext/filters/client_channel/channel_connectivity.cc", - "src/src/core/ext/filters/client_channel/client_channel.cc", - "src/src/core/ext/filters/client_channel/client_channel.h", - "src/src/core/ext/filters/client_channel/client_channel_channelz.cc", - "src/src/core/ext/filters/client_channel/client_channel_channelz.h", - "src/src/core/ext/filters/client_channel/client_channel_factory.cc", - "src/src/core/ext/filters/client_channel/client_channel_factory.h", - "src/src/core/ext/filters/client_channel/client_channel_plugin.cc", - "src/src/core/ext/filters/client_channel/client_channel_service_config.cc", - "src/src/core/ext/filters/client_channel/client_channel_service_config.h", - "src/src/core/ext/filters/client_channel/config_selector.cc", - "src/src/core/ext/filters/client_channel/config_selector.h", - "src/src/core/ext/filters/client_channel/connector.h", - "src/src/core/ext/filters/client_channel/dynamic_filters.cc", - "src/src/core/ext/filters/client_channel/dynamic_filters.h", - "src/src/core/ext/filters/client_channel/global_subchannel_pool.cc", - "src/src/core/ext/filters/client_channel/global_subchannel_pool.h", - "src/src/core/ext/filters/client_channel/health/health_check_client.cc", - "src/src/core/ext/filters/client_channel/health/health_check_client.h", - "src/src/core/ext/filters/client_channel/http_proxy.cc", - "src/src/core/ext/filters/client_channel/http_proxy.h", - "src/src/core/ext/filters/client_channel/lb_call_state_internal.h", - "src/src/core/ext/filters/client_channel/lb_policy/address_filtering.cc", - "src/src/core/ext/filters/client_channel/lb_policy/address_filtering.h", - "src/src/core/ext/filters/client_channel/lb_policy/backend_metric_data.h", - "src/src/core/ext/filters/client_channel/lb_policy/child_policy_handler.cc", - "src/src/core/ext/filters/client_channel/lb_policy/child_policy_handler.h", - "src/src/core/ext/filters/client_channel/lb_policy/grpclb/client_load_reporting_filter.cc", - "src/src/core/ext/filters/client_channel/lb_policy/grpclb/client_load_reporting_filter.h", - "src/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb.cc", - "src/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb.h", - "src/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_balancer_addresses.cc", - "src/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_balancer_addresses.h", - "src/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_client_stats.cc", - "src/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_client_stats.h", - "src/src/core/ext/filters/client_channel/lb_policy/grpclb/load_balancer_api.cc", - "src/src/core/ext/filters/client_channel/lb_policy/grpclb/load_balancer_api.h", - "src/src/core/ext/filters/client_channel/lb_policy/oob_backend_metric.cc", - "src/src/core/ext/filters/client_channel/lb_policy/oob_backend_metric.h", - "src/src/core/ext/filters/client_channel/lb_policy/oob_backend_metric_internal.h", - "src/src/core/ext/filters/client_channel/lb_policy/outlier_detection/outlier_detection.cc", - "src/src/core/ext/filters/client_channel/lb_policy/outlier_detection/outlier_detection.h", - "src/src/core/ext/filters/client_channel/lb_policy/pick_first/pick_first.cc", - "src/src/core/ext/filters/client_channel/lb_policy/priority/priority.cc", - "src/src/core/ext/filters/client_channel/lb_policy/ring_hash/ring_hash.cc", - "src/src/core/ext/filters/client_channel/lb_policy/ring_hash/ring_hash.h", - "src/src/core/ext/filters/client_channel/lb_policy/rls/rls.cc", - "src/src/core/ext/filters/client_channel/lb_policy/round_robin/round_robin.cc", - "src/src/core/ext/filters/client_channel/lb_policy/subchannel_list.h", - "src/src/core/ext/filters/client_channel/lb_policy/weighted_round_robin/static_stride_scheduler.cc", - "src/src/core/ext/filters/client_channel/lb_policy/weighted_round_robin/static_stride_scheduler.h", - "src/src/core/ext/filters/client_channel/lb_policy/weighted_round_robin/weighted_round_robin.cc", - "src/src/core/ext/filters/client_channel/lb_policy/weighted_target/weighted_target.cc", - "src/src/core/ext/filters/client_channel/local_subchannel_pool.cc", - "src/src/core/ext/filters/client_channel/local_subchannel_pool.h", - "src/src/core/ext/filters/client_channel/resolver/binder/binder_resolver.cc", - "src/src/core/ext/filters/client_channel/resolver/dns/c_ares/dns_resolver_ares.cc", - "src/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver.h", - "src/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_posix.cc", - "src/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_windows.cc", - "src/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.cc", - "src/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper.h", - "src/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_posix.cc", - "src/src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_wrapper_windows.cc", - "src/src/core/ext/filters/client_channel/resolver/dns/dns_resolver_selection.cc", - "src/src/core/ext/filters/client_channel/resolver/dns/dns_resolver_selection.h", - "src/src/core/ext/filters/client_channel/resolver/dns/native/dns_resolver.cc", - "src/src/core/ext/filters/client_channel/resolver/fake/fake_resolver.cc", - "src/src/core/ext/filters/client_channel/resolver/fake/fake_resolver.h", - "src/src/core/ext/filters/client_channel/resolver/polling_resolver.cc", - "src/src/core/ext/filters/client_channel/resolver/polling_resolver.h", - "src/src/core/ext/filters/client_channel/resolver/sockaddr/sockaddr_resolver.cc", - "src/src/core/ext/filters/client_channel/retry_filter.cc", - "src/src/core/ext/filters/client_channel/retry_filter.h", - "src/src/core/ext/filters/client_channel/retry_service_config.cc", - "src/src/core/ext/filters/client_channel/retry_service_config.h", - "src/src/core/ext/filters/client_channel/retry_throttle.cc", - "src/src/core/ext/filters/client_channel/retry_throttle.h", - "src/src/core/ext/filters/client_channel/service_config_channel_arg_filter.cc", - "src/src/core/ext/filters/client_channel/subchannel.cc", - "src/src/core/ext/filters/client_channel/subchannel.h", - "src/src/core/ext/filters/client_channel/subchannel_interface_internal.h", - "src/src/core/ext/filters/client_channel/subchannel_pool_interface.cc", - "src/src/core/ext/filters/client_channel/subchannel_pool_interface.h", - "src/src/core/ext/filters/client_channel/subchannel_stream_client.cc", - "src/src/core/ext/filters/client_channel/subchannel_stream_client.h", - "src/src/core/ext/filters/deadline/deadline_filter.cc", - "src/src/core/ext/filters/deadline/deadline_filter.h", + "src/src/core/ext/filters/channel_idle/legacy_channel_idle_filter.cc", + "src/src/core/ext/filters/channel_idle/legacy_channel_idle_filter.h", "src/src/core/ext/filters/fault_injection/fault_injection_filter.cc", "src/src/core/ext/filters/fault_injection/fault_injection_filter.h", "src/src/core/ext/filters/fault_injection/fault_injection_service_config_parser.cc", @@ -5056,12 +5710,12 @@ source_set("grpc_unsecure") { "src/src/core/ext/transport/chttp2/transport/bin_encoder.h", "src/src/core/ext/transport/chttp2/transport/chttp2_transport.cc", "src/src/core/ext/transport/chttp2/transport/chttp2_transport.h", - "src/src/core/ext/transport/chttp2/transport/context_list.cc", - "src/src/core/ext/transport/chttp2/transport/context_list.h", + "src/src/core/ext/transport/chttp2/transport/context_list_entry.h", "src/src/core/ext/transport/chttp2/transport/decode_huff.cc", "src/src/core/ext/transport/chttp2/transport/decode_huff.h", "src/src/core/ext/transport/chttp2/transport/flow_control.cc", "src/src/core/ext/transport/chttp2/transport/flow_control.h", + "src/src/core/ext/transport/chttp2/transport/frame.cc", "src/src/core/ext/transport/chttp2/transport/frame.h", "src/src/core/ext/transport/chttp2/transport/frame_data.cc", "src/src/core/ext/transport/chttp2/transport/frame_data.h", @@ -5080,6 +5734,8 @@ source_set("grpc_unsecure") { "src/src/core/ext/transport/chttp2/transport/hpack_encoder.h", "src/src/core/ext/transport/chttp2/transport/hpack_encoder_table.cc", "src/src/core/ext/transport/chttp2/transport/hpack_encoder_table.h", + "src/src/core/ext/transport/chttp2/transport/hpack_parse_result.cc", + "src/src/core/ext/transport/chttp2/transport/hpack_parse_result.h", "src/src/core/ext/transport/chttp2/transport/hpack_parser.cc", "src/src/core/ext/transport/chttp2/transport/hpack_parser.h", "src/src/core/ext/transport/chttp2/transport/hpack_parser_table.cc", @@ -5091,54 +5747,106 @@ source_set("grpc_unsecure") { "src/src/core/ext/transport/chttp2/transport/huffsyms.cc", "src/src/core/ext/transport/chttp2/transport/huffsyms.h", "src/src/core/ext/transport/chttp2/transport/internal.h", + "src/src/core/ext/transport/chttp2/transport/legacy_frame.h", + "src/src/core/ext/transport/chttp2/transport/max_concurrent_streams_policy.cc", + "src/src/core/ext/transport/chttp2/transport/max_concurrent_streams_policy.h", "src/src/core/ext/transport/chttp2/transport/parsing.cc", + "src/src/core/ext/transport/chttp2/transport/ping_abuse_policy.cc", + "src/src/core/ext/transport/chttp2/transport/ping_abuse_policy.h", + "src/src/core/ext/transport/chttp2/transport/ping_callbacks.cc", + "src/src/core/ext/transport/chttp2/transport/ping_callbacks.h", + "src/src/core/ext/transport/chttp2/transport/ping_rate_policy.cc", + "src/src/core/ext/transport/chttp2/transport/ping_rate_policy.h", "src/src/core/ext/transport/chttp2/transport/stream_lists.cc", - "src/src/core/ext/transport/chttp2/transport/stream_map.cc", - "src/src/core/ext/transport/chttp2/transport/stream_map.h", "src/src/core/ext/transport/chttp2/transport/varint.cc", "src/src/core/ext/transport/chttp2/transport/varint.h", + "src/src/core/ext/transport/chttp2/transport/write_size_policy.cc", + "src/src/core/ext/transport/chttp2/transport/write_size_policy.h", "src/src/core/ext/transport/chttp2/transport/writing.cc", "src/src/core/ext/transport/inproc/inproc_plugin.cc", "src/src/core/ext/transport/inproc/inproc_transport.cc", "src/src/core/ext/transport/inproc/inproc_transport.h", - "src/src/core/ext/upb-generated/google/api/annotations.upb.c", - "src/src/core/ext/upb-generated/google/api/annotations.upb.h", - "src/src/core/ext/upb-generated/google/api/http.upb.c", - "src/src/core/ext/upb-generated/google/api/http.upb.h", - "src/src/core/ext/upb-generated/google/protobuf/any.upb.c", - "src/src/core/ext/upb-generated/google/protobuf/any.upb.h", - "src/src/core/ext/upb-generated/google/protobuf/descriptor.upb.c", - "src/src/core/ext/upb-generated/google/protobuf/descriptor.upb.h", - "src/src/core/ext/upb-generated/google/protobuf/duration.upb.c", - "src/src/core/ext/upb-generated/google/protobuf/duration.upb.h", - "src/src/core/ext/upb-generated/google/protobuf/empty.upb.c", - "src/src/core/ext/upb-generated/google/protobuf/empty.upb.h", - "src/src/core/ext/upb-generated/google/protobuf/struct.upb.c", - "src/src/core/ext/upb-generated/google/protobuf/struct.upb.h", - "src/src/core/ext/upb-generated/google/protobuf/timestamp.upb.c", - "src/src/core/ext/upb-generated/google/protobuf/timestamp.upb.h", - "src/src/core/ext/upb-generated/google/protobuf/wrappers.upb.c", - "src/src/core/ext/upb-generated/google/protobuf/wrappers.upb.h", - "src/src/core/ext/upb-generated/google/rpc/status.upb.c", - "src/src/core/ext/upb-generated/google/rpc/status.upb.h", - "src/src/core/ext/upb-generated/src/proto/grpc/gcp/altscontext.upb.c", - "src/src/core/ext/upb-generated/src/proto/grpc/gcp/altscontext.upb.h", - "src/src/core/ext/upb-generated/src/proto/grpc/gcp/handshaker.upb.c", - "src/src/core/ext/upb-generated/src/proto/grpc/gcp/handshaker.upb.h", - "src/src/core/ext/upb-generated/src/proto/grpc/gcp/transport_security_common.upb.c", - "src/src/core/ext/upb-generated/src/proto/grpc/gcp/transport_security_common.upb.h", - "src/src/core/ext/upb-generated/src/proto/grpc/health/v1/health.upb.c", - "src/src/core/ext/upb-generated/src/proto/grpc/health/v1/health.upb.h", - "src/src/core/ext/upb-generated/src/proto/grpc/lb/v1/load_balancer.upb.c", - "src/src/core/ext/upb-generated/src/proto/grpc/lb/v1/load_balancer.upb.h", - "src/src/core/ext/upb-generated/src/proto/grpc/lookup/v1/rls.upb.c", - "src/src/core/ext/upb-generated/src/proto/grpc/lookup/v1/rls.upb.h", - "src/src/core/ext/upb-generated/validate/validate.upb.c", - "src/src/core/ext/upb-generated/validate/validate.upb.h", - "src/src/core/ext/upb-generated/xds/data/orca/v3/orca_load_report.upb.c", - "src/src/core/ext/upb-generated/xds/data/orca/v3/orca_load_report.upb.h", - "src/src/core/ext/upb-generated/xds/service/orca/v3/orca.upb.c", - "src/src/core/ext/upb-generated/xds/service/orca/v3/orca.upb.h", + "src/src/core/ext/transport/inproc/legacy_inproc_transport.cc", + "src/src/core/ext/transport/inproc/legacy_inproc_transport.h", + "src/src/core/ext/upb-gen/google/api/annotations.upb.h", + "src/src/core/ext/upb-gen/google/api/annotations.upb_minitable.c", + "src/src/core/ext/upb-gen/google/api/annotations.upb_minitable.h", + "src/src/core/ext/upb-gen/google/api/http.upb.h", + "src/src/core/ext/upb-gen/google/api/http.upb_minitable.c", + "src/src/core/ext/upb-gen/google/api/http.upb_minitable.h", + "src/src/core/ext/upb-gen/google/protobuf/any.upb.h", + "src/src/core/ext/upb-gen/google/protobuf/any.upb_minitable.c", + "src/src/core/ext/upb-gen/google/protobuf/any.upb_minitable.h", + "src/src/core/ext/upb-gen/google/protobuf/descriptor.upb.h", + "src/src/core/ext/upb-gen/google/protobuf/descriptor.upb_minitable.c", + "src/src/core/ext/upb-gen/google/protobuf/descriptor.upb_minitable.h", + "src/src/core/ext/upb-gen/google/protobuf/duration.upb.h", + "src/src/core/ext/upb-gen/google/protobuf/duration.upb_minitable.c", + "src/src/core/ext/upb-gen/google/protobuf/duration.upb_minitable.h", + "src/src/core/ext/upb-gen/google/protobuf/empty.upb.h", + "src/src/core/ext/upb-gen/google/protobuf/empty.upb_minitable.c", + "src/src/core/ext/upb-gen/google/protobuf/empty.upb_minitable.h", + "src/src/core/ext/upb-gen/google/protobuf/struct.upb.h", + "src/src/core/ext/upb-gen/google/protobuf/struct.upb_minitable.c", + "src/src/core/ext/upb-gen/google/protobuf/struct.upb_minitable.h", + "src/src/core/ext/upb-gen/google/protobuf/timestamp.upb.h", + "src/src/core/ext/upb-gen/google/protobuf/timestamp.upb_minitable.c", + "src/src/core/ext/upb-gen/google/protobuf/timestamp.upb_minitable.h", + "src/src/core/ext/upb-gen/google/protobuf/wrappers.upb.h", + "src/src/core/ext/upb-gen/google/protobuf/wrappers.upb_minitable.c", + "src/src/core/ext/upb-gen/google/protobuf/wrappers.upb_minitable.h", + "src/src/core/ext/upb-gen/google/rpc/status.upb.h", + "src/src/core/ext/upb-gen/google/rpc/status.upb_minitable.c", + "src/src/core/ext/upb-gen/google/rpc/status.upb_minitable.h", + "src/src/core/ext/upb-gen/src/proto/grpc/gcp/altscontext.upb.h", + "src/src/core/ext/upb-gen/src/proto/grpc/gcp/altscontext.upb_minitable.c", + "src/src/core/ext/upb-gen/src/proto/grpc/gcp/altscontext.upb_minitable.h", + "src/src/core/ext/upb-gen/src/proto/grpc/gcp/handshaker.upb.h", + "src/src/core/ext/upb-gen/src/proto/grpc/gcp/handshaker.upb_minitable.c", + "src/src/core/ext/upb-gen/src/proto/grpc/gcp/handshaker.upb_minitable.h", + "src/src/core/ext/upb-gen/src/proto/grpc/gcp/transport_security_common.upb.h", + "src/src/core/ext/upb-gen/src/proto/grpc/gcp/transport_security_common.upb_minitable.c", + "src/src/core/ext/upb-gen/src/proto/grpc/gcp/transport_security_common.upb_minitable.h", + "src/src/core/ext/upb-gen/src/proto/grpc/health/v1/health.upb.h", + "src/src/core/ext/upb-gen/src/proto/grpc/health/v1/health.upb_minitable.c", + "src/src/core/ext/upb-gen/src/proto/grpc/health/v1/health.upb_minitable.h", + "src/src/core/ext/upb-gen/src/proto/grpc/lb/v1/load_balancer.upb.h", + "src/src/core/ext/upb-gen/src/proto/grpc/lb/v1/load_balancer.upb_minitable.c", + "src/src/core/ext/upb-gen/src/proto/grpc/lb/v1/load_balancer.upb_minitable.h", + "src/src/core/ext/upb-gen/src/proto/grpc/lookup/v1/rls.upb.h", + "src/src/core/ext/upb-gen/src/proto/grpc/lookup/v1/rls.upb_minitable.c", + "src/src/core/ext/upb-gen/src/proto/grpc/lookup/v1/rls.upb_minitable.h", + "src/src/core/ext/upb-gen/validate/validate.upb.h", + "src/src/core/ext/upb-gen/validate/validate.upb_minitable.c", + "src/src/core/ext/upb-gen/validate/validate.upb_minitable.h", + "src/src/core/ext/upb-gen/xds/data/orca/v3/orca_load_report.upb.h", + "src/src/core/ext/upb-gen/xds/data/orca/v3/orca_load_report.upb_minitable.c", + "src/src/core/ext/upb-gen/xds/data/orca/v3/orca_load_report.upb_minitable.h", + "src/src/core/ext/upb-gen/xds/service/orca/v3/orca.upb.h", + "src/src/core/ext/upb-gen/xds/service/orca/v3/orca.upb_minitable.c", + "src/src/core/ext/upb-gen/xds/service/orca/v3/orca.upb_minitable.h", + "src/src/core/handshaker/endpoint_info/endpoint_info_handshaker.cc", + "src/src/core/handshaker/endpoint_info/endpoint_info_handshaker.h", + "src/src/core/handshaker/handshaker.cc", + "src/src/core/handshaker/handshaker.h", + "src/src/core/handshaker/handshaker_factory.h", + "src/src/core/handshaker/handshaker_registry.cc", + "src/src/core/handshaker/handshaker_registry.h", + "src/src/core/handshaker/http_connect/http_connect_handshaker.cc", + "src/src/core/handshaker/http_connect/http_connect_handshaker.h", + "src/src/core/handshaker/http_connect/http_proxy_mapper.cc", + "src/src/core/handshaker/http_connect/http_proxy_mapper.h", + "src/src/core/handshaker/proxy_mapper.h", + "src/src/core/handshaker/proxy_mapper_registry.cc", + "src/src/core/handshaker/proxy_mapper_registry.h", + "src/src/core/handshaker/security/secure_endpoint.cc", + "src/src/core/handshaker/security/secure_endpoint.h", + "src/src/core/handshaker/security/security_handshaker.cc", + "src/src/core/handshaker/security/security_handshaker.h", + "src/src/core/handshaker/security/tsi_error.cc", + "src/src/core/handshaker/security/tsi_error.h", + "src/src/core/handshaker/tcp_connect/tcp_connect_handshaker.cc", + "src/src/core/handshaker/tcp_connect/tcp_connect_handshaker.h", "src/src/core/lib/address_utils/parse_address.cc", "src/src/core/lib/address_utils/parse_address.h", "src/src/core/lib/address_utils/sockaddr_utils.cc", @@ -5146,7 +5854,10 @@ source_set("grpc_unsecure") { "src/src/core/lib/avl/avl.h", "src/src/core/lib/backoff/backoff.cc", "src/src/core/lib/backoff/backoff.h", + "src/src/core/lib/backoff/random_early_detection.cc", + "src/src/core/lib/backoff/random_early_detection.h", "src/src/core/lib/channel/call_finalization.h", + "src/src/core/lib/channel/call_tracer.cc", "src/src/core/lib/channel/call_tracer.h", "src/src/core/lib/channel/channel_args.cc", "src/src/core/lib/channel/channel_args.h", @@ -5159,19 +5870,18 @@ source_set("grpc_unsecure") { "src/src/core/lib/channel/channel_stack_builder.h", "src/src/core/lib/channel/channel_stack_builder_impl.cc", "src/src/core/lib/channel/channel_stack_builder_impl.h", - "src/src/core/lib/channel/channel_trace.cc", - "src/src/core/lib/channel/channel_trace.h", - "src/src/core/lib/channel/channelz.cc", - "src/src/core/lib/channel/channelz.h", - "src/src/core/lib/channel/channelz_registry.cc", - "src/src/core/lib/channel/channelz_registry.h", + "src/src/core/lib/channel/channel_stack_trace.cc", + "src/src/core/lib/channel/channel_stack_trace.h", "src/src/core/lib/channel/connected_channel.cc", "src/src/core/lib/channel/connected_channel.h", "src/src/core/lib/channel/context.h", + "src/src/core/lib/channel/metrics.cc", + "src/src/core/lib/channel/metrics.h", "src/src/core/lib/channel/promise_based_filter.cc", "src/src/core/lib/channel/promise_based_filter.h", "src/src/core/lib/channel/status_util.cc", "src/src/core/lib/channel/status_util.h", + "src/src/core/lib/channel/tcp_tracer.h", "src/src/core/lib/compression/compression.cc", "src/src/core/lib/compression/compression_internal.cc", "src/src/core/lib/compression/compression_internal.h", @@ -5189,6 +5899,15 @@ source_set("grpc_unsecure") { "src/src/core/lib/debug/stats_data.h", "src/src/core/lib/debug/trace.cc", "src/src/core/lib/debug/trace.h", + "src/src/core/lib/event_engine/ares_resolver.cc", + "src/src/core/lib/event_engine/ares_resolver.h", + "src/src/core/lib/event_engine/cf_engine/cf_engine.cc", + "src/src/core/lib/event_engine/cf_engine/cf_engine.h", + "src/src/core/lib/event_engine/cf_engine/cfstream_endpoint.cc", + "src/src/core/lib/event_engine/cf_engine/cfstream_endpoint.h", + "src/src/core/lib/event_engine/cf_engine/cftype_unique_ref.h", + "src/src/core/lib/event_engine/cf_engine/dns_service_resolver.cc", + "src/src/core/lib/event_engine/cf_engine/dns_service_resolver.h", "src/src/core/lib/event_engine/channel_args_endpoint_config.cc", "src/src/core/lib/event_engine/channel_args_endpoint_config.h", "src/src/core/lib/event_engine/common_closures.h", @@ -5197,11 +5916,16 @@ source_set("grpc_unsecure") { "src/src/core/lib/event_engine/default_event_engine_factory.cc", "src/src/core/lib/event_engine/default_event_engine_factory.h", "src/src/core/lib/event_engine/event_engine.cc", - "src/src/core/lib/event_engine/executor/executor.h", + "src/src/core/lib/event_engine/event_engine_context.h", + "src/src/core/lib/event_engine/extensions/can_track_errors.h", + "src/src/core/lib/event_engine/extensions/chaotic_good_extension.h", + "src/src/core/lib/event_engine/extensions/supports_fd.h", "src/src/core/lib/event_engine/forkable.cc", "src/src/core/lib/event_engine/forkable.h", + "src/src/core/lib/event_engine/grpc_polled_fd.h", "src/src/core/lib/event_engine/handle_containers.h", - "src/src/core/lib/event_engine/memory_allocator.cc", + "src/src/core/lib/event_engine/memory_allocator_factory.h", + "src/src/core/lib/event_engine/nameser.h", "src/src/core/lib/event_engine/poller.h", "src/src/core/lib/event_engine/posix.h", "src/src/core/lib/event_engine/posix_engine/ev_epoll1_linux.cc", @@ -5211,10 +5935,13 @@ source_set("grpc_unsecure") { "src/src/core/lib/event_engine/posix_engine/event_poller.h", "src/src/core/lib/event_engine/posix_engine/event_poller_posix_default.cc", "src/src/core/lib/event_engine/posix_engine/event_poller_posix_default.h", + "src/src/core/lib/event_engine/posix_engine/grpc_polled_fd_posix.h", "src/src/core/lib/event_engine/posix_engine/internal_errqueue.cc", "src/src/core/lib/event_engine/posix_engine/internal_errqueue.h", "src/src/core/lib/event_engine/posix_engine/lockfree_event.cc", "src/src/core/lib/event_engine/posix_engine/lockfree_event.h", + "src/src/core/lib/event_engine/posix_engine/native_posix_dns_resolver.cc", + "src/src/core/lib/event_engine/posix_engine/native_posix_dns_resolver.h", "src/src/core/lib/event_engine/posix_engine/posix_endpoint.cc", "src/src/core/lib/event_engine/posix_engine/posix_endpoint.h", "src/src/core/lib/event_engine/posix_engine/posix_engine.cc", @@ -5241,6 +5968,8 @@ source_set("grpc_unsecure") { "src/src/core/lib/event_engine/posix_engine/wakeup_fd_posix.h", "src/src/core/lib/event_engine/posix_engine/wakeup_fd_posix_default.cc", "src/src/core/lib/event_engine/posix_engine/wakeup_fd_posix_default.h", + "src/src/core/lib/event_engine/query_extensions.h", + "src/src/core/lib/event_engine/ref_counted_dns_resolver_interface.h", "src/src/core/lib/event_engine/resolved_address.cc", "src/src/core/lib/event_engine/resolved_address_internal.h", "src/src/core/lib/event_engine/shim.cc", @@ -5249,22 +5978,37 @@ source_set("grpc_unsecure") { "src/src/core/lib/event_engine/slice_buffer.cc", "src/src/core/lib/event_engine/tcp_socket_utils.cc", "src/src/core/lib/event_engine/tcp_socket_utils.h", - "src/src/core/lib/event_engine/thread_pool.cc", - "src/src/core/lib/event_engine/thread_pool.h", + "src/src/core/lib/event_engine/thread_pool/thread_count.cc", + "src/src/core/lib/event_engine/thread_pool/thread_count.h", + "src/src/core/lib/event_engine/thread_pool/thread_pool.h", + "src/src/core/lib/event_engine/thread_pool/thread_pool_factory.cc", + "src/src/core/lib/event_engine/thread_pool/work_stealing_thread_pool.cc", + "src/src/core/lib/event_engine/thread_pool/work_stealing_thread_pool.h", + "src/src/core/lib/event_engine/thready_event_engine/thready_event_engine.cc", + "src/src/core/lib/event_engine/thready_event_engine/thready_event_engine.h", "src/src/core/lib/event_engine/time_util.cc", "src/src/core/lib/event_engine/time_util.h", "src/src/core/lib/event_engine/trace.cc", "src/src/core/lib/event_engine/trace.h", "src/src/core/lib/event_engine/utils.cc", "src/src/core/lib/event_engine/utils.h", + "src/src/core/lib/event_engine/windows/grpc_polled_fd_windows.cc", + "src/src/core/lib/event_engine/windows/grpc_polled_fd_windows.h", "src/src/core/lib/event_engine/windows/iocp.cc", "src/src/core/lib/event_engine/windows/iocp.h", + "src/src/core/lib/event_engine/windows/native_windows_dns_resolver.cc", + "src/src/core/lib/event_engine/windows/native_windows_dns_resolver.h", "src/src/core/lib/event_engine/windows/win_socket.cc", "src/src/core/lib/event_engine/windows/win_socket.h", "src/src/core/lib/event_engine/windows/windows_endpoint.cc", "src/src/core/lib/event_engine/windows/windows_endpoint.h", "src/src/core/lib/event_engine/windows/windows_engine.cc", "src/src/core/lib/event_engine/windows/windows_engine.h", + "src/src/core/lib/event_engine/windows/windows_listener.cc", + "src/src/core/lib/event_engine/windows/windows_listener.h", + "src/src/core/lib/event_engine/work_queue/basic_work_queue.cc", + "src/src/core/lib/event_engine/work_queue/basic_work_queue.h", + "src/src/core/lib/event_engine/work_queue/work_queue.h", "src/src/core/lib/experiments/config.cc", "src/src/core/lib/experiments/config.h", "src/src/core/lib/experiments/experiments.cc", @@ -5274,7 +6018,9 @@ source_set("grpc_unsecure") { "src/src/core/lib/gprpp/bitset.h", "src/src/core/lib/gprpp/chunked_vector.h", "src/src/core/lib/gprpp/cpp_impl_of.h", + "src/src/core/lib/gprpp/down_cast.h", "src/src/core/lib/gprpp/dual_ref_counted.h", + "src/src/core/lib/gprpp/if_list.h", "src/src/core/lib/gprpp/load_file.cc", "src/src/core/lib/gprpp/load_file.h", "src/src/core/lib/gprpp/manual_constructor.h", @@ -5283,9 +6029,12 @@ source_set("grpc_unsecure") { "src/src/core/lib/gprpp/orphanable.h", "src/src/core/lib/gprpp/overload.h", "src/src/core/lib/gprpp/packed_table.h", + "src/src/core/lib/gprpp/per_cpu.cc", "src/src/core/lib/gprpp/per_cpu.h", "src/src/core/lib/gprpp/ref_counted.h", "src/src/core/lib/gprpp/ref_counted_ptr.h", + "src/src/core/lib/gprpp/ref_counted_string.cc", + "src/src/core/lib/gprpp/ref_counted_string.h", "src/src/core/lib/gprpp/single_set_ptr.h", "src/src/core/lib/gprpp/sorted_pack.h", "src/src/core/lib/gprpp/status_helper.cc", @@ -5295,14 +6044,14 @@ source_set("grpc_unsecure") { "src/src/core/lib/gprpp/time.h", "src/src/core/lib/gprpp/time_averaged_stats.cc", "src/src/core/lib/gprpp/time_averaged_stats.h", + "src/src/core/lib/gprpp/type_list.h", "src/src/core/lib/gprpp/unique_type_name.h", + "src/src/core/lib/gprpp/uuid_v4.cc", + "src/src/core/lib/gprpp/uuid_v4.h", "src/src/core/lib/gprpp/validation_errors.cc", "src/src/core/lib/gprpp/validation_errors.h", "src/src/core/lib/gprpp/work_serializer.cc", "src/src/core/lib/gprpp/work_serializer.h", - "src/src/core/lib/handshaker/proxy_mapper.h", - "src/src/core/lib/handshaker/proxy_mapper_registry.cc", - "src/src/core/lib/handshaker/proxy_mapper_registry.h", "src/src/core/lib/http/format_request.cc", "src/src/core/lib/http/format_request.h", "src/src/core/lib/http/httpcli.cc", @@ -5373,8 +6122,6 @@ source_set("grpc_unsecure") { "src/src/core/lib/iomgr/iomgr_posix.cc", "src/src/core/lib/iomgr/iomgr_posix_cfstream.cc", "src/src/core/lib/iomgr/iomgr_windows.cc", - "src/src/core/lib/iomgr/load_file.cc", - "src/src/core/lib/iomgr/load_file.h", "src/src/core/lib/iomgr/lockfree_event.cc", "src/src/core/lib/iomgr/lockfree_event.h", "src/src/core/lib/iomgr/nameser.h", @@ -5445,6 +6192,8 @@ source_set("grpc_unsecure") { "src/src/core/lib/iomgr/unix_sockets_posix.cc", "src/src/core/lib/iomgr/unix_sockets_posix.h", "src/src/core/lib/iomgr/unix_sockets_posix_noop.cc", + "src/src/core/lib/iomgr/vsock.cc", + "src/src/core/lib/iomgr/vsock.h", "src/src/core/lib/iomgr/wakeup_fd_eventfd.cc", "src/src/core/lib/iomgr/wakeup_fd_nospecial.cc", "src/src/core/lib/iomgr/wakeup_fd_pipe.cc", @@ -5457,52 +6206,49 @@ source_set("grpc_unsecure") { "src/src/core/lib/json/json_object_loader.cc", "src/src/core/lib/json/json_object_loader.h", "src/src/core/lib/json/json_reader.cc", + "src/src/core/lib/json/json_reader.h", "src/src/core/lib/json/json_writer.cc", - "src/src/core/lib/load_balancing/lb_policy.cc", - "src/src/core/lib/load_balancing/lb_policy.h", - "src/src/core/lib/load_balancing/lb_policy_factory.h", - "src/src/core/lib/load_balancing/lb_policy_registry.cc", - "src/src/core/lib/load_balancing/lb_policy_registry.h", - "src/src/core/lib/load_balancing/subchannel_interface.h", + "src/src/core/lib/json/json_writer.h", "src/src/core/lib/promise/activity.cc", "src/src/core/lib/promise/activity.h", + "src/src/core/lib/promise/all_ok.h", "src/src/core/lib/promise/arena_promise.h", + "src/src/core/lib/promise/cancel_callback.h", "src/src/core/lib/promise/context.h", - "src/src/core/lib/promise/detail/basic_join.h", "src/src/core/lib/promise/detail/basic_seq.h", + "src/src/core/lib/promise/detail/join_state.h", "src/src/core/lib/promise/detail/promise_factory.h", "src/src/core/lib/promise/detail/promise_like.h", + "src/src/core/lib/promise/detail/seq_state.h", "src/src/core/lib/promise/detail/status.h", - "src/src/core/lib/promise/detail/switch.h", "src/src/core/lib/promise/exec_ctx_wakeup_scheduler.h", + "src/src/core/lib/promise/for_each.h", "src/src/core/lib/promise/if.h", "src/src/core/lib/promise/interceptor_list.h", - "src/src/core/lib/promise/intra_activity_waiter.h", "src/src/core/lib/promise/latch.h", "src/src/core/lib/promise/loop.h", "src/src/core/lib/promise/map.h", + "src/src/core/lib/promise/party.cc", + "src/src/core/lib/promise/party.h", "src/src/core/lib/promise/pipe.h", "src/src/core/lib/promise/poll.h", + "src/src/core/lib/promise/prioritized_race.h", "src/src/core/lib/promise/promise.h", "src/src/core/lib/promise/race.h", "src/src/core/lib/promise/seq.h", "src/src/core/lib/promise/sleep.cc", "src/src/core/lib/promise/sleep.h", + "src/src/core/lib/promise/status_flag.h", "src/src/core/lib/promise/trace.cc", "src/src/core/lib/promise/trace.h", "src/src/core/lib/promise/try_join.h", "src/src/core/lib/promise/try_seq.h", - "src/src/core/lib/resolver/resolver.cc", - "src/src/core/lib/resolver/resolver.h", - "src/src/core/lib/resolver/resolver_factory.h", - "src/src/core/lib/resolver/resolver_registry.cc", - "src/src/core/lib/resolver/resolver_registry.h", - "src/src/core/lib/resolver/server_address.cc", - "src/src/core/lib/resolver/server_address.h", "src/src/core/lib/resource_quota/api.cc", "src/src/core/lib/resource_quota/api.h", "src/src/core/lib/resource_quota/arena.cc", "src/src/core/lib/resource_quota/arena.h", + "src/src/core/lib/resource_quota/connection_quota.cc", + "src/src/core/lib/resource_quota/connection_quota.h", "src/src/core/lib/resource_quota/memory_quota.cc", "src/src/core/lib/resource_quota/memory_quota.h", "src/src/core/lib/resource_quota/periodic_update.cc", @@ -5557,27 +6303,14 @@ source_set("grpc_unsecure") { "src/src/core/lib/security/security_connector/load_system_roots_fallback.cc", "src/src/core/lib/security/security_connector/load_system_roots_supported.cc", "src/src/core/lib/security/security_connector/load_system_roots_supported.h", + "src/src/core/lib/security/security_connector/load_system_roots_windows.cc", "src/src/core/lib/security/security_connector/security_connector.cc", "src/src/core/lib/security/security_connector/security_connector.h", "src/src/core/lib/security/transport/auth_filters.h", "src/src/core/lib/security/transport/client_auth_filter.cc", - "src/src/core/lib/security/transport/secure_endpoint.cc", - "src/src/core/lib/security/transport/secure_endpoint.h", - "src/src/core/lib/security/transport/security_handshaker.cc", - "src/src/core/lib/security/transport/security_handshaker.h", "src/src/core/lib/security/transport/server_auth_filter.cc", - "src/src/core/lib/security/transport/tsi_error.cc", - "src/src/core/lib/security/transport/tsi_error.h", "src/src/core/lib/security/util/json_util.cc", "src/src/core/lib/security/util/json_util.h", - "src/src/core/lib/service_config/service_config.h", - "src/src/core/lib/service_config/service_config_call_data.h", - "src/src/core/lib/service_config/service_config_impl.cc", - "src/src/core/lib/service_config/service_config_impl.h", - "src/src/core/lib/service_config/service_config_parser.cc", - "src/src/core/lib/service_config/service_config_parser.h", - "src/src/core/lib/slice/b64.cc", - "src/src/core/lib/slice/b64.h", "src/src/core/lib/slice/percent_encoding.cc", "src/src/core/lib/slice/percent_encoding.h", "src/src/core/lib/slice/slice.cc", @@ -5591,8 +6324,6 @@ source_set("grpc_unsecure") { "src/src/core/lib/slice/slice_string_helpers.h", "src/src/core/lib/surface/api_trace.cc", "src/src/core/lib/surface/api_trace.h", - "src/src/core/lib/surface/builtins.cc", - "src/src/core/lib/surface/builtins.h", "src/src/core/lib/surface/byte_buffer.cc", "src/src/core/lib/surface/byte_buffer_reader.cc", "src/src/core/lib/surface/call.cc", @@ -5600,13 +6331,13 @@ source_set("grpc_unsecure") { "src/src/core/lib/surface/call_details.cc", "src/src/core/lib/surface/call_log_batch.cc", "src/src/core/lib/surface/call_test_only.h", - "src/src/core/lib/surface/call_trace.cc", "src/src/core/lib/surface/call_trace.h", "src/src/core/lib/surface/channel.cc", "src/src/core/lib/surface/channel.h", + "src/src/core/lib/surface/channel_create.cc", + "src/src/core/lib/surface/channel_create.h", "src/src/core/lib/surface/channel_init.cc", "src/src/core/lib/surface/channel_init.h", - "src/src/core/lib/surface/channel_ping.cc", "src/src/core/lib/surface/channel_stack_type.cc", "src/src/core/lib/surface/channel_stack_type.h", "src/src/core/lib/surface/completion_queue.cc", @@ -5621,47 +6352,144 @@ source_set("grpc_unsecure") { "src/src/core/lib/surface/init_internally.h", "src/src/core/lib/surface/lame_client.cc", "src/src/core/lib/surface/lame_client.h", + "src/src/core/lib/surface/legacy_channel.cc", + "src/src/core/lib/surface/legacy_channel.h", "src/src/core/lib/surface/metadata_array.cc", - "src/src/core/lib/surface/server.cc", - "src/src/core/lib/surface/server.h", "src/src/core/lib/surface/validate_metadata.cc", "src/src/core/lib/surface/validate_metadata.h", "src/src/core/lib/surface/version.cc", + "src/src/core/lib/surface/wait_for_cq_end_op.cc", + "src/src/core/lib/surface/wait_for_cq_end_op.h", + "src/src/core/lib/transport/batch_builder.cc", + "src/src/core/lib/transport/batch_builder.h", "src/src/core/lib/transport/bdp_estimator.cc", "src/src/core/lib/transport/bdp_estimator.h", + "src/src/core/lib/transport/call_arena_allocator.cc", + "src/src/core/lib/transport/call_arena_allocator.h", + "src/src/core/lib/transport/call_filters.cc", + "src/src/core/lib/transport/call_filters.h", + "src/src/core/lib/transport/call_final_info.cc", + "src/src/core/lib/transport/call_final_info.h", + "src/src/core/lib/transport/call_spine.cc", + "src/src/core/lib/transport/call_spine.h", "src/src/core/lib/transport/connectivity_state.cc", "src/src/core/lib/transport/connectivity_state.h", + "src/src/core/lib/transport/custom_metadata.h", "src/src/core/lib/transport/error_utils.cc", "src/src/core/lib/transport/error_utils.h", - "src/src/core/lib/transport/handshaker.cc", - "src/src/core/lib/transport/handshaker.h", - "src/src/core/lib/transport/handshaker_factory.h", - "src/src/core/lib/transport/handshaker_registry.cc", - "src/src/core/lib/transport/handshaker_registry.h", "src/src/core/lib/transport/http2_errors.h", - "src/src/core/lib/transport/http_connect_handshaker.cc", - "src/src/core/lib/transport/http_connect_handshaker.h", + "src/src/core/lib/transport/message.cc", + "src/src/core/lib/transport/message.h", + "src/src/core/lib/transport/metadata.cc", + "src/src/core/lib/transport/metadata.h", "src/src/core/lib/transport/metadata_batch.cc", "src/src/core/lib/transport/metadata_batch.h", + "src/src/core/lib/transport/metadata_compression_traits.h", + "src/src/core/lib/transport/metadata_info.cc", + "src/src/core/lib/transport/metadata_info.h", "src/src/core/lib/transport/parsed_metadata.cc", "src/src/core/lib/transport/parsed_metadata.h", - "src/src/core/lib/transport/pid_controller.cc", - "src/src/core/lib/transport/pid_controller.h", + "src/src/core/lib/transport/simple_slice_based_metadata.h", "src/src/core/lib/transport/status_conversion.cc", "src/src/core/lib/transport/status_conversion.h", - "src/src/core/lib/transport/tcp_connect_handshaker.cc", - "src/src/core/lib/transport/tcp_connect_handshaker.h", "src/src/core/lib/transport/timeout_encoding.cc", "src/src/core/lib/transport/timeout_encoding.h", "src/src/core/lib/transport/transport.cc", "src/src/core/lib/transport/transport.h", "src/src/core/lib/transport/transport_fwd.h", - "src/src/core/lib/transport/transport_impl.h", "src/src/core/lib/transport/transport_op_string.cc", "src/src/core/lib/uri/uri_parser.cc", "src/src/core/lib/uri/uri_parser.h", + "src/src/core/load_balancing/address_filtering.cc", + "src/src/core/load_balancing/address_filtering.h", + "src/src/core/load_balancing/backend_metric_data.h", + "src/src/core/load_balancing/backend_metric_parser.cc", + "src/src/core/load_balancing/backend_metric_parser.h", + "src/src/core/load_balancing/child_policy_handler.cc", + "src/src/core/load_balancing/child_policy_handler.h", + "src/src/core/load_balancing/delegating_helper.h", + "src/src/core/load_balancing/endpoint_list.cc", + "src/src/core/load_balancing/endpoint_list.h", + "src/src/core/load_balancing/grpclb/client_load_reporting_filter.cc", + "src/src/core/load_balancing/grpclb/client_load_reporting_filter.h", + "src/src/core/load_balancing/grpclb/grpclb.cc", + "src/src/core/load_balancing/grpclb/grpclb.h", + "src/src/core/load_balancing/grpclb/grpclb_balancer_addresses.cc", + "src/src/core/load_balancing/grpclb/grpclb_balancer_addresses.h", + "src/src/core/load_balancing/grpclb/grpclb_client_stats.cc", + "src/src/core/load_balancing/grpclb/grpclb_client_stats.h", + "src/src/core/load_balancing/grpclb/load_balancer_api.cc", + "src/src/core/load_balancing/grpclb/load_balancer_api.h", + "src/src/core/load_balancing/health_check_client.cc", + "src/src/core/load_balancing/health_check_client.h", + "src/src/core/load_balancing/health_check_client_internal.h", + "src/src/core/load_balancing/lb_policy.cc", + "src/src/core/load_balancing/lb_policy.h", + "src/src/core/load_balancing/lb_policy_factory.h", + "src/src/core/load_balancing/lb_policy_registry.cc", + "src/src/core/load_balancing/lb_policy_registry.h", + "src/src/core/load_balancing/oob_backend_metric.cc", + "src/src/core/load_balancing/oob_backend_metric.h", + "src/src/core/load_balancing/oob_backend_metric_internal.h", + "src/src/core/load_balancing/outlier_detection/outlier_detection.cc", + "src/src/core/load_balancing/outlier_detection/outlier_detection.h", + "src/src/core/load_balancing/pick_first/pick_first.cc", + "src/src/core/load_balancing/pick_first/pick_first.h", + "src/src/core/load_balancing/priority/priority.cc", + "src/src/core/load_balancing/rls/rls.cc", + "src/src/core/load_balancing/rls/rls.h", + "src/src/core/load_balancing/round_robin/round_robin.cc", + "src/src/core/load_balancing/subchannel_interface.h", + "src/src/core/load_balancing/weighted_round_robin/static_stride_scheduler.cc", + "src/src/core/load_balancing/weighted_round_robin/static_stride_scheduler.h", + "src/src/core/load_balancing/weighted_round_robin/weighted_round_robin.cc", + "src/src/core/load_balancing/weighted_target/weighted_target.cc", + "src/src/core/load_balancing/weighted_target/weighted_target.h", "src/src/core/plugin_registry/grpc_plugin_registry.cc", "src/src/core/plugin_registry/grpc_plugin_registry_noextra.cc", + "src/src/core/resolver/binder/binder_resolver.cc", + "src/src/core/resolver/dns/c_ares/dns_resolver_ares.cc", + "src/src/core/resolver/dns/c_ares/dns_resolver_ares.h", + "src/src/core/resolver/dns/c_ares/grpc_ares_ev_driver.h", + "src/src/core/resolver/dns/c_ares/grpc_ares_ev_driver_posix.cc", + "src/src/core/resolver/dns/c_ares/grpc_ares_ev_driver_windows.cc", + "src/src/core/resolver/dns/c_ares/grpc_ares_wrapper.cc", + "src/src/core/resolver/dns/c_ares/grpc_ares_wrapper.h", + "src/src/core/resolver/dns/c_ares/grpc_ares_wrapper_posix.cc", + "src/src/core/resolver/dns/c_ares/grpc_ares_wrapper_windows.cc", + "src/src/core/resolver/dns/dns_resolver_plugin.cc", + "src/src/core/resolver/dns/dns_resolver_plugin.h", + "src/src/core/resolver/dns/event_engine/event_engine_client_channel_resolver.cc", + "src/src/core/resolver/dns/event_engine/event_engine_client_channel_resolver.h", + "src/src/core/resolver/dns/event_engine/service_config_helper.cc", + "src/src/core/resolver/dns/event_engine/service_config_helper.h", + "src/src/core/resolver/dns/native/dns_resolver.cc", + "src/src/core/resolver/dns/native/dns_resolver.h", + "src/src/core/resolver/endpoint_addresses.cc", + "src/src/core/resolver/endpoint_addresses.h", + "src/src/core/resolver/fake/fake_resolver.cc", + "src/src/core/resolver/fake/fake_resolver.h", + "src/src/core/resolver/polling_resolver.cc", + "src/src/core/resolver/polling_resolver.h", + "src/src/core/resolver/resolver.cc", + "src/src/core/resolver/resolver.h", + "src/src/core/resolver/resolver_factory.h", + "src/src/core/resolver/resolver_registry.cc", + "src/src/core/resolver/resolver_registry.h", + "src/src/core/resolver/server_address.h", + "src/src/core/resolver/sockaddr/sockaddr_resolver.cc", + "src/src/core/server/server.cc", + "src/src/core/server/server.h", + "src/src/core/server/server_call_tracer_filter.cc", + "src/src/core/server/server_call_tracer_filter.h", + "src/src/core/server/server_interface.h", + "src/src/core/service_config/service_config.h", + "src/src/core/service_config/service_config_call_data.h", + "src/src/core/service_config/service_config_channel_arg_filter.cc", + "src/src/core/service_config/service_config_impl.cc", + "src/src/core/service_config/service_config_impl.h", + "src/src/core/service_config/service_config_parser.cc", + "src/src/core/service_config/service_config_parser.h", "src/src/core/tsi/alts/handshaker/transport_security_common_api.cc", "src/src/core/tsi/alts/handshaker/transport_security_common_api.h", "src/src/core/tsi/fake_transport_security.cc", @@ -5673,27 +6501,431 @@ source_set("grpc_unsecure") { "src/src/core/tsi/transport_security_grpc.cc", "src/src/core/tsi/transport_security_grpc.h", "src/src/core/tsi/transport_security_interface.h", - "src/third_party/xxhash/xxhash.h", + "src/third_party/upb/upb/generated_code_support.h", + "src/third_party/upb/upb/mini_descriptor/build_enum.c", + "src/third_party/upb/upb/mini_descriptor/build_enum.h", + "src/third_party/upb/upb/mini_descriptor/decode.c", + "src/third_party/upb/upb/mini_descriptor/decode.h", + "src/third_party/upb/upb/mini_descriptor/internal/base92.c", + "src/third_party/upb/upb/mini_descriptor/internal/base92.h", + "src/third_party/upb/upb/mini_descriptor/internal/decoder.h", + "src/third_party/upb/upb/mini_descriptor/internal/encode.c", + "src/third_party/upb/upb/mini_descriptor/internal/encode.h", + "src/third_party/upb/upb/mini_descriptor/internal/encode.hpp", + "src/third_party/upb/upb/mini_descriptor/internal/modifiers.h", + "src/third_party/upb/upb/mini_descriptor/internal/wire_constants.h", + "src/third_party/upb/upb/mini_descriptor/link.c", + "src/third_party/upb/upb/mini_descriptor/link.h", + "src/third_party/upb/upb/wire/decode.c", + "src/third_party/upb/upb/wire/decode.h", + "src/third_party/upb/upb/wire/encode.c", + "src/third_party/upb/upb/wire/encode.h", + "src/third_party/upb/upb/wire/eps_copy_input_stream.c", + "src/third_party/upb/upb/wire/eps_copy_input_stream.h", + "src/third_party/upb/upb/wire/internal/constants.h", + "src/third_party/upb/upb/wire/internal/decode_fast.c", + "src/third_party/upb/upb/wire/internal/decode_fast.h", + "src/third_party/upb/upb/wire/internal/decoder.h", + "src/third_party/upb/upb/wire/internal/reader.h", + "src/third_party/upb/upb/wire/reader.c", + "src/third_party/upb/upb/wire/reader.h", + "src/third_party/upb/upb/wire/types.h", ] public_deps = [ + ":absl_algorithm_container", + ":absl_base_config", + ":absl_base_no_destructor", ":absl_cleanup_cleanup", ":absl_container_flat_hash_map", ":absl_container_flat_hash_set", ":absl_container_inlined_vector", - ":absl_functional_any_invocable", ":absl_functional_bind_front", ":absl_functional_function_ref", ":absl_hash_hash", ":absl_meta_type_traits", + ":absl_random_bit_gen_ref", + ":absl_random_distributions", ":absl_status_statusor", ":absl_types_span", ":absl_utility_utility", ":address_sorting", ":gpr", - ":upb", + ":upb_message_lib", + ":utf8_range_lib", + "..:zlib", ] public_configs = [ "..:grpc_internal_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true +} + +static_library("re2") { + sources = [ + "src/third_party/re2/re2/bitmap256.h", + "src/third_party/re2/re2/bitstate.cc", + "src/third_party/re2/re2/compile.cc", + "src/third_party/re2/re2/dfa.cc", + "src/third_party/re2/re2/filtered_re2.cc", + "src/third_party/re2/re2/filtered_re2.h", + "src/third_party/re2/re2/mimics_pcre.cc", + "src/third_party/re2/re2/nfa.cc", + "src/third_party/re2/re2/onepass.cc", + "src/third_party/re2/re2/parse.cc", + "src/third_party/re2/re2/perl_groups.cc", + "src/third_party/re2/re2/pod_array.h", + "src/third_party/re2/re2/prefilter.cc", + "src/third_party/re2/re2/prefilter.h", + "src/third_party/re2/re2/prefilter_tree.cc", + "src/third_party/re2/re2/prefilter_tree.h", + "src/third_party/re2/re2/prog.cc", + "src/third_party/re2/re2/prog.h", + "src/third_party/re2/re2/re2.cc", + "src/third_party/re2/re2/re2.h", + "src/third_party/re2/re2/regexp.cc", + "src/third_party/re2/re2/regexp.h", + "src/third_party/re2/re2/set.cc", + "src/third_party/re2/re2/set.h", + "src/third_party/re2/re2/simplify.cc", + "src/third_party/re2/re2/sparse_array.h", + "src/third_party/re2/re2/sparse_set.h", + "src/third_party/re2/re2/stringpiece.cc", + "src/third_party/re2/re2/stringpiece.h", + "src/third_party/re2/re2/tostring.cc", + "src/third_party/re2/re2/unicode_casefold.cc", + "src/third_party/re2/re2/unicode_casefold.h", + "src/third_party/re2/re2/unicode_groups.cc", + "src/third_party/re2/re2/unicode_groups.h", + "src/third_party/re2/re2/walker-inl.h", + "src/third_party/re2/util/logging.h", + "src/third_party/re2/util/mix.h", + "src/third_party/re2/util/mutex.h", + "src/third_party/re2/util/rune.cc", + "src/third_party/re2/util/strutil.cc", + "src/third_party/re2/util/strutil.h", + "src/third_party/re2/util/utf.h", + "src/third_party/re2/util/util.h", + ] + public_deps = [] + public_configs = [ "..:grpc_internal_config" ] + configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true +} + +source_set("upb_base_lib") { + sources = [ + "src/third_party/upb/upb/base/descriptor_constants.h", + "src/third_party/upb/upb/base/status.c", + "src/third_party/upb/upb/base/status.h", + "src/third_party/upb/upb/base/status.hpp", + "src/third_party/upb/upb/base/string_view.h", + "src/third_party/upb/upb/base/upcast.h", + "src/third_party/upb/upb/port/atomic.h", + "src/third_party/upb/upb/port/def.inc", + "src/third_party/upb/upb/port/undef.inc", + "src/third_party/upb/upb/port/vsnprintf_compat.h", + ] + public_deps = [] + public_configs = [ "..:grpc_internal_config" ] + configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true +} + +static_library("upb_json_lib") { + sources = [ + "src/third_party/upb/upb/json/decode.c", + "src/third_party/upb/upb/json/decode.h", + "src/third_party/upb/upb/json/encode.c", + "src/third_party/upb/upb/json/encode.h", + "src/third_party/upb/upb/lex/atoi.c", + "src/third_party/upb/upb/lex/atoi.h", + "src/third_party/upb/upb/lex/round_trip.c", + "src/third_party/upb/upb/lex/round_trip.h", + "src/third_party/upb/upb/lex/strtod.c", + "src/third_party/upb/upb/lex/strtod.h", + "src/third_party/upb/upb/lex/unicode.c", + "src/third_party/upb/upb/lex/unicode.h", + "src/third_party/upb/upb/message/copy.c", + "src/third_party/upb/upb/message/copy.h", + "src/third_party/upb/upb/mini_descriptor/build_enum.c", + "src/third_party/upb/upb/mini_descriptor/build_enum.h", + "src/third_party/upb/upb/mini_descriptor/decode.c", + "src/third_party/upb/upb/mini_descriptor/decode.h", + "src/third_party/upb/upb/mini_descriptor/internal/base92.c", + "src/third_party/upb/upb/mini_descriptor/internal/base92.h", + "src/third_party/upb/upb/mini_descriptor/internal/decoder.h", + "src/third_party/upb/upb/mini_descriptor/internal/encode.c", + "src/third_party/upb/upb/mini_descriptor/internal/encode.h", + "src/third_party/upb/upb/mini_descriptor/internal/encode.hpp", + "src/third_party/upb/upb/mini_descriptor/internal/modifiers.h", + "src/third_party/upb/upb/mini_descriptor/internal/wire_constants.h", + "src/third_party/upb/upb/mini_descriptor/link.c", + "src/third_party/upb/upb/mini_descriptor/link.h", + "src/third_party/upb/upb/reflection/common.h", + "src/third_party/upb/upb/reflection/def.h", + "src/third_party/upb/upb/reflection/def.hpp", + "src/third_party/upb/upb/reflection/def_pool.c", + "src/third_party/upb/upb/reflection/def_pool.h", + "src/third_party/upb/upb/reflection/def_type.c", + "src/third_party/upb/upb/reflection/def_type.h", + "src/third_party/upb/upb/reflection/desc_state.c", + "src/third_party/upb/upb/reflection/enum_def.c", + "src/third_party/upb/upb/reflection/enum_def.h", + "src/third_party/upb/upb/reflection/enum_reserved_range.c", + "src/third_party/upb/upb/reflection/enum_reserved_range.h", + "src/third_party/upb/upb/reflection/enum_value_def.c", + "src/third_party/upb/upb/reflection/enum_value_def.h", + "src/third_party/upb/upb/reflection/extension_range.c", + "src/third_party/upb/upb/reflection/extension_range.h", + "src/third_party/upb/upb/reflection/field_def.c", + "src/third_party/upb/upb/reflection/field_def.h", + "src/third_party/upb/upb/reflection/file_def.c", + "src/third_party/upb/upb/reflection/file_def.h", + "src/third_party/upb/upb/reflection/internal/def_builder.c", + "src/third_party/upb/upb/reflection/internal/def_builder.h", + "src/third_party/upb/upb/reflection/internal/def_pool.h", + "src/third_party/upb/upb/reflection/internal/desc_state.h", + "src/third_party/upb/upb/reflection/internal/enum_def.h", + "src/third_party/upb/upb/reflection/internal/enum_reserved_range.h", + "src/third_party/upb/upb/reflection/internal/enum_value_def.h", + "src/third_party/upb/upb/reflection/internal/extension_range.h", + "src/third_party/upb/upb/reflection/internal/field_def.h", + "src/third_party/upb/upb/reflection/internal/file_def.h", + "src/third_party/upb/upb/reflection/internal/message_def.h", + "src/third_party/upb/upb/reflection/internal/message_reserved_range.h", + "src/third_party/upb/upb/reflection/internal/method_def.h", + "src/third_party/upb/upb/reflection/internal/oneof_def.h", + "src/third_party/upb/upb/reflection/internal/service_def.h", + "src/third_party/upb/upb/reflection/internal/strdup2.c", + "src/third_party/upb/upb/reflection/internal/strdup2.h", + "src/third_party/upb/upb/reflection/internal/upb_edition_defaults.h", + "src/third_party/upb/upb/reflection/message.c", + "src/third_party/upb/upb/reflection/message.h", + "src/third_party/upb/upb/reflection/message.hpp", + "src/third_party/upb/upb/reflection/message_def.c", + "src/third_party/upb/upb/reflection/message_def.h", + "src/third_party/upb/upb/reflection/message_reserved_range.c", + "src/third_party/upb/upb/reflection/message_reserved_range.h", + "src/third_party/upb/upb/reflection/method_def.c", + "src/third_party/upb/upb/reflection/method_def.h", + "src/third_party/upb/upb/reflection/oneof_def.c", + "src/third_party/upb/upb/reflection/oneof_def.h", + "src/third_party/upb/upb/reflection/service_def.c", + "src/third_party/upb/upb/reflection/service_def.h", + "src/third_party/upb/upb/wire/decode.c", + "src/third_party/upb/upb/wire/decode.h", + "src/third_party/upb/upb/wire/encode.c", + "src/third_party/upb/upb/wire/encode.h", + "src/third_party/upb/upb/wire/eps_copy_input_stream.c", + "src/third_party/upb/upb/wire/eps_copy_input_stream.h", + "src/third_party/upb/upb/wire/internal/constants.h", + "src/third_party/upb/upb/wire/internal/decode_fast.c", + "src/third_party/upb/upb/wire/internal/decode_fast.h", + "src/third_party/upb/upb/wire/internal/decoder.h", + "src/third_party/upb/upb/wire/internal/reader.h", + "src/third_party/upb/upb/wire/reader.c", + "src/third_party/upb/upb/wire/reader.h", + "src/third_party/upb/upb/wire/types.h", + ] + public_deps = [ + ":upb_message_lib", + ":utf8_range_lib", + ] + public_configs = [ "..:grpc_internal_config" ] + configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = false +} + +source_set("upb_mem_lib") { + sources = [ + "src/third_party/upb/upb/mem/alloc.c", + "src/third_party/upb/upb/mem/alloc.h", + "src/third_party/upb/upb/mem/arena.c", + "src/third_party/upb/upb/mem/arena.h", + "src/third_party/upb/upb/mem/arena.hpp", + "src/third_party/upb/upb/mem/internal/arena.h", + "src/third_party/upb/upb/port/atomic.h", + "src/third_party/upb/upb/port/def.inc", + "src/third_party/upb/upb/port/undef.inc", + "src/third_party/upb/upb/port/vsnprintf_compat.h", + ] + public_deps = [] + public_configs = [ "..:grpc_internal_config" ] + configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true +} + +source_set("upb_message_lib") { + sources = [ + "src/third_party/upb/upb/base/internal/endian.h", + "src/third_party/upb/upb/base/internal/log2.h", + "src/third_party/upb/upb/hash/common.c", + "src/third_party/upb/upb/hash/common.h", + "src/third_party/upb/upb/hash/int_table.h", + "src/third_party/upb/upb/hash/str_table.h", + "src/third_party/upb/upb/message/accessors.c", + "src/third_party/upb/upb/message/accessors.h", + "src/third_party/upb/upb/message/array.c", + "src/third_party/upb/upb/message/array.h", + "src/third_party/upb/upb/message/compat.c", + "src/third_party/upb/upb/message/compat.h", + "src/third_party/upb/upb/message/internal/accessors.h", + "src/third_party/upb/upb/message/internal/array.h", + "src/third_party/upb/upb/message/internal/extension.c", + "src/third_party/upb/upb/message/internal/extension.h", + "src/third_party/upb/upb/message/internal/map.h", + "src/third_party/upb/upb/message/internal/map_entry.h", + "src/third_party/upb/upb/message/internal/map_sorter.h", + "src/third_party/upb/upb/message/internal/message.c", + "src/third_party/upb/upb/message/internal/message.h", + "src/third_party/upb/upb/message/internal/tagged_ptr.h", + "src/third_party/upb/upb/message/internal/types.h", + "src/third_party/upb/upb/message/map.c", + "src/third_party/upb/upb/message/map.h", + "src/third_party/upb/upb/message/map_gencode_util.h", + "src/third_party/upb/upb/message/map_sorter.c", + "src/third_party/upb/upb/message/message.c", + "src/third_party/upb/upb/message/message.h", + "src/third_party/upb/upb/message/tagged_ptr.h", + "src/third_party/upb/upb/message/value.h", + "src/third_party/upb/upb/mini_table/enum.h", + "src/third_party/upb/upb/mini_table/extension.h", + "src/third_party/upb/upb/mini_table/extension_registry.c", + "src/third_party/upb/upb/mini_table/extension_registry.h", + "src/third_party/upb/upb/mini_table/field.h", + "src/third_party/upb/upb/mini_table/file.h", + "src/third_party/upb/upb/mini_table/internal/enum.h", + "src/third_party/upb/upb/mini_table/internal/extension.h", + "src/third_party/upb/upb/mini_table/internal/field.h", + "src/third_party/upb/upb/mini_table/internal/file.h", + "src/third_party/upb/upb/mini_table/internal/message.c", + "src/third_party/upb/upb/mini_table/internal/message.h", + "src/third_party/upb/upb/mini_table/internal/size_log2.h", + "src/third_party/upb/upb/mini_table/internal/sub.h", + "src/third_party/upb/upb/mini_table/message.c", + "src/third_party/upb/upb/mini_table/message.h", + "src/third_party/upb/upb/mini_table/sub.h", + ] + public_deps = [ + ":upb_base_lib", + ":upb_mem_lib", + ] + public_configs = [ "..:grpc_internal_config" ] + configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true +} + +static_library("upb_textformat_lib") { + sources = [ + "src/third_party/upb/upb/lex/atoi.c", + "src/third_party/upb/upb/lex/atoi.h", + "src/third_party/upb/upb/lex/round_trip.c", + "src/third_party/upb/upb/lex/round_trip.h", + "src/third_party/upb/upb/lex/strtod.c", + "src/third_party/upb/upb/lex/strtod.h", + "src/third_party/upb/upb/lex/unicode.c", + "src/third_party/upb/upb/lex/unicode.h", + "src/third_party/upb/upb/message/copy.c", + "src/third_party/upb/upb/message/copy.h", + "src/third_party/upb/upb/mini_descriptor/build_enum.c", + "src/third_party/upb/upb/mini_descriptor/build_enum.h", + "src/third_party/upb/upb/mini_descriptor/decode.c", + "src/third_party/upb/upb/mini_descriptor/decode.h", + "src/third_party/upb/upb/mini_descriptor/internal/base92.c", + "src/third_party/upb/upb/mini_descriptor/internal/base92.h", + "src/third_party/upb/upb/mini_descriptor/internal/decoder.h", + "src/third_party/upb/upb/mini_descriptor/internal/encode.c", + "src/third_party/upb/upb/mini_descriptor/internal/encode.h", + "src/third_party/upb/upb/mini_descriptor/internal/encode.hpp", + "src/third_party/upb/upb/mini_descriptor/internal/modifiers.h", + "src/third_party/upb/upb/mini_descriptor/internal/wire_constants.h", + "src/third_party/upb/upb/mini_descriptor/link.c", + "src/third_party/upb/upb/mini_descriptor/link.h", + "src/third_party/upb/upb/reflection/common.h", + "src/third_party/upb/upb/reflection/def.h", + "src/third_party/upb/upb/reflection/def.hpp", + "src/third_party/upb/upb/reflection/def_pool.c", + "src/third_party/upb/upb/reflection/def_pool.h", + "src/third_party/upb/upb/reflection/def_type.c", + "src/third_party/upb/upb/reflection/def_type.h", + "src/third_party/upb/upb/reflection/desc_state.c", + "src/third_party/upb/upb/reflection/enum_def.c", + "src/third_party/upb/upb/reflection/enum_def.h", + "src/third_party/upb/upb/reflection/enum_reserved_range.c", + "src/third_party/upb/upb/reflection/enum_reserved_range.h", + "src/third_party/upb/upb/reflection/enum_value_def.c", + "src/third_party/upb/upb/reflection/enum_value_def.h", + "src/third_party/upb/upb/reflection/extension_range.c", + "src/third_party/upb/upb/reflection/extension_range.h", + "src/third_party/upb/upb/reflection/field_def.c", + "src/third_party/upb/upb/reflection/field_def.h", + "src/third_party/upb/upb/reflection/file_def.c", + "src/third_party/upb/upb/reflection/file_def.h", + "src/third_party/upb/upb/reflection/internal/def_builder.c", + "src/third_party/upb/upb/reflection/internal/def_builder.h", + "src/third_party/upb/upb/reflection/internal/def_pool.h", + "src/third_party/upb/upb/reflection/internal/desc_state.h", + "src/third_party/upb/upb/reflection/internal/enum_def.h", + "src/third_party/upb/upb/reflection/internal/enum_reserved_range.h", + "src/third_party/upb/upb/reflection/internal/enum_value_def.h", + "src/third_party/upb/upb/reflection/internal/extension_range.h", + "src/third_party/upb/upb/reflection/internal/field_def.h", + "src/third_party/upb/upb/reflection/internal/file_def.h", + "src/third_party/upb/upb/reflection/internal/message_def.h", + "src/third_party/upb/upb/reflection/internal/message_reserved_range.h", + "src/third_party/upb/upb/reflection/internal/method_def.h", + "src/third_party/upb/upb/reflection/internal/oneof_def.h", + "src/third_party/upb/upb/reflection/internal/service_def.h", + "src/third_party/upb/upb/reflection/internal/strdup2.c", + "src/third_party/upb/upb/reflection/internal/strdup2.h", + "src/third_party/upb/upb/reflection/internal/upb_edition_defaults.h", + "src/third_party/upb/upb/reflection/message.c", + "src/third_party/upb/upb/reflection/message.h", + "src/third_party/upb/upb/reflection/message.hpp", + "src/third_party/upb/upb/reflection/message_def.c", + "src/third_party/upb/upb/reflection/message_def.h", + "src/third_party/upb/upb/reflection/message_reserved_range.c", + "src/third_party/upb/upb/reflection/message_reserved_range.h", + "src/third_party/upb/upb/reflection/method_def.c", + "src/third_party/upb/upb/reflection/method_def.h", + "src/third_party/upb/upb/reflection/oneof_def.c", + "src/third_party/upb/upb/reflection/oneof_def.h", + "src/third_party/upb/upb/reflection/service_def.c", + "src/third_party/upb/upb/reflection/service_def.h", + "src/third_party/upb/upb/text/encode.c", + "src/third_party/upb/upb/text/encode.h", + "src/third_party/upb/upb/wire/decode.c", + "src/third_party/upb/upb/wire/decode.h", + "src/third_party/upb/upb/wire/encode.c", + "src/third_party/upb/upb/wire/encode.h", + "src/third_party/upb/upb/wire/eps_copy_input_stream.c", + "src/third_party/upb/upb/wire/eps_copy_input_stream.h", + "src/third_party/upb/upb/wire/internal/constants.h", + "src/third_party/upb/upb/wire/internal/decode_fast.c", + "src/third_party/upb/upb/wire/internal/decode_fast.h", + "src/third_party/upb/upb/wire/internal/decoder.h", + "src/third_party/upb/upb/wire/internal/reader.h", + "src/third_party/upb/upb/wire/reader.c", + "src/third_party/upb/upb/wire/reader.h", + "src/third_party/upb/upb/wire/types.h", + ] + public_deps = [ + ":upb_message_lib", + ":utf8_range_lib", + ] + public_configs = [ "..:grpc_internal_config" ] + configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = false +} + +source_set("utf8_range_lib") { + sources = [ + "src/third_party/utf8_range/utf8_range.c", + "src/third_party/utf8_range/utf8_range.h", + ] + public_deps = [] + public_configs = [ "..:grpc_internal_config" ] + configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } static_library("grpc++") { @@ -5736,10 +6968,15 @@ static_library("grpc++") { "src/src/core/ext/transport/binder/wire_format/wire_reader_impl.h", "src/src/core/ext/transport/binder/wire_format/wire_writer.cc", "src/src/core/ext/transport/binder/wire_format/wire_writer.h", + "src/src/core/xds/grpc/xds_enabled_server.h", + "src/src/cpp/client/call_credentials.cc", "src/src/cpp/client/channel_cc.cc", + "src/src/cpp/client/channel_credentials.cc", "src/src/cpp/client/client_callback.cc", "src/src/cpp/client/client_context.cc", "src/src/cpp/client/client_interceptor.cc", + "src/src/cpp/client/client_stats_interceptor.cc", + "src/src/cpp/client/client_stats_interceptor.h", "src/src/cpp/client/create_channel.cc", "src/src/cpp/client/create_channel_internal.cc", "src/src/cpp/client/create_channel_internal.h", @@ -5751,14 +6988,11 @@ static_library("grpc++") { "src/src/cpp/common/alarm.cc", "src/src/cpp/common/auth_property_iterator.cc", "src/src/cpp/common/channel_arguments.cc", - "src/src/cpp/common/channel_filter.cc", - "src/src/cpp/common/channel_filter.h", "src/src/cpp/common/completion_queue_cc.cc", "src/src/cpp/common/resource_quota_cc.cc", "src/src/cpp/common/rpc_method.cc", "src/src/cpp/common/secure_auth_context.cc", "src/src/cpp/common/secure_auth_context.h", - "src/src/cpp/common/secure_channel_arguments.cc", "src/src/cpp/common/secure_create_auth_context.cc", "src/src/cpp/common/tls_certificate_provider.cc", "src/src/cpp/common/tls_certificate_verifier.cc", @@ -5766,6 +7000,8 @@ static_library("grpc++") { "src/src/cpp/common/validate_service_config.cc", "src/src/cpp/common/version_cc.cc", "src/src/cpp/server/async_generic_service.cc", + "src/src/cpp/server/backend_metric_recorder.cc", + "src/src/cpp/server/backend_metric_recorder.h", "src/src/cpp/server/channel_argument_option.cc", "src/src/cpp/server/create_default_thread_pool.cc", "src/src/cpp/server/dynamic_thread_pool.h", @@ -5776,15 +7012,16 @@ static_library("grpc++") { "src/src/cpp/server/health/health_check_service.cc", "src/src/cpp/server/health/health_check_service_server_builder_option.cc", "src/src/cpp/server/insecure_server_credentials.cc", - "src/src/cpp/server/orca/call_metric_recorder.cc", "src/src/cpp/server/secure_server_credentials.cc", "src/src/cpp/server/secure_server_credentials.h", "src/src/cpp/server/server_builder.cc", "src/src/cpp/server/server_callback.cc", "src/src/cpp/server/server_cc.cc", "src/src/cpp/server/server_context.cc", + "src/src/cpp/server/server_credentials.cc", "src/src/cpp/server/server_posix.cc", "src/src/cpp/server/thread_pool_interface.h", + "src/src/cpp/server/xds_server_builder.cc", "src/src/cpp/server/xds_server_credentials.cc", "src/src/cpp/thread_manager/thread_manager.cc", "src/src/cpp/thread_manager/thread_manager.h", @@ -5793,9 +7030,13 @@ static_library("grpc++") { "src/src/cpp/util/string_ref.cc", "src/src/cpp/util/time_cc.cc", ] - public_deps = [ ":grpc" ] + public_deps = [ + ":grpc", + "..:protobuf_full", + ] public_configs = [ "..:grpc_internal_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("grpc++_alts") { @@ -5806,6 +7047,7 @@ source_set("grpc++_alts") { public_deps = [ ":grpc++" ] public_configs = [ "..:grpc_internal_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("grpc++_error_details") { @@ -5813,14 +7055,19 @@ source_set("grpc++_error_details") { public_deps = [ ":grpc++" ] public_configs = [ "..:grpc_internal_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("grpc++_unsecure") { sources = [ + "src/src/cpp/client/call_credentials.cc", "src/src/cpp/client/channel_cc.cc", + "src/src/cpp/client/channel_credentials.cc", "src/src/cpp/client/client_callback.cc", "src/src/cpp/client/client_context.cc", "src/src/cpp/client/client_interceptor.cc", + "src/src/cpp/client/client_stats_interceptor.cc", + "src/src/cpp/client/client_stats_interceptor.h", "src/src/cpp/client/create_channel.cc", "src/src/cpp/client/create_channel_internal.cc", "src/src/cpp/client/create_channel_internal.h", @@ -5828,8 +7075,6 @@ source_set("grpc++_unsecure") { "src/src/cpp/client/insecure_credentials.cc", "src/src/cpp/common/alarm.cc", "src/src/cpp/common/channel_arguments.cc", - "src/src/cpp/common/channel_filter.cc", - "src/src/cpp/common/channel_filter.h", "src/src/cpp/common/completion_queue_cc.cc", "src/src/cpp/common/insecure_create_auth_context.cc", "src/src/cpp/common/resource_quota_cc.cc", @@ -5837,6 +7082,8 @@ source_set("grpc++_unsecure") { "src/src/cpp/common/validate_service_config.cc", "src/src/cpp/common/version_cc.cc", "src/src/cpp/server/async_generic_service.cc", + "src/src/cpp/server/backend_metric_recorder.cc", + "src/src/cpp/server/backend_metric_recorder.h", "src/src/cpp/server/channel_argument_option.cc", "src/src/cpp/server/create_default_thread_pool.cc", "src/src/cpp/server/dynamic_thread_pool.h", @@ -5847,11 +7094,11 @@ source_set("grpc++_unsecure") { "src/src/cpp/server/health/health_check_service.cc", "src/src/cpp/server/health/health_check_service_server_builder_option.cc", "src/src/cpp/server/insecure_server_credentials.cc", - "src/src/cpp/server/orca/call_metric_recorder.cc", "src/src/cpp/server/server_builder.cc", "src/src/cpp/server/server_callback.cc", "src/src/cpp/server/server_cc.cc", "src/src/cpp/server/server_context.cc", + "src/src/cpp/server/server_credentials.cc", "src/src/cpp/server/server_posix.cc", "src/src/cpp/server/thread_pool_interface.h", "src/src/cpp/thread_manager/thread_manager.cc", @@ -5861,30 +7108,63 @@ source_set("grpc++_unsecure") { "src/src/cpp/util/string_ref.cc", "src/src/cpp/util/time_cc.cc", ] - public_deps = [ ":grpc_unsecure" ] + public_deps = [ + ":grpc_unsecure", + "..:protobuf_full", + ] public_configs = [ "..:grpc_internal_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("grpc_authorization_provider") { sources = [ - "src/src/core/ext/filters/client_channel/lb_policy/backend_metric_data.h", - "src/src/core/ext/upb-generated/google/protobuf/any.upb.c", - "src/src/core/ext/upb-generated/google/protobuf/any.upb.h", - "src/src/core/ext/upb-generated/google/rpc/status.upb.c", - "src/src/core/ext/upb-generated/google/rpc/status.upb.h", - "src/src/core/ext/upb-generated/src/proto/grpc/gcp/altscontext.upb.c", - "src/src/core/ext/upb-generated/src/proto/grpc/gcp/altscontext.upb.h", - "src/src/core/ext/upb-generated/src/proto/grpc/gcp/handshaker.upb.c", - "src/src/core/ext/upb-generated/src/proto/grpc/gcp/handshaker.upb.h", - "src/src/core/ext/upb-generated/src/proto/grpc/gcp/transport_security_common.upb.c", - "src/src/core/ext/upb-generated/src/proto/grpc/gcp/transport_security_common.upb.h", + "src/src/core/channelz/channel_trace.cc", + "src/src/core/channelz/channel_trace.h", + "src/src/core/channelz/channelz.cc", + "src/src/core/channelz/channelz.h", + "src/src/core/channelz/channelz_registry.cc", + "src/src/core/channelz/channelz_registry.h", + "src/src/core/ext/upb-gen/google/protobuf/any.upb.h", + "src/src/core/ext/upb-gen/google/protobuf/any.upb_minitable.c", + "src/src/core/ext/upb-gen/google/protobuf/any.upb_minitable.h", + "src/src/core/ext/upb-gen/google/rpc/status.upb.h", + "src/src/core/ext/upb-gen/google/rpc/status.upb_minitable.c", + "src/src/core/ext/upb-gen/google/rpc/status.upb_minitable.h", + "src/src/core/ext/upb-gen/src/proto/grpc/gcp/altscontext.upb.h", + "src/src/core/ext/upb-gen/src/proto/grpc/gcp/altscontext.upb_minitable.c", + "src/src/core/ext/upb-gen/src/proto/grpc/gcp/altscontext.upb_minitable.h", + "src/src/core/ext/upb-gen/src/proto/grpc/gcp/handshaker.upb.h", + "src/src/core/ext/upb-gen/src/proto/grpc/gcp/handshaker.upb_minitable.c", + "src/src/core/ext/upb-gen/src/proto/grpc/gcp/handshaker.upb_minitable.h", + "src/src/core/ext/upb-gen/src/proto/grpc/gcp/transport_security_common.upb.h", + "src/src/core/ext/upb-gen/src/proto/grpc/gcp/transport_security_common.upb_minitable.c", + "src/src/core/ext/upb-gen/src/proto/grpc/gcp/transport_security_common.upb_minitable.h", + "src/src/core/handshaker/endpoint_info/endpoint_info_handshaker.cc", + "src/src/core/handshaker/endpoint_info/endpoint_info_handshaker.h", + "src/src/core/handshaker/handshaker.cc", + "src/src/core/handshaker/handshaker.h", + "src/src/core/handshaker/handshaker_factory.h", + "src/src/core/handshaker/handshaker_registry.cc", + "src/src/core/handshaker/handshaker_registry.h", + "src/src/core/handshaker/proxy_mapper.h", + "src/src/core/handshaker/proxy_mapper_registry.cc", + "src/src/core/handshaker/proxy_mapper_registry.h", + "src/src/core/handshaker/security/secure_endpoint.cc", + "src/src/core/handshaker/security/secure_endpoint.h", + "src/src/core/handshaker/security/security_handshaker.cc", + "src/src/core/handshaker/security/security_handshaker.h", + "src/src/core/handshaker/security/tsi_error.cc", + "src/src/core/handshaker/security/tsi_error.h", "src/src/core/lib/address_utils/parse_address.cc", "src/src/core/lib/address_utils/parse_address.h", "src/src/core/lib/address_utils/sockaddr_utils.cc", "src/src/core/lib/address_utils/sockaddr_utils.h", "src/src/core/lib/avl/avl.h", + "src/src/core/lib/backoff/backoff.cc", + "src/src/core/lib/backoff/backoff.h", "src/src/core/lib/channel/call_finalization.h", + "src/src/core/lib/channel/call_tracer.cc", "src/src/core/lib/channel/call_tracer.h", "src/src/core/lib/channel/channel_args.cc", "src/src/core/lib/channel/channel_args.h", @@ -5897,19 +7177,18 @@ source_set("grpc_authorization_provider") { "src/src/core/lib/channel/channel_stack_builder.h", "src/src/core/lib/channel/channel_stack_builder_impl.cc", "src/src/core/lib/channel/channel_stack_builder_impl.h", - "src/src/core/lib/channel/channel_trace.cc", - "src/src/core/lib/channel/channel_trace.h", - "src/src/core/lib/channel/channelz.cc", - "src/src/core/lib/channel/channelz.h", - "src/src/core/lib/channel/channelz_registry.cc", - "src/src/core/lib/channel/channelz_registry.h", + "src/src/core/lib/channel/channel_stack_trace.cc", + "src/src/core/lib/channel/channel_stack_trace.h", "src/src/core/lib/channel/connected_channel.cc", "src/src/core/lib/channel/connected_channel.h", "src/src/core/lib/channel/context.h", + "src/src/core/lib/channel/metrics.cc", + "src/src/core/lib/channel/metrics.h", "src/src/core/lib/channel/promise_based_filter.cc", "src/src/core/lib/channel/promise_based_filter.h", "src/src/core/lib/channel/status_util.cc", "src/src/core/lib/channel/status_util.h", + "src/src/core/lib/channel/tcp_tracer.h", "src/src/core/lib/compression/compression.cc", "src/src/core/lib/compression/compression_internal.cc", "src/src/core/lib/compression/compression_internal.h", @@ -5927,6 +7206,15 @@ source_set("grpc_authorization_provider") { "src/src/core/lib/debug/stats_data.h", "src/src/core/lib/debug/trace.cc", "src/src/core/lib/debug/trace.h", + "src/src/core/lib/event_engine/ares_resolver.cc", + "src/src/core/lib/event_engine/ares_resolver.h", + "src/src/core/lib/event_engine/cf_engine/cf_engine.cc", + "src/src/core/lib/event_engine/cf_engine/cf_engine.h", + "src/src/core/lib/event_engine/cf_engine/cfstream_endpoint.cc", + "src/src/core/lib/event_engine/cf_engine/cfstream_endpoint.h", + "src/src/core/lib/event_engine/cf_engine/cftype_unique_ref.h", + "src/src/core/lib/event_engine/cf_engine/dns_service_resolver.cc", + "src/src/core/lib/event_engine/cf_engine/dns_service_resolver.h", "src/src/core/lib/event_engine/channel_args_endpoint_config.cc", "src/src/core/lib/event_engine/channel_args_endpoint_config.h", "src/src/core/lib/event_engine/common_closures.h", @@ -5935,11 +7223,16 @@ source_set("grpc_authorization_provider") { "src/src/core/lib/event_engine/default_event_engine_factory.cc", "src/src/core/lib/event_engine/default_event_engine_factory.h", "src/src/core/lib/event_engine/event_engine.cc", - "src/src/core/lib/event_engine/executor/executor.h", + "src/src/core/lib/event_engine/event_engine_context.h", + "src/src/core/lib/event_engine/extensions/can_track_errors.h", + "src/src/core/lib/event_engine/extensions/chaotic_good_extension.h", + "src/src/core/lib/event_engine/extensions/supports_fd.h", "src/src/core/lib/event_engine/forkable.cc", "src/src/core/lib/event_engine/forkable.h", + "src/src/core/lib/event_engine/grpc_polled_fd.h", "src/src/core/lib/event_engine/handle_containers.h", - "src/src/core/lib/event_engine/memory_allocator.cc", + "src/src/core/lib/event_engine/memory_allocator_factory.h", + "src/src/core/lib/event_engine/nameser.h", "src/src/core/lib/event_engine/poller.h", "src/src/core/lib/event_engine/posix.h", "src/src/core/lib/event_engine/posix_engine/ev_epoll1_linux.cc", @@ -5949,10 +7242,13 @@ source_set("grpc_authorization_provider") { "src/src/core/lib/event_engine/posix_engine/event_poller.h", "src/src/core/lib/event_engine/posix_engine/event_poller_posix_default.cc", "src/src/core/lib/event_engine/posix_engine/event_poller_posix_default.h", + "src/src/core/lib/event_engine/posix_engine/grpc_polled_fd_posix.h", "src/src/core/lib/event_engine/posix_engine/internal_errqueue.cc", "src/src/core/lib/event_engine/posix_engine/internal_errqueue.h", "src/src/core/lib/event_engine/posix_engine/lockfree_event.cc", "src/src/core/lib/event_engine/posix_engine/lockfree_event.h", + "src/src/core/lib/event_engine/posix_engine/native_posix_dns_resolver.cc", + "src/src/core/lib/event_engine/posix_engine/native_posix_dns_resolver.h", "src/src/core/lib/event_engine/posix_engine/posix_endpoint.cc", "src/src/core/lib/event_engine/posix_engine/posix_endpoint.h", "src/src/core/lib/event_engine/posix_engine/posix_engine.cc", @@ -5979,6 +7275,8 @@ source_set("grpc_authorization_provider") { "src/src/core/lib/event_engine/posix_engine/wakeup_fd_posix.h", "src/src/core/lib/event_engine/posix_engine/wakeup_fd_posix_default.cc", "src/src/core/lib/event_engine/posix_engine/wakeup_fd_posix_default.h", + "src/src/core/lib/event_engine/query_extensions.h", + "src/src/core/lib/event_engine/ref_counted_dns_resolver_interface.h", "src/src/core/lib/event_engine/resolved_address.cc", "src/src/core/lib/event_engine/resolved_address_internal.h", "src/src/core/lib/event_engine/shim.cc", @@ -5987,22 +7285,37 @@ source_set("grpc_authorization_provider") { "src/src/core/lib/event_engine/slice_buffer.cc", "src/src/core/lib/event_engine/tcp_socket_utils.cc", "src/src/core/lib/event_engine/tcp_socket_utils.h", - "src/src/core/lib/event_engine/thread_pool.cc", - "src/src/core/lib/event_engine/thread_pool.h", + "src/src/core/lib/event_engine/thread_pool/thread_count.cc", + "src/src/core/lib/event_engine/thread_pool/thread_count.h", + "src/src/core/lib/event_engine/thread_pool/thread_pool.h", + "src/src/core/lib/event_engine/thread_pool/thread_pool_factory.cc", + "src/src/core/lib/event_engine/thread_pool/work_stealing_thread_pool.cc", + "src/src/core/lib/event_engine/thread_pool/work_stealing_thread_pool.h", + "src/src/core/lib/event_engine/thready_event_engine/thready_event_engine.cc", + "src/src/core/lib/event_engine/thready_event_engine/thready_event_engine.h", "src/src/core/lib/event_engine/time_util.cc", "src/src/core/lib/event_engine/time_util.h", "src/src/core/lib/event_engine/trace.cc", "src/src/core/lib/event_engine/trace.h", "src/src/core/lib/event_engine/utils.cc", "src/src/core/lib/event_engine/utils.h", + "src/src/core/lib/event_engine/windows/grpc_polled_fd_windows.cc", + "src/src/core/lib/event_engine/windows/grpc_polled_fd_windows.h", "src/src/core/lib/event_engine/windows/iocp.cc", "src/src/core/lib/event_engine/windows/iocp.h", + "src/src/core/lib/event_engine/windows/native_windows_dns_resolver.cc", + "src/src/core/lib/event_engine/windows/native_windows_dns_resolver.h", "src/src/core/lib/event_engine/windows/win_socket.cc", "src/src/core/lib/event_engine/windows/win_socket.h", "src/src/core/lib/event_engine/windows/windows_endpoint.cc", "src/src/core/lib/event_engine/windows/windows_endpoint.h", "src/src/core/lib/event_engine/windows/windows_engine.cc", "src/src/core/lib/event_engine/windows/windows_engine.h", + "src/src/core/lib/event_engine/windows/windows_listener.cc", + "src/src/core/lib/event_engine/windows/windows_listener.h", + "src/src/core/lib/event_engine/work_queue/basic_work_queue.cc", + "src/src/core/lib/event_engine/work_queue/basic_work_queue.h", + "src/src/core/lib/event_engine/work_queue/work_queue.h", "src/src/core/lib/experiments/config.cc", "src/src/core/lib/experiments/config.h", "src/src/core/lib/experiments/experiments.cc", @@ -6012,7 +7325,9 @@ source_set("grpc_authorization_provider") { "src/src/core/lib/gprpp/bitset.h", "src/src/core/lib/gprpp/chunked_vector.h", "src/src/core/lib/gprpp/cpp_impl_of.h", + "src/src/core/lib/gprpp/down_cast.h", "src/src/core/lib/gprpp/dual_ref_counted.h", + "src/src/core/lib/gprpp/if_list.h", "src/src/core/lib/gprpp/load_file.cc", "src/src/core/lib/gprpp/load_file.h", "src/src/core/lib/gprpp/manual_constructor.h", @@ -6021,9 +7336,12 @@ source_set("grpc_authorization_provider") { "src/src/core/lib/gprpp/orphanable.h", "src/src/core/lib/gprpp/overload.h", "src/src/core/lib/gprpp/packed_table.h", + "src/src/core/lib/gprpp/per_cpu.cc", "src/src/core/lib/gprpp/per_cpu.h", "src/src/core/lib/gprpp/ref_counted.h", "src/src/core/lib/gprpp/ref_counted_ptr.h", + "src/src/core/lib/gprpp/ref_counted_string.cc", + "src/src/core/lib/gprpp/ref_counted_string.h", "src/src/core/lib/gprpp/sorted_pack.h", "src/src/core/lib/gprpp/status_helper.cc", "src/src/core/lib/gprpp/status_helper.h", @@ -6032,14 +7350,12 @@ source_set("grpc_authorization_provider") { "src/src/core/lib/gprpp/time.h", "src/src/core/lib/gprpp/time_averaged_stats.cc", "src/src/core/lib/gprpp/time_averaged_stats.h", + "src/src/core/lib/gprpp/type_list.h", "src/src/core/lib/gprpp/unique_type_name.h", "src/src/core/lib/gprpp/validation_errors.cc", "src/src/core/lib/gprpp/validation_errors.h", "src/src/core/lib/gprpp/work_serializer.cc", "src/src/core/lib/gprpp/work_serializer.h", - "src/src/core/lib/handshaker/proxy_mapper.h", - "src/src/core/lib/handshaker/proxy_mapper_registry.cc", - "src/src/core/lib/handshaker/proxy_mapper_registry.h", "src/src/core/lib/iomgr/block_annotate.h", "src/src/core/lib/iomgr/buffer_list.cc", "src/src/core/lib/iomgr/buffer_list.h", @@ -6104,8 +7420,6 @@ source_set("grpc_authorization_provider") { "src/src/core/lib/iomgr/iomgr_posix.cc", "src/src/core/lib/iomgr/iomgr_posix_cfstream.cc", "src/src/core/lib/iomgr/iomgr_windows.cc", - "src/src/core/lib/iomgr/load_file.cc", - "src/src/core/lib/iomgr/load_file.h", "src/src/core/lib/iomgr/lockfree_event.cc", "src/src/core/lib/iomgr/lockfree_event.h", "src/src/core/lib/iomgr/nameser.h", @@ -6176,6 +7490,8 @@ source_set("grpc_authorization_provider") { "src/src/core/lib/iomgr/unix_sockets_posix.cc", "src/src/core/lib/iomgr/unix_sockets_posix.h", "src/src/core/lib/iomgr/unix_sockets_posix_noop.cc", + "src/src/core/lib/iomgr/vsock.cc", + "src/src/core/lib/iomgr/vsock.h", "src/src/core/lib/iomgr/wakeup_fd_eventfd.cc", "src/src/core/lib/iomgr/wakeup_fd_nospecial.cc", "src/src/core/lib/iomgr/wakeup_fd_pipe.cc", @@ -6183,52 +7499,48 @@ source_set("grpc_authorization_provider") { "src/src/core/lib/iomgr/wakeup_fd_posix.cc", "src/src/core/lib/iomgr/wakeup_fd_posix.h", "src/src/core/lib/json/json.h", + "src/src/core/lib/json/json_args.h", "src/src/core/lib/json/json_reader.cc", + "src/src/core/lib/json/json_reader.h", "src/src/core/lib/json/json_writer.cc", - "src/src/core/lib/load_balancing/lb_policy.cc", - "src/src/core/lib/load_balancing/lb_policy.h", - "src/src/core/lib/load_balancing/lb_policy_factory.h", - "src/src/core/lib/load_balancing/lb_policy_registry.cc", - "src/src/core/lib/load_balancing/lb_policy_registry.h", - "src/src/core/lib/load_balancing/subchannel_interface.h", + "src/src/core/lib/json/json_writer.h", "src/src/core/lib/matchers/matchers.cc", "src/src/core/lib/matchers/matchers.h", "src/src/core/lib/promise/activity.cc", "src/src/core/lib/promise/activity.h", + "src/src/core/lib/promise/all_ok.h", "src/src/core/lib/promise/arena_promise.h", "src/src/core/lib/promise/context.h", - "src/src/core/lib/promise/detail/basic_join.h", "src/src/core/lib/promise/detail/basic_seq.h", + "src/src/core/lib/promise/detail/join_state.h", "src/src/core/lib/promise/detail/promise_factory.h", "src/src/core/lib/promise/detail/promise_like.h", + "src/src/core/lib/promise/detail/seq_state.h", "src/src/core/lib/promise/detail/status.h", - "src/src/core/lib/promise/detail/switch.h", "src/src/core/lib/promise/exec_ctx_wakeup_scheduler.h", + "src/src/core/lib/promise/for_each.h", "src/src/core/lib/promise/if.h", "src/src/core/lib/promise/interceptor_list.h", - "src/src/core/lib/promise/intra_activity_waiter.h", + "src/src/core/lib/promise/latch.h", "src/src/core/lib/promise/loop.h", "src/src/core/lib/promise/map.h", + "src/src/core/lib/promise/party.cc", + "src/src/core/lib/promise/party.h", "src/src/core/lib/promise/pipe.h", "src/src/core/lib/promise/poll.h", "src/src/core/lib/promise/promise.h", "src/src/core/lib/promise/race.h", "src/src/core/lib/promise/seq.h", + "src/src/core/lib/promise/status_flag.h", "src/src/core/lib/promise/trace.cc", "src/src/core/lib/promise/trace.h", - "src/src/core/lib/promise/try_join.h", "src/src/core/lib/promise/try_seq.h", - "src/src/core/lib/resolver/resolver.cc", - "src/src/core/lib/resolver/resolver.h", - "src/src/core/lib/resolver/resolver_factory.h", - "src/src/core/lib/resolver/resolver_registry.cc", - "src/src/core/lib/resolver/resolver_registry.h", - "src/src/core/lib/resolver/server_address.cc", - "src/src/core/lib/resolver/server_address.h", "src/src/core/lib/resource_quota/api.cc", "src/src/core/lib/resource_quota/api.h", "src/src/core/lib/resource_quota/arena.cc", "src/src/core/lib/resource_quota/arena.h", + "src/src/core/lib/resource_quota/connection_quota.cc", + "src/src/core/lib/resource_quota/connection_quota.h", "src/src/core/lib/resource_quota/memory_quota.cc", "src/src/core/lib/resource_quota/memory_quota.h", "src/src/core/lib/resource_quota/periodic_update.cc", @@ -6239,6 +7551,8 @@ source_set("grpc_authorization_provider") { "src/src/core/lib/resource_quota/thread_quota.h", "src/src/core/lib/resource_quota/trace.cc", "src/src/core/lib/resource_quota/trace.h", + "src/src/core/lib/security/authorization/audit_logging.cc", + "src/src/core/lib/security/authorization/audit_logging.h", "src/src/core/lib/security/authorization/authorization_engine.h", "src/src/core/lib/security/authorization/authorization_policy_provider.h", "src/src/core/lib/security/authorization/authorization_policy_provider_vtable.cc", @@ -6256,6 +7570,8 @@ source_set("grpc_authorization_provider") { "src/src/core/lib/security/authorization/rbac_policy.h", "src/src/core/lib/security/authorization/rbac_translator.cc", "src/src/core/lib/security/authorization/rbac_translator.h", + "src/src/core/lib/security/authorization/stdout_logger.cc", + "src/src/core/lib/security/authorization/stdout_logger.h", "src/src/core/lib/security/certificate_provider/certificate_provider_factory.h", "src/src/core/lib/security/certificate_provider/certificate_provider_registry.cc", "src/src/core/lib/security/certificate_provider/certificate_provider_registry.h", @@ -6285,25 +7601,14 @@ source_set("grpc_authorization_provider") { "src/src/core/lib/security/security_connector/load_system_roots_fallback.cc", "src/src/core/lib/security/security_connector/load_system_roots_supported.cc", "src/src/core/lib/security/security_connector/load_system_roots_supported.h", + "src/src/core/lib/security/security_connector/load_system_roots_windows.cc", "src/src/core/lib/security/security_connector/security_connector.cc", "src/src/core/lib/security/security_connector/security_connector.h", "src/src/core/lib/security/transport/auth_filters.h", "src/src/core/lib/security/transport/client_auth_filter.cc", - "src/src/core/lib/security/transport/secure_endpoint.cc", - "src/src/core/lib/security/transport/secure_endpoint.h", - "src/src/core/lib/security/transport/security_handshaker.cc", - "src/src/core/lib/security/transport/security_handshaker.h", "src/src/core/lib/security/transport/server_auth_filter.cc", - "src/src/core/lib/security/transport/tsi_error.cc", - "src/src/core/lib/security/transport/tsi_error.h", "src/src/core/lib/security/util/json_util.cc", "src/src/core/lib/security/util/json_util.h", - "src/src/core/lib/service_config/service_config.h", - "src/src/core/lib/service_config/service_config_call_data.h", - "src/src/core/lib/service_config/service_config_parser.cc", - "src/src/core/lib/service_config/service_config_parser.h", - "src/src/core/lib/slice/b64.cc", - "src/src/core/lib/slice/b64.h", "src/src/core/lib/slice/percent_encoding.cc", "src/src/core/lib/slice/percent_encoding.h", "src/src/core/lib/slice/slice.cc", @@ -6317,8 +7622,6 @@ source_set("grpc_authorization_provider") { "src/src/core/lib/slice/slice_string_helpers.h", "src/src/core/lib/surface/api_trace.cc", "src/src/core/lib/surface/api_trace.h", - "src/src/core/lib/surface/builtins.cc", - "src/src/core/lib/surface/builtins.h", "src/src/core/lib/surface/byte_buffer.cc", "src/src/core/lib/surface/byte_buffer_reader.cc", "src/src/core/lib/surface/call.cc", @@ -6326,13 +7629,11 @@ source_set("grpc_authorization_provider") { "src/src/core/lib/surface/call_details.cc", "src/src/core/lib/surface/call_log_batch.cc", "src/src/core/lib/surface/call_test_only.h", - "src/src/core/lib/surface/call_trace.cc", "src/src/core/lib/surface/call_trace.h", "src/src/core/lib/surface/channel.cc", "src/src/core/lib/surface/channel.h", "src/src/core/lib/surface/channel_init.cc", "src/src/core/lib/surface/channel_init.h", - "src/src/core/lib/surface/channel_ping.cc", "src/src/core/lib/surface/channel_stack_type.cc", "src/src/core/lib/surface/channel_stack_type.h", "src/src/core/lib/surface/completion_queue.cc", @@ -6347,25 +7648,37 @@ source_set("grpc_authorization_provider") { "src/src/core/lib/surface/lame_client.cc", "src/src/core/lib/surface/lame_client.h", "src/src/core/lib/surface/metadata_array.cc", - "src/src/core/lib/surface/server.cc", - "src/src/core/lib/surface/server.h", "src/src/core/lib/surface/validate_metadata.cc", "src/src/core/lib/surface/validate_metadata.h", "src/src/core/lib/surface/version.cc", + "src/src/core/lib/surface/wait_for_cq_end_op.cc", + "src/src/core/lib/surface/wait_for_cq_end_op.h", + "src/src/core/lib/transport/batch_builder.cc", + "src/src/core/lib/transport/batch_builder.h", + "src/src/core/lib/transport/call_arena_allocator.cc", + "src/src/core/lib/transport/call_arena_allocator.h", + "src/src/core/lib/transport/call_filters.cc", + "src/src/core/lib/transport/call_filters.h", + "src/src/core/lib/transport/call_final_info.cc", + "src/src/core/lib/transport/call_final_info.h", + "src/src/core/lib/transport/call_spine.cc", + "src/src/core/lib/transport/call_spine.h", "src/src/core/lib/transport/connectivity_state.cc", "src/src/core/lib/transport/connectivity_state.h", + "src/src/core/lib/transport/custom_metadata.h", "src/src/core/lib/transport/error_utils.cc", "src/src/core/lib/transport/error_utils.h", - "src/src/core/lib/transport/handshaker.cc", - "src/src/core/lib/transport/handshaker.h", - "src/src/core/lib/transport/handshaker_factory.h", - "src/src/core/lib/transport/handshaker_registry.cc", - "src/src/core/lib/transport/handshaker_registry.h", "src/src/core/lib/transport/http2_errors.h", + "src/src/core/lib/transport/message.cc", + "src/src/core/lib/transport/message.h", + "src/src/core/lib/transport/metadata.cc", + "src/src/core/lib/transport/metadata.h", "src/src/core/lib/transport/metadata_batch.cc", "src/src/core/lib/transport/metadata_batch.h", + "src/src/core/lib/transport/metadata_compression_traits.h", "src/src/core/lib/transport/parsed_metadata.cc", "src/src/core/lib/transport/parsed_metadata.h", + "src/src/core/lib/transport/simple_slice_based_metadata.h", "src/src/core/lib/transport/status_conversion.cc", "src/src/core/lib/transport/status_conversion.h", "src/src/core/lib/transport/timeout_encoding.cc", @@ -6373,10 +7686,29 @@ source_set("grpc_authorization_provider") { "src/src/core/lib/transport/transport.cc", "src/src/core/lib/transport/transport.h", "src/src/core/lib/transport/transport_fwd.h", - "src/src/core/lib/transport/transport_impl.h", "src/src/core/lib/transport/transport_op_string.cc", "src/src/core/lib/uri/uri_parser.cc", "src/src/core/lib/uri/uri_parser.h", + "src/src/core/load_balancing/backend_metric_data.h", + "src/src/core/load_balancing/lb_policy.cc", + "src/src/core/load_balancing/lb_policy.h", + "src/src/core/load_balancing/lb_policy_factory.h", + "src/src/core/load_balancing/lb_policy_registry.cc", + "src/src/core/load_balancing/lb_policy_registry.h", + "src/src/core/load_balancing/subchannel_interface.h", + "src/src/core/resolver/endpoint_addresses.cc", + "src/src/core/resolver/endpoint_addresses.h", + "src/src/core/resolver/resolver.cc", + "src/src/core/resolver/resolver.h", + "src/src/core/resolver/resolver_factory.h", + "src/src/core/resolver/resolver_registry.cc", + "src/src/core/resolver/resolver_registry.h", + "src/src/core/resolver/server_address.h", + "src/src/core/server/server_interface.h", + "src/src/core/service_config/service_config.h", + "src/src/core/service_config/service_config_call_data.h", + "src/src/core/service_config/service_config_parser.cc", + "src/src/core/service_config/service_config_parser.h", "src/src/core/tsi/alts/handshaker/transport_security_common_api.cc", "src/src/core/tsi/alts/handshaker/transport_security_common_api.h", "src/src/core/tsi/transport_security.cc", @@ -6384,25 +7716,59 @@ source_set("grpc_authorization_provider") { "src/src/core/tsi/transport_security_grpc.cc", "src/src/core/tsi/transport_security_grpc.h", "src/src/core/tsi/transport_security_interface.h", + "src/third_party/upb/upb/generated_code_support.h", + "src/third_party/upb/upb/mini_descriptor/build_enum.c", + "src/third_party/upb/upb/mini_descriptor/build_enum.h", + "src/third_party/upb/upb/mini_descriptor/decode.c", + "src/third_party/upb/upb/mini_descriptor/decode.h", + "src/third_party/upb/upb/mini_descriptor/internal/base92.c", + "src/third_party/upb/upb/mini_descriptor/internal/base92.h", + "src/third_party/upb/upb/mini_descriptor/internal/decoder.h", + "src/third_party/upb/upb/mini_descriptor/internal/encode.c", + "src/third_party/upb/upb/mini_descriptor/internal/encode.h", + "src/third_party/upb/upb/mini_descriptor/internal/encode.hpp", + "src/third_party/upb/upb/mini_descriptor/internal/modifiers.h", + "src/third_party/upb/upb/mini_descriptor/internal/wire_constants.h", + "src/third_party/upb/upb/mini_descriptor/link.c", + "src/third_party/upb/upb/mini_descriptor/link.h", + "src/third_party/upb/upb/wire/decode.c", + "src/third_party/upb/upb/wire/decode.h", + "src/third_party/upb/upb/wire/encode.c", + "src/third_party/upb/upb/wire/encode.h", + "src/third_party/upb/upb/wire/eps_copy_input_stream.c", + "src/third_party/upb/upb/wire/eps_copy_input_stream.h", + "src/third_party/upb/upb/wire/internal/constants.h", + "src/third_party/upb/upb/wire/internal/decode_fast.c", + "src/third_party/upb/upb/wire/internal/decode_fast.h", + "src/third_party/upb/upb/wire/internal/decoder.h", + "src/third_party/upb/upb/wire/internal/reader.h", + "src/third_party/upb/upb/wire/reader.c", + "src/third_party/upb/upb/wire/reader.h", + "src/third_party/upb/upb/wire/types.h", ] public_deps = [ + ":absl_base_config", + ":absl_base_no_destructor", ":absl_cleanup_cleanup", ":absl_container_flat_hash_map", ":absl_container_flat_hash_set", ":absl_container_inlined_vector", - ":absl_functional_any_invocable", ":absl_functional_function_ref", ":absl_hash_hash", ":absl_meta_type_traits", ":absl_status_statusor", ":absl_types_span", ":absl_utility_utility", + ":address_sorting", ":gpr", ":re2", - ":upb", + ":upb_message_lib", + ":utf8_range_lib", + "..:zlib", ] public_configs = [ "..:grpc_internal_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } source_set("grpc_plugin_support") { @@ -6426,6 +7792,8 @@ source_set("grpc_plugin_support") { "src/src/compiler/php_generator.cc", "src/src/compiler/php_generator.h", "src/src/compiler/php_generator_helpers.h", + "src/src/compiler/proto_parser_helper.cc", + "src/src/compiler/proto_parser_helper.h", "src/src/compiler/protobuf_plugin.h", "src/src/compiler/python_generator.cc", "src/src/compiler/python_generator.h", @@ -6438,9 +7806,13 @@ source_set("grpc_plugin_support") { "src/src/compiler/ruby_generator_string-inl.h", "src/src/compiler/schema_interface.h", ] - public_deps = [ "..:protoc_lib" ] + public_deps = [ + "..:protobuf_full", + "..:protoc_lib", + ] public_configs = [ "..:grpc_internal_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } executable("grpc_cpp_plugin") { @@ -6448,4 +7820,5 @@ executable("grpc_cpp_plugin") { public_deps = [ ":grpc_plugin_support" ] public_configs = [ "..:grpc_internal_config" ] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = true } diff --git a/buildtools/grpc/protobuf_include/google/protobuf/compiler/csharp/names.h b/buildtools/grpc/protobuf_include/google/protobuf/compiler/csharp/names.h new file mode 100644 index 0000000000..6412317f98 --- /dev/null +++ b/buildtools/grpc/protobuf_include/google/protobuf/compiler/csharp/names.h @@ -0,0 +1,22 @@ +/* + * 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 PROTOBUF_INCLUDE_GOOGLE_PROTOBUF_COMPILER_CSHARP_NAMES_H__ +#define PROTOBUF_INCLUDE_GOOGLE_PROTOBUF_COMPILER_CSHARP_NAMES_H__ + +#include + +#endif // PROTOBUF_INCLUDE_GOOGLE_PROTOBUF_COMPILER_CSHARP_NAMES_H__ diff --git a/buildtools/grpc/protobuf_include/google/protobuf/compiler/objectivec/names.h b/buildtools/grpc/protobuf_include/google/protobuf/compiler/objectivec/names.h new file mode 100644 index 0000000000..db49c6cb10 --- /dev/null +++ b/buildtools/grpc/protobuf_include/google/protobuf/compiler/objectivec/names.h @@ -0,0 +1,22 @@ +/* + * 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 GOOGLE_PROTOBUF_COMPILER_OBJECTIVEC_NAMES_H__ +#define GOOGLE_PROTOBUF_COMPILER_OBJECTIVEC_NAMES_H__ + +#include + +#endif // GOOGLE_PROTOBUF_COMPILER_OBJECTIVEC_NAMES_H__ diff --git a/docs/analysis/perfetto-sql-syntax.md b/docs/analysis/perfetto-sql-syntax.md index cb9418a563..e6ef4f03da 100644 --- a/docs/analysis/perfetto-sql-syntax.md +++ b/docs/analysis/perfetto-sql-syntax.md @@ -125,6 +125,60 @@ CREATE PERFETTO TABLE foo(x INT, y STRING) AS SELECT 1 as x, 'test' as y ``` +### Index + +`CREATE PERFETTO INDEX` lets you create indexes on Perfetto tables, similar to +how you create indexes in SQLite databases. These indexes are built on specific +columns, and Perfetto internally maintains these columns in a sorted order. +This means operations benefiting from sorting on an indexed column (or group of +columns) will be significantly faster, as if you were operating on a column +that's already sorted. + +NOTE: Indexes have non-trivial memory cost, so it's important to only use them +when there is a need for performance improvement. + +NOTE: Indexes will be used by views created on the indexed table, but they will +not be inherited by any child tables, as shown in the below SQL. + +NOTE: If the query filters/joins on `id` column of the table (one that is a +primary key of the table) there is no need to add a Perfetto index, as Perfetto +tables already have special performance optimizations for operations that can +benefit from sorting. + +Example of usage: +```sql +CREATE PERFETTO TABLE foo AS +SELECT * FROM slice; + +-- Creates and stores an index `foo_track` on column `track_id` of table foo. +CREATE PERFETTO INDEX foo_track ON foo(track_id); +-- Creates or replaces an index created on two columns. It will be used for +-- operations on `track_id` and can be used on operations on `name` only if +-- there has been an equality constraint on `track_id` too. +CREATE OR REPLACE PERFETTO INDEX foo_track_and_name ON foo(track_id, name); +``` + +The performance of those two queries should be very different now: +```sql +-- This doesn't have an index so it will have to linearily scan whole column. +SELECT * FROM slice WHERE track_id = 10 AND name > "b"; + +-- This has an index and can use binary search. +SELECT * FROM foo WHERE track_id = 10 AND name > "b"; + +-- The biggest difference should be noticeable on joins: +-- This join: +SELECT * FROM slice JOIN track WHERE slice.track_id = track.id; +-- will be noticeably slower than this: +SELECT * FROM foo JOIN track WHERE slice.track_id = track.id; +``` + +Indexes can be dropped: +```sql +DROP PERFETTO INDEX foo_track ON foo; +``` + + ## Creating views with a schema Views can be created via `CREATE PERFETTO VIEW`, taking an optional schema. diff --git a/docs/case-studies/memory.md b/docs/case-studies/memory.md index 93f67a21db..a9508be492 100644 --- a/docs/case-studies/memory.md +++ b/docs/case-studies/memory.md @@ -407,15 +407,17 @@ diamond marker that shows. ![Profile Diamond](/docs/images/profile-diamond.png) -This will present a flamegraph of the memory attributed to the shortest path -to a garbage-collection root. In general an object is reachable by many paths, -we only show the shortest as that reduces the complexity of the data displayed -and is generally the highest-signal. The rightmost `[merged]` stacks is the -sum of all objects that are too small to be displayed. +This will present a set of flamegraph views as explained below. -![Java Flamegraph](/docs/images/java-heap-graph.png) +#### "Size" and "Objects" tabs -The tabs that are available are +![Java Flamegraph: Size](/docs/images/java-heap-graph.png) + +These views show the memory attributed to the shortest path to a +garbage-collection root. In general an object is reachable by many paths, we +only show the shortest as that reduces the complexity of the data displayed and +is generally the highest-signal. The rightmost `[merged]` stacks is the sum of +all objects that are too small to be displayed. * **Size**: how many bytes are retained via this path to the GC root. * **Objects**: how many objects are retained via this path to the GC root. @@ -432,4 +434,26 @@ that could be caused by notifications, we can put "notification" in the Focus bo We aggregate the paths per class name, so if there are multiple objects of the same type retained by a `java.lang.Object[]`, we will show one element as its -child, as you can see in the leftmost stack above. +child, as you can see in the leftmost stack above. This also applies to the +dominator tree paths as described below. + +#### "Dominated Size" and "Dominated Objects" tabs + +![Java Flamegraph: Dominated Size](/docs/images/java-heap-graph-dominated-size.png) + +Another way to present the heap graph as a flamegraph (a tree) is to show its +[dominator tree](/docs/analysis/stdlib-docs.autogen#memory-heap_graph_dominator_tree). +In a heap graph, an object `a` dominates an object `b` if `b` is reachable from +the root only via paths that go through `a`. The dominators of an object form a +chain from the root and the object is exclusvely retained by all objects on this +chain. For all reachable objects in the graph those chains form a tree, i.e. the +dominator tree. + +We aggregate the tree paths per class name, and each element (tree node) +represents a set of objects that have the same class name and position in the +dominator tree. + +* **Dominated Size**: how many bytes are exclusively retained by the objects in +a node. +* **Dominated Objects**: how many objects are exclusively retained by the +objects in a node. diff --git a/docs/contributing/build-instructions.md b/docs/contributing/build-instructions.md index 85025d6b3e..9b37df44ba 100644 --- a/docs/contributing/build-instructions.md +++ b/docs/contributing/build-instructions.md @@ -169,18 +169,14 @@ used in conjunction with `--no-build`, and on its own as well. We use `eslint` to lint TypeScript and JavaScript, and `prettier` to format TypeScript, JavaScript, and SCSS. -Eslint has a `--fix` option which can auto-fix a lot of issues. +To auto-format all source files, run ui/format-sources, which takes care of +running both prettier and eslint on the changed files: ```bash -ui/eslint-all --fix # Fix all files -# -- or -- -ui/eslint-all # Just report issues -``` - -To auto-format all source files, run: - -```bash -ui/prettier-all +# By default it formats only files that changed from the upstream Git branch +# (typicaly origin/main). +# Pass --all for formatting all files under ui/src +ui/format-sources ``` For VSCode users, we recommend using the eslint & prettier extensions to handle diff --git a/docs/contributing/getting-started.md b/docs/contributing/getting-started.md index bbac4c298f..bc6581676b 100644 --- a/docs/contributing/getting-started.md +++ b/docs/contributing/getting-started.md @@ -43,22 +43,25 @@ tools/ninja -C out/mac_debug ### Contributing 1. Create an account at [android.googlesource.com](https://android.googlesource.com/). -2. Download `depot_tools`, a collection of helper scripts which make uploading changes to Android gerrit easier. +2. (if you are a Googler) Follow go/sync#get-credentials to allow uploading to +Android Gerrit. +3. Download `depot_tools`, a collection of helper scripts which make uploading changes +to Android Gerrit easier. ```sh cd perfetto git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git ``` -3. Add `depot_tools` to your path: +4. Add `depot_tools` to your PATH (you may want to add this to your .bashrc/.zshrc): ```sh depot_path="$(realpath depot_tools)" export PATH=$depot_path:$PATH ``` -4. Create a branch with the change: +5. Create a branch with the change: ```sh git new-branch first-contribution ``` -5. Make change in the repo. -5. Add and commit the change: +6. Make change in the repo. +7. Add, commit and upload the change: ```sh git add . git commit -m "My first contribution" diff --git a/docs/data-sources/native-heap-profiler.md b/docs/data-sources/native-heap-profiler.md index d3071e1bad..e130ebaa8a 100644 --- a/docs/data-sources/native-heap-profiler.md +++ b/docs/data-sources/native-heap-profiler.md @@ -372,11 +372,14 @@ provide a deobfuscation map to turn them back into human readable. To do so, use the `PERFETTO_PROGUARD_MAP` environment variable, using the format `packagename=map_filename[:packagename=map_filename...]`, e.g. `PERFETTO_PROGUARD_MAP=com.example.pkg1=foo.txt:com.example.pkg2=bar.txt`. -All tools -(traceconv, trace_processor_shell, the heap_profile script) support specifying -the `PERFETTO_PROGUARD_MAP` as an environment variable. +All tools (traceconv, trace_processor_shell, the heap_profile script) support +specifying the `PERFETTO_PROGUARD_MAP` as an environment variable. + +``` +PERFETTO_PROGUARD_MAP=com.example.pkg1=proguard_map1.txt:com.example.pkg2=proguard_map2.txt ./tools/heap_profile -n com.example.app +``` -You can get a deobfuscation map for your trace using +You can get a deobfuscation map for the trace you already collected using `tools/traceconv deobfuscate`. Then concatenate the resulting file to your trace to get a deobfuscated version of it (the input trace should be in the perfetto format, otherwise concatenation will not produce a reasonable output). diff --git a/docs/design-docs/continuous-integration.md b/docs/design-docs/continuous-integration.md index aa9ca370a3..0d23137d1c 100644 --- a/docs/design-docs/continuous-integration.md +++ b/docs/design-docs/continuous-integration.md @@ -33,7 +33,7 @@ It uses a non-privileged gmail account and has no meaningful voting power. The controller loop does mainly the following: -- It periodically (every 5s) polls Gerrit for CLs updated in the last 24h. +- It periodically (every 15s) polls Gerrit for CLs updated in the last 24h. - It checks the list of CLs against the list of already known CLs in the DB. - For each new CL it enqueues `N` new jobs in the database, one for each configuration defined in [config.py](/infra/ci/config.py) (e.g. `linux-debug`, @@ -51,7 +51,7 @@ The frontend is an AppEngine service that hosts the CI website @ Conversely to the Controller, it is exposed to the public via HTTP. - It's an almost fully static website based on HTML and Javascript. -- The only backend-side code ([frontend.py](/infra/ci/frontend/frontend.py)) +- The only backend-side code ([frontend.py](/infra/ci/frontend/main.py)) is used to proxy XHR GET requests to Gerrit, due to the lack of Gerrit CORS headers. - Such XHR requests are GET-only and anonymous. @@ -304,8 +304,6 @@ but this involves: - Manually stopping the production AppEngine instance via the Cloud Console (stopping via the `gcloud` cli doesn't seem to work, b/136828660) -- Downloading the testing service credentials `test-credentials.json` - (they are in the internal Team drive). ### Worker/Sandbox changes diff --git a/docs/images/java-heap-graph-dominated-size.png b/docs/images/java-heap-graph-dominated-size.png new file mode 100644 index 0000000000000000000000000000000000000000..94b818693ef9db8d91e04bd7e137870b0708043b GIT binary patch literal 119910 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%0De!;bW5~UqYKIdF`RF1>fr_4(5u>YRk`hTA#tDr& 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%0Den>397^1y7@|aL4?abbUV86f(P1XZG~_S?k4K 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%0Ag#<3;~s0YqIDKP55Gc zsdl)Lh5z@^oc=F|cD`9Rvd?}@8vkB}C=qK^kHvcWtke=B5%hhj?)!^dpnUk}@^9+= z_g6a?@&opNII2k)s%uO0Z-E3A8Ik6d`4}c?*pRB-(BoVqgi|J^x1znwka`)4<E$)xxf8P=`Ty{iTZJzqm#&)4Wmf zGuX=&XN-U5p@e}c%9QKddo?`c&CV5XXY?A9=#Aj24CB%thrrQx=np7@4G=rPT6s+2%h%s_nNiak`xH+AYWzG+qJWFKE_|O%Cik6Zq4pqp1K~Q z#ZH$rKCjmsdpwl-y4IfY{D)(tE;}su~BI7$vu(cm~uPP)P zMzsIHog>L#eR&x!EiqK`Yw2Ri14HqxrlIX@vk z_JY2kw1xkm9m3;D{W0iy8ay zA>p}vMH+vWM@7sW`IaLvJ{^18eDLSin_dAKh^HRZ^vb(nn)6Z!AN$!7GCCH`Z>{#q z+cC+p{>Svz#3REJIo0X!F1|O_o_wJFFnB-uEKu$$K_ju9pO`VNt)PJ!c8x9M&~Dc1 zmK${-S!{y3#jR>UtdfGi3e3;cPi{CJKNz7c;a3vmg)}$h{Nnp)_zBfqjxGp;}VmC@yg?zb;jzGK781iLH~e4u2XbDm;FNl zMe;)*G0w2=$i$)uxi0Eg>Q5c&H*IbTbxn{8S^Zv>hmz%m_GiuF72RBl@zmP|*yw*= zxGp+E1e`NyIYd{hx8q`bqzecJC59e<@LaI%ENTW(epYa%;x|e>jZ)1tq?1$UlAu{% z5!?PQ;sY&jAxPXy)BgG+>=1J{JBASrMj56)GX3L)17cks2UP)1!D9VY!@=G6TbM8> z9CC6>^0ILL*Cn1DeM!iTdCeu`Y!$rd-|FzgJ!Ug~M z@j?5PI)C@6@d%xZMt$2;Nzhv7AbQ5w_+5^|4#mYKl6EvFF=b4WHk`yz;Jk{xy+UKg zBLTi^de4^*NtEoTrbhqRM#F4*F^E4|HL)qzCv|CMW$#YCUBkE2>GhpF!|N#_w)6Rf z>_-jjwVUkd25COh)qS;>9~l{h(q4>E+*%Qrl1-kTCn^-5MiWAI(ejIg1BGon@@n|TPyxTm~wWxaNQvD5kVbWXB{W_~k}m^W);vb+OMb zA;yrYezbQiE)9ujM9#rV^{{dUTPLhLT$#@7tB9!S&JEBz>xvu+qc09E4oVE`-_mUV zNKA_jFYBd$KdWf)`J%=lmLTptZTwsNvMwDN*aEehP3in+C&R^>iZ~TDsXcAAr4_>F zL9h}0I0@nhg&5CdD#wPhxnhE}LhKI-MuIPFlug&Te2m-;%(nsuHXS@QaRTI*zdCq2 zDD(5lv}I7angUPLKR;$ADC0^K9_w zQ)YCD#XyJPVMz&z^*Uk5-zM0<4TmKp467U|#Ph>%r6a~Kv$iVVvL1;1+$}KLA*U<# zi$F_%F0pS@5qB+9V6-||(JHlJ*e>wq`GHu64M>ntbBQ%lWd@%>N}hjJC{rgzlq3$T z%SgJ0l$gj{G_4+{Et<|}x(JWJd3^aEC#({ChAlyZ@LnnI^BmmiL-ertu#~VQJ88;U zxBydJd6{3UCD*C?A}-m948dc2zFk~7Rrmtdio+NC#d6jv)JL5cZvMi4GA02+Ow8}{ zqm^(H5;V?y!t*#vraIOn(>nGOdfUa zy_|@w0;a3(MU3X~rSVq;i5cl}Q7^ZR=YZO3NCO-sHq}`901uC$iO0AqpHwjI9VB0@u`Bconqv|-D%9h$ zZ1U00T@!&I0iO8!C6t){j)4*6Xh34w(JMQkhQ&p*)?MEL18`QMZeo7} zKTK!1({P39RrG-a`hJBnDc3{t0&4Z5uXL1NE&ww}3p+nG?u+0Uln>>E770+c5Vi{; z2o85hz&lRXOG7F&K*cij`NI7Vp!bLTp%U=9nB68PM*}x60|?G~l~DSXc7TQj%<2zm zj{ig?2Gq*nhjvL2(7^CqF)BVjxU!P!x8Qorrk|;a0Th#Qq1$#(X2Y&l#88FUjApib zsO4y4(eOEQoWrG+z*q;ZjQ?UpPzqUbzQgZ{y(#+S_@Ijba_2vwhd zmi1fJ^$tY9dex0!zT>mS|oUyLkAUrabrCxnwq}t-?A9lxL7Anvc%vbY7yrTWx+& ztfODT1J4#c9~y80te&*5`UG z)LIr1D>n@%+pL(rmBHW57d$XQ`b_8u1XY}T40!H!y0NLfuawKIcL2RzmwxG@feNJ4{8uli!@$3bj6A2Sj*1;ZYP$bcUzRckIo)FgpDK zcL)xk6_lP#yF!LTRbTmUKLj$6G3{yJtMw6;o0uwW9bG_&L}5 zgj*o@LE#7t1~;eA0loVlq-VgLClMGO#kc_5#DF&%Tqo5k_r}7Fw%Ka#`9rRU>DZZS zDjXm+2POpR*ayA=RmiI$_M|I0NPcLiB6&Y#BpI}Ifb*&6M>Tdi+<~`DNfz5TXA7bA z8Qd?J+4?F}rjcA#+HlaPXwMZuHs^x}*x>DEmM)#gwRvT7AM|?Cw=f8rXVMie_vBG) z;m&g{^?nV{)rymh=r%r}82=VJ6;U-6T>LK&o-GR4^hgC)3xt(c@*t zpgbo=Gdl{L)Z0HC3J){Gxp`GP>y%ZO$Q2{0Qapas=pY zpAf2<7zg22>KN!%wx8SEey*ccRwo57EulR>$daeG`9q?yo=}hd+>9ttt87dH#H*pIMAe?Gg8AT{^RE9Fl z4xl06O_fupKYLEO3wj?fwZdq&dfIJ@zRS;-e6`1F@zpKTQ-26y)!E@n*Tn#??6tzt z&VKjwuNLqIcPH(DVeZ{$oOKAnKd`$SJ$OYWDxd4j6Qovs|Guph(iM1t_fe98#^5a~ zj|9G6YGdN!A;&#GS(~GhLCidWDW|74h|YlgTX9(_(O+(3Qrg)aP=I&_wM#21L$jcU zWU~i#T9%gEFMhOl?>v11qqIXAlGv$2he&gB=Aq^r`260L2Ufo2p+=gTILk2{sSvmh z0k>t@)Q6u2KFTw78Bqu#KSc8OimK6$d6NF_Cv@BGIl;^@=lmZa`&WCY*% zT@t`uDV!mQOf!+M#{z8C}UF^jIoNSh&g4-LE(NtH*8Kj$o)Fv!!m&O@4 zP8t3X6QA%&1@y1S4u}XtRx7#DeFv&8?(Tn4Co|~yjhIg4cx{O?*VPDhkozx^V;yhl znfQvd+-55n8`EoAh1&fpthORP{MZRZ<}|voKyLkz>GI8JnJP}>W+%djmqS($pu@D3=aD2d@ta1xxjYN z9~m0&&x)f$TwN(kO3Rj7EP;f|*ZEz4%C|Kx&aQ?}4wyFqVMTP8bD!~|8lWYkQc6-bNz)=jzPYy7s68WXEN zfl{S9lq^3Xe(n2^SUxwyjp~Ah%^C!>+|%yfIfA9|E3y)qC5}l4sCuglF%1oZpa>!e zLJr))!8e6LBT&G1|BW^tm#ThO;<5-;!8R8;Sk{-tQqoqD`pvV)se(h1=O#SoH_lXa zv8mtvgeXMUuVVAMu{-^&K*k48z?0kAW|4MKW-vCqgum*sQaXLg3O5t@K_Uf(sci)w{U!=d|gvB>(AGZz*K(IlbJ>h=dRF{t0tx@fMvCo0GE)yp#Nz2~Pf z(>Z4vf|X{1P^Z%JNtZ!A_5t&m5(Mfq6k#$IY(Sh~k)EULUa@ddBg3WwbZVw(Kl#|&dP6}+2dDT0IiUF9Bdpg%S_8@V@0g8FM=YWd`%$H;q*a$^;~9xX z>NR2F#vtGbhZHWj((y`EQZifRc1?eEIGcFF>hJ?92t{$=bams~{RJ`j#h^M1C-qfq ze*R!|A`i#}Iyxbla`KDI4tPZ%i_;FJ#W}F1*~1g<=S73fV~yw57S>OXCbT1)II24J zjFTlcV`I_C4^Um+*rh6C0rfZkgQ%~Lis}p7JwrDr-K8Mi-5?+mQqqld4c*<1N=i4< z-Q7xecQ;D+J^sG?-MjdM#Vpp$IeYfm``PdN1d`zk2Ya=F;VZxMa{=>}=2z7(*eGKj1bVpmp~Fd3xK5pkDFhT+|rqTRpQ58ZiQMXIiq zGSU$&y*Uw|a#>!!eMLzRf1dk*Y0x~5#gf@38d{!ixYdpqooeq$^K3&R=ajU5U3X-@ z+)9|n-JESxh#`^_+08Er@yk^CZm`ktI_Xqb{OQM)VX;X}J87d;YD7>GPA8^uMrZFF z3d@#$uEa%s zZ_1QGr`ZuhS$Q(~X%9w@TsV|ev(rEN!$+CkmR>iV2>#ev`6a`Z*DR_P%HM4|9B!y) zZK|ZSr0DNm2?J{8KB1@pwE12EAwHOmEH|MNac@`O_8UfC39BK_G%T9&oeH-Hht#EZ zespDeNb7blVG>tx5Ya>5XNM~v;>IOT%#sjB;(LW=lK z!Djzr`k5+d043)`l!>znsXt=dD-Tn|zkeOGdF<(EFjAPUUBCT;4wL4_rql9J-}iYl zrnkS=`r(*uB?4=VAQxbI##VZ~#x|i*ZA+$L-PmivUh%y$COt-ayjsa>W6i0&@{e|S z$qjS1-pIWMIHY>;dlA+5DvN?lR;{)o%@_i{sIBs0CKeuZ`{gDI-Yi=g#_c1Vd1eJ1 zO!RcBM)GNYHuRLSxoB$CKlP^Gu-(R|g&oo_3l(-v;}Wd+QjM+%2AY(kE|?|YB-pc2 z6z9m6ia&&d0<^o)-*a#kTlzUR7Z|KlRT6=V(V7CTp!;}R2mI!1qlq#b77e`p+NUBB zX32Dk;U)arTtkKrA3m->PqjeVmw_v8p`l~ylTYLH2XaTji}7C*#irMIHV8L^^hocG zmy~=9KvvUHQ0Mkc3w~9*LHgAZJKSD*CQboR;LI)FfvA)8(8D(P`V4yu%eoEIQhv#Q zU3kvMh1dRoA|59`Tf~G<>S{}GU(ojgO$~=I)lok>x`E7^Yb)}})t~MTmn*cZYbzjC+;rtq6k1OSV7LW4e_lY`ZOpRgJ_qaR7 zybgve+{aq6+tY6bhv-Drq%cmjo%-+dg{?cLFL6rI^#WyGyOpk9(rL@^%FUKK%)(#* z73J^iyA7`!&UD33eq6DA8Y0m}on?>qU*i@C(&tB#gf|&ZsV0%Qj~@8GcT?%IrTlJ? zfr^WXP}I+8Y}C2XstBk8+;NQH%DlPWsBB)oC40 z>(ww%LCZtTK}%#fJx*w?t)_+`2*U_{QsDi;Z5h41Y>l=rq?HWgmi-GVN0=KcLCdrC zEAkx8DASZ1Eq}Yuj?@{F?S;N}$iGym@)~tZRyxHCK&hLp#VIxSYNx zg^2!u9V)Yd9xl6okd^6zc6sfOq>RLvJxSSlJzgSL{rSV|_zc5eXB9b;L8e&tOV=(~ zw-pJSSRgO2z^Hd!jMw3?mKC1$WUkuG?)dLT-{g$dIX5$zp{Il<>9&Tc>6zaOB4)Nj z{zr{AFT#RYa`g;4_3~;EZo&G2aNX(BA>=#$4f|syM51!QE)^>L?I=AvA329?SyFd~ zU!&zW-jnCGNykjx{?x0vm~{Mtj;qz=+Vu5?k&g3Wjd4 zts_ar*MNqWuld?U4Xtr)2o3kyLd+f;3!BUByIs?FZsohpzp$qV=o3DVH%Qwd{I$8* z^ogWnf{~~Pr9YAL?fwroVKFT-vD}T0yC{d##Up^|$dO86vI1x)>eW52 zO?d8v!9yP*Ud)?}rL*PYI$n&6b*BKPwB2-tdet8O=H-Jr0wDARQ)O|%otIx|xIIn@ zb*!YD6U5oO{fTx4XcXaK0`$N)Q3VGKoFjXKt&2+C&Llhi0>%FdTYZ@&yC!X-la639 zq4|1pHLnU^`CZ<29z`x=;Bb(f;j2kLTCkd=AjXofm{R&l^TX$_qRMKNjGB`_cENLT z@gqHuWtK(GZL<_rTk8}>!j5ChjdA?weKm_-s<#(L>HQG+d%9Q&dsX3Rq9&4GATFsw zU(7X_EhqCadV;9O{`OKPZLvu`8K!aI13II2Ba*Oo0M^yIu(kYf)d%g+mJHX*u2Tub zrlUI(nq*i6h&8ak)#Cb310VL3WUN<}o zE5T>(c9doP_8Ob*Vyuc)!$KJi5X7oRWjL`JZzA5?o~!L6Z-m~Xfe@O?-1lAlXVpr# zU26oELn7iT?h6OUg%0nTMV~Dwk9Y68*|GdYyhb+~w0?e6==R6=DFB7i#htsm+w(SE zfo0S!$e^5OFgUZF?bx}%ulALGrG3}6^{cT!r9DrSDzl8q&+bon?HnkDyx+Dd2EaKGp)w63aWL&%eGTl!q@W%k5y zrYSoGeKx_BD`l$e&Jlrt%-wr;L_SqJs$!L|49LaDMEMGAWU5n=h@sKv0;FJ^p- z7_(;$9 zxeFN$z{5IN z4Kdp6^`U4~0`8*8?ZU+Z`6Z;Ku{Smv3{C3b_r_WzWMncDa>=g~nODLT(x2ZU0L@Lo zo4*qf=PSFgWMqDhPgeJQdyDNu2(Z#Jpi04Vv5tHpY>p)esf=4`bl4`azB!ZnI}oc; z&i4Ag{3xrSJVvPeG|c-Rje!S|mG|r{wSjHG5;qDFJy2$xMzAqqf`ILRxd5Ou%38nJ z*n-UV?Mlb%aM4Jv%7u=|1jl4PeAq&{fd1hFG@v$JnE2wjbi(TLLmJXzK&shj_jM;( z9&KS~))|GlwsuZQKi09m%Rs(_bOgk&S=TdLqNAO_ENeioyJ)GFZX3sUIrT$6=vh`h zydEP|+3kTF5=$7IE~ahobOt#_7a5Il-=vU7BN4`NM914)4xg&jH1w}=rOs9jht=?T z5AOWv%a@8J#q^2tTwOiM=8O&#eaK0ys-Jf!|HSiv0kbs@)okPDjB*8p2JdgWkdT)` zsnrz>JD6OVlvi&nVExotblx>~@Q&oA2zlsqJ@vKz{rK7F4#d5e7|wk|4PzUX^Q2>| zg8e!rbn})i_Y z03yEoW7M_q!44{LxOm8P`aV{?J=b{^=21t~IWL*i6$aL66ZvA`NM5_uIyDoLwKs+a zV{q7azD46IDGpFxBI74ViQRerF!G@fMu4f2;33O~<1`!O-7N4eXb4;3TB=u|CVc#? zRPuv9Mv|oSU~ubPV`J>M&HPDJYpYHHCOm`@rw9M0J0zhCymk{?kJ6L3D)Q&NG(@tx zPMW?*ZuBK0`J|;qf2fk$k%zbp3eO@&Ef41#2XI-8GL5!=cRNa1VEE%%h>!v;Xb6_Q;jV*&*Fn4UdFvMPys5k$qRJUp zNm?%FTb%vvItLDp@AdQEOetyihBD!7VKsHJek4K@u$Fc))}x1P)>BduoZ=!gnQ4KMLS&I);YatTbdyxg+mzJ|QG5>QzR zLfoRyUHlx38fK9srbc#M{JF*?%ig>+R>MZ088u6y$xiN#ns;?t-EUB8)T8_GS^|rd z@@bA?8ME@Tf)X5Uf=3c|0rTCaAV|TGX?b__eo5uZmxvxRd_uok$? ztzT=gfJ^plI=@LsS-oU&K$qN>$3imFkS#;a<#wM+P0c`x7eYMm0|#J`^AcXv4xiuN z36<&<29gq2WPSUFQXkw*dDw-UOl%AIM;5qfjsK|3BLI@We~*T*QOj1lYA(_N^NQ3} z`l5?2&N1o(R(>12qx&3^o+>;ofML*SG~u{1+}Ea&k^`)xr2aHtR79_#A>P_uLDgvK zLRwWA7=i;n>CZ>1?78&JauN`xSX4sOq7Syv#w+$dSg-B*C(p$iDnU;%g{qM!V(pAa z)H`o)h+cB8RB^x!Ksy#JV{~yrVKW5piB-7zjqPq$OM_MaGTdQL|@ww2C#~ z$h&zk<9WDpFh?kvk5= ztI2u?u=2en)J3Qj+fVE){k`_zN=&Z&$*9Rcqz{nqsZXf_ZJ*dR{aHu_VYfuWJafWLR$U2MVZpgz!=7aOb&E zU~b>#Gy8k$BVqzDTsycE8clef?dSimmH80tLwwKe0}q^c&kV8Fatm2lD0Lm0J;z)Lo;_Krv4LBEOXKxr6y&mH zbddLnzHwmEKC1c32Xzm%#zr2Ph>h45m%_r7{L=eYgGJjPd#<9o0~vUeCBGiQUF2=% zW{YeDohs7vuJVe`AHO;}!s*-wK2YFXRhVPFn<;ms-3=O8Y$bq{m@ot>#%CSR`4Kl! zyVxzl@aPNSH~S~>z(bWn*~A3kw))yV;5}JJn@-k8S9pEaDITwnaa>y70C6N|&D@qD z#4MR84;;?PG=cqa<!O0Y8RC$f#Wy_NmzPf2sJVrK*u-W5#}QKQfM@_cqU(OZ z)?9_C>tD|8?cMH$F+|gQ1T++!_cHf!OPLjs#MhZ3LlL<05U=Z7nOoi#R;;LiYg%;B zre8WU0`M#Fh$P7R*%BYf7}0+^qMa;3>TiZ~iID7(Ys%~U!tB_B&6W9!Yiy$X8>yL& zH6slMQAFg#&Aj%CLWx?9FY<7Hqr)>yAgaWegGkO~GqtD!aE*KNT`I{oxyeA7X7mSy zkB@KH;{#TasI0>|7+Y>E5?2S(IFiUkaQN{O^FPUKz5N)NY&K?4+(2Nc#H0DJJHvvFx1tiw#|` zpBR2H=wRbH)ne2aERt$7fJM?bthN(5_5^~-Eq)#D7j_?HWZ1D{44SV`QjJb^ zRxYam!ej7M6NLOUo+AlsZY8j>DfuZmd-78k#=XV;~cK)E{l3MvT$+vo2p=hhQoJcU?;aKS0ywUtx}DYXBzfNwzJ|l6xAbsOk-S30prqu+b1Y@d zo@>LjzCJNMC+qT%B5ty(nG8s~ub$*k%q81fZJAp^{S}_cI%mpN(&HM;w>)akqWY#a zx+lVr`jyuiw{04KpoFkbrNE#N^pPK)bw(D*8mp5X<~uig4?0%#acn?qoV)!am$?vi zS*)*+JJTdlu`Z6`Y0wG72hDC4oKgBNUm3F*w{vq2IL7FRP=VO)0P&XDQDFVYi_JR* zhtalyauTs5wZkIaGMC_HWg@_h@(@Kw+#5Qd;V3>hG&ZpNU4YHC*J&dXXUF0k>_R1a z`F;f$)F;Mc8i}4qf%PR!yR1A#WP12=oeS#%!-#$~v=g>(U`Ubo6^t}Z-$WQJFSa`x z83PQK&lb*Lv|Y=JSpCVrhgD7r6dVsCr)?DJ$StD|YjV6Y^R7Uh>N}j5>x<9k$ewV_ zQ~cXzNZIIcBhCiIEhGGT8Y%)R89t#Hv;e4IcD_`3{}dq?q0OrPhT>>2RZRfM_9$JUUX^tQ>E7uvNr!KN^Y}3?RR*X5&c3jC z2ko*v%tSTYUVlB^Gqs$YPVU7LTF@1C$Wmq6eInZ+{S}<-lMHCzw-ey(^7pUd zF)vxC6YantTga3B`~_8@-a&HRu$1A6vYnG{eoWdj$(JuCl{NwIkO8?dxt^@Mbm;6E zw?M4YaLo_2bo)p`oV&IhPLR0FK)SW(B3cB_F5J?c3-y>R8wTg;th&-QY;GEVb2U~3 zxOHy@nchms_l?aKnD!%u?g=a8W|#d~1shM}iQix6Ph4fcG$0#9d)}Z#gXf0dE738% z{p(!k3{d4a05oz>a#lFFegjA*_SlGhan+yUmmD0kE-RsnL+6OMtXodU5#&&=h;__`6n4ac@CbSuv5VSV) zk(<2+{X)VV_2H>;!6i3qIzx_K}TTCHa^QZ1DDWp@~&PeI! zx7Wp=H*^%a3M~wu<(Q(TroR9bHa8BnW=k$)ykW2dvBt3-8JATb+7#uCBl$hlfLJs+iBtBR*&R$CUMi@e7b? zFn+Nm;diek00^FLU;W^+{q3jjA*`4yFCp0Oh0j$o*SSOdwGvv}efLEBiueXEkj9eg zev|3Cb$zSw{dKBJpvRTw6$boIN8~|lp8o-1fN0qt6;>0pz1f;UYdTn*r2YM_Ww1wu zU!_+SVS~EZl)(3pumcpCS%$e-+GLBEwO1}tn?;2_r@;@;oP+B{n-n?-oVf_5r7uyt zH!T&l_>=$xH8mQ&b`SqV-=eA&7HO%@X%9l?^v35;sZ7o<`nbSj@-6ZKAtxr^jVU6} zr#D`5K_$A`ghMy4gAoDL1Q7rJhq}(3P3cPaKm)<;a%y&zv9%1_;ikEn-!TwBkAk6B zxGw(4m%At0MX>|{jV%+mmwd?bEw23~slH-Oot5kV!4C6kbL4)j7J}hmf;ZAS$Yk_y>&^K2w1xW%c$skZ}Fld0blrjz!n^F23nZDQ=1= zfA+0nSq`X=bF^ObvA#eBnF;jl9FvrEZ-Z5Mnw)s_VO8Le_}ttpYPXE)Td08WI5<#)MszJC2@kITii0Y% zA@1FXcVXWEQHL4gm6i^y=iKThK0b|;Rt#)Ys3-vO!&AW6=gPW8@1Fw&-gyf# z5rBbtRB3{jLLEUdW!rALr;nWlM z99$dLQY?oqEP&s7nOap!+`E z!*X)TL(lZiLG{tuiYEqUtan)C!{BI7yblliWNOo$H#$(Rg&W52T+H@iqO%kB@X%7c zvl0ZLldxwCDvuDgFJCyVZ5lS`tGW?O<1rddh7iygK|Bd$HQ$RL&l`4$N=N9U1A-l9 zOT=hxSUXz;N0z+2-@3p$SAz=VM1r6n@_0A~@8`~(Cb#1_4)NyTivEzog~nds}Q zBtyGqBI$A{i-lq`qfS6#*-TbVkmtm%^gmcq;#$EvVT4wWxO`_jl86AD^7$fdG_Z8d zHQu7K1hhGj9$8%ya9#uTpe>N8O?CQU6rRBl=Qt|Mo7FMWh>n)vI9C*)MGb0n^w-b)H|sc;7&ck+vTYj!h>m6eTm-x zkf`j^fW6l$CU)vILR{NhsuV0@lmM5S;z7WA(@Y1CVYCAQrr4({Eh-R4bp%EyS1n`p z!YEgoI4@v#T}?YPgWeztVPSFSEg@+z=?pxBroeZMvQBnO55!NU+$lN6L(Ib~W89r2 zl<l??1`oxc3S$dEIYDSK~}{*}j8Qd*4w$m7F>8o*N7W}2+c z)tW=0}Ud8;BJPWdLWIALyNUgG(d&!U4rfH)pw}2 zQnFh4n}5X{*;L<4K8U-Irx2D}<)~?_-k=U1-Jqn3qtd6_(nbFk-!-`WJQvn;95vUI z)wkBd+kty_8Gg#7D!(_26Pl9F-ho>{W~)bUrx@f>ddK0yfiUmR>?J&>S*zRz9CtV{ zuOiCOZ)xt&j;yfb*3y=1!iZ#hrOIkFlE165xQt(dsX(MiUx_9Ozq8=DA7D2oJo^Jy!cE=9{AD zZALro2tdk1_Eo%*Vs0Zq@!9M4QY{T_EjlWS(;O{1h|U-frRxvZ(O$M=OZ)zEInOYO zsbPtF5LCMfp&>1p0R?av`%dzQ;!xP?&rBr2yk}X@Aad@|3@o^pY=f=APE2xZ0b~6CQKsi9N`}$gBBBAR zWxtxRoQW1u5d&N{&F5u$J0e5d2yb*E%DZOWTQ=U`T7X+J80Er=h#A;|?mEJv5Dy1L z9FENaSreAnyVug?LHzyq`>Rb1c*DswyE;l+aXiKu-nfNQEtq(R(mSOG!tLS+fR=B28{yq zUao|{VSt*Nqm8M;%`vWl6x{jeJP(#A#e<_s68$d%mu5au z56n-AWvcM*&i51o(t@;X$jDhPZwa^ap)73ixc{|5!L4+bp6=!Pg$syli8=2MKW9CC zOb4+_U?}@sM+KzQm0Vb~Ods=&uvb6H;~?a`@2Yzb;Vr~Z0k4ED3*W3)tgC!)=Eug) zRZ;iRop&~`*CE{XEqsD+=f{OWu#SNj@{jY{_nsfXb8c)Qcg9C2;xQW6Kf|*`ns1Os z!hEIi{rnjgwuc_H?NsG4J9ax3cKpUSsuZE<2rE$q?yEp8v{riTc{#Oj6UI+=Z&W(8 zT$jL4N~}E4Dv=qxo-VEy^pqqU(cU;+QXgiE-ZX>w)jX3#%G8)v*8U90svUUJYUn9b zXVI{1{FXxn7<@3mf*nWFQc+blItbV@UVAnM#*M;Xfx)_coas%CK6mV^?HqT2&{u-51HoRqHzFqNZ;uMKevzBYCT@gjNi0k;?8Yh0bw_iTw*(v<7mXouI7h6eCP0nUA22OrC~-2C`jPFYeO_iH&y;Dc=(+|kt> zL3fNK@EmdY$wP~X27MMwyzMRNSikcqT3p_r^+1s04Kln%aR6T1w6|zz{l?=IqZ_AJ zRu~EeI4vO|di(QLS%R+TcaccW7ePEct(JrfLMbT~EZO?~oXmdb|7=Qc{xFnh*tJa-$%D_t1TWqJG!tOrk-Z5Vc7Jg`9LwDPRB`(*bgDloxV zoftYI-_1jn(W+!|Sx1tBqv8HRWMkiX*f~)J_u{*v zAt>rfki@mlZ<5KK!@$)2g#v~%AUWkv^ziaiADi`jd)@kTchmZF03skSlvG*Uz73T= z3RB)vww$c~H<6cp1d+t7TsYeL-~mnkd9Lpfe89J(^}KAYQhBlXUEo8E`}VA~64-5> z7&7N9n@h{5@;N_dVbm)@_Z>(x0C>|1Rviyq~B1#;oN zRQ`{Sz#H()#w_GzpPA1^qft`&9PdJQw+N|x2Q;1?E9k`VvpA^^{~mq+a+hvDyagkh zE4Sfm0K4iP)l=;LwnOGo-yV2FI*4vpB9s&nJenE*yTK|ogBPnERXu04r>PSX=S0NW z!GYna(-|&{$bbf%qA&TQ4%3YjNBoUI60Cd$(sc|9jBlc04V1t_!>xD>VpoSjdN?XD zX^%d42Kh8aQERTc4m8fC^Zivy4R|*KQB+OMX&-p@A&jPhKVrOJ&}SSuqZvv!S6h!b zs83JT1_RIf0>VS~)qo#L9qct6iVdc)AiJrDqxV&&%C~j%K?n=Z{WS-xN9Px`GhHM( zZw2}8!M--od6{qjvFIJ>O zOmFI8Y}5+<$F*@gzmaczND2SX|MYbD^ZtB2{`ZeM!1%8rtg;`jz4NbhBHlZYE@PR~ zS*v7u%3%0ST{drsj(cwm<$)`d-CAGPHzSk8k0~TC$1C{8(6uT8E++&l=D%tLvhby= z_7o4PB!I6J*izv9ksDjU^uzFLxM9~to<*{tClMxqvW)P8NCF1vuo+o+%j2;aWv%XO z*06d0-3G(Jz&S)f67{{Mkx>ZzDAjt&vDJBe96uM!w5`OzP})`6H=Qfj4-z!0BLAr^ z#sJ`;xy&RpX1;b58`je?1bb%saBHJ)2@S}D{&g_yup<%iM9FYDnyAO!`+-eR4KLlJ zVDJzk`i_p?u&qJBXlw168pJKnV4oATj#X2`P#bE*mjtKpPaTb3z92V878%nora2Zj z+>PG5>>yp`3H;{AlwdauOy5o)Nqg%@fPO?G-a#^s{hRZr97XySkn)vg6l2H07a4#R z>WLJSfzso$h7S7m_Ij|`_4fDu*ToQ5Q-dcYZ^V~LII0u>ZH%#w&w~`Ec2amhf|^^~ zANPyIv{fGwF+!Rl?U|xWt zCF86t#v7tNt`O~6zO{Z3a)V5+k-EkJwhX?!bq7`19HsApxZ=swf?$MbV&jD$Gj8`c zYsox@uQBvv3`DW=Df4kMMxRsXF}cCr1$K<7YdHE73FzKl{X|J=O>RMWx(DxMI-|+}{J>wyS z9Q&5(%u>_+!M2=LGw~fuWhJ~K<_h@PhOR6^+cVII>oF7+yu<(lD8IZkI}x@&AL+Ww6UujJQLhi3aqM79i#T0xL@Dds2kD@^hN>0g`@(s7$jB=hZ z-LGF+(_;`%R}Lu0pcS>3?ncQ8?Z*NqK-QqS254 z>Zr;dJFla_n9gGK^(B9fHc+RF@xNSvPj1#2??Dw)rSE_O81Yb5I}=au9^`?Efar3v z0DF2J6W0EK&rQCmEEx}Ou0NJia{JlU2YQnDO0-ZumdINFqn-(1ws4m@`|oE}e9usEUNvF#1!FJ2flt%i?qaG(AC%bI0_xcz0%!0y(fp84JT zD_W3eBb3?@a8^!gzSM+motV0Qxsls=bswzw0Fw17I*nmn(s;W$bMGj5eUVZBy!8Cz zK`;H|L8GDY$EXzA8cY?KFp{37@hPx!ruk7yG*bvZVmOX2HP4XH(|^=x9LH_*Z0O68 zH12}|4oK?Us%ZP84*s!2*xDQ%946|^xB~*@CbA_sj#oVW>+E-aBY>(8?1TR?%{za7 zsxK8$ZOF@eUj>$7L3{faGx7?-6B8^OCpJN2nNy;4-}gpIH5o_#fH5}<_SVbha25yRh(RkYV;lX^ z(&MvaG0sOWUCUkwBqa4cgXXVgxpXoHf0o89{aS0MlvT`}5Jex>G6}Km;ey()iKQ*p zlg3D6tUGo~GtP|%WhV(}6}yTRmC))*N%weJVS)VUf2(vPMf6s@iB$LLq}kZ%YLAht znU}{4nedR@M5B$>uxlkQGEh*M*J<9Vm<)HH?LKhLi-$F=4D+zk{QD3u4`BWUVWsB6 zB}0&9^VskL+0jH?M>B!7rD%FDPjDB<3>gZ=oQR?xDTdGivPx)mSLFnCmG6M9^$luw zwya@k#TFvk0GN)P+@gUAhpNS>83NKn7V6|IKcb+Sv2?;1!=~{&pS{k@tZo#F+D#`Sk(97UbwmdZ*CRZ`hz@UDpEjYO69z80&q)#19`nM^Dc~j`EbJOaqY@+Ft=8SqLWVj z#Z?4(!cleZbccf0Pb1LYC&9pt!KNq@)SKY5-kA<6u#MKX@(DwoC%2-@<2zmb;;C6y zTKJyPhmRjnbdi3Z&Yc+qJ4Oy{yyN(??cQPnHV3{mmIoa7ITomOt98%(1-HcoxpZE=rkET}Ng3fP2NZUu4;L?H% zd5_!v^bd?d>hc>qk!DEQ#^U*M9Y#OL@J90r#N&I^csU;{@-hPVjaW~=h~y(DzS6qlqf%I1Bh^%=p8EI1jF zCn{r!Od&THe3!_TujdCsv$u>ACH*#rY-?g~fDcF3QQ$X)Q(AK}0Fx|B#`mCvqzw3; zfOdz1F1(wcA7$s69ZU=gGoH|R32hCsN(`)xHZvp!EMIp=M9FK~pv@bhiLjT8w~Jy&NLcXgdy|h;k0o!yxUO5)d9aT)w{)^Br18LC@6ci|5y{%lEQ5K3*d zrQiMKE5mEy{;p{s1V%KTO4Z%XJfPj<0&P@h9@Q~7+L}oj%d5dBmrbYinze}8e2-ty z%Zu3GcEh^m9SGKUGdiL|g1_=8PYL%7Itcbhr)e|7LqS+JcN%0orKZiBY-d83ZliZ8onrZ5s|L5nh?F!i?g`-|C`SmJNgM>J*csd#u0PwO9)T4?sL6zu;l+b;V$!5W8J zQM=9Y4`&);yqsVp^Zi09d){cu2TReQ$>u}_54dB1>IrR);xf#kt9o!FO`E@nL-~v0 zm!{K^3wks^p3K!CqU|26FP16`0@ypRUHozp*exaNEL9%`HskJDMEKq?fV3P=~6ltira`iPevazdrs;#ayF9 z803Kw80;IWC({)QV#oh$Ra7#9x@2m6&2UY{;=2I}0JFXAEM}n& zl=&Fu69L1Ac3lmN4kAqI)2wn!8@0KT`8fhEN2+)Rx~;#4r%e#}{KvMdIa0XhTZ&U} zK__{%@hWFew?zTeA;DO8NbXDlw9ZBRqF+8Z^Lr0#u-4ir($!K-y=7TSpss>#`QSgC zNvB&?|CRX~F(fO-l606`$FG?Fb3{JC{8L0GQzUn4JOiq?<>w=GU%BOR!Ht*wyo{O= z52Ims?Ul@^6_IPEU=&=&M<%)KpA$d@-n)iCKPnn@f0~W6bS=x^L?j^8rZ4lWVoMGf zjCNPj;Uye%DvYkFsX1TP&>bx>hvgpPC=t=@N{ms?tzmY8z>&H$KBpPb7V45KEA2e| z<9C~%;_7V!DNrL!OT}=o#j{cn-_elsES^<}y1CjGT3&_BUvGo{iS|@7g{ei{e&-&2 ze=I$k%PQT_&JY3BQ(A+7SiDVTz~NV?0D$+qPXN?JgRGszqf1J)jTd?#`HGHE6FIY8 z$k(%ZV^&`Y$?4Dg&JW0VkdlBl;J7x=;!CqzCyEKFix zyjfCq79$^cUWCL6>+gJtHDhDhIFn;x>fIRAoT}`bA{J_aYIaAJsZ|{%{Qmu$*LUaj zuEJD6%@_h)i;dsMo!>^$)c=&)*qA^Bmv=jzDm`!uO7Kdjc?_}?pQO-1mUAGe@2f%C z2cNp2?ikX*9Obu+I`#2QlS*N*0D|4QX%d@!Oq`$%0liF{ewh zTXW@=h*LR!bceC|8_Msc-m{v26?a^o2TwKh(g(^7ZVyxp4Bz>}QWmv1ib5wFTx>Ml z94QrTm#TX9R||P$@msx~#akmIr5{GqHRzYJo;<;R^EQIC$l*ii>1&iv6e;bq!*&Rp zfA(UHAbu>F!m#=NZ~fvZc*TmkoRM|E~z)4IiJzXDj99+=N80_h9+R&5V+SM_2Mu`b-J>TTGIbwl&xsxIAk-a&Emg5u zr%4`2;J!~oH-qB&C&SIA^LgkX-67ZKzbO>m8dE|G!OpW_mL)G<8kx-)K_nz3-nQo@?Hw6lz93zI`LW2s*gJo)FH(LjjGj`RyY$+ho%IkkN;tDI&yL2 z<)sqhLRYCZ+xp#v%2>uc!xHxGlFq92b}le9vdo4VLg1Y$2e1w z)(Vq~3bU@xng}P0C!eW1n@obdf{Mt2%H7caDKjL_FUtr_u@AV{s+lJ$(Ja9eS0IyY z3qkMD9C}3jCm!zz1zFCb0c*1rYr(oL-PuL&#tiCmEbNhsa$l;e9g?*(Qc~~&mv=|B zW0A4%tEL+l7Zn=aCa_ISeH2x!7=yOhIcnITS+8WNTMfi0Njl8N+#6d$shF6srwY*f zcZP1(*ICIyu^kBKzAd)5ho)TUUmgB7UvA_Eg?eG|Kz~0?=khYS>zS}bu2gYl{jBL} z2u`k4N)>0Xr17tB$Y?=%#+8*woiim9=JRn66Y458(rf@L>pAb^Q!JI>a)QM>B-%o( zpx_l4KBRA{IiaDD2iKM6U^FSlPw@zVGARq$d*kone5)NEBc1%D0#-%bo1+U8U*{@k zW?v!Ot>u7PJG99X<(iOrAS?3JXnSY3^9}TSWy`Pd@cBk3_~&+aV&_zDe=S$%H?%(8 zRCCT$U^#xhA~Fff+GMERSIgxjDz$Jd0v@1gd-22Vkv2@shaU|&7Edpbr=X?{Ed(2J z&>+u^EGi`;;DfVJqbDZARn+;t2^zoC2^#TDo%wV;tf!HFM;CqI&h61GDW~Nkft3!4 zCSe5TpR=rmHW9|zMML#*HO{+S!tL#CEaxpe3@PZ(zx1y`cKRo}OoLsk0IP|wMd~$V za&^NK4X}H7dSBn617Dh3-Qd2+8kkV0h6$yY0Em+8qGz{p&cej4~(~!ERN45#X6+u zRy?NA9~9_pHicUgj>+&930?d1Igyna$&L8`d?P?%xtN#&goD#LEB{yi5|vw@YbIih z&hpTt(iKR0E^wR`)%@zIL4yM1oy7C=+~0Y-m7e3*OCPvAj6L8PU1;M5l$W(|K&_%H zkZq-;b8JUE{=e={BATFOzgo2r4i6=#^v~-&{g_W~L;Y4)UWnNb*^R z3w!=0uGzLD_a-qiVC}Fe)vR(jVaP*~(>7Gl*ta>9ccZ(El`*v`!oM&Ef$UiSDENq= z^zKtGClGh&eYpv-=1!rweqyM#SpC|xIiZu6#b&=^iR!Nackq=Ato#h2_V>39&U<}S z2-@k-N4vJtX`tgTaYpo(f@XkX%K+zk;rR%$N={p_eS`-(i;Pd0lrTAxsm7@XU2Awp z5q6r0P^>#kA{#sA8d#g#B+V8Wq%%1r!;gVusrV(mEyB~^IkoKk{7&ef0!~&1aSC-d z#Xt}lI9|vWaq7qQU9CJR5)uDoXZ*8x=0|-`@g2tSD@82T8x$TPtlIf{O=&od6T?(< zrdP|ZPpc+_ABK#&6`A&$yR4T<24K|5Oh9un=?+idBFcGe8d+iq)3 zM|m6|IqffUc{>f-uu7RGCP)(|Eb5Rsv_7Aa$ zMrTUCaLpk#YUuDPbd|-E47W7cSDq%11yGlx+avhv8jq&h zg*ZVz@XzHg>@Hp?6#*Zg=Kh)^j#KS0L40yzMpXFQw4>Q_6tRcd70qS?7(5Xlk61kU zCF=V>O!H;SI-vG|PJ@G6a#l{tlpnmX_5O56MoG`ua)~O9C8_;4AG{Z<2XJFER{eAD z>Ww4_ur_;1V1Av%Eu*RvxmEf`8_l!^{tB<*t)k#BGBOw^h2 z3~QX1S(O&ufl9SB{tjxu?c!9IAX#(WUGAUtA*{PQ-{tWlE~@D$7BVpTCnGwn(}95) zWC7-wC6)7f>_%yToUnkh+M0nX4nsv{)n4|e;AF0F&v3ryCqyUm?C`ooJS3EbV38l( zhBq=ir|&5@&%3G>BH!BX{UdWc5&{(R?Pb|71FM>Tl69@Vk3)D&2!@^&E72?I^DF5{ zC`JrfNI5|i1n*p2p}5+NXvbC*-0wbq;2C-mahwkhHgVkujhJ)K+!I#6mTTuQ8%=W* z-g`LhIlQuZm4p?G+#|L1n@OZ`i7#7e1!1yj!Shn|a(^1V0K4Wo=Z)&xmq^WX%*H^_ z86-!(CXv%CbB-U=qWvM3JZtUNkhj@}T#Iuz16OjOH_J%n6yDqAV!<27=0}6_JQ?Wt zOgCgwBOvHvon2|v!+&+~?9=d|7JNd9Xot8Q+!qn-c5?u`$ZZFY>4{GeKUsK(1egmu zbAQHpvs12X8p?da+qXPkSz*3fA~egW34aFLqIhC=O8Bcy8q(;Kvp)9W!$JfgTJ1|| zK<xkp?vYdUk>&5aEsCiLnv^Gmkz+uB{jknNcTtduWdk~Zmd4gI? zgZmj^6=Z+?eS~q{mj#$9R+UQK9TD}paM%%@HYu4~&qPf!=TxM{UM2O5l;Z~N9Dru!^_-kID zSV*G=15njShWZ50j|4^a#y_Z{Atg6V zTicdHM?1=$OLsF5{1(zRbQkTy{SxA6t z9a>MZ$k=OihD`T$b$8G=^R{I1vT#n8 zVhZ9YU=(`7R(yHWl1v4hyB_rTR;8EdnC9q4uA0wP-+VgK6N7YiTT;LRe4n0U00YAj z{~R3*SOAPvrv^1xo~;LwE_O-LlJ58TjW!Ew3}p-JatX+~Hl(OJs};kbE$buh34JlJ zK6VKYig#K;`6U9t4pg42XM)4c=!JrCpYFW}hGV29gwca&e@&e*t_s0$dkLWId3(E9;D>V3TJH8QY9`DZ9gwA877fJT}puCl#N z3bVHuerTdXTFt3wEt~usH#?x{Qu`ML^2jj|5AoAnHU0N)gn-6v2`2ciYlR&-@BiI8 zKlleXG59)bN0pA`IIQ%|ap00AV(&oT#CT=>QM!`crwXL{c&-C!_fDT*?6(=b@P%cm z7V{>i!?6E;+}-`aN%pdA@NY0B{_kKC0!5$IP`3^1dS46EQZVVwvIs&jf9!B};s4oD z$V0P75I>csI+nkT<@4A4ISdPaEE>S570B*l;Y-9mYhO-#8A*&+)=y`F4huxiLfua^ zqlsO|x-An?!IYPK72*M)vvmcWB}_=1@E;zDBBmSXGx=E}KboKcU%=T1_GgJyA+ZAz zYvn>4%??bQ(=(C3Rat6Xum~Dx|J{BgVmnLn8P4`)nsO1lDcWu zheF=Tp<1zzmLVSrXQuro`Ni`F3W^gmDnf$JX+I!66)AhHAdtRF$a(ucaZJ63b_D9B z5cT!Bd=x*cChPU|>t6bVp}BQx{!8v}Io)4|@GL~%OiCEdtKYZSHvI@gA6^qM<>KNZ zsN?v3)fyIGXfgP2vKMnoUL=4Qap$1^Rel%{y^{cyhyn7N2ms!&9V0$CZ~ynF)9x68 z06g~xCM`IDzmwU%09jrg?EkYwJ+fJ0FkE$<{riubfm^dHaN!aenv(1wLjZP#r2dW0 z|4f2pkQUeRe^`49raHQ&3v_S~?hZkNCb+x11Pe}rySuvwclThyU4y#^cXtUE;7*?R zyZ0B|q6(@G6mw=y@4eURUcH*4250{kfC9eq_X6F2vnc<2cpecvY|(7-@$vpao6vEn zTEl}i`QIsmGEwp{WeRCT(qg`LFOGq>K2ED}g^{Yc#RGxK%|vc|0xJ2cxw?9ULjc%x z(dDR;GzSVhe#wdpf}h}CEc(A7bN#;{`(NDr?|WA*92o6{sCwUK!M(zG0DQmKFC+h3 z{+EM4xPQ#Ob^iN{4bq*Qc`&+O85D6}Jitrbq?DTAKt3pH0N|t(>G-nS;I{sc z(CA^v*CoE?CrKj5CC6wbyOoJzdUKXF1rs?cg}>(mJ5p}#EGH?xJYyTQMcO>FFFZUl~% z&Dm@vJh=Mlv|cE}=iK;8Pf{dinw$_B;Uf{^ljA~y;-JI8i&RxBK!h~k+fPj``jrNS zy%Il5e5U%|*p!JA_A#-){|LX?^-wpDF-oE#lCmLHNIN?u3{@Tim6f$+zAj3}QY89s z73^_FKXbTL^v;ZV;sT(Y7U4sJ^78Fz=K4G3ckp-;A*|Z?nM;8H;NVVkGt}nR;PpZ% z&thGZd!Q>$DjHD{CPBwe1{dQV!_x&Ns=5mvbjLLWXSRvO3zmR9%y%~R-_Zuf&?&d7 ziZ1c+wo`wJ{iL|Ng#|5*T&PrGbm5d*t3do64JAKRc%GIg?oZnph74@-_e|_CC4wCz z8wh1a7GV4ymQrMcJK;4P7{o9c%@+73aKED5{r-RZ+AQ%Tqo)2a=k4=B11ajY9}HNJ z=ps{+2U7mr3Cw)4pvGQi#j|} zxA2ihp$y@xAT^nO@^f*QXhien0ADq=y4s>dnxE&RMXdy#Xg?uAMW&=~7HTaQR9V?| z5-@ayakLR1|2UsnXuuv1Z`c%2cn7-M;85_R*pOU3hP@Sr1Wh8$?Cg$u6}U)9MUPg> z6gwCrQIZQ{pikQ1mcGApz~0ml(b#LRbb6JLaatdqpLds1oD|Yd|6AFiG!!VntmpAA z{8omHis!yvm2*za&JBZDQoOhrHgQ^B+#~9TMSvU$J3GAdd^|ylB!R=13TPGMy3V-w zkM58HbI`vEEik!Pgy+cXN6n!i+1rQnmUDlK4r`O|`rlGggI+iMw zhVs$#<4e@904oLjLrFTFrBLwxQ<%o6?=MIQ)DS-QLUV*mMn{xkUpfZ38=+)4Cuf-E zYwE<7!Z5d73wPE4m1|d9a`RSLeWm7=7q>?;0J3iGPx=c3*Bm@i$UPPC%; zX1h;07)c^;j5v|;Bq{GOAn#?}D7G=)nmi4}ZKxx3shqdtvzdGy6-2Yc>B){P8D<<; zyS_i;MSonDl6$786p<}}{4O#>rm*ve%LFx9S6Hkn_90VS+okBEbnC-|qLgBaH%U=) zE)sb)*9^vN;UtAQSJO5H+Ao&;bTKs<6=Rh$#c=baX%P`K$h9u1sMDl5*>7hlVF3in zDx;%6Hv%zH0+yD3cy^w;_Zv|L1rH}14z4v?R zF`}{uWoI0%994*qs0Bj_^<4?Rz7Ci(G!+6h!!4~i5C-gp&b^h z@#>|fPr7;2weNr#OCvN!jNVU&&-UZwz^_h9KF&L)#99+QUHh)kJ@{Af%pC_G_q&aR z9mk}KVcfhW+)gJ_@|Mn^5NZmJ(59Tz@mh+%`jRl( z#>5|pr-!UINE*EtY{~x0>3~vb$%#-V%T$;ao2u6};X#^THY~}S-=5J!*Wn|A)sK&l zcKN^}GgW--_}x3*!``{Dq}a4$g=}(g#C-aMss%?X(RtXJ`pS}GZ72SO==U=^jDd96 z#;Nst#1;x&^hFMbwZB-3E3$ty-!redCe4rBc`5z?t< zGD)nqacRH%&58C^=Vx^SpJkyZkG#N|?YF{jWTK2v+a|2uK9Wy~vqOAjp2$Y;Nz+gZ zv%Z@S7TQR^NfTs6@Hpwk^T+Kz(b;~fd;7yp4tr(3>Z~nE8{nS`=ikVIA(~Js816f? zns6GqONUrO-M=+EK08vb;_;=+gO^M^J_px!nEE*Lt*PCQB4@W1;MwQ9?oSv*_0cMD%JB zta@2-iX4{~3pBh1(Xz1yS>=MEmh9|!2y*78bZZ|T%HJCaH^-&~zY|7Ox0B2RO(MQ= zFp}@`&S2~cVv;>Vr&E|EG1D
    Uoi2T40kjR-(ZGO$Py(w(SeJ7#7~mtbh667f}aKI>%K^! zw&^|Hiq*AH;D+6yypa-(EI>i7hP;{{xkzC^N{rr)v>WoEV^($Gv(O^;gbOocQ5ITk5&~{jPR2aNR_l)GFiP6!b;knh0__#1D5pQS5pGxxHl~(7L&P zk6m3)Mhn&Pm08U&GLw8xXDdtCr6n%eTR}cg67}J_GDN*97AN>VQ8kIS9*!g<=|v93 zoG4`O;4J5e^99#l=Or^owp&-N?MU_48kFuuiZ76>$mc|+4=Udc=@{4qzmCjoJ>9YZ{c7qKE|SDZv+fll=WGO6fBSraodZiZ=8no7v9hl;Fi$)$bTn-YSdZ=*6=3Fp_4@rtBWCpC=ag zQw9v`JsW0k$n`jNPVzZN`rvrRd-33P->=*(@0n%PSJuiYCehTpx1$Z8#k#nJ^OmI0 zQ0sPe@R_{?N+Ar>W%}^_j^qCBP5`JX5E4=$eJnn#%|a3V>*LcYbsA@Ac{Mfb3jaVj z_$e=y>cGUYdkCvd30EGI$L9dfQxwZu7bHA0|CI#D#WEZD3tR(JbRK|-SN;P4^9C|Q9pvbN zoanAky)*%H?ib16Z^p3geHU3M;Acz%4#|K00ds<2cB#6KgA5SKg`*h_*g%L+{Y_F>WkQa?SCV zFI^du%PTYp=Vbx=$=P52_x~Uyoq+8&1BXCV`Kis-rG(YP%2*9DXRc!c;}hy|K(AsH zBKlKt`4W3l2YgiH`ONL6+)4%4J4#7=>3VM|-r=I}Ex1VX0mz5Rz~YF}8l2~V)QvfC zDsOpyx$`8i^hE|cK;5N7#DS+6UUUAahX3jh&{FkT6uT0|aN0LUH`i+3C; zG+A{NQ=F65D%Bm$ZBVUYxBp$0aa6Q`;i6U4s{iikP$Ruj?8GCj=~;LdA3xtxXATjO z9vy?=2f%XTd;8mLy^_Ig>~!a2&*zBRn~U?$3`#L3`t>zUx0tk6I(0TQ0sw{>Kc_5%j&RH6egStsCGc3%dvmPN8f=U2v$b z5wScD({%i{+mjLkWV(@+m({MDdWrPCN~~w51J?D}bcf-z&oTf&KRB#Riu6b&+4u<< zC0XsO#oiadW*Si^F_l|1XD&ANo`I?zi<0TQmfVKh+;sTv0?~|zlQ-zi7m!%nb zaeY+d4*T1Xqdk~J82-CoTB@q~1ay;Fr5%(a=k^d{?^{8QG+E-K9}@-6MArHot)tWa zuBNg|2Eu;0uA@_2-$P-m(dDP|R>*wx#5Y>upw=TyKEki9ryltg?Ei0*8}$NX)|mR(pr( zmy;msktNw_cRSu?0n>rpU*dy3YPIIh()MNn+@GDs;b;5Dvtm&~@$Tc(??=>5j8Pm> z{s?fXNscwIR35;M2G$;+^Y}0)$L}WrD+y3D3--OKr=o=!!;CL^6C~IA=+JvZzgwbO zo|iiUby`b0FZ6oT4&@7hVb3eU=E0_-_fqlj*-Q{{NdM3SEtjzKU+)*{5=AN;RyWn? ze${%dq+d=8TFQ3MK{1K}SnIh$*N<=;$ll@3ShD_Dj@Wxjv22^8HKjyR$bI>&o4Pe# z_riNx=XurCega?@<3?XRrVo^^E8Nin}0 z_9(;EW~yDk(F<^ODF1p*T6b!*a7pFuW`n47N~PBzsE`e}Cxmj$jhrd@Q)5#Ys8YIV|<=1)kMf^T_wfNlcy~Tx;Jyp+M8XP=9 zrIaB3SbjXS8duRI1_sT-2e)JUbJB{e8Wp-ON9w=;-GRhFs&lO0bxzt+0gq8JyWEe* z{`{V;6zR%89H!M5>rBaYzs3z)XE>!bEbt7s+Y-5M3T?3fFUcGd?Xg@J`&Y`EuCm+y$^(Yl$5=d7>3GrM*40l@a92lN?jeD;H-`Vq6`?aI*tMX(Wm(>}?a zWN)1y`nsqcrKGfu1NAjX!|zBZHkY$HUND=lJn|yv!S3X5N>_G1;VKAXHNTM55^F}& zHNg4inOV*;gN`^x+El4|WqDW8kJ_*%9_Y4@2`#Wvu<9x_0`d6NQ~u5{H_PEp@Us4z zSqN>NglBjYEz|G|i1|b@3VVO8L2pZk08{~RDtBg3&sOKaC0+%a{qK7$!XJrTIhk08 zQ}ZW_`HtGo@|dHi2g&*zBaXPpqSJYS6|PU~I_yC$kfHVvJ)N0a{YqVT%UF6bwinzi zNTr$T;g12epQJgok9M>Nd=U#w)7Z~q+(wx(DvJ!8G(lf!=@OII=qQwi+}Y=EFVG0dsVIFqq{|J3H z%a+7vIy%kV4Q-f(-+8wYzC!*jAwj)G$5IwvIAe^w>PB}rH<)gW!?409VdAu0n)2-1 z#yX+uasWB-*4|MROR07{oNS!A0MFX8KY9;a?+C`(Yq=#V?-{Xz9E9MBan{q0t!idiS`tB}4L}`fF`WtKTl= z9JI`4ZXW+d5`9^VMJB+#G~TAhA7tpx#EQ8p1W=kz);?ZO6EX@R;Sey zDxbe!ykDLdLM8reWO^D$zf{E}1kl;mV=-g{aKNv2_C}26&ot3{mN^c&6@mdAzfUDx z&4=N4ofN(L+Fv6eYapNG^L7XT>UTeIR@;T$wn+f(eNahr-Xp=KUvJ;q1wu_Y3GdDn z=sPHhJAV64z|M{{@m2k3+$HRhlu}Xj-C$cK^zIPyy_6^XNE>ia8u7j$S0T$AM1p$S zQAZX|PyB$cSgq`U;p28#fu{xXYzry5O^FSQx_Xd_OiFmEZMHRM1}9we9%s}o0iK8O z=xT#Utrh~E3a;u(N{tfFs~x~2C6k~F5@U7c9kXlaU}07b^?N&%c`z^6d(@WI0X#*K z&nL1%d|Rwm`eSkaug0gOR7-a@m5S8$K~#?}dUnevWms12-L2Vy_FR}%^oiY87B@q0 zS(s5Eb#SX>C~FH#&YqPOgspNf%~n%C>aKXxO?C4ydRed@>#C+9 zfB*;g(g}{d9x3Mjz!NgNGgX89Vxma+VAE}4J=Q1z1>*Bhh?;#lRuN7q`rp>X;?SD$4NYpp=o*DHQC^*^;sTcQMSb z{p^Rc-@Vo}e`tvW+XKe=rV_?A3Lv0pBKjqIIjLY_v^{D zzTK|^=Q9IlO-~|kGy+@|KQK+f%B6U+(!E-I_DVB)$X+&{Xg&o-M!t*Lhw{|aDW$H% z=~H!@p!W(m`caDSa&YAp-rbLk%2)jH2QYJ&Qc|CS##8{*ovmd%{lWzFcD{a1!ou!T zmpoNr)4M27flA@Ek<;>e+Pc9)N(2bX^+A}ag2s&@`P0KiT@xl;p>V~#svUJ0IY%kD z0$~c7&UXLA!f=R7L%^Gn00z6jTXBf@NpEgr6oii(UrM2J& z8TD>#(humL&nsZ;fxirJJ1bKG&A}!f<^6H=0x^931tKQcVjZeeDHn%+W+-rkgmIKg(f-c5B{Ytw|{@E*7AYaJzl2Yi^?r zIww~W=X+7H@KjN47e~oNDD}W@rd%uxK9`R0Iq?dO)1zOx{cgQ1BW_urb5_>;V=HkM z&%1=d&BfG=v3zNHqU-0Om$OxQ*boG#<8%guaeHhy{)n%9hh&K4+~})Z{YjA(QX6{E z8`)wT${^^HZ<@t@^Zg652>I>^RHf{XqPWFG7E2$twcaGj@>7m|r+h%!4q5bCTC21) z#AwBK3tjikc&RKq+2$tsFuA>DBJbM?P@l`-cNjPXj&7Rki8`IaP&BWqrI+v?y>g1k==$<=PA2c>tE zE2tg;0Av4Qet!SOl8(t@@}jpcL|5+u4|sO_7a4U$Kh3+$;D~Q2f;oJFq{%;Ms8f3u zHxu-ZVz@2Qd4cI9^7rN60xJ6*u}xNFa?R;O(e&>Vpxjt+ev%{;yg!e#u-u#jERHMF zdbpzvX~ic*P;$ld*gVJ!gO(&)Ta=@1%?izzRn#X0K-O+pJi??pA4tCaESFgbP;pBx zbKf4#;SG~e>=u~L#E3FcqPHY$kc+lU_hYCOX!yd7%2lk0i(WP;4HfEd*>g&Ts)xRD zy?4<$0bg$(m|h8KC&|NVadkcksk7BV0&$WYEi7W|Y8+y%KCGo^ApJx7H)TOwQ94hm z4L?hb}0maCg>kFzBuu)jx++u!HTh7akLdhnl?yo}KXG7JYg>DcZo3Tx3?(jEuZ;`bRxb$2+H* z+s?>7%o~;FajP%mz(XF3I{w~Bs~EQQDowwp7OIsZa4F9Oy=k}uI_ zgpW-rEZ0cv0`n$F@??|57Kh<~0EWPQD8)K|Xm>DYYHvpbC}BRo*<8T}U_+e1^HJWV z%iv$w>PU0|$C1$m?*(CB3lXmKlGG*5DKqB);0#yNDSm5$i{Qb?^pc!1 zzFe}5r?7F47>MdO=QIF(vfgJrexmxB*Sf~_Yb?k> z8b`!$)da9|eY}yZO*_25jrt`o20w0+u}9z5>|7yiiy@MZ<+-i?2HWs zLnigdT>lSUZy69}+eM8Ef^?^JcSxroB}#Y0NOwyJ%pl#N64Kq>%}68N-AJc20|V#w zdB5+RUuS+06U;qV?6vn=>)ISG0iLxR^b0q|Q#M97Ay^NWPc)T4 zu$$-Uid6a@d15uI^wCxsDqB1;xm@J{(j`%>%>(1p8aFz&=k7lS_vJr6SLt_0yZ3d2 zBB1-ObQoG&%PVr#9js*})L9N&Eqe&^DiZ!VQ(^51!}{zc((NCf@To@nfr#>8{7e@O zFt%;>I^P3vSw)oE>~ES7*I!2E2uRAwbQ-^F{m+}D`)y1+J!zxC%+)0&-b6u=Z%mWo z2d%2xaT247RDpw1&B{igg3A_tci-I8#hEsLbvhgKtIV^dz5|4pJU%RtV2OXbUVE!poX^wby1yu203J}`Br;@eaC(sYOms#( z)coMLAI>A6zS6?%pq$l_-E)?~`a$92WQb)VKQNZdgbEgd1^Zn{BzeFo5zD2AolWZZ z;G$P*B#};;z1Fp#*|PA{8f;ozk33InOkROgJSO8CZAu97Q?fhU$EOkxL9r$y{;f`Ro<&@%EOqJa-RU8U|435ct-0BL zw7&m_rfKC8bboSF=J*h+a=I4iO8h={XH~Dvu<46pUt|L)lvszpUzz1wab-z^&5qrv z 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

    d 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 diff --git a/docs/images/java-heap-graph.png b/docs/images/java-heap-graph.png index 829c7fe3ed117367114f2f2ee6623a1ee76fd26a..730437f6b8fedb906719edbaf60b57336f10bd24 100644 GIT binary patch literal 231736 zcmb5WcT`i`w>G>%0SzJ+A{qoN2c?Lh(xev^Q9=*Biu4kYUPBR3A&5$oB0WGt?=1mT zKtXyZKu~Hz7eWc`-QN4&Z;Wq@-?{HO|0Ilrowc*pEYE!AGxJ(QO_>D(g8%@)qVo8G z76APDlb-GbBlr|HKL-P!=-jlF6@j9z%VYq!0;oJt(D621nmCyPryjMfcVBEJAeWzL zK6riMLjJ*nmsa0muFkzZER+*DenuXk`-cy~1J_GD7ImKv0qN}fQa-=4 zQzcwKXJ%!=QF<3O&{xdcJx&YF*a@B75L%YaUhI{atJjtGJ# z^-Y%BMR%-pj=u&y!Eb&||LtVV!$^{w=E_<67gM zd)gS)I=SsSK2tN=nXf)KY8@(~#jqFGeV*NORJFG?>1=eqkIVc5k zGT&%y8n1P;qz@V`xAE!+wCRL*yN?+?_{YXacl_D;#~MJ1Vi;s=n}n*Tnz;8qtTJy7 ziSd8}(%x(7e1JYi^<#`#TgZ2zbRZpL*Oe+&%?BjjceADs`i_E)T>IxflwHy^^ZmmF zmx%MYMA-JsQs+%xKmv8>zc^l7Zqy${cdX!9%bTm*Y;;XA`C9%8LO>p0klV~R1wgo^ z`-IfLIH>P}YoLP@%e!S4#rA%82pQphspl%0L2rHWaGPJR=<>WT;b6-Mr<4Ew;e!Xq zjc+T^HGN%@p_4Bkegv<|Q~g)~-}vhB@@q*90^k8_uOeyN?SR>^MWf5-fx0^;)0XnC%vZBG*zPR_d+ojeV2 z$@-5JiWpTnu1m)3FaoUH_wAJ)HCjc+7rJ}Tc7UT8roDZ zut@b2TpGkBh}!l2HRXAZ?f-M9!V1*U z;rf$%t>VClL(^j^_nlWB_QZi`PB~{2KbA74)k5paRs8k8q_6J8%Woce@e17cUMLy# zuBq(w^@^SS7U((UI;-xmG%Bm)_&BXdH%}vwL&7nHnSYDYcVh~B9(3HJi5DDl)MLrQ zn>^Krg+$M~qfcfv^1Kn}imgrnt&x#o|KjVXp71~1ogc!$Iiy^Fy{tr%CK(O~8X9eW zB=&~0ayt*=^wfLKzJ-al)2NH{W!A;`tt6SXren@B+<`k8%$)W~=2n}AmG;ni+IcqM zmQhu~@+6*E2ztYQmH+PV7}jgDbn*;GYz@hGv5hAFKYg4zRs)5Jx(C0L@%!W2L(4hM zo+jjTTRq1HohDj4x1LugIBnHuIn5OmFNdlVDze(5iAth4;@if8$_c-%Q5vawWKHi= z`35tzM9(ZdEP)PTb=4g z506FM((78<8v1q^FRoscIGwsXs<$;;9qFXsxUyn1)>`qge0lV=;PSV3S*6Zx;=o1Q zj{!`l4I8f_YwS^Bzvs)Hm$}QGq&y8s&6X3calEDjk64y`hTK0JRWFwiLP;f+9AXCR z9YU4P%SI!+Va!QbM%mu{YDB(wR>1DEmo5eSY^YkGhW$E90ZL@tay+?#W_KFA#f=qApr^O?V{meM{&Od;aYV`=NHC%SefZ zv#1lX$|K`TZd%~jSfvBI``}3k02)>E;o%;#`gi2*x^EY5>7Q{|nxtPp3Gk~9@8^Ho z>WnylOX&VK*U(Tb-MoW_19$dJe< z_guDyx2V-m9cP+8-3DrAy9>o?)Y*qG6EavmTY@--hs4uEAp^b?0L=v;8 z_G2?%a%Hn}hxXT*HeKkN!VBy5hjR_}Tat^)+aw#z)vNN5an85d=Y6gN7hhCcEBy0Z z^;ns{Et<2ORd%O|%V*}SmkxnW{$MZFYqFGy+rQ_oO;<`@*4F2jnwpxkwh)Rl7_jX-g+FkG62w6KWdBGiU(v1 zDq#2(HiREOsGsc@($A{ft_4!~j}7@&-~$etCHmdJOum3{S1pgVA=sh>fIMoU!FD}5 zNum>RwV{~iG*oaHdI1{8H{}@~<-aDbSDX~=2PpgJdjw)I#qe{G@H(o;FLZM<=NiHL-!EpbinEIm~!P$lDx zXTR3Kz}!_Y%KR@tjO2wiRv>&s1B6aa)2(PXoa1CeEp?&y`ph7h9xwhLvosI}V?iu zMI7^=rLKFLX6!pdrsRp2#Ago@0rPY#u7I+7e9!Jlz25F>1}vbzu|g~i#C3_SX1%@> z2^z0;53}KdW*Z1xEo_r#z!!wzUf#>G*xt9DG`077r% zI_r?j;I@vS26WH5i#7(2lq}JCFhd7{XydikMr~YG^~+Tr%1G(|4B2&>`Re8L1taGO2J8#AQUtmt`HoXuR1C5DPh@`Bp*xoM zdUb4|w|JxvDrW@7?GdBvdj?en20y$^-pXxc#?^1xI~X=%@JH*H_kI&wis=79iF3fI z+K93%{~Z2ZajBBmVgk-nQquu*u-tIkUYT%C-rbn5p6y78XgK%-ceo@SLm#P@wRE5= zerSGNDk2JuS)vyW!(W{sR#_%^1gwkh+3cy+@6sHcdbb6XAi#>8%Qw?YVw3U3`z`Pm z93+HlPvR5?@96D$96#zcmCj5n+_ytR`D424s*o0c65w^^Xv0M{yr6dLV?WEaNE-&=Xsm96cKVXT zqPVlGLJOF(Y7VY|@pAk5zlfLeQ|Caqi%YFJY=s&TRSmMaaOPK@4< z5DWH1DX$_hS+u>FZMVArVy5KB$9qN~mi$cM)P42OYt|Fl)_<1L5iW2TE^_8KhSM46 zYvsf&p9I2H<9;!p160?wn@<1pDp;QX%ws(eEqjAkMw$L5!~^EPeartvUGq4VK9q^o zQ)-A1u%}ZQYES=24(K|&t zbWL9(SnK(N5Lg|R-^-=JFPV9PJlPBi3iN~PK%&bN)7SrEnU{zw*RFl`Tp71JOY-07 zB!h@?G+#USb6xO=uIK)a>!0$MnQkag^ZNJdHpX+e!NRR8?la1u+`2OA(!IOj{@wTC z`8WF~7JTZ`-I0}vdcj}rFJcdwiO%KM{y9AOFODJxpp&Q3{fkAxde2TH2r!39+b!Mw zch?;U$jAN_vvVR_fsQ`NZY{gCAA|{)KIU-v&G=dHLXDOsD^x_%c|s=Re}Rd$;fRY=_N#w>eeO zy|2;#<~#FUl?Q}*2Kv8=i6bXdu%A&}$%;V_->|7%f3tMsA0Di{@7aaEoTt9`*`lwr zoY-6?$t{>-Mu91*)dou{Ww3meu-Da|AjG{Xe`90+cz zfrGEc+2Z(A7 zbTc2kW*cP^djlTxOa3!YE(#2IlwPLKIPtP?{sa(y?PGZSRUk23?Lz&Z|M*{yP9An< zgb+|KbFkUJ)xvJ3@9thPfjT-6v+cRU{+@a0aDPL+e&<)DNzGzW{R*VY`~Be|2k-`M)daLLQTZ3uTHZiESP>C8 z-O40U2g7>L<@Z*8!D4rPH*Gn@ol^u2%D!H^c1_FO?7hSE>)N9CFSyx-OdZ8Z{^j6J zsq12O^0UWve$>ew?YlV)V&e`#b|+rOZ|nIeruvn_caJ9NHZZc}AA z*fDy2dm`~xo6FM9Ne&6ms!pS_q2Dv@_Ua-9_SJEXcddyV@%z>?jGfXBX3lu>lf?sf z_;$GGDv;ilFXt{CkzMRc$%LXRH1V18OPKc9qzK$_uP|(W70j zKjb>@SL}-UO?F=h_?3;Q%czkV&Bj`Fo|}z7>6xNZ_Qkb8Bvx4W#p-$DQxbi~=6(90 z{B+g@4i0LOx{~$uopx6{Yt`rna2DvYBnLHDp*+!)B&tK8q zY)yYMDwvqYz;ev|(miW;ZRN#dDqu?O+@MXrm0QY;ye;Xl?YT5!G20$@57BT-Cr_is zQ$}TY1N0;#Fqut=l|BFdi{FC)(vB+#Yv~E)=O?@ zEmOu*BwVk}P%+gG!ZXKg{ue0K_bLeUxez4o_zJR1S(g!vgyU$E-oap%R5Gy}_FKPJ>*3NusjWj# zU;(t2w{fu06)$K^n3UbMk3*A*U*DMNN)+r7yjvI-`%rZYD z!m3lh-@EXUK4h7z(Awah&5QZ;C%d?D*L()4u96kU69UXUQP9u!`p1J~7!=8F7R&$_ z$g+cU_#6T4vNUq1o{UKzPj*}X)nXF#MBy(6Etr_&!s#Zze(%M?>H>vO#tXxXmQv4< z5OX};LNeg1)FXCN{gc0L%pwul=s|jSD4HjK=r_s%LBg zW-$@`Xg=R9ukSnwHOu}qaxR9+Y&mU9DSFIIrlQKOzcnsVx4*7JwfmEd)5>`53Xyd_ z4Mkv{`mWK*-0w~q_#XCqvj>CBw?*mH87vn!9+o*2)wYIJgTx+ti4vLWD$4M26MaU8 zjsKJwNc`^YOut=DMiNRLN6Tv6)~7;Q4ZBkAE3H&y+`gE`WEvo)e6Sp+XX;9NOaUok zKNr2DC;|U$tI8;vydt;v?A~mGS)JVX-UeXGixeF8=<*?NO5w1K}MFs%DcdIBqo<0bs z=P;1>j`>&q$RjQBqcb{M$ZpgDWXw+5Y&tJLBcDE{broTE7hnC=R8NKc;T&C4w)7|S zMduNVf(h?stK0qy{4+`{hrE-U%kmn;tZ6aY{T>GRs5m zv2s=VC9{?iQtgR?!)8BCNkzjt+TV(Z(_TzO_sqcJ_ONXcPRF-H`L?ViztH?Qrps;0 zBdKvu43i%rChBggnb}6h7kg#M9&HU~4i5P(w28U78|f{t&3xC^U0naUC11H%Fsk&k zP=2iP$UVVqfmmBbWVv)$ZkdMfUDe!Pz0n<2&QVnRV@yo8oOFKD$h>^+z_CBPvPvWR zo@UHRZ&zN6g5w4830KZ&fVrQ&3P{Id`JQJ@8Gt>X0+7qTJq0qkFQfu!70lQPw{Dr+ z1-Y1r6EZ!H#H!A)v0uIcj>-!lxh3K-QCC@S0OISf_Jdd}5Xcnyy$*4P@a5A!@&}R8 z_U*BJ&fH+Gocrv}^CZ@Vq^0>-gT$u!SUtE&&n#fDQ0b=-VxW&8w>$Hp>RAh9yEC4N z%ex7qp2j6`m~!83sY`0rJ#Iut(l9vXtt{=^eXz_%?JbMz@u>ggcCz^r&T^aAXBsUo z`4%+ou}RcXz_ua7Py>)o?=jZ>IgD9KnRA#`Zr^;^)XzyZ9UiyBM(FPjKg~(uCy^~w z6vVv$(+^Jvfthptn})OQopz+JW4RPLn!M3;QkoDf&!% z+ML=%od+Cxc$!OkfoAOp1+5Y|$)d&MPBaBH+U6JzH_-(|=lV|H?UmG?!5iNf&%u5? zSy+5ObLCg9R_gh%_{D+zY14dgn}Wc;*xUJ}_nzzjW8SfLwT&;QcMO>R9_YM=YBWQk z;`(5{{Uf%9f8{rb7wbPpx5o=EiuLBIbBo{358;PE>{Z1;a;@mhn&-p zi6a5KkG;7 zwHI?)ny<2_a(j-pFf7+rZIAJ=&$xB0j(9XNMx{F8bdT(Ie?PAV@mm>DAXdVrvC??? z=A)5UlPrZg>&z3YX0WZ-%tI`T9+;p3hw1qmlz=xoXF%UwWveKIxBQ2KCbhkiB8~Uv zY&z>yOYZUrm3qRRy%4*yDdl~W+`BLL4(i~=KtH*PrfD>P1&Z z9L9CW5IY@bI@l(uW9X`SoUtXEOZxo9qs>L`i920BOk;L8(WzNuY{?c#B)<1D*R( zf>Al`H57+KxBqcgu>6r=!ZWVT?a2{&lSHQ8aC&?LBji0?|IF4r7okzCtZHtMt<^^ zQME&DhGP$UlV43%Y|iClkXYL^s(d~s;;Bpt84gDZ+xrITFvugCBA1($SnGT(`@a-S zdJk;2Vkv=Ohe75!Hl>_vW?<7=`q|eL+ZxH@NK?H+HC6=HFi%_+r0w5#V`;hn)z(h8 z0;!(yMf@92-?f2b?~^8LsIA3Jk2z6!AUxDyJ$F~P@_v}?pw4T}{#pKkd~wZgg=hji zu6r}4fb2t!Dcs0-6G0I-)etH=I3)_?wfjNBD52rQ?)`(uqPL7#ArOEfF_W#4(^bgz ziJG3ZQQ|xysn@I-Adfgd*d}695|UA8fFoa3qLZS^1OUCOn=Zv3*zbyn9k$;COlPUZ zU_I3(;~3_9D#fbwum zv=<@4Z-Y|bSf)qXbdo%cC>mh8&S2<) z7vROQdQakc*!DEy*I5BdXc^Z_R&LoPF4IA|nGC-mF8|p~ChOI@p$OH0g}&Xs2tyh7 z?qu=qxCzuMetW#z<21BAE}<*GtMLakaOnPEgoH^kzd#Y#4a>b!33`0FWZL0@SP`N4 zFniG`K)(5rjC-3HgG-4e^DOMtfHJ*FD8yoKyF8n~s6=ME75cX|ti0hljS+QAv79Gy zFQ(7J)Hjf{TI*XIZ@$|8XlACPF#*$tTRZF1&c<-dHpsr+t%pCWxl#MQXrkSzr|q}Uys@o1Y-_y|Z(8$2E)CW3I$)=~*Jj%+T7lOKQ{QpCVc{Mb4+Rz7jpe#ALkroOk612>Qrx=E3ai z(E!o5yR1Ad4P)xN*bT<=3Gc+#8V_k$W08JoCI?6Vx^I}H$fpZX=xl4#esb#*0iC?5 z9}RBU(wpcBdq( zJV0@}uRYH8vcXj|v<*d4gQT-2J`(V7FT>T5BQ(8vRG)akr?C?r z4tDi*I}FY=i!fl%>%R5_jAfeL?K9i@`1Y*{R*t1CA-y`gfXIEMp&2q6Qf>z!-4PP6 z!rhi59e5AK)>cjJQMZO1Z0I|>Sr2;xO}48!rTu?IU%u~*K=N61%etzo>i2E~qC9H{ zZ&@nU1aU-8Xz^(n;O{W!BAQXn8% zapb$ErkDcL=g7V&`7#E;@_g@?pvk{B;S56P!ej~to;_e3!Q{uBwAs#u5ZvOV}H=4AMz1M08Es|xLV=HPoz$NSMHUcmEA}m&R)UANSz{!;z#*=W;lS@NcMKf0Iq^dCW=yN3iRw%z21h&oZ^2V2G-Sy z=`pK!A!dBfvq^pmZ5qC=B_N!X!*1px<9HuuGd&>9F#uGS^D^Ff;Ct(xL@| zwcj@;K8)TKa?Cg5oBZA-dt0^sGXh^R5vNEP;asc$g+m1b@Hb#tk3p2|uMI{( zkAQU92V~+?-;&Zy>>gd4aCQSqU``U1{7>ny%?6CC0rX_A_lG1!KU1qaM}WC;2mgcYqw3;2%xH9;ofM2)s|G%O z26BNvDErqn$Z^RCdGqdso@&wE7ccw*0=o=W$m3hJu)*1fYVw`UQGB4;SA>RLh_bD2XD@ zh12~K?^>6|kdv1y_H2!HE!80=_Qq;(I(alnASG|vzQO3~z1@6@`LnH?Bei{7qOIAk zN(?-rvT>6}C5!wcyMw#EHMJqW_ONNh(XfvWpz)Co-Qrh8@qri_Vm%lWz?NBcT<{{ePY%e%X7os8t(=FwWpz)rL+$Sojw*AbUbbEo?JC0D zv6|3!NAqedg!$o1oQ${Qb~dw)MMN7OW$C>;`!|nleq4eT{@j>xunA~zEEzr zqm_I6+JA^E(ZG_B!h-{DKZm(zepowET!3D!TQnScG?{h~ikGU0!=f^#@zp%Mm@rB@bQEGe8j!s>*!ugZxa?Q6Qrpss zb?AhHP<>uda+N-mS?RtS1F$$$GifL;q1~j?FU)>BhFDi!+ZK3anVbA<1Q%PgTWqp2 z^2zZ*TFP-({uM7u~c1`=F`} zs7{P1$Fjvx;0;SI{fzNkCH*7}<@&`##bFhx(72d2y*l61+u45h6rx|te`%Y$NB@0;qy zT8T)Y$4^*$=ZScVh7lM!hq5B%@Xd=B^3B5d3_o!$@MhBd;M}J+kwOhQYagGmE;jlK zYcwgdo`dAK3@Wk!*hcctR=>Qhq5Q)P*=(0YS?RoPN`nvLNJmulU{D;$YjhwD*ihGs zk}+Jon;8nEADWFC{p-9MIUJ=&I#>vfa^vqm zaQW(?Z?Pt~4zDNJdvQ*tZNh~Oc zZ6q;L_UcSG1sMPDD3*mW}aup*{O>84TH16VGngS0~by;d%l+ zvC9OA4&m-tpb_@*0-_9&B6C>RdtoQ9U%zfrqavs;%$EU1s$CyMng}6l{f2?<9>mvs z+G&glt<)|*9oI}BWSG!e!B(@Ggr(Z;E4MrnIh{%Mh90@ARXCv&m>gi@10!k3pO=Ls z=Fkd0mTw_Bd6`E473zL(>CwT%WUMyKY+X(Cn!Aecvhs&#t=mIGG!xYsl-PdtU##uQ z^}219?|OGuq=7%K&YJC3pXfYXpm4SUR1yUi4%<4CyzK4JR~@9)>2l47KYFE7sFM=s+G~!;yv$ufY)?Ds$Onwn9Xb`kQVriK+*|(EJ1w?0 z8Zq8z(|Pcn7QO$25KtYtx6Q01`$GFvwUcp+bFs$VE`ASeX0_9B;Noy?bfM`{HI-}Y zwTY)I9S7-%l>Z5uy@`1+JkToT$z4(!kepI^)Ob|-#n3(ZIj5117u};ubGjzh`2v@I zidcR?g{VU~)(dTo8DuTO*DsO$vDj*81)*mE_X5g*5J?A`<)C{h+bfT84 zej)>%5CXTB4S+#I3c=PUpv`2u8Tp;H0LR%fut(!vb9(QQ75sP9XD6XNTe*mEqwkhgeSMg}Zf zs5f$$zcpr;R{6AXdetx~=S+#Bi$6bvq>>dBS-Jlx@AmtXoj~!xXGzGp7BFs{$(&57 za7J+HxI7`{^@Li0(nM%VABz+Zn3nHrl5J$JVB1qdB<@ndIfK+K0E$;j1`mUQ$|-YrvIvAEGwT zP!JItEY^G}*udMQd&rgC@iNG{j>n|vKF`7ZVD z_K4>BsiwCVp__Q)`OOtU%Qq7_WI`kW~_-Sp=XkJpi-p9Vrx_gx*SID+1D6T}42I!jD$4 z$CrY>Adi09{*~iqzof{8T;Mh2^VbV!`XxmP=L_jxxRE46KoXCA^ri^gxs} z?WN3+$NN+>-fHF_K7Z(=j?Tn`PZc9?6F?L!&_zAEY1kJcoeX^n*g(NOwcuhCIFzTsjYC{Eh zKlWSzbwY$ygw9|cbVQd|k8Wbv3^g&E-9Qe)r}43-JE&i}QS6)k;vRn{W>FM8F8Y!> zQx7MBom|z(1RIrw()GFlzBBPaI)No4Ixpf{u~sa->60j59wVqeNJ9zItrD5Q#1{FO zQ*Sn!>XKg8ftA2qpezP_R037a#+dYvA)=+CVd^*|dVU;19}Sat!E@k7Eot z7F5?u4(EwrWP#OK_Om<{7l!vQhOIUvo0r`0QDOo3`3m{$@b+&E8$%Q!yX2or2V!7v z#L)Kp3oh{@cC^$CdcGuz4Y?hYs4=ev{45l9w zFNi1K5=NYz0T1>wCUu^t>3}b)Lz2j$Nth?PHMhXerGG}@I2^`)cCCx1(y2gC4$_0A zsg%>xW+Mh)yhcG+1`5y!P;A^@QSdG7stznd=6b{DjZvS$un?5=OK&)AvdN=(@K_aC zi3Zy-ocVAK$n&d^J;m#vzZX$-KY*i44p@l${I+lvB+=ZmSbOY)>4PA|a9>Bmw?*nl z4jLq3$bj-dF^DfpsR`K)j9&)z^jZJDcai^4ey}B8$ZREq*>s~dQRH^k`W1AJ-7RHH zK?1x*LPz*BsJ|aBOQInxuP2$@{^Q#NJHa99utsXCg(~z|VHn_i`}S!&=%3Ddq}%f;iz05t z!wU_g^%Y1Ecf1}=GoNoS*9>|X0JnTft_^!((gMm2uR^WPiOl6*Nl&`V3S(zs19Z+_ zHxV+mVDHuoFqn2pDpbD|VSZq!9c6DkR860L7no$rZ7#a!Bt8y?rWJuY?&A(9C!#}V zy=uuLQKV=85v}@9;~Q^!^vx7SI5dFH{OJ>JBotN}A?xnmVB4XA#Q$uu(KGBCn5Y(< z{I(~R#9p);gTWzf!m}e*+7DumthH=YABk`DB7&rTx%08fYJoRwWjA5Eu5qgble!^i z-hlks_K$;tvdh{GKx6q^k}bR2tI*Z}3+6^M-41DLc zGL!-qdRg=o@LWPqU8x%hPO+Mxe2 zTT1!QA|P*ipt_Omh4z%z;1(E6uz~jWW=?LPG-hQLV&6VmO*Z0xQn2_b^H03)TaiRz z&*d(vA`AFki&!ne+Yh_yaT=l7wN2!zd*mV4U!C(F3+8`>g$D8)M3!=x=a#eghTer% zlH(Um@R>&3qw*udv*^viKIue&#(`odXwyU_wO&5J?BH>{Z{l#ousmIpMs#QLqyz{I zSZTqu$Hv3yNV%uc4#N4g(~A%PRA(0Iww#`n)}4lR6ZFGzuz@{PWFjRF&pa$#uVlVm(}bq-31dpnlh!g? z14KPDrczp;>704gDZus%TDLBa76cX$E`5pdhJ#py$=M{3xq*qty=%=G0L!6uP4sI-GsXfc<3+OQbzaZcsGGSm7;~%(z5l zD@i14TX*h^XD`@{^`;@zswDH$*CRCKQGb6slO?tPY=X=mT7;$TT_Ht)E&-Pp-WoVT8%U<(bz`K zCd4ZD`h~RqjuX&mzHFuIZbFg#Q=gQ8%@Xy^K|Ql&t}<{QaGLAQBQ$K5{vkvxKi@Y_ zQ8($DR)(xh`od4u*9a2Dx2~Dj5p1ea$*b!={~eF=m=%Lz0Ys&%S7a9Wyz^%2`J;WT z!YiZbpZJ;&98xkq<1XgrdsD4*r!u4AbTT}@sPGFUzQBO(KZ(za)p0DEON0uOi6wUm zfoMrl+_!&#trSwjC&6=*L4Om-Z%U#^fOASKARX-|$rZN7mZ@VX7YmXw`5X~X+}c1+ zgMYtekWCC{QeWi(ih~%AHs`SEX_u*Ms|)}$lz+xq7yIC*gJ?kiqpLS%@*Gf`YggS2 za3~+`X2!9=JUV&2se(r8v9KFZ;C(8snBMzS0s)$l29`b8BtAB5gb4{q=l?9_*_I;o(;k#g1WQuA z!tM>e6)7yHMLTanqU?zf6D&&L6;`&;fq$G%p0gZNIdos+qPl|0gpm8;Z1&#kuG%fQ zCoMgbRS^>PU6s`rdkagB^JvsG)1+$REqu7kq@|V{e^4zR4kt~XLd0nf^5VhGD0I+{ z^mjwI% zx;)iXMy?gUAdeLfQV$a#7Xn=;gkVqE?woS-1?Y6pGFz+f3>ww%*Wj zSDH44Woh)%hYeS}g>h@UGNAFk4E7>inRoQiQg*lQ%7W9HLLx(XmP0#b5a+xr4|(aD z%mR0Ltg8gjOduok@E+TV0q>8kx}t$Om-=;Ll`~jLCD{Qm|+86aEeR}WMP1yedSrchBU2#>b(9^W#YLfBQ$p5R?FhjQq+)H8?0tc0Qt#&tH48Q=~<06L`Jn$#iOug9?d zP3xB%kP=&C9n0VCa^7LS{@uAK(r`_EYi!&d&7bGc>#a7BDf4ZpNn5+JZQ21bFMneR zubyD=_U5YFAh|)87o{ekb0wj2`F@M%vWzX*K$6dhJq|bi)H2R4`ty-S+`o^#< zc2r4xq_*42K##obS8Igurw;;ke`Vq7jXyDmh$&=;S-sP@ulI)<9ZUqw=Ef;5hDT-G zsOmJ-UPst?yO~$5%+bKXoi^d;pA)v0%PO}9vIZUZx(>wl3y}p5i}i)RvpA>uPoY~2 z_ol~VYd}5N9M@E3pPs4hqWG!{^oTMUfCowQT`}^(!>ZX;lRI=xgzIwUtEjP=p~5W+ zhnYUw=&8XXV|9x!*?FYwi|&5IsO-+OFy@;CPzu%&FWN3BWkFN)+y0O-im2a20ym+zT{H+TNI5X zh%T#-Z{qJH$-KG1VU9>7GnG2&pa>=ggRt67>m2vG_r+uE_)(vqN+!>JHM9l=my<)R z_|F!Wyo**x#`~7{DP??q0m&!tP#H5_^wTj-@42$s#(TdcalQ0-2M!<(Fx;{c%te0T z4a!DlUzTub!rOtpW4sDZF_r%l`Ez`!xRV_(ojl@fzmji9yx@eAar%W@K;AIX71gjeY`uZ2loG z!oK_IUS?oAQf6VCKYcd9;TZ^yRzky_F_d9R>k0G9G}BkAtf~+iIW*e2$gr_1V@QY< z5u(GZ9$?0Lq0T3PSM#$|M{=9Y$eoDaeyd7v;}Ng4NbVY_?TK(_n(qj)&I7mD0U_8O zy$5T!_6Xl^I;0^nYDxZbhc@&MG#jC>#htj>44+pKgT#^jy$AOP^gIsPu!Hw?(g#3l zSsczlamVw!V8o@1wJ_{}PUA+Zv!R-A`XDu%$uNu!DybuBN{mNuqjNR(Lj|+M-Kjnd z^A#q`#arJUAnJ<>MheeOvMAh?o2GIsHVpD%1|EV7Bh*3}lh7Bs=3R}X*1>HLnT5I@ z@gaBa_aDKsw2ggfM6s6f81-zST#x7isrx+*c4~-eN9OO#M>|b3Lt8NWpJKeVvPVdbh^^+Tjaxf@-njwS!V%P9s`YntZVgA_x)}e6+h9*dCopv_%?n2tj2Pt6Q2S)qqDH#P)raVV z@N>Wb4e?6hA+Jup3ImHsd}d5`QY*?TdtC?F&&(1AWw_ds{R8GZ?c@ zr6T%R>yY~=5zJiYSLiJuapaF^?Rl2#7me2Z{$Cz5jor;fIP-ZuIwQ0U&OD|ot93!- zHtrf9?B?L^KxOtd2?&%=0vD4y?2e{*!5Vewpt}Mi?0?KqKs`gggjQ&pC5t_vCKWY< zb1wDk%E%u>+dBt)gw{Cl*8-vz3BM4NIu|vkdk+g$+FZBx(xkd~eg3dJ_&w)}J;Mer z2QQAE!lDTsOy2UCTaPJ+TwDB6!ze>Lg@pPrVOFU6!{OI?U?8$;zp{g2D$roNn)%{T zEiPrS{sWhP!(9~d1oo+*f$JH96WCK=&jS9 z4G~5;<9XaL`osD8@8$&$I~lGg%jop}o;?(I%qPDQ4O?U1K$Z6Ab~+ck#4(?<=We(# z?8=x-zf;dGHj-F9Kbm&5RLb1l;qX?xrZctvWfiDhspO<8LGNkZ;rAg2G%?piw~Q~~C+ zXRk@U-eAM4hP_emsJsCpP{=!oX}-R?y)ER6?F}g?ER#pAi5cLxlve_R*B~#? zwL(@e7!Xk=f26yvhP_&b^1&||0fehYzYmtrsR*f_M@!J!Iq7s9IEs5p0r|5_8OYQ5 znI9D&)2vUi&>erT9^@drTQGX=f4Pr+aR!moSXVueMG(lv{U>yA#0Q8+vR?SJXU1wRZSl=6lviY!{_G3zF}8f^+xI zNi(sw{CV1VUk@BO7+9cPf%z(%Lv;e@Iu~tX4%9p8GhUWzZ5);(?bN|dQU-*-`M@6o z8aRJYEbO?m_S3m*uj6+84^W-IN+0~%4&10gaHGthb)7H%`|9(5%F@0!@{NH91#a_Q z@mH0?*+#A4hIs)CLxo0f9MjN;UJU$l<0Q`g>Q&D>2dPJg+n3Mv?J1qKliYraDIQYQRSJ<9FBcof}84{yioB|LfZYZwma`_X92&<@j^|{gHgG z)_vc^ST9SM*L$t1A+E}Y9sSSH+rOJt)3_2DF8|`><|+zfXlD%mW`*$8z_)*^Tl-IM z$n*ah-h-$^>R-xX=1c^yV^cI&H4JN9zdV4KannX2&%&Hpu?azZTltG|2PFJC7 z8R>*G$XYhJS@_3Y{+z^QrQP6I(d2f~FzbERjmP13Zn)U!Ql{;3RDC_fHrn;bRGM%D zogMYQ-=|IaU=*rp30AkjA|Yc6zei2;-A)PzD7$pya0CTuSC*hSRyjye38-pi{BPM; zbPe2u7I0bv5`aM-nOckIUB_FmwKhnRVt!-zlX+pM}Vwf8#)n3 zolx2*XFM%i;nYQef6Qbo=aZFXkk+u-;SF;Bs*-fv`%5JJ1Os6I(cq!0t*u6&icW*` z5#gmPHt+~e=Ua9BKz0WCJ8GbglO;&%1o1lwuk+lbyx@`dKk*O-wen2UiH75rGq7bjcjxIC10o0K*YDEc%c4mBMBcX*8O?4EC}9X~n39 zFM&L|H5dnt<8+VX3Y8KKbx@lPwF+b-^(k);#?eNBqEd5|QG6SP5O;72U6IMvN?pq4+9KkT}o>Vx-Cthm=(FWKq)A*A)iqL%3O`pEB(>?HNzSIAKVl| zWo)JG7Lc?%DSK7qn`NVz+ZKpUv;cCqN))m_*_Gadm()qWTfpmn4ir8sb~#z8@qTg# zm3n>^4eTx|4a-Boxv%f+V2kD+^dBpw4F~k9AqqIi&Hqu&TTr!XG#&q4>P`U$=>LbW z|KI*e{)F6x13d0!M;$&toM5_+y#m7$KgptvGDHDPVJ-$!I8W5W{(Q*vw*-ld4uZ2& zji6YIlj1D>yL`Qr!tH`5u$a!_da9=?J8H3>4x=`GEAV`4P^8n`fVyG!Sx5Q4@#%We zpr$}?9i&s*fV0^ZJHsi6NAUiNI6?tf|ELvtH=x>3$-hndkIUHIiDDcCGBSV6L%F7VL+c{jZ(v|HKpS%aeoiQUn}e zuNH!U4tQTpt1?CJu*;4Y>7}Wpvzf@6(Lf z-~{0;3*ZIzd_L#P{M^Xf^SYilG4N(L9>P~`mw!kGLwaVj5ZE-jD(!GDbWCPYWx_+4 z`q-%h4)Y6wq|me!?>d>EAB1NM;H$i3vztJ6ux6uki4!Q3qD*~R2H=yBSn))HsvwJF zSt24DoT&e6vfBTI5})(l4{U6;tP7r)enW-y@B)7lz#89hw_unR$okMLl(^Ue@YS{!*mqn)NaDWAYzg_;y{Sl%^g*8+w~Z3 zV0?dRs86FP%PK%Yu^)5(!opjS7uaBBJe15jS9tfoN!b41msN=H4fq~CxX%|D{ep1G zV3Ww-4h#_aMd7suy%^B3tnVNne(+zoSoOL|D^j~TC@O0}@f+p8Nljo-9SK-!`WJF6 zmg>MxIh|S(XhX@XW`#N>&_qpZJDi(OW+;7Th2H|8m-mq*ybWth{5MORd1oXXmdVY} z_t0G#qr(!9c7OQN(5xCL{1G@hy+MC*bvQpsl9b1y0}|e^_9uIlz~PVG5tSBCH;>!( zAd&g*WFxR=axE|G{jbyQWu*xDEDI|m@}FSv|I1dD^nvlq12DEFLAf+OP)!nbnvN?! z)E%Ahqpdz~Spm$d$4RqEZxopeMJtFRGnCt{!SKUjyC#J9{I6774H6-Ds(*c}(`j~B z$reZV0JZBxxDjfHK-VKXc`Ys~ss_H~@T`~2CE=kevK9QQ#b)7U^*e;B?PC^&;ab1TWjHMn_f5>+-U$*-g1r6*5#fQ>(Zyfu|z6t+#P8xLVNGU?UVg7|1 z8UVjWu|4$vi#7eKf;az6asRI|G$ zxPSXj@g_?xX<6&PaIUFyEtT4%l_WJD<)quoTmosm4$k#H+`^)ZFx~N;PI-&7@OWP&{}a8&NR+ z{1_HbN1*@Z0grZgz(C#N+B0b(i_C?3{`xQWkO?Z&su6AHEjQnXw-Zc!Dl;{fvU4xH z!zpZ)IPa>4e#;Yw+}Q2*D=(=Wui~D}8Vx6Rm#(S}MfN&muo;gU5>*=hO>2w{Y#L@% zfBSxM`b*7`!s%w?Mw3j2Zdz1o*Zm$c1VnLTcTK--+i z`XTHnbihh8D)s4?zdFkNy4&u`_sUA$+t{s=9BvS2lV;nC?sHl6YDk#84GxfO0&h_lmhcF8e|3rS#dD!>km$g zMe5E$@b~)Z_#6x9jig>b_-&yBIz9#S6|1!fd}=Vh#{29*o#EAMi%WaTupulnX$hw4 z)OmY^R@R&j>gR>KBxR`Duv|Ki8JkY3el8P30?JLS_NV`J@c-c=)J8N@+F_6KVpTOa zc`c|G)!)zlEoffOPQq!ujWt{}c3&Dh#mrmG;B{78O^nK)TMx*7ITZBlE43J>+QAn% z<{kTtTmSK}H=Y>M7U5G|UDFH&K}TYc*9&aP=@T=}B4#U7g}_k|?IOp#!XC^UL40dX zmq)W@jcfELu>#lrOG4mrqqF}ST1Y+9Vsfr6@m<%_fV!-?|JXCrj$UAM-)45g0w{M# zX?K2-r9R5)aiLk1YHZe$Cc1FOY;3yi?yni1`kS?d1Uy$5m0WqZq(}UW_8i659r|Sn ziUM>k2)=po&>zg(E|w^(J=%~CrR_JNfsKp`y&mGdIXDC;+n1Q@(p4wiJlqcLab;O+q?f} zRbd03B{zm1E*q9E*a<}mm8#+{GExoYR=g!}zzrFfv8Spi zz`|FoJM_GIA=7&+I10B2`>#Oo`o;ss_vQW-fVPr^4K%Hpsy}R!(E~ksV!&xQ3Jx%7 z4c|r$E~sjLv-zY>GVWtwbZDX}TV2Gm(m9QV=kEMV>rLs1%e?OlY_a3`M1iTzJkEbQ zZgMMBEFgUQF@UmdyW|=a%2vtsB1*ZmlxO&Q+vM9!%?<}7J>G|r@HkHu>Zmf!yE}nW zSjP(P$iRVXlvu_ci$?wOCPx+&tc1PHBG1_9STUoW=-D*e8@JKcB;tYzn>dHpwZMbp zFSK4t@U_lSxL<*qVXsNcE;S0&bp;ijsmsbv{Y{T5DTkn*W=Qw!@{sJZvCgQ0IilSRbJ1%WaUD4?NeCP&DUnE zK%Lv9Igvol_vUkrHIvXSG@wP$DtOu9#UPPdx+Z!=#vKz-*&plbaq{jDP0z6RTL4R0 z3n7ZMN6R+G^Bo!9l4rnL#op$O$GK!mwPCu^KytsiYRhE0_n!hW<36lOlfWjydD6!G zQlr6G_8f2Q$bgA&fbG@&@J!aMZOYRQ0|Xh=wUtwB{ESHSv9uD0&fS}}y zMDcpPadqx*fZ$U;jPFU)O?AnwUE0;jGQF_#y8C#A!OO!7j!R7?a&}{$DPY@4W>K=Z z#=&tk&ArakWT-7~F9QK<&P$4f1K4dRo|oX!M&Qwj_OqIQAZ-tYj^$aKJd3$ft~MA( zC~JbxY8KxSYb#Xa*4)vkfxVIWGH9{SY*$8Q6%RZpve;H@3*&2Ii3EVf#~DNtHc%05 zX~Pope`IaXgD`M_0r$f?8eo1Ut@2{N9ja?aKuHOET@5Zg@%i(|#R`|G<|)3jmpCc` zJmq%+S*6VU@IbRc%l;WI9N@*%cD850B;-7tFpGGIYeoTZ-F3dYo{`8dff_JtPM_cW zSm>73Z&K~yH?cvy%_zvagh(Mn?{54#WD*3%{KRQ;{n z@@J2h;mqXOuMn1%aJqAUP+f!5M$5|W07@4&X@wODUZ#{cm3Vhn>(Mmo0}&~vq(kWHzNW`rFZ=UxD0k94pNd_`Z}fuNf~oZ%7b6|fZKD; zY^&vrl*5FXw8PsFQwz3Ru|{am=b8IwiEoj&=l3pi8?R0@+WGueZ90bxKwEV^bgg=- zGS1fa8BTz3|1+D)Z~KG{+BIsaCqe7S27WV~1_wzst}BHvN`P(eNOO(Li7_K%t!6o@;TK7Beyz;mu|JH=;m}^2$Y#Oev9o@!Qgm*0?2?@I$W7~+2mJb4-6_&s${w%)Bp=k@F^WvQAdl#o55K%*Q4)M zo&r-2FN~xu7cQ6sKEj{!>iU>b8n>L(d}%SHgFeh{Ch|&PsM3|XRQ47RywKl1H9O=h z?~nEDV3HUGoNrZcTg=X(zUiIM^QqA|3uCImBel}c4b!nEs(rS)(m6B>iXyE51-3LJ zi%kk6842zJAIiM*j%0sc)ED`VXN^#*v@ZPIzue%_ePitPP0(sd@B0GZc4FH7HXet~ zG>Qq{5N&B(iOs`}w}Zde`SPcKAcCDirKa?XNFBU&0v*^H4-s&iXwhgH`mC)o6(}%0 zPSUEOYV^EsjB3t9Tl187jin`L)QVgbuhd3@--rRMf$`)|D` z+EN4cX16#eQud*=IE%7(*39fSW=ZKT&eQ?%C;%zHa~FcFXFG1mZO>auHjVEO8Ve#W z{0A5kkWw#1=f&&gmpK=P-34VaY0VR7k@qhz2*r41_cI9c2S!1W#8si&h4W3hl?Hjh zii=*<@{6b&^kqnmh^r;YYPET~=hA%@6l1BTuxT;OU`{rhuiUAc!4x!Lr+*>yFo8+x zynZ>0qW*#s<26iZG)nr{c-6^_;`2kPm#)&pXVjATPxE2L^RWt|b&he=@wp0BSkEsG zdFi3<$h6{CVTq685u2nj6_c-s%3Oi}l3SR)gJ)5(8dO z-V&-*z5Zj68P+DgftduW8qO5nn@0c1scpfsr`et7dj}GANjdj z@tQnflZ4o`q~t|u$4kc&Yul13-t61EZT{?g@WG1OThIt5m>fZXL?3dd76~*EVY9fL ze;C4V^)0;^&E{1*$@7P833*5F9rTkvf$7Vl_P6ocULlQRJ2~7cVwEwk$MbDAIL)e@ z)?NfHapw`(cfW>v^joE|tml6{dmnZ{M2S~I`(%5$9~6lI$r|nJeahF0^4UJj$Xxp| zXOrorE<5&o7}SS(Pgr)Q43$Tj7ME(Cs!B*vESd-YbK-UH8Kr*tdUu);SQ{kf%iWE9 zTKWor+Uv;U`srLacmX@ZntD4O%E`2<$m$tf#C}h&G*8b&=nRbq^#elbngcEWeia5K zzhdloak6QLJC2}9d(WC4ho3B79qaX7m_)tWunH;mu@=tJMtot}b|sTrG`MH)L(Rbb;LNVq(gr6DY2(z{F&;(6!E2fi+Ek*6wUKPmG` zrgcfS)t{VMy2<@Ho{4r5+j==MkySMX6{a>uVXC~NxO;#&S6Vvk)uE9T$&LFZY4G2_ znD~N50`kWmqA5o$&V63@SlS+!o#rIG!B=rV$|?m!7o55VoQzZ5(Ny}^%Bp+t`RzDX zwpxWUgSd}Z8bVb{7QcnWlJ+KysTt9%Y&DD8jq@T^e~V;=T?8(Py22}_aukD6R)S~C z?Du(!sw~HI%oIzXuCA}78f;JO!0_AJNyj;of zZ@F1X(i%fQt$rfiq`}B?@_hG%gqe@jMJDqjQvubqB&l<$d+6@7##e$(TFmb0oW}g7UhOZtw)N^DWZ5HXV!9ubJ&HSo9Q_=PZ>(@|lkI+_UfL+MFtxIJghndNGX@ zCUrxNP$u{P#%_GIEJy1J_yOJHEJqv=h!=K#c9lcX<7_;_vZfBEjnXAwpTh}HT!4kNy={Z7yfJ5RZv?eVmNe%c3d(wtczIo0Rv{(HE^p z3`%ji&V<2UE;?USR9z88Bea4L$yFrT6|>q+0x zvN?ptGtjQ@_YBd&!TrD@*71_FGxY&BqJ9V~DBv%9#Al$oNkSes+&CU<@z?t|(Ig|% zLyD53I%nv90y_RfjdpG5$IvyD2O9!k6Zz_bhUjkux&-i$@R{jCkS< zrHu}8n@>gpzAK~)_*|xFrMao%v({c&1<;Ud68zq#-TJ6ke}`YItJjjhoq&9{uq3DG z*TU@cH>o;%^M#UBFf6|~Sg z{@gAu)#CMEJml?pnP|~ zY8S7*+M6hK<{T|s)FA@YG(El^FZ)|4VbP$+z|c*WDqEl&MI15>ys*@0HK! zoOlmcdu){T{ST7mWde&e+~jJg`^X;o0>aI)p!fvQOvd4%Uy9ja5El>*g~Cgc^t9)B7VN^iHH%hlcMX9o9Up?Bd#n{0_=b&1^AvT> zQjI5nerv^c(wSF_(I-9YqYcT{K!%B_l|oxn{~)=hw@>QX9tPTy5*9Q?ax|@_7{*(EX3{~pN_uYC- zR`VgoKW0kxwY*rqx7qqw*jVB>=^MA_cWjq0+t_Ux0zQEzmvW+MJb}2wDF)L++zKhR zKeE4cg=EkD_2KlAW29vx$zxC%e^~dStb_OpWIdE~7eBfZ@uIa`D@StLx2!5{dzPuX zwY=aQm7)T~D%(rMZet-Uc#nl^2*66c13~0=FQs5BP@~FC>Z9WE?>+Q&ujYFiPI#f_ zc&NZkuENzeBcRr(<1Y(8MkhiXz|7EI`Rj8nlqtBl_oN<0o^&QP|t? z@+3Hkn#`QB6UNsE7f4P|z6s{2L}Dms7bm<>Sz9dh%ZDj z4?K9Nw2VKF=ru=zoX?UrD|q!ed!)iUXU=y|X9qGmOV?O6vO=2$7*gHGc3T~#E~VE!ohMU4RImm|M9C=JoS7ooa56c+lWx1ok`l$v8nD! zRpzY2gC8YMWbgyd8uOGKbgx{<1pRAkNb~Ub*80RT9p4zOMgQ^#KsxwAaCv#a z?Td&fuCV48B@Gx|eoBp0z<1CnuR}{G+JP*kjZPS5 z1e44&*M?EA2FKin*O?<4Qa?X=F7+Tm04cY-eYOh6-)Mc`Kf_4Ijx8S|Q$QevtGT?j zdJV&vyIwG3{u!+!XNrq0(TJ&3{8jxZ@I!?BxL@)kC+%8S$Ez3UTTubD(6g>=6ZP`R z>z?nl1PZlrBITRjmj??;FuvnwS+<<$Vo@W_`X?sAq-oum)e~qZp(y2Q5`^Ld*U4P= z2sL7jo1z<8=D?a|bquz-X0s&X@a?a0r<4oSHs%2w%nakNL1=@{M`9AL>jfxKg0+UIr#S2zRSRbwvnhE#@~D~&j)#F-UablT<1?*~l?l08u; z+yYO#UzB#=cj@~)Zrjgl%lCJ#^dx3&?id`fn2{F?`T1w=pAm^Sbk%WI*)jK30!|8ZrkjpBPi zr$KQwLd|8v)-8~6VLGx3v(&>_`+PfYv;?#nAUc!p%P^uHJ$_M935?hoh*qs^8~YL$ z?Yzy{Q8U**6aYDHI%H>coHAro%M$iuJ)_7T&vL-Dbl?o|=tI8cfeO3df0OxDA8!$N zgucb{Sp)ZPn9Am1h3O%(orDk?TOKl?ru9e~=nT14Qb9n>#!<_L3PGFGoRKwC9rc?i z;Es5mzXz2N2A~&#-D9TvWC3~7Hzq7kpL`qrIo;G+jS)T$^HB(>g~CM1lNogkIq_Q2 z(%o#MuI+yFZGjC^h6N0qUDvO#sP@z!HniJ`xCjOfLVh#JgDk$LkMJl#QE7pbb}?Lb zrnE~U^1fWRF^&yzb(G9eA5QYV@IN{0{x1LGyXanpZ61*T9Xeafa4Zawn4>$8ll;wr z*mf9100M&7c_aFJbGfulN9+{bp9n9uX6%m2(!CJu;eq=7i)@3q*{gbpZx5jk*3lTk z0*w#F<_INzft%hc9VSYlm2<==l^sPcSJSsQ`wXTGjU8S^6bP75X-HcMZ;34*2lC63 zS2+60f*Myh&=_SYx@%o6Qhb6E#-gf$_xuWXk9Gx?h|ym`NhvgG2 z6h5~{VlY$2@*YDz64g|+{UTHjKSVO?n{&!OyX=2UrOOj&lWX)z*XYd#^W8Y!xhRP(5pSvRUcw3J{mX{WZ{+3-?n z9UgJ9x{duf8PfyzwduipTc@vqS^X-PN2=WUeH0hOgGSRp+v0vwdABrP4=x3E&c)mx zaeYxQQa}h1+Pa5&z0H7Tw|UQf>kjRl-h2ltdO3S+%uh0!LOnL6oDmKgx7YR>GCq0W ze2If4=R86JOiic}MdAQ7tfY?gcbHpSe7*s)T+2Dl3B+Jtnwz;k|HJe!#8E;B$$_8C z#h7^{bpckfh$SAoH7IP8zHoi+tf}$!g?r>(gBF-wbn|0NK@P!reh3g}tsP_(%z4dg znU|vE$l;edMEq_fS$@6sF7cP-^{T9{p;p~bl+k1sV%tkG{c#UT=m2F$2l@tom)@%_ zW$%o>Azs&zUx1MH5e2aJS%Q+QV9(&Wjt(heH7bq{&&;FmX$mea!B%0Wd+L=zxzUSU zS1SL=8$eR1-KxT4tx5%R69K*G)tsd%hObmX?K}Kh!7;ymPJWZV;UvF}4FYU$)QCul zJsQQ!<8gvO3~wyxIDzpmhA*e3NE6bIdNzN;3EoI_@UsvG$x*4m0um!%I=jY2GArF* z{bJwDhN67j4Jz`%{y-)W5L8W*zSH9WH-@2xhv-|fsO|{A*vuv|<77GLpJ$Y5Yb)O7 zn_p}(sq0`my2B0m}AT9@RKAYSHd{yJDzXUTI3QqR&gpR0@(|*ql zWqbEn>)t}W5&N|7baNh%GPgtUeGE3Ts`ytbzoDUouyr-#x9ri5RWHnqG8PaBvqOBB z7DND{h=fQMhp+8Jcbnx5A4zxR^K2JH60bc1&CK-~mWPA6zTP;cCDojN4Fn4% zYu@K}BhQ_9wvSuW=0Ij@EWcrXREIgdy6A}@s>=y~oqhWF_z(}4m%EGKfceg2i_!}2 zf_WH=+lB%a_3H1nwdX(Qn~L28_R>a~b^dp2aY?sn|E$M$q$pp3B}EY(Si;>52^#&o z_U9;~n3h1zgf>W__X!KSfOm$6v8HtX#WjAF>>+8D3_7+;5TKUHfUX;cba_O9kwlJ) zoq*deHkyRT-BN_c39uN;-#rSKc|PmCN0cxzeij0R+-*#>ylqTsVJwu0SsdpKms*^V zHJhCDElK^aj~d%MOFRWGm|=Z+BfBX$_W2&shxeb#&&7Gw`pVjKvM6mi-s3pyFJc)R zt3Jz2zIL``8@Z-ExDZf8z#5ZzRBDnhJ zh%e5raV*T-viwUe_`G)$yv3isImfOpB2aWSe^-QL3~9T^VAp7gz!F@jZVinx>b}y+ zfCns+!r=j4@@#1BaWsr)2YJR*yeH!y8l6d+;h5QC9Q)9sGRlP}{?L)DdvVhcR_b@p z_Sp+3!*`41Pla5cLx0{lQ2o$k7g0}Qe#dxs0kkq7$OkN{Wj3l%JUsJk(OlSUltSPzHDn^Ie1zXBxU*_)TN zBT1^&dice1J>u0Z*%umM?QR%S<^4MD>SF;LF{H^jg|Oq6*)7sxWtPr21eV;dUGkw2 z!LJ)3?5h3O{7~nebnT>BnsOG;`NJ%L=ZBU%6>rDSMf_fq<(RPvtXGtJI}Asx+32Aa zKw!@*iobiI_rTIw zxW_)HwDy-JrV$y$#jI=lNYdX)h7MxtW4m;M-t*ej-3>KqE+JC7#6g8jzDHFKjM*nEt z6NLGj^7LT8)(qEPN5D@gEYVc?_*gu3EpbR8bKksGZv8W9CM^|NJI$tEYNI>xdJqiO zhGR4bH3Kg!2>4ncKuB1sBty@5!P^~+$bd#Er28vF;yr(Zrqc!%vL1_MJW(oA{>CUL z4(UROuMY2F!G#X|m`VnATaFi;0Oc3}v7@2GXj}I+JYbRl+T1?aCvzln_izTNjjt*v z5-6bS?Cjc3Y@|0}2R+fcbgUY?q-hH+9R*KjYK-wXmnUGcXEq1ZPEf6w4dT)Cnw)A5#U4TNof_Rt$;6i+OON!PJ$HyN zEfZvMs(1%6WyT&FgC$fl)u4ehxP&i08%z1u=Imo&fGEM>1ppAA3`*y|3_# z(mCnh!cHtQ*N9E=Kl-)AMYv{)nemIsC6$WanE)p+reVU0+i#(Nn_N&8=u!Li-Jz5a z*M<`9j1P-3v%aqs%vyj|O)eG@WQJx$&ELBDzI|GYAjKtJA~T=c{TjFIO(bO|A!Lfa zr|S1104GYTC?NpTD4SaUW7Ab)t%HgGUhnvW^5JPEA{m)rFZT%)yR8etV%&&lr8_Oj z^_OUtIm0ieljEMvu0XBUl#4?`N2rJ+p_j|={61I}5s;l7&KvRU6dxROg2utjNxBhE ze>*QWGMx!0E4$G7+AH!}+#*lJJB?tPe`ysGk@7LX{};SA?x9skRBo=Lpwz=ki#NP* zmI^io)fXZFzkV1Vd`%ux1eLXRqolq>(EEqFYgM0+nExeV`P2CWg~r1NQYd-~69pJtmCc+C0*9B`G*yfS&Z#@X52J7;pYJ z&M8yET8Df!1oi=&!A0bLn_c%G%xb>Ip6F#;;eU&?`R(%tU^#v?)({N2i@RED5Q}Qr zySJ#JbLvka?;KKnUV?4hvdc^O%f&w(U;+uFBF%68z?DY&uxCl9Q0 zSNJ&LG$6DQ$tC+4-J}%JVd>gc79!&toZv9LzGLIN{uTE0%zz*?Oqxzj>RZ1f<=)SJ zgN>tTLuw@ptccsH*9|=LMF%g00+eAR7bd;o%Si=sJb(5E5jY^`6#wE(?(F2 zh=u(2gGP0!K>nB#3F@0LtjojhA-ZqQ9$C>-^`-W5XNUVC(TtQ((C8OR`QE<5H7sSq+XWF^L;}DUtiJtH|5Lgt{n*(VTw|w#fBL z*GL0xgF&!kayjQ(vICLYnIX}#H28B-`wnGm_y-^N>RQepeO|`d7{644YyJ-nLPQp3 zD>WrPZf<7ZIKk2|yuDKZ7HVD@XZE^%NanR-T>deghjriRH}IhlV#iEX>O-|5r`<6E zOIxux8n}CQSBWz7ZviW9e_OKyr^C=s@z@E!4eB**qHlDXMwr3Sm0g?I^hTv{sNMCa zzAXo)sP0+1T3#1me8cv4yxBzJlzp>vS3ZMP-}h}K6=J5Wrhd9}(f?S-J-sHJhY{ED zqd4eU=+7RmIMTqIlTcQ#I(oT$`0M~7hX&7UQ*_Ia@;`45cMB@`us09@mEx9@HMg#e zIxTcy=$4j<-@`G7oRP#w1SiIm1F#bNVMo2ql*)P3)y^;1*FW63fiok?cO;#W)#R=1 z?di-UDDZUs7OVkc%uMfoUi$US3uy~=j(KRqp{WGpf$l0%A#ZWp1OZAihcSjzWJVbq zDhXf4FJ)cj@^~i7nA-LHkrf_ZDrZG1P&4`cwUS-^ZWaSn`3HzpcO>8cQvE47 zmpDq+Gm-_Yb@fZL2)k31*l7CFHRp|{<%g2!Z(k7Lk_rv{=PW??`Q1>3?NYthovsU@ zW!X%s5w^wR#1)s$y}d%ha~*g^0)SLtG+byHd*$w=QIhrQ534zjRNI@PB=1$t%Tm2mEQ5QtHc8IIhvzCC~e7!FeTzueTq_~IOq8_diZ&a`3x_9HPlqa!*X z>RY-plqOnM^?U{RFd>MgvGoO*v=K~!1&Mw%6iC%ec z;bM=sdQ{_deAhBow`e!UzuaRRSV@}Zx@KCfM~gDW6kQZ^NPL4Rv{}-_wd0J|py|kJ z)<+edgdfFi><~?ujFKdY2i`^_79UP+G}nWxmq}Ys%<5tEWPv1ADg-Z z+Z_1(FD}Bbv)RQdJKp-k_@Xl6wb}lppg~U)O^UHf^rHXXkMd@SBMp7CHJpMi+-+qr z4=BA*>cIW4kWLTh)(qwGH4yFVipnT!$Di!B{)JtVM;&G}7r{FJ*n=uKn@FmJ$kOt8 z86cm>kU}Q%=1Xtrc=axlPl;#0olDdc9gyRk2pHs%4L=kFz8+X$nVI!M&@5np0C+e$ z8C7=41+8s$2Nn_Yk5AkW+o(evtU~T;hSm1Euv%SD3L`6o1%CRe_DwLphVZc}+-qZy z#|tW=N%8P5cW$>}BsR=nN*wh<8umy%(inlUm9Vle(vIZ96GQGjhk2QAA$Sp6DiiU& zIAA#A(sIHX5bzQQ%VMgxTYl%oUkujsO0P5lp^3oLH)nHkwkRiIVHK9=*ngPSWNt1j zfM_8v{WA$HtKKHA4G!@ZCe18VyWgo6E$Of?F{{Xc)Yo^+X;g~4w*^P05dt2B$g^Jy z9;jtQABcW5{4U=CJ0$L8^_O^lptqpwyt!_8Ea2yhj6 zo2_Av_l-#KOBUG%tRh{GxWACTNf~jrp1#-=T%G#Ep5C82?B0-40bp&p*bytJ5zmcV zC|kVG^O6(efp*fdM;F#tAJOyd12ZBGJrM}rY?hi)lE@tnuerLb1^YX$tS!tAbT9VPh83-7)SIb>iswHtB-| z3=K&3w%C$wH4h0>e*C@f@^nL`$k|#3X~QC82(E&PQizdBkQ}T@Wb*t=fvNb*(_FSi zXuevXVA!BUMk{sV2ru1TT0Iqr=>1!@0_Ft&To0;-D?`1hd%&0K%4Wz}{uk*_-*|hA za8pA4*LJMJx4YNudyDtN(6Y1gC2T*Y<%cmNYO#|F&JM(+QIcGRt;g0WR`<7YpJPO= zfXt*c1nZpCU0?sMb>AH1YYf2$aC^gbDQf9SL2Apsb1{Y==lf0;_)U35b*(q=nwo2$ z=xv@L0Q+-cjX(I;d%s+^{K&haUfXVolPSC`g9yP4s5cgOj>SB&YJVotqBMf(NSL!Soa#C&UQwn2pze=uPC={~+l=guXu8f8*(Z!JvR< z(0MmeZxpp!&H3g6~C1)&^^OtfmD0 z$_~_)AaH>kVjf=Qnwf`4OSLcZ$q}xB9}*jz$Tg`xC%(3ImPQ`wiF52x4e@>DQ8-E$ zEE3ys?v1pRj?#Y44R6=f80#7CAAs}a4Y0SF1$*ZAHJP>OQ43%9xuXdKQD}q1)aHG! z-y10sWr)z<(MIQn^>tWdoh!bZS4!+!EY6;}gQ~IsSkY=h0Q#L1wyD>S9bQ{4I(?dX zay$&X7G5bLzPIbo4)W;~^Rq1i`Q-#ga_Kvbym}yf*fe)!*DCR);8_qTzthi; zBy}Y{u@q|;uX>utht9Hz<-XP-^$Q~@=O!(aGRnYO*mOCTJ8y$2P5AaqWcwzBdaC&@ zN8;V6hw>YsuY@1mL(HpN22Ix}hKGES%g7RcL@4YyslMm}<-f=G)d^UZcOtnBXhkga zjm&2P5rD60HDqo3T^2H}%}ja|08!Z!0zh@Tm_ElAd|Hu-wUj%xc5|dB-=6l zzUS}WW!~eIf)q%=j(`61=++tI@X^b58aom+W~b?XtG1lgVHhcbpBH>lq&7Y#0-w(H zeSVGHf=*};Y~^AS$$&Rj2g+9JP#tE;Qy03mZk~s4bwIc5K{b66FeyXUI>NA)1EDz* zbYFuhrON9x&nCo`855pj<*YJajy+yj-B(Ato)6mP4!3@Jd_t0leHvGZ_sfNb{ztv; zju05~xI?(;v7n?gpd}UT^E>#eNnB>H{Za<_?$=hXZPrG?cso8Wd;Hj{e8#ex{nel& z_tPqv#Lt+^jt8DurzJ3e_2LVJU!az>ZNGB%wBYc~`8g_=nJ;vr<(r!bc!8R;Y!Y?} zxQvK?xTcSNnzKCE&WdEZ+dB&yD4^DxmJ>z^2^EVjYU0Dx0GYu zlQt26i93;aB2wdoQ8Mri3JKYKZV3T2ww5@?UkO*cK>0 zuyuh7<2F>E2xYah{-Q@>uij`gS@F18n_lF!)hN0KL$Tih(kZS%D@1!CYuP{k}Y@6s9bF2fYg~H&1Jumt2U>j~S zF}Fk5R(iB~V-Cr>>{2fH%kj9~%F2bsniouC*jY2ZMD+U0zNf&hT@CrzKKD4uect$^ zNNd_)5Sw0WK80sX`5^2&gfc5)WrN8WpAHwW&sGoP>l6{Q0JL$Qps?y_qMNm^uURZy z5}_Xu3Rb6iN}`x;aJl|jft4QTqrfUT(8d1dhRhYS zFHul)`;{`3jA0^rG0%V_rop9Ni*gF;g)iuUq!!lZh-l}iG2Sc`-R@42HTU_#ilRW{S~38 zB3fL0udaj@=#|Nk$;}G?uBSM0oEUmP@LnLyFiwAu@47WN@}>c9ua}7!CrIU&$rwmA zluE)EleZV>1nZ)UU^B1ZGOCQVm$NFGzGt@oyWe`m>(q&B!hcD?5+C>aMo#0yCl1X{ z%F8VmSYJaKizjwmLYm`f;`VT}fS*rmjnHptU@8k>V_|pvogT95VgOuutnnsF(*t7o zH-X!%D!9V1pibHi;*!d@<_-XrnJ0B%M0U3;JPraD^ z71UGluo(dr15zKDWXCGoDqvow8xCx@dF#JU6AOU9ujsrfAc{G zpw4I3Op7lCA<7=HZ6X#knxHgv&Ou-r`0$qEd_MQb^%NX=k|@!Egt#%zYl({;n5ipfL9jbN zo!Vjt6r+IKwk^9NtK8|^4mJpbbBgl@@RKzHeoNFBvDlG#KJ4K9pO*t03#;RA>%eFL zAB=C8m%PEzyC26*Vx!Ddg)qJ;{7z=>-(roEhKlGX$U#+-?WC)KlN!;gSNcmbQnr)F zM2Kx2oC=zqT)=Bp#rM+{V`FwfThZsx_H*x3^d?CVt&pQ2p@>NSK|jB+Uq8x1-?`g4>sTS{1eI}=blKW*oAr9 z;w*+Qh&wiV@EUgCUEeNFSe>H)v40JAEZ$CaV#7XMXF^}kwyWORa5H&q)q+C{_0#Gp ziH6VY&VIN{CG0H1p>BC&Hosw*NH0rX0Uw>_1LckuKlvO@*no8;e|!=bghtO7UJTy{ zO8NDre(bt?sN!xDODn8vOdO^nZO?Qx7YcKVaqyLM(W}-M7!GTrkuuTRdV^{`XsO?q zH~7mvSBgT1Vyk2@Kz(%(IB2#H;QQmkJXZ0)zyr~df&}mD8cp>&=FZPJ)gX9pJm~<* zZIk_6QHU zIV{uXMWAh1XZs?Ediw%VLaa|Frsxeoh2iTcK&dK_aVHC`y@g(lsWS%Ab>f2^YP|Z( zRqG?(Pc~t^f!F&I6hg22T_*HNUi-$8J~yE?lQCWJ1LXWGxaRH4g#&lqvEZWW{Y$Ua zfby@(xInST4|X%KH7Ffe3Fy85S^0N4`) zA`|3oFD0;<#UYHTGd$ytow|s2K-Jf(4ld*!H)e2Z$?Mrzx*X-dJ{}q2y^#S)!u*|z z7#JU_YU`ai%XcL}`gk_UGrBO%;K`U4tonN>s`D#shC?pv)CE> zK7rQrDmVuy?T4_eyVDbQc4ArUi?oY7$#a7FKUztE^q%`xH)Dw1g89eo3fCLb%P@Qx z+G58lzoGINg*09=v!5?|_02dNshrBv4l-?$f6+IlRA0dnR-&TzFcBd-Vwr`#e=smz zWXR+>hp(+F<&i~iS{+aD_oqT~d{>ekh@~hUPRJ770K6V!rMBfIWs(c=Be7Q~&{2sh{7VKnK z=J2@Glz|+zr){g1jk0(|p^(OwalKmJ@>Krqz%hLk6NGee@vW2y5#6EO^pc~J0mHcb z_@y=7;b*9IJU|Nrebr2Y@ezA`9|uRq{(n|OoL@Z2uNkf=#|HSDsY6i4MPNGr8%q(Oh6 zlTa^!2_+AO)9tp&LaBBp7w?xNZ=Y4!Tayp6a$r6Oe1^L%A5?r(G~%)<;|?^icGZ;r z?lrYJOy1_(`O_j&dNRI6kMUAi&upcmr~yxHis>ax6^wSE;omVs%e?@OZfBeJ^Uzy+!h~#Cjj{h z!MBo#{){_HL=11-x z-ud+U2Kp2QSD} zXna5#1eK6)@$lK+n+5hnId|!eL_o9dTq0%1Xl9XGIimIPySQ|A9FWYjaa}@CIcWf! zh(@)y5ax(MI~wt|q`1q>ntAb?Tb^llKzy3NVOUEvaTHnbc+D7PfxZCBBtlI}`SzkA zXU%I+U$u(%DPg$5Gp&TUBqtpsNWyqfuT5)YPH@9a>Hdw?yzuo##F9czPT2M218LMn zSa~4^>>#LMQ1)?%q7H=oy=XJS<8`{;={kH&mq;#2d?s^PS`Q|xOK%dICj>mLRMr*r zjbV5n4pjI=4M47S+(<7Y>=*RGz10#NTQa$OYui29KtAXhTgRo)j5$sR%0Ri9{{M9Q zO3P)O;4NOOZ>&aSS#ISQr8-;)4+ygf?f0Xuaob54XL6bkSK2NyUp5pL_r5uAl{zkR zZ##;epJY03H7CWyB@@BFn`vrHN{*--I>y4=)nn11grfPw%N@PNFCre$9qnd}xM&75 zfliIMTAfqh1}#Yo$Kukk{$Y}3FKx(lH6g-9Kf%o-rWV~tQh~!~8aRNq_`Myi_BN2T zcqbnL=U1@NIxC3|o7~!RttC$S;fr@sC?o4lh7px%wPV?OlvLvz@Nrb$r@Xos>)R#Hj^SUfymqq;P(5VGaWV9Uk z&O0TPn+A;a9tXyC-&p$O+18^_U~Gwy5o0pgF+Q(_hQ70C-de1s0e+NiJ1z1?bZ%DC zPm&1;U>Mo81%u%rdB`*;ZrdYzXH!TUB8GE_y%Dywv>+v8SJH&+7>+oNhwWrfTrLiW zbvdW4vPB%4pev(T$8u}!SX9#CIPZ&Sd|3gKngY+rDS-v)Ot`-7@~sIzAzj z&bwc6WN$_m$OBK>eO{vUr|Z~4yTW=n()nYZtugq3GhaC@I*Xz1PcuN`*O;yyU#xHe z?@}FPBd4R;X>wO(A#UqMP_nt!f`mD^ZK669Se}j+uubOBhRb?)k;-R>j2yWtC7tRx zyXs6wu64Q^Qhj97c#Iwx8bqAwZWsJgtwA`ut6Ti{;fjL~h=aKEoh*knWbqobdZE$u z1Mja+k<8UJ%FzQtPrtEyHi=3uE3&&7QNW&xN~%E@lN;-4F@f1d$H4pKraPIx8;N=z zixw)yB5*5cXRedo1D($T&O4?30@_8!Vbe6j2&h}xdF6s~O|r__Q@HKuhe;D9B+kdw zmdkI7m}sU~aHV>@O;0DB9j=gMC!q{oF!6HH1Ij|-EPrnzEC|HSxk^ssTZ7N^j)&iY z8Ea#8XWf1lwFFhK@}nEH`pa(x3NiB~dms&Ft=V7MJ8 z_TU*ZPcMY34Xfdnw3W0QD`QK(A{tMkUO5KonsH9$qAR_zjeI#NU<-xstf6NDnz7bL zw*^4art$}(T&N;K>ID*SjO^fiYyJdmpNU+N{LRE-w%p1-BoF$XyXf}K{VFw2S{Ddjt#=(y!y%)c(iIUgtse%YBpUir8^K~Uf#0x;*mDN?Nx!Ty8Qw!%(`&aYj!C20>Al_JutC-!;B}U4?anGtf<(b2cXx35Zn`l;PxV=2HnFHjRK$} zOd;ScY1Ys#7YNUZXm(alhWAApA8$1VMpgPw-t#dqd@?d`}(@GFL*wKWci-&b^*fdPFQeBkqyF%%>=GjP(N5zeb{rIgn}2i-Qz-JXlT zVg7DLB16gZY^> zL_!2%!)Ec5u-6NswK*4s&on%cV0NK&5r>-?GHOCbTSPCpq)Q^J7yHq?!?tS4fpeSo z>FSwsS`rb9o`zl9hP@A+7$wJ7wDIx`{ptGo{N`Dgx!xd($9Y!VQpRitt=bnF%T9Go z`p_s?BjCSY>$?nRLjYthG?abeK)$n;HSgG%sv;ypDmCmqtPk}%X$K$kg8EJV1Bcli;(WP*eC|N=L*&)vsxuPqcN)n0<=j0kP%5AE%~28%NciHp(i)oS zhP^8lcJ?u|$uIhV%eMfR^t;ABiijOxq;L8!s@zv|d}H(*&2yhl%$E*M1Znuw+#MBK zh2?OvngVZZatW;yKQUUD+Q*Zjnqvq$L*v7+T8?JbrJS~9#pmmliBSVGal_73p=pxNhASh)Mhf>;3hRR6s| zCk~+fkQ)N+0h?((p$CA-+T`Rjod(VQ$Z}1y(3|^blWE^(;lF2^$QgC`R*`mO-a!Sp zBlUW79m;(}GBybiHZPhdZO%Ba~5rvExjz4Fll5pMU!gKqqpY6G{D{MlR1B+X{~ z-q^JAjhEXATGN8{S^XIguMH2B=lQr?ElTY+3;P#D7(aDsBQVO`6Q+VVCOLwaSnvhx zAhNJW9|yKW{N<=%{v|Ih^?)?SHPQ9kx^G>TfeK#E2?uk-@?>xK38*G@a9?;^Cgf_F z%t>GYQjyQtnsaAwHpbwA2vPDSTT9&mn9X-n+f`n<_#*gC(Vr%@@ebcj>sKdzB%!6H z2pm*}@d#O)71}IuOT{h@A_Fq_9JJSK_aS8;TyHo^O;EVJvI*P-|(ZP zw_?t9P26aXGunASBj0@Rnr302AHsIG)02EuTCt4hNc^G&1H_)7<-YY5X`ugz#zcKQ zC~$Wi=hFbfhxU0XQP` zO6g6o^cbgGK)~|VFN^PBhJ$EQlFG*_E6mjR0uj1j;fZ5pos$~yh)X6Y4G@p;@C(r* zu;D!sqpP2p;4TM-YpL!%X={vlVh<#H_gqOgbCd+?3NM(b&Df*LWn(d4D-+^+n*hTS&=yUc_&gH{_Q&1Xvv-Ln_Lj|o4F{&6>>z1 zlz_PJG>@`3Z*CWuklA$vN>m5p3*eK9ZjOhVZB;Z0IlL&d%{oGoaw!bjJQSzSSKp5W zoVwLm{RoIpM;IIwRq?2_3PI?lqWcS5X8NOmuq=@uTC*o9vcafy|J}-SdV^7B_%+2lg6NsSc>5-L(|9>Wy7fFxA>ny!6%x`62C-Z81Rk ziJk#M+9VLm?(viR*&_fFj`!=UT-z@#Wh#pjxNniG5^kf6r61Yh{-p{K5*o!Am|(lC zu zA3l7{t|L6$(dcT9*?5`vI})#3p}dy)`tzJ&ff8 z+M%hF&ysR$M8@P_2*K3lmiN;AS)uL@sH+4iM!iqJ8dDRLsAT^@ncxC8OERhD@+ZX_ zTBF;5pLB4+^wQNWLaZ~pq8BZ1YU%c8ng#ysd zx#~)ZeKz}SI(ZfUk6sDYQd*)vXKjVi)K?zI^EI~)`$jfPCicSmsO&Hev7(>n#v2e9 ze7VU9ZLkNMoMhBH2{t)XiAwa3L=p($j>F$3hDn*ZzR^p6Z3f`!{N6XYVVHoA?p44) zV4q!{}_H_l-?u^Bdjfq}WJvTAKKHm|FcUB`-2DN8OizDX<;d(N_D>U(#f zaa9~SAE9Q^HxQ$*p#cfrcW4qj1Eann6TyWOtxukP)r@Rlg^>GoyIB?w^(Ha8In>)@ zw{T@)#6bL@X`Zvss-$lAf-<|*1ggrNvulV3MY-Ci?dV>}`13?0?+@8O*8ZMr*TCKR z7l!WEbl%z#?OLbO*ukj&02WHBKJG?n_}f#(c$qeqlWT|$Bw#?Pswqi0*5TEA97@Qp zg}s!N@5BF^7;eL;=mY`N_*&7*#(b7TX7ep}soPm#zcTI0p=QNgF;Y^udkew>9&L^!-gjNL#hYgWIuuKMc5@9?f`BpHbuEvjX>NiZ_R^ zx#rE+QYcd3wZA^9iUt znc~+C`fo@ahIeNMtYb7ptw7wF#7WP0qKLu-OiPOYKAoUdGrd$HgLdWXd{%jXjE183 z)3p^W;FFf4#vah@J%SwK zn>o0!;XeZ~DwY+)%;L1#RI&NES-_ApT3J~~z6dPvJ%c{DX{sGg6A?(gr0&r1`nxtE zqe@=^tPf)yViKAvQ#FXOi26S$i1AZO7+5J0Hxh?oH=JcA+{s?A!vUzVf`QYAx9c@> zyRs#Z%B5qGFze4psC$hJ8$bOo{7rJ&?Px(X+EFP59#CJG{R{xenJ;}@I{2Kk9ZX<& zmOfTH?3m-`iYCVpuN4SbSRit6Q;~$Q@soRk1clp_+qC1@+q56=m0LT~ZcDbLomH@C zZ$`U|*%aV&bFlsyR`33|VI_tFJQ3~2EY?eKBJo%b^NS}ookM0|;v3&!mHP)W(syNx@vF7VtbxoqU1D3Lyo>;*%C-nNw_pMHpWt~Tjp?|B`i%yd7fLm7GeVV zw%84>c|*wz1j1uOoif1zYYWq-x4zF>uM`FO3|NhZX?ea8t^iqUQR>XV9R)n_+K&3p{2lVP zK2jqz6%tfHqg#T;OH8%q%V)b_{|w>|y_U6YtuD6h z#&mn1qPMcKe!W`9a!{QJs}vu4Jz}i}C~wOjaRA7`5B@v5<$|&;Gb5Im zOS|v@v7KsX(v|f4D~RV!JGDcL&Y$(c zO$Zg^Tkvh4{@z3hje@O~=`fopANai$-d~g=B3QnUsKHe1)1hONYM0n4ft~)BiUe@=H7wp`Of-Y`d3Hz~BMjaPn)j=6M42 z-Q3K}sZAOj@Jg1|Q637?use#)W~{e);LbWGG{JAa4!WIzUo)Q_0~@TC2-30UBLDA< zIt#QPTc#2`=jptbF|A+9;?(qf$tHG=Rb0yF_$1|{-b$ROkj(hmkFLS*YxSzA>ZO_J zHfnHvcfAkl!Up5cYZD{MU;b;O0{PBtZ~wKcaMfgKknt=L4}AU{W`>rgS2;Lv!ueM( zz5dJsiQi(sh5VOBD}ivGvnaI-7 zVTw{i-YT!;lf~Q%qi8bTT+7>kgZ>#@zt&LVf2!UvJUm?j;oP;ti-KYG>9IE8ZJgYP%2L@RShVJjve?%jSIiY;p zTWecH-jHCeDXlZ&LiM^LGCLhQ@I|d=`oAOM|MMOb&W{iPHM-=t6_RJZ|JV8a-wv$* z->Y>)wI4qM92b=2cBEc^1C~En8*7B z=T{7^YWvRN_}tp(VOVM3Pebeq%lg}Jo_UAW<@Kr^o2ZJMJ7Gzm657877Hl7&UpQaE1_PRJt`V*Dki|N`Ej=SR6Br(g^i<82?$D2`NmMJg0H-+yGM+Xij2^ZcdgI8_2 zM`t}ZIDt$QDi=?_QkIkU_F-|_*2}&+Uoi^B-1MK1g}Mr%^7P>*@|{t(^gS2iGCr9u zg*?_uPSjBQ5PMRb$}{a^qq)j1ro{BN)Q;79vOHdF=uh>0T~t?UN$1{{wa%RwkoUS= zT4G%8U?KOl{1z3pxcjrCI^MMJ8}9JSZppZ5$*>N-DPUo-8crW@?P{}q@!y!I9mTYHQ;^cfX!zx7y~OG1Id zf(m!_k2o3*6Jy8ov7qLF-meHWe`^*$;$az{lY@aA+~ZJ48I$sNdypH)HmY4x|BN^9 zh&(h!=Dj+KD4t5@*!pv*CP~!}!^1o@8)r$31HYaMal@)%XIKzMQgioMKL%Vx%=f~D)X$RT7moh5T)KWdh%Mz?-NTwij~<6I`+Fp>p_ZE_6!aVb0J zc>V>fr{Q$IZzX|&;mRSNE3Jk<%o{W0ZCop*0UJ9Ul7)mO^EaB1u<^OscgwgSy8Jij z3OT8GeAMN#dR{Gi`UU(53*~LTISJ$7I`9+W01u;8nwjftFdq;P>;hM<=~I;#Wmq6F zyTIhcN~nkY?A0t0)8`7+{y$3q3IG2Ji$5E}C!Mx(jab zVz2{(G7G1+KBfvz3jbN3>IrtZRA`03bWQMjjX?(o(2`38OV*5Y62@Hb2u*y@DVl65 z!2nZcdI-06{6nlNuz1?~7;R&UB?#cW&c$I73R_wgqR%1#XV;PD;Vp#sLsZfRSHIHIp4;)j;YzMHJLUu`Z+Oa;Mc9G3 zM;mZ%ik8i$Uxq>jXjnJxYQthvh<3sU7bf9<0gPq@{q+AHWg`m9`2?AfFwR}cf}}hz zgFTTv0(fOIJ&`cKYHl`QeRbeD{P7k&v+l^Rx?wF}5S;tB!W#6@>meIo&(OF-OMOZN zNz`4TAZ-XVSSe?0nYOhD!{!Dw06HIOthk!kKtnN=oe`? zc;F?(!%(Bl5@6r%6th^CBuw`e)L$Xr>VHkX zkVY|~nd_wJ4+|*V)5Ap$TosUPid-L@d-cva)`34&1fKgj| z%xUH?bb!$NS=8(51W`gF@P7tk&VFt4`O(JZXuN*ZX9o{x^?RlBaz8?nU>(c$cf$mK zFGmz_eWQD6Z~4=;dCOzDC=9@q{2eL)6=42E1n!Q2QFM-n^-l?KO2x&M5*0zTz>be& zLHM_Qf;-T9k$BlZEB|z3y93@4DTQjKm4$m|WTK@X^8g{j2ETELoPd=gurU>ud#AK7 zoN8*nN+5mkgQ?8>Vwm+vWGhrT=YqrQj+8mm8XgJ?Nv$TP>dH)`Q1%b4^)jF}-6amRRV;bfelAxr>jvPFF9tobZxDCto#Rtdg1ho0!y|bL zuH7Gc$6xXB@TiZ%*}BF~B#bxAhchh09rJfI1PuMd5UB1|prdM6EEd$Ts2W`#sb|U= zeH#^TQrHYDZBxit4MouX{Gd=tgz+MG%AhDHC=kJbguSb^sGHwL}rE}IRZ^6VK+{* zFJ}|Z?CwlIhmVVyx%Zm?7O z+}T}ZvPwO=h;hc-Uq-Tfw((5zp4Uop*XZwbVJf#Rku4K19W<3t*7=F8U|EAh6VFWJ z?|U7=272B`vQQT$CD^GXlt<%eMa<7z&6>Y3_|U8_wSA$1ebEaiDQRs)>E0f8w^d06 zgQpqM#TJyXdXEh?3DK|0Il)a|dmrBydrbpezN$tunWZxgq3KE2vrveUsR)q^UVzA|iN~}$6=9(QUEYZ2SWKeXuBjg5# z&@Q?k1m$&gP2xg0%)7BXjo54tEsV}Zn}-)>w50)(+j6^a#A8~4_PdFMAAm}&+pZs;1%y8i}$?c9INIxEP}6!!F~;<#!p)k zZ_nUt9YGzJZho^vRQC3`-{G&rlkytubDsj_ajs(Bz6Sr+enmW=PLad3(9cp`GSSlX z{N>tu>x;Qmo};PG+V~2_HxYr|$N5VvL`V0&7Onl-(Jr1@OibJml$862vmG9h6EZBt zC6Dtvs@Y#-ka~+zDJ#2&=^fGgW?L}w?zZE72X5P4*FzSozgebThbQvIcD-Usm-Mr5 z8hsuRdx+l~jeeUUWn_NePJV7^xDcLDr0ov;ae8x)Ew*YR`k=d-p?pvXhjm!@bJi) zWTskuf!rO2#^HtA=NW=-D|P0 z_(>VDel2uJmQ_kY67^MA1VO6Exm+UXI0&mfINvfyg^_(#$6zYoPu7u>ffS9FM7&7e z8y6q{{5!PANI@Ot8ATObGBII=K)XvhatAK;Fbk@=6g4Fy-M1=~{=aNxe|VY+z%W5J zf^XIR54K}_j|!^!FJ1SqJ=yIFq3_ncmVI3?CKU^+>^g~2)emBqpM+7z$-k`fOJryxhR1C>(_=LUjb`)QH%>-zEMmkb@>f6cs8+!_gb&BWTCl9N9R# z3D`pg0wW{C!W`GK$p;On0J5E=Yu_F}itpPkGwE#LBJb%ToL_R@(UF!Wm5@LJ(d|F( zjvnp}tia#n3~*1`gJ;nP_Y@TY($Pp~WX^;*kNB|VTew!-3QeM9=tc$BWN2;NE@h4Q z$5w!vtOs-Ehj|bpZ$h=;!dO-t_D>~ zGpn(gUUVep=)Us%c15R^l&B@b1NgoWuyy=iVueoTtnylp>e((qYHd|<@u zYwx?eUFaap7lh|{?SZV#Q+zwd-LFeL#WFk+XhFz95$kp+qT0dh;2Ito3+?4ak?wn! zSE8VW>%$5$41O_r8&Gnc5`^~!*+C05k8DE!V=IByH6NT69N^+&7(o|ZSas%KDEi9bu_ z*I?}p{PD#LB`{a|B=<;LiC)z!2-6X{UO#)|A7{M=cWF*Sb#wEs(TfB!wf{K@tS&xO z%wC^~to2A^!90^06lr3D#&9P~eJ7%*7?c)>aeTX8Ro0l}f`Z>$`Bznhhk2q>dTlk| z;b56OQ@DmiG-LF;mCh$v$XU2Zcw;3+atRE$siUEIyQY%d=bDC*>+Le_e332$j5Hn` zbSrmR$suW%k4ComA+4>km2P_B0#hy=9uR4;-h^0NqU6`(B?hC!1}M0LWY)?&Oo|2` zNkClH@5#b7ij)%vc9|-GmLp}v3zHxPqKYQJEZ2%$QT7G#EGj5GTs!HoxVj1xGr1tl zfPW55SBowVp#5~4afF^!qN)X~|JoG7B6CHRvGzR&rDge-EF^ua1iXHuzVkVPgSD;8 zr6m0Hc%oD?u4YD9G^{5VSk3z3nSs6DwT6iIJ~Qx^&i7|@YD_MTxswRV>DuO3f-!Ol z^E!lV5yha9bb(4|fsp!RAyt6=>GYVrW;JHkRuIxZf>^dRs0N&@W|f>R79il1h}Xv1 zA!s46AjA8C2CzLRWbgcdkTZd+;Z0o5%)$O%nIJSy5o>1|N&Ni2JxpvF0VU&XfMm#) znabAw$K{9)b#$Qgx3V3~jP7sv=r_rGEAF_XS&PA3^zLd|h#@UjD*mPJq5*E4-zW{0 zaMnjB{V+anxBUIv1jFzDg<`l=zhd3qBo!uN4}+m0ousw1NITi?t~YM?od~^ zCEj=M-zt#m-}hhy&S|Qc_4R0$9ypuM&I(5tgQvOu>szaeb5Jre;e7V7$FMno5`m<2 zp$qC{8BHsmV1_?*keZM8Td$8NWV!N6uMfhvn z`DP-d&uI2tkQ`)zpsf&(Mhmvkv8EDCTvMjNK&!uA%+S#p&IaE! z@uApx1du?YX6G;)B8%DQ4dX`SL6{l#PtF2!_X)?#Nlc)4aE@VJEEuIc*`9JOv&^|S<7(G|b221;6W>Ms;2o5>e8oz54FA{S zKer%77^+)e^^H2a9K!3I7ovxev zJMj!_qt^y2Dw{tco`-c~@7&*(F;Q!C6LwoUr_mqv#%mjZ{7gi6+w=41azh+d7Ty&3SuR>!0~&8_U=x&#VR(1W|j>JrlLrZ(VW5shXI zJzlNIh5Vr3go#0rL1}}o1B3Jjo?Ul4Ca~MfWMc3wE}*_Y2WM+mU~FEGXKc>fsk%mj zQ&eJnF`TQ%v-r23;%%$}WK;?vHX+-1fs_Q6tUB1Ie=&Bw3q*25DWH~pbK7C*`5<^S zg}v2o?892(OU;vNnbqFJ58#~~Vc3%egh@^*VFgM-K`TaGchtir9Hv*kQ0Rc?X7@k^ zYs=IoyWTY0j31|a(fj$+?9R-$&!_dKV&sMG0sT5;w1vXmq*5fL7u8a-Y4y0kvL6E} z_#agKqcVLcm7jEYG13-5Vfc#3>*jPqu1XAExX=xYt!*dZM_@kRG<|g6Fo)DxVPz%m zMMk-GDAu#L>7QYw@K!ez-c=z-s(Bc+9_hE^vZB8cMTjEgtK0{{FuTq4(t4=4H%_hf z0<;cqIj+oA73bZy^IN(Z;@HJA8lKYyci*t4ofW@gPCAFfx6)pXkd>lM4;|23l|>Qx zdIfP=u>@^JN$dMrOe_M4a5cp2H)3J+hm5VBFYqzxO6;p9Au(-&c|R+|+g+kiqkFRC z|2^w%`6`UuhBdK85dN_aL}LgqI$i!8R7V)7Kzw(`dlhI>5d%&lH6djx5Z6cfMDIOU zB^Gh*WR&FZj%V}mYJp@%P6N&psg#$#eG>B_r~v0#Hb`T8ewD?tPl!H$h~lHL!wTCs zgV&1lh9JodbZx=-OXC#XSNn?Nj@a>p zo+pdPxZEx#S0t?KZ4c3(vBZsLE1c{OPbfutMH`L_sfw63aR9QC+~Uamz<(mvhJium z9>q=?fip@!UhX-fk8%zTBny+gc^k0egZcpqlDgNTZI2Z$5@z#@c!-;n-E`Dpc!p4o zgma456O{jOro3p~9Iuk+C-clF1^CU+y`o4K2qJf)rRL*N=C|rF=Em-m{M5CkM#1d# zd-^1vmp@kNX^R|DYkTpA?>1p|TM?Ql&-*eGX~G5Zvt zgo4C!}C_7c8(MmM+ z;<^HTlgC`NB@o=7{~{mNGoAl&w5CEY2K7uLGC(WFCn8)E8o&s$!vIRHN;r{5*UIG0r;*rhBllyaeuFq3FS!-i z($N~^V=ejUDNTWziw;XW42xn_+Yj9qpZTFZ_R|z0L9c1wWkw%);n*X9_XguQukF0h z$FAQ`V>;-Vi^SHN-0^hfattM0w{GpBSh2|*+mjh3>wA-AmNt37TMo=C-LPi~A5=xe z{9i&^pY)J5+ehB_Bj?A@pZLOIqZR(>_(Bbbs^J}5@KVN2{w}v~qWg=4wl*x$sX@Nw zxz_rYhZ*oAz8ClB7-!H2<h<>~n`|ayep-#TcNzFp)<^Td$nLD4{yJfxmy{^G zD!HQ(BT6*+b7i(9-{|u|_bF!NN3Z_QyuPG7r<-Xq-8DFK;|cKTKS@0!Fh2&RVl7H| zKK^65#<9q?{bNxPRZp*>I*!p|z zk9Q6)CeF)^Zt${QtyP`TH8O~Co=0(FE}AX*in?%HK3k=}s_i|D(7+$9@u#OJ?hiCo zEe#dpdolH_2ql?ppx;L>2X9|3%T;^I><|aA;IS7hvb4;<9slc#tJNCob(I%?u$9zo z8YBa{y!`MaNJ`)K$N&&3wJ$%if~Ye!txPU|F%Z`&)vKj2Ztz3@uCrl7p)xl1GMLy~ zA@Yl#vycWgQ{&y_t^`eBS#4-40^}l-jfxz%J1U~3;i4i1 zF*Zjs-&@iCUc2aQtGd5>!)>)#TFVf4f!myd0E~Q5_z{2ag|;DGUIrG1+WeFM6TiEN z;547F!m>xoq|ZP4Yx6nrZ|}+)mOD6D4*xm<^VkKQbu~XxGWzq=?j=5MM$$l-2o10x zf+NeSEa)!WTf{>aT09XLaBUa7KIp~+n6Z9R`&xO~tIryk0Y-iX==iZGfdOZ_nDzrr z*^#2&s9D}W^T0M}U(W%lcE};6)S!WlHzel9HdvaQ61(i@-I+?kZ}y3UIoYB?|4lY@{LW!llo&k(>sk4 z)tPk=16NMyg`3>$yVBMQ-Y@XCe-k;ZO#HQpJo4q;J%@FL1Ib%FugQ6J5LD)4fq=v> zs1gXhZsT($ z6+}dB(1%6OgMj$1ZRWFmdGEsOm|?rb)AMHq(nSi`qQg}=^2lQ68?nHR(zYN6ItLs$ zz&wfw`rU%tIJdjxboq$(;Z7_o+2<-FH)^l#_5EX$>}qSo zbGP<^fS8!bl^Bh#g#uA@gcQPed^7>_z(_uO@U)eymB6B+LZ-wnsQ@cEig}||eKoPr z_EDJdRtfFMxG%+j1)VMFu~4-myF@;$4XJ`c3cBHXx2|`|-Ur;(;}P z`pbJ{BWO&wqr#8qKy9X7=>Fo=xHm05=T&e9280cw9N3f236iNOq6CA^Qn^5EQ!wpM zwWJE)CE10ma9lcpGxx;$qf&m?5Nz}pb$I+CgSLIF7ibh(Q1pAeB8PrUoAT>AVvthudWpn!h#v_O?$fm@M` z`t*rdX>(;A_|vRIDrVZC?MwbKjF?QMzT=)$a2p!;bBFc4w5cfCxnFVv?TM+fFXG4mi+H(6|>Ql~$C{iVeBe=QSF)&*TFGg8?fhx}aXUZA>29^=886Mzsme zSCSZrW~VExm!Y4sX*7MOgx~CYfbMK^Jm3v z^=kMUzH&Ya_F+RcgM`)1wE2&EOJUlxbKM1G?|1ZwoDk!feTGySp z1$TD{4#C|mxD#9wB*ER?ouC1N1_%zp-6g@@-QC^xO#Zb_?VD4l>fEG&s_;#^dv=c= z?>nA>v55^(ZEs>SZ+55%3wYnKJQPRe(j^kga^)z@H!@1HeB!)PCxK=Nh2eO%MVOON zeLa~!4GHqp${|}NCUA@*r|ovPR4RQ3D0_df^}4^n_v&R~9|)Wl_3jt9?d{_ODd!cm zBhZ`jTS5<#w?B=mSsni%@{QPnu%qjE^&Z7uZpo#Yf%;E!PUP`#5u9W@YA`50;;{Zu zhK`gvJgd9t@OqNJ;kK2W*h^U|EeAxt_XfVzlWxm=o`Fy0eqmAgd9z#kJj4ClD0f41~9l6hHO z4x&2(5cx_n*H?73lUk!hg7}rAT``mP{W8VA-{Nm4dWG+DDC*D+8q>7(_Ft#ksTRm( zV;L*Ph#fhiEZ^&!#D2xB9CtzR3dmhQ{v2kr3}-ae@GFt`qgDJXDNJ1HvvHo8@gMn- z1(quCV_f@_8oU`01A2=xKw}ib?>>$RV~q|PPme|a~E~f`*=w7NPEZ}a>021ZFeZ$&=AUIt1L4S4N z3y1SrJz$jmojOgXzN-c_*6EeWQQo2djM0_^k*TOw)zwczd~1jP8Kq#K09`G5obVCz zwH-N1=wxk4zr~20;!5}>aZzcLX|NO2!Rqsh4Ts=62)f-pf=Q09o|sekj`n)n&W>rg zg*>=U3L{(?;_z+nr!Xj=%rIWZuVV4OOdtGx@&+fG!Na&pQ20a8^IFx#N+LOR`uP_4q9q@5vUgu)3x@usa;PP$*n_nnbGYpO)x9 zjmg##Cl9BoKj=z=4UbxB)svbN#iFmVfg!K-@UiF|H$5*!?{60vgG4FDAfs{>!f(94 zV&kdpvU2WIOm*|<#>up2KKKDpfGh2BZhX32lth!7R)&&jCu}qiDZf6CU3>v5jgfJ< z1~lMt;E`MX88?avw)YDnFh%l1{4sf-LhTFxg)~G*ko4`E`&cNX&(_0hRw7)(jwi%$ zewx3;Hi0n*1pn;a=FqUC+w}?O(5@o~)!EnEbtWFyLrCwPOD^_24rJDP(HUHTbzcS2 zh2*N|us-84GnDR?Z0MFCdIy}{J#>rk9R(dx?=-F>0q2}0No=)#yA#j9!{T8yT47K= zyYNFR9?~st#K?*rK?L{P#ccu=#gC0A#jxx4aH-x=O;kMrKebt<$%}Rhs9dFATDO!bejRYzFkh@R33TZxB)G;jq81k`@Z~$sc~hqF}57X|RxR z7;CW|!5N2tSnyU|1-Q!R;HR;R=S-^#ukxV4JGYO}C+csIE%cxf&mhTU8}wvB6mxd&flosF>d+6t@yeHYRDb=;sI33%Y30Qb_VUF zOlh^E8-eL(H->{C_?@C5eyTw*5q1K*1LXAmmLrWo6r_WeiIO0`LaL!ZuKviYfdygL z`KN8J{+an%^&4IVKsHX-PL@(2PO~o-C`J^Ybe$>1PURxV1y*g+3-XTJzR#9~lqhJG z_qTc+x#}bckE&;%!sqv@NRmolbe$u#T`3Ut+z)Z!7oBw?(qyhGtlX;aV=^&Sbs(}? zj-gkOl~-L_Y4VOQ*nM6o2D^ILqmflp9pqGpqJ$JF^ahPzf~ZQ3viLvXu8reSH9{6- z6UhSev+bt6_*!k=GWbUv4LaO17g4_CK%hH@JM?70LnzaDrQ+&y!c3%fG07GwtRLsMhR%;suyA)q@9n1wz6UwoR8FE zCXw7zG`~A}yBLWh(OVI7jpv9Ah(VMh!j@lvCTFF=!7F@nmRt@xBR#5OE+GC*cJc zxUB*%{5qXRc5c;!Tzit}Aw)1B;w0SD^VM2n#R4rk1faMTb=)x^Dk*@or57-N-v*c} zjWo8B38N0e-iDapNiLX&Yv4dAcTZuwf9CP^1#?gcST`4zhzkrjr+Jvt=m#hAqS6-_ zG>6apd$EK8pSWUphzayibi5pdW^J{(`>VNp+( zg4#EAGQmI}LRowiHaV`oW75=!cao{*W=lo*xeR>P^-(3wc zhmQ4nws_L`qxW6dZc)Y92U(Ql2ZOyCHGgn9nN(1B{ia8I9Bl&cBo^iM_`fd7sw7+q zS_{Tz>^c&8yFtrr4&lXVJH5W*R>n8B2AHpi6LP)@3`ZgTN|$@>>6{qBU3p$mI z5PqEbAD=%Lz5~YS$bvF=XP9@7UQy>ss-xF_VpRNUGe#d?bzOjBznwkDQ&CAz? zfJiHHx}}bxzVpb-EURNj#(8%MGyq=Mk3$trveG%PL$!dj@oJ8tca?qDm7s+b+D1J~ zAnoRZNs=Uxh=v+U ze$(W*rv%HwNs0PMW8x3|a=J&br{Pfr#6owKWj-Y=gX2s(SZ>&`K`aX)Vv>&fASNP$ zwretK+O10H6u&RwyNX$bYHphuSm6&BeYD}|#xU{Dpvy;gK2#7pnrVAmO};)YRQq-C z>Z}_#md$iougA7oQ%yCoj*^@u_;FM?kL*>hvF>-nt_7;!{2v8nm7S&AsBaZvtt(o^ z|3(US17j?sxAC-S6j6m!AJ1Gx}RnO$D6;*F9>MH=}+3UG!&M zpl04Ua3?x#GOP>x1DGPQgzf-g1d(2(5Ox{+JmA9@?uWEiA3uj<1*dyygviC$ zP$r_3FPh4k+uE}sWbiac z76{Q~6V%`T8GMDObv-ueD1$%zo{}wDJ(J%CN~IF*(do{tPGeYaj`+*f1BuBMW#oJz z#jUx|{83|0N-XFNAWa-VXYe#Z(Hnq@IN@5ZW=h>KGGmGHNO&XkgPK-ul{yllo`bIN z4~CIzA$7Nq|c11A$WTfnQb4=v_K4+FEt8R3GISak#dQ zwsz3KWKIL7D+25nug-org6QI6Az84!`S;%50jR0iua4OBkxg<_Ms>_+-WoK9xo5(>YpgNitZWK~{2 z8{%J?yJtUwgRH;K_Cu&<**4jV2&C&~h0)fXI}gSVk|3rMBJWGoG}y78*44T{I&2av zChyUrmtsh}2sLRv56zVgqm%I1=!8^WZLA5u)KB$QG_^@Sofe1#?5F~=3i6qBeh;Yp z21}Kphkf3&UM|O=yce`P--CeWU+miM-#G>v%426m;uyeNhqc zzIoB2q+it-Z4d+VBg<&M`;Z0&1x8%~)Ox#L?xJ3ew=3JUkGD}9ZT^svk$=FX274BG zzxSe1!D;t;3E=hkGz;ZKI$t5q@PYSWb@_`HtHX@|(fMs~W+ZJ7wcDz65{OsyZb-At zkxX~ts6DCf|82)L6Er!330eS%JX?t4GasNAW^CLZ>5&PqcEnd#(xo;xsbS6r&FSEs zs)BYt>(+ek|IkgfT`}rGIcJ5C7iac!Rf>vwDd16P>RAv#*#lT(8^b(xF$06UmHCbu7| z|1b@JO_^eID)!%G>El?i%wiDE!sLjGv*GD{UUT?Jv~(1(PHcq|E`@B=)P?K~M<=T0 zNv{Mpipl%p%w@WiD2m5cZd*h{^~)#=VmZ6bi2}nU!KnNc2J=v|hKOy~d2!sJ9Gl>M z*ycl0b1IuHRox>=C#;g7jEn<`&FjzS6Le7DyUSyLT`kwb2i#ch**J#3R}IcXg62b$ zB$YY=W?;@VxlTfbT=&KaHVEwNFDtTNASdZT~&UE8k5z+ov9~C^d$~SLimv zv1-EPk&Ljp9;!Xtvw+$W3Z0WhtPVqIVCW$=&=~0rT0>`IK=>j`MmC+lDeB$iZ{&8Y zzz7Q9tErkZBb!Sl{qbejwOgq(YDe&Q5{tLYQV@H&U1C9*Q9b$Tu4~OBeg=aHSfy)KVd%#Y*Bm2Vu?+-{E1Adym}K9rQSsA%sF$f1Gcb zOk8&o(%JQ4Z#o$BzJZVC|5DbcW&W(%Ks6a}OoYosD0G>5voQWL2#>}CpsBKN;DeBU!gO|zq2?WQ~%-Qem?%(I3eXW~RGoToIhZbqg zIsU#}UJWBYO2YjH6w<{|VWM<7aGt!LYJ?#05yl!kO4ww0`y~}qbA+M1rHaV7`Ef~_ zGE;wTun^=|x~K1;Vc|#|1gbMGS0?US-k}3$le~0=zP7^hexV~$EgrXseLg$NUymU}KOM>qqKTY! z^yZzh*##P%U)BdG!R$3Vz&~kuihoc<;Ns_6yNa&P2iCrax~>!KAT;+L8nv22B`fF! zQKrP^X@SZezXR8(M_d3Xlx%&QrBa~gNwt>VMG>?+*nqf$T{Nz6;l0~!xB2315i{A-*UHSA)sBPGOl-_L2()5(TnI=DC;X)pwt7C24@kFG7=Vb+ zD$*wU-(7vi_{XnnZI@xK_M`-9n`>?MEb>FU-Mz-#PH)U|JSz^eHf7R5*n`i*75;lD z&Z_0bH-d?LYaQc}m5vBfSC+Oi^%em~v5jM#*FA{C?Vy3qxyEvts7oxsEiNMhIk_bZ zsV&nl3)CY)8uymYZ!xhJ^75tD5O+%R?%Ob7fom>NRlR^+cV@b`^F?&35zz68>Ov8- zv&hphfQ-=l8+-PEX!hOjz;DK{POmsi^>!!ZzEj%JA}dLz0NGzzU)vX=qhX83!g@l? zPZntU?b025ha}_vEr1%6hjdyUgfW+ByzMf*k8)8)A3_Cti%13L#I&3gL z503RmrRt%oJUn7{N;!>zScdJm%%;W2qUxIMz$F#(>2NC9*A}p6!@+8|W#Vl{3mf|s zjC34`4_4SH3&Mc9ykM)g<-dZPGq7%16X+N!b)9SJO$=~nN~`xTXyOg72}3^%_j81f zz`(@rA1W$g^}yRDCj%?C#uNOp)E75!5L8q~koYdJ3&08$#H#Rc&W^oxM?1O)o)&#Y z6>Sg-FY|OhZ{{D=Qv&8sGV8b-UW33}j%v)06hR!4pa7+NWkS|)NE7I*`_zPRce!?7 z^VTr<0A}S`E7+4?GVcqG7Y%uf6Jc3pBY46)nGZD7QupO52u5L(1M$7kOS1}W``90 zL`y!81$qrtd7^Q_zJ4XU@MmoO-9HxNY^b4zu@5bs#7&?b1e~p;d3l@PUI$Kxpgj=Q z3;jM%kcP5^73XS z_T4{q=Y>b9lw496SvZ}v<>1h=oAC+n?UNWg-`X)VUHTE6+B_CT(&TXujS}KPv8h0? zg_W37R7+yL=vwN~rb_Y(b*cdCc6)-Y{tzQ^5$Lw}&8Fa=i*>T&^EJl;Y`B@*@dd(= zI=93a&KIjIM6l~6le>Nwc5(@LTrW$LfFoClo4WyyoFQfcj=g~e8YTMmz^l;qu4zYu zES4r^9ASN!12vsT0_Mhgr5D7^9js_o%6rRnT$Etr_EX;RgINkp<>-kufg4$YDPV-pn6zc_>f>huu);%_g)ZD z?5l>84d~l&cnNlI9Cs&o%&A>LD9-D1 zxEU|P#L^jZ`v%)s{%5R4JGPFPPm|YBOYtRIpYXVoh0HHU&1~&xWf`<1fuUfA!}M%d z%4ind&%0XmRFwiXEg24*s4s;|Mg?``0bJUqn>ltI@jKq6V5{kBp_cAs$Y|e z!=@;a`lq@k+SH`?doUNX^pnv+PE;fEJD3PLShlB+QCJ{sI-3_BH0^^D;RFMHGoQpRtSm z`vJ+x((E}6Sk-81H2V#Lnm6VqdCq8J-Q2m7gEYdWOhsUJ`{beu-cPu;<-mFyOtEg9 zt%u3kAuL6L^)7gv$}n7fkz_Po^RLi9k@$$fhp=$}l4Ad_0%i!09XjIIakk}2&`zHtxG?YE&c zHOyamj*@FfQshYUK%)e#U8dL?osjq~qLPRe)N;%mepU^O!}BL{B@)uGIeBnC4Tw|F zysBsNe3d#|H6N>qhyJ)&^P^*J79hhPg=B{Vjh+7QxfRuIOsJYndZx7z)xKIpWbQOw zwG(PpWV4z$gF99#QP!K+S_l~>S!vhW6Eqz30x}ZWYR|b{Rs&(r=48?Xg_p$XF8thz(XSb(bCkVa+t22^r1V?SqrLIUq@-cCfUXtVQQ9^_Y z8t&I{wsZ&c3{!;2+8ZrX@~cq<8ZjaI1JU+fzDgl5R<&1T_&qb5!=E7;^^FzHd2`iZ z0a0OssHUs6Snu`PWJ3#xZiI25Yfqqzi#i*Hx2Nerlg9HIlJQ*CDpaF8DV-N?x*bg4 zAW`<)iXl*AcD{XS4VHTOLN$(Mt@}qcI}wzD__!by+xqM{7EuNj2CGckc)Mcm^ORgU zOH2EzQ!DyM|AO4I1`TkJqL-wvXTeL@APs1$yhOY(!nIN|Y)ddLDJbtcGR_!C(SYz% zBv%Xz6NHmPM%wel?@*f!d~w}GG>_8QeqOjW2RdBwzp=7kb(W>0lCi83ct1FcN=eaV zEb)+PH1h?oc}Xx1F>+eWBt&O|>%)am%V=uuf5WqhXO}HV0k7LlQ$>=U5xj5)HUvBE z`Zs^{x6coTJ)_c%AsHrN;OBi&E`F7fQ7{HI1brkz+J4qMyMDOC2kg?OU&k~G*cGnb zHWujcMyC9o;{?07Nkj)Leo37*ksdMtGNh zCCXqY9@mf`!%!-4U=3R&daHDYM?5E0FZDiW1X#?Xr_d5{(|f8*dzA&MN-IhwrBrI! z@BhYJ`4Xl7l{nuiF(@+tYrZ<;d?YOikMwU&gwX@xh(;dFznWg43vH&IcUQMpJdsye>v~~@cT?ZSoP)AW8hDUW(hNZ*K+&D zWRtXGQubD>S!aQ?0?7Vz(K*-d!@Z^ZdnT;{tOPGAlC^Wv0}NNwln_>YMI7~-FPmoi zZmqlj&cNsYnE_`w;G#{f#PF+En268~nUr69d1TYZuh~x=DW46V`{;jmQUEhyQKj*( zKmU>`Vig4lx&EsZSNU7O|5n|bMT#O|xyc*dXK}W3brTXACitLvaHyT{FI~Y|00P@7 z>-#lHCZV&1&ITJM$3a$LyT3H}n~-UWaP+BEkS4Q){GY$xvtBO7+(dl;QdKXUX9*Uk zzX<%l(GEBZKk3)@03A)LZKZ}bz*DECSZ zi2&XIfkUn+hb#Hy z{aG-J%PKPGl_^FD~qYa#dA?Wn0<8N}Imm_@ebQ^6cMf&>=;1+AG znA<3FX)+!~z68>U$c^3?swPcU#6RoU;(=VK>vG%H`-N^q2( zT&gZhmtW@}XFf8pZP&Jc+S=xBcj|iaoKbCJ!8tfVumCL4D#Wg`)$q{T`RYcBQV*+ZZtF{rV1I&D^b>kt*el5UT5l99p73e4~}v zS9Ys)ma`_F|LOkzZd^qS9km!>&hJgjaTVwN*b?zj&IAFW0xzr~d2$IE=7c7$UhS#p zjk&hjUpX`~py+k0-w1n5ux|Pp0-6w!qCFJa#>~qwME!tf8UA%^S;|;t-PPI&J;DD< z_-VlV+jVP&i5OZkn#4)TFH?+*i2;u*1aO`>0iz_O`VgcZ+Wzj(&Dfbg1VsyPzOPkI z`4_x?>I040Q?1~1mOkbcNM)Er|)ziH-s-C)A=bK z6wNypvUtEZrLFI}C^Y0A>df}(mPM_2GcO_o`hwJ4Q-@lP2~!s4mO)@eMGQiz`YVG# z!Nq?9onT*oe`r)EQ=EF7!9%*9F}HwEn)K^(Y5ZF(lI^@bM-}I#U7ci5q@(%N`Z)~3 z@A@;J&j3F7M#Wii>V$)=HBPV!e+=(0E`&-02AJVK1vS#cgw>V;;00Ug@+)Km*Rv5g zBM1K!9cqR^2)|Far3yWg*&wTF=*XA+Em=UH(qk?wlH05tmaF4n_Q*)|54+dyvWa2p z|3wL{x$N8KDx~MES@UDzc;&PsL?DTNR@TjM8)yu*p{IfFt@EV;w9|K;X8vwSXO4dX zBD+_nwf|s19a-hzpuJw0vs40eTPf%=e5G1I?Ff-npCkXxc_dsl^;Jn zX+eni>`R5+oxvI+{>6#-CTXLB)&~)974Uytf1*hx{f9Hk_Mtyh1B_HuREC2Ce`Ykx4&N)@S`bw{kY6G zRZrfZ9y@N2A-psrViH;N!#yr`9GbeQ!7n^$4Tp0MmYYydYdSXg@ei&HWvj{}M4$J9 zhZ<1VFgKvu&&;>lf8VdaXiFA|Sksd+Qb#o4bPbHWsxK%g(aDA_?fk&CUT9`hMCxCc z8z0SQEE{H1pK_5KFK~0OQYmv~pq)iiA~Dxmh^=H5eyDTvf5N59c<1HiwyJ`RYOpow zOV5ER9mr)HryZw!*j5Zrm%V4%TVyE5hl_?2s19l@57dOGh<|1zBu9!kEFn9Q@Ub75 zCM2M{zIF|Ge3gtv3PTjBDZx5f^3XlpEIlQ@!e2yGnMaMkY+MV~*TAxh{ZRu}E5=%Y zi5?aV1}nqj+IK6f6>hDyL_Fx|>$dL?5#TRO*R(A&Jk&JXN)q3aw6sePI>jBqH#+gQ zD7}?SxS|Ziu%U|5?A8?(kzbQ8uI6j^hqPVo+30@!vD%@wWAX`6MrL4BG@eJ*@y`gl ze3@ZMhmj>UmG?k@Nz0PqqP^frSy9+q0-v_<0G5oYE8hUw_iuUv85N;FnW%Pk?Y$JC ztCl}Mt$YFT_+4HtlBPJe6UY^q1U|HM;RjDSz9_1Q2Fd(1la+L;)x5KCJ?cUwx$}i) z1G1F2o%jQu`uEOq-ru-oVR%2%INoT9yR&^1_RurbE15!tKo^C-O6kvUadkc^sriW) z(GjcF=v+-28Wu+Ws;XDEM>e0s1fs+YQ6~an*SiDF2RttwXFIWZ8OLM^FfOd)eGlmz z4BK9d*brpH^JHydTH3B)nL=xOA`@d}~e2`+-D(VUw-ZUd}!BE)a>A*lFd)|S5?e241Q2@@@?pz1O--1*R+6F`5*U4h2( ztK@}$v9J~^sG@IO&S3l23FCMHYJ(~M+^=~>HR5ZW6>U_FqifrxhKM@6>i6w)E+M2` z{a!njfJXw{t=&+s?o63KTT7w7*2jb#K~`SmZCGCZ+F4#LL#-cjxU6-b{!-^sOmF+n z4ZWxC(*M^B;E784Nz06m%2WE?1t@=uC4N(f4_2j<3=fI_XghCvGw_Cv;;Q~@oLD|c z19c=3l)b*meBq!!Xv{YVeR`tahqxGA@GzK6!2`nw(dnfE)RBk@6#a*kIe>whIx7|fW#*U^Z`i`tY{P*LWwPDPl{zYdX*lqal`BXn$tG9-7Y;%yx zqfqf@#@p}4>W)Y+7^f^wVnRIInbzKh3 zUIiw>(y$WomDf0Ee)c||r?j)1rm3+hRxR2*o8*N8E;YbEr41*XPB{D1RN!MLexa$d z?MgCfN^v3|s-aNVMA=M39b2~QLq+&^@F?;s=>tSyf#jJ$aEP_pa3Csc4Ad_VUZVjc z2?;D3g(~T@z+;U%GS3?W31wxT7J=bntDzQuJOI8F&n9YMH^W>!FbRou%FZgO9103R z8U$H;6OCk`i~aELsNYYX)`G{I(6f*AFdz~`01jpR)dy%1Q09Gw_Y++gwG-TkpsbUo zKM7EW5oEG6^s_lwW>v7&@gEP-M(KQLJj^L7{s5y{NnZTdtWm01(rIP<-?8W4{`dCr zQdf+PV7I-qWAY;gzr{s2$I`La zInn^9Zod`0i|&1PqY3-T`IWq4@nl0+DV%8LMV|4{SnKPZiMalke+ zs;|2&Fut>{!u7Bk^5(RZ1>pOg_B>tKy0qF{}C{AwgoQ+7iDPwvvR>1 zH0}lGLx-i<{Tdv;i?m~5JN)(a)5@97pL2gWApnlqZ-{b+v>fVXD=aJ|t2KlVeVAm- zbShhdW#2~BwU1r=O)mSEHa~p(Ffjbv{}zL`=H!P86GTTzD9WZUc~0u)k@~O6XBf^q z2w+u8qSMTu|3=TD44u8ajLy$_h_++a0ZN^4M_5|(Iv@!L5OBK25_`FQGvZiUyr6#K z_EZ3H&5j4>SD+FUYpx9DJpq0y=;(IbAAs4&6nZ^&5PD%D`vGxHI0G_O68K2x2cfQk zXawF(?zE5&&rttHu)@z9qrs_tvMyr40I2lg-~^@Nj%UztaOh`>ilKl8 z>q&dB=;+X-?oxVZAD^rBSu*g>S%?L$wZo4G#&BiH2mkj3u07+Tju&Z2!y_SFy5_#* z=rGZISD}Lzue=hFsy|G9@CLK$I3mmJ-B$vkqI-EUF0Tt49Lmb#Fa zpX^Z!4Q!g4#$%Dj(v;~W9Xa`mBKv@HP?WrY?-Rjs^Hby7t^D|=I!M1;!=PL>hG^jj z0X-dnmFtCJBLQQ)Saw^~nfe~39axKeL>((m(9AbTzlQ+W+25T#oSW7hPvrk7N_yad z0wR+^%N@APFQd#@^^&fhZPPrFR9T7;y%L?Kg ztlwNIW96{P1=vgI_U7xMzUxSN(C880Y_uoTlTlMbBv50rdC41kl-3uJdt9G}!MSFR zkB&UR<$4!t+TDK8$z^nf$}-BE^zpiX!nR&4>gaPvA>i7Pz&@Dppr5hk>h)+|9vB+v z(yO+|6Lv-uOioTifjcB_aOZ@!o=TtG6qmZ%C;(jQmSH~d(s{YP!f1`IN{_Z3uQyq< zS$lpwmArGiqZAax%%eT%aGIxrM?hGYHbH#&vfV!KyB39c%wHiP?cG`2yW&QC4gm%0 z+BuDcT6r0UY)I#)MAU?6c$(l8HL^Ti_2)OqXS3FvDcuigE`-CUAsIw1hL()Q_Uqw@esYJ_>UKY{?MHS;+7wYP z$XWU2kWS}yfs%B=b|Mz5wE8?xNVPtb(Q0;7GE}kb{vvB*2jk(4`bg6%^z$H%dZCm8 z*4i2u>HBCNA>D7mqyQ^FEu4+C%UZ|wWE>i9|5c|c>u%O>z3LkS0SMGrL7CB^4hdp! z#&nL*hqm#DZ4eSAVb`Pf=6HtGHA^s;;44QR;)Uev-Pc*m&mIJ&t_>TX2Z(=i+al1} z%Ud2&wj+Pr#9Y^5c<+#);mA2zxIMTn}-w+a9f6JDrGX~o|1%l`ALV>!kw0aW(T z=-t7B3(XA}AQPb>(AfqqE-ej3pV)d%2VYE8EDFV4_ztFY_Kxg9jmX1g(yw}%XsU!k z*T6BtiiNT?x<(5FZAP*8v1aXEY!1~Tp({prgtZ|7=#FxB-~Q2UY*Add`Eu;&f_tO4 z6q{Iswf7m-ue)@5N=ZP;s<*#E$&SN7{P63cLH+%+{hS%94u`7fTEe2;LOVoOyN?t< zwlmLYmGtIA^!tocA}zHydsC>G&OG}=@`A}CC7rThxJ;##NMG+y zXcB_1^QqeExbF$k4CT8>5 z$WmvUGu+;@1+Km|-aLVr{4Xn?e5MJE##6)djNG`N&|8Ps-OEQ*UZVqBAGDfn^x+LF z@PM<0@9by1l_-|`MSDeMNSNZfVUhL}(V1-xxQ|bQaq-_`a`-TfIV|y83$<%k;2|Lc z+;GsN+Ll8ouIBYUl&XxUMW>Rq)+{|eJm>{n=PBN95d;MVv8`v#;|c!wOyLcNS!WYq zrm#jUGfgWC7jWA-A})1&anIsWCakNwzYX`i)ej1#-F5fafF@?F-V#tE#IFd=&ISSk zDOsH?xR+XQqb}vEe6g{xct%}%u^ogzH=E&>>!bQt#-W*wzQC~;psssmd1iIg+X|oE zUvlfUqa!LHYq(i)ZQtPI-Qj&Xa;2oB!)}7AwSVdIwn1F;yAJoHvck1`2H@8( zRpl>>Q9{02{a78)KL;65XzN8Zs*!I`tA(WP>|=!|R8@1z7Llrn7|9qZqA)W((QnU@ zqKJ7NX>MySo1+7V*A*{KJV=n^o&pb`o>ycf5@RSurdsOlr!v&Z_Kq+^}72mP1)`F zHDM_UKMB9s;LDT7k{`G@$Fl8Gm0YqF!2UF7XgmGKG=iMO=~k}mC$(ts2uQ{gN4~5s zu)BOlY|cy}(Wj`(@vVES6{w(hFFMMtWOKnG3&FJFSgL?>=q@qKo|=seV% zCYZpH}kkw+KMSQb0AgY(%%L@6XRWr^4zrD&Vu8Ws~v9uOHz@$jWrFl-@lj{#pPd z_T2v*GM8X`R=>uj1PPrFBnZ~fXRJaP7ZhLjsHZVxV=>NhTHy_7sT~fT6DtCvkj}4c zRV*fu^Aj4j!)%r5uRdJzaeI4ew`c+hug}Ns+yg ziiS4V_OXq}`4MX;IwQ1^lIJlqC6J~2-j7|#_ccnTRl3ux2^n_)6&|r8rzCVtEswS? zbK(7=@WuL5CgwFYVq`1srZ4FWTr0B&ya!3pab$g-^W+Q`*|pGq^mED$B?B$e&gKx? zhm=dfxU@{fL*khR0tf7a;pzDJB!Bc)sY zTk4Ul#=zHBH&kg?%qSwG81dbV@dnFtut|W3Tzf2C-BGRBm)ae>$_vrY@A)zWaaF1i z03MG3lp#&|oXVtqM%;|{P07wSCc;?NwYVvTFr}^xI+RpvzB&MKyKy56#zVN0q`0T6 zw+LFC6<@bHghOrLj*Cy~nz$z%Z}yeO8zGeKr%GS^@n+u=QmK6#1`x^fJs#63!{5=B z?>HumHMZUf?|v;~KnI@AgkWLwG)+!r;ik0W3fFQI6QMQdMxn#kZuuw>nGMXGmh=}# z)_&)~H#`SnhYLk&5oBlC&=M{B!bsof_nb!(HjHTNL3fxwvTRO`AA%v9m@ho}LxW9~ zNHYqPTlsACAS&2wLvA8;7T@7v4mm@>3z@`cFz#zc2+ZVnqZ&Jot1C}s;Oy)K3G1D@ z^*o-YS*ArnX%KZufl63xN-P=>SK8e`7szU&XZX>4S6Sq1;&wqgITK|#H3Rb5nhnnk z<6=m)e>xd6xn}Mw7B?;=fU&yjJcPoe!`&&>|Lk_VL&^21qxk?6KnmF1-f!|a?jp7@ z9mOXl!HAp7X)An-^&xIv_t+Dhd$0~zb-*2XFKhbJH@&x7#Sbn>F^CSE&pH-hx!PbI z@8(8`p!Sw!D4RE|sa|`Iz;50C9rF^3Cw#1A-Q5c%1pD_Ca@j4Q(-Dw^n0(> z4)W;DFq?8St-zD(=H4~X26Q~9D6Q{ZzphpHApFRpL)n=#F^)xHnsOTfeN57oH_L!8 zKZ?YcH4Fc6zHZ}e?lefhgk#*!P75}vtEt`*zr2R4GaSV^N0Q3dTJ~U_YMjbcqaecA zK%*VY*w>k3MgTDTXlAm_fyq%k3poGS&epEK$WbxTwFivuB4y;WHiN#eBwOT6zzxHTO2JkwWvO|@Im^yufkBlRHn>JF#~S9 z`-0k!<^Ju<{#iaILE(IxM%moqUA~m1wBYrMvx(gC@;7k<@p2UAP3RdVHG;RibJgr0 z+|R>{9e?_9==SN(^tLo}3&4G+rLR{K7)qB`N`UB8N) zLVfU3$dCvJEb)xuIAa~I8_H2igtUho5)n2StRQ^jbKt2Qfg7)&U|V(O-N-eq9I=C8 ztXs%UO7=*S|Aq_f)!CCAOsVwz@MS{B$B#iScU;@?z<>A7`MtI7CrH23E=ptJWdu!r zrtt=Z`$e{0jX#gWoL{e!?tyC5u=2m?Azbqqd?b z3G4DO-v-tsG9Gb_NYBPRpPvr6gWdirW{{(?(z~4(-yO}_p(|!aW-pq*0Xw_)tL5oS zFQZ+bBWtEMP+o{b4_VW8b+3>Pr>pz)P=Q4_1_WS^XFsuAB{~8M=+2Thd3Zy6v}7EP z!$e@*n1o8eU92S9emE;`+}wxi!=IvdXWjN&>gseB{cLs=N1+ zuySAt(^TrVv!UwQ?ek{c`P-G2d-&UEny|Z7IQa|U0;-Z*?yyXOI4o|aBr-4oJ6VV(>6zpK~f-|7vO9YxSS z+0`=zYcQH=$nDwh&y}YTFWGv~FEc z+qgV^^l(Esp7lFY;>X+WNq$#49*bSytK9{K(2<_-G4|9hwbuhkU}tBCf|eGJZQMRJ zzr6(j8YYk5uC(8WC7sZ?;hsYv(Jq)owhJcea(4D&a@#+*d)htS=bpzPhsJ+Tisl`> zG;}K+Lb9_4GlE&6v>k(1Ot?VH5UHyanj}*cD375Gc~5T97Oc;pg7g4N8?rz{m!DGLw5W}T=LxM8%s@&;1TTWE*xVZjF?3&B=cUO3oDdv z&ZG*4SjVZOFOSfEcb?dd`9i`=RY1^;Zw5?ed}BQ z>{*YXcf>j!Mu{gz0z1lsG!UmOZA^%C1*s5yF-Cu_C0`b7Wy^w}m5Ba~Yy7>R0?|!@ zsO}9?%FAGM#;qr!5e6pF%Eb3x%JrbgDgxrM zj8b?TNPYzpTF0(pb%1Q5AL|U2D~m+mn`$J@%y6O`Bs>13j;^wLWWzw(c#0_+5WF6+ z6#5yj`e$(O_3ttF1zCE41ZiQ3UI}#EM`C2GOUhr^aDXDQWwbla=6H#r^g?X?B6<^V z)0hLs=rv-~b0RH}TDX1skm%%sX1GxO{AR|6!~gwYA4ghtTpSryeh(dRw=d49$@}c( z$LmZ8jYnlyCQ-GmUdRFIau24di7ZT>8Pf?H`mH)Hov2{v@)V!wed}VLsKH+e-+EUh#zo+ z{_l)V3ACMj>+^DxLHfZeZkFxClkoJ~ARfg~X;KQN2yHh%(yIVp)KtLL# zySqagNonZ@>F!XvyIZ7Fav#6H*#FGVzS(`ljLaE1=REg))hDjcea27BRl;_%J_*TL zs3YS((~PG>Pv1p%Cc!2p5Jte zMl#kh4@BI z=X!`QB&;LoW%HEJY`u%iZ((9yHXE5|FWj8gx_=3I`Hp*z2>|?F zFTmBz;@_O6(IY?C2Q>W=f}Z-V>+O_*6{W#g49!`LeTqXUQKm z5+E%*8`76fTGMxcNkePK=eEHg+-TdDYxha{I%7KN7YuO6?_8BLQ^~h5v3SV$#CDDA z_1m2-CUUiP4i{K;oJ=2g9@k(R_awxR!LOE>s%{g>6dIe9X%HSn*;)PkmENDskLP^N z0d!9D@*R?Bv9#8%#dv8&k=sl*r?i;77|5d9FHI2dsI$}VBgNLu#; z8Jtfuje?-jZL8>pzax-aH!|_qk1KkHoxg+eaF2T(?g}4xwA`dM@J@34iYsu-#^%}K zq8APch$UUYuhiuY-5Pa8XzwIss%Vr{QX-r|@sQ1U#vnzuWI!AqQJnF6qtw_pTk7TV z4g&byV<5kg9e;j`1QWq&m-pgU9S%OL$Uk4I=^X6$%v4lJUyBy;0f5&|Na5tMq22cp zac6L4({EDqK~LdV*VjLW?5-HsH{K5AWPBckybyBmwV*8($9qg9ibOSRH6Y_QO9KF& zE`*_Wv-A8+#_J|H?mo%U+(El#;$xE*X^U-GKg)|NyxsjAsQ zFyCXg9vDx$VKlA^qAhu~=L@C41Ead}o@;~i^Z@(BY4^?x0urAA07QR@2uS?#R!T}b z;cFIZaN|kM-n#AZd^vm~o=Z+Xb_~kx&CHt$B2KjICJ3e6TP>g0*y|*mS^H6WMc+|h zPOL89zITGU+4#x-k|*Kczmm)-;o!DN;OFPWX{`8XPf~5JpJ@5Tzw^5M+~W@eu)Wy& zJIjeFso#W_LXR4C%|RGJ2=3=VeIuB!zTa|_>H@&-pN#keqw(jK$68X+JEatjWr1^K z@IQADIv0h3`2^lX8L5uIA8P6m;CnY6x3Hc>ziZ5t5nh_^vgn_XQiOn)jQyni3Un6{ z5;PX|3D}#=1JMH`N014ISyG!wlhpNDtSA(vQ#5ktAZc?x!GZ!Rvg!vy1c>-$(}Q?d z2e3I^spz=AC-hGE-Bh)t==Jm=?DO6O!a5mB@v|}p4o?3M`H_%r`4qkkUn#K896Jp~-WtX2FUL7Le763mNc0zCIrIePtJlJ zkxwCLv>`}&X~PlF5x6E$gwiV;(XA{2Pq0qys7>O35BSgFaZ4P4c&SqANdl-LIv<&a=tU0nx!+(ZJ=H(B`hvJjsMn(29J07RjQ}14F zK=EQdPg-mfm5J+v;}4pfV7pMGgA{d-t1k4z@^U40`h~yg%FYQ>Ue|~Dk#-$i9QNO+ zU)qkuT`BgS+a?yb-I9X7!074Kco+wkgn$89AS)Vd1(nEgg%i`EjWS?}kQOsE>sF;Q_>1SvAZvq28tL{k;=L+3Ek`0#J7?jOh|%MJ9@+ zdX^^qJdi?9l<@3t_M?4JaXh#^11L&mpSU(t-td|^ntj3t@iy=D=pk@mlRMuoP02~O z#1gbM={R{j9ZnYY}Ox6%yoiUtf0`b)BX zb&Oba$F2UpVYE#&1932Z4{VlMB2Otww*6SmU-XND2Jm$~CF`;n&EIVmXgoADdy`9U zgTR1(3cA%bp$(EfFXwX6cSR)#!0-1XxoB-{e7qOLF;}^|rBbnZRdzHNN%;q6Vj8b`38k0o79gBslFz>4M4g(R<| zolF0}qE(29$lnhPX*1)zdrxFO>65VS@cTB!+rlPKFh-z{s04ykHfarzTJTY${G%<{ z&f&9N6urN^R6BnWV1n!ujuRktNu%w}z6amU(*rjq>oNNFQk}a?^Y9&q zgN}9dR-ZQx1eiz^4#{7rac{!CJ?WC7>~FtrlZFbQ`EZsX3AHYR zOdfu<$W@(yc+bH33k1ObW+#!#YNc0FO9u%s^$#d`ptW@WS*NnSaGzrtFjh*&tD+~h zSZO%wsQ)Fj<27{TZ&rLtYT?j&NJsyLQrpGN3_g)C)8$|5|3XHe@NuIAutCfl1MtDV z#TnwYEear)9l}5FbSJ(xJR0fQ&Bbrd73RW*H( z&|kfgTPYY{1hxMG1YikXxiMEhp;2ugxNe%5qNMNBM4;s{fCcy{)wN?wfQ|&QV825Y_RJ~NQABH=ONYe$HPyMo9lmM|V zK$N6bSZ$N)>UKsqx{gqmvbX&bJeZqekf*(8b2%z_v=adwR_9NXW_I|0n$}gs#nr&? zgP{q5yHy?JcpmPV408S#-%kuxHMbY4yB#5*OQzZT<(h?v*H$b~wBz(@tJawiUu5OC zAIioymf;?I`6xgU_ss+hVa<#4o(u1AM+g#PTsK4aIPuSpA^H%B9IB4;K?9 z0t(_wf@8~lS|*LwmqBg4YSXdMs#h!a_wSj+_TcNjt6N#YDTQYyCWa{~!())YLXVSW zS_ty8!p~@QcewIRJzV}XNRp8r%>A%p?qb5SIcsas0w3 zsws=TKd}lmkk+5Aor)(BA=Vv2?yQbQF6cn$ZZA}H(Kth~#R|3k)>-TkQ*CX~Z< zS&KHGxd5DnBhN4h^A)`E*iIru3H_O@Effd^47dtK=bv+(&0)r z^xVsT#as@sa6c}6<}*J~dSpfg_d%L=*B&B zQnCY;J7OHE4Y8H%HKz*cpaLG*$Oj99pu$K=J^8>_?tM?8U%YZbiDH;bc3vhaI%UscM117=+{?~RDy_YKy4obrJ?+tSE@`9xI z$qpCuXu(56NwHQo*V*hc)u0FD9(^CXRb+8R;T}~X6)2n&^H>)M68Iu5$?vZSBNiLm!C@ba%;kOe zP?}F9WDl=2xKslET8@e4a$VzMeD2X!j|y? z@x)M*yf}%*q2OA}S`tY3M>MXr^RrMXK+m!UOPHH4y%6-r+i;h(EdwF4<@#lts{bj7 z-{AZ!h~M=Z$1;KCnAI9@wTNL}9w%CsZnziH<>FyI`TH}Gz8+gBV-eqh%+h~$D-jU$ zrvjkU?}&1oqz&pb8z(Eh^k!Q!o<7$!ao}$uJ+$qMe1y#Q)x$N){Zp47cyvquIc zco=7fDteRBAJX_@up>hX09HoG=2$u9qG1>Ox_1TrVzWmTaqG==cLi*w3lEF}g_)cR z_P5LC1|7}^85B=y?^{#v>#3=rzzYWtQc@B*? z!4o}ms;U*pzxZ{CJO4%XSYqoxfBdEC+9UH$hAxEgaWR%iunN6*R1wXVUf?48>V`RogcNe5!xEFdSVpO8HV4`QU_9dv*msA{@`*n6?@=SNK#vT<$5n-L}p2MdsU= z)PS^hkbAUC4OZ}`hTB~+-x#}n25@MeE#RI9#x8Ch@84f^uo+1QsH5O~^1fOHL+(NM0beHt6Elt%vnEMEH@;lz(qWM13D5EH*1YBTV1T6c_;D_{4dbF z8=a@cL_8mes>oywyPW83NZflo!bFs_J76nsE7>idy45mtzk*Zq%e#wXb`YxB9JyoD zZXZA*`eg<3Tl%t^_U4ljyk3BLb(Xw^(0q9Lzlvg=)Q#Shj8qEL^TJ;c2IgX39akL3 zIgq3OaFa;3qHX(i-dv*zd0Zf@?-O5KKEKj$n)BF+LjkHoKJ>7gFW_QQQME+XyU&dI z?bdD}>~!9+3TZI6ZQihKk4@-_s*?q&n?y@BB?tlS*O4j;VmYC$=bsv3KdrbGy$A4{ zGj_Iabv2MdQl8Lz1fg_z&}pwrc-ghEG&Pu7qITE>(Z*c+Jt<wY1DU&D6_?x51_euNd+{H>M;pB#;ey+f8{ zV3+(W>p(bqb?fyOsAFmq;nw%@WijGptZoKlrW6x4ham(YQ5YVJ$6 z6xKF?>9cxWBpCN2^l^v9_ky4?zj8`i%9s{7CQKYBT;1D6ldL&1ImMqwF)Ml&me;+z z?~(68$NrlEqdG*mYobVzPgm4aiNispD>vPIzSJSIFea5lRB59%c{g$wi!DSk#ARX% zYH*Y)_9pk;KlvP{i8YHdC|G_*zWXq*HbebS1XICe(6#~&okhHF@xtmdYu%f;H8hfH zmIu+Nr?IpxZ3D^|Bn;1L;Jw{+#g#RYoDbUav{X=;(?q|p0rBewX?BCdm>uk<4lh?! zfoERV7V!BW9}xU-0;SE#YCZ}F&p!&oo>yZ6{!en^{-m7u4?1e>j^Stg->~@Zq24(8 zcmJip!G($^A?7xxg*$f5GtuuN`+_?!((E^vHJ)rLK0G$o(<7@hVmy9ND_ zu_h4Q@49cAn}(eTmf)E45@|iWDRLhEH5!yR$j-cS6oN-O>#(B@+%9_eT#aMSe%O@? zrwhGSnfKb>{W#YI<%296JV5Z-TpReu8vZhcj5>Lh6DapL0cNQ+ zGd1q=R9DGMIiRo_`&((l=K4(HbCTX}?}!hJj+<)VCZWlpGo`1V)R^^LEwe?uHP9trGwnbhSq`0jgb%^%Vt?R(D+vv)Vk%GA zjzln4_}2O#ZRR(l)AZXRzlg9t4QM$9#lE9IM+W;p2&>6O10&m*I{p6!u|%=tRpgb_CQOV*S{$-HLU@QVLp1`B`zQdxM}C@4WB8&W32Qj}gcV~W-)V5(l1E9ZGh0|RNMr}= zGPM_{TP2%|J#P{```Yj@Cu=?;=eMjRoo;aG`XRjz!!XSx%aHO?xLvTquegAP3HNOC%@d;HzBG#C2o~d*z z)lMCZ9eo$GL*oA}6oLHINYE42jh;58=05qOk;a#nob3&-v=5I zbNHe0zjO;|d~$c{{Z@ZPN|IoPf76D`@V zq}zgo3as|g_J7|>N9yhO4lD0#iv{S6SbK^4HV2OkPijhjC=<byyIx5C{jM%b@3252fX|HA}ikTRcnocfkAmCVN4M68#6*lK>?Ca_j|K`Lxfv``WfFR->^mEGBDCX(0gxWb$l^FN!J?{hM3DhK*~f> z%VHu**auYzrNmVr&MCX1{pa6(Qh)%@_cXzFqS7NgN?V6^kb$twrm(dIQnAfFL!vCF z0V;GpTOGMq+R{{bl>K%~z4=~rK(4#_CcJTLa<1;$)~1hg2V9@&U$@S-N$>=Tr4r_Z z28W0JUl^PjfFTv6&H8AU!!)HLOqkEFoRNa4mn}E3sLdok@U|JH(^p;|6M^7Tj>2XR zMFG6~T_~I9)eCBF!r-=RX|YLHR)EiEfLSQc=aZPiOf4t#b*=T#7rQy1Ze+!>vg#2{>q33j>wfW zTOw~x!{f6P5dXtb9t-H7h>7>U>oYq00Z%SEkmp>?<517BFZ0^wBTPUOdahh;mS)=u zrKwnC{8+_#IFm07i=e1$g$3NRTnLVKFk8ZiLULWzk5MkuSAw3d9mTiM**DgrWf~v3 zKLnptHSl>JGZ)a%aJ>IaGQR#7{a_)ds5C%+E7L@hev$VvCtZ&aTitJx-W|ryy0`+4 zpMl>+>r4#*+DUwHHANb)hoX)oiwu`&kmKESWd35>Tnf-IxXdN3;CWYM2ps5q$P@+% z7>U;{m|ob&=xx-$6}gD!?Bytbu749&cn6h<55;be)&&X3tInRR?@Y+q$X9vH#<@OT zEy9jTlLNX1CxSpC;FqSHb@8Svpd!mml5+I7{x4|YP$JxuR?CD0x9310!Mg9Tg$}sm+CN$F37fHC+ z*~W8T)Q2~u$a_VVMh)W_-n{U=wvR~ls+xVnImAOpgqCS`MUvDA%J-jnw5mX4b8Ej2 zLM|#R(dJ7Y@#TwnzS3vAw$O5!d}(Lb;a{`jn;p(+JmVzUemsk(SOE{N$qLlod95H& z#Y_$YL9D4|m@(j3uP;H${`ASaNd^cD9^aRK8o)X5>tf_2H{^q zU$3U{v}Sq%>!!RDa6Eba&F2cyX5oIyyKFpbWpleQ97N}*rW|X2-1h9esh(vzURKit*e`U2VE6S!e7?w(jpfc)3S)KA z8V2gV_(^%F(ytz@bU&^;BD`XKczsBCb(md4xBOX3z-P(VYbW}BGq=p#?-nZYlvj`` zs@KbtmX-rCL+Jzq^9kkGiH?MVGT!esK@g4%X4?n@`$vRKUegDHcRD=81z8F65falC zLYr_mcBHk&%^XUSPu7Ii0;DGRN(X)#Nx~NfoyJG;>1mC>V6@OOWhCc{Ffg$P%Ht0l zgz93>3h9vnIe{*}UHaHodkGyUBsk@75j89B^!>$UKRp?OAGUT&54jA1>li;zp8H!= z{r#wmOCxro(lUukT`d~F$1qqs;^vc=tDC-$IiwD4UXe10qfAXIIX(LDvaK8Q_J0WU zmfZu80XTzB$k~7*LsGNREy*{h`_Wrci;ZGl6ivLL@p)kCvzZj zLPF;Y~Ekdjtc2ERtW~Iqy`)Pm<|XEF6U(_cx81@j}qBi zjiNLG8AH&(e~PQ~J?s)5t5}eTDVh_`|6Y56^t^X(Geg_52AtyB(3>j0xTagjfU1q; zT>)t_Hju?6Anf$ayc+jF!Ow#G6)v{_qy`>GXI8^DitWOH!}JKJ;+uIGw3f0&gAr#$ zWCYdj9A~EEHPhEIkPe|0Ll(9F+bX_P9sX_{)-dxg)%{XMpknvO*$<4*&#chENPUPj zk1;3=#rtpHlw@=u(fMR`%^By{&k0dih|Ryv2>*2ds$;k%I6%#}iNDo6_D4@K1O*$UUJs#0FF`jcb0Xdii-qJK*#6FLb+vy>30Ve1{k~1 z0T0Isk#-cc!Cs|I8+?E7EqK6(7&DZ1Cez_5+dU>eAC$T+*M)B#eyha!@~B=|4=y~6 z8p3?KL<~iL)c~>I%*r)f>sT@w$q&g2oXMaPiIEDTJX75Bc-n!~lwX|&Gi`sV9H%oc zZl=p6zJ1&T`;F=LcW6i|c|2Y!Ibub{^XodeV2MQfKPxZ5u`fALQF5&I{yDxv6%_Vf zV?e#kelFQb8DgPNqHL3S|6GRulscT;(fUlSQ#KFok9-PWFv#~xQq60iF%&Z38b}&bjWNoIuS33%JPzxf&4~C=bv1r#%q> zOI5FXb2FVDkdLC#Wc9EQ3nb1q(>w^v+aG^!L)3SVb(rbVjB#x+!2!>$vKx<*S$mHM zS}Qtv808Fdk?Nx^E+~1z>Kgs|vj?vjI^y{n*G+fSIs~Nw5g7F+>qOHPI!24 zdJ4YrNIThSB*+X$-im)KL4lC_v&^-E-Ohh>nzX>Z5CU8*9L}${|59ozxQ-_`zLjTr zQkn3dRLsY3%wGS(={~;MuGllYUwHC$HZC?Q?9})ZM)%~#I5`pH-}mJs)8y9bS2*B9 z9Mk1A=lJhJf0p^58oh45T-%yg;IyyOW8mU{xBzwWlPGeF`*R3BozFUp^OUcg->$~h87(BHNvGjQrI4CPX z1c{Xf+92kZQ`ffXGl|QnFlNNr_pPYJtkC?Q?aS{byMGss4!m$=e!qb3-s6A2+{#F{ zgayNf`dZmh2Bt&rHBJ3&6 z%Gd?u7Hx@nbso7Mg*V8gaK)VE{G-U$iFEQE{(;cqnvK?cL>*i2V4QoW4{UmwP=~Cw zhNh42P(`Y+JCT?J z`t>M%U4{WPn;O6nTA}QAl>upHwAp_H>tMDF#)a>5X8)XV|9|Ly4kWYw*B&BJ{(qbP zzicKnZ)A5&-cg*$s)`ZZ40!Dv$M{TY)HYFJ8PHPQI5YPDj*^huV^K>KnS5|UvQ5B3 zGNmh$kokc5-Rb6>yWr3_?N|HYkIr<+hkuSei-1W&T}7Vy&#P7EPvqTI7x= zvuq*oQxuv2hUse&+)vQ|+H{}q|A`8PHxwWfUK3P77v6jag?M1_wjc9r0)2V>{ZK)b zy$9-x44|sqh_6p$?(~LH0uzLwpaiWNDM?-1Vm-?BgK1pC9}@ynR#sUkv7 z86rj_7#u2ET!f`)g?IdlvgTlKIFG6K)R6-0<)X&m8Sme2qHBP1{=ZV6!5`O;4GpTq z(!(V@;t%M7u4P89}#$P->#Ek8Yd@;${ z@Im(vG%_?2a_iLma8o}SLqDKg|knJ6ge5iFB6hi&OXnHXw)ZrGTpAnh<%{*gAS zdXgUrF%cS{L+(dubfh&@!RK9g!Lf(@tgEa+bRv;K`6#OyX|iyliq9~`fMFdU7;+wg zb&MPtNl{jg^8GEJNdgrNB}x!pCwvEEV~6%kYrK|yY;!joMgnG!a|4Nw{D{hl>UMVp zL_HS?Hy$5XvP~-vlt1wg$tBq5MHZy~7*-gU=<`Fp;6Fiea;__%WPAz3!b=I7e65cs z%OIzvtuCzp!9}&PHxb!IrdUN=PLGz5R_{~EwTtn_AD=KiQ=_R1|2-xlSHe;71XSj1 z69fW@w__bsieZ}5Ls|mKAouM)38V^*Bo@MQi0*1t6b$7apIX1egUx97K(uOsdGXbs z#?UmZ-|+jz4SyScGzam8(1gT4iKP26&uJhTaMzvRThoXZXMZfn1B`>L79f5uT?l=M0euQLT0s z?q7Q)bOkjxACjfFt(9@ee~brx54vt^5GwRbeTcVZ(9LGmUa_hYJ6|}6kn=DYt2Eue zP%{jWd$a536PQGxEbvydOdqO+=fpvGn>4YGZ=gk(BPk0C zxL+6^{h3EM67?lN%L54u+aSps>1KD%QMhySIT(bsy!>tBu3vH6RT8b4!d1xC<8N0u z+af~PlZg7M_s>v=FhV<&zlHx_r$Zn@Uszj6`k2;h403P^ek~qd9}l%zr?Yn<+)L4NGQ|Ez$k)`}_<7vcwF@x&w z>X2P`(Rhsa51&<_D4}N0RsoeA^@~&MEq?3L*`5XXjVQg=ST7}tv@Fz%hi!@prYF1~ zTs1*@~b2j zRVf5SUn$INy?a5)4>Gt)K^YWpIN_(qLeHO<+Nd6_COdGA&$__Pr#(40!}>D$d88Mo zJ?>8tEJF6B0><&F-l4OLPiKTJcwn^d`zu)b@lA-#o;4FAa|C5phM0mLfjwFWgK3nu zFvqUx9tCG$-3B!yn}RPrICK8~BPX7yFH#B&ppa0zPYL&o*rlRX_=m;WMLbT0rkL>> zlm5TIMCLpHK*0*?n({k#tq8Ag<|^=y68|-nO&-v zC^9qA9~v?x*g^@Rt)`?7zj`jFD-Mgj5x}dV=6@hIhVWZc(a?T*ShmH$PU{NSS1Bjk z&KaTx^SLpSu}3*$bP0LpRYheG$#)aAb82qv8`J(t5osI-dj)rqbMNt5uMgz`xiu6? zE=eT@b-n**l+9%XbGqS&vavy9q$eXVAKi!J=fa_ZPDH&?gJ9=&IK0RHcjuUwuhWtf zR2H+zvh7*_7Whn-4YMB7!)Se|oZWx&r)NO%T+cL?u2R?JgyEG6-UxIfss}Shr#@Ka z9SZvYqlvXI=H`ehgXzo#A4M~x9887Qot$8Pn5d@wSMEcB1<66`03^Wio1Fg#jD4`s z`1hMn;Oi9235rEP1CWInr~t8apu6)sf(qEjUQ{fD{))orL4o;DAuyp1{v*D_a=(&J zdauKE{~wMAGRWg2puK@k}!XY3mgWp^&!|TfOZiaa5`dJcb z@$8}>G+a151i|(i+lyt!+7Ph9F{>H_a>GjrPqQE6z8Kfe5of_4X-1O22n>JOqGn$h zARL>UWAyz~ziUdeQcsw-U~gD;B~U-hc%NeLiE{p_<=gm{R&I6k%RKSeU_^`aWKo@4 z9%C)}_bncGvdr;p)*qm6l46qrXpXWF1r-468xEETf~kI$V<6SwQ`@)cOM4g~pK86w zFOjwpp1kyjS#$FNS8s53_o%g5a!fie=-U&xWzKZ|#Pa6%(yxzj>?9oZ3JZbYJKr-0 zpQIkxk}yLIkA#X`{+S^ObX;03&;vD!&_d7pjR*k}OZ*t+Y=cw)-WDA$8B|R*LGM>2 zS&@oZOd#CN4)SxE0IJ5BxPP_e{{L`H`f%r?*2rYgqlvtVpPS*u`)(#fbF&*`gi>47 zU6|_wH7}n(SuvRvHhO|SP)1FXilAk;#pV9b@|wGid)X|HV>RA(6u;OQE#DH-wt1U@ z7?adZe7C2#Ab96bSdpYdATovj^{v|%2 z6B4^{VbFmKGfL}|I?&}q5UswhYuMr}E6=L6H)Nk90{uZriA&=>E0THFi@`?p4+QwXps;wS-j3;VSRy<7)(xgjM3xN@Ks|Sqx+GS zr0*F&uGVWX&YR)+agN3Y!SGmQUc1H?>bHf~M#Dz}kS4v2D#RjGbNeUkGK;`$*PJpy z5wAo+5&)}hHkTMQs8K-dMQBbN9;|1P^y|V^<*G7>L(RJI*Lqco^X_Hb^S{wvQTJv>S+owpvP~?pfB0uRm9O^Nx(Hb{U}_GB^olzuUH|3H7J1~P*0 zC3x2>2bLPzLRr<<2%#sQqZH94!Fw4!$qg=@-Mzpd#tj{8d|8*|!t)%((EX=TU_99qoX|eq{+Do;n5d6TsHWAYRjVzh9?OEJ zL9;rK;|orLdJ8y5j@lpa&)CjfghoLbf8v0AdMcszkx&7>)*rGn71RBtP+4DHi+vhS<}gy$uc5YM(f`>%7So~&uMz1 z6j)wflKBD6BX=8Dtw9o?T-Qib^uL?Me_pAy45}=E;cAAqP2HW0#yxV0#q40e*-YL_ zGIGX&xm8>&blZ0C%S?nfX_RtstI?npQ$$3>qjF3U9kK*`qotD+DJ+ zrPFFbz4+$cBw?7}4jkpvGW0`J%azTcTX}0uRM*v>m^Q$^qe%CsscInf zabc?Ly}Ie%uAv7nbtiWAYvl8y>pr_jIw2QH_HZBeD=@2lI+1n z_br%HyDbUzK_4GX=FOS?T!r?G`9T@>D28YR-oCahR7ruz&)irBv(q=?*kvE%hr_Wn_KNHdM zQ)U+gC;Dm_9k9I`PGG(p*%sG_GUQ3kq{ zdVM53Zj-~8&Vzv=K0YNZq<{uag{QBEaK@33@MmUs7V&I7H+z zo*so>ozDQS>qrgHVy$ZSd`qDA<89h*4G!G%!?a_--X%v=^#lHh`(yYr8n^kOZ3DX;?Ql-;iwQlg$n_SU-nUA|FO zec@An*uL_uHTI%tRd+!@bZm#2nED*fDRe|0NAO|zScWP$XJ%s@v+v+$tF#U^AL=B4 zE>CRu$HEm1GBQ|HR9t4~w1}!tEe)0eQ>f7JZv8L5_~!xwXu)u7(mE_YUwKWZN9}h`lG+Qxsnwoy^c#oeZ`0;$=m9s5w=X_9j_$4t^#3Y8pZVLx# ztvbCm+V4Pi)fc5tT=@KcP%<7|Q`SLd*10<<-1V^eGN6kSLcsIPMv&oncPHy}OU2DW z7)g$GPO*zuf8%1y`&^O{mG^q5Aw^Y=L5y_I`z%p zvAf*oIU7B6PR*6cR{7<{#p7}3>BL)3 zP9f||B9)Z16#GpaWg3qYz1p^!CNz1I9Uk%z%uk!!pEU$PMymcDuT4`*)rL;%6V zF3&r?F-vmOLQ@Pphm2~(r3r`SS_uA`!S{*7nQ*1B^a~g-pWZJF_vLqT(T9U4*g}zh zVWp>aj&qMB2&NiqNA3_H()lS6_1MAApQ6CbjNRdh1|>sH_%!(gBH#sU+ZUF5;=*t} zb@rvw&H8?1TWM8r(cW3sW0upRq{L`Ripgc}MAderh#8nHkl$)px+!6*zd@=V`><9D z9mu@nfszesVE;?Rhw=XBdh}~fOAbdDmFnOTaK%e< zshEgXp z+B>?%X_+&=CLBH&Sbni}1^+bljLxU*wEG%{NkSCui+`I}Zl`4<&=|Sjb>JT-veWMq z9ave-qTlWt2t^7{acxQ8xM&xs44qZowR3Mgu_QeFb0qM_!a9w>aYLTa;H_cT92x$p zfmpdrB^c{Nx7NbyFuFfHj14oI{GV3psy`re5I`XAQ-Jc|nxPF32_^ivVb_rI{o1mm zJqaR^mM|V1lLIYB-?O)aXMB0NWU)*#Q|ws*S{j zY$x?yo>*2|Gv9BX(zqlQ`7}{OI`is`xD--XN5eJN_NeHd@1(5tdir)`mkH?P$J`c0 z8z3)6sQHClsPhH5YEPwLVSugL!8b`NlrIFjAuWnd0CS}%tQivoSm$$0mvCE?x$fCQ z4TPKq!T{HeI>9eH@c9Q8)MbcJPjXbiW;EK)WaDS==uAO0tVFDm@=rN0?9L8rcI|Oq zQT*9%9>f=L$|fh9P9CJcd96bzs)cbbnbLB0GSDfv*RdDv^x?LF5*=C8xT( z_S*393ui^3kcgW!MR>H<^bU#3YKh=Qk!Ks34g0Dcf93@P+z*pM>lJev*LGlMd?lgc zVXy)#Z;6&`Sh|gbfDBZLHj^%pZv@{7y?GeC^3bx@j(wXCY~GxESONf7v9oQO>XZa2$BExJEmsK_>x1bDiS2}Z~jc6(x&>_R3Yn+%54X*aw`6C*r zeQ}0~1dwUBp_NAFOxgB3AkAg1HfBNMwBo?d+CQ$(ypB9OY`u5TQ*+Vh08h9HDL?`u zn7!Icy0uVcVg%el(*@qJk|OmE^Vczid;~5rfg0Ot{pK2fQK9k7us4WMLZ<8jsBgoR zx?a+1JTP6_Tnm9WfugT9X`vzRXEh#+yVhT@&%^LdS~}rQcMpb#$Dg4}RnanHC}3uWbMxSHaONMqXwHwZ zd*ffH+T7=$9$$}v&6D$EhZXac{8-1L${5*U|>+P~C@b*lL^-aq(;n zqFR-Rdic3y_aC;N`{_)XfWw*@7SJVX43x=lU2JzC6&kF(|nq#L9p zq`On48|m&A=?+2ZM!Gwt`(6Ccz2~0u;r-y2F&GMK?_bRM%qJjT<2&Heg8w8O7-D$8 zVY;D|<)w(D2**r?=H#Ui+;mRpj5h{{AmF;XUjxeLvm2&AUXTdG3QD5`P`b#Z!ePQY zUD;J+@XBvOym0MIisG3C#9c*nltQ6>HYJNtyn@blksWTBVF8jAcmK#F2IGd|E$}8k zf4RvK02~3e%Wqa_Ss@zU!0BC&J@Ofm!S{5fust^J2%<5B!XN~dn@^3Af-dV6Q30CA z!rje+2oAkL3q@~Q=HC3&=j*abVTGil1oR(2HvWij8Kq@R1Sf9^48K6vZyO#D2y{Bm zBO74U#nL#ALyxgU_3BI_8z}~ zJPME3f^z=SF6{JR4VzHUHzkdqTa6oxZa{!hkQeU%hzw|*4*g|X6AXQOrv?CeZuS(p z%N>C`gl-b8k-b_pYJ{$T!p$$!)&~+dcwEjoKqsW#M!_pDG8%4#a0Qz|kF7GTs}&?b z;Biz*PuPW!%buoOZ?6m^7zip!$*G~KE2$B+@Bl_d#-Xe9NMcVx7N0q1dORqgqP2ph z{n0jvqiJk%I|5ljPhl+g9g0?DefN)Q>*__LFY#qcs(}4ylOPYCBgWJDV{B4v z=vn2)n#D#~z2dydW7lQl(+ywijt2!D;}_!Zgl3>K{#S!tZQlPGQT{8GWb1vl4k+Od zvHCb&K>7x1SV0#xY-QN5n1Tc}K!{=!yB2P2_<^)2*}aOIaYf_G_bJ!TgF3u$%+$h_ zs6T#;1qz6nlGfX4SM2>0)w-tK$sRY5HZqC!)1)VnNTpOC?{7$Fh~lL#BH)jLpu75s zpS?XiqZ-7YoIto-SDVZ~{6Jas57;FyYaFv=a->H&1f1EF^K2s_(LquvK(XqnjOFTy z8WgaL+bk`@9LM}*OZZ{ajI3%1>K*JHA&W7s%Nt511g^)zcRbOAh8!OtKj%`qv$M$H z0GIiO?mePsaw^t0kbtsk=ls_o`ANNFL8Xu9yk zPD?r8EWhB$^eO4l3oaNAKwwTsZRse=i}tn92?^O@!>gYt0#te}!eR^mB=e^AQ4F-`u73hWN(#6v1)HXADmc&5~Ev4hU2Z@Vr zypLu1y(z6=#mkF)IP7c~DOI5}fzhY7fN!)jXMC749p#_v6V^uOT^5(;GW{wh*QRp+!8P%^!vh-SRc=2hjV!(ZlzRy2 z`m*4Vv!P^kET`yC5pYj=@*Sh+SNg#|q>qndv~yR9r>DycV}c2Wp97MTs41zaV$#yW za2ORG&O=GwtG}iLcs{;YeqhbzKicS)OdGGW5>&YV>GESJzCHrAgC6{x3j7l^(}Pm= zD0nx2%dI|6=9A|s$~{IzFbhnT3^xhd32lDKPmp++uVJ(g*(FC`Pyq^8cKsb*Aq&v1OQaf1slc$J{_Mvh+Y!h61@Ugy;N z$%p{}=#?vy_t&;xeJ&wUc@@=rGrpcxq6-gRLU(Bp@no`)n8Bvbcz5@`C;*UIgl~F8 zA4xpcM1)#&N-Y3@8(yl{twYSkMFwe6UF@eASZb=(-rFDUu5St|rvj>!5q#cWEe9q= z{$O!HL`ad8=ocxgG++BZnpN1?G>Kj+x44*?Ko0}pOj+?*&4A83I2ZShV`jndVW7nx zD<+WVGORCKQV96NJ$ey5LxOM~$N(U2bkl4z`|aovLX@suC=$$1W0{=*(qsRn z9sITKJ2jO!H3`FmuDK5za2fE6D7DiWzVN`@h8n?#b_{1ZjgL9ObJqeNe6NR?$S5FS zrenUeG>S~lR2>o<*8AARUS86YIyKNbhjzlCV9i4f(|dq+zxVL;It!0?5*ohkew=Oe zWGSGy7%%n5WR>H$GA!^U)*IVWLxi-^f;LIL5~A|oySTl$+UzG!3{GT#~(5%(54LW9<%y(5Wa2RVU#~# z>91H{paTkv`j6Bya4+J)lbD!C6Tc>em&O5a{H^?M(Zr|TkC*`wj^&v4W_dI z-{vj4JKbJNV>wGguo*hL*6sYc{;`ZtACwzAe`=@is2s=Gno*r0C@Qu4%FdTMM&KF8 zHo<1p;z8%4LHPxIHXUa^6};wLBYkYZzlGwdfGiojMD-o2?jfzdgtEd>;ZOcn42DHVSd^#FgZYY*E{MySaU5lrF|Z`UTX4|%>Rq-nqNy4 z1-oDnbXxw<{CC3i8$qDaq#Ntv4L^o{nEIn`cgMoCs1{JYF(h2w?<)v*-iYu+42bc! zT9$TQduThjKxk8@%QBv7=SPESJ?VA;tyTzxGzdZWI^c$$3qFsC@y5=7^QHmxD}g~e zrJ?`LD-`1j@3Jo`z8NQx%_phtF&D80to@}n20EYW3$=KCpoA$u$pWk8mk^FARwP?V zRbL^?lvNC6$RR^X)e4ONne3NcE(;FwrzVj>OkA<5Y@pZoYtigT(Kxfi~O~nS&|`NB>?5sKXvC*3>G`0ABhhz@9B{Rp9#N= z&uCom@9Tn9jm3lnLI5)bEzr2&0w<+C67r1Y)rgSQy+{o16PfO)V+UP0RT|k(6b42c zKL%Aa)LwxOW;Ne3npHG~-@tk7AB*dO$vI69nVk4PECVpVTYwSPQ*_`!sXOP=fFqsx z4La0S<)as&h7f@j|K6`()yLMsjBcMcQckEpn3=CL6EL(xB;wmm`#bAA7zPFg&vCh- zNM|#IVO}dL-u)SM1W-}J17|}?dw=@o0^5^C6oXzK zOsvn(d_YQaa(IPwtHai^$DL1V%HJ;}@Qvi;Gw*@1Sy$e=OP$U+8wJTwb@Mmi2v80r zva={DJtW6M$!+6Qgx;PKgVJbwpwSydCmSZy9L$;XG+P$G9Wjn9Z^E%I4t@oGj>Ur=Ba zCSt=i?Jq^~G;~KquIZc{JoESyUN?GWRu9m+AEAlabkE*LXpT=Ys#IvK$*1=*YJv|1 z?f=gB9VuBL(yH|FY;KlR&vhYl!gRu(V7a0p!Q1&JL^m*#U?)0@)ffpEIk{EOtM?x9 zIAdp<3FHV0quN^~e=&pt9undZuiFmJZZ2Q(CG0lhm8Wk<&q+_ryK&lWYU1AC@f%6krgtC&MYpL?$wpDNHC*X9D*^5 zamNh>zy!kBcgmg`?5ZC!M7RwH??&Uq6Ix8vcULa=4~AiSH}rmm_9u9&IbJP8 z$-x4}Jn(>tR|V?MyqFTYGmq|^vyX5(zhMJY)}i%n^AvPX>{I*J{yevs2%T=RpH{iX ziANA-Cooh-PXvA@^%2Oy0u`RhT^H)-W2H_6u6&xRP{vRq>pv(;XwR|D~1P!iSn>UDaH18^7^!Ozmwd*2-2 z=RBbTr&r&lwd(qac4i+b#)u|xIjLT2MZ6VKFa=u*$D>0NR%GJuN!cyLZbEwsyW!T$ zVIb*9cpJE(M52YzoqE=UtCemr)P4(=8+}UnVW5 z5O<)j$?twmvO$SJmp<~kL3Et=KTnNj8C!G2Ks&(wGQCqk&VF;ju-^+7xc+@m{zs8_ zi9FN>AL&(vd;$xTa6*XMD$2)mIxr)CrX?T)$Bqtt6M`*gq?=f1UDki{xQRZ+%t|3| zL^c>IbPUS4uO-q%ftFfdCP6GRbPn?gaa|c#Yyic0??aW0~I3@)_eIb zM3ts=4DX~a>nVh8Qt-n2)btoGY+qoZPIV#Yc#ns5UT3|%UnMtq=fsgN zywa1usllyJucZyBPVpY0S!-buebiAux0+($ zfqyu>BfdzQE}75qe4(dQ@R zMK}oYFRCXA-SCB+zQO?_B0W+1J8ZyOwp|RN)4_FD*q7G!J89x3KwCx{-eE&MZqq>y zG}7D=Bg;;V7!Yi>4+25?cw$T}CYMQ)9bwY918Wy{D zH9TRLQtJG1v}^|L&voEER_Vds9-qCH8aei98GE~jw=q25+%KYk`e=Dfh3;v2->(lU zX2I`!=i6xQv3t)BE>R2U)+!G*P_>$kCVHUg8 z_m+6=UC#6$=*e%5?B7tG>4$_V2T|SKMq5i^`djKm9=^9mdE}3~L)bCBTCKH_xMghNsbv+W z_?#gcLg8dr(`O8SpV-SAv;toDbE(`{O{)>UYpLWT5u@Cye|wdCq-F7zQSqLZA{Cg`P zk|H}`h~?=vFctf`@gAFCry}&*hJrIem$rL+4Mu|`+sTLJDSUflV-#=(!Yz%()Cd5S zWPUz2Go5?>2xw~Jgzyx{f_--=dOTFn5mCBEN;|8+*UmvSF+M7fG~MCPVCojF?8|9g zl}+US_YN-PA#PIbtf=R$0x?@?C_qi-N7C+%Jjx5Xxw(A>in_DQrcv!C4?z}!_%*NT zZig)G3YsXtJo6Kc^sfgSu;lWacW=!SVj%y4!1}=HoHa za0Tt?P=ucJcwJEZtSf%(>&vZOciK334P%g{P^aStO1+8=L(}Z(X3lb-wml2V57mk* zPxgZNunw}aHrkDN`)CA4`(MRWJ_nb!d0i2jS85NRoGMx1%KRQ!Wj^pgoUg{*fw@Kk z{Q1+<$>j+HWqzWzZC?gTG%V1Z@P#V$BG>ql#G$JkpI#N`OuCg)yofgZ$p#pVGIF zh~z{?K+gqMW31G`v6`};urKto1{a5(1IL!TPNR3GM{k1*5 zAWo$;x|&{&Iv0A>?vetYd~RVJZ2)1!;?hRlBx{)|v4%PT9 zh}GFHz=lEe1H%uP^g6cD(9jauh=62XJ89DjJ{6Uiz8?o`EN^Df+uNnrw#<+uCGF0u z%P%^P0U5C;A>Uh!N+*HX(_0F{N1JgmdEBeX#ouv-a~mMf&HM6(S0inq+qVIigMeG2 zlMNm01-hI|%tC(N`jEZcZeZ3sy|zYqoFhFrSGw7EpwviX4tYFQ9|Ji7P=l>x6wD}- z1?=|EVze$Jy0z9+K39g^PQ1=7dmHHC4q~A7M!R=U`s-b;TxPyEPHVfe%S^QIVew)TG9!mEKgr!?*KAUjE_B7 zLK8K`*m)-Ng|{*5pA|Q{o4Jo?hwJlFlf-Ys_LGEq5as;o++5J zPqL%GzJS1}-J5BjV_WQJytBizl|Ty-yyV{}q(p7h`{uUYXUkh~J^nVCdyp4YKY>k-YOWSpx;$}v_p7B+*1 zciztac9>4M8^zl1+8<)MP>ux2=zvYGXtB=_@hS~D-pTh}r#0a>e%sNmDEhp9wPA6p z{~>Z`{F71h5Sqq@6bj()W3FX-JQs^M^PNF;(nIAIUnAw8IY~XEu78pa+U<2y?{LiF zkyh2YsBT6f3jtruNEmI6`E%;m17&<>7^kH+7N-mQ^mbpA%c*HnI;JSl&9>Tv3wK#N zC4Z`wv+`G~l&6yCmp>ioP(w!@-%Qu8LM$ihk(h{@gY}tOR(+NT=<(B|&OU9d@pZkf z{adT$VM5hmhxPY*t0y^SgEx#1=I+m(sJtf1rxPv`!25QW3Q{iU|BETUrpEyxRDmF{Ou8!fn=ktXL4Qc#Yc6%L z947N?sXGoX5g)SWUWeMYO-GmhPK7saOKY1daDw*-Qee$s=|TGbHOlPbQ@Cj3;~``y zQgN;QlVFm97SQ0gXnZ+KxXW$JU^6Q}bj=UtU*u}rfAsyi%XPyXW9LA9a+u}gl|bEz zwZ!G0<62Y~e9q8vFrfI8o_I$MHBa*&+$8HZ(lV8HTm)ZOVBqKvp7FgHdY$bAau`!a z@E{Yj-n9@i+H1~yvwYLaWEgV@ci)~a?mOiQ2p~HeV6}vwCH=@UF;# z2xCm(L5Jt=+eOd2mAvYU+Tik-QPUMy9W|*}O}Dw9M-^xt-hzb!J=zPe_zo3-#Rp3Z zO3+tq zUPOPu>f^wD${H)olk1aZ#WX${x)=r#ZX{h$6Rr>ly#A^IXEr%S6=6+&BYo4=Wfz%e zo2Btne?Fa`4fA;Iy!9^k&L++CU$St^i*zM$wpxNS5H$dAL?rJD@7VOr2w|e9-c7Q2 zZNydNC8TPx|B{#et`bRv@y|yASrMz*J9ZxmEpk_QOoR2k{H^SgrZ6ix5JXh3RumCR z&a(nbu{eJ0PoSOGg2Rv~5i?j01@~vnVg9*!tQ% z!!EyT_=65zRTaFfM4be z0lZbjZHRAp-|W-6KlYlH^BzfyN<#xzZ`d&!o?DaxM(HY@ire3+8-O)N=TJFw^~!z# zf%0))j;H?o>FS6NEs&BD2Ww5D8Dn2R??TXBZKg|ENNJi8A~@`JJ8sJZ=T6Q^K?K8p zC1G!g2+{(+M2AFm#3Tj` zD8QoYKJcJ?*c0O7Rm@H%`w!D`i|e*SF+g6>c&=3c8$e3;{kW407M3JmWv%oL%5I7b zpT`FD5wnBvlyy0bg_AW$S(Rk8ItT)8K|t@;oh~5X!rTbVylTX8P3}(coFAq`ZQQsZ z3vj?Vf+6A=bIjOhyawpVrfT5A!xt()8 z^2t`8!O;pD-J+C8b)dv5_qyw>*?`fLg|xS~gX4kg03BVf>GKn|%012%{|`X`_nM?L z^*Qj?4AHsX^-clbym2&T{AFOmwSu%oLX*9eYQ>I0a*p%U{FQO7C7BEE;_)gi%W21v z4XgJy>b{2i#wJ~W=^#3hh?>ucWJ9A62s_2aG!;bBZFY)!rYEO{axJwOhk_6tLlO>j z3J-vT0J>Q^_UJKyjGW*#Qq$fJuP|ANyp@)Ts$mZfB?A%8jBJ64&*P0ja2H-wB5h^93Qbaz0p5cYl$S(<`)flyno?QVyUXuEr$3Y*t_R( ze=I2zl@bkr4*Ia%amt3Vx*P0;%QQCnT%X3waxDx4{@k)7)KVeGXkMBKNKRO+Jy zr7dvm?pY-x0Z|W=P!&2{Vih{bJRbYh!#6>ib93vlWrM^5Nn zeztl`WC?n;h{}t{jOx9eQQu9~SA!B^OJR^!GF`zJ{^0jZ?1jJRR2z_>0P+_>YqPE` z@Zv|#5Gq%pdJRSgZjL;k&o!9e&z^Un45hy%rRwovhDRXGc{cO=RcPn2fva4=yjbzA z8C6jFQW}Pcv7D`;KXkCA+U(5J@`p_~da``xM#WvqJA^Ld$>;TG(!X8o$g4h|eZjFc zYP(QJec=|kvt;3-nN}qI$>IFR0?xw3g%oMkbEPaNEZM9+0Cu7vtgutj+H(Tp>&pIcv%+UIjFE#}^^J zv$*toK0p6pxWf}mc$!oHL^}+#M(?KODV}|ww&H_z%uPd+-0cZRN0osv$Vp&-Z3U}i zZ7nJ*3dwNM1&ts$?tjyE-WWstH!DBjx7SO5K>Ol{(`~2 z?o*~W3%7_I9rR-`JIA3$C410A3ypw*)6(Aal0~zLku*fFOyMpA4AffGJJjuf$AugP z)s?@Eph+>^-4yFg;DP1(IR3Rg*!BEEHX_qH62O&iBtZ%N3f~~iU~`8Fkha-{r2FrP zK7&{%g+1dT*J2@KV5TdIx56E)Y0H^?ie*UWxpaT;K}~#|i|Id_c*nmn8R?3+ zprOY1oKn&dE&`dK z|01BFOTbOTLuvczY0u>*_5E(x^0ENRpY@Ej`m9D&LIL*9Z8tjnn%ShP#{d~{?AUN$ z8`}~e7#I>U3~IOMprD5R3d&G}A2qxd-5p`jrU@oo;oXSAT#m0kk=3gIeKA*v`~dv7 zj^P+iO*W9_G3eqlFcY)p0=$p);V* z1s>)(^eauJ9z{b@&1`%=$_FK2H99?L9{}7kkh+(4qE|U*Z>1rU*^J=TyLVL7bDPcn zpGqYSB|XSWnmPeiy2Kf)%5fn7CwFxr+|)Q#OPZ>PbM*DtjQFrS{MusN;UG_Rm&M=D z{*8xm`r09Ts9?FK0jgmBQRs~QujZr}51<`#DbDrytX7*sy8{eAMol7XavOxCI`&1v zw;!P(QM8(G@?fpOHB%rn`{o>?7QgZdIrQ5{I3yz?f>Y2|& zEC#qf%VUS?R}86uqNF#Ma?H9*Ab|sq!{vKd=^n~EU2V8?E}p#1RO95pedZ zxO+DL=KJaH-`6R8b0|ELaWR9-=jHDVr4bmSh?wfjeuM7~(JcmVE60<=7PY60S0!TY znn95!k@tW=)PpyZo~I%xnS8rq18Pz?RSpa2=*Gn`H4|Zpvc3+kFt#B%cC!zrV{cXd z=E-t{G!>+)xfI~9FS&G)SRQ;F$nD}MCWWT8ges|8#hg#?iWcaRRR|hc!~&|s(PHJ zYzK^pgUrA3v7g?lz1w}) z;lSwK{Ma<-xqw!TUb845Go1wOVSvd6&S(B1c+mhi9N2|+g`j86mKzFkEpm6J-Rs}d z>2P5#L{da>+>b3t{Z%AINNwKG9S3r8KTqJPUh zPjzX*6|iEGT4oXR+-TcZv07HG^+{TsQe7SNq)?~RuFKqq2V}#;nBaOq#?b`3`|fxl z1;}Ro+jArI*&8|R=-`x(3x1e@D_~*jmg9k0p7X`i1Sf+4++%(k<-L9_u6dwoOoq9?6NT9b2Q7=0dFTD0)F0{VEyW3m?b?JLABP088c>EM|B@Y7>Rep;sGv>}- zsbicO(h0cxczE1We3#sJnA0U}u+Y?Cq!VvV7K>Kn@Ga z@Xu5hCypS4>-{#aWO$(RTQioiCP{8dJH))=8_bWpeRcXb;4zUD59O~MVqoopP%myN z#%#H$RPB$F=T=&5trN)055xy&^M{;0j_=#R ze_rmX=;s{S9SS^+Ki*}`zoLdcJ;_ISQ^rE(@5iYKy1)psYa{|jmlxG0KadHa8%=&B zS0#Yo9MsG^Z355HT8)?U+o)?F+^}mNx#U!lkUeCX_B2FUGuh&LHA!UPSX0oPAu z@dcj|r}!Eq9?-y?Y9tRNS}skAa!2apBA@Im+3+uUUYhD+3S~q3XgW9iN zoTU^~;ARm14gNo5zBs;Ir@L|Oue=A}@TbC8hF^?9Pp@Kk@Ga;KG?Dac{O7&ge0eWH zt$Lt!7&zEnX}YjIo+q*vuPHIX}?NvRp(w=~vooSEyk zqeLWN|LTOw_$1Q3C;3INOEUj^)dk-o&rs)Md9lAHv#R`XUIk+$?O6lWb}Yv0pymkZ z|eVGPH6c^_6eqvZy9obK`PX-?WR1%@EwV3I0h^ zQJfN6?}_7NvXp2mepzqY$@l8bd73Qk| zXF*x&z>1kDaj$TlYg%|a&Hsg7i2FrTjL09@%{H*rd`S=PqQj^LsR)8z`RC zdGq7@-%hXopkQNip|h`{-?3i54!hgQlo`P4J1EY*W-(bOx11@&AzpeVVDWJ}Ny>@W zVSqD1d@+${K3_+g)7$sq&F|*lGL7mt88lT?(NoEKOKkgW{&+E%x(=Eo!J_`$`kJnFsAR9h9?@Nz1L#7?vvueRao@wCy^mYL=;Rk2*b-qB`~WCCO@xz9*OqpMcFRc96PW(Nc4*=3_s8e`fDD%#+t}H z4KFKlmY!GZ+Oes9CND_{M^8S%j+L6uhKXU00yUyzySZN*p7k*Y;%D25?ZTbz^C*9P zmCqCXN}P%NJ!@%2<&|N#Fabc9Fu<^;bs}vu5JYljXsIJhf`yv;VX}C=>HfxgHrm^qR$h$~HquF#}}!qy5!z zcbum|p65uZ+TDsj1lrfNF?aN@Re$7@GBH4`T&zx)&towA1sA@4{+#W)Gyl1x>&Qi( zRz_v^PHUQQQF$w~NS&%+j7$=WPC34$@`Imr#dn?98W0JWUNk%QiaAEAQ~TMUw)mqi z$&T@k@aYQs`W_ldR$)Zl;f=$cn}#FKb4pz*$C0%TM+3sJ>LtC%jKq?!OIZV^2LFGN zkg~D?7{D)Zw?ijBNBN{@%9glSLegRI1Qz?l)~&dE@+p*5y&nZgV-HKd7jJMlH?(!{ zQ1=6`FrKB37g^^&g*nEn*FB6Kx+*d1H)PgeY8)!?K!#Eg%I(Oz%LZC8unu^SDWA~6 z08Mn@%|2cL228y;#hw-!=K3u_z>9r0(-RDGi?{_F;Z*^9U=;}Vz6DJ($dYMahyb*k zs&rfgAcxw_7;Kb@Q333!ATXH@{9@Dj28@;)y|c|`TVd==^hrL2NYzUX3=j6LZH475 zXf>3kVU!Ai^%>Qx!T)G6JDH0+KS2QyeE}rpyru@L-lYpbS?;H(be>d z9>y+Z@HYxhjmC$eL3CU=|0tT3q-Mc|`(=b=c8guiNqblSNfJy+?ausITv5d6{2F}l z{JTUzg!LPb(}ij-#bl873Xwq1o7VzK=Ru->;Th6J$Fq6{O}bu zI|B;}St_8IQ4=h9*+BN2_3nC&JN_`QHWOzC`nl-FAcbkcIUAs&A_iLR`N^uk25w5! zxN<$JVEH-;fk9ys^>=~105quhx=8FL1irx-bs>XgNjg^T`g04!U-+OQ;J0ss9rfAC;Z3*XP2^x>deEkctkz9V8TB{)Q(n%|JS0 zpSMx2jw;6R4wv|D-;F_)1a$b3SjT?{VpAwZV3F6&C<~%j#Q)6Su9{AQ9)+*EzdXS> zNiIiAcz)!Yzp~M_DVy;rUnAT^m3%GfDZtE)-o)F~*!T=Kt0uwFKo$rV`f)#^v{bm( zMJX6iZxalMUAvqhN7iuw_QO-07_VQ29GUCR&wk4=H`%Sr!4qne zigJQ-oY4{Hf;%IsCq@{rqtxyFd^>h9V*6hEsyg4Pd|XC+w;rtQn%{SPL^NuGca(X= z&rIxm-wpVyI~ex=YsaYU4l)mF^&x}p8B^_jr}#(?lrE5i_2V9@0%?MCx0)+}KBda*S}Usy~7-YwQ1l zqt@~;Xq96ebOM3llv zKoVd45ET7G-`^jkFRdvl!|4CDTYy@&H}ZQ1ASE#)GK@JZ+cbQ=EAX$&kH1Zf+)P2f zlQY6Z0O5fEifvk5cLLdl{o1m+jR2wl`N5UAlpH?Tjg2l#h$(8nF8%t0o*5OW%C`Ni zEEjx$4H>%uA&6=@%1X>cuyM1;2cfOSIIvmTg6d>`V|aSir^{hADZw-DXH>cBZ`9in z*)M_6)xd(%eEMSiaxzCqPEHQpMy>?9+vHkPf>_}dyG{G|H_XRv{%IuyPqnvY-JtH~ z(w^{42s%szfzLNA7INd&wPLwc08*!Qoj(Tj@#9XKf-TEk$UQj7{lA4!yF13b@ z0S`pDU%~&FPRPmq-S%EZExzds%Fh%$o@Z_BhHi@^Vs-=G`sQ$?}c#BM%fnC+I=d>V1RA$md$TzO1S20{+&G zm8$cR_S%t7+srbmCRn?8ld$9^w=t#qKgj$ygsI5r=+K5!r>fkG7_?QjfEa-WZqO{J z#-u$h_z3K~;SyierT=~0gV)F23I})tT&1OK{kJxq6Zt)8_wLX7YL>SytX6db5rt>; z#P(;h0?(0FSon37xSj&)WLRVvs6PaK29V_+roCvToC!ppeX$;))s}WPO0O|vPD!3< zplc3>$WGbVji%ZWLZ&Huse(0fd4TG=ryIgQ0liZnyk#Cf#~^T9x-Uc~F+C`L=rC=; z1ZxR7N95hbdEg73>}ie8yN`b4D{Y})1_>8WdEr@EkELO9ryJWTSjw&az<6H8{e@8y za={>k@1mwGUJ_&}qQ66h+LQ+w*=joLa;P7!MwZ$Frvut=PI)s!F46lq)lf~fL zIQAW&vsy0HaZW4Ae}~F7Sut94L;;OBrl{T-NJY)y?z^QMGQ$8N1}2J0Q`$ROQ`wj_82gayBX?&sxcv)07Z&7da|XbCYgo6!hu_hUtFttBZ#r`se0E`KXu zlyGe*V`%(+2ij}-#P4-R8cwF=5|Yz(U3jGhREnNuhZ*=Gg7_ccy>^EY`dT~ByQ z>)7v{)V>>JaoCg4c}}l%>XhFg6K2lZCUar@(-yp)X2yf^3+dOzN;|ht&cjmlxH@Rj zT`%jtbU;%nuBwBzfg2gNW+XmuC#3_=#W)vyYQ3gXk%!D~!CuE^(}xcA(6F@fBAUfV z;{#@=5Oxzp5ON+Trc~2<0TOGX^qedQU62TI@GA(Tb{X+i7#?jd%H=wrd^E^2QGV zT$1;E#vFHC%8KxScqK`HeW&`sLtFsUQ6$U4BGQDar2e6MNTQupF z4|MusAs8;v)U<2LL%@H{pX(6c{26qQvADN&$NHChj1CuOsJ_b};qPOABLPTwcc}{w z$4Nk^Q={h|8JA6c-?(`4YLhuhT>JznBLy5C9n${x&Ynf&6gF}5zVgDt1L1GXAgvLo z;2jBD%pfDJe=VZi^7{GH^Q)HXgQ4ZBdycj>zP`-kNq3U7{`+99M#YWA{Q&{|5w33E zet@H-EQH(PlX1r|+}bmi~k4Fui%UI7_Jbou^ zSaHIlk~KI>(y4qO$P3XjK4TluUlif+sSq1Bp;zE@GEW$Q^30&tdQ!_CoO#Vhp(W#n z>G+kCM($5F!^>#z)|7K{bHGDkTfD)V2DHctmc%t}%z6^O8i6VAKYrf$bX1W8y53Rp zr%AgH_?8ZYt#FDin{ZKW85S_^QwVX!GRa{Qq>f^)n&f*<4{z^OATKa)w z)7|n;`hEH}?=|1`fv+D#^qpeJw(v#=Ix{Fk??z*~I8pO$aqyiL{b2SXdocScWvvyK zJollhCXrif8VLvu3-0{W`I$>pPY&zJ7~Ny|*T_&W1VMVS!#f1}9fiec;BsAw^ZoB% zYGZ!g?ia+vyulr4eu2~M7F`EFIj1nO=&R%tm!H5(YO#4j^biI0p|&zVjF$q=$-HPo zPaIgiQI-LGk2u+SQ5S3NOWKU8S^%ANae5gwVQ9fNNkfZ?qolf=^ zmZhlo1P7YuP#tfmZMfvX<)bJ4-H(&7SE4jVZ1b>A(!&L*u*+z{3qstj%D&MZcMov) zC0DI(p%!8ztDOZfyVI4pTp4o`=PTze(plWO>v`347Oa`l!u>`WU5Fl7U>#(>XNum3^;chH}&{0 zlV$$BY8FJf!4zoEJpf>&25Apm`N)e`Qu* zM_2Ny#V=8;V+3w8ZszDfJ8qJYZo7q#gf16(3lVw>IaU0~QbGM^jN%}xUi*j)SiNEo zt6rKX8YbX?LmDj{&!)3Q4_L>s(+aiKI4-Wr50oT0B4eh|WCCtJ4N$@A3Bmahpc}Mk z9-W7#e5QT-#GMB1Pw6q#ApQPFDiV6Ut5SG(gK?oq>dJvZK zqT$NFS%+fiyiVNk$PT-jGA60=53H7AYFN&aOAYfWp3*XE?>ibL`M4Aq(v`{GqLBe+ zpz&Ng-M0IGxd4Y&>8&0xLO2>=z+g~K+AABhqV?#g42P{QL7toYt`+k)oB0K-pO)}1 zPUhZF=au0+WQ&1@irgz=zA?ifHv|p=*yH7AMjyunDAzdG2i z5HDk@VDVhGOkeg)4RnL&TFm`oC9K9n>>AwqRp7zqF*`fg-=T2Be({=TK=Uc&*r^~R zEeH8Vo+Z}^m##zR0r5Fj=^bKu9G|x9PddNN7<;dRM0u@M?jF)G3 zUilqXIVkXVuIzpV#uAtX)WazYV9 zVI!s=aS=2qEq6~-Xj-MUD{>+Md==~gae?#^G&*b33-&yf8896`0%GFTvspAh454U^ z=!V|}5iLvBjGv zqx5zr>NjYGr&&+5{Cdb&IZ9sKRnK9O#87j75h$_z>V9zWdoDejR-Rxxi#*dp(mTD- zz7^t!>)sd{!pA%Lh8bcyG!e9i@jKotG|`1;DIEZV5up`}B*ySuxQ4haRM zMF9!vlI|`E=@6w$>F$sgkZuWS>E_(LU%lsB>zseO6rE>go_+6q)jr6XH;CGldilVC z#=j;$Ru-Y135c7<*U~CzHX*UBAh_7weQmMirS8ff|Lw3X`?2gN1)oF53*RUkNVt-A zJY@NQS$;d$6L?@9pOTIQM^aL}?bzV*C>j{~ZsBnJ7R!qjpGH7{px3Uvl8?8Of=nd2 zL`#tfM2OH*Eux|vJDmP@$!KyeAcP7fyb(?jUAuVU#7KpG5DrK%*Uo#6Dz|IWK?}5zV+H00EtqPK7Dibm*xhym5K3F1`_3S5~Q31L39|{823@R`nCSH=k zw9kUbub*eMo28k7t@}55)+Ob-YjyC`0tWJc3rEX9UwOZe>B8qSbdnL;I_J&x;kQjO z0Sq+UJY;fNK-GIe65D($YH4jbVT-7SXt`L;H`JR>#mM^PD~XDO=ad!jW1%lZ`mw+q z-OOWVb4zrL&0@9Uw{DL$!~H@i?HcYASgq+tWy;pv=AUr*3a)7@ukZLVA@jc(lR{X8 zVg!iQ=H+RqE?P~R0UAWxHlUy+W%QMO_}Ri1Jtlc5*e2vx|MyH!5I^OYL%Ap1&{@?$ zCtg}KUtGpHHqmUVrLKB(@`-43lJJ3$+C%z#A|Om1e1{1G4Y*Fv8o0N;o*rFsKBBX! zF$hfhz&|Euv#WTrUF+@Nhkd%O)&Q-?X!nZ09D)ed6OdNuoikSwC zpEwDZud;8<3OM!b97EoIriR8`DhLqBQRZdl)*LKRV+5~PcLGgH(PsjDU)UA&3=D3P zSzwx8Yj`iM3By5GS5`VP6H?F|KwBYU62cS=idPkzH2YW|hm@(fjl zfCt|Sl|>qp8_XCXd$M3#%~4x`fmw2kcnw#H*zI2)Q)$} z62YhSTnF1ope62@D%dDA>?64%Z4~<-4u=zw4Y!DpznKTTnReT%QrLD+si)04V{rA}G^zS`j+bez3Pe zkkPvaWHw%szfk+{-@N8;%|A&$X{h0F7W zpuHKL3&o=~-)I-s(sE@-Z$A~wsbs$Q~v}aKj z@lG53qN##RC_Ijb0FcAau{m#EX-3H zRfEZ9_n){3XBtqGXisM+IChl+tU&s=$?7H9)fVvxdg zAz*`?)|wkU#3rD(wzu=0bI~$FLipByY2Wj8{jmMQq9Z~12g9~zp5Nl_Xr8$6u`=X> zYyCotIN-s3*jgMV5Shg)tT-thaj!gsaUq~BYEpFDt6fxuX&-m}Y|7OE>+5_QXh-9+ zxw+B#E&(q@4>NE>R(ad?=a+fiCo9AaJ?0{&|IQO^*UIUIX$O!>>C$0j-}|ibXU)-( z{=2LsQQJac_qHvw2!{GK^7CKWOq-_42&*?;hA0RQ5yn%(^0PWQ+iznm{^@jqPgvB2 z1q+EIyW4L5jPt}%(n8f%_b8+b5fkMkOI=_67!u`t12DC@ZdZ$iXVE@owiwS?N{#>4_*4d%3 z=zs%JRA@}Q2^qVJ#bl^E+IMc5PYWg`uUMt4Hg7m@gLNhEj{}Tb1u9F=)`e)^9 zck?dn1wq%q9TO8xJpD8(tEfx>iL(O(RrnLg*a@flR;{ zz=|^+WAHiGUy=Hb)6OEwP~EM=rI_W}2zj_`O9SWgV2~t*8m&+CysooTwq0iWWBKIx z(XKXlr)>;q@#ms@SIaKi(#^WDUtgN5lFm62aQ`H2_PF0Mgb(x#YK0-Yr|CmU}2wfzcUqxr9lr;R)_So&qb47TsfKe$Uz3@i8S7?NA>6u1P#C#D3{!(zx{>s=< zj_G)9^vdCony5)MpPoKYD{g!+yV;^gDPzIoZSKD^#n4G<_Au0a<%&zKc) z@fn*I__9XlXA`xyl~b-nFYcX6nHvXRlC?dBz;<*|o0{rp>0g&aRkoB%-*ZnPLY%C( zcnV=FGZzbITZ-MU;E}E+^@{d(V=3?G-uQr*P;Q2WRM+>>n#`Nfm;UguGA=Q*)uaF$ zunojc-*o)=Lpeih(CG+Z^K^ z{r7yGNmln$DufI&KE%lKl{5IwU&c*eReB_IGKk!Zw~P_T6U8}OI3 zM|cPRhsDaMka0u7-cPL>0*-p{-iz^~5a;g!Z+D7$cO|3U9x+=b6FLFaO%1FxyV^8d?;d~gG7uzSfXMGT_!Vs&zdqXO>M%RBQColLP_32 zVru7r1mC9e;m2h9!DI>|wHICrUi-er(_x*f#~^!EosUJS*s&U0X*2)UK=)Pc(^N^r zx1i_Z2Wlu`aK%eu6J!q2CCjDacBF0oq0{8YP&?`ec1k`bybIXFj<*Hq=6He^d<<|5 zC~)V*vys9G>=aGS22fnk`0(&R5Bv#ob@k{3Gazox;>qR}8jzEPGYnBA+)g;ttU4s*1P!(qK*1ISGS{~%s2oy)m;x^f`_ zinYB%3sDBN?a_QB6Zd!_5WDJQxxvv-6-2>7Y8PYwpz*SZ{n^MWsk-;W`4`jSQWjpuvS@ukunh3^Mn6xsyY3?#+E<+GPSq_TfgBY*h3{2^l%VDpM`25MptN~R?TW-LMS(P)86sM7Ojv$NU{3Jk zj3Y{CI^rxk(+bq?T2uQ9`?441mp%eK41{@r-u(qNS9j|*B0x87&c#FOXJ}d7vZQVl z7hvSkzWJ4P@!mR}3jcclEvN0{$$($h$?BS2K$ds?kv17G!@61A%g5F}Fa_IrpGvfO z*s?+92G(JHr_0RL%|gv6yy{^$*3`IsYB|CPy-yTeob>DoVF#EXX0a1pparT?>l75|C**?>nF<B~BK2xX|qy02%xMu*?{%KUvk}^N$`pq5JFC~tClLjpW`Hd*H2QO9AaqSwacoFm#8di z=YxkN=_opMScLS{P2TciReUVHbQax6$^sFi`tqy8P5KixQvz{d@Y*%ghX`c=H)X(y{sM)=7LJ zQ+2EW$?a}@kDEwptHs9}ireOH+4B0-ke<_(vU@BzJTQa*Tj$_Ao5WK<3%@Dpeq>hh zM*xxLs02tpL1??O3+9JQ5|=O9#rRcS5zsboLG}AE`Q2@9(B$`@fhTAd#CDhSvZPQ7 zF`Hb4Ys)t1u8y5*{x`yx3Co885WY5e(R3j%oP%dUrsKUEmDz9o$6Ffg#m!eu#?};1 zOX6u_UggjhNcuATW3h)FMZgGCS8F#jW~kPh4jL@YJEDERo>%2OPb^6M5dHRyls<|4Re`k8`xwuu0W%@drH$Vw``H9kL4on zI^=B*F^%X4#s?tFHaRKbD^-jl_@Ygb*-JFigQ;@=A*;JVgleXFCM|?7we|;cWbYkj zV54)_mz!Msxs6!E+Vesdw&(_OyY0ntxXlN?7}vv<4ao%$VvN@<*t*^(m^KXR!Gl?u zoavm{nfltClK@2U&;p^}_E+Z{mM_|fK)`rWQd@^4+$HHnhv&pu+{a>re!Qftg*C6Y zQZStX5=VT&I>d@Hjb&H?60NL!zRR&Y{T^vB&xM{V@Wtijeo(~0c`E(vf{^yN?9Vn5 zr5b3e5m)+G-t#8=9WaiOkc^0Y%9tL%j(>;dTO=fR`q4Lq~y;D6=Q^&oZ z7$$_)f)(*poWZ1SI!1T_uhRnl9g8Fk{;9sC%xnTy^Y0C!hG$z_TkAjXl_^c5qRFEF z)Fc{c{oxaBgR)RX{}!xfi>5SLrU@=&{;;oFX%qd2yWHMu#tjmg5n^vLe@ zvz|OrF9~UA*x*E7?B%R~{aW4&E(+5<<>HTDaO>|s01 zcChw&n0>+-?Sim3D#3+OzKMmB7mCfZnX$??TKFUO0z+OePGsbav`-Ne^J-eU9|f^| zG9Q7a8-prWj7e?cF84e->d(d|sp(u^FET>{nz?HaT;zss1S-=G{e!x7bt^zzsGQN* z^|#Bd8gC)6^d6L6JyCbuAX7MW`;~gk62 zhIYKa0~D1rZbWlMbs>YL8_o2(w%_FYKWfaV8bx&o!__+s-9@?fwg4^xl`O5UYqf-7 zrP>g_l3R2aO3?*RjD@cIk@Ms2>&h4HKMdaUbK+nGmVu+5pyVZkjmju#9$vl_gAEK?5W z=W@K-b`Tf#bJ0jo|0JH0ZO>m)=u+GiaarkFJ4fCmu!0vS7KC-XsJr_lW6gp1<>dEB zO&PyBcQ@NKv^Sh@vF|aj7q~24bfw8%XmY-$mzAJg zVm_rp`tGeNB^^tkyi^_6m8N#ARYuc<_r|u4#AN5u@hl5cEVUdgcHNj=W08KPo$r-iNd(Vv+5JNos}D3-T|&!1$S`=YFTE=Buu<-;-W= z(j+xqrd}bent8{cy%2?(OR!?QiLR%px7S)pvd=g7x!{F*sl+$()%TsNuQvl@|4?wb z&g!aAh?8j{sx!Gt(_pQpHCS(p`^Y9}#7hSHqx*E1&w}yvzY$jx6z!77!&KZIJBH4u z-2FzJ*n8eBu>O-^W9?2m>KA2C<<4<&&$Fmbn_wm;zF=6d+|m2b7y*m(&eEFixJ5VT zPmbdn3+h<|+J^O=$8AP$yt@;M+>g)YxTsY7RQvKAcr;#ocqDt?q~Fg*?>~^|V|efL z>W>Foz2_PE8=C)f?rBN}v^&gVWt>*T7jmM);%qr0w2cL9Plv~M>6DQ}y)tK}+;iJa ziXgR7fjgtsCH|d!YyGs{FJhJ`g3oV0zpdU|pB^=-nMH{I%e6ft9VyJfuVdR?BCvI0 zF=S{Fm{Y02ILNZr?nuF_x!H*Uz#9G74&t&5V<@iqR@cb{6T_epVnRknITM>+n_WFfWH{gPQjQu!l<>nl!Ij=eGUNP}#O0O19YBvB*Xi{SQH( zlcf!)lGn-6QuAMJb16&wCOQgC#g|CW@6QFlU!zUpPF*H9O@(lkg5!S1 z@I0fBUJ8sQ)clbjwvEJ*5yOv|#Xz!wJ^C9>_&)%*jzg6xI-!z~H{+8<=^ZFGq^m%EE^0O{LdGRAA zWII^`BhIAgXUgYoQ+6y_OCBgzLRN(ATtOpPnT|5RsN0?Z0i^dP*K#ZmCSg6nbj?m0pf3E^BA4q+Js+Hx;U)J(z8<=1 z71P7CPga||+kETnIx;C_7U))ydF8y{K0J_-!D=(N5%emTfRc(TINW!*EADIZq6xg9fRBuG|XvFQg7W9M!&8i=t z`!myTozMK>a!5VDq%-ow#7 z+?6$vf<^zO6)%60r5_fn3~1R_Z!%P#k1p@*Pa1_jbxsRCE?Kw4Bo9h-FaV(7ELx7$l!2UA?q@?7HM zjx>f&Zb>@C3EWrv6y_fZyitfE-{OtQ&vu6=D5$wBIkkv&{L(nc7s{QH*t!**U*6_- zI0y0_JQEE{_OzZ%N|uSOH3V?7zgtE7uTkXg7W3jPm{JBdSf**Kx8p4?rrtNn*SR0& z%L%Jwvq7APaB56?pxpWU1_owI)xNFnPp#eEzdb+N8FQ%+uaY;CI zm+C)D2t|qmpAJ`LPgqHK5LD-D14;u#1JsS%-A@{h>fE|^c#v`nd$R-QV__jlVNW9v)=y+ z3Uc7!vEG9CJ191i$o2Ts-SI) zLgy2EMc7g=)bPP_q}3OfY`o7`vz$WC2!Vamadev%?*9_d7eCL@hiMNheEDhZU5H7x zZ}Hcfno|64pXgY-g-&clcQ9ekb%4|UO(W0p1co=rMYCElPAUqi_y)*D<*(V)Pun1q zvPUzuhfIew^w5mq$3fBBt=5;xo6o9TqwfD+21)_WC6mGh1tKx|)7w?DLPh9E-tF8C z7Aje5E$4^xn-WR^h+958AljY2Ry7W4`cXh*W~~h9vm&NHCF$R?Y2{EuCMmOb*p+5T z2rG|X@=M?)KNJqKaHB9;_3SXoE4FpGs+od^2=TCCrkQ$=kTICsq-rSpK$r5OW`PeK zIuN!eccT#Py4FE0X=L%5?yG=X*<=4+MQwtxV{K32h?)ZTrK!Oe>QKK!t=2ta+8|w#!<; z!m_&0P7k2$&J=4Zh7==ku^IW~U$Hl8oQr9C4hK7mWKqVi7hFjw2n;x7JzJ zFQ+L0sk1$j!iX5!JB&j5#TTX|)ykqj+-FSUd~+vMF7aLJiUl^uKr1AFYycx1t;_$uu$%D^B&K^Njb8vJ`5A z^_-wFUWEs&UO_hpI`r$P31(0=nQ+#c*0XLmTJ^!Eb0D3;!S!rbDf7F+>Zc$=G=0%W z+xd<=?tWTLiF0OvcDpo$nn(O^;JSS@=kX2ZnbuuGeriK z{PR-$^C4X_!gVum83ATMcz#4jA8U+(ASXmPO6*_66MCXWHmKEwl86KWZ!tpMU6$q7 zU%2NJrQ`BV^5}I(7c+)>P)?PrG0OEe+OM9WBv&jvf*U5;A~oQN7@B?3gdK#RDcos3 z{EkuJ-KCagCy3Dr1@Zin`$oNmG}=gkE-sahvns^B7xG^Ka;4={P`PX?F_~6mns&!) zc`;tD%>(=*2I2sroBkXez?;~qshshed2x!8nE}y(&LIcmVS@8Qi``Mv>FAFOv$$OH zo&R}VjZ(3abom$fH9PRt)gXC9_HKVbln?SRr+#}Kt-&PQ8ixo}y&o%9WT_u=&RzBO zO#*L+@f%t_iGP)w%NLJ{^{GQ(43XT|e$4w=2bX`p$@q`d>`Lp?1~71|z-&VOf6iD0 zp%*s42S-6n&_7J;?RwBgAvwY&GK_)PhqpB^f?;}DkD>k8D!#z4k zeYH87xybwI&%l;hDxL_Vbg3Qrk}>Mtfrie`0v;ol)Xt!G;kmZ-X~S;M@ozV)FcE6G zark^$+A%>Ll2N5F%PvS#O&Hi=dhk{+3W1fXtxxc~5%+(ITvps3KW*E3;dk+e+JCCw zHWAdnZ2C|3z_EaqZh4v}_zM|7fy>LM_n*lT9E~%+RT172G2ug540YSFDdCZUznfuw|q2usI zT+*lDj6~8#)z%tOmmpf-$R80aPWl*=*VCc8h7Pg?@-mvTI25tZ{_bEWVTYRaUH9V; zbTkY_HZOx0vt>HMxX2aoFG`;Wt~>vSpK70ebW5``GBTPt{`YP5`yZD!%)i`E|9a#8 zf7Wb`lgEEj^!p(lSa0t$G((;W3(G{w%#M~hqSUWCAob%T?qe0t55+n#U&1PEO=F~` zri~a;jE+0o8+L1YTJnnE%ELDPrl=oXQrCd+SpQg|`65@>$3f2F_gh&ij>wzhINA;J zvUu_<7@|WQ<$a}=C0lIfjL7<-Qk)Zo@Gwa|W}^2JJ&F3EF*e>$zKGx1HC0yP;wIg! zm@RlTswy|rX#XUp{Oj{)zX*A_Z7DRbmf8hTDORa(YxF7%gQ?uwYQLz23O(L6p&sYr zx{0-g=S>_5KKh28{v}od%0J+1VrL%%g_V)1qN(ieZ)$Fl;QK{&H8^fXX@%xjwe03m zNy1E28&)W?ym@hHM|lPfvORCGi!>)$m1cWYV9q5q5Sd=KwJecoZ zm46{k6lS3kp(CmCuPT(t5lSPv~^TBgOX*R@}d8+>8!7 z;@)}@p4J~ZAhVl=?>OP)F+VU}kAs4v;L)3jiSkiJi-_?;qjxuuZd!+EJgKQh_L+^i z*kSxG?d<3c%NLQscu^(8L@WwibvG7z4n(lZ*^@nQD{9kGAsM%KU49_R3g7sRh|6pE#IFGWomlTI1t!#*x3;V8g;yd|4ws6Niiz@3G6PxO!=1MRXVmA*4d@pFHbn)L4FO)wgnk&YT-I*f&?Nz$q zZ5_?un2{O3+hMi3g=eQ}{rn9h{MN+HB+nm2M73`C*Xg+jo+3Dp)m{-gR8%CUB-s(9yztX!F4`!^2Hg6)w%dQ!UkEW_y1g2=cAwL zQ&I`SATZ{fL<&~>WXk5tNoK8|d_Rdl{lkia60u1;z`#s}TJz%W@6KCd&1I)8JqM!a z)jJi44tjPza>kJ|`YQ20N@W}IckE=+ozfb+70;$MT9v;DLcewmFpDZQi`I=-)1)Vh z7_XaEcW^$kq^av6>z(TH9S?FT{z}-rs8FA#(Lf4oBT~Omt)@@=i0eU(W4@H&!ePo} z+RvLTwG*jbj^@xtP%Jb3K|ycsC7px*3t2Jb0pyGNW!fal^l7qzSj}xA4Nbjo-B-!v zA7kpR7hIApIi@v9ud=^WO=w5oCue3vfBL>4N;NufzJU6);V|<4g6&SLF&@o|@KAqn z`uDvY50fR6WNu3*$DGJ7w*Tu4nkoTDZplkyU=Gju^%yFYO#-2L;K0%l&^$p!Uavj0 z?N=IjakEgY22~o>)hawud<~h@#G0M$fs=OXv$f0A56~MW-H}0($KPl-7d|@50X-9oOy-p_({o6DUxwbo~cQL$p6^E!v|Q)2eUzPu+TYdI!`WIAOk&&Tvh9R<7lEMKbP zgKiJ0{pVX8aPu|^ON#Wt^|XP$i1jC6bu#cldyV0Dn=R$DoFRb>Dl;i&qW-~T57Rd6ZdzoIDrGw$#0Q^@k z>~;V!?0B8@5(M4$;9WP6Lc22h20^ZVLQt~kL@FF5@M!z`AhlEcwaaO?mUpqWfJ|JN z26x{4kLYg2O#?i!=H#wBa4icX67A-{*-!`$wv`p`{GsZ z^ESN=%qV>gI0?u^TNYPz>x0>TaE(N14{+x&h=FD|JK*1U2sGvvamxY>5uJ3U zLRC#AR0_e&?lm&pvpb`p;+$d30=2`XsF|#fllA`?=ncB>G8MBT-%Nc6`wE7DSw`A+|WAsasteb63f2# zK(1wjVCVD(^_T?KJ+WMfUt4pz7=nytWjnlD8Pr~^5<_6|^EvC9H28>>&96cD4R~Ym z{^NHvnYI@Hb6+33J4(+Ld20Plokr z|B8He1ixjWl+)e`GN3gbgCq2Xh)gai;cmyBWP^VsC8q)+G9@0&9K`owp8>g>89LWfH}5g3`Tq-AA4 z{pZbH2~CvE_fnD@d?s-~BMwOa4|Umj-gIYWC=e&Z77w|YSNModJtC+w-!MU@q|xeb z2AHh=xzGxkFTc#qz;^hzjJh)aYK z%xAN>8-W>qIG&e^&$?MJ^~Fd=-wwzTS-BSdR7J@C3JD-RTOa0J8y#?dwQhv z{!L2Gcr}}&lhat+1*6dulWa1KxTNyPnM;L*dT)Hg`HQaOo^-3(k6Ui9m&c>4)jb=p zKZy?zcXoCvj4Je8re?a^p1X=E?y{>Ku79rVwcuKMCZAI`f(5@QgyeQ3<0Ukj+2Rb{ z#NLc~SHGW!d;8shibab+OZ5hbx*42z^OfD6W*=ej47pS}t-CP(D4`TxV^=U&k zJ^h#GKFl2tvE+)J_wY}1wy#%+h8I@{z*F1 zmTWfZ14G&4KKRKbtso6Dswfiir|Zv#@^q5<$+w=w28)i~5lPDY{By*KBWgjMYC5wXVK85PzrGmlidgm&)!;@usF=VSwEvBq9hJ z86E61ij@(p7Cv%UtOg2-W+IGV z_!~72Fwro&XiEQ>zzwsMrQ%zaYnXVhZGaz-a)zCX-3M9%+9-b)=Qu~6@O%1lS{Mg8 zP!|NmVooLAUp?JDVW5y(X$GyoEOMh$iYS&*ETHnq?=4&_V4F2iL_F3XJ`&=?OejtW6S~MZNG-;Jnmtx&&ppbdUEk6- zo|3??_h4Y9l5Fe~Id|EXit^uGp-*o47-;3RVP{MD5?6Qnr^>Jp3}tZM0UsRjU% z{3l?8L>tucs<=MI>e19&C??#h?RS-*gvQ4wg-aP;cY1_uqUA@dI$J*S7Ecr%xi_rYW+6uVjmKr4R?te#gY-({H0t5g_1vjaJQweG zy6WhF9quajUb_4&gE_b9>4I0~Sx(ki`%e9W2=4(S(mUfw^coLw_g3nr=}i?0FNt;H zEd2!f;8=|gk_r)b)+WuyUTq_l{XPr|#C~KDL2fIuy4FI8lH)$~DyEki{qlgJjpzy) zW?>=6(tExB#K;o365d~C&IKE8UHB69-P>oGUc4c_J7TB8XaAn;%NG-9Kej|MOj1b0 z%}(q(pK-K3^|X+=*yIYA<@DWj*}~gVJ?j{l%o{0VH?(2v#&N_BcUE|i)To0MCx)xtfLP+<1leqg3h81(MC zRg5<(T2EpZ2ZnNxcUs32c)mN!w-y!e^RB$;H2S@{)#=qNgw|TRrQO)!rLB<(<>ZW# zvM^N0J|N$Xf8Zrhy0X1|Rhrd6Y$VWJ=rfh-BM)gbd5jA^v^!W>TBUd!u_`|R*UB`- zYzVr1?Zj8$*a$95wss#b-a9MEJ1G%`Dop)h3Mrm&QtFoW07!H$+M%x|n(6 zG8paZ(dA9hyUe2e^Ns}b>jd7ndcY#+BLcMsGfCsq_<();mKjb7!4jpQc;h zfAG23ZmKQ3WMn5}$e_j< zvwT4`d^%p;w59zi&k$03-#+hTB1y_z_-Pcc3LjWiGrws0s~VmEa+aD$1kN?G*^&qr z+WB*j$%CGv!DxH6Lo~DN{gnHsOBFEvkc+k=&&wI%Lf>8agH{D6fZlGUh6dNnrev0= zvy<9#AxG#yv>uki7YQXmQF*Y#V^I`^V<(nB_lA~(Nr78`ED|Xa&$a!h12u12bw}cZ z9_8t(PQ6EJhs9JIP=r=F#B&;66YvXCgyuDKBM^L{{lau9PkOubOs;)oDE)wb<@ehl z`Gvqc0^Rs+o_R|qPiOL=34)8Y$t$s$HJ_{kD=V<13+`fVAqgtIoW~?e%`OxlWc3;^ z_6X(fV-e)7)P^d-p(AXBT}cp!|>XzTkzw!&Z0T!_Z2h&oRyeq>TuPxjw2@!L1i+1brUS^5=AI0F}3&9-j=Ca4ln@r6mAGBTE!t%BP4mGlz-VTl}2Ikt?|MfRPZLT>kO?^*%Yj+8S&bvvr5J>EJ3T2E1SvgR!G#IiaVR* z*T>_-OK`Nhx6sJf>?So32%w{<$zg?R?dD+Lu3Jzu?qR41u=f4Y5g9~)LUoDFrbt1$ zPOe;fW_%`s?Yh5v?G1CMkwjo?m%*ieX@J@;hVBP@9;3czrYU6%5{9K3TkI~EdBrkBoe^3Okp3RpE<4T}F6BHy zfkMPlgS&W<&?*W2_Fv39?EYnIR`x=2d+N6L|FEyhcnlXM?1Ll+l$3arjjg}G$_M%w%F=5?X5D$+W!DSqCUTdjl)&{AJDElL$OWWN<+ zT~F?asBXN_x7J|IxoAKz$i|<7+=q=M4%^3P+bzTZb zxjWM)OB$DUo+@(0_3&iTt!6;!Kb>yAy%n8nt$6Bx&dWFVdZ_Zxf;h$U+W8fpw!F{7 z99zt-Dy7Vwkg#C$nRtF{{r+DP*iZG>!^YgO%ER6{i~2|n4cSFaq%bT`6P8|w^hoZV zqXc9`iEhrK^-VtL<$T!kulD94z|7NFkGp?LNlzb>{~J?7Q+vY&DB8y+?RdYcR7y_A z_>MJ;V0wUXFoFpNy(A$cOmc)IWC;J@@|E|G^kZ{&gp38)bcEfpdQ;RLdPZ7WG(}`9 zqj8{+I^KN3#QFr%eet0c9Y!UCJT~JvurH!(lJ8ro(5ozr0GkT%eE3|6=!YoaGW|3E zlxGEr2gX%m>ASo`#^m9swyf}fJ`p-@Yg$MC`g@B+^GFlw&rL{<{r4Hs{q+jf zw~j5S7?>d~ZEz!HLa#RZjP>x2pTb_soFeI>*D!

    }A?Fv{W>s4a34}j@>H*f#a{; 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

    _IIcoPvUG7neY;gTD<2 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 diff --git a/docs/instrumentation/tracing-sdk.md b/docs/instrumentation/tracing-sdk.md index 7978cc69af..40d398489a 100644 --- a/docs/instrumentation/tracing-sdk.md +++ b/docs/instrumentation/tracing-sdk.md @@ -30,7 +30,7 @@ repository](/examples/sdk/README.md). To start using the Client API, first check out the latest SDK release: ```bash -git clone https://android.googlesource.com/platform/external/perfetto -b v45.0 +git clone https://android.googlesource.com/platform/external/perfetto -b v46.0 ``` The SDK consists of two files, `sdk/perfetto.h` and `sdk/perfetto.cc`. These are diff --git a/docs/reference/perfetto-cli.md b/docs/reference/perfetto-cli.md index 195ceb1938..377502e2d5 100644 --- a/docs/reference/perfetto-cli.md +++ b/docs/reference/perfetto-cli.md @@ -124,7 +124,7 @@ The following table lists the available options when using `perfetto` in : Specifies the path to a configuration file. In normal mode, some configurations may be encoded in a configuration protocol buffer. This file must comply with the protocol buffer schema defined in AOSP - [`trace_config.proto`](/protos/perfetto/config/data_source_config.proto). + [`trace_config.proto`](/protos/perfetto/config/trace_config.proto). You select and configure the data sources using the DataSourceConfig member of the TraceConfig, as defined in AOSP [`data_source_config.proto`](/protos/perfetto/config/data_source_config.proto). diff --git a/docs/reference/synthetic-track-event.md b/docs/reference/synthetic-track-event.md index e382f8789b..ff894e9a94 100644 --- a/docs/reference/synthetic-track-event.md +++ b/docs/reference/synthetic-track-event.md @@ -422,6 +422,7 @@ packet { # Creates a mapping from the iid "1" to the string name: any |name_iid| field # in this packet onwards will transparently be remapped to this string by trace # processor. + # Note: iid 0 is *not* a valid IID and should not be used. event_names { iid: 1 name: "A very very very long slice name which we don't want to repeat" diff --git a/docs/visualization/deep-linking-to-perfetto-ui.md b/docs/visualization/deep-linking-to-perfetto-ui.md index 11324d9ab0..ce08dfd7de 100644 --- a/docs/visualization/deep-linking-to-perfetto-ui.md +++ b/docs/visualization/deep-linking-to-perfetto-ui.md @@ -163,7 +163,7 @@ Supported parameters: Try the following examples: - [visStart & visEnd](https://ui.perfetto.dev/?url=https%3A%2F%2Fstorage.googleapis.com%2Fperfetto-misc%2Fexample_android_trace_15s&visStart=261191575272856&visEnd=261191675272856) -- [ts & dur](https://ui.perfetto.dev/?url=https%3A%2F%2Fstorage.googleapis.com%2Fperfetto-misc%2Fexample_android_trace_15s&ts=261192091615668&dur=16229012) +- [ts & dur](https://ui.perfetto.dev/?url=https%3A%2F%2Fstorage.googleapis.com%2Fperfetto-misc%2Fexample_android_trace_15s&ts=261192482777530&dur=1667500) - [query](https://ui.perfetto.dev/?url=https%3A%2F%2Fstorage.googleapis.com%2Fperfetto-misc%2Fexample_android_trace_15s&query=select%20'Hello%2C%20world!'%20as%20msg) You must take care to correctly escape strings where needed. diff --git a/examples/sdk/README.md b/examples/sdk/README.md index bd0707e721..c8fa4cb51c 100644 --- a/examples/sdk/README.md +++ b/examples/sdk/README.md @@ -15,7 +15,7 @@ Dependencies: First, check out the latest Perfetto release: ```bash -git clone https://android.googlesource.com/platform/external/perfetto -b v45.0 +git clone https://android.googlesource.com/platform/external/perfetto -b v46.0 ``` Then, build using CMake: diff --git a/examples/shared_lib/example_shlib_track_event.c b/examples/shared_lib/example_shlib_track_event.c index 21a69419d3..143180c0df 100644 --- a/examples/shared_lib/example_shlib_track_event.c +++ b/examples/shared_lib/example_shlib_track_event.c @@ -16,6 +16,7 @@ #include "perfetto/public/abi/track_event_abi.h" #include "perfetto/public/producer.h" +#include "perfetto/public/protos/trace/track_event/track_event.pzc.h" #include "perfetto/public/te_category_macros.h" #include "perfetto/public/te_macros.h" #include "perfetto/public/track_event.h" @@ -51,7 +52,7 @@ static void EnabledCb(struct PerfettoTeCategoryImpl* c, int main(void) { uint64_t flow_counter = 0; - struct PerfettoProducerInitArgs args = {0}; + struct PerfettoProducerInitArgs args = PERFETTO_PRODUCER_INIT_ARGS_INIT(); args.backends = PERFETTO_BACKEND_SYSTEM; PerfettoProducerInit(args); PerfettoTeInit(); @@ -87,6 +88,11 @@ int main(void) { PERFETTO_TE_NAMED_TRACK("dynamictrack", 2, PerfettoTeProcessTrackUuid()), PERFETTO_TE_TIMESTAMP(PerfettoTeGetTimestamp())); + PERFETTO_TE(physics, PERFETTO_TE_INSTANT("name9"), + PERFETTO_TE_PROTO_FIELDS(PERFETTO_TE_PROTO_FIELD_NESTED( + perfetto_protos_TrackEvent_source_location_field_number, + PERFETTO_TE_PROTO_FIELD_CSTR(2, __FILE__), + PERFETTO_TE_PROTO_FIELD_VARINT(4, __LINE__)))); PERFETTO_TE(PERFETTO_TE_DYNAMIC_CATEGORY, PERFETTO_TE_COUNTER(), PERFETTO_TE_DOUBLE_COUNTER(3.14), PERFETTO_TE_REGISTERED_TRACK(&mycounter), diff --git a/gn/BUILD.gn b/gn/BUILD.gn index f59c1821b9..ed055ee700 100644 --- a/gn/BUILD.gn +++ b/gn/BUILD.gn @@ -211,6 +211,7 @@ group("gtest_main") { # Full protobuf is just for host tools .No binary shipped on device should # depend on this. protobuf_full_deps_allowlist = [ + "../buildtools/grpc:*", "../src/ipc/protoc_plugin:*", "../src/protozero/protoc_plugin:*", "../src/protozero/filtering:filter_util", @@ -290,7 +291,7 @@ config("protobuf_gen_config") { "GOOGLE_PROTOBUF_NO_STATIC_INITIALIZER", ] cflags = [] - if (!is_clang && !is_win) { + if (is_gcc) { cflags += [ "-Wno-deprecated-declarations" ] } if (is_clang && is_win) { @@ -373,6 +374,13 @@ if (enable_perfetto_trace_processor_json) { } } +if (enable_perfetto_grpc) { + group("grpc") { + public_configs = [ "//buildtools:grpc_public_config" ] + public_deps = [ "//buildtools/grpc:grpc++" ] + } +} + if (enable_perfetto_trace_processor_linenoise) { # Used by the trace_processor_shell for REPL history. # Only available in standalone builds. diff --git a/gn/perfetto.gni b/gn/perfetto.gni index 800232c45a..ce106c5d00 100644 --- a/gn/perfetto.gni +++ b/gn/perfetto.gni @@ -70,6 +70,10 @@ if (!defined(is_nacl)) { is_nacl = false } +if (!defined(is_gcc)) { + is_gcc = !is_clang && !is_win +} + declare_args() { # The Android blueprint file generator set this to true (as well as # is_perfetto_build_generator). This is just about being built in the diff --git a/gn/perfetto_integrationtests.gni b/gn/perfetto_integrationtests.gni index 78469f552b..f9237faa83 100644 --- a/gn/perfetto_integrationtests.gni +++ b/gn/perfetto_integrationtests.gni @@ -48,6 +48,7 @@ if (enable_perfetto_trace_processor && perfetto_build_standalone && !is_android) { perfetto_integrationtests_targets += [ "src/trace_processor:integrationtests" ] + perfetto_integrationtests_targets += [ "src/traceconv:integrationtests" ] } # This test requires traces that are not available on Android builds. diff --git a/gn/perfetto_unittests.gni b/gn/perfetto_unittests.gni index f1d46ba04b..f54ef7a439 100644 --- a/gn/perfetto_unittests.gni +++ b/gn/perfetto_unittests.gni @@ -75,7 +75,6 @@ if (enable_perfetto_traced_perf) { if (enable_perfetto_trace_processor) { perfetto_unittests_targets += [ "src/trace_processor:unittests" ] - perfetto_unittests_targets += [ "src/traceconv:unittests" ] if (enable_perfetto_trace_processor_sqlite) { perfetto_unittests_targets += [ "src/trace_processor/metrics:unittests" ] diff --git a/gn/proto_library.gni b/gn/proto_library.gni index 8bf8d78850..0c11748aed 100644 --- a/gn/proto_library.gni +++ b/gn/proto_library.gni @@ -373,9 +373,10 @@ template("perfetto_proto_library") { proto_library_sources = invoker.sources proto_import_dirs = import_dirs_ exports = [] - foreach (i, public_deps_) { + foreach(i, public_deps_) { # Get the absolute target path - exports += [get_label_info(i, "dir") + ":" + get_label_info(i, "name")] + exports += + [ get_label_info(i, "dir") + ":" + get_label_info(i, "name") ] } } forward_variables_from(invoker, vars_to_forward) diff --git a/gn/standalone/BUILD.gn b/gn/standalone/BUILD.gn index ecd5c951f1..36fb6371a5 100644 --- a/gn/standalone/BUILD.gn +++ b/gn/standalone/BUILD.gn @@ -95,7 +95,7 @@ config("extra_warnings") { # codebase cleanup. "-Wno-switch-default", ] - } else if (!is_clang && !is_win) { + } else if (is_gcc) { # Use return std::move(...) for compatibility with old GCC compilers. cflags_cc = [ "-Wno-redundant-move" ] diff --git a/gn/standalone/protoc.py b/gn/standalone/protoc.py index 52e156e9cd..cbcbeec89d 100644 --- a/gn/standalone/protoc.py +++ b/gn/standalone/protoc.py @@ -16,45 +16,8 @@ This script exists to work-around the bad depfile generation by protoc when generating descriptors.""" -from __future__ import print_function -import argparse -import os import sys import subprocess -import tempfile -import uuid - -from codecs import open - - -def main(): - parser = argparse.ArgumentParser() - parser.add_argument('--descriptor_set_out', default=None) - parser.add_argument('--dependency_out', default=None) - parser.add_argument('protoc') - args, remaining = parser.parse_known_args() - - if args.dependency_out and args.descriptor_set_out: - tmp_path = os.path.join(tempfile.gettempdir(), str(uuid.uuid4())) - custom = [ - '--descriptor_set_out', args.descriptor_set_out, '--dependency_out', - tmp_path - ] - try: - cmd = [args.protoc] + custom + remaining - subprocess.check_call(cmd) - with open(tmp_path, 'rb') as tmp_rd: - dependency_data = tmp_rd.read().decode('utf-8') - finally: - if os.path.exists(tmp_path): - os.unlink(tmp_path) - - with open(args.dependency_out, 'w', encoding='utf-8') as f: - f.write(args.descriptor_set_out + ":") - f.write(dependency_data) - else: - subprocess.check_call(sys.argv[1:]) - if __name__ == '__main__': - sys.exit(main()) + sys.exit(subprocess.call(sys.argv[1:])) diff --git a/include/perfetto/base/build_config.h b/include/perfetto/base/build_config.h index 5f7a7bd96d..c416adcf27 100644 --- a/include/perfetto/base/build_config.h +++ b/include/perfetto/base/build_config.h @@ -109,7 +109,7 @@ #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_COMPILER_CLANG() 1 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_COMPILER_GCC() 0 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_COMPILER_MSVC() 0 -#elif defined(__GNUC__) // Careful: Clang also defines this! +#elif defined(__GNUC__) // Careful: Clang also defines this! #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_COMPILER_CLANG() 0 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_COMPILER_GCC() 1 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_COMPILER_MSVC() 0 diff --git a/include/perfetto/base/compiler.h b/include/perfetto/base/compiler.h index e4b6b0cae8..22ebb8c5ce 100644 --- a/include/perfetto/base/compiler.h +++ b/include/perfetto/base/compiler.h @@ -42,16 +42,6 @@ #define PERFETTO_UNUSED #endif -#if defined(__clang__) -#define PERFETTO_ALWAYS_INLINE __attribute__((__always_inline__)) -#define PERFETTO_NO_INLINE __attribute__((__noinline__)) -#else -// GCC is too pedantic and often fails with the error: -// "always_inline function might not be inlinable" -#define PERFETTO_ALWAYS_INLINE -#define PERFETTO_NO_INLINE -#endif - #if defined(__GNUC__) || defined(__clang__) #define PERFETTO_NORETURN __attribute__((__noreturn__)) #else diff --git a/include/perfetto/base/status.h b/include/perfetto/base/status.h index 506bb6e615..ee39593b67 100644 --- a/include/perfetto/base/status.h +++ b/include/perfetto/base/status.h @@ -109,7 +109,7 @@ inline Status OkStatus() { return Status(); } -PERFETTO_PRINTF_FORMAT(1, 2) Status ErrStatus(const char* format, ...); +Status ErrStatus(const char* format, ...) PERFETTO_PRINTF_FORMAT(1, 2); } // namespace base } // namespace perfetto diff --git a/include/perfetto/ext/base/getopt.h b/include/perfetto/ext/base/getopt.h index bf993fc1f6..abf8cca467 100644 --- a/include/perfetto/ext/base/getopt.h +++ b/include/perfetto/ext/base/getopt.h @@ -45,7 +45,7 @@ constexpr auto required_argument = ::perfetto::base::getopt_compat::required_argument; #else -#include +#include // IWYU pragma: export #endif #endif // INCLUDE_PERFETTO_EXT_BASE_GETOPT_H_ diff --git a/include/perfetto/ext/base/sys_types.h b/include/perfetto/ext/base/sys_types.h index bcb6433a23..33ef5e7d65 100644 --- a/include/perfetto/ext/base/sys_types.h +++ b/include/perfetto/ext/base/sys_types.h @@ -20,7 +20,7 @@ // This headers deals with sys types commonly used in the codebase that are // missing on Windows. -#include +#include // IWYU pragma: export #include #include "perfetto/base/build_config.h" diff --git a/include/perfetto/ext/base/unix_socket.h b/include/perfetto/ext/base/unix_socket.h index f0e796130a..75e90cd056 100644 --- a/include/perfetto/ext/base/unix_socket.h +++ b/include/perfetto/ext/base/unix_socket.h @@ -41,7 +41,7 @@ namespace base { // Define the ScopedSocketHandle type. #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) -int CloseSocket(SocketHandle); // A wrapper around ::closesocket(). +int CloseSocket(SocketHandle); // A wrapper around ::closesocket(). using ScopedSocketHandle = ScopedResource(-1)>; #else diff --git a/include/perfetto/ext/base/unix_task_runner.h b/include/perfetto/ext/base/unix_task_runner.h index 8d1ab6d893..ecde733798 100644 --- a/include/perfetto/ext/base/unix_task_runner.h +++ b/include/perfetto/ext/base/unix_task_runner.h @@ -90,7 +90,7 @@ class UnixTaskRunner : public TaskRunner { void RunFileDescriptorWatch(PlatformHandle); ThreadChecker thread_checker_; - PlatformThreadId created_thread_id_ = GetThreadId(); + std::atomic created_thread_id_ = GetThreadId(); EventFd event_; diff --git a/include/perfetto/ext/base/utils.h b/include/perfetto/ext/base/utils.h index f3bfa1ce87..58bbb4fa6d 100644 --- a/include/perfetto/ext/base/utils.h +++ b/include/perfetto/ext/base/utils.h @@ -139,6 +139,10 @@ std::string GetCurExecutableDir(); void* AlignedAlloc(size_t alignment, size_t size); void AlignedFree(void*); +// Detects Sync-mode MTE (currently being tested in some Android builds). +// This is known to use extra memory for the stack history buffer. +bool IsSyncMemoryTaggingEnabled(); + // A RAII version of the above, which takes care of pairing Aligned{Alloc,Free}. template struct AlignedDeleter { diff --git a/include/perfetto/ext/traced/sys_stats_counters.h b/include/perfetto/ext/traced/sys_stats_counters.h index 94117f00b3..8ab43f332e 100644 --- a/include/perfetto/ext/traced/sys_stats_counters.h +++ b/include/perfetto/ext/traced/sys_stats_counters.h @@ -64,6 +64,11 @@ constexpr KeyAndId kMeminfoKeys[] = { {"VmallocChunk", protos::pbzero::MeminfoCounters::MEMINFO_VMALLOC_CHUNK}, {"CmaTotal", protos::pbzero::MeminfoCounters::MEMINFO_CMA_TOTAL}, {"CmaFree", protos::pbzero::MeminfoCounters::MEMINFO_CMA_FREE}, + {"Gpu", protos::pbzero::MeminfoCounters::MEMINFO_GPU}, + {"Zram", protos::pbzero::MeminfoCounters::MEMINFO_ZRAM}, + {"Misc", protos::pbzero::MeminfoCounters::MEMINFO_MISC}, + {"ION_heap", protos::pbzero::MeminfoCounters::MEMINFO_ION_HEAP}, + {"ION_heap_pool", protos::pbzero::MeminfoCounters::MEMINFO_ION_HEAP_POOL}, }; const KeyAndId kVmstatKeys[] = { diff --git a/include/perfetto/profiling/pprof_builder.h b/include/perfetto/profiling/pprof_builder.h index 9ded53e2de..4a932bc7cd 100644 --- a/include/perfetto/profiling/pprof_builder.h +++ b/include/perfetto/profiling/pprof_builder.h @@ -35,6 +35,7 @@ namespace trace_to_text { enum class ProfileType { kHeapProfile, + kJavaHeapProfile, kPerfProfile, }; @@ -46,7 +47,7 @@ struct SerializedProfile { std::string heap_name; }; -enum class ConversionMode { kHeapProfile, kPerfProfile }; +enum class ConversionMode { kHeapProfile, kPerfProfile, kJavaHeapProfile }; enum class ConversionFlags : uint64_t { kNone = 0, diff --git a/include/perfetto/public/abi/producer_abi.h b/include/perfetto/public/abi/producer_abi.h index a939b7370c..8899d0098e 100644 --- a/include/perfetto/public/abi/producer_abi.h +++ b/include/perfetto/public/abi/producer_abi.h @@ -25,11 +25,46 @@ extern "C" { #endif +// Opaque pointer to an object that stores the initialization params. +struct PerfettoProducerBackendInitArgs; + +// Creates an object to store the configuration params for initializing a +// backend. +PERFETTO_SDK_EXPORT struct PerfettoProducerBackendInitArgs* +PerfettoProducerBackendInitArgsCreate(void); + +// Tunes the size of the shared memory buffer between the current +// process and the service backend(s). This is a trade-off between memory +// footprint and the ability to sustain bursts of trace writes (see comments +// in shared_memory_abi.h). +// If set, the value must be a multiple of 4KB. The value can be ignored if +// larger than kMaxShmSize (32MB) or not a multiple of 4KB +PERFETTO_SDK_EXPORT void PerfettoProducerBackendInitArgsSetShmemSizeHintKb( + struct PerfettoProducerBackendInitArgs*, + uint32_t size); + +PERFETTO_SDK_EXPORT void PerfettoProducerBackendInitArgsDestroy( + struct PerfettoProducerBackendInitArgs*); + // Initializes the global system perfetto producer. -PERFETTO_SDK_EXPORT void PerfettoProducerSystemInit(void); +// +// It's ok to call this function multiple times, but if the producer was +// already initialized, most of `args` would be ignored. +// +// Does not take ownership of `args`. `args` can be destroyed immediately +// after this call returns. +PERFETTO_SDK_EXPORT void PerfettoProducerSystemInit( + const struct PerfettoProducerBackendInitArgs* args); // Initializes the global in-process perfetto producer. -PERFETTO_SDK_EXPORT void PerfettoProducerInProcessInit(void); +// +// It's ok to call this function multiple times, but if the producer was +// already initialized, most of `args` would be ignored. +// +// Does not take ownership of `args`. `args` can be destroyed immediately +// after this call returns. +PERFETTO_SDK_EXPORT void PerfettoProducerInProcessInit( + const struct PerfettoProducerBackendInitArgs* args); // Informs the tracing services to activate any of these triggers if any tracing // session was waiting for them. diff --git a/include/perfetto/public/abi/track_event_hl_abi.h b/include/perfetto/public/abi/track_event_hl_abi.h index d176d5b265..3d8720cfdd 100644 --- a/include/perfetto/public/abi/track_event_hl_abi.h +++ b/include/perfetto/public/abi/track_event_hl_abi.h @@ -36,6 +36,76 @@ extern "C" { #endif +// The type of the proto field. +enum PerfettoTeHlProtoFieldType { + PERFETTO_TE_HL_PROTO_TYPE_CSTR = 0, + PERFETTO_TE_HL_PROTO_TYPE_BYTES = 1, + PERFETTO_TE_HL_PROTO_TYPE_NESTED = 2, + PERFETTO_TE_HL_PROTO_TYPE_VARINT = 3, + PERFETTO_TE_HL_PROTO_TYPE_FIXED64 = 4, + PERFETTO_TE_HL_PROTO_TYPE_FIXED32 = 5, + PERFETTO_TE_HL_PROTO_TYPE_DOUBLE = 6, + PERFETTO_TE_HL_PROTO_TYPE_FLOAT = 7, +}; + +// Common header for all the proto fields. +struct PerfettoTeHlProtoField { + enum PerfettoTeHlProtoFieldType type; + // Proto field id. + uint32_t id; +}; + +// PERFETTO_TE_HL_PROTO_TYPE_CSTR +struct PerfettoTeHlProtoFieldCstr { + struct PerfettoTeHlProtoField header; + // Null terminated string. + const char* str; +}; + +// PERFETTO_TE_HL_PROTO_TYPE_BYTES +struct PerfettoTeHlProtoFieldBytes { + struct PerfettoTeHlProtoField header; + const void* buf; + size_t len; +}; + +// PERFETTO_TE_HL_PROTO_TYPE_NESTED +struct PerfettoTeHlProtoFieldNested { + struct PerfettoTeHlProtoField header; + // Array of pointers to the fields. The last pointer should be NULL. + struct PerfettoTeHlProtoField* const* fields; +}; + +// PERFETTO_TE_HL_PROTO_TYPE_VARINT +struct PerfettoTeHlProtoFieldVarInt { + struct PerfettoTeHlProtoField header; + uint64_t value; +}; + +// PERFETTO_TE_HL_PROTO_TYPE_FIXED64 +struct PerfettoTeHlProtoFieldFixed64 { + struct PerfettoTeHlProtoField header; + uint64_t value; +}; + +// PERFETTO_TE_HL_PROTO_TYPE_FIXED32 +struct PerfettoTeHlProtoFieldFixed32 { + struct PerfettoTeHlProtoField header; + uint32_t value; +}; + +// PERFETTO_TE_HL_PROTO_TYPE_DOUBLE +struct PerfettoTeHlProtoFieldDouble { + struct PerfettoTeHlProtoField header; + double value; +}; + +// PERFETTO_TE_HL_PROTO_TYPE_FLOAT +struct PerfettoTeHlProtoFieldFloat { + struct PerfettoTeHlProtoField header; + float value; +}; + // The type of an event extra parameter. enum PerfettoTeHlExtraType { PERFETTO_TE_HL_EXTRA_TYPE_REGISTERED_TRACK = 1, @@ -54,15 +124,14 @@ enum PerfettoTeHlExtraType { PERFETTO_TE_HL_EXTRA_TYPE_TERMINATING_FLOW = 14, PERFETTO_TE_HL_EXTRA_TYPE_FLUSH = 15, PERFETTO_TE_HL_EXTRA_TYPE_NO_INTERN = 16, + PERFETTO_TE_HL_EXTRA_TYPE_PROTO_FIELDS = 17, }; -// List of extra event parameters. Each type of parameter should embed this as -// its first member. +// An extra event parameter. Each type of parameter should embed this as its +// first member. struct PerfettoTeHlExtra { // enum PerfettoTeHlExtraType. Identifies the exact type of this. uint32_t type; - // Pointer to the next PerfettoTeHlExtra in the list or NULL. - const struct PerfettoTeHlExtra* next; }; // PERFETTO_TE_HL_EXTRA_TYPE_REGISTERED_TRACK @@ -174,6 +243,13 @@ struct PerfettoTeHlExtraFlow { uint64_t id; }; +// PERFETTO_TE_HL_EXTRA_TYPE_PROTO_FIELDS +struct PerfettoTeHlExtraProtoFields { + struct PerfettoTeHlExtra header; + // Array of pointers to the fields. The last pointer should be NULL. + struct PerfettoTeHlProtoField* const* fields; +}; + // Emits an event on all active instances of the track event data source. // * `cat`: The registered category of the event, it knows on which data source // instances the event should be emitted. Use @@ -182,12 +258,13 @@ struct PerfettoTeHlExtraFlow { // PerfettoTeType`. // * `name`: All events (except when PERFETTO_TE_TYPE_SLICE_END) can have an // associated name. It can be nullptr. -// * `extra_data`: Optional parameters associated with the events. +// * `extra_data`: Optional parameters associated with the events. Array of +// pointers to each event. The last pointer should be NULL. PERFETTO_SDK_EXPORT void PerfettoTeHlEmitImpl( struct PerfettoTeCategoryImpl* cat, int32_t type, const char* name, - const struct PerfettoTeHlExtra* extra_data); + struct PerfettoTeHlExtra* const* extra_data); #ifdef __cplusplus } diff --git a/include/perfetto/public/compiler.h b/include/perfetto/public/compiler.h index 3e38853a77..fba72045c6 100644 --- a/include/perfetto/public/compiler.h +++ b/include/perfetto/public/compiler.h @@ -51,4 +51,14 @@ #define PERFETTO_NULL NULL #endif +#if defined(__clang__) +#define PERFETTO_ALWAYS_INLINE __attribute__((__always_inline__)) +#define PERFETTO_NO_INLINE __attribute__((__noinline__)) +#else +// GCC is too pedantic and often fails with the error: +// "always_inline function might not be inlinable" +#define PERFETTO_ALWAYS_INLINE +#define PERFETTO_NO_INLINE +#endif + #endif // INCLUDE_PERFETTO_PUBLIC_COMPILER_H_ diff --git a/include/perfetto/public/data_source.h b/include/perfetto/public/data_source.h index 83fb527923..5902624577 100644 --- a/include/perfetto/public/data_source.h +++ b/include/perfetto/public/data_source.h @@ -117,7 +117,8 @@ static inline bool PerfettoDsRegister(struct PerfettoDs* ds, PerfettoPbMsgInit(&desc.msg, &writer); perfetto_protos_DataSourceDescriptor_set_cstr_name(&desc, data_source_name); - perfetto_protos_DataSourceDescriptor_set_will_notify_on_stop(&desc, params.will_notify_on_stop); + perfetto_protos_DataSourceDescriptor_set_will_notify_on_stop( + &desc, params.will_notify_on_stop); desc_size = PerfettoStreamWriterGetWrittenSize(&writer.writer); desc_buf = malloc(desc_size); diff --git a/include/perfetto/public/pb_macros.h b/include/perfetto/public/pb_macros.h index 03724a5fdb..ba12ed2cc2 100644 --- a/include/perfetto/public/pb_macros.h +++ b/include/perfetto/public/pb_macros.h @@ -110,87 +110,87 @@ #define PERFETTO_I_PB_GET_MSG(C_TYPE) PERFETTO_I_PB_CONCAT_3(C_TYPE, _, get_msg) -#define PERFETTO_I_PB_FIELD_STRING(PROTO, C_TYPE, NAME, NUM) \ - static inline void PERFETTO_I_PB_SETTER_CSTR_NAME(PROTO, NAME)( \ +#define PERFETTO_I_PB_FIELD_STRING(PREFIX, PROTO, C_TYPE, NAME, NUM) \ + static inline void PERFETTO_I_PB_SETTER_CSTR_NAME(PREFIX, NAME)( \ struct PROTO * msg, const char* value) { \ PerfettoPbMsgAppendCStrField(&msg->msg, NUM, value); \ } \ - static inline void PERFETTO_I_PB_SETTER_NAME(PROTO, NAME)( \ + static inline void PERFETTO_I_PB_SETTER_NAME(PREFIX, NAME)( \ struct PROTO * msg, const void* data, size_t len) { \ PerfettoPbMsgAppendType2Field( \ &msg->msg, NUM, PERFETTO_STATIC_CAST(const uint8_t*, data), len); \ } \ - static inline void PERFETTO_I_PB_SETTER_BEGIN_NAME(PROTO, NAME)( \ + static inline void PERFETTO_I_PB_SETTER_BEGIN_NAME(PREFIX, NAME)( \ struct PROTO * msg, struct PerfettoPbMsg * nested) { \ PerfettoPbMsgBeginNested(&msg->msg, nested, NUM); \ } \ - static inline void PERFETTO_I_PB_SETTER_END_NAME(PROTO, NAME)( \ + static inline void PERFETTO_I_PB_SETTER_END_NAME(PREFIX, NAME)( \ struct PROTO * msg, struct PerfettoPbMsg * nested) { \ (void)nested; \ PerfettoPbMsgEndNested(&msg->msg); \ } -#define PERFETTO_I_PB_FIELD_VARINT(PROTO, C_TYPE, NAME, NUM) \ - static inline void PERFETTO_I_PB_SETTER_NAME(PROTO, NAME)( \ +#define PERFETTO_I_PB_FIELD_VARINT(PREFIX, PROTO, C_TYPE, NAME, NUM) \ + static inline void PERFETTO_I_PB_SETTER_NAME(PREFIX, NAME)( \ struct PROTO * msg, C_TYPE value) { \ PerfettoPbMsgAppendType0Field(&msg->msg, NUM, \ PERFETTO_STATIC_CAST(uint64_t, value)); \ } -#define PERFETTO_I_PB_FIELD_ZIGZAG(PROTO, C_TYPE, NAME, NUM) \ - static inline void PERFETTO_I_PB_SETTER_NAME(PROTO, NAME)( \ +#define PERFETTO_I_PB_FIELD_ZIGZAG(PREFIX, PROTO, C_TYPE, NAME, NUM) \ + static inline void PERFETTO_I_PB_SETTER_NAME(PREFIX, NAME)( \ struct PROTO * msg, C_TYPE value) { \ uint64_t encoded = \ PerfettoPbZigZagEncode64(PERFETTO_STATIC_CAST(int64_t, value)); \ PerfettoPbMsgAppendType0Field(&msg->msg, NUM, encoded); \ } -#define PERFETTO_I_PB_FIELD_FIXED64(PROTO, C_TYPE, NAME, NUM) \ - static inline void PERFETTO_I_PB_SETTER_NAME(PROTO, NAME)( \ - struct PROTO * msg, C_TYPE value) { \ - uint64_t val; \ - memcpy(&val, &value, sizeof val); \ - PerfettoPbMsgAppendFixed64Field(&msg->msg, NUM, val); \ +#define PERFETTO_I_PB_FIELD_FIXED64(PREFIX, PROTO, C_TYPE, NAME, NUM) \ + static inline void PERFETTO_I_PB_SETTER_NAME(PREFIX, NAME)( \ + struct PROTO * msg, C_TYPE value) { \ + uint64_t val; \ + memcpy(&val, &value, sizeof val); \ + PerfettoPbMsgAppendFixed64Field(&msg->msg, NUM, val); \ } -#define PERFETTO_I_PB_FIELD_FIXED32(PROTO, C_TYPE, NAME, NUM) \ - static inline void PERFETTO_I_PB_SETTER_NAME(PROTO, NAME)( \ - struct PROTO * msg, C_TYPE value) { \ - uint32_t val; \ - memcpy(&val, &value, sizeof val); \ - PerfettoPbMsgAppendFixed32Field(&msg->msg, NUM, val); \ +#define PERFETTO_I_PB_FIELD_FIXED32(PREFIX, PROTO, C_TYPE, NAME, NUM) \ + static inline void PERFETTO_I_PB_SETTER_NAME(PREFIX, NAME)( \ + struct PROTO * msg, C_TYPE value) { \ + uint32_t val; \ + memcpy(&val, &value, sizeof val); \ + PerfettoPbMsgAppendFixed32Field(&msg->msg, NUM, val); \ } -#define PERFETTO_I_PB_FIELD_MSG(PROTO, C_TYPE, NAME, NUM) \ - static inline void PERFETTO_I_PB_SETTER_BEGIN_NAME(PROTO, NAME)( \ - struct PROTO * msg, struct C_TYPE * nested) { \ - struct PerfettoPbMsg* nested_msg = \ - PERFETTO_REINTERPRET_CAST(struct PerfettoPbMsg*, nested); \ - PerfettoPbMsgBeginNested(&msg->msg, nested_msg, NUM); \ - } \ - static inline void PERFETTO_I_PB_SETTER_END_NAME(PROTO, NAME)( \ - struct PROTO * msg, struct C_TYPE * nested) { \ - (void)nested; \ - PerfettoPbMsgEndNested(&msg->msg); \ +#define PERFETTO_I_PB_FIELD_MSG(PREFIX, PROTO, C_TYPE, NAME, NUM) \ + static inline void PERFETTO_I_PB_SETTER_BEGIN_NAME(PREFIX, NAME)( \ + struct PROTO * msg, struct C_TYPE * nested) { \ + struct PerfettoPbMsg* nested_msg = \ + PERFETTO_REINTERPRET_CAST(struct PerfettoPbMsg*, nested); \ + PerfettoPbMsgBeginNested(&msg->msg, nested_msg, NUM); \ + } \ + static inline void PERFETTO_I_PB_SETTER_END_NAME(PREFIX, NAME)( \ + struct PROTO * msg, struct C_TYPE * nested) { \ + (void)nested; \ + PerfettoPbMsgEndNested(&msg->msg); \ } -#define PERFETTO_I_PB_FIELD_PACKED(PROTO, C_TYPE, NAME, NUM) \ - static inline void PERFETTO_I_PB_SETTER_NAME(PROTO, NAME)( \ - struct PROTO * msg, const void* data, size_t len) { \ - PerfettoPbMsgAppendType2Field( \ - &msg->msg, NUM, PERFETTO_STATIC_CAST(const uint8_t*, data), len); \ - } \ - static inline void PERFETTO_I_PB_SETTER_BEGIN_NAME(PROTO, NAME)( \ - struct PROTO * msg, struct PerfettoPbPackedMsg##C_TYPE * nested) { \ - struct PerfettoPbMsg* nested_msg = \ - PERFETTO_REINTERPRET_CAST(struct PerfettoPbMsg*, nested); \ - PerfettoPbMsgBeginNested(&msg->msg, nested_msg, NUM); \ - } \ - static inline void PERFETTO_I_PB_SETTER_END_NAME(PROTO, NAME)( \ - struct PROTO * msg, struct PerfettoPbPackedMsg##C_TYPE * nested) { \ - (void)nested; \ - PerfettoPbMsgEndNested(&msg->msg); \ - } \ +#define PERFETTO_I_PB_FIELD_PACKED(PREFIX, PROTO, C_TYPE, NAME, NUM) \ + static inline void PERFETTO_I_PB_SETTER_NAME(PREFIX, NAME)( \ + struct PROTO * msg, const void* data, size_t len) { \ + PerfettoPbMsgAppendType2Field( \ + &msg->msg, NUM, PERFETTO_STATIC_CAST(const uint8_t*, data), len); \ + } \ + static inline void PERFETTO_I_PB_SETTER_BEGIN_NAME(PREFIX, NAME)( \ + struct PROTO * msg, struct PerfettoPbPackedMsg##C_TYPE * nested) { \ + struct PerfettoPbMsg* nested_msg = \ + PERFETTO_REINTERPRET_CAST(struct PerfettoPbMsg*, nested); \ + PerfettoPbMsgBeginNested(&msg->msg, nested_msg, NUM); \ + } \ + static inline void PERFETTO_I_PB_SETTER_END_NAME(PREFIX, NAME)( \ + struct PROTO * msg, struct PerfettoPbPackedMsg##C_TYPE * nested) { \ + (void)nested; \ + PerfettoPbMsgEndNested(&msg->msg); \ + } #define PERFETTO_I_PB_NUM_FIELD(PROTO, NAME, NUM) \ enum { PERFETTO_I_PB_NUM_FIELD_NAME(PROTO, NAME) = NUM } @@ -259,10 +259,20 @@ // nested): Begins (and ends) a packed helper nested submessage (of the // right type) to allow users to push repeated entries one by one // directly into the stream writer buffer. -#define PERFETTO_PB_FIELD(PROTO, TYPE, C_TYPE, NAME, NUM) \ - PERFETTO_I_PB_FIELD_##TYPE(PROTO, C_TYPE, NAME, NUM) \ +#define PERFETTO_PB_FIELD(PROTO, TYPE, C_TYPE, NAME, NUM) \ + PERFETTO_I_PB_FIELD_##TYPE(PROTO, PROTO, C_TYPE, NAME, NUM) \ PERFETTO_I_PB_NUM_FIELD(PROTO, NAME, NUM) +// Defines accessors for a field of a message for an extension. +// * `EXTENSION`: The name of the extension. it's going to be used as a prefix. +// There doesn't need to be a PERFETTO_PB_MSG definition for this. +// * `PROTO`: The (base) message that contains this field. This should be the +// same identifier passed to PERFETTO_PB_MSG. +// The rest of the params are the same as the PERFETTO_PB_FIELD macro. +#define PERFETTO_PB_EXTENSION_FIELD(EXTENSION, PROTO, TYPE, C_TYPE, NAME, NUM) \ + PERFETTO_I_PB_FIELD_##TYPE(EXTENSION, PROTO, C_TYPE, NAME, NUM) \ + PERFETTO_I_PB_NUM_FIELD(EXTENSION, NAME, NUM) + // Defines an enum type nested inside a message (PROTO). #define PERFETTO_PB_ENUM_IN_MSG(PROTO, ENUM) \ enum PERFETTO_I_PB_CONCAT_3(PROTO, _, ENUM) diff --git a/include/perfetto/public/pb_utils.h b/include/perfetto/public/pb_utils.h index 8c36389c57..7e7a83bdc2 100644 --- a/include/perfetto/public/pb_utils.h +++ b/include/perfetto/public/pb_utils.h @@ -19,6 +19,7 @@ #include #include +#include #include "perfetto/public/compiler.h" @@ -161,4 +162,16 @@ static inline int64_t PerfettoPbZigZagDecode64(uint64_t value) { return PERFETTO_STATIC_CAST(int64_t, ((value >> 1) ^ mask)); } +static inline uint64_t PerfettoPbDoubleToFixed64(double value) { + uint64_t val; + memcpy(&val, &value, sizeof val); + return val; +} + +static inline uint32_t PerfettoPbFloatToFixed32(float value) { + uint32_t val; + memcpy(&val, &value, sizeof val); + return val; +} + #endif // INCLUDE_PERFETTO_PUBLIC_PB_UTILS_H_ diff --git a/include/perfetto/public/producer.h b/include/perfetto/public/producer.h index 8fa1351b21..d6b2ed2e0b 100644 --- a/include/perfetto/public/producer.h +++ b/include/perfetto/public/producer.h @@ -28,20 +28,39 @@ struct PerfettoProducerInitArgs { // Bitwise-or of backends that should be enabled. PerfettoBackendTypes backends; + + // [Optional] Tune the size of the shared memory buffer between the current + // process and the service backend(s). This is a trade-off between memory + // footprint and the ability to sustain bursts of trace writes (see comments + // in shared_memory_abi.h). + // If set, the value must be a multiple of 4KB. The value can be ignored if + // larger than kMaxShmSize (32MB) or not a multiple of 4KB. + uint32_t shmem_size_hint_kb; }; // Initializes a PerfettoProducerInitArgs struct. #define PERFETTO_PRODUCER_INIT_ARGS_INIT() \ - { 0 } + { 0, 0 } // Initializes the global perfetto producer. +// +// It's ok to call this function multiple times, but if a backend was already +// initialized, most of `args` would be ignored. static inline void PerfettoProducerInit(struct PerfettoProducerInitArgs args) { + struct PerfettoProducerBackendInitArgs* backend_args = + PerfettoProducerBackendInitArgsCreate(); + + PerfettoProducerBackendInitArgsSetShmemSizeHintKb(backend_args, + args.shmem_size_hint_kb); + if (args.backends & PERFETTO_BACKEND_IN_PROCESS) { - PerfettoProducerInProcessInit(); + PerfettoProducerInProcessInit(backend_args); } if (args.backends & PERFETTO_BACKEND_SYSTEM) { - PerfettoProducerSystemInit(); + PerfettoProducerSystemInit(backend_args); } + + PerfettoProducerBackendInitArgsDestroy(backend_args); } // Informs the tracing services to activate the single trigger `trigger_name` if diff --git a/include/perfetto/public/protos/common/builtin_clock.pzc.h b/include/perfetto/public/protos/common/builtin_clock.pzc.h index 51218e460f..a7fa7e3556 100644 --- a/include/perfetto/public/protos/common/builtin_clock.pzc.h +++ b/include/perfetto/public/protos/common/builtin_clock.pzc.h @@ -33,6 +33,7 @@ PERFETTO_PB_ENUM(perfetto_protos_BuiltinClock){ PERFETTO_PB_ENUM_ENTRY(perfetto_protos_BUILTIN_CLOCK_MONOTONIC_COARSE) = 4, PERFETTO_PB_ENUM_ENTRY(perfetto_protos_BUILTIN_CLOCK_MONOTONIC_RAW) = 5, PERFETTO_PB_ENUM_ENTRY(perfetto_protos_BUILTIN_CLOCK_BOOTTIME) = 6, + PERFETTO_PB_ENUM_ENTRY(perfetto_protos_BUILTIN_CLOCK_TSC) = 9, PERFETTO_PB_ENUM_ENTRY(perfetto_protos_BUILTIN_CLOCK_MAX_ID) = 63, }; diff --git a/include/perfetto/public/protos/config/data_source_config.pzc.h b/include/perfetto/public/protos/config/data_source_config.pzc.h index ed7632ba43..5a78ca5ef3 100644 --- a/include/perfetto/public/protos/config/data_source_config.pzc.h +++ b/include/perfetto/public/protos/config/data_source_config.pzc.h @@ -43,6 +43,7 @@ PERFETTO_PB_MSG_DECL(perfetto_protos_JavaHprofConfig); PERFETTO_PB_MSG_DECL(perfetto_protos_NetworkPacketTraceConfig); PERFETTO_PB_MSG_DECL(perfetto_protos_PackagesListConfig); PERFETTO_PB_MSG_DECL(perfetto_protos_PerfEventConfig); +PERFETTO_PB_MSG_DECL(perfetto_protos_PixelModemConfig); PERFETTO_PB_MSG_DECL(perfetto_protos_ProcessStatsConfig); PERFETTO_PB_MSG_DECL(perfetto_protos_ProtoLogConfig); PERFETTO_PB_MSG_DECL(perfetto_protos_StatsdTracingConfig); @@ -243,6 +244,11 @@ PERFETTO_PB_FIELD(perfetto_protos_DataSourceConfig, perfetto_protos_AndroidInputEventConfig, android_input_event_config, 128); +PERFETTO_PB_FIELD(perfetto_protos_DataSourceConfig, + MSG, + perfetto_protos_PixelModemConfig, + pixel_modem_config, + 129); PERFETTO_PB_FIELD(perfetto_protos_DataSourceConfig, STRING, const char*, diff --git a/include/perfetto/public/protos/config/trace_config.pzc.h b/include/perfetto/public/protos/config/trace_config.pzc.h index 76492e33bf..cea261039a 100644 --- a/include/perfetto/public/protos/config/trace_config.pzc.h +++ b/include/perfetto/public/protos/config/trace_config.pzc.h @@ -36,6 +36,7 @@ PERFETTO_PB_MSG_DECL(perfetto_protos_TraceConfig_GuardrailOverrides); PERFETTO_PB_MSG_DECL(perfetto_protos_TraceConfig_IncidentReportConfig); PERFETTO_PB_MSG_DECL(perfetto_protos_TraceConfig_IncrementalStateConfig); PERFETTO_PB_MSG_DECL(perfetto_protos_TraceConfig_ProducerConfig); +PERFETTO_PB_MSG_DECL(perfetto_protos_TraceConfig_SessionSemaphore); PERFETTO_PB_MSG_DECL(perfetto_protos_TraceConfig_StatsdMetadata); PERFETTO_PB_MSG_DECL(perfetto_protos_TraceConfig_TraceFilter); PERFETTO_PB_MSG_DECL(perfetto_protos_TraceConfig_TraceFilter_StringFilterChain); @@ -275,6 +276,23 @@ PERFETTO_PB_FIELD(perfetto_protos_TraceConfig, perfetto_protos_TraceConfig_CmdTraceStartDelay, cmd_trace_start_delay, 35); +PERFETTO_PB_FIELD(perfetto_protos_TraceConfig, + MSG, + perfetto_protos_TraceConfig_SessionSemaphore, + session_semaphores, + 39); + +PERFETTO_PB_MSG(perfetto_protos_TraceConfig_SessionSemaphore); +PERFETTO_PB_FIELD(perfetto_protos_TraceConfig_SessionSemaphore, + STRING, + const char*, + name, + 1); +PERFETTO_PB_FIELD(perfetto_protos_TraceConfig_SessionSemaphore, + VARINT, + uint64_t, + max_other_session_count, + 2); PERFETTO_PB_MSG(perfetto_protos_TraceConfig_CmdTraceStartDelay); PERFETTO_PB_FIELD(perfetto_protos_TraceConfig_CmdTraceStartDelay, diff --git a/include/perfetto/public/protos/trace/android/android_track_event.pzc.h b/include/perfetto/public/protos/trace/android/android_track_event.pzc.h new file mode 100644 index 0000000000..40384e54da --- /dev/null +++ b/include/perfetto/public/protos/trace/android/android_track_event.pzc.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Autogenerated by the ProtoZero C compiler plugin. +// Invoked by tools/gen_c_protos +// DO NOT EDIT. +#ifndef INCLUDE_PERFETTO_PUBLIC_PROTOS_TRACE_ANDROID_ANDROID_TRACK_EVENT_PZC_H_ +#define INCLUDE_PERFETTO_PUBLIC_PROTOS_TRACE_ANDROID_ANDROID_TRACK_EVENT_PZC_H_ + +#include +#include + +#include "perfetto/public/pb_macros.h" +#include "perfetto/public/protos/trace/track_event/track_event.pzc.h" + +PERFETTO_PB_EXTENSION_FIELD(perfetto_protos_AndroidTrackEvent, + perfetto_protos_TrackEvent, + STRING, + const char*, + binder_service_name, + 2001); +PERFETTO_PB_EXTENSION_FIELD(perfetto_protos_AndroidTrackEvent, + perfetto_protos_TrackEvent, + STRING, + const char*, + binder_interface_name, + 2002); +PERFETTO_PB_EXTENSION_FIELD(perfetto_protos_AndroidTrackEvent, + perfetto_protos_TrackEvent, + STRING, + const char*, + apex_name, + 2003); +#endif // INCLUDE_PERFETTO_PUBLIC_PROTOS_TRACE_ANDROID_ANDROID_TRACK_EVENT_PZC_H_ diff --git a/include/perfetto/public/protos/trace/clock_snapshot.pzc.h b/include/perfetto/public/protos/trace/clock_snapshot.pzc.h new file mode 100644 index 0000000000..cc00030f34 --- /dev/null +++ b/include/perfetto/public/protos/trace/clock_snapshot.pzc.h @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Autogenerated by the ProtoZero C compiler plugin. +// Invoked by tools/gen_c_protos +// DO NOT EDIT. +#ifndef INCLUDE_PERFETTO_PUBLIC_PROTOS_TRACE_CLOCK_SNAPSHOT_PZC_H_ +#define INCLUDE_PERFETTO_PUBLIC_PROTOS_TRACE_CLOCK_SNAPSHOT_PZC_H_ + +#include +#include + +#include "perfetto/public/pb_macros.h" +#include "perfetto/public/protos/common/builtin_clock.pzc.h" + +PERFETTO_PB_MSG_DECL(perfetto_protos_ClockSnapshot_Clock); + +PERFETTO_PB_ENUM_IN_MSG(perfetto_protos_ClockSnapshot_Clock, BuiltinClocks){ + PERFETTO_PB_ENUM_IN_MSG_ENTRY(perfetto_protos_ClockSnapshot_Clock, + UNKNOWN) = 0, + PERFETTO_PB_ENUM_IN_MSG_ENTRY(perfetto_protos_ClockSnapshot_Clock, + REALTIME) = 1, + PERFETTO_PB_ENUM_IN_MSG_ENTRY(perfetto_protos_ClockSnapshot_Clock, + REALTIME_COARSE) = 2, + PERFETTO_PB_ENUM_IN_MSG_ENTRY(perfetto_protos_ClockSnapshot_Clock, + MONOTONIC) = 3, + PERFETTO_PB_ENUM_IN_MSG_ENTRY(perfetto_protos_ClockSnapshot_Clock, + MONOTONIC_COARSE) = 4, + PERFETTO_PB_ENUM_IN_MSG_ENTRY(perfetto_protos_ClockSnapshot_Clock, + MONOTONIC_RAW) = 5, + PERFETTO_PB_ENUM_IN_MSG_ENTRY(perfetto_protos_ClockSnapshot_Clock, + BOOTTIME) = 6, + PERFETTO_PB_ENUM_IN_MSG_ENTRY(perfetto_protos_ClockSnapshot_Clock, + BUILTIN_CLOCK_MAX_ID) = 63, +}; + +PERFETTO_PB_MSG(perfetto_protos_ClockSnapshot); +PERFETTO_PB_FIELD(perfetto_protos_ClockSnapshot, + MSG, + perfetto_protos_ClockSnapshot_Clock, + clocks, + 1); +PERFETTO_PB_FIELD(perfetto_protos_ClockSnapshot, + VARINT, + enum perfetto_protos_BuiltinClock, + primary_trace_clock, + 2); + +PERFETTO_PB_MSG(perfetto_protos_ClockSnapshot_Clock); +PERFETTO_PB_FIELD(perfetto_protos_ClockSnapshot_Clock, + VARINT, + uint32_t, + clock_id, + 1); +PERFETTO_PB_FIELD(perfetto_protos_ClockSnapshot_Clock, + VARINT, + uint64_t, + timestamp, + 2); +PERFETTO_PB_FIELD(perfetto_protos_ClockSnapshot_Clock, + VARINT, + bool, + is_incremental, + 3); +PERFETTO_PB_FIELD(perfetto_protos_ClockSnapshot_Clock, + VARINT, + uint64_t, + unit_multiplier_ns, + 4); + +#endif // INCLUDE_PERFETTO_PUBLIC_PROTOS_TRACE_CLOCK_SNAPSHOT_PZC_H_ diff --git a/include/perfetto/public/protos/trace/interned_data/interned_data.pzc.h b/include/perfetto/public/protos/trace/interned_data/interned_data.pzc.h index 71e26fa007..d2c5a1ba38 100644 --- a/include/perfetto/public/protos/trace/interned_data/interned_data.pzc.h +++ b/include/perfetto/public/protos/trace/interned_data/interned_data.pzc.h @@ -193,5 +193,25 @@ PERFETTO_PB_FIELD(perfetto_protos_InternedData, perfetto_protos_InternedString, protolog_stacktrace, 37); +PERFETTO_PB_FIELD(perfetto_protos_InternedData, + MSG, + perfetto_protos_InternedString, + viewcapture_package_name, + 38); +PERFETTO_PB_FIELD(perfetto_protos_InternedData, + MSG, + perfetto_protos_InternedString, + viewcapture_window_name, + 39); +PERFETTO_PB_FIELD(perfetto_protos_InternedData, + MSG, + perfetto_protos_InternedString, + viewcapture_view_id, + 40); +PERFETTO_PB_FIELD(perfetto_protos_InternedData, + MSG, + perfetto_protos_InternedString, + viewcapture_class_name, + 41); #endif // INCLUDE_PERFETTO_PUBLIC_PROTOS_TRACE_INTERNED_DATA_INTERNED_DATA_PZC_H_ diff --git a/include/perfetto/public/protos/trace/trace_packet.pzc.h b/include/perfetto/public/protos/trace/trace_packet.pzc.h index d83ab18d14..01518214ea 100644 --- a/include/perfetto/public/protos/trace/trace_packet.pzc.h +++ b/include/perfetto/public/protos/trace/trace_packet.pzc.h @@ -29,13 +29,13 @@ PERFETTO_PB_MSG_DECL(perfetto_protos_AndroidCameraFrameEvent); PERFETTO_PB_MSG_DECL(perfetto_protos_AndroidCameraSessionStats); PERFETTO_PB_MSG_DECL(perfetto_protos_AndroidEnergyEstimationBreakdown); PERFETTO_PB_MSG_DECL(perfetto_protos_AndroidGameInterventionList); -PERFETTO_PB_MSG_DECL(perfetto_protos_AndroidInputEvent); PERFETTO_PB_MSG_DECL(perfetto_protos_AndroidLogPacket); PERFETTO_PB_MSG_DECL(perfetto_protos_AndroidSystemProperty); PERFETTO_PB_MSG_DECL(perfetto_protos_BatteryCounters); PERFETTO_PB_MSG_DECL(perfetto_protos_ChromeBenchmarkMetadata); PERFETTO_PB_MSG_DECL(perfetto_protos_ChromeEventBundle); PERFETTO_PB_MSG_DECL(perfetto_protos_ChromeMetadataPacket); +PERFETTO_PB_MSG_DECL(perfetto_protos_ChromeTrigger); PERFETTO_PB_MSG_DECL(perfetto_protos_ClockSnapshot); PERFETTO_PB_MSG_DECL(perfetto_protos_CpuInfo); PERFETTO_PB_MSG_DECL(perfetto_protos_DeobfuscationMapping); @@ -62,6 +62,8 @@ PERFETTO_PB_MSG_DECL(perfetto_protos_NetworkPacketEvent); PERFETTO_PB_MSG_DECL(perfetto_protos_PackagesList); PERFETTO_PB_MSG_DECL(perfetto_protos_PerfSample); PERFETTO_PB_MSG_DECL(perfetto_protos_PerfettoMetatrace); +PERFETTO_PB_MSG_DECL(perfetto_protos_PixelModemEvents); +PERFETTO_PB_MSG_DECL(perfetto_protos_PixelModemTokenDatabase); PERFETTO_PB_MSG_DECL(perfetto_protos_PowerRails); PERFETTO_PB_MSG_DECL(perfetto_protos_ProcessDescriptor); PERFETTO_PB_MSG_DECL(perfetto_protos_ProcessStats); @@ -101,6 +103,7 @@ PERFETTO_PB_MSG_DECL(perfetto_protos_V8RegExpCode); PERFETTO_PB_MSG_DECL(perfetto_protos_V8WasmCode); PERFETTO_PB_MSG_DECL(perfetto_protos_VulkanApiEvent); PERFETTO_PB_MSG_DECL(perfetto_protos_VulkanMemoryEvent); +PERFETTO_PB_MSG_DECL(perfetto_protos_WinscopeExtensions); PERFETTO_PB_ENUM_IN_MSG(perfetto_protos_TracePacket, SequenceFlags){ PERFETTO_PB_ENUM_IN_MSG_ENTRY(perfetto_protos_TracePacket, @@ -213,6 +216,11 @@ PERFETTO_PB_FIELD(perfetto_protos_TracePacket, perfetto_protos_Trigger, trigger, 46); +PERFETTO_PB_FIELD(perfetto_protos_TracePacket, + MSG, + perfetto_protos_ChromeTrigger, + chrome_trigger, + 109); PERFETTO_PB_FIELD(perfetto_protos_TracePacket, MSG, perfetto_protos_PackagesList, @@ -453,6 +461,11 @@ PERFETTO_PB_FIELD(perfetto_protos_TracePacket, perfetto_protos_ProtoLogViewerConfig, protolog_viewer_config, 105); +PERFETTO_PB_FIELD(perfetto_protos_TracePacket, + MSG, + perfetto_protos_WinscopeExtensions, + winscope_extensions, + 112); PERFETTO_PB_FIELD(perfetto_protos_TracePacket, MSG, perfetto_protos_EtwTraceEventBundle, @@ -483,16 +496,21 @@ PERFETTO_PB_FIELD(perfetto_protos_TracePacket, perfetto_protos_V8CodeMove, v8_code_move, 103); -PERFETTO_PB_FIELD(perfetto_protos_TracePacket, - MSG, - perfetto_protos_AndroidInputEvent, - android_input_event, - 106); PERFETTO_PB_FIELD(perfetto_protos_TracePacket, MSG, perfetto_protos_RemoteClockSync, remote_clock_sync, 107); +PERFETTO_PB_FIELD(perfetto_protos_TracePacket, + MSG, + perfetto_protos_PixelModemEvents, + pixel_modem_events, + 110); +PERFETTO_PB_FIELD(perfetto_protos_TracePacket, + MSG, + perfetto_protos_PixelModemTokenDatabase, + pixel_modem_token_database, + 111); PERFETTO_PB_FIELD(perfetto_protos_TracePacket, MSG, perfetto_protos_TestEvent, diff --git a/include/perfetto/public/protos/trace/track_event/track_descriptor.pzc.h b/include/perfetto/public/protos/trace/track_event/track_descriptor.pzc.h index ee3d9788df..7f0a5ad47c 100644 --- a/include/perfetto/public/protos/trace/track_event/track_descriptor.pzc.h +++ b/include/perfetto/public/protos/trace/track_event/track_descriptor.pzc.h @@ -43,6 +43,11 @@ PERFETTO_PB_FIELD(perfetto_protos_TrackDescriptor, const char*, name, 2); +PERFETTO_PB_FIELD(perfetto_protos_TrackDescriptor, + STRING, + const char*, + static_name, + 10); PERFETTO_PB_FIELD(perfetto_protos_TrackDescriptor, MSG, perfetto_protos_ProcessDescriptor, diff --git a/include/perfetto/public/protos/trace/track_event/track_event.pzc.h b/include/perfetto/public/protos/trace/track_event/track_event.pzc.h index 2ee81f1802..e9e324779c 100644 --- a/include/perfetto/public/protos/trace/track_event/track_event.pzc.h +++ b/include/perfetto/public/protos/trace/track_event/track_event.pzc.h @@ -41,6 +41,7 @@ PERFETTO_PB_MSG_DECL(perfetto_protos_ChromeUserEvent); PERFETTO_PB_MSG_DECL(perfetto_protos_ChromeWindowHandleEventInfo); PERFETTO_PB_MSG_DECL(perfetto_protos_DebugAnnotation); PERFETTO_PB_MSG_DECL(perfetto_protos_LogMessage); +PERFETTO_PB_MSG_DECL(perfetto_protos_PixelModemEventInsight); PERFETTO_PB_MSG_DECL(perfetto_protos_Screenshot); PERFETTO_PB_MSG_DECL(perfetto_protos_SourceLocation); PERFETTO_PB_MSG_DECL(perfetto_protos_TaskExecution); @@ -250,6 +251,11 @@ PERFETTO_PB_FIELD(perfetto_protos_TrackEvent, perfetto_protos_Screenshot, screenshot, 50); +PERFETTO_PB_FIELD(perfetto_protos_TrackEvent, + MSG, + perfetto_protos_PixelModemEventInsight, + pixel_modem_event_insight, + 51); PERFETTO_PB_FIELD(perfetto_protos_TrackEvent, MSG, perfetto_protos_SourceLocation, diff --git a/include/perfetto/public/te_macros.h b/include/perfetto/public/te_macros.h index f54823801c..9ab741d48c 100644 --- a/include/perfetto/public/te_macros.h +++ b/include/perfetto/public/te_macros.h @@ -19,7 +19,12 @@ #include +#ifdef __cplusplus +#include +#endif + #include "perfetto/public/abi/track_event_hl_abi.h" +#include "perfetto/public/pb_utils.h" #include "perfetto/public/track_event.h" // This header defines the PERFETTO_TE macros and its possible params (at the @@ -28,254 +33,292 @@ // // The macro uses the High level ABI to emit track events. -#define PERFETTO_I_TE_STATIC_ASSERT_NUM_PARAMS_( \ - NAME_AND_TYPE1, NAME_AND_TYPE2, EXTRA1, EXTRA2, EXTRA3, EXTRA4, SENTINEL, \ - ...) \ - static_assert((SENTINEL) == 0, \ - "Too many arguments for PERFETTO_TE " \ - "macro") - -// Fails to compile if there are too many params and they don't fit into -// PerfettoTeHlMacroParams. -#define PERFETTO_I_TE_STATIC_ASSERT_NUM_PARAMS(...) \ - PERFETTO_I_TE_STATIC_ASSERT_NUM_PARAMS_(__VA_ARGS__, 0, 0, 0, 0, 0, 0) - -#define PERFETTO_I_TE_LIMIT_4__(NAME_AND_TYPE1, NAME_AND_TYPE2, EXTRA1, \ - EXTRA2, EXTRA3, EXTRA4, ...) \ - NAME_AND_TYPE1, NAME_AND_TYPE2, EXTRA1, EXTRA2, EXTRA3, EXTRA4 -#define PERFETTO_I_TE_LIMIT_4_(MACRO, ARGS) MACRO ARGS -#define PERFETTO_I_TE_LIMIT_4(...) \ - PERFETTO_I_TE_LIMIT_4_(PERFETTO_I_TE_LIMIT_4__, (__VA_ARGS__)) - -// In C we have to use a compound literal. In C++ we can use a regular -// initializer. -#ifndef __cplusplus -#define PERFETTO_I_TE_HL_MACRO_PARAMS_PREAMBLE (struct PerfettoTeHlMacroParams) -#else -#define PERFETTO_I_TE_HL_MACRO_PARAMS_PREAMBLE -#endif +#define PERFETTO_I_TE_HL_MACRO_PARAMS_(NAME_AND_TYPE, ...) \ + NAME_AND_TYPE.type, NAME_AND_TYPE.name, \ + PERFETTO_I_TE_COMPOUND_LITERAL_ARRAY(struct PerfettoTeHlExtra*, \ + {__VA_ARGS__}) // Provides an initializer for `struct PerfettoTeHlMacroParams` and sets all the // unused extra fields to PERFETTO_NULL. -#define PERFETTO_I_TE_HL_MACRO_PARAMS(...) \ - PERFETTO_I_TE_HL_MACRO_PARAMS_PREAMBLE { \ - PERFETTO_I_TE_LIMIT_4(__VA_ARGS__, PERFETTO_NULL, PERFETTO_NULL, \ - PERFETTO_NULL, PERFETTO_NULL, PERFETTO_NULL, \ - PERFETTO_NULL) \ - } +#define PERFETTO_I_TE_HL_MACRO_PARAMS(...) \ + PERFETTO_I_TE_HL_MACRO_PARAMS_(__VA_ARGS__, PERFETTO_NULL) + +// Implementation of the PERFETTO_TE macro. If `CAT` is enabled, emits the +// tracing event specified by the params. +// +// Uses `?:` instead of `if` because this might be used as an expression, where +// statements are not allowed. +#define PERFETTO_I_TE_IMPL(CAT, ...) \ + ((PERFETTO_UNLIKELY(PERFETTO_ATOMIC_LOAD_EXPLICIT( \ + (CAT).enabled, PERFETTO_MEMORY_ORDER_RELAXED))) \ + ? (PerfettoTeHlEmitImpl((CAT).impl, \ + PERFETTO_I_TE_HL_MACRO_PARAMS(__VA_ARGS__)), \ + 0) \ + : 0) #ifndef __cplusplus +#define PERFETTO_I_TE_COMPOUND_LITERAL(STRUCT, ...) (struct STRUCT) __VA_ARGS__ #define PERFETTO_I_TE_COMPOUND_LITERAL_ADDR(STRUCT, ...) \ &(struct STRUCT)__VA_ARGS__ +#define PERFETTO_I_TE_COMPOUND_LITERAL_ARRAY(TYPE, ...) (TYPE[]) __VA_ARGS__ #define PERFETTO_I_TE_EXTRA(STRUCT, ...) \ ((struct PerfettoTeHlExtra*)PERFETTO_I_TE_COMPOUND_LITERAL_ADDR( \ STRUCT, __VA_ARGS__)) #else +#define PERFETTO_I_TE_COMPOUND_LITERAL(STRUCT, ...) STRUCT __VA_ARGS__ #define PERFETTO_I_TE_COMPOUND_LITERAL_ADDR(STRUCT, ...) \ &(STRUCT{} = STRUCT __VA_ARGS__) +#define PERFETTO_I_TE_COMPOUND_LITERAL_ARRAY(TYPE, ...) \ + static_cast((std::initializer_list __VA_ARGS__).begin()) #define PERFETTO_I_TE_EXTRA(STRUCT, ...) \ reinterpret_cast( \ PERFETTO_I_TE_COMPOUND_LITERAL_ADDR(STRUCT, __VA_ARGS__)) #endif +#define PERFETTO_I_TE_HL_MACRO_NAME_AND_TYPE(NAME, TYPE) \ + (PERFETTO_I_TE_COMPOUND_LITERAL(PerfettoTeHlMacroNameAndType, {NAME, TYPE})) + +#define PERFETTO_I_TE_CONCAT2(a, b) a##b +#define PERFETTO_I_TE_CONCAT(a, b) PERFETTO_I_TE_CONCAT2(a, b) +// Generate a unique name with a given prefix. +#define PERFETTO_I_TE_UID(prefix) PERFETTO_I_TE_CONCAT(prefix, __LINE__) + struct PerfettoTeHlMacroNameAndType { const char* name; int32_t type; }; -struct PerfettoTeHlMacroParams { - struct PerfettoTeHlMacroNameAndType name_and_type; - struct PerfettoTeHlExtra* extra1; - struct PerfettoTeHlExtra* extra2; - struct PerfettoTeHlExtra* extra3; - struct PerfettoTeHlExtra* extra4; -}; - -static inline void PerfettoTeHlCall(struct PerfettoTeCategoryImpl* cat, - struct PerfettoTeHlMacroParams params) { - struct PerfettoTeHlExtra* perfetto_i_extra_data = PERFETTO_NULL; - if (params.extra1) { - params.extra1->next = perfetto_i_extra_data; - perfetto_i_extra_data = params.extra1; - } - if (params.extra2) { - params.extra2->next = perfetto_i_extra_data; - perfetto_i_extra_data = params.extra2; - } - if (params.extra3) { - params.extra3->next = perfetto_i_extra_data; - perfetto_i_extra_data = params.extra3; - } - if (params.extra4) { - params.extra4->next = perfetto_i_extra_data; - perfetto_i_extra_data = params.extra4; - } - PerfettoTeHlEmitImpl(cat, params.name_and_type.type, - params.name_and_type.name, perfetto_i_extra_data); -} - // Instead of a previously registered category, this macro can be used to // specify that the category will be provided dynamically as a param. #define PERFETTO_TE_DYNAMIC_CATEGORY PerfettoTeRegisteredDynamicCategory() +// --------------------------------------------------------------- +// Possible types of fields for the PERFETTO_TE_PROTO_FIELDS macro +// --------------------------------------------------------------- + +// A string or bytes protobuf field (with field id `ID`) and value `VAL` (a null +// terminated string). +#define PERFETTO_TE_PROTO_FIELD_CSTR(ID, VAL) \ + PERFETTO_REINTERPRET_CAST(struct PerfettoTeHlProtoField*, \ + PERFETTO_I_TE_COMPOUND_LITERAL_ADDR( \ + PerfettoTeHlProtoFieldCstr, \ + {{PERFETTO_TE_HL_PROTO_TYPE_CSTR, ID}, VAL})) + +// A string or bytes protobuf field (with field id `ID`) with a `SIZE` long +// value starting from `VAL`. +#define PERFETTO_TE_PROTO_FIELD_BYTES(ID, VAL, SIZE) \ + PERFETTO_REINTERPRET_CAST( \ + struct PerfettoTeHlProtoField*, \ + PERFETTO_I_TE_COMPOUND_LITERAL_ADDR( \ + PerfettoTeHlProtoFieldBytes, \ + {{PERFETTO_TE_HL_PROTO_TYPE_BYTES, ID}, VAL, SIZE})) + +// An varint protobuf field (with field id `ID`) and value `VAL`. +#define PERFETTO_TE_PROTO_FIELD_VARINT(ID, VAL) \ + PERFETTO_REINTERPRET_CAST(struct PerfettoTeHlProtoField*, \ + PERFETTO_I_TE_COMPOUND_LITERAL_ADDR( \ + PerfettoTeHlProtoFieldVarInt, \ + {{PERFETTO_TE_HL_PROTO_TYPE_VARINT, ID}, \ + PERFETTO_STATIC_CAST(uint64_t, VAL)})) + +// An zigzag (sint*) protobuf field (with field id `ID`) and value `VAL`. +#define PERFETTO_TE_PROTO_FIELD_ZIGZAG(ID, VAL) \ + PERFETTO_REINTERPRET_CAST( \ + struct PerfettoTeHlProtoField*, \ + PERFETTO_I_TE_COMPOUND_LITERAL_ADDR( \ + PerfettoTeHlProtoFieldVarInt, \ + {{PERFETTO_TE_HL_PROTO_TYPE_VARINT, ID}, \ + PerfettoPbZigZagEncode64(PERFETTO_STATIC_CAST(int64_t, VAL))})) + +// A fixed64 protobuf field (with field id `ID`) and value `VAL`. +#define PERFETTO_TE_PROTO_FIELD_FIXED64(ID, VAL) \ + PERFETTO_REINTERPRET_CAST(struct PerfettoTeHlProtoField*, \ + PERFETTO_I_TE_COMPOUND_LITERAL_ADDR( \ + PerfettoTeHlProtoFieldFixed64, \ + {{PERFETTO_TE_HL_PROTO_TYPE_FIXED64, ID}, \ + PERFETTO_STATIC_CAST(uint64_t, VAL)})) + +// A fixed32 protobuf field (with field id `ID`) and value `VAL`. +#define PERFETTO_TE_PROTO_FIELD_FIXED32(ID, VAL) \ + PERFETTO_REINTERPRET_CAST(struct PerfettoTeHlProtoField*, \ + PERFETTO_I_TE_COMPOUND_LITERAL_ADDR( \ + PerfettoTeHlProtoFieldFixed32, \ + {{PERFETTO_TE_HL_PROTO_TYPE_FIXED32, ID}, \ + PERFETTO_STATIC_CAST(uint32_t, VAL)})) + +// A double protobuf field (with field id `ID`) and value `VAL`. +#define PERFETTO_TE_PROTO_FIELD_DOUBLE(ID, VAL) \ + PERFETTO_REINTERPRET_CAST(struct PerfettoTeHlProtoField*, \ + PERFETTO_I_TE_COMPOUND_LITERAL_ADDR( \ + PerfettoTeHlProtoFieldDouble, \ + {{PERFETTO_TE_HL_PROTO_TYPE_DOUBLE, ID}, \ + PERFETTO_STATIC_CAST(double, VAL)})) + +// A float protobuf field (with field id `ID`) and value `VAL`. +#define PERFETTO_TE_PROTO_FIELD_FLOAT(ID, VAL) \ + PERFETTO_REINTERPRET_CAST( \ + struct PerfettoTeHlProtoField*, \ + PERFETTO_I_TE_COMPOUND_LITERAL_ADDR( \ + PerfettoTeHlProtoFieldFloat, {{PERFETTO_TE_HL_PROTO_TYPE_FLOAT, ID}, \ + PERFETTO_STATIC_CAST(float, VAL)})) + +// A nested message protobuf field (with field id `ID`). The rest of the +// argument can be PERFETTO_TE_PROTO_FIELD_*. +#define PERFETTO_TE_PROTO_FIELD_NESTED(ID, ...) \ + PERFETTO_REINTERPRET_CAST(struct PerfettoTeHlProtoField*, \ + PERFETTO_I_TE_COMPOUND_LITERAL_ADDR( \ + PerfettoTeHlProtoFieldNested, \ + {{PERFETTO_TE_HL_PROTO_TYPE_NESTED, ID}, \ + PERFETTO_I_TE_COMPOUND_LITERAL_ARRAY( \ + struct PerfettoTeHlProtoField*, \ + {__VA_ARGS__, PERFETTO_NULL})})) + // ------------------------------------------------- // Possible types of event for the PERFETTO_TE macro // ------------------------------------------------- // Begins a slice named `const char* NAME` on a track. #define PERFETTO_TE_SLICE_BEGIN(NAME) \ - { NAME, PERFETTO_TE_TYPE_SLICE_BEGIN } + PERFETTO_I_TE_HL_MACRO_NAME_AND_TYPE(NAME, PERFETTO_TE_TYPE_SLICE_BEGIN) // Ends the last slice opened on a track. -#define PERFETTO_TE_SLICE_END() \ - { PERFETTO_NULL, PERFETTO_TE_TYPE_SLICE_END } +#define PERFETTO_TE_SLICE_END() \ + PERFETTO_I_TE_HL_MACRO_NAME_AND_TYPE(PERFETTO_NULL, \ + PERFETTO_TE_TYPE_SLICE_END) // Reports an instant event named `const char* NAME`. #define PERFETTO_TE_INSTANT(NAME) \ - { NAME, PERFETTO_TE_TYPE_INSTANT } + PERFETTO_I_TE_HL_MACRO_NAME_AND_TYPE(NAME, PERFETTO_TE_TYPE_INSTANT) // Reports the value of a counter. The counter value must be specified // separately on another param with PERFETTO_TE_INT_COUNTER() or // PERFETTO_TE_DOUBLE_COUNTER(). #define PERFETTO_TE_COUNTER() \ - { PERFETTO_NULL, PERFETTO_TE_TYPE_COUNTER } + PERFETTO_I_TE_HL_MACRO_NAME_AND_TYPE(PERFETTO_NULL, PERFETTO_TE_TYPE_COUNTER) -// ------------------------------------------------- -// Possible types of event for the PERFETTO_TE macro -// ------------------------------------------------- +// ----------------------------------------------------------- +// Possible types of extra arguments for the PERFETTO_TE macro +// ----------------------------------------------------------- // The value (`C`) of an integer counter. A separate parameter must describe the // counter track this refers to. This should only be used for events with // type PERFETTO_TE_COUNTER(). -#define PERFETTO_TE_INT_COUNTER(C) \ - PERFETTO_I_TE_EXTRA( \ - PerfettoTeHlExtraCounterInt64, \ - {{PERFETTO_TE_HL_EXTRA_TYPE_COUNTER_INT64, PERFETTO_NULL}, C}) +#define PERFETTO_TE_INT_COUNTER(C) \ + PERFETTO_I_TE_EXTRA(PerfettoTeHlExtraCounterInt64, \ + {{PERFETTO_TE_HL_EXTRA_TYPE_COUNTER_INT64}, C}) // The value (`C`) of a floating point. A separate parameter must describe the // counter track this refers to. This should only be used for events with type // PERFETTO_TE_COUNTER(). -#define PERFETTO_TE_DOUBLE_COUNTER(C) \ - PERFETTO_I_TE_EXTRA( \ - PerfettoTeHlExtraCounterDouble, \ - {{PERFETTO_TE_HL_EXTRA_TYPE_COUNTER_DOUBLE, PERFETTO_NULL}, C}) +#define PERFETTO_TE_DOUBLE_COUNTER(C) \ + PERFETTO_I_TE_EXTRA(PerfettoTeHlExtraCounterDouble, \ + {{PERFETTO_TE_HL_EXTRA_TYPE_COUNTER_DOUBLE}, C}) // Uses the timestamp `struct PerfettoTeTimestamp T` to report this event. If // this is not specified, PERFETTO_TE() reads the current timestamp with // PerfettoTeGetTimestamp(). -#define PERFETTO_TE_TIMESTAMP(T) \ - PERFETTO_I_TE_EXTRA( \ - PerfettoTeHlExtraTimestamp, \ - {{PERFETTO_TE_HL_EXTRA_TYPE_TIMESTAMP, PERFETTO_NULL}, T}) +#define PERFETTO_TE_TIMESTAMP(T) \ + PERFETTO_I_TE_EXTRA(PerfettoTeHlExtraTimestamp, \ + {{PERFETTO_TE_HL_EXTRA_TYPE_TIMESTAMP}, T}) // Specifies that the current track for this event is // `struct PerfettoTeRegisteredTrack* T`, which must have been previously // registered. -#define PERFETTO_TE_REGISTERED_TRACK(T) \ - PERFETTO_I_TE_EXTRA( \ - PerfettoTeHlExtraRegisteredTrack, \ - {{PERFETTO_TE_HL_EXTRA_TYPE_REGISTERED_TRACK, PERFETTO_NULL}, \ - &(T)->impl}) +#define PERFETTO_TE_REGISTERED_TRACK(T) \ + PERFETTO_I_TE_EXTRA( \ + PerfettoTeHlExtraRegisteredTrack, \ + {{PERFETTO_TE_HL_EXTRA_TYPE_REGISTERED_TRACK}, &(T)->impl}) // Specifies that the current track for this event is a track named `const char // *NAME`, child of a track whose uuid is `PARENT_UUID`. `NAME`, `uint64_t ID` // and `PARENT_UUID` uniquely identify a track. Common values for `PARENT_UUID` // include PerfettoTeProcessTrackUuid(), PerfettoTeThreadTrackUuid() or // PerfettoTeGlobalTrackUuid(). -#define PERFETTO_TE_NAMED_TRACK(NAME, ID, PARENT_UUID) \ - PERFETTO_I_TE_EXTRA(PerfettoTeHlExtraNamedTrack, \ - {{PERFETTO_TE_HL_EXTRA_TYPE_NAMED_TRACK, PERFETTO_NULL}, \ - NAME, \ - ID, \ - PARENT_UUID}) +#define PERFETTO_TE_NAMED_TRACK(NAME, ID, PARENT_UUID) \ + PERFETTO_I_TE_EXTRA( \ + PerfettoTeHlExtraNamedTrack, \ + {{PERFETTO_TE_HL_EXTRA_TYPE_NAMED_TRACK}, NAME, ID, PARENT_UUID}) // When PERFETTO_TE_DYNAMIC_CATEGORY is used, this is used to specify `const // char* S` as a category name. -#define PERFETTO_TE_DYNAMIC_CATEGORY_STRING(S) \ - PERFETTO_I_TE_EXTRA( \ - PerfettoTeHlExtraDynamicCategory, \ - {{PERFETTO_TE_HL_EXTRA_TYPE_DYNAMIC_CATEGORY, PERFETTO_NULL}, \ - PERFETTO_I_TE_COMPOUND_LITERAL_ADDR( \ - PerfettoTeCategoryDescriptor, \ - {S, PERFETTO_NULL, PERFETTO_NULL, 0})}) +#define PERFETTO_TE_DYNAMIC_CATEGORY_STRING(S) \ + PERFETTO_I_TE_EXTRA(PerfettoTeHlExtraDynamicCategory, \ + {{PERFETTO_TE_HL_EXTRA_TYPE_DYNAMIC_CATEGORY}, \ + PERFETTO_I_TE_COMPOUND_LITERAL_ADDR( \ + PerfettoTeCategoryDescriptor, \ + {S, PERFETTO_NULL, PERFETTO_NULL, 0})}) // Adds the debug annotation named `const char * NAME` with value `bool VALUE`. -#define PERFETTO_TE_ARG_BOOL(NAME, VALUE) \ - PERFETTO_I_TE_EXTRA( \ - PerfettoTeHlExtraDebugArgBool, \ - {{PERFETTO_TE_HL_EXTRA_TYPE_DEBUG_ARG_BOOL, PERFETTO_NULL}, \ - NAME, \ - VALUE}) +#define PERFETTO_TE_ARG_BOOL(NAME, VALUE) \ + PERFETTO_I_TE_EXTRA( \ + PerfettoTeHlExtraDebugArgBool, \ + {{PERFETTO_TE_HL_EXTRA_TYPE_DEBUG_ARG_BOOL}, NAME, VALUE}) // Adds the debug annotation named `const char * NAME` with value `uint64_t // VALUE`. -#define PERFETTO_TE_ARG_UINT64(NAME, VALUE) \ - PERFETTO_I_TE_EXTRA( \ - PerfettoTeHlExtraDebugArgUint64, \ - {{PERFETTO_TE_HL_EXTRA_TYPE_DEBUG_ARG_UINT64, PERFETTO_NULL}, \ - NAME, \ - VALUE}) +#define PERFETTO_TE_ARG_UINT64(NAME, VALUE) \ + PERFETTO_I_TE_EXTRA( \ + PerfettoTeHlExtraDebugArgUint64, \ + {{PERFETTO_TE_HL_EXTRA_TYPE_DEBUG_ARG_UINT64}, NAME, VALUE}) // Adds the debug annotation named `const char * NAME` with value `int64_t // VALUE`. -#define PERFETTO_TE_ARG_INT64(NAME, VALUE) \ - PERFETTO_I_TE_EXTRA( \ - PerfettoTeHlExtraDebugArgInt64, \ - {{PERFETTO_TE_HL_EXTRA_TYPE_DEBUG_ARG_INT64, PERFETTO_NULL}, \ - NAME, \ - VALUE}) +#define PERFETTO_TE_ARG_INT64(NAME, VALUE) \ + PERFETTO_I_TE_EXTRA( \ + PerfettoTeHlExtraDebugArgInt64, \ + {{PERFETTO_TE_HL_EXTRA_TYPE_DEBUG_ARG_INT64}, NAME, VALUE}) // Adds the debug annotation named `const char * NAME` with value `double // VALUE`. -#define PERFETTO_TE_ARG_DOUBLE(NAME, VALUE) \ - PERFETTO_I_TE_EXTRA( \ - PerfettoTeHlExtraDebugArgDouble, \ - {{PERFETTO_TE_HL_EXTRA_TYPE_DEBUG_ARG_DOUBLE, PERFETTO_NULL}, \ - NAME, \ - VALUE}) +#define PERFETTO_TE_ARG_DOUBLE(NAME, VALUE) \ + PERFETTO_I_TE_EXTRA( \ + PerfettoTeHlExtraDebugArgDouble, \ + {{PERFETTO_TE_HL_EXTRA_TYPE_DEBUG_ARG_DOUBLE}, NAME, VALUE}) // Adds the debug annotation named `const char * NAME` with value `const char* // VALUE`. -#define PERFETTO_TE_ARG_STRING(NAME, VALUE) \ - PERFETTO_I_TE_EXTRA( \ - PerfettoTeHlExtraDebugArgString, \ - {{PERFETTO_TE_HL_EXTRA_TYPE_DEBUG_ARG_STRING, PERFETTO_NULL}, \ - NAME, \ - VALUE}) +#define PERFETTO_TE_ARG_STRING(NAME, VALUE) \ + PERFETTO_I_TE_EXTRA( \ + PerfettoTeHlExtraDebugArgString, \ + {{PERFETTO_TE_HL_EXTRA_TYPE_DEBUG_ARG_STRING}, NAME, VALUE}) // Adds the debug annotation named `const char * NAME` with value `void* VALUE`. -#define PERFETTO_TE_ARG_POINTER(NAME, VALUE) \ - PERFETTO_I_TE_EXTRA( \ - PerfettoTeHlExtraDebugArgPointer, \ - {{PERFETTO_TE_HL_EXTRA_TYPE_DEBUG_ARG_POINTER, PERFETTO_NULL}, \ - NAME, \ - VALUE}) +#define PERFETTO_TE_ARG_POINTER(NAME, VALUE) \ + PERFETTO_I_TE_EXTRA( \ + PerfettoTeHlExtraDebugArgPointer, \ + {{PERFETTO_TE_HL_EXTRA_TYPE_DEBUG_ARG_POINTER}, NAME, VALUE}) // Specifies that this event is part (or starts) a "flow" (i.e. a link among // different events). The flow is identified by `struct PerfettoTeFlow VALUE`. -#define PERFETTO_TE_FLOW(VALUE) \ - PERFETTO_I_TE_EXTRA( \ - PerfettoTeHlExtraFlow, \ - {{PERFETTO_TE_HL_EXTRA_TYPE_FLOW, PERFETTO_NULL}, (VALUE).id}) +#define PERFETTO_TE_FLOW(VALUE) \ + PERFETTO_I_TE_EXTRA(PerfettoTeHlExtraFlow, \ + {{PERFETTO_TE_HL_EXTRA_TYPE_FLOW}, (VALUE).id}) // Specifies that this event terminates a "flow" (i.e. a link among different // events). The flow is identified by `struct PerfettoTeFlow VALUE`. -#define PERFETTO_TE_TERMINATING_FLOW(VALUE) \ - PERFETTO_I_TE_EXTRA( \ - PerfettoTeHlExtraFlow, \ - {{PERFETTO_TE_HL_EXTRA_TYPE_TERMINATING_FLOW, PERFETTO_NULL}, \ - (VALUE).id}) +#define PERFETTO_TE_TERMINATING_FLOW(VALUE) \ + PERFETTO_I_TE_EXTRA( \ + PerfettoTeHlExtraFlow, \ + {{PERFETTO_TE_HL_EXTRA_TYPE_TERMINATING_FLOW}, (VALUE).id}) // Flushes the shared memory buffer and makes sure that all the previous events // emitted by this thread are visibile in the central tracing buffer. -#define PERFETTO_TE_FLUSH() \ - PERFETTO_I_TE_EXTRA(PerfettoTeHlExtra, \ - {PERFETTO_TE_HL_EXTRA_TYPE_FLUSH, PERFETTO_NULL}) +#define PERFETTO_TE_FLUSH() \ + PERFETTO_I_TE_EXTRA(PerfettoTeHlExtra, {PERFETTO_TE_HL_EXTRA_TYPE_FLUSH}) // Turns off interning for event names. -#define PERFETTO_TE_NO_INTERN() \ - PERFETTO_I_TE_EXTRA(PerfettoTeHlExtra, \ - {PERFETTO_TE_HL_EXTRA_TYPE_NO_INTERN, PERFETTO_NULL}) +#define PERFETTO_TE_NO_INTERN() \ + PERFETTO_I_TE_EXTRA(PerfettoTeHlExtra, {PERFETTO_TE_HL_EXTRA_TYPE_NO_INTERN}) + +// Adds some proto fields to the event. The arguments should use the +// PERFETTO_TE_PROTO_FIELD_* macros and should be fields of the +// perfetto.protos.TrackEvent protobuf message. +#define PERFETTO_TE_PROTO_FIELDS(...) \ + PERFETTO_I_TE_EXTRA( \ + PerfettoTeHlExtraProtoFields, \ + {{PERFETTO_TE_HL_EXTRA_TYPE_PROTO_FIELDS}, \ + PERFETTO_I_TE_COMPOUND_LITERAL_ARRAY(struct PerfettoTeHlProtoField*, \ + {__VA_ARGS__, PERFETTO_NULL})}) // ---------------------------------- // The main PERFETTO_TE tracing macro @@ -293,8 +336,8 @@ static inline void PerfettoTeHlCall(struct PerfettoTeCategoryImpl* cat, // * PERFETTO_TE_SLICE_END() // * PERFETTO_TE_INSTANT() // * PERFETTO_TE_COUNTER() -// * `...`: One or more (up to 4) macro parameters from the above list that -// specify the data to be traced. +// * `...`: One or more macro parameters from the above list that specify the +// data to be traced. // // Examples: // @@ -307,14 +350,78 @@ static inline void PerfettoTeHlCall(struct PerfettoTeCategoryImpl* cat, // PERFETTO_TE(PERFETTO_TE_DYNAMIC_CATEGORY, PERFETTO_TE_INSTANT("instant"), // PERFETTO_TE_DYNAMIC_CATEGORY_STRING("category")); // -#define PERFETTO_TE(CAT, ...) \ - do { \ - if (PERFETTO_UNLIKELY(PERFETTO_ATOMIC_LOAD_EXPLICIT( \ - (CAT).enabled, PERFETTO_MEMORY_ORDER_RELAXED))) { \ - PERFETTO_I_TE_STATIC_ASSERT_NUM_PARAMS(__VA_ARGS__); \ - PerfettoTeHlCall((CAT).impl, \ - PERFETTO_I_TE_HL_MACRO_PARAMS(__VA_ARGS__)); \ - } \ - } while (0) +#define PERFETTO_TE(CAT, ...) PERFETTO_I_TE_IMPL(CAT, __VA_ARGS__) + +#ifdef __cplusplus + +// Begins a slice named `const char* NAME` on the current thread track. +// +// This is supposed to be used with PERFETTO_TE_SCOPED(). The implementation is +// identical to PERFETTO_TE_SLICE_BEGIN(): this has a different name to +// highlight the fact that PERFETTO_TE_SCOPED() also adds a +// PERFETTO_TE_SLICE_END(). +#define PERFETTO_TE_SLICE(NAME) \ + PERFETTO_I_TE_HL_MACRO_NAME_AND_TYPE(NAME, PERFETTO_TE_TYPE_SLICE_BEGIN) + +namespace perfetto::internal { +template +class TeCleanup { + public: + explicit TeCleanup(F&& f) PERFETTO_ALWAYS_INLINE : f_(std::forward(f)) {} + + ~TeCleanup() PERFETTO_ALWAYS_INLINE { f_(); } + + private: + TeCleanup(const TeCleanup&) = delete; + TeCleanup(TeCleanup&&) = delete; + TeCleanup& operator=(const TeCleanup&) = delete; + TeCleanup& operator=(TeCleanup&&) = delete; + F f_; +}; + +template +TeCleanup MakeTeCleanup(F&& f) { + return TeCleanup(std::forward(f)); +} + +} // namespace perfetto::internal + +// ------------------------ +// PERFETTO_TE_SCOPED macro +// ------------------------ +// +// Emits an event immediately and a PERFETTO_TE_SLICE_END event when the current +// scope terminates. +// +// All the extra params are added only to the event emitted immediately, not to +// the END event. +// +// TRACK params are not supported. +// +// This +// { +// PERFETTO_TE_SCOPED(category, PERFETTO_TE_SLICE("name"), ...); +// ... +// } +// is equivalent to +// { +// PERFETTO_TE(category, PERFETTO_TE_SLICE_BEGIN("name"), ...); +// ... +// PERFETTO_TE(category, PERFETTO_TE_SLICE_END()); +// } +// +// Examples: +// +// PERFETTO_TE_SCOPED(category, PERFETTO_TE_SLICE("name")); +// PERFETTO_TE_SCOPED(category, PERFETTO_TE_SLICE("name"), +// PERFETTO_TE_ARG_UINT64("count", 42)); +// +#define PERFETTO_TE_SCOPED(CAT, ...) \ + auto PERFETTO_I_TE_UID(perfetto_i_te_cleanup) = \ + (PERFETTO_I_TE_IMPL(CAT, __VA_ARGS__), \ + perfetto::internal::MakeTeCleanup( \ + [&] { PERFETTO_TE(CAT, PERFETTO_TE_SLICE_END()); })) + +#endif // __cplusplus #endif // INCLUDE_PERFETTO_PUBLIC_TE_MACROS_H_ diff --git a/include/perfetto/trace_processor/BUILD.gn b/include/perfetto/trace_processor/BUILD.gn index d0dae7f48e..8c260542f3 100644 --- a/include/perfetto/trace_processor/BUILD.gn +++ b/include/perfetto/trace_processor/BUILD.gn @@ -17,7 +17,6 @@ source_set("trace_processor") { "iterator.h", "metatrace_config.h", "read_trace.h", - "ref_counted.h", "trace_processor.h", ] public_deps = [ @@ -28,6 +27,7 @@ source_set("trace_processor") { source_set("storage") { sources = [ + "ref_counted.h", "trace_blob.h", "trace_blob_view.h", "trace_processor_storage.h", diff --git a/include/perfetto/trace_processor/read_trace.h b/include/perfetto/trace_processor/read_trace.h index 415d7d2ded..1b8ab12e22 100644 --- a/include/perfetto/trace_processor/read_trace.h +++ b/include/perfetto/trace_processor/read_trace.h @@ -32,7 +32,8 @@ class TraceProcessor; util::Status PERFETTO_EXPORT_COMPONENT ReadTrace( TraceProcessor* tp, const char* filename, - const std::function& progress_callback = {}); + const std::function& progress_callback = + [](uint64_t) {}); util::Status PERFETTO_EXPORT_COMPONENT DecompressTrace(const uint8_t* data, size_t size, std::vector* output); diff --git a/include/perfetto/trace_processor/ref_counted.h b/include/perfetto/trace_processor/ref_counted.h index bbe2c67c92..9a693998d1 100644 --- a/include/perfetto/trace_processor/ref_counted.h +++ b/include/perfetto/trace_processor/ref_counted.h @@ -144,8 +144,8 @@ class RefPtr { return !(*this == rhs); } - bool operator==(nullptr_t) const noexcept { return ptr_ == nullptr; } - bool operator!=(nullptr_t) const noexcept { return ptr_ != nullptr; } + bool operator==(std::nullptr_t) const noexcept { return ptr_ == nullptr; } + bool operator!=(std::nullptr_t) const noexcept { return ptr_ != nullptr; } T* get() const { return ptr_; } T* operator->() const { return ptr_; } diff --git a/include/perfetto/tracing/internal/data_source_internal.h b/include/perfetto/tracing/internal/data_source_internal.h index 281bd9e051..a62e9091a3 100644 --- a/include/perfetto/tracing/internal/data_source_internal.h +++ b/include/perfetto/tracing/internal/data_source_internal.h @@ -112,6 +112,10 @@ struct DataSourceState { // second time. bool async_stop_in_progress = false; + // Whether this data source instance should call NotifyDataSourceStopped() + // when it's stopped. + bool will_notify_on_stop = false; + // This lock is not held to implement Trace() and it's used only if the trace // code wants to access its own data source state. // This is to prevent that accessing the data source on an arbitrary embedder diff --git a/include/perfetto/tracing/internal/track_event_data_source.h b/include/perfetto/tracing/internal/track_event_data_source.h index 6dcb7fe468..70b0dee57d 100644 --- a/include/perfetto/tracing/internal/track_event_data_source.h +++ b/include/perfetto/tracing/internal/track_event_data_source.h @@ -76,7 +76,8 @@ template <> struct TraceTimestampTraits { static inline TraceTimestamp ConvertTimestampToTraceTimeNs( const uint64_t& timestamp) { - return {static_cast(internal::TrackEventInternal::GetClockId()), timestamp}; + return {static_cast(internal::TrackEventInternal::GetClockId()), + timestamp}; } }; @@ -108,14 +109,15 @@ static constexpr bool IsValidNormalTrack() { // Because the user can use arbitrary timestamp types, we can't compare against // any known base type here. Instead, we check that a track or a trace lambda // isn't being interpreted as a timestamp. -template >::ConvertTimestampToTraceTimeNs(std::declval())), - typename NotTrackCheck = - typename std::enable_if()>::type, - typename NotLambdaCheck = - typename std::enable_if()>::type> +template < + typename T, + typename CanBeConvertedToNsCheck = + decltype(::perfetto::TraceTimestampTraits>::ConvertTimestampToTraceTimeNs(std::declval())), + typename NotTrackCheck = + typename std::enable_if()>::type, + typename NotLambdaCheck = + typename std::enable_if()>::type> static constexpr bool IsValidTimestamp() { return true; } @@ -328,14 +330,13 @@ class TrackEventDataSource } static void Flush() { - Base::template Trace([](typename Base::TraceContext ctx) { ctx.Flush(); }); + Base::Trace([](typename Base::TraceContext ctx) { ctx.Flush(); }); } // Determine if *any* tracing category is enabled. static bool IsEnabled() { bool enabled = false; - Base::template CallIfEnabled( - [&](uint32_t /*instances*/) { enabled = true; }); + Base::CallIfEnabled([&](uint32_t /*instances*/) { enabled = true; }); return enabled; } @@ -349,7 +350,7 @@ class TrackEventDataSource static bool IsDynamicCategoryEnabled( const DynamicCategory& dynamic_category) { bool enabled = false; - Base::template Trace([&](typename Base::TraceContext ctx) { + Base::Trace([&](typename Base::TraceContext ctx) { enabled = enabled || IsDynamicCategoryEnabled(&ctx, dynamic_category); }); return enabled; @@ -496,7 +497,7 @@ class TrackEventDataSource const protos::gen::TrackDescriptor& desc) { PERFETTO_DCHECK(track.uuid == desc.uuid()); TrackRegistry::Get()->UpdateTrack(track, desc.SerializeAsString()); - Base::template Trace([&](typename Base::TraceContext ctx) { + Base::Trace([&](typename Base::TraceContext ctx) { TrackEventInternal::WriteTrackDescriptor( track, ctx.tls_inst_->trace_writer.get(), ctx.GetIncrementalState(), *ctx.GetCustomTlsState(), TrackEventInternal::GetTraceTime()); @@ -1026,7 +1027,7 @@ class TrackEventDataSource Lambda lambda) PERFETTO_ALWAYS_INLINE { using CatTraits = CategoryTraits; if (CatTraits::kIsDynamic) { - Base::template TraceWithInstances(instances, std::move(lambda)); + Base::TraceWithInstances(instances, std::move(lambda)); } else { Base::template TraceWithInstances( instances, std::move(lambda), {CatTraits::GetStaticIndex(category)}); diff --git a/include/perfetto/tracing/internal/track_event_macros.h b/include/perfetto/tracing/internal/track_event_macros.h index 2c2ddb42ed..68da366093 100644 --- a/include/perfetto/tracing/internal/track_event_macros.h +++ b/include/perfetto/tracing/internal/track_event_macros.h @@ -170,7 +170,9 @@ /* that the scoped event is exactly ONE line and can't escape the */ \ /* scope if used in a single line if statement. */ \ EventFinalizer(...) {} \ - ~EventFinalizer() { TRACE_EVENT_END(category); } \ + ~EventFinalizer() { \ + TRACE_EVENT_END(category); \ + } \ \ EventFinalizer(const EventFinalizer&) = delete; \ inline EventFinalizer& operator=(const EventFinalizer&) = delete; \ diff --git a/include/perfetto/tracing/tracing.h b/include/perfetto/tracing/tracing.h index 8da3c72a5e..6f28e8da0a 100644 --- a/include/perfetto/tracing/tracing.h +++ b/include/perfetto/tracing/tracing.h @@ -503,8 +503,8 @@ class PERFETTO_EXPORT_COMPONENT StartupTracingSession { virtual void AbortBlocking() = 0; }; -PERFETTO_ALWAYS_INLINE inline std::unique_ptr -Tracing::NewTrace(BackendType backend) { +PERFETTO_ALWAYS_INLINE inline std::unique_ptr Tracing::NewTrace( + BackendType backend) { // This code is inlined to allow dead-code elimination for unused consumer // implementation. The logic behind it is the following: // Nothing other than the code below references the GetInstance() method diff --git a/include/perfetto/tracing/track_event.h b/include/perfetto/tracing/track_event.h index 872a29f269..8db70a62df 100644 --- a/include/perfetto/tracing/track_event.h +++ b/include/perfetto/tracing/track_event.h @@ -133,7 +133,9 @@ // Deprecated; see perfetto::Category(). #define PERFETTO_CATEGORY(name) \ - ::perfetto::Category { #name } + ::perfetto::Category { \ + #name \ + } // Internal helpers for determining if a given category is defined at build or // runtime. diff --git a/infra/ci/Makefile b/infra/ci/Makefile index 09c57a0ab5..dd7e7a64f3 100644 --- a/infra/ci/Makefile +++ b/infra/ci/Makefile @@ -71,17 +71,18 @@ clean: gcloud compute --quiet --project=${PROJECT} \ instance-templates create ${GCE_TEMPLATE} \ --machine-type=${GCE_VM_TYPE} \ - --network=projects/perfetto-ci/global/networks/default \ + --network=projects/${PROJECT}/global/networks/default \ --network-tier=PREMIUM \ - --metadata='startup-script-url=${GCE_STARTUP_SCRIPT},num-workers=${NUM_WORKERS_PER_VM},google-logging-enabled=true' \ + --metadata='startup-script-url=${GCE_STARTUP_SCRIPT},num-workers=${NUM_WORKERS_PER_VM},sandbox-img=${SANDBOX_IMG},worker-img=${WORKER_IMG},google-logging-enabled=true,enable-oslogin=TRUE' \ --maintenance-policy=MIGRATE \ - --service-account=gce-ci-worker@perfetto-ci.iam.gserviceaccount.com \ + --service-account=gce-ci-worker@${PROJECT}.iam.gserviceaccount.com \ --scopes=${GCE_SCOPES} \ --image=cos-85-13310-1209-10 \ --image-project=cos-cloud \ --boot-disk-size=100GB \ --boot-disk-type=pd-ssd \ --boot-disk-device-name=ci-worker-template \ + --local-ssd=interface=NVME \ --local-ssd=interface=NVME touch $@ @@ -96,29 +97,37 @@ deploy-frontend: .PHONY: restart-workers restart-workers: stop-workers start-workers -define start-workers-for-zone +define start-workers-for-region gcloud compute --project=${PROJECT} \ instance-groups managed create ${GCE_GROUP_NAME}-$1 \ - --zone=$1 \ + --region=$1 \ --base-instance-name=ci-$1 \ --template=ci-worker-template \ - --size=${NUM_VMS} - + --size=1 +gcloud compute --quiet --project=$(PROJECT) \ + instance-groups managed set-autoscaling ${GCE_GROUP_NAME}-$1 \ + --region=$1 \ + --min-num-replicas=0 \ + --max-num-replicas=${MAX_VMS_PER_REGION} \ + --cool-down-period=1800 \ + --stackdriver-metric-filter="resource.type = \"global\"" \ + --update-stackdriver-metric="custom.googleapis.com/$(PROJECT)/ci_job_queue_len" \ + --stackdriver-metric-single-instance-assignment=${NUM_WORKERS_PER_VM} endef .PHONY: start-workers start-workers: .deps/gce-template - $(foreach zone,$(GCE_ZONES),$(call start-workers-for-zone,$(zone))) + $(foreach region,$(GCE_REGIONS),$(call start-workers-for-region,$(region))) -define stop-workers-for-zone +define stop-workers-for-region gcloud compute --quiet --project=${PROJECT} \ - instance-groups managed delete ${GCE_GROUP_NAME}-$1 --zone=$1 || true + instance-groups managed delete ${GCE_GROUP_NAME}-$1 --region=$1 || true endef .PHONY: stop-workers stop-workers: - $(foreach zone,$(GCE_ZONES),$(call stop-workers-for-zone,$(zone))) + $(foreach region,$(GCE_REGIONS),$(call stop-workers-for-region,$(region))) # These are for testing only, start an individual VM. Use start-group for # production. diff --git a/infra/ci/common_utils.py b/infra/ci/common_utils.py index 61d600c181..90fdc745c0 100644 --- a/infra/ci/common_utils.py +++ b/infra/ci/common_utils.py @@ -12,26 +12,43 @@ # See the License for the specific language governing permissions and # limitations under the License. +import asyncio +import concurrent.futures +import google.auth +import google.auth.transport.requests import json -import httplib2 -import os import logging -import threading +import os +import requests +from base64 import b64encode from datetime import datetime -from oauth2client.client import GoogleCredentials from config import PROJECT -tls = threading.local() +# Thread pool for making http requests asynchronosly. +thread_pool = concurrent.futures.ThreadPoolExecutor(max_workers=8) # Caller has to initialize this SCOPES = [] +cached_gerrit_creds = None +cached_oauth2_creds = None class ConcurrentModificationError(Exception): pass +def get_access_token(): + global cached_oauth2_creds + creds = cached_oauth2_creds + if creds is None or not creds.valid or creds.expired: + creds, _project = google.auth.default(scopes=SCOPES) + request = google.auth.transport.requests.Request() + creds.refresh(request) + cached_oauth2_creds = creds + return creds.token + + def get_gerrit_credentials(): '''Retrieve the credentials used to authenticate Gerrit requests @@ -41,18 +58,28 @@ def get_gerrit_credentials(): user: typically looks like git-user.gmail.com. gitcookie: is the password after the = token. ''' - body = {'query': {'kind': [{'name': 'GerritAuth'}]}} - res = req( - 'POST', - 'https://datastore.googleapis.com/v1/projects/%s:runQuery' % PROJECT, - body=body) - auth = res['batch']['entityResults'][0]['entity']['properties'] - user = auth['user']['stringValue'] - gitcookie = auth['gitcookie']['stringValue'] - return user, gitcookie - - -def req(method, uri, body=None, req_etag=False, etag=None, gerrit=False): + global cached_gerrit_creds + if cached_gerrit_creds is None: + body = {'query': {'kind': [{'name': 'GerritAuth'}]}} + res = req( + 'POST', + 'https://datastore.googleapis.com/v1/projects/%s:runQuery' % PROJECT, + body=body) + auth = res['batch']['entityResults'][0]['entity']['properties'] + user = auth['user']['stringValue'] + gitcookie = auth['gitcookie']['stringValue'] + cached_gerrit_creds = user, gitcookie + return cached_gerrit_creds + + +async def req_async(method, url, body=None, gerrit=False): + loop = asyncio.get_running_loop() + # run_in_executor cannot take kwargs, we need to stick with order. + return await loop.run_in_executor(thread_pool, req, method, url, body, gerrit, + False, None) + + +def req(method, url, body=None, gerrit=False, req_etag=False, etag=None): '''Helper function to handle authenticated HTTP requests. Cloud API and Gerrit require two different types of authentication and as @@ -62,39 +89,33 @@ def req(method, uri, body=None, req_etag=False, etag=None, gerrit=False): these connections won't be recycled for too long. ''' hdr = {'Content-Type': 'application/json; charset=UTF-8'} - tls_key = 'gerrit_http' if gerrit else 'oauth2_http' - if hasattr(tls, tls_key): - http = getattr(tls, tls_key) - else: - http = httplib2.Http() - setattr(tls, tls_key, http) - if gerrit: - http.add_credentials(*get_gerrit_credentials()) - elif SCOPES: - creds = GoogleCredentials.get_application_default().create_scoped(SCOPES) - creds.authorize(http) - + if gerrit: + creds = '%s:%s' % get_gerrit_credentials() + auth_header = 'Basic ' + b64encode(creds.encode('utf-8')).decode('utf-8') + elif SCOPES: + auth_header = 'Bearer ' + get_access_token() + logging.debug('%s %s [gerrit=%d]', method, url, gerrit) + hdr['Authorization'] = auth_header if req_etag: hdr['X-Firebase-ETag'] = 'true' if etag: hdr['if-match'] = etag body = None if body is None else json.dumps(body) - logging.debug('%s %s', method, uri) - resp, res = http.request(uri, method=method, headers=hdr, body=body) - if resp.status == 200: - res = res[4:] if gerrit else res # Strip Gerrit XSSI projection chars. - return (json.loads(res), resp['etag']) if req_etag else json.loads(res) - elif resp.status == 412: + resp = requests.request(method, url, headers=hdr, data=body, timeout=60) + res = resp.content.decode('utf-8') + resp_etag = resp.headers.get('etag') + if resp.status_code == 200: + # [4:] is to strip Gerrit XSSI projection prefix. + res = json.loads(res[4:] if gerrit else res) + return (res, resp_etag) if req_etag else res + elif resp.status_code == 412: raise ConcurrentModificationError() else: - delattr(tls, tls_key) raise Exception(resp, res) # Datetime functions to deal with the fact that Javascript expects a trailing # 'Z' (Z == 'Zulu' == UTC) for timestamps. - - def parse_iso_time(time_str): return datetime.strptime(time_str, r'%Y-%m-%dT%H:%M:%SZ') @@ -103,8 +124,15 @@ def utc_now_iso(utcnow=None): return (utcnow or datetime.utcnow()).strftime(r'%Y-%m-%dT%H:%M:%SZ') +def defer(coro): + loop = asyncio.get_event_loop() + task = loop.create_task(coro) + task.set_name(coro.cr_code.co_name) + return task + + def init_logging(): logging.basicConfig( - format='%(asctime)s %(levelname)-8s %(message)s', + format='%(levelname)-8s %(asctime)s %(message)s', level=logging.DEBUG if os.getenv('VERBOSE') else logging.INFO, datefmt=r'%Y-%m-%d %H:%M:%S') diff --git a/infra/ci/config.py b/infra/ci/config.py index bde2077358..a96ebfc6ba 100755 --- a/infra/ci/config.py +++ b/infra/ci/config.py @@ -18,7 +18,8 @@ makefile dumps of the variables. This is so all vars can live in one place. ''' -from __future__ import print_function +import os +import sys # Gerrit config GERRIT_HOST = 'android-review.googlesource.com' @@ -26,32 +27,32 @@ GERRIT_REVIEW_URL = ('https://android-review.googlesource.com/c/' + GERRIT_PROJECT) REPO_URL = 'https://android.googlesource.com/' + GERRIT_PROJECT -GERRIT_POLL_SEC = 15 GERRIT_VOTING_ENABLED = True LOGLEVEL = 'info' # Cloud config (GCE = Google Compute Engine, GAE = Google App Engine) PROJECT = 'perfetto-ci' + GAE_VERSION = 'prod' DB_ROOT = 'https://%s.firebaseio.com' % PROJECT DB = DB_ROOT + '/ci' -SANDBOX_IMG = 'eu.gcr.io/%s/sandbox' % PROJECT -WORKER_IMG = 'eu.gcr.io/%s/worker' % PROJECT +SANDBOX_IMG = 'us-docker.pkg.dev/%s/containers/sandbox' % PROJECT +WORKER_IMG = 'us-docker.pkg.dev/%s/containers/worker' % PROJECT CI_SITE = 'https://ci.perfetto.dev' GCS_ARTIFACTS = 'perfetto-ci-artifacts' -JOB_TIMEOUT_SEC = 45 * 60 -CL_TIMEOUT_SEC = 60 * 60 * 3 +JOB_TIMEOUT_SEC = 45 * 60 # 45 min +CL_TIMEOUT_SEC = 60 * 60 * 3 # 3 hours LOGS_TTL_DAYS = 15 TRUSTED_EMAILS = '^.*@google.com$' -GCE_ZONES = 'us-central1-b us-east1-b us-west1-b' +GCE_REGIONS = 'us-west1' GCE_VM_NAME = 'ci-worker' -GCE_VM_TYPE = 'c2-standard-8' +GCE_VM_TYPE = 'c2d-standard-32' GCE_TEMPLATE = 'ci-worker-template' GCE_GROUP_NAME = 'ci' -NUM_VMS = 3 -NUM_WORKERS_PER_VM = 2 +MAX_VMS_PER_REGION = 8 +NUM_WORKERS_PER_VM = 4 GCE_SCOPES = [ 'https://www.googleapis.com/auth/cloud-platform', @@ -70,46 +71,58 @@ 'non_hermetic_clang_stdlib="libc++" ' 'enable_perfetto_merged_protos_check=true', 'PERFETTO_TEST_SCRIPT': 'test/ci/linux_tests.sh', + 'PERFETTO_INSTALL_BUILD_DEPS_ARGS': '', }, 'linux-clang-x86_64-tsan': { 'PERFETTO_TEST_GN_ARGS': 'is_debug=false is_tsan=true', 'PERFETTO_TEST_SCRIPT': 'test/ci/linux_tests.sh', + 'PERFETTO_INSTALL_BUILD_DEPS_ARGS': '', }, 'linux-clang-x86_64-msan': { 'PERFETTO_TEST_GN_ARGS': 'is_debug=false is_msan=true', 'PERFETTO_TEST_SCRIPT': 'test/ci/linux_tests.sh', + 'PERFETTO_INSTALL_BUILD_DEPS_ARGS': '', }, 'linux-clang-x86_64-asan_lsan': { 'PERFETTO_TEST_GN_ARGS': 'is_debug=false is_asan=true is_lsan=true', 'PERFETTO_TEST_SCRIPT': 'test/ci/linux_tests.sh', + 'PERFETTO_INSTALL_BUILD_DEPS_ARGS': '', }, 'linux-clang-x86-asan_lsan': { 'PERFETTO_TEST_GN_ARGS': 'is_debug=false is_asan=true is_lsan=true ' 'target_cpu="x86"', 'PERFETTO_TEST_SCRIPT': 'test/ci/linux_tests.sh', + 'PERFETTO_INSTALL_BUILD_DEPS_ARGS': '', }, - 'linux-gcc7-x86_64-release': { - 'PERFETTO_TEST_GN_ARGS': 'is_debug=false is_clang=false ' - 'cc="gcc-7" cxx="g++-7"', + 'linux-gcc8-x86_64-release': { + 'PERFETTO_TEST_GN_ARGS': + 'is_debug=false is_clang=false enable_perfetto_grpc=true ' + 'cc="gcc-8" cxx="g++-8"', 'PERFETTO_TEST_SCRIPT': 'test/ci/linux_tests.sh', + 'PERFETTO_INSTALL_BUILD_DEPS_ARGS': '--grpc', }, 'android-clang-arm-release': { 'PERFETTO_TEST_GN_ARGS': 'is_debug=false target_os="android" target_cpu="arm"', 'PERFETTO_TEST_SCRIPT': 'test/ci/android_tests.sh', + 'PERFETTO_INSTALL_BUILD_DEPS_ARGS': + '--android', }, 'linux-clang-x86_64-libfuzzer': { 'PERFETTO_TEST_GN_ARGS': 'is_debug=false is_fuzzer=true is_asan=true', 'PERFETTO_TEST_SCRIPT': 'test/ci/fuzzer_tests.sh', + 'PERFETTO_INSTALL_BUILD_DEPS_ARGS': '', }, 'linux-clang-x86_64-bazel': { 'PERFETTO_TEST_GN_ARGS': '', 'PERFETTO_TEST_SCRIPT': 'test/ci/bazel_tests.sh', + 'PERFETTO_INSTALL_BUILD_DEPS_ARGS': '', }, 'ui-clang-x86_64-release': { 'PERFETTO_TEST_GN_ARGS': 'is_debug=false', 'PERFETTO_TEST_SCRIPT': 'test/ci/ui_tests.sh', + 'PERFETTO_INSTALL_BUILD_DEPS_ARGS': '--ui', }, } diff --git a/infra/ci/controller/.gcloudignore b/infra/ci/controller/.gcloudignore new file mode 100644 index 0000000000..22765ab66c --- /dev/null +++ b/infra/ci/controller/.gcloudignore @@ -0,0 +1,5 @@ +.gcloudignore +.git +.gitignore +__pycache__/ +/setup.cfg diff --git a/infra/ci/controller/Makefile b/infra/ci/controller/Makefile index 337d72e92f..3b4240c8bd 100644 --- a/infra/ci/controller/Makefile +++ b/infra/ci/controller/Makefile @@ -15,12 +15,15 @@ include $(shell python3 ../config.py makefile) -test: lib/.stamp config.py common_utils.py - GOOGLE_APPLICATION_CREDENTIALS=../test-credentials.json \ - python3 `which dev_appserver.py` app.yaml --dev_appserver_log_level ${LOGLEVEL} +.EXPORT_ALL_VARIABLES: +GOOGLE_CLOUD_PROJECT=${PROJECT} -deploy: lib/.stamp config.py common_utils.py - gcloud app deploy -q app.yaml queue.yaml cron.yaml \ +test: config.py common_utils.py + gcloud auth application-default login --impersonate-service-account perfetto-ci@appspot.gserviceaccount.com + python3 `which dev_appserver.py` app.yaml --support_datastore_emulator=False --log_level ${LOGLEVEL} + +deploy: config.py common_utils.py + gcloud app deploy -q app.yaml cron.yaml \ --project ${PROJECT} \ -v ${GAE_VERSION} \ --stop-previous-version @@ -30,11 +33,6 @@ stop: $(shell gcloud app instances list --project ${PROJECT} -v ${GAE_VERSION} -s default | tail -n1 | awk '{print $$3}') \ --project ${PROJECT} -v ${GAE_VERSION} -s default -q -lib/.stamp: - echo "If this fails run sudo apt install python-pip" - python2.7 -m pip install -t lib/ rsa==4.0 oauth2client==4.1.3 httplib2==0.20.4 - touch $@ - config.py: ../config.py cp ../$@ $@ diff --git a/infra/ci/controller/app.yaml b/infra/ci/controller/app.yaml index ae1117bee5..2841790502 100644 --- a/infra/ci/controller/app.yaml +++ b/infra/ci/controller/app.yaml @@ -12,21 +12,21 @@ # See the License for the specific language governing permissions and # limitations under the License. -runtime: python27 -service: controller -api_version: 1 -threadsafe: yes # B4 := 2.4 GHz 1024 MB instance_class: B4 manual_scaling: instances: 1 +runtime: python39 +service: controller +entrypoint: gunicorn --workers 1 --threads 16 --max-requests 200 --timeout 600 -b :$PORT main:app + # Login: admin is to avoid exposing the controller to the public and directly # poking at its internals. These endpoints are hit only by the tasks posted # by the controller itself. Everything happens as a continuation of either the # /_ah/start call or the Cron job calls (see cron.yaml). handlers: - url: (/controller/.*)|(/_ah/.*) - script: controller.app + script: auto login: admin diff --git a/infra/ci/controller/cron.yaml b/infra/ci/controller/cron.yaml index 2ffcb131a1..61707b7757 100644 --- a/infra/ci/controller/cron.yaml +++ b/infra/ci/controller/cron.yaml @@ -12,25 +12,27 @@ # See the License for the specific language governing permissions and # limitations under the License. cron: +- description: "Gerrit poller" + target: controller + url: /controller/tick + schedule: every 1 minutes + - description: "Run postsubmits" - url: /controller/queue_postsubmit_jobs?branch=main - schedule: every 1 hours target: controller + url: /controller/queue_postsubmit_jobs + schedule: every 1 hours -- description: "Logs cleanup" - url: /controller/delete_expired_logs - schedule: every 24 hours +- description: "Delete stale workers" target: controller + url: /controller/delete_stale_workers + schedule: every 1 hours - description: "Delete stale jobs from the running queue" + target: controller url: /controller/delete_stale_jobs schedule: every 15 minutes - target: controller -# This is unnecessary if everything works fine all the time, because each -# /worker/tick task enqueues the next one. However if anything goes wrong the -# chain would break. We use this cron job to re-kick it if this happens. -- description: "Gerrit poller failsafe" - url: /controller/tick - schedule: every 5 minutes +- description: "Logs cleanup" target: controller + url: /controller/delete_expired_logs + schedule: every 15 minutes diff --git a/infra/ci/controller/index.yaml b/infra/ci/controller/index.yaml index bdce6e0cbb..bb224b5631 100644 --- a/infra/ci/controller/index.yaml +++ b/infra/ci/controller/index.yaml @@ -15,8 +15,10 @@ indexes: # AUTOGENERATED -# This index.yaml is automatically updated whenever the Cloud Datastore -# emulator detects that a new type of query is run. If you want to manage the -# index.yaml file manually, remove the "# AUTOGENERATED" marker line above. -# If you want to manage some indexes manually, move them above the marker line. - +# This index.yaml is automatically updated whenever the dev_appserver +# detects that a new type of query is run. If you want to manage the +# index.yaml file manually, remove the above marker line (the line +# saying "# AUTOGENERATED"). If you want to manage some indexes +# manually, move them above the marker line. The index.yaml file is +# automatically uploaded to the admin console when you next deploy +# your application using appcfg.py. diff --git a/infra/ci/controller/controller.py b/infra/ci/controller/main.py similarity index 54% rename from infra/ci/controller/controller.py rename to infra/ci/controller/main.py index fed3b01b69..a61de7ab71 100644 --- a/infra/ci/controller/controller.py +++ b/infra/ci/controller/main.py @@ -12,21 +12,19 @@ # See the License for the specific language governing permissions and # limitations under the License. +import asyncio +import flask import logging import re -import time -import urllib +import urllib.parse from datetime import datetime, timedelta -from google.appengine.api import taskqueue - -import webapp2 - -from common_utils import req, utc_now_iso, parse_iso_time, SCOPES -from config import DB, GERRIT_HOST, GERRIT_PROJECT, GERRIT_POLL_SEC, PROJECT +from common_utils import init_logging, defer, req_async, utc_now_iso, parse_iso_time, SCOPES +from config import DB, GERRIT_HOST, GERRIT_PROJECT, PROJECT from config import CI_SITE, GERRIT_VOTING_ENABLED, JOB_CONFIGS, LOGS_TTL_DAYS from config import TRUSTED_EMAILS, GCS_ARTIFACTS, JOB_TIMEOUT_SEC from config import CL_TIMEOUT_SEC +from functools import wraps from stackdriver_metrics import STACKDRIVER_METRICS STACKDRIVER_API = 'https://monitoring.googleapis.com/v3/projects/%s' % PROJECT @@ -37,108 +35,139 @@ SCOPES.append('https://www.googleapis.com/auth/monitoring') SCOPES.append('https://www.googleapis.com/auth/monitoring.write') -last_tick = 0 +app = flask.Flask(__name__) + +is_handling_route = {} # ------------------------------------------------------------------------------ # Misc utility functions # ------------------------------------------------------------------------------ -def defer(action, **kwargs): - '''Appends a task to the deferred queue. +def is_trusted(email): + return re.match(TRUSTED_EMAILS, email) - Each task will become a new HTTP request made by the AppEngine service. - This pattern is used extensively here for several reasons: - - Auditability in logs: it's easier to scrape logs and debug. - - Stability: an exception fails only the current task not the whole function. - - Reliability: The AppEngine runtime will retry failed tasks with exponential - backoff. - - Performance: tasks are run concurrently, which is quite important given that - most of them are bound by HTTP latency to Gerrit of Firebase. - ''' - taskqueue.add( - queue_name='deferred-jobs', - url='/controller/' + action, - params=kwargs, - method='GET') +def no_concurrency(f): + route_name = f.__name__ + is_handling_route[route_name] = False -def create_stackdriver_metric_definitions(): - logging.info('Creating Stackdriver metric definitions') - for name, metric in STACKDRIVER_METRICS.iteritems(): - logging.info('Creating metric %s', name) - req('POST', STACKDRIVER_API + '/metricDescriptors', body=metric) + @wraps(f) + async def decorated_function(*args, **kwargs): + if is_handling_route[route_name]: + return flask.abort( + 423, description='Handler %s already running' % route_name) + is_handling_route[route_name] = True + try: + return await f(*args, **kwargs) + finally: + is_handling_route[route_name] = False + return decorated_function -def write_metrics(metric_dict): - now = utc_now_iso() - desc = {'timeSeries': []} - for key, spec in metric_dict.iteritems(): - desc['timeSeries'] += [{ - 'metric': { - 'type': STACKDRIVER_METRICS[key]['type'], - 'labels': spec.get('l', {}) - }, - 'resource': { - 'type': 'global' - }, - 'points': [{ - 'interval': { - 'endTime': now - }, - 'value': { - 'int64Value': str(spec['v']) - } - }] - }] - try: - req('POST', STACKDRIVER_API + '/timeSeries', body=desc) - except Exception as e: - # Metric updates can easily fail due to Stackdriver API limitations. - msg = str(e) - if 'written more frequently than the maximum sampling' not in msg: - logging.error('Metrics update failed: %s', msg) +# ------------------------------------------------------------------------------ +# HTTP handlers +# ------------------------------------------------------------------------------ -def is_trusted(email): - return re.match(TRUSTED_EMAILS, email) +@app.route('/_ah/start', methods=['GET', 'POST']) +async def http_start(): + init_logging() + await create_stackdriver_metric_definitions() + return 'OK ' + datetime.now().isoformat() -# ------------------------------------------------------------------------------ -# Deferred job handlers -# ------------------------------------------------------------------------------ +@app.route('/controller/tick', methods=['GET', 'POST']) +@no_concurrency +async def http_tick(): + # The tick is invoked by cron.yaml every 1 minute, it doesn't allow sub-minute + # jobs. Here we want to poll every 15 seconds to be more responsive. So every + # tick keeps repeating the polling for a minute. + deadline = datetime.now() + timedelta(seconds=55) + while datetime.now() < deadline: + await check_new_cls() + await check_pending_cls() + await update_queue_metrics() + asyncio.sleep(15) + return 'OK ' + datetime.now().isoformat() -def start(handler): - create_stackdriver_metric_definitions() - tick(handler) +@app.route('/controller/queue_postsubmit_jobs', methods=['GET', 'POST']) +@no_concurrency +async def http_queue_postsubmit_jobs(): + await queue_postsubmit_jobs('main') + return 'OK ' + datetime.now().isoformat() -def tick(handler): - global last_tick - now = time.time() - # Avoid avalanching effects due to the failsafe tick job in cron.yaml. - if now - last_tick < GERRIT_POLL_SEC - 1: - return - taskqueue.add( - url='/controller/tick', queue_name='tick', countdown=GERRIT_POLL_SEC) - defer('check_new_cls') - defer('check_pending_cls') - defer('update_queue_metrics') - last_tick = now + +@app.route('/controller/delete_stale_jobs', methods=['GET', 'POST']) +@no_concurrency +async def http_delete_stale_jobs(): + await delete_stale_jobs() + return 'OK ' + datetime.now().isoformat() + + +@app.route('/controller/delete_stale_workers', methods=['GET', 'POST']) +@no_concurrency +async def http_delete_stale_workers(): + await delete_stale_workers() + return 'OK ' + datetime.now().isoformat() + + +@app.route('/controller/delete_expired_logs', methods=['GET', 'POST']) +@no_concurrency +async def http_delete_expired_logs(): + await delete_expired_logs(LOGS_TTL_DAYS) + return 'OK ' + datetime.now().isoformat() + + +# Enddpoints below are only for manual testing & mainteinance. + + +@app.route( + '/controller/delete_expired_logs/', methods=['GET', 'POST']) +async def http_delete_expired_logs_ttl(ttl_days): + await delete_expired_logs(ttl_days) + return 'OK ' + datetime.now().isoformat() + + +@app.route('/controller/delete_job_logs/', methods=['GET', 'POST']) +async def http_delete_job_logs(job_id): + await delete_job_logs(job_id) + return 'OK ' + datetime.now().isoformat() + + +# This is to test HTTP timeouts +@app.route('/controller/sleep/', methods=['GET', 'POST']) +async def http_sleep(sleep_sec): + await asyncio.sleep(sleep_sec) + return 'OK ' + datetime.now().isoformat() -def check_new_cls(handler): +@app.route('/controller/sleep_locked/', methods=['GET', 'POST']) +@no_concurrency +async def http_sleep_locked(sleep_sec): + await asyncio.sleep(sleep_sec) + return 'OK ' + datetime.now().isoformat() + + +# ------------------------------------------------------------------------------ +# Deferred jobs +# ------------------------------------------------------------------------------ + + +async def check_new_cls(): ''' Poll for new CLs and asynchronously enqueue jobs for them.''' logging.info('Polling for new Gerrit CLs') date_limit = (datetime.utcnow() - timedelta(days=1)).strftime('%Y-%m-%d') url = 'https://%s/a/changes/' % GERRIT_HOST - url += '?o=CURRENT_REVISION&o=DETAILED_ACCOUNTS&o=LABELS&n=200' + url += '?o=CURRENT_REVISION&o=DETAILED_ACCOUNTS&o=LABELS&n=32' url += '&q=branch:main+project:%s' % GERRIT_PROJECT url += '+is:open+after:%s' % date_limit - resp = req('GET', url, gerrit=True) + resp = await req_async('GET', url, gerrit=True) + tasks = [] for change in (change for change in resp if 'revisions' in change): - rev_hash = change['revisions'].keys()[0] + rev_hash = list(change['revisions'].keys())[0] rev = change['revisions'][rev_hash] owner = rev['uploader']['email'] prs_ready = change['labels'].get('Presubmit-Ready', {}).get('approved', {}) @@ -147,15 +176,16 @@ def check_new_cls(handler): # account or are marked as Presubmit-Verified by a trustd account. if not is_trusted(owner) and not is_trusted(prs_owner): continue - defer( - 'check_new_cl', - cl=str(change['_number']), - patchset=str(rev['_number']), - change_id=change['id'], - rev_hash=rev_hash, - ref=rev['ref'], - owner=rev['uploader']['email'], - wants_vote='1' if prs_ready else '0') + tasks.append( + defer( + check_new_cl( + cl=str(change['_number']), + patchset=str(rev['_number']), + change_id=change['id'], + rev_hash=rev_hash, + ref=rev['ref'], + wants_vote=True if prs_ready else False))) + await asyncio.gather(*tasks) def append_jobs(patch_obj, src, git_ref, now=None): @@ -170,7 +200,7 @@ def append_jobs(patch_obj, src, git_ref, now=None): ''' logging.info('Enqueueing jobs fos cl %s', src) timestamp = (now or datetime.utcnow()).strftime('%Y%m%d%H%M%S') - for cfg_name, env in JOB_CONFIGS.iteritems(): + for cfg_name, env in JOB_CONFIGS.items(): job_id = '%s--%s--%s' % (timestamp, src.replace('/', '-'), cfg_name) logging.info('Enqueueing job %s', job_id) patch_obj['jobs/' + job_id] = { @@ -184,39 +214,37 @@ def append_jobs(patch_obj, src, git_ref, now=None): patch_obj[src]['jobs'][job_id] = 0 -def check_new_cl(handler): +async def check_new_cl(change_id: str, rev_hash: str, cl: str, patchset: str, + ref: str, wants_vote: bool): '''Creates the CL + jobs entries in the DB for the given CL if doesn't exist If exists check if a Presubmit-Ready label has been added and if so updates it with the message + vote. ''' - change_id = handler.request.get('change_id') - rev_hash = handler.request.get('rev_hash') - cl = handler.request.get('cl') - patchset = handler.request.get('patchset') - ref = handler.request.get('ref') - wants_vote = handler.request.get('wants_vote') == '1' - # We want to do two things here: # 1) If the CL doesn't exist (hence vote_prop is None) carry on below and # enqueue jobs for it. # 2) If the CL exists, we don't need to kick new jobs. However, the user # might have addeed a Presubmit-Ready label after we created the CL. In # this case update the |wants_vote| flag and return. - vote_prop = req('GET', '%s/cls/%s-%s/wants_vote.json' % (DB, cl, patchset)) + logging.debug('check_new_cl(%s-%s)', cl, patchset) + vote_prop = await req_async( + 'GET', '%s/cls/%s-%s/wants_vote.json' % (DB, cl, patchset)) if vote_prop is not None: if vote_prop != wants_vote and wants_vote: logging.info('Updating wants_vote flag on %s-%s', cl, patchset) - req('PUT', '%s/cls/%s-%s/wants_vote.json' % (DB, cl, patchset), body=True) + await req_async( + 'PUT', '%s/cls/%s-%s/wants_vote.json' % (DB, cl, patchset), body=True) # If the label is applied after we have finished running all the jobs just # jump straight to the voting. - defer('check_pending_cl', cl_and_ps='%s-%s' % (cl, patchset)) + await check_pending_cl(cl_and_ps='%s-%s' % (cl, patchset)) + logging.debug('check_new_cl(%s-%s): already queued', cl, patchset) return # This is the first time we see this patchset, enqueue jobs for it. # Dequeue jobs for older patchsets, if any. - defer('cancel_older_jobs', cl=cl, patchset=patchset) + await cancel_older_jobs(cl=cl, patchset=patchset) src = 'cls/%s-%s' % (cl, patchset) # Enqueue jobs for the latest patchset. @@ -230,43 +258,52 @@ def check_new_cl(handler): 'wants_vote': wants_vote, } append_jobs(patch_obj, src, ref) - req('PATCH', DB + '.json', body=patch_obj) + logging.debug('check_new_cl(%s-%s): queueing jobs', cl, patchset) + await req_async('PATCH', DB + '.json', body=patch_obj) -def cancel_older_jobs(handler): - cl = handler.request.get('cl') - patchset = handler.request.get('patchset') +async def cancel_older_jobs(cl: str, patchset: str): first_key = '%s-0' % cl last_key = '%s-z' % cl filt = 'orderBy="$key"&startAt="%s"&endAt="%s"' % (first_key, last_key) - cl_objs = req('GET', '%s/cls.json?%s' % (DB, filt)) or {} - for cl_and_ps, cl_obj in cl_objs.iteritems(): + cl_objs = await req_async('GET', '%s/cls.json?%s' % (DB, filt)) or {} + tasks = [] + for cl_and_ps, cl_obj in cl_objs.items(): ps = int(cl_and_ps.split('-')[-1]) if cl_obj.get('time_ended') or ps >= int(patchset): continue logging.info('Cancelling jobs for previous patchset %s', cl_and_ps) - map(lambda x: defer('cancel_job', job_id=x), cl_obj['jobs'].keys()) + for job_id in cl_obj['jobs'].keys(): + tasks.append(defer(cancel_job(job_id=job_id))) + await asyncio.gather(*tasks) -def check_pending_cls(handler): +async def check_pending_cls(): # Check if any pending CL has completed (all jobs are done). If so publish # the comment and vote on the CL. - pending_cls = req('GET', '%s/cls_pending.json' % DB) or {} - for cl_and_ps, _ in pending_cls.iteritems(): - defer('check_pending_cl', cl_and_ps=cl_and_ps) + pending_cls = await req_async('GET', '%s/cls_pending.json' % DB) or {} + tasks = [] + for cl_and_ps, _ in pending_cls.items(): + tasks.append(defer(check_pending_cl(cl_and_ps=cl_and_ps))) + await asyncio.gather(*tasks) -def check_pending_cl(handler): +async def check_pending_cl(cl_and_ps: str): # This function can be called twice on the same CL, e.g., in the case when the # Presubmit-Ready label is applied after we have finished running all the # jobs (we run presubmit regardless, only the voting is conditioned by PR). - cl_and_ps = handler.request.get('cl_and_ps') - cl_obj = req('GET', '%s/cls/%s.json' % (DB, cl_and_ps)) + cl_obj = await req_async('GET', '%s/cls/%s.json' % (DB, cl_and_ps)) all_jobs = cl_obj.get('jobs', {}).keys() pending_jobs = [] + interrupted_jobs = [] for job_id in all_jobs: - job_status = req('GET', '%s/jobs/%s/status.json' % (DB, job_id)) + job_status = await req_async('GET', '%s/jobs/%s/status.json' % (DB, job_id)) pending_jobs += [job_id] if job_status in ('QUEUED', 'STARTED') else [] + interrupted_jobs += [job_id] if job_status in ('INTERRUPTED') else [] + + # Interrupted jobs are due to VMs being shutdown (usually due to a scale-down) + # Automatically re-queue them so they get picked up by some other vm. + await asyncio.gather(*[requeue_job(job_id) for job_id in interrupted_jobs]) if pending_jobs: # If the CL has been pending for too long cancel all its jobs. Upon the next @@ -276,9 +313,11 @@ def check_pending_cl(handler): if age_sec > CL_TIMEOUT_SEC: logging.warning('Canceling %s, it has been pending for too long (%s sec)', cl_and_ps, int(age_sec)) - map(lambda x: defer('cancel_job', job_id=x), pending_jobs) - return + tasks = [defer(cancel_job(job_id)) for job_id in pending_jobs] + await asyncio.gather(*tasks) + if pending_jobs or interrupted_jobs: + return logging.info('All jobs completed for CL %s', cl_and_ps) # Remove the CL from the pending queue and update end time. @@ -286,16 +325,16 @@ def check_pending_cl(handler): 'cls_pending/%s' % cl_and_ps: {}, # = DELETE 'cls/%s/time_ended' % cl_and_ps: cl_obj.get('time_ended', utc_now_iso()), } - req('PATCH', '%s.json' % DB, body=patch_obj) - defer('update_cl_metrics', src='cls/' + cl_and_ps) - map(lambda x: defer('update_job_metrics', job_id=x), all_jobs) + await req_async('PATCH', '%s.json' % DB, body=patch_obj) + await update_cl_metrics(src='cls/' + cl_and_ps) + tasks = [defer(update_job_metrics(job_id)) for job_id in all_jobs] + await asyncio.gather(*tasks) if cl_obj.get('wants_vote'): - defer('comment_and_vote_cl', cl_and_ps=cl_and_ps) + await comment_and_vote_cl(cl_and_ps=cl_and_ps) -def comment_and_vote_cl(handler): - cl_and_ps = handler.request.get('cl_and_ps') - cl_obj = req('GET', '%s/cls/%s.json' % (DB, cl_and_ps)) +async def comment_and_vote_cl(cl_and_ps: str): + cl_obj = await req_async('GET', '%s/cls/%s.json' % (DB, cl_and_ps)) if cl_obj.get('voted'): logging.error('Already voted on CL %s', cl_and_ps) @@ -311,7 +350,7 @@ def comment_and_vote_cl(handler): ui_links = [] cancelled = False for job_id in cl_obj['jobs'].keys(): - job_obj = req('GET', '%s/jobs/%s.json' % (DB, job_id)) + job_obj = await req_async('GET', '%s/jobs/%s.json' % (DB, job_id)) job_config = JOB_CONFIGS.get(job_obj['type'], {}) if job_obj['status'] == 'CANCELLED': cancelled = True @@ -336,7 +375,7 @@ def comment_and_vote_cl(handler): msg += 'FAIL:\n' msg += ''.join([ '- %s/%s (%s)\n' % (log_url, job_id, status) - for (job_id, status) in failed_jobs.iteritems() + for (job_id, status) in failed_jobs.items() ]) if passed_jobs: msg += '#\nPASS:\n' @@ -351,33 +390,31 @@ def comment_and_vote_cl(handler): logging.info('Posting results for CL %s', cl_and_ps) url = 'https://%s/a/changes/%s/revisions/%s/review' % ( GERRIT_HOST, cl_obj['change_id'], cl_obj['revision_id']) - req('POST', url, body=body, gerrit=True) - req('PUT', '%s/cls/%s/voted.json' % (DB, cl_and_ps), body=True) + await req_async('POST', url, body=body, gerrit=True) + await req_async('PUT', '%s/cls/%s/voted.json' % (DB, cl_and_ps), body=True) -def queue_postsubmit_jobs(handler): +async def queue_postsubmit_jobs(branch: str, revision: str = None): '''Creates the jobs entries in the DB for the given branch or revision Can be called in two modes: 1. ?branch=main: Will retrieve the SHA1 of main and call the one below. 2. ?branch=main&rev=deadbeef1234: queues jobs for the given revision. ''' - prj = urllib.quote(GERRIT_PROJECT, '') - branch = handler.request.get('branch') - revision = handler.request.get('revision') - assert branch + prj = urllib.parse.quote(GERRIT_PROJECT, '') + assert (branch) if not revision: # Get the commit SHA1 of the head of the branch. url = 'https://%s/a/projects/%s/branches/%s' % (GERRIT_HOST, prj, branch) - revision = req('GET', url, gerrit=True)['revision'] - assert revision - defer('queue_postsubmit_jobs', branch=branch, revision=revision) + revision = (await req_async('GET', url, gerrit=True))['revision'] + assert (revision) + await queue_postsubmit_jobs(branch=branch, revision=revision) return # Get the committer datetime for the given revision. url = 'https://%s/a/projects/%s/commits/%s' % (GERRIT_HOST, prj, revision) - commit_info = req('GET', url, gerrit=True) + commit_info = await req_async('GET', url, gerrit=True) time_committed = commit_info['committer']['date'].split('.')[0] time_committed = datetime.strptime(time_committed, '%Y-%m-%d %H:%M:%S') @@ -396,30 +433,63 @@ def queue_postsubmit_jobs(handler): } ref = 'refs/heads/' + branch append_jobs(patch_obj, src, ref, now) - req('PATCH', DB + '.json', body=patch_obj) + await req_async('PATCH', DB + '.json', body=patch_obj) + + +async def delete_expired_logs(ttl_days=LOGS_TTL_DAYS): + url = '%s/logs.json?limitToFirst=1000&shallow=true' % (DB) + logs = await req_async('GET', url) or {} + tasks = [] + logging.debug('delete_expired_logs: got %d keys', len(logs.keys())) + for job_id in logs.keys(): + age_days = (datetime.now() - datetime.strptime(job_id[:8], '%Y%m%d')).days + if age_days > ttl_days: + logging.debug('Delete log %s', job_id) + tasks.append(defer(delete_job_logs(job_id=job_id))) + await asyncio.gather(*tasks) -def delete_stale_jobs(handler): +async def delete_stale_jobs(): '''Deletes jobs that are left in the running queue for too long This is usually due to a crash in the VM that handles them. ''' - running_jobs = req('GET', '%s/jobs_running.json?shallow=true' % (DB)) or {} - for job_id in running_jobs.iterkeys(): - job = req('GET', '%s/jobs/%s.json' % (DB, job_id)) + running_jobs = await req_async('GET', '%s/jobs_running.json?shallow=true' % + (DB)) or {} + tasks = [] + for job_id in running_jobs.keys(): + job = await req_async('GET', '%s/jobs/%s.json' % (DB, job_id)) time_started = parse_iso_time(job.get('time_started', utc_now_iso())) age = (datetime.now() - time_started).total_seconds() if age > JOB_TIMEOUT_SEC * 2: - defer('cancel_job', job_id=job_id) + tasks.append(defer(cancel_job(job_id=job_id))) + await asyncio.gather(*tasks) -def cancel_job(handler): +async def delete_stale_workers(): + '''Deletes workers that have been inactive for too long + + This is usually due to a crash in the VM that handles them. + ''' + workers = await req_async('GET', '%s/workers.json' % (DB)) or {} + patch_obj = {} + for worker_id, worker in workers.items(): + last_update = parse_iso_time(worker.get('last_update', utc_now_iso())) + age = (datetime.now() - last_update).total_seconds() + if age > 60 * 60 * 12: + patch_obj['workers/' + worker_id] = {} # DELETE + if len(patch_obj) == 0: + return + logging.info('Purging %d inactive workers', len(patch_obj)) + await req_async('PATCH', DB + '.json', body=patch_obj) + + +async def cancel_job(job_id: str): '''Cancels a job if not completed or failed. This function is racy: workers can complete the queued jobs while we mark them as cancelled. The result of such race is still acceptable.''' - job_id = handler.request.get('job_id') - status = req('GET', '%s/jobs/%s/status.json' % (DB, job_id)) + status = await req_async('GET', '%s/jobs/%s/status.json' % (DB, job_id)) patch_obj = { 'jobs_running/%s' % job_id: {}, # = DELETE, 'jobs_queued/%s' % job_id: {}, # = DELETE, @@ -427,26 +497,34 @@ def cancel_job(handler): if status in ('QUEUED', 'STARTED'): patch_obj['jobs/%s/status' % job_id] = 'CANCELLED' patch_obj['jobs/%s/time_ended' % job_id] = utc_now_iso() - req('PATCH', DB + '.json', body=patch_obj) + await req_async('PATCH', DB + '.json', body=patch_obj) -def delete_expired_logs(handler): - logs = req('GET', '%s/logs.json?shallow=true' % (DB)) or {} - for job_id in logs.iterkeys(): - age_days = (datetime.now() - datetime.strptime(job_id[:8], '%Y%m%d')).days - if age_days > LOGS_TTL_DAYS: - defer('delete_job_logs', job_id=job_id) +async def requeue_job(job_id: str): + '''Re-queues a job that was previously interrupted due to a VM shutdown.''' + logging.info('Requeuing interrupted job %s', job_id) + patch_obj = { + 'jobs_running/%s' % job_id: {}, # = DELETE, + 'jobs_queued/%s' % job_id: 0, + 'jobs/%s/status' % job_id: 'QUEUED', + 'jobs/%s/time_queued' % job_id: utc_now_iso(), + 'jobs/%s/time_started' % job_id: {}, # = DELETE + 'jobs/%s/time_ended' % job_id: {}, # = DELETE + 'jobs/%s/worker' % job_id: {}, # = DELETE + } + await req_async('PATCH', DB + '.json', body=patch_obj) -def delete_job_logs(handler): - req('DELETE', '%s/logs/%s.json' % (DB, handler.request.get('job_id'))) +async def delete_job_logs(job_id: str): + await req_async('DELETE', + '%s/logs/%s.json?writeSizeLimit=unlimited' % (DB, job_id)) -def update_cl_metrics(handler): - cl_obj = req('GET', '%s/%s.json' % (DB, handler.request.get('src'))) +async def update_cl_metrics(src: str): + cl_obj = await req_async('GET', '%s/%s.json' % (DB, src)) t_queued = parse_iso_time(cl_obj['time_queued']) t_ended = parse_iso_time(cl_obj['time_ended']) - write_metrics({ + await write_metrics({ 'ci_cl_completion_time': { 'l': {}, 'v': int((t_ended - t_queued).total_seconds()) @@ -454,9 +532,8 @@ def update_cl_metrics(handler): }) -def update_job_metrics(handler): - job_id = handler.request.get('job_id') - job = req('GET', '%s/jobs/%s.json' % (DB, job_id)) +async def update_job_metrics(job_id: str): + job = await req_async('GET', '%s/jobs/%s.json' % (DB, job_id)) metrics = {} if 'time_queued' in job and 'time_started' in job: t_queued = parse_iso_time(job['time_queued']) @@ -477,47 +554,49 @@ def update_job_metrics(handler): 'v': int((t_ended - t_started).total_seconds()) } if metrics: - write_metrics(metrics) + await write_metrics(metrics) -def update_queue_metrics(handler): +async def update_queue_metrics(): # Update the stackdriver metric that will drive the autoscaler. - queued = req('GET', DB + '/jobs_queued.json?shallow=true') or {} - running = req('GET', DB + '/jobs_running.json?shallow=true') or {} - write_metrics({'ci_job_queue_len': {'v': len(queued) + len(running)}}) - - -class ControllerHandler(webapp2.RequestHandler): - ACTIONS = { - 'start': start, - 'tick': tick, - 'check_pending_cls': check_pending_cls, - 'check_pending_cl': check_pending_cl, - 'check_new_cls': check_new_cls, - 'check_new_cl': check_new_cl, - 'comment_and_vote_cl': comment_and_vote_cl, - 'cancel_older_jobs': cancel_older_jobs, - 'queue_postsubmit_jobs': queue_postsubmit_jobs, - 'update_job_metrics': update_job_metrics, - 'update_queue_metrics': update_queue_metrics, - 'update_cl_metrics': update_cl_metrics, - 'delete_expired_logs': delete_expired_logs, - 'delete_job_logs': delete_job_logs, - 'delete_stale_jobs': delete_stale_jobs, - 'cancel_job': cancel_job, - } + queued = await req_async('GET', DB + '/jobs_queued.json?shallow=true') or {} + running = await req_async('GET', DB + '/jobs_running.json?shallow=true') or {} + logging.debug('ci_job_queue_len: %d + %d', len(queued), len(running)) + await write_metrics({'ci_job_queue_len': {'v': len(queued) + len(running)}}) - def handle(self, action): - if action in ControllerHandler.ACTIONS: - return ControllerHandler.ACTIONS[action](self) - raise Exception('Invalid request %s' % action) - get = handle - post = handle +async def create_stackdriver_metric_definitions(): + logging.info('Creating Stackdriver metric definitions') + for name, metric in STACKDRIVER_METRICS.items(): + logging.info('Creating metric %s', name) + await req_async('POST', STACKDRIVER_API + '/metricDescriptors', body=metric) -app = webapp2.WSGIApplication([ - ('/_ah/(start)', ControllerHandler), - (r'/controller/(\w+)', ControllerHandler), -], - debug=True) +async def write_metrics(metric_dict): + now = utc_now_iso() + desc = {'timeSeries': []} + for key, spec in metric_dict.items(): + desc['timeSeries'] += [{ + 'metric': { + 'type': STACKDRIVER_METRICS[key]['type'], + 'labels': spec.get('l', {}) + }, + 'resource': { + 'type': 'global' + }, + 'points': [{ + 'interval': { + 'endTime': now + }, + 'value': { + 'int64Value': str(spec['v']) + } + }] + }] + try: + await req_async('POST', STACKDRIVER_API + '/timeSeries', body=desc) + except Exception as e: + # Metric updates can easily fail due to Stackdriver API limitations. + msg = str(e) + if 'written more frequently than the maximum sampling' not in msg: + logging.error('Metrics update failed: %s', msg) diff --git a/infra/ci/controller/queue.yaml b/infra/ci/controller/queue.yaml deleted file mode 100644 index 1c5e53e24b..0000000000 --- a/infra/ci/controller/queue.yaml +++ /dev/null @@ -1,28 +0,0 @@ -# Copyright (C) 2019 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -queue: -- name: tick - rate: 12/m - bucket_size: 1 - max_concurrent_requests: 1 - retry_parameters: - task_retry_limit: 1 -- name: deferred-jobs - rate: 50/s - bucket_size: 10 - max_concurrent_requests: 10 - retry_parameters: - task_retry_limit: 3 - min_backoff_seconds: 5 - max_backoff_seconds: 30 \ No newline at end of file diff --git a/infra/ci/controller/requirements.txt b/infra/ci/controller/requirements.txt new file mode 100644 index 0000000000..bd513b23b3 --- /dev/null +++ b/infra/ci/controller/requirements.txt @@ -0,0 +1,7 @@ +Flask[async]==2.2.5 +google-auth +google-cloud +google-cloud-logging +gunicorn +oauth2client +requests diff --git a/infra/ci/controller/stackdriver_metrics.py b/infra/ci/controller/stackdriver_metrics.py index 2fa5fb8bb9..9d3fe47207 100644 --- a/infra/ci/controller/stackdriver_metrics.py +++ b/infra/ci/controller/stackdriver_metrics.py @@ -12,12 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. +from config import PROJECT + STACKDRIVER_METRICS = { 'ci_job_queue_len': { 'name': 'ci_job_queue_len', 'displayName': 'ci_job_queue_len', 'description': 'Length of the CI jobs queue', - 'type': 'custom.googleapis.com/perfetto-ci/ci_job_queue_len', + 'type': 'custom.googleapis.com/%s/ci_job_queue_len' % PROJECT, 'metricKind': 'GAUGE', 'valueType': 'INT64', 'metadata': { @@ -31,7 +33,7 @@ 'name': 'ci_job_queue_time', 'displayName': 'ci_job_queue_time', 'description': 'Queueing time of CI jobs, before they start running', - 'type': 'custom.googleapis.com/perfetto-ci/ci_job_queue_time', + 'type': 'custom.googleapis.com/%s/ci_job_queue_time' % PROJECT, 'metricKind': 'GAUGE', 'valueType': 'INT64', 'unit': 's', @@ -49,7 +51,7 @@ 'name': 'ci_job_run_time', 'displayName': 'ci_job_run_time', 'description': 'Running time of CI jobs', - 'type': 'custom.googleapis.com/perfetto-ci/ci_job_run_time', + 'type': 'custom.googleapis.com/%s/ci_job_run_time' % PROJECT, 'metricKind': 'GAUGE', 'valueType': 'INT64', 'unit': 's', @@ -67,7 +69,7 @@ 'name': 'ci_cl_completion_time', 'displayName': 'ci_cl_completion_time', 'description': 'Time it takes for all jobs of a CL to complete', - 'type': 'custom.googleapis.com/perfetto-ci/ci_cl_completion_time', + 'type': 'custom.googleapis.com/%s/ci_cl_completion_time' % PROJECT, 'metricKind': 'GAUGE', 'valueType': 'INT64', 'unit': 's', diff --git a/infra/ci/frontend/.gcloudignore b/infra/ci/frontend/.gcloudignore new file mode 100644 index 0000000000..22765ab66c --- /dev/null +++ b/infra/ci/frontend/.gcloudignore @@ -0,0 +1,5 @@ +.gcloudignore +.git +.gitignore +__pycache__/ +/setup.cfg diff --git a/infra/ci/frontend/app.yaml b/infra/ci/frontend/app.yaml index fffd024a43..93a95afdd6 100644 --- a/infra/ci/frontend/app.yaml +++ b/infra/ci/frontend/app.yaml @@ -12,9 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -runtime: python27 -api_version: 1 -threadsafe: yes +runtime: python39 service: default handlers: - url: / @@ -26,5 +24,5 @@ handlers: static_dir: static/ secure: always - url: /gerrit/.* - script: frontend.app + script: auto secure: always diff --git a/infra/ci/frontend/frontend.py b/infra/ci/frontend/frontend.py deleted file mode 100644 index c6697cd7bb..0000000000 --- a/infra/ci/frontend/frontend.py +++ /dev/null @@ -1,77 +0,0 @@ -# Copyright (C) 2019 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import logging -import webapp2 -import urllib - -from google.appengine.api import urlfetch -from google.appengine.api import memcache -from config import GERRIT_HOST, GERRIT_PROJECT -''' Makes anonymous GET-only requests to Gerrit. - -Solves the lack of CORS headers from AOSP gerrit.''' - - -def req_cached(url): - '''Used for requests that return immutable data, avoid hitting Gerrit 500''' - resp = memcache.get(url) - if resp is not None: - return 200, resp - result = urlfetch.fetch(url) - if result.status_code == 200: - memcache.add(url, result.content, 3600 * 24) - return result.status_code, result.content - - -class GerritCommitsHandler(webapp2.RequestHandler): - - def get(self, sha1): - project = urllib.quote(GERRIT_PROJECT, '') - url = 'https://%s/projects/%s/commits/%s' % (GERRIT_HOST, project, sha1) - status, content = req_cached(url) - self.response.status_int = status - self.response.write(content[4:]) # 4: -> Strip Gerrit XSSI chars. - - -class GerritLogHandler(webapp2.RequestHandler): - - def get(self, first, second): - url = 'https://%s/%s/+log/%s..%s?format=json' % (GERRIT_HOST.replace( - '-review', ''), GERRIT_PROJECT, first, second) - status, content = req_cached(url) - self.response.status_int = status - self.response.write(content[4:]) # 4: -> Strip Gerrit XSSI chars. - - -class GerritChangesHandler(webapp2.RequestHandler): - - def get(self): - url = 'https://%s/changes/?q=project:%s+' % (GERRIT_HOST, GERRIT_PROJECT) - url += self.request.query_string - result = urlfetch.fetch(url) - self.response.headers['Content-Type'] = 'text/plain' - self.response.status_int = result.status_code - if (result.status_code == 200): - self.response.write(result.content[4:]) # 4: -> Strip Gerrit XSSI chars. - else: - self.response.write('HTTP error %s' % result.status_code) - - -app = webapp2.WSGIApplication([ - ('/gerrit/commits/([a-f0-9]+)', GerritCommitsHandler), - ('/gerrit/log/([a-f0-9]+)..([a-f0-9]+)', GerritLogHandler), - ('/gerrit/changes/', GerritChangesHandler), -], - debug=True) diff --git a/infra/ci/frontend/main.py b/infra/ci/frontend/main.py new file mode 100644 index 0000000000..a6f19f51b6 --- /dev/null +++ b/infra/ci/frontend/main.py @@ -0,0 +1,98 @@ +# Copyright (C) 2019 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import flask +import logging +import os +import re +import requests +import time +import urllib.parse + +from collections import namedtuple +from config import GERRIT_HOST, GERRIT_PROJECT +''' Makes anonymous GET-only requests to Gerrit. + +Solves the lack of CORS headers from AOSP gerrit. +''' + +HASH_RE = re.compile('^[a-f0-9]+$') +CACHE_TTL = 3600 # 1 h +CacheEntry = namedtuple('CacheEntry', ['contents', 'expiration']) + +app = flask.Flask(__name__) + +logging.basicConfig( + format='%(levelname)-8s %(asctime)s %(message)s', + level=logging.DEBUG if os.getenv('VERBOSE') else logging.INFO, + datefmt=r'%Y-%m-%d %H:%M:%S') + +cache = {} + + +def DeleteStaleCacheEntries(): + now = time.time() + for url, entry in list(cache.items()): + if now > entry.expiration: + cache.pop(url, None) + + +def req_cached(url): + '''Used for requests that return immutable data, avoid hitting Gerrit 500''' + DeleteStaleCacheEntries() + entry = cache.get(url) + contents = entry.contents if entry is not None else None + if not contents: + resp = requests.get(url) + if resp.status_code != 200: + err_str = 'http error %d while fetching %s' % (resp.status_code, url) + return resp.status_code, err_str + contents = resp.content.decode('utf-8') + cache[url] = CacheEntry(contents, time.time() + CACHE_TTL) + return contents, 200 + + +@app.route('/gerrit/commits/', methods=['GET', 'POST']) +def commits(sha1): + if not HASH_RE.match(sha1): + return 'Malformed input', 500 + project = urllib.parse.quote(GERRIT_PROJECT, '') + url = 'https://%s/projects/%s/commits/%s' % (GERRIT_HOST, project, sha1) + content, status = req_cached(url) + return content[4:], status # 4: -> Strip Gerrit XSSI chars. + + +@app.route( + '/gerrit/log/..', methods=['GET', 'POST']) +def gerrit_log(first, second): + if not HASH_RE.match(first) or not HASH_RE.match(second): + return 'Malformed input', 500 + url = 'https://%s/%s/+log/%s..%s?format=json' % (GERRIT_HOST.replace( + '-review', ''), GERRIT_PROJECT, first, second) + content, status = req_cached(url) + return content[4:], status # 4: -> Strip Gerrit XSSI chars. + + +@app.route('/gerrit/changes/', methods=['GET', 'POST']) +def gerrit_changes(): + url = 'https://%s/changes/?q=project:%s+' % (GERRIT_HOST, GERRIT_PROJECT) + url += flask.request.query_string.decode('utf-8') + resp = requests.get(url) + hdr = {'Content-Type': 'text/plain'} + status = resp.status_code + if status == 200: + resp = resp.content.decode('utf-8')[4:] # 4: -> Strip Gerrit XSSI chars. + else: + resp = 'HTTP error %s' % status + return resp, status, hdr diff --git a/infra/ci/frontend/requirements.txt b/infra/ci/frontend/requirements.txt new file mode 100644 index 0000000000..ef4e2e209f --- /dev/null +++ b/infra/ci/frontend/requirements.txt @@ -0,0 +1,3 @@ +Flask>=2.2.2 +requests +gunicorn diff --git a/infra/ci/frontend/static/script.js b/infra/ci/frontend/static/script.js index 24591d45dd..017ca6e40f 100644 --- a/infra/ci/frontend/static/script.js +++ b/infra/ci/frontend/static/script.js @@ -18,7 +18,7 @@ // If you add or remove job types, do not forget to fix the colspans below. const JOB_TYPES = [ - { id: 'linux-gcc7-x86_64-release', label: 'rel' }, + { id: 'linux-gcc8-x86_64-release', label: 'rel' }, { id: 'linux-clang-x86_64-debug', label: 'dbg' }, { id: 'linux-clang-x86_64-tsan', label: 'tsan' }, { id: 'linux-clang-x86_64-msan', label: 'msan' }, @@ -199,7 +199,7 @@ var CLsPageRenderer = { m('td[colspan=2]', 'android'), ), m('tr', - m('td', 'gcc7'), + m('td', 'gcc8'), m('td[colspan=7]', 'clang'), m('td[colspan=1]', 'ui'), m('td[colspan=1]', 'clang-arm'), diff --git a/infra/ci/sandbox/Dockerfile b/infra/ci/sandbox/Dockerfile index ebd5539685..8968b312d7 100644 --- a/infra/ci/sandbox/Dockerfile +++ b/infra/ci/sandbox/Dockerfile @@ -20,27 +20,27 @@ FROM debian:buster RUN set -ex; \ export DEBIAN_FRONTEND=noninteractive; \ - echo deb http://deb.debian.org/debian buster-backports main > \ + echo deb http://archive.debian.org/debian buster-backports main > \ /etc/apt/sources.list.d/backports.list; \ apt-get update; \ apt-get -y install python3 python3-pip git curl sudo lz4 tar ccache tini \ libpulse0 libgl1 libxml2 libc6-dev-i386 libtinfo5 \ gnupg2 pkg-config zip g++ zlib1g-dev unzip \ - python3-distutils gcc-7 g++-7; \ + python3-distutils gcc-8 g++-8; \ apt-get -y install libc++-8-dev libc++abi-8-dev clang-8; \ update-alternatives --install /usr/bin/python python /usr/bin/python3.7 1; \ - gcc-7 --version; \ - g++-7 --version; \ + gcc-8 --version; \ + g++-8 --version; \ clang-8 --version; \ clang++-8 --version; \ - pip3 install --quiet protobuf pandas; \ + pip3 install protobuf pandas grpcio; \ groupadd -g 1337 perfetto; \ useradd -d /ci/ramdisk -u 1337 -g perfetto perfetto; \ apt-get -y autoremove; \ rm -rf /var/lib/apt/lists/* /usr/share/man/* /usr/share/doc/*; RUN set -ex; \ - curl -LO https://github.com/bazelbuild/bazel/releases/download/2.2.0/bazel-2.2.0-installer-linux-x86_64.sh; \ + curl -LO https://github.com/bazelbuild/bazel/releases/download/7.0.2/bazel-7.0.2-installer-linux-x86_64.sh; \ chmod +x bazel-*-installer-linux-x86_64.sh; \ ./bazel-*-installer-linux-x86_64.sh; \ rm bazel-*-installer-linux-x86_64.sh; \ @@ -60,6 +60,11 @@ RUN set -ex; \ xdg-utils fonts-liberation fonts-ipafont-gothic fonts-wqy-zenhei \ fonts-thai-tlwg fonts-kacst fonts-freefont-ttf +# Cleanup to reduce image size +RUN apt-get -y autoremove; \ + rm -rf /var/lib/apt/lists/* /usr/share/man/* /usr/share/doc/*; \ + rm -rf /root/.cache/; + COPY testrunner.sh /ci/testrunner.sh COPY init.sh /ci/init.sh RUN chmod -R a+rx /ci/ diff --git a/infra/ci/worker/Dockerfile b/infra/ci/worker/Dockerfile index 6dc57d6dcb..556b80a26c 100644 --- a/infra/ci/worker/Dockerfile +++ b/infra/ci/worker/Dockerfile @@ -16,7 +16,8 @@ FROM docker:stable RUN apk update && apk add python3 py-pip sudo tini -RUN pip3 install oauth2client httplib2 +RUN pip3 install oauth2client httplib2 google-auth google-cloud requests; + # Unfortunately Docker doesn't allow to copy a file from ../. So we copy instead # the config files into tmp/ from the Makefile that runs docker build. diff --git a/infra/ci/worker/gce-startup-script.sh b/infra/ci/worker/gce-startup-script.sh index 7672c420b5..2e0f608bd7 100644 --- a/infra/ci/worker/gce-startup-script.sh +++ b/infra/ci/worker/gce-startup-script.sh @@ -15,33 +15,41 @@ set -eux -o pipefail -# num-workers is set at VM creation time in the Makefile. -URL='http://metadata.google.internal/computeMetadata/v1/instance/attributes/num-workers' +# num-workers, {sandbox,worker}-img are set at VM creation time in the Makefile. + +ATTRS='http://metadata.google.internal/computeMetadata/v1/instance/attributes' +URL="$ATTRS/num-workers" NUM_WORKERS=$(curl --silent --fail -H'Metadata-Flavor:Google' $URL || echo 1) +URL="$ATTRS/sandbox-img" +SANDBOX_IMG=$(curl --silent --fail -H'Metadata-Flavor:Google' $URL) + +URL="$ATTRS/worker-img" +WORKER_IMG=$(curl --silent --fail -H'Metadata-Flavor:Google' $URL) + for SSD in /dev/nvme0n*; do mkswap $SSD swapon -p -1 $SSD done # This is used by the sandbox containers, NOT needed by the workers. -# Rationale for size=100G: by default tmpfs mount are set to RAM/2, which makes +# Rationale for size=500G: by default tmpfs mount are set to RAM/2, which makes # the CI depend too much on the underlying VM. Here and below, we pick an # arbitrary fixed size (we use local scratch NVME as a swap device). export SHARED_WORKER_CACHE=/mnt/disks/shared_worker_cache rm -rf $SHARED_WORKER_CACHE mkdir -p $SHARED_WORKER_CACHE -mount -t tmpfs tmpfs $SHARED_WORKER_CACHE -o mode=777,size=100G +mount -t tmpfs tmpfs $SHARED_WORKER_CACHE -o mode=777,size=500G # This is used to queue build artifacts that are uploaded to GCS. export ARTIFACTS_DIR=/mnt/disks/artifacts rm -rf $ARTIFACTS_DIR mkdir -p $ARTIFACTS_DIR -mount -t tmpfs tmpfs $ARTIFACTS_DIR -o mode=777,size=100G +mount -t tmpfs tmpfs $ARTIFACTS_DIR -o mode=777,size=500G # Pull the latest images from the registry. -docker pull eu.gcr.io/perfetto-ci/worker -docker pull eu.gcr.io/perfetto-ci/sandbox +docker pull $WORKER_IMG +docker pull $SANDBOX_IMG # Create the restricted bridge for the sandbox container. # Prevent access to the metadata server and impersonation of service accounts. @@ -78,7 +86,7 @@ docker run -d \ --name worker-$i \ --hostname worker-$i \ --log-driver gcplogs \ - eu.gcr.io/perfetto-ci/worker + $WORKER_IMG done @@ -86,7 +94,7 @@ done cat > /etc/systemd/system/graceful_shutdown.sh < { + fs.watch(absDir, {recursive: true}, (_eventType, filePath) => { if (cfg.verbose) { console.log('File change detected', _eventType, filePath); } diff --git a/meson.build b/meson.build index 5ef6fce52c..8fb1e26275 100644 --- a/meson.build +++ b/meson.build @@ -19,7 +19,6 @@ project( 'perfetto', ['cpp'], - default_options: ['cpp_std=c++17'] ) fs = import('fs') @@ -41,6 +40,7 @@ lib_perfetto = static_library( sources: 'sdk/perfetto.cc', dependencies: deps_perfetto, install: true, + cpp_args: '-std=c++17', ) inc_perfetto = include_directories('sdk') diff --git a/perfetto.rc b/perfetto.rc index 3f1801da0c..5c6d853458 100644 --- a/perfetto.rc +++ b/perfetto.rc @@ -58,7 +58,7 @@ on property:persist.traced.enable=1 mkdir /data/misc/perfetto-traces/bugreport 0773 root shell # Traces in this directory are only accessed by system server - mkdir /data/misc/perfetto-traces/profiling 0773 root shell + mkdir /data/misc/perfetto-traces/profiling 0777 root shell # This directory allows shell to save configs file in a place where the # perfetto cmdline client can read then. /data/local/tmp/ isn't safe because diff --git a/protos/perfetto/bigtrace/BUILD.gn b/protos/perfetto/bigtrace/BUILD.gn new file mode 100644 index 0000000000..1ba673bdae --- /dev/null +++ b/protos/perfetto/bigtrace/BUILD.gn @@ -0,0 +1,47 @@ +# 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("../../../gn/proto_library.gni") + +perfetto_proto_library("worker_@TYPE@") { + proto_generators = [ + "lite", + "zero", + "source_set", + ] + deps = [ "../trace_processor:@TYPE@" ] + sources = [ "worker.proto" ] +} + +perfetto_proto_library("orchestrator_@TYPE@") { + proto_generators = [ + "lite", + "zero", + "source_set", + ] + deps = [ "../trace_processor:@TYPE@" ] + sources = [ "orchestrator.proto" ] +} + +if (enable_perfetto_grpc) { + perfetto_grpc_library("orchestrator_grpc") { + deps = [ ":orchestrator_lite" ] + sources = [ "orchestrator.proto" ] + } + + perfetto_grpc_library("worker_grpc") { + deps = [ ":worker_lite" ] + sources = [ "worker.proto" ] + } +} diff --git a/protos/perfetto/bigtrace/orchestrator.proto b/protos/perfetto/bigtrace/orchestrator.proto new file mode 100644 index 0000000000..06557efd8d --- /dev/null +++ b/protos/perfetto/bigtrace/orchestrator.proto @@ -0,0 +1,43 @@ +/* + * 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 = "proto3"; + +package perfetto.protos; + +import "protos/perfetto/trace_processor/trace_processor.proto"; + +// gRPC Interface for a Bigtrace Orchestrator + +// Each Bigtrace instance has an orchestrator which is responsible for receiving +// requests from the client and loading and querying traces by sharding them +// across a set of "Workers" +service BigtraceOrchestrator { + // Executes a SQL query on the specified list of traces and returns a stream + // of the result of the query for a given trace + rpc Query(BigtraceQueryArgs) returns (stream BigtraceQueryResponse) {} +} + +// Request/Response for TraceListQuery +message BigtraceQueryArgs { + repeated string traces = 1; + optional string sql_query = 2; +} + +message BigtraceQueryResponse { + optional string trace = 1; + repeated QueryResult result = 2; +} diff --git a/protos/perfetto/bigtrace/worker.proto b/protos/perfetto/bigtrace/worker.proto new file mode 100644 index 0000000000..b0e658c6f1 --- /dev/null +++ b/protos/perfetto/bigtrace/worker.proto @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto3"; + +package perfetto.protos; + +import "protos/perfetto/trace_processor/trace_processor.proto"; + +// gRPC Interface for a Bigtrace Worker +// +// Workers are owned by an "Orchestrator" which forward traces from requests by +// end users. Workers are responsible for loading the traces with +// TraceProcessor and executing the requests. +service BigtraceWorker { + // Executes a SQL query on the specified trace and returns a stream of + // execution responses. Note that this method returns a stream because each + // trace can return >1 result due to chunking of protos at the + // TraceProcessor::QueryResult level. + rpc QueryTrace(BigtraceQueryTraceArgs) returns (BigtraceQueryTraceResponse); +} + +// Request/Response for QueryTrace. +message BigtraceQueryTraceArgs { + optional string trace = 1; + optional string sql_query = 2; +} +message BigtraceQueryTraceResponse { + optional string trace = 1; + repeated QueryResult result = 2; +} diff --git a/protos/perfetto/common/observable_events.proto b/protos/perfetto/common/observable_events.proto index 85767a62ac..b841a0b55f 100644 --- a/protos/perfetto/common/observable_events.proto +++ b/protos/perfetto/common/observable_events.proto @@ -63,6 +63,9 @@ message ObservableEvents { // consumer has no idea of what is the TSID of its own tracing session and // there is no other good way to plumb it. optional int64 tracing_session_id = 1; + + // The trigger name of the CLONE_SNAPSHOT trigger which was hit. + optional string trigger_name = 2; } repeated DataSourceInstanceStateChange instance_state_changes = 1; diff --git a/protos/perfetto/common/sys_stats_counters.proto b/protos/perfetto/common/sys_stats_counters.proto index 4b23cddf05..884e880052 100644 --- a/protos/perfetto/common/sys_stats_counters.proto +++ b/protos/perfetto/common/sys_stats_counters.proto @@ -57,6 +57,11 @@ enum MeminfoCounters { MEMINFO_VMALLOC_CHUNK = 31; MEMINFO_CMA_TOTAL = 32; MEMINFO_CMA_FREE = 33; + MEMINFO_GPU = 34; + MEMINFO_ZRAM = 35; + MEMINFO_MISC = 36; + MEMINFO_ION_HEAP = 37; + MEMINFO_ION_HEAP_POOL = 38; } // Counter definitions for Linux's /proc/vmstat. diff --git a/protos/perfetto/config/android/BUILD.gn b/protos/perfetto/config/android/BUILD.gn index ec4f7ce391..b8d976421a 100644 --- a/protos/perfetto/config/android/BUILD.gn +++ b/protos/perfetto/config/android/BUILD.gn @@ -30,5 +30,6 @@ perfetto_proto_library("@TYPE@") { "protolog_config.proto", "surfaceflinger_layers_config.proto", "surfaceflinger_transactions_config.proto", + "windowmanager_config.proto", ] } diff --git a/protos/perfetto/config/android/android_input_event_config.proto b/protos/perfetto/config/android/android_input_event_config.proto index 0b4e22c236..ce196dfbbb 100644 --- a/protos/perfetto/config/android/android_input_event_config.proto +++ b/protos/perfetto/config/android/android_input_event_config.proto @@ -24,97 +24,111 @@ package perfetto.protos; // // Next ID: 5 message AndroidInputEventConfig { + // Trace modes are tracing presets that are included in the system. + enum TraceMode { + // Preset mode for maximal tracing. + // WARNING: This will bypass all privacy measures on debuggable builds, and + // will record all + // input events processed by the system, regardless of the context + // in which they were processed. It should only be used for tracing + // on a local device or for tests. It should NEVER be used for + // field tracing. + TRACE_MODE_TRACE_ALL = 0; + // Use the tracing rules defined in this config to specify what events to + // trace. + TRACE_MODE_USE_RULES = 1; + } - // Trace modes are tracing presets that are included in the system. - enum TraceMode { - // Preset mode for maximal tracing. - // WARNING: This will bypass all privacy measures on debuggable builds, and will record all - // input events processed by the system, regardless of the context in which they - // were processed. It should only be used for tracing on a local device or for - // tests. It should NEVER be used for field tracing. - TRACE_MODE_TRACE_ALL = 0; - // Use the tracing rules defined in this config to specify what events to trace. - TRACE_MODE_USE_RULES = 1; - } + // The tracing mode to use. If unspecified, it will default to + // TRACE_MODE_USE_RULES. + optional TraceMode mode = 1; - // The tracing mode to use. If unspecified, it will default to TRACE_MODE_USE_RULES. - optional TraceMode mode = 1; + // The level of tracing that should be applied to an event. + enum TraceLevel { + // Do not trace the input event. + TRACE_LEVEL_NONE = 0; + // Trace the event as a redacted event, where certain sensitive fields are + // omitted from the trace, including the coordinates of pointer events and + // the key/scan codes of key events. + TRACE_LEVEL_REDACTED = 1; + // Trace the complete event. + TRACE_LEVEL_COMPLETE = 2; + } - // The level of tracing that should be applied to an event. - enum TraceLevel { - // Do not trace the input event. - TRACE_LEVEL_NONE = 0; - // Trace the event as a redacted event, where certain sensitive fields are omitted from - // the trace, including the coordinates of pointer events and the key/scan codes of key - // events. - TRACE_LEVEL_REDACTED = 1; - // Trace the complete event. - TRACE_LEVEL_COMPLETE = 2; - } + // A rule that specifies the TraceLevel for an event based on matching + // conditions. All matchers in the rule are optional. To trigger this rule, an + // event must match all of its specified matchers (i.e. the matchers function + // like a series of conditions connected by a logical 'AND' operator). A rule + // with no specified matchers will match all events. Next ID: 6 + message TraceRule { + // The trace level to be used for events that trigger this rule. + // If unspecified, TRACE_LEVEL_NONE will be used by default. + optional TraceLevel trace_level = 1; - // A rule that specifies the TraceLevel for an event based on matching conditions. - // All matchers in the rule are optional. To trigger this rule, an event must match all - // of its specified matchers (i.e. the matchers function like a series of conditions connected - // by a logical 'AND' operator). A rule with no specified matchers will match all events. - // Next ID: 6 - message TraceRule { - // The trace level to be used for events that trigger this rule. - // If unspecified, TRACE_LEVEL_NONE will be used by default. - optional TraceLevel trace_level = 1; + // --- Optional Matchers --- - // --- Optional Matchers --- + // Package matchers + // + // Respectively matches if all or any of the target apps for this event are + // contained in the specified list of package names. + // + // Intended usage: + // - Use match_all_packages to selectively allow tracing for the listed + // packages. + // - Use match_any_packages to selectively deny tracing for certain + // packages. + // + // WARNING: Great care must be taken when designing rules for field tracing! + // This is because each event is almost always sent to more than + // one app. + // For example, when allowing tracing for a package that has a + // spy window + // over the display (e.g. SystemUI) using match_any_packages, + // essentially all input will be recorded on that display. This is + // because the events will be sent to the spy as well as the + // foreground app, and regardless of what the foreground app is, + // the event will end up being traced. + // Alternatively, when attempting to block tracing for specific + // packages using + // match_all_packages, no events will likely be blocked. This is + // because the event will also be sent to other apps (such as, but + // not limited to, ones with spy windows), so the matcher will not + // match unless all other targets are also listed under the + // match_all_packages list. + repeated string match_all_packages = 2; + repeated string match_any_packages = 3; - // Package matchers - // - // Respectively matches if all or any of the target apps for this event are contained in - // the specified list of package names. - // - // Intended usage: - // - Use match_all_packages to selectively allow tracing for the listed packages. - // - Use match_any_packages to selectively deny tracing for certain packages. - // - // WARNING: Great care must be taken when designing rules for field tracing! - // This is because each event is almost always sent to more than one app. - // For example, when allowing tracing for a package that has a spy window - // over the display (e.g. SystemUI) using match_any_packages, essentially all - // input will be recorded on that display. This is because the events will be sent - // to the spy as well as the foreground app, and regardless of what the foreground - // app is, the event will end up being traced. - // Alternatively, when attempting to block tracing for specific packages using - // match_all_packages, no events will likely be blocked. This is because the event - // will also be sent to other apps (such as, but not limited to, ones with spy - // windows), so the matcher will not match unless all other targets are also - // listed under the match_all_packages list. - repeated string match_all_packages = 2; - repeated string match_any_packages = 3; + // Matches if the event is secure, which means that at least one of the + // targets of this event is using the window flag FLAG_SECURE. + optional bool match_secure = 4; - // Matches if the event is secure, which means that at least one of the targets of - // this event is using the window flag FLAG_SECURE. - optional bool match_secure = 4; + // Matches if there was an active IME connection while this event was being + // processed. + optional bool match_ime_connection_active = 5; + } - // Matches if there was an active IME connection while this event was being processed. - optional bool match_ime_connection_active = 5; - } + // The list of rules to use to determine the trace level of events. + // Each event will be traced using the TraceLevel of the first rule that it + // triggers from this list. The rules are evaluated in the order in which they + // are specified. If an event does not match any of the rules, + // TRACE_LEVEL_NONE will be used by default. + repeated TraceRule rules = 2; - // The list of rules to use to determine the trace level of events. - // Each event will be traced using the TraceLevel of the first rule that it triggers - // from this list. The rules are evaluated in the order in which they are specified. - // If an event does not match any of the rules, TRACE_LEVEL_NONE will be used by default. - repeated TraceRule rules = 2; + // --- Control flags --- - // --- Control flags --- + // Trace input events processed by the system as they are being dispatched + // to application windows. All trace rules will apply. + // - If this flag is used without enabling trace_dispatcher_window_dispatch, + // it will + // trace InputDispatcher's inbound events (which does not include events + // synthesized within InputDispatcher) that match the rules. + // - If used with trace_dispatcher_window_dispatch, all inbound and outbound + // events + // matching the rules, including all events synthesized within + // InputDispatcher, will be traced. + optional bool trace_dispatcher_input_events = 3; - // Trace input events processed by the system as they are being dispatched - // to application windows. All trace rules will apply. - // - If this flag is used without enabling trace_dispatcher_window_dispatch, it will - // trace InputDispatcher's inbound events (which does not include events synthesized - // within InputDispatcher) that match the rules. - // - If used with trace_dispatcher_window_dispatch, all inbound and outbound events - // matching the rules, including all events synthesized within InputDispatcher, - // will be traced. - optional bool trace_dispatcher_input_events = 3; - - // Trace details about which windows the system is sending each input event to. - // All trace rules will apply. - optional bool trace_dispatcher_window_dispatch = 4; + // Trace details about which windows the system is sending each input event + // to. All trace rules will apply. + optional bool trace_dispatcher_window_dispatch = 4; } diff --git a/protos/perfetto/config/android/windowmanager_config.proto b/protos/perfetto/config/android/windowmanager_config.proto new file mode 100644 index 0000000000..1b16890a9d --- /dev/null +++ b/protos/perfetto/config/android/windowmanager_config.proto @@ -0,0 +1,48 @@ +/* + * 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; + +// Custom configuration for the "android.windowmanager" data source. +message WindowManagerConfig { + enum LogFrequency { + LOG_FREQUENCY_UNSPECIFIED = 0; + + // Trace state snapshots when a frame is committed. + LOG_FREQUENCY_FRAME = 1; + + // Trace state snapshots every time a transaction is committed. + LOG_FREQUENCY_TRANSACTION = 2; + } + optional LogFrequency log_frequency = 1; + + enum LogLevel { + LOG_LEVEL_UNSPECIFIED = 0; + + // Logs all elements with maximum amount of information. + LOG_LEVEL_VERBOSE = 1; + + // Logs all elements but doesn't write all configuration data. + LOG_LEVEL_DEBUG = 2; + + // Logs only visible elements, with the minimum amount of performance + // overhead + LOG_LEVEL_CRITICAL = 3; + } + optional LogLevel log_level = 2; +} diff --git a/protos/perfetto/config/data_source_config.proto b/protos/perfetto/config/data_source_config.proto index 3b8e860d9b..5249100a37 100644 --- a/protos/perfetto/config/data_source_config.proto +++ b/protos/perfetto/config/data_source_config.proto @@ -30,6 +30,7 @@ import "protos/perfetto/config/android/pixel_modem_config.proto"; import "protos/perfetto/config/android/protolog_config.proto"; import "protos/perfetto/config/android/surfaceflinger_layers_config.proto"; import "protos/perfetto/config/android/surfaceflinger_transactions_config.proto"; +import "protos/perfetto/config/android/windowmanager_config.proto"; import "protos/perfetto/config/chrome/chrome_config.proto"; import "protos/perfetto/config/chrome/v8_config.proto"; import "protos/perfetto/config/etw/etw_config.proto"; @@ -50,7 +51,7 @@ import "protos/perfetto/config/track_event/track_event_config.proto"; import "protos/perfetto/config/system_info/system_info.proto"; // The configuration that is passed to each data source when starting tracing. -// Next id: 130 +// Next id: 131 message DataSourceConfig { enum SessionInitiator { SESSION_INITIATOR_UNSPECIFIED = 0; @@ -196,11 +197,15 @@ message DataSourceConfig { optional ProtoLogConfig protolog_config = 126 [lazy = true]; // Data source name: android.input.inputevent - optional AndroidInputEventConfig android_input_event_config = 128 [lazy = true]; + optional AndroidInputEventConfig android_input_event_config = 128 + [lazy = true]; // Data source name: android.pixel.modem optional PixelModemConfig pixel_modem_config = 129 [lazy = true]; + // Data source name: android.windowmanager + optional WindowManagerConfig windowmanager_config = 130 [lazy = true]; + // This is a fallback mechanism to send a free-form text config to the // producer. In theory this should never be needed. All the code that // is part of the platform (i.e. traced service) is supposed to *not* truncate diff --git a/protos/perfetto/config/ftrace/ftrace_config.proto b/protos/perfetto/config/ftrace/ftrace_config.proto index 1872290199..eb9bffd310 100644 --- a/protos/perfetto/config/ftrace/ftrace_config.proto +++ b/protos/perfetto/config/ftrace/ftrace_config.proto @@ -18,13 +18,17 @@ syntax = "proto2"; package perfetto.protos; -// Next id: 28 +// Next id: 29 message FtraceConfig { // Ftrace events to record, example: "sched/sched_switch". repeated string ftrace_events = 1; // Android-specific event categories: repeated string atrace_categories = 2; repeated string atrace_apps = 3; + // Some processes can emit data through atrace or through the perfetto SDK via + // the "track_event" data source. For these categories, the SDK will be + // preferred, if possible, for this config. + repeated string atrace_categories_prefer_sdk = 28; // Size of each per-cpu kernel ftrace ring buffer. // Not guaranteed if there are multiple concurrent tracing sessions, as the diff --git a/protos/perfetto/config/perfetto_config.proto b/protos/perfetto/config/perfetto_config.proto index 7777d1c8d6..96205440a0 100644 --- a/protos/perfetto/config/perfetto_config.proto +++ b/protos/perfetto/config/perfetto_config.proto @@ -367,99 +367,113 @@ message AndroidGameInterventionListConfig { // // Next ID: 5 message AndroidInputEventConfig { + // Trace modes are tracing presets that are included in the system. + enum TraceMode { + // Preset mode for maximal tracing. + // WARNING: This will bypass all privacy measures on debuggable builds, and + // will record all + // input events processed by the system, regardless of the context + // in which they were processed. It should only be used for tracing + // on a local device or for tests. It should NEVER be used for + // field tracing. + TRACE_MODE_TRACE_ALL = 0; + // Use the tracing rules defined in this config to specify what events to + // trace. + TRACE_MODE_USE_RULES = 1; + } - // Trace modes are tracing presets that are included in the system. - enum TraceMode { - // Preset mode for maximal tracing. - // WARNING: This will bypass all privacy measures on debuggable builds, and will record all - // input events processed by the system, regardless of the context in which they - // were processed. It should only be used for tracing on a local device or for - // tests. It should NEVER be used for field tracing. - TRACE_MODE_TRACE_ALL = 0; - // Use the tracing rules defined in this config to specify what events to trace. - TRACE_MODE_USE_RULES = 1; - } + // The tracing mode to use. If unspecified, it will default to + // TRACE_MODE_USE_RULES. + optional TraceMode mode = 1; + + // The level of tracing that should be applied to an event. + enum TraceLevel { + // Do not trace the input event. + TRACE_LEVEL_NONE = 0; + // Trace the event as a redacted event, where certain sensitive fields are + // omitted from the trace, including the coordinates of pointer events and + // the key/scan codes of key events. + TRACE_LEVEL_REDACTED = 1; + // Trace the complete event. + TRACE_LEVEL_COMPLETE = 2; + } - // The tracing mode to use. If unspecified, it will default to TRACE_MODE_USE_RULES. - optional TraceMode mode = 1; - - // The level of tracing that should be applied to an event. - enum TraceLevel { - // Do not trace the input event. - TRACE_LEVEL_NONE = 0; - // Trace the event as a redacted event, where certain sensitive fields are omitted from - // the trace, including the coordinates of pointer events and the key/scan codes of key - // events. - TRACE_LEVEL_REDACTED = 1; - // Trace the complete event. - TRACE_LEVEL_COMPLETE = 2; - } + // A rule that specifies the TraceLevel for an event based on matching + // conditions. All matchers in the rule are optional. To trigger this rule, an + // event must match all of its specified matchers (i.e. the matchers function + // like a series of conditions connected by a logical 'AND' operator). A rule + // with no specified matchers will match all events. Next ID: 6 + message TraceRule { + // The trace level to be used for events that trigger this rule. + // If unspecified, TRACE_LEVEL_NONE will be used by default. + optional TraceLevel trace_level = 1; - // A rule that specifies the TraceLevel for an event based on matching conditions. - // All matchers in the rule are optional. To trigger this rule, an event must match all - // of its specified matchers (i.e. the matchers function like a series of conditions connected - // by a logical 'AND' operator). A rule with no specified matchers will match all events. - // Next ID: 6 - message TraceRule { - // The trace level to be used for events that trigger this rule. - // If unspecified, TRACE_LEVEL_NONE will be used by default. - optional TraceLevel trace_level = 1; - - // --- Optional Matchers --- - - // Package matchers - // - // Respectively matches if all or any of the target apps for this event are contained in - // the specified list of package names. - // - // Intended usage: - // - Use match_all_packages to selectively allow tracing for the listed packages. - // - Use match_any_packages to selectively deny tracing for certain packages. - // - // WARNING: Great care must be taken when designing rules for field tracing! - // This is because each event is almost always sent to more than one app. - // For example, when allowing tracing for a package that has a spy window - // over the display (e.g. SystemUI) using match_any_packages, essentially all - // input will be recorded on that display. This is because the events will be sent - // to the spy as well as the foreground app, and regardless of what the foreground - // app is, the event will end up being traced. - // Alternatively, when attempting to block tracing for specific packages using - // match_all_packages, no events will likely be blocked. This is because the event - // will also be sent to other apps (such as, but not limited to, ones with spy - // windows), so the matcher will not match unless all other targets are also - // listed under the match_all_packages list. - repeated string match_all_packages = 2; - repeated string match_any_packages = 3; - - // Matches if the event is secure, which means that at least one of the targets of - // this event is using the window flag FLAG_SECURE. - optional bool match_secure = 4; - - // Matches if there was an active IME connection while this event was being processed. - optional bool match_ime_connection_active = 5; - } + // --- Optional Matchers --- + + // Package matchers + // + // Respectively matches if all or any of the target apps for this event are + // contained in the specified list of package names. + // + // Intended usage: + // - Use match_all_packages to selectively allow tracing for the listed + // packages. + // - Use match_any_packages to selectively deny tracing for certain + // packages. + // + // WARNING: Great care must be taken when designing rules for field tracing! + // This is because each event is almost always sent to more than + // one app. + // For example, when allowing tracing for a package that has a + // spy window + // over the display (e.g. SystemUI) using match_any_packages, + // essentially all input will be recorded on that display. This is + // because the events will be sent to the spy as well as the + // foreground app, and regardless of what the foreground app is, + // the event will end up being traced. + // Alternatively, when attempting to block tracing for specific + // packages using + // match_all_packages, no events will likely be blocked. This is + // because the event will also be sent to other apps (such as, but + // not limited to, ones with spy windows), so the matcher will not + // match unless all other targets are also listed under the + // match_all_packages list. + repeated string match_all_packages = 2; + repeated string match_any_packages = 3; + + // Matches if the event is secure, which means that at least one of the + // targets of this event is using the window flag FLAG_SECURE. + optional bool match_secure = 4; + + // Matches if there was an active IME connection while this event was being + // processed. + optional bool match_ime_connection_active = 5; + } - // The list of rules to use to determine the trace level of events. - // Each event will be traced using the TraceLevel of the first rule that it triggers - // from this list. The rules are evaluated in the order in which they are specified. - // If an event does not match any of the rules, TRACE_LEVEL_NONE will be used by default. - repeated TraceRule rules = 2; - - // --- Control flags --- - - // Trace input events processed by the system as they are being dispatched - // to application windows. All trace rules will apply. - // - If this flag is used without enabling trace_dispatcher_window_dispatch, it will - // trace InputDispatcher's inbound events (which does not include events synthesized - // within InputDispatcher) that match the rules. - // - If used with trace_dispatcher_window_dispatch, all inbound and outbound events - // matching the rules, including all events synthesized within InputDispatcher, - // will be traced. - optional bool trace_dispatcher_input_events = 3; - - // Trace details about which windows the system is sending each input event to. - // All trace rules will apply. - optional bool trace_dispatcher_window_dispatch = 4; + // The list of rules to use to determine the trace level of events. + // Each event will be traced using the TraceLevel of the first rule that it + // triggers from this list. The rules are evaluated in the order in which they + // are specified. If an event does not match any of the rules, + // TRACE_LEVEL_NONE will be used by default. + repeated TraceRule rules = 2; + + // --- Control flags --- + + // Trace input events processed by the system as they are being dispatched + // to application windows. All trace rules will apply. + // - If this flag is used without enabling trace_dispatcher_window_dispatch, + // it will + // trace InputDispatcher's inbound events (which does not include events + // synthesized within InputDispatcher) that match the rules. + // - If used with trace_dispatcher_window_dispatch, all inbound and outbound + // events + // matching the rules, including all events synthesized within + // InputDispatcher, will be traced. + optional bool trace_dispatcher_input_events = 3; + + // Trace details about which windows the system is sending each input event + // to. All trace rules will apply. + optional bool trace_dispatcher_window_dispatch = 4; } // End of protos/perfetto/config/android/android_input_event_config.proto @@ -801,6 +815,39 @@ message SurfaceFlingerTransactionsConfig { // End of protos/perfetto/config/android/surfaceflinger_transactions_config.proto +// Begin of protos/perfetto/config/android/windowmanager_config.proto + +// Custom configuration for the "android.windowmanager" data source. +message WindowManagerConfig { + enum LogFrequency { + LOG_FREQUENCY_UNSPECIFIED = 0; + + // Trace state snapshots when a frame is committed. + LOG_FREQUENCY_FRAME = 1; + + // Trace state snapshots every time a transaction is committed. + LOG_FREQUENCY_TRANSACTION = 2; + } + optional LogFrequency log_frequency = 1; + + enum LogLevel { + LOG_LEVEL_UNSPECIFIED = 0; + + // Logs all elements with maximum amount of information. + LOG_LEVEL_VERBOSE = 1; + + // Logs all elements but doesn't write all configuration data. + LOG_LEVEL_DEBUG = 2; + + // Logs only visible elements, with the minimum amount of performance + // overhead + LOG_LEVEL_CRITICAL = 3; + } + optional LogLevel log_level = 2; +} + +// End of protos/perfetto/config/android/windowmanager_config.proto + // Begin of protos/perfetto/config/chrome/chrome_config.proto message ChromeConfig { @@ -873,13 +920,17 @@ message EtwConfig { // Begin of protos/perfetto/config/ftrace/ftrace_config.proto -// Next id: 28 +// Next id: 29 message FtraceConfig { // Ftrace events to record, example: "sched/sched_switch". repeated string ftrace_events = 1; // Android-specific event categories: repeated string atrace_categories = 2; repeated string atrace_apps = 3; + // Some processes can emit data through atrace or through the perfetto SDK via + // the "track_event" data source. For these categories, the SDK will be + // preferred, if possible, for this config. + repeated string atrace_categories_prefer_sdk = 28; // Size of each per-cpu kernel ftrace ring buffer. // Not guaranteed if there are multiple concurrent tracing sessions, as the @@ -2845,6 +2896,11 @@ enum MeminfoCounters { MEMINFO_VMALLOC_CHUNK = 31; MEMINFO_CMA_TOTAL = 32; MEMINFO_CMA_FREE = 33; + MEMINFO_GPU = 34; + MEMINFO_ZRAM = 35; + MEMINFO_MISC = 36; + MEMINFO_ION_HEAP = 37; + MEMINFO_ION_HEAP_POOL = 38; } // Counter definitions for Linux's /proc/vmstat. @@ -3103,6 +3159,10 @@ message SysStatsConfig { // Polls /proc/pressure/* every X ms, if non-zero. // This is required to be > 10ms to avoid excessive CPU usage. optional uint32 psi_period_ms = 11; + + // Polls /sys/class/thermal/* every X ms, if non-zero. + // This is required to be > 10ms to avoid excessive CPU usage. + optional uint32 thermal_period_ms = 12; } // End of protos/perfetto/config/sys_stats/sys_stats_config.proto @@ -3257,7 +3317,7 @@ message TrackEventConfig { // Begin of protos/perfetto/config/data_source_config.proto // The configuration that is passed to each data source when starting tracing. -// Next id: 130 +// Next id: 131 message DataSourceConfig { enum SessionInitiator { SESSION_INITIATOR_UNSPECIFIED = 0; @@ -3403,11 +3463,15 @@ message DataSourceConfig { optional ProtoLogConfig protolog_config = 126 [lazy = true]; // Data source name: android.input.inputevent - optional AndroidInputEventConfig android_input_event_config = 128 [lazy = true]; + optional AndroidInputEventConfig android_input_event_config = 128 + [lazy = true]; // Data source name: android.pixel.modem optional PixelModemConfig pixel_modem_config = 129 [lazy = true]; + // Data source name: android.windowmanager + optional WindowManagerConfig windowmanager_config = 130 [lazy = true]; + // This is a fallback mechanism to send a free-form text config to the // producer. In theory this should never be needed. All the code that // is part of the platform (i.e. traced service) is supposed to *not* truncate @@ -3433,7 +3497,7 @@ message DataSourceConfig { // It contains the general config for the logging buffer(s) and the configs for // all the data source being enabled. // -// Next id: 39. +// Next id: 40. message TraceConfig { message BufferConfig { optional uint32 size_kb = 1; @@ -4099,6 +4163,51 @@ message TraceConfig { optional uint32 max_delay_ms = 2; } optional CmdTraceStartDelay cmd_trace_start_delay = 35; + + // When non-empty, ensures that for a each semaphore named `name at most + // `max_other_session_count`` *other* sessions (whose value is taken of the + // minimum of all values specified by this config or any already-running + // session) can be be running. + // + // If a semaphore "acquisition" fails, EnableTracing will return an error + // and the tracing session will not be started (or elgible to start in + // the case of deferred sessions). + // + // This is easiest to explain with an example. Suppose the tracing service has + // the following active tracing sessions: + // S1 = [{name=foo, max_other_session_count=2}, + // {name=bar, max_other_session_count=0}] + // S2 = [{name=foo, max_other_session_count=1}, + // {name=baz, max_other_session_count=1}] + // + // Then, for a new session, the following would be the expected behaviour of + // EnableSession given the state of `session_semaphores`. + // Q: session_semaphores = [] + // A: Allowed because it does not specify any semaphores. Will be allowed + // no matter the state of any other tracing session. + // Q: session_semaphores = [{name=baz, max_other_session_count=1}] + // A: Allowed because both S2 and this config specify + // max_other_session_count=1 for baz. + // Q: session_semaphores = [{name=foo, max_other_session_count=3}] + // A: Denied because S2 specified max_other_session_count=1 for foo and S1 + // takes that slot. + // Q: session_semaphores = [{name=bar, max_other_session_count=0}] + // A: Denied because S1 takes the the slot specified by both S1 and + // this config. + // + // Introduced in 24Q3 (Android V). + message SessionSemaphore { + // The name of the semaphore. Acts as a unique identifier across all + // tracing sessions (including the one being started). + optional string name = 1; + + // The maximum number of *other* sesssions which specify the same semaphore + // which can be active. The minimum of this value across all tracing + // sessions and the value specified by the config is used when deciding + // whether the tracing session can be started. + optional uint64 max_other_session_count = 2; + } + repeated SessionSemaphore session_semaphores = 39; } // End of protos/perfetto/config/trace_config.proto diff --git a/protos/perfetto/config/sys_stats/sys_stats_config.proto b/protos/perfetto/config/sys_stats/sys_stats_config.proto index 1cae840bf5..56ab93012c 100644 --- a/protos/perfetto/config/sys_stats/sys_stats_config.proto +++ b/protos/perfetto/config/sys_stats/sys_stats_config.proto @@ -79,4 +79,8 @@ message SysStatsConfig { // Polls /proc/pressure/* every X ms, if non-zero. // This is required to be > 10ms to avoid excessive CPU usage. optional uint32 psi_period_ms = 11; + + // Polls /sys/class/thermal/* every X ms, if non-zero. + // This is required to be > 10ms to avoid excessive CPU usage. + optional uint32 thermal_period_ms = 12; } diff --git a/protos/perfetto/config/trace_config.proto b/protos/perfetto/config/trace_config.proto index 29173ec7be..8fe4424451 100644 --- a/protos/perfetto/config/trace_config.proto +++ b/protos/perfetto/config/trace_config.proto @@ -26,7 +26,7 @@ import "protos/perfetto/config/data_source_config.proto"; // It contains the general config for the logging buffer(s) and the configs for // all the data source being enabled. // -// Next id: 39. +// Next id: 40. message TraceConfig { message BufferConfig { optional uint32 size_kb = 1; @@ -692,4 +692,49 @@ message TraceConfig { optional uint32 max_delay_ms = 2; } optional CmdTraceStartDelay cmd_trace_start_delay = 35; + + // When non-empty, ensures that for a each semaphore named `name at most + // `max_other_session_count`` *other* sessions (whose value is taken of the + // minimum of all values specified by this config or any already-running + // session) can be be running. + // + // If a semaphore "acquisition" fails, EnableTracing will return an error + // and the tracing session will not be started (or elgible to start in + // the case of deferred sessions). + // + // This is easiest to explain with an example. Suppose the tracing service has + // the following active tracing sessions: + // S1 = [{name=foo, max_other_session_count=2}, + // {name=bar, max_other_session_count=0}] + // S2 = [{name=foo, max_other_session_count=1}, + // {name=baz, max_other_session_count=1}] + // + // Then, for a new session, the following would be the expected behaviour of + // EnableSession given the state of `session_semaphores`. + // Q: session_semaphores = [] + // A: Allowed because it does not specify any semaphores. Will be allowed + // no matter the state of any other tracing session. + // Q: session_semaphores = [{name=baz, max_other_session_count=1}] + // A: Allowed because both S2 and this config specify + // max_other_session_count=1 for baz. + // Q: session_semaphores = [{name=foo, max_other_session_count=3}] + // A: Denied because S2 specified max_other_session_count=1 for foo and S1 + // takes that slot. + // Q: session_semaphores = [{name=bar, max_other_session_count=0}] + // A: Denied because S1 takes the the slot specified by both S1 and + // this config. + // + // Introduced in 24Q3 (Android V). + message SessionSemaphore { + // The name of the semaphore. Acts as a unique identifier across all + // tracing sessions (including the one being started). + optional string name = 1; + + // The maximum number of *other* sesssions which specify the same semaphore + // which can be active. The minimum of this value across all tracing + // sessions and the value specified by the config is used when deciding + // whether the tracing session can be started. + optional uint64 max_other_session_count = 2; + } + repeated SessionSemaphore session_semaphores = 39; } diff --git a/protos/perfetto/ipc/OWNERS b/protos/perfetto/ipc/OWNERS index bead02d78b..7aaadfb84c 100644 --- a/protos/perfetto/ipc/OWNERS +++ b/protos/perfetto/ipc/OWNERS @@ -5,4 +5,3 @@ set noparent eseckler@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 af7aa8f5c5..574ae5fcc5 100644 --- a/protos/perfetto/metrics/android/BUILD.gn +++ b/protos/perfetto/metrics/android/BUILD.gn @@ -26,6 +26,7 @@ perfetto_proto_library("@TYPE@") { "android_blocking_calls_unagg.proto", "android_boot.proto", "android_boot_unagg.proto", + "android_broadcasts_metric.proto", "android_frame_timeline_metric.proto", "android_garbage_collection_unagg_metric.proto", "android_oom_adjuster_metric.proto", @@ -80,5 +81,6 @@ perfetto_proto_library("@TYPE@") { "thread_time_in_state_metric.proto", "trace_quality.proto", "unsymbolized_frames.proto", + "wattson_app_startup.proto", ] } diff --git a/protos/perfetto/metrics/android/android_blocking_call.proto b/protos/perfetto/metrics/android/android_blocking_call.proto index 0352fec24e..772d21bebe 100644 --- a/protos/perfetto/metrics/android/android_blocking_call.proto +++ b/protos/perfetto/metrics/android/android_blocking_call.proto @@ -36,4 +36,8 @@ message AndroidBlockingCall { optional int64 max_dur_ns = 7; // Minimal duration within the CUJ in nanoseconds optional int64 min_dur_ns = 8; + // Avg duration within the CUJ + optional int64 avg_dur_ms = 9; + // Avg duration within the CUJ in nanoseconds + optional int64 avg_dur_ns = 10; } diff --git a/protos/perfetto/metrics/android/android_blocking_calls_unagg.proto b/protos/perfetto/metrics/android/android_blocking_calls_unagg.proto index 96b73ba27d..91465bd00e 100644 --- a/protos/perfetto/metrics/android/android_blocking_calls_unagg.proto +++ b/protos/perfetto/metrics/android/android_blocking_calls_unagg.proto @@ -23,14 +23,13 @@ import "protos/perfetto/metrics/android/process_metadata.proto"; // All blocking calls for a trace. Shows count and total duration for each. message AndroidBlockingCallsUnagg { + repeated ProcessWithBlockingCalls process_with_blocking_calls = 1; - repeated ProcessWithBlockingCalls process_with_blocking_calls = 1; + message ProcessWithBlockingCalls { + // Details about the process (uid, version, etc) + optional AndroidProcessMetadata process = 1; - message ProcessWithBlockingCalls { - // Details about the process (uid, version, etc) - optional AndroidProcessMetadata process = 1; - - // List of blocking calls on the process main thread. - repeated AndroidBlockingCall blocking_calls = 2; - } + // List of blocking calls on the process main thread. + repeated AndroidBlockingCall blocking_calls = 2; + } } diff --git a/protos/perfetto/metrics/android/android_boot.proto b/protos/perfetto/metrics/android/android_boot.proto index 9f90ba5555..d6c33f89d4 100644 --- a/protos/perfetto/metrics/android/android_boot.proto +++ b/protos/perfetto/metrics/android/android_boot.proto @@ -57,7 +57,8 @@ message AndroidBootMetric { optional double mb_per_ms_of_gc = 12; } message OomAdjusterTransitionCounts { - // name of the item aggregated by. example: process_name, oom_adjuster_reason. + // 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; @@ -67,7 +68,8 @@ message AndroidBootMetric { optional int64 count = 4; } message OomAdjBucketDurationAggregation { - // name of the item aggregated by. example: process_name, oom_adjuster_reason + // name of the item aggregated by. example: process_name, + // oom_adjuster_reason optional string name = 1; // name of oom bucket. optional string bucket = 2; @@ -81,14 +83,35 @@ message AndroidBootMetric { optional int64 oom_adj_event_count = 4; optional string oom_adj_reason = 5; } + message BroadcastCountAggregation { + optional string name = 1; + optional int64 count = 2; + } + // Stats for Broadcasts aggregated with duration. + message BroadcastDurationAggregation { + optional string name = 1; + optional double avg_duration = 2; + optional int64 max_duration = 3; + optional int64 sum_duration = 4; +} 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 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; + repeated BroadcastCountAggregation post_boot_broadcast_process_count_by_intent = 16; + repeated BroadcastCountAggregation post_boot_broadcast_count_by_process = 17; + repeated BroadcastDurationAggregation post_boot_brodcast_duration_agg_by_intent = 18; + repeated BroadcastDurationAggregation post_boot_brodcast_duration_agg_by_process = 19; + } diff --git a/protos/perfetto/metrics/android/android_broadcasts_metric.proto b/protos/perfetto/metrics/android/android_broadcasts_metric.proto new file mode 100644 index 0000000000..554b42da9f --- /dev/null +++ b/protos/perfetto/metrics/android/android_broadcasts_metric.proto @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + syntax = "proto2"; + + package perfetto.protos; + + // Provides aggregated information about broadcasts + message AndroidBroadcastsMetric { + // Next id: 5 + // Stats for Broadcasts aggregated with count. + message BroadcastCountAggregation { + optional string name = 1; + optional int64 count = 2; + } + // Stats for Broadcasts aggregated with duration. + message BroadcastDurationAggregation { + optional string name = 1; + optional double avg_duration = 2; + optional int64 max_duration = 3; + optional int64 sum_duration = 4; + } + repeated BroadcastCountAggregation process_count_by_intent = 1; + repeated BroadcastCountAggregation broadcast_count_by_process = 2; + repeated BroadcastDurationAggregation brodcast_duration_agg_by_intent = 3; + repeated BroadcastDurationAggregation brodcast_duration_agg_by_process = 4; + } diff --git a/protos/perfetto/metrics/android/android_oom_adjuster_metric.proto b/protos/perfetto/metrics/android/android_oom_adjuster_metric.proto index 95af7b4b06..8f34d0fd37 100644 --- a/protos/perfetto/metrics/android/android_oom_adjuster_metric.proto +++ b/protos/perfetto/metrics/android/android_oom_adjuster_metric.proto @@ -20,7 +20,8 @@ package perfetto.protos; message AndroidOomAdjusterMetric { message OomAdjusterTransitionCounts { - // name of the item aggregated by. example: process_name, oom_adjuster_reason. + // 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; @@ -30,7 +31,8 @@ message AndroidOomAdjusterMetric { optional int64 count = 4; } message OomAdjBucketDurationAggregation { - // name of the item aggregated by. example: process_name, oom_adjuster_reason + // name of the item aggregated by. example: process_name, + // oom_adjuster_reason optional string name = 1; // name of oom bucket. optional string bucket = 2; @@ -44,10 +46,15 @@ message AndroidOomAdjusterMetric { 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 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/android/dma_heap_metric.proto b/protos/perfetto/metrics/android/dma_heap_metric.proto index c9f95c25d3..52ffbe1acc 100644 --- a/protos/perfetto/metrics/android/dma_heap_metric.proto +++ b/protos/perfetto/metrics/android/dma_heap_metric.proto @@ -20,6 +20,13 @@ package perfetto.protos; // dma-buf heap memory stats on Android. message AndroidDmaHeapMetric { + message ProcessStats { + // process that either directly or indirectly allocated the buffers + optional string process_name = 1; + // Bytes allocated but not freed during this trace + optional int32 delta_bytes = 2; + } + optional double avg_size_bytes = 1; optional double min_size_bytes = 2; optional double max_size_bytes = 3; @@ -27,4 +34,8 @@ message AndroidDmaHeapMetric { // Total allocation size. // Essentially the sum of positive allocs. optional double total_alloc_size_bytes = 4; + + // Total delta size (bytes allocated but not freed during the trace) + optional int32 total_delta_bytes = 5; + repeated ProcessStats process_stats = 6; } diff --git a/protos/perfetto/metrics/android/io_metric.proto b/protos/perfetto/metrics/android/io_metric.proto index a2126ac9b0..f8a1c18c9d 100644 --- a/protos/perfetto/metrics/android/io_metric.proto +++ b/protos/perfetto/metrics/android/io_metric.proto @@ -19,8 +19,8 @@ syntax = "proto2"; package perfetto.protos; // Measure Android IO stats in a trace. -// Note: This is an aggregated metric, for unaggregated metrics look at AndroidIoUnaggregated -// in io_unagg_metric.proto. +// Note: This is an aggregated metric, for unaggregated metrics look at +// AndroidIoUnaggregated in io_unagg_metric.proto. message AndroidIo { // Next id: 3 // Stats for Counters in an f2fs file system. diff --git a/protos/perfetto/metrics/android/java_heap_class_stats.proto b/protos/perfetto/metrics/android/java_heap_class_stats.proto index ecde153930..9cf5158320 100644 --- a/protos/perfetto/metrics/android/java_heap_class_stats.proto +++ b/protos/perfetto/metrics/android/java_heap_class_stats.proto @@ -19,9 +19,12 @@ package perfetto.protos; import "protos/perfetto/metrics/android/process_metadata.proto"; message JavaHeapClassStats { - // Next id: 11 + // Next id: 13 message TypeCount { + reserved 11; optional string type_name = 1; + optional bool is_libcore_or_array = 12; + optional int64 obj_count = 2; optional int64 size_bytes = 3; optional int64 native_size_bytes = 4; diff --git a/protos/perfetto/metrics/android/process_metadata.proto b/protos/perfetto/metrics/android/process_metadata.proto index fa766bea97..83f86faff5 100644 --- a/protos/perfetto/metrics/android/process_metadata.proto +++ b/protos/perfetto/metrics/android/process_metadata.proto @@ -38,14 +38,8 @@ message AndroidProcessMetadata { // match this field is empty. optional Package package = 7; - // All packages using this uid. - // - // Shared uid documentation: - // https://developer.android.com/guide/topics/manifest/manifest-element#uid - repeated Package packages_for_uid = 8; - // Pid of the process name. optional int64 pid = 9; - reserved 3, 4, 5, 6; + reserved 3, 4, 5, 6, 8; } diff --git a/protos/perfetto/metrics/android/startup_metric.proto b/protos/perfetto/metrics/android/startup_metric.proto index 66f01ec4bb..f22803640d 100644 --- a/protos/perfetto/metrics/android/startup_metric.proto +++ b/protos/perfetto/metrics/android/startup_metric.proto @@ -236,6 +236,7 @@ message AndroidStartupMetric { } // Contains information for slow startup causes. + // Next id: 11. message SlowStartReason { // Points to reason description and solution. enum ReasonId { @@ -270,6 +271,15 @@ message AndroidStartupMetric { // Brief description for human readability. optional string reason = 2; + // Severity level for a reason. + enum SeverityLevel { + SEVERITY_UNSPECIFIED = 0; + ERROR = 1; + WARNING = 2; + INFO = 3; + } + optional SeverityLevel severity = 10; + // Expected value (inherited from threshold definition). optional ThresholdValue expected_value = 3; @@ -305,6 +315,7 @@ message AndroidStartupMetric { NS = 1; PERCENTAGE = 2; TRUE_OR_FALSE = 3; + COUNT = 4; } optional ThresholdUnit unit = 2; @@ -331,6 +342,8 @@ message AndroidStartupMetric { optional int64 end_timestamp = 2; optional uint32 slice_id = 3; + + optional string slice_name = 4; } // Contains information for a section of a thread. @@ -340,9 +353,11 @@ message AndroidStartupMetric { optional int64 end_timestamp = 2; optional uint32 thread_utid = 3; + + optional string thread_name = 4; } - // Next id: 24 + // Next id: 25 message Startup { // Random id uniquely identifying an app startup in this trace. optional uint32 startup_id = 1; @@ -424,9 +439,17 @@ message AndroidStartupMetric { // Optional. repeated string slow_start_reason = 17; - // Same as slow_start_reason, but with more detailed information. + // Same as slow_start_reason, but with more detailed information, obsolete. repeated SlowStartReasonDetailed slow_start_reason_detailed = 21; + // Similar to slow_start_reason_detailed, but with much more comprehensive + // info. such as expected threshold, actual value and threads/slices to + // inspect. slow_start_reason will be obsolete once + // slow_start_reason_with_details is completed since + // slow_start_reason_with_details contains all the data in slow_start_reason + // and more. + repeated SlowStartReason slow_start_reason_with_details = 24; + reserved 10; } diff --git a/protos/perfetto/metrics/android/wattson_app_startup.proto b/protos/perfetto/metrics/android/wattson_app_startup.proto new file mode 100644 index 0000000000..75c60c38b5 --- /dev/null +++ b/protos/perfetto/metrics/android/wattson_app_startup.proto @@ -0,0 +1,36 @@ +/* + * 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 AndroidWattsonTimePeriodMetric { + optional int32 metric_version = 1; + repeated AndroidWattsonEstimateInfo period_info = 2; +} + +message AndroidWattsonEstimateInfo { + optional int32 period_id = 1; + optional int64 period_dur = 2; + repeated AndroidWattsonRailEstimate rail = 3; +} + +message AndroidWattsonRailEstimate { + optional string name = 1; + optional float estimate_mw = 2; + repeated AndroidWattsonRailEstimate rail = 3; +} diff --git a/protos/perfetto/metrics/metrics.proto b/protos/perfetto/metrics/metrics.proto index 434818796c..de0042ee63 100644 --- a/protos/perfetto/metrics/metrics.proto +++ b/protos/perfetto/metrics/metrics.proto @@ -74,6 +74,8 @@ 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"; +import "protos/perfetto/metrics/android/android_broadcasts_metric.proto"; +import "protos/perfetto/metrics/android/wattson_app_startup.proto"; // Trace processor metadata message TraceMetadata { @@ -120,7 +122,7 @@ message TraceAnalysisStats { // Root message for all Perfetto-based metrics. // -// Next id: 68 +// Next id: 70 message TraceMetrics { reserved 4, 10, 13, 14, 16, 19; @@ -260,7 +262,8 @@ message TraceMetrics { optional AndroidMonitorContentionMetric android_monitor_contention = 50; - optional AndroidSysUINotificationsBlockingCallsMetric android_sysui_notifications_blocking_calls_metric = 51; + optional AndroidSysUINotificationsBlockingCallsMetric + android_sysui_notifications_blocking_calls_metric = 51; // Metrics to track codec framework. optional AndroidCodecMetrics codec_metrics = 52; @@ -277,16 +280,19 @@ message TraceMetrics { optional AndroidAnrMetric android_anr = 55; // Aggregated Android Monitor Contention metrics - optional AndroidMonitorContentionAggMetric android_monitor_contention_agg = 56; + optional AndroidMonitorContentionAggMetric android_monitor_contention_agg = + 56; optional AndroidBootMetric android_boot = 57; // Metric for AdServices module. optional AdServicesMetric ad_services_metric = 58; - optional SysuiNotifShadeListBuilderMetric sysui_notif_shade_list_builder_metric = 59; + optional SysuiNotifShadeListBuilderMetric + sysui_notif_shade_list_builder_metric = 59; - optional SysuiUpdateNotifOnUiModeChangedMetric sysui_update_notif_on_ui_mode_changed_metric = 60; + optional SysuiUpdateNotifOnUiModeChangedMetric + sysui_update_notif_on_ui_mode_changed_metric = 60; // Metrics for Process starts. optional AndroidAppProcessStartsMetric android_app_process_starts = 61; @@ -295,7 +301,8 @@ message TraceMetrics { optional AndroidBootUnagg android_boot_unagg = 62; // Android garbage collection metrics - optional AndroidGarbageCollectionUnaggMetric android_garbage_collection_unagg = 63; + optional AndroidGarbageCollectionUnaggMetric + android_garbage_collection_unagg = 63; // Multiuser - metrics for switching users. // Specific for Android Auto @@ -307,6 +314,12 @@ message TraceMetrics { // Android OOM unaggregated metrics. optional AndroidOomAdjusterMetric android_oom_adjuster = 66; + // Android Broadcasts aggregated metrics + optional AndroidBroadcastsMetric android_broadcasts = 68; + + // Android Wattson app startup metrics. + optional AndroidWattsonTimePeriodMetric wattson_app_startup = 69; + // 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 bffca26feb..3c14ed31e5 100644 --- a/protos/perfetto/metrics/perfetto_merged_metrics.proto +++ b/protos/perfetto/metrics/perfetto_merged_metrics.proto @@ -73,6 +73,10 @@ message AndroidBlockingCall { optional int64 max_dur_ns = 7; // Minimal duration within the CUJ in nanoseconds optional int64 min_dur_ns = 8; + // Avg duration within the CUJ + optional int64 avg_dur_ms = 9; + // Avg duration within the CUJ in nanoseconds + optional int64 avg_dur_ns = 10; } // End of protos/perfetto/metrics/android/android_blocking_call.proto @@ -100,16 +104,10 @@ message AndroidProcessMetadata { // match this field is empty. optional Package package = 7; - // All packages using this uid. - // - // Shared uid documentation: - // https://developer.android.com/guide/topics/manifest/manifest-element#uid - repeated Package packages_for_uid = 8; - // Pid of the process name. optional int64 pid = 9; - reserved 3, 4, 5, 6; + reserved 3, 4, 5, 6, 8; } // End of protos/perfetto/metrics/android/process_metadata.proto @@ -149,16 +147,15 @@ message AndroidBlockingCallsCujMetric { // All blocking calls for a trace. Shows count and total duration for each. message AndroidBlockingCallsUnagg { + repeated ProcessWithBlockingCalls process_with_blocking_calls = 1; - repeated ProcessWithBlockingCalls process_with_blocking_calls = 1; - - message ProcessWithBlockingCalls { - // Details about the process (uid, version, etc) - optional AndroidProcessMetadata process = 1; + message ProcessWithBlockingCalls { + // Details about the process (uid, version, etc) + optional AndroidProcessMetadata process = 1; - // List of blocking calls on the process main thread. - repeated AndroidBlockingCall blocking_calls = 2; - } + // List of blocking calls on the process main thread. + repeated AndroidBlockingCall blocking_calls = 2; + } } // End of protos/perfetto/metrics/android/android_blocking_calls_unagg.proto @@ -204,7 +201,8 @@ message AndroidBootMetric { optional double mb_per_ms_of_gc = 12; } message OomAdjusterTransitionCounts { - // name of the item aggregated by. example: process_name, oom_adjuster_reason. + // 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; @@ -214,7 +212,8 @@ message AndroidBootMetric { optional int64 count = 4; } message OomAdjBucketDurationAggregation { - // name of the item aggregated by. example: process_name, oom_adjuster_reason + // name of the item aggregated by. example: process_name, + // oom_adjuster_reason optional string name = 1; // name of oom bucket. optional string bucket = 2; @@ -228,16 +227,37 @@ message AndroidBootMetric { optional int64 oom_adj_event_count = 4; optional string oom_adj_reason = 5; } + message BroadcastCountAggregation { + optional string name = 1; + optional int64 count = 2; + } + // Stats for Broadcasts aggregated with duration. + message BroadcastDurationAggregation { + optional string name = 1; + optional double avg_duration = 2; + optional int64 max_duration = 3; + optional int64 sum_duration = 4; +} 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 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; + repeated BroadcastCountAggregation post_boot_broadcast_process_count_by_intent = 16; + repeated BroadcastCountAggregation post_boot_broadcast_count_by_process = 17; + repeated BroadcastDurationAggregation post_boot_brodcast_duration_agg_by_intent = 18; + repeated BroadcastDurationAggregation post_boot_brodcast_duration_agg_by_process = 19; + } // End of protos/perfetto/metrics/android/android_boot.proto @@ -326,6 +346,31 @@ message AndroidBootUnagg { // End of protos/perfetto/metrics/android/android_boot_unagg.proto +// Begin of protos/perfetto/metrics/android/android_broadcasts_metric.proto + + // Provides aggregated information about broadcasts + message AndroidBroadcastsMetric { + // Next id: 5 + // Stats for Broadcasts aggregated with count. + message BroadcastCountAggregation { + optional string name = 1; + optional int64 count = 2; + } + // Stats for Broadcasts aggregated with duration. + message BroadcastDurationAggregation { + optional string name = 1; + optional double avg_duration = 2; + optional int64 max_duration = 3; + optional int64 sum_duration = 4; + } + repeated BroadcastCountAggregation process_count_by_intent = 1; + repeated BroadcastCountAggregation broadcast_count_by_process = 2; + repeated BroadcastDurationAggregation brodcast_duration_agg_by_intent = 3; + repeated BroadcastDurationAggregation brodcast_duration_agg_by_process = 4; + } + +// End of protos/perfetto/metrics/android/android_broadcasts_metric.proto + // Begin of protos/perfetto/metrics/android/android_frame_timeline_metric.proto message AndroidFrameTimelineMetric { @@ -400,7 +445,8 @@ message AndroidFrameTimelineMetric { message AndroidOomAdjusterMetric { message OomAdjusterTransitionCounts { - // name of the item aggregated by. example: process_name, oom_adjuster_reason. + // 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; @@ -410,7 +456,8 @@ message AndroidOomAdjusterMetric { optional int64 count = 4; } message OomAdjBucketDurationAggregation { - // name of the item aggregated by. example: process_name, oom_adjuster_reason + // name of the item aggregated by. example: process_name, + // oom_adjuster_reason optional string name = 1; // name of oom bucket. optional string bucket = 2; @@ -424,11 +471,16 @@ message AndroidOomAdjusterMetric { 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 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 @@ -863,6 +915,13 @@ message AndroidDisplayMetrics { // dma-buf heap memory stats on Android. message AndroidDmaHeapMetric { + message ProcessStats { + // process that either directly or indirectly allocated the buffers + optional string process_name = 1; + // Bytes allocated but not freed during this trace + optional int32 delta_bytes = 2; + } + optional double avg_size_bytes = 1; optional double min_size_bytes = 2; optional double max_size_bytes = 3; @@ -870,6 +929,10 @@ message AndroidDmaHeapMetric { // Total allocation size. // Essentially the sum of positive allocs. optional double total_alloc_size_bytes = 4; + + // Total delta size (bytes allocated but not freed during the trace) + optional int32 total_delta_bytes = 5; + repeated ProcessStats process_stats = 6; } // End of protos/perfetto/metrics/android/dma_heap_metric.proto @@ -1230,8 +1293,8 @@ message AndroidHwuiMetric { // Begin of protos/perfetto/metrics/android/io_metric.proto // Measure Android IO stats in a trace. -// Note: This is an aggregated metric, for unaggregated metrics look at AndroidIoUnaggregated -// in io_unagg_metric.proto. +// Note: This is an aggregated metric, for unaggregated metrics look at +// AndroidIoUnaggregated in io_unagg_metric.proto. message AndroidIo { // Next id: 3 // Stats for Counters in an f2fs file system. @@ -1480,9 +1543,12 @@ message AndroidJankCujMetric { // Begin of protos/perfetto/metrics/android/java_heap_class_stats.proto message JavaHeapClassStats { - // Next id: 11 + // Next id: 13 message TypeCount { + reserved 11; optional string type_name = 1; + optional bool is_libcore_or_array = 12; + optional int64 obj_count = 2; optional int64 size_bytes = 3; optional int64 native_size_bytes = 4; @@ -2352,6 +2418,7 @@ message AndroidStartupMetric { } // Contains information for slow startup causes. + // Next id: 11. message SlowStartReason { // Points to reason description and solution. enum ReasonId { @@ -2386,6 +2453,15 @@ message AndroidStartupMetric { // Brief description for human readability. optional string reason = 2; + // Severity level for a reason. + enum SeverityLevel { + SEVERITY_UNSPECIFIED = 0; + ERROR = 1; + WARNING = 2; + INFO = 3; + } + optional SeverityLevel severity = 10; + // Expected value (inherited from threshold definition). optional ThresholdValue expected_value = 3; @@ -2421,6 +2497,7 @@ message AndroidStartupMetric { NS = 1; PERCENTAGE = 2; TRUE_OR_FALSE = 3; + COUNT = 4; } optional ThresholdUnit unit = 2; @@ -2447,6 +2524,8 @@ message AndroidStartupMetric { optional int64 end_timestamp = 2; optional uint32 slice_id = 3; + + optional string slice_name = 4; } // Contains information for a section of a thread. @@ -2456,9 +2535,11 @@ message AndroidStartupMetric { optional int64 end_timestamp = 2; optional uint32 thread_utid = 3; + + optional string thread_name = 4; } - // Next id: 24 + // Next id: 25 message Startup { // Random id uniquely identifying an app startup in this trace. optional uint32 startup_id = 1; @@ -2540,9 +2621,17 @@ message AndroidStartupMetric { // Optional. repeated string slow_start_reason = 17; - // Same as slow_start_reason, but with more detailed information. + // Same as slow_start_reason, but with more detailed information, obsolete. repeated SlowStartReasonDetailed slow_start_reason_detailed = 21; + // Similar to slow_start_reason_detailed, but with much more comprehensive + // info. such as expected threshold, actual value and threads/slices to + // inspect. slow_start_reason will be obsolete once + // slow_start_reason_with_details is completed since + // slow_start_reason_with_details contains all the data in slow_start_reason + // and more. + repeated SlowStartReason slow_start_reason_with_details = 24; + reserved 10; } @@ -2751,6 +2840,27 @@ message UnsymbolizedFrames { // End of protos/perfetto/metrics/android/unsymbolized_frames.proto +// Begin of protos/perfetto/metrics/android/wattson_app_startup.proto + +message AndroidWattsonTimePeriodMetric { + optional int32 metric_version = 1; + repeated AndroidWattsonEstimateInfo period_info = 2; +} + +message AndroidWattsonEstimateInfo { + optional int32 period_id = 1; + optional int64 period_dur = 2; + repeated AndroidWattsonRailEstimate rail = 3; +} + +message AndroidWattsonRailEstimate { + optional string name = 1; + optional float estimate_mw = 2; + repeated AndroidWattsonRailEstimate rail = 3; +} + +// End of protos/perfetto/metrics/android/wattson_app_startup.proto + // Begin of protos/perfetto/metrics/metrics.proto // Trace processor metadata @@ -2798,7 +2908,7 @@ message TraceAnalysisStats { // Root message for all Perfetto-based metrics. // -// Next id: 68 +// Next id: 70 message TraceMetrics { reserved 4, 10, 13, 14, 16, 19; @@ -2938,7 +3048,8 @@ message TraceMetrics { optional AndroidMonitorContentionMetric android_monitor_contention = 50; - optional AndroidSysUINotificationsBlockingCallsMetric android_sysui_notifications_blocking_calls_metric = 51; + optional AndroidSysUINotificationsBlockingCallsMetric + android_sysui_notifications_blocking_calls_metric = 51; // Metrics to track codec framework. optional AndroidCodecMetrics codec_metrics = 52; @@ -2955,16 +3066,19 @@ message TraceMetrics { optional AndroidAnrMetric android_anr = 55; // Aggregated Android Monitor Contention metrics - optional AndroidMonitorContentionAggMetric android_monitor_contention_agg = 56; + optional AndroidMonitorContentionAggMetric android_monitor_contention_agg = + 56; optional AndroidBootMetric android_boot = 57; // Metric for AdServices module. optional AdServicesMetric ad_services_metric = 58; - optional SysuiNotifShadeListBuilderMetric sysui_notif_shade_list_builder_metric = 59; + optional SysuiNotifShadeListBuilderMetric + sysui_notif_shade_list_builder_metric = 59; - optional SysuiUpdateNotifOnUiModeChangedMetric sysui_update_notif_on_ui_mode_changed_metric = 60; + optional SysuiUpdateNotifOnUiModeChangedMetric + sysui_update_notif_on_ui_mode_changed_metric = 60; // Metrics for Process starts. optional AndroidAppProcessStartsMetric android_app_process_starts = 61; @@ -2973,7 +3087,8 @@ message TraceMetrics { optional AndroidBootUnagg android_boot_unagg = 62; // Android garbage collection metrics - optional AndroidGarbageCollectionUnaggMetric android_garbage_collection_unagg = 63; + optional AndroidGarbageCollectionUnaggMetric + android_garbage_collection_unagg = 63; // Multiuser - metrics for switching users. // Specific for Android Auto @@ -2985,6 +3100,12 @@ message TraceMetrics { // Android OOM unaggregated metrics. optional AndroidOomAdjusterMetric android_oom_adjuster = 66; + // Android Broadcasts aggregated metrics + optional AndroidBroadcastsMetric android_broadcasts = 68; + + // Android Wattson app startup metrics. + optional AndroidWattsonTimePeriodMetric wattson_app_startup = 69; + // Android // Demo extensions. extensions 450 to 499; diff --git a/protos/perfetto/trace/android/BUILD.gn b/protos/perfetto/trace/android/BUILD.gn index 900a64b083..350e7e2088 100644 --- a/protos/perfetto/trace/android/BUILD.gn +++ b/protos/perfetto/trace/android/BUILD.gn @@ -15,11 +15,13 @@ import("../../../../gn/proto_library.gni") perfetto_proto_library("@TYPE@") { - deps = [ "../../common:@TYPE@", ":winscope_regular_@TYPE@" ] + deps = [ + ":winscope_regular_@TYPE@", + "../../common:@TYPE@", + ] sources = [ "android_game_intervention_list.proto", - "android_input_event.proto", "android_log.proto", "android_system_property.proto", "camera_event.proto", @@ -44,8 +46,8 @@ perfetto_proto_library("winscope_common_@TYPE@") { # Winscope messages added to TracePacket directly perfetto_proto_library("winscope_regular_@TYPE@") { deps = [ - "../../common:@TYPE@", ":winscope_common_@TYPE@", + "../../common:@TYPE@", ] sources = [ "protolog.proto", @@ -64,40 +66,76 @@ perfetto_proto_library("winscope_extensions_@TYPE@") { ] public_deps = [ ":winscope_common_@TYPE@" ] sources = [ - "inputmethodeditor.proto", + "android_input_event.proto", "graphics/pixelformat.proto", + "inputmethodeditor.proto", "inputmethodservice/inputmethodservice.proto", "inputmethodservice/softinputwindow.proto", "server/inputmethod/inputmethodmanagerservice.proto", "typedef.proto", - "view/inputmethod/editorinfo.proto", - "view/inputmethod/inputconnection.proto", - "view/inputmethod/inputmethodmanager.proto", + "app/statusbarmanager.proto", + "app/window_configuration.proto", + "content/activityinfo.proto", + "content/configuration.proto", + "content/locale.proto", + "privacy.proto", + "server/animationadapter.proto", + "server/surfaceanimator.proto", + "server/windowcontainerthumbnail.proto", + "server/windowmanagerservice.proto", "view/display.proto", "view/displaycutout.proto", + "view/displayinfo.proto", + "view/enums.proto", "view/imefocuscontroller.proto", "view/imeinsetssourceconsumer.proto", + "view/inputmethod/editorinfo.proto", + "view/inputmethod/inputconnection.proto", + "view/inputmethod/inputmethodmanager.proto", "view/insetsanimationcontrolimpl.proto", "view/insetscontroller.proto", "view/insetssource.proto", "view/insetssourceconsumer.proto", "view/insetssourcecontrol.proto", "view/insetsstate.proto", + "view/remote_animation_target.proto", + "view/surface.proto", "view/surfacecontrol.proto", "view/viewrootimpl.proto", "view/windowlayoutparams.proto", + "viewcapture.proto", + "windowmanager.proto", "winscope_extensions_impl.proto", ] import_dirs = [ "${perfetto_protobuf_src_dir}" ] } +static_library("perfetto_winscope_extensions_zero") { + complete_static_lib = true + deps = [ ":winscope_extensions_zero" ] +} + perfetto_proto_library("winscope_descriptor") { proto_generators = [ "descriptor" ] generate_descriptor = "winscope.descriptor" deps = [ - ":winscope_regular_source_set", ":winscope_extensions_source_set", + ":winscope_regular_source_set", ] sources = [ "winscope.proto" ] import_dirs = [ "${perfetto_protobuf_src_dir}" ] } + +# Android track_event extensions +perfetto_proto_library("android_track_event_@TYPE@") { + proto_generators = [ "source_set" ] + public_deps = [ "../track_event:@TYPE@" ] + sources = [ "android_track_event.proto" ] +} + +perfetto_proto_library("android_track_event_@TYPE@") { + proto_generators = [ "descriptor" ] + generate_descriptor = "android_track_event.descriptor" + sources = [ "android_track_event.proto" ] + deps = [ ":android_track_event_source_set" ] +} diff --git a/protos/perfetto/trace/android/android_input_event.proto b/protos/perfetto/trace/android/android_input_event.proto index 6a91950307..85a89c0728 100644 --- a/protos/perfetto/trace/android/android_input_event.proto +++ b/protos/perfetto/trace/android/android_input_event.proto @@ -18,33 +18,39 @@ syntax = "proto2"; package perfetto.protos; +import "protos/perfetto/trace/android/typedef.proto"; + // A representation of an Android MotionEvent. // See: https://developer.android.com/reference/android/view/MotionEvent +// +// The timestamp of the TracePacket that contains this event corresponds to the time at which +// this event was processed by InputDispatcher. message AndroidMotionEvent { // A representation of one pointer inside a MotionEvent. // Each Pointer contains the values present in its corresponding // MotionEvent.PointerCoords and MotionEvent.PointerProperties. message Pointer { message AxisValue { - optional int32 axis = 1; + optional int32 axis = 1 [(.perfetto.protos.typedef) = "android.view.MotionEvent.Axis"]; optional float value = 2; } repeated AxisValue axis_value = 1; optional int32 pointer_id = 2; - optional int32 tool_type = 3; + optional int32 tool_type = 3 [(.perfetto.protos.typedef) = "android.view.MotionEvent.ToolType"]; } // The randomly-generated ID used to track the event through the pipeline. optional fixed32 event_id = 1; // The event's timestamp - generated by the kernel. optional int64 event_time_nanos = 2; - optional uint32 source = 3; - optional int32 action = 4; + optional uint32 source = 3 [(.perfetto.protos.typedef) = "android.view.InputDevice.Source"]; + optional int32 action = 4 [(.perfetto.protos.typedef) = "android.view.MotionEvent.ActionMasked"]; optional int32 device_id = 5; // Use a signed int for display_id, because -1 (DISPLAY_IS_NONE) is a common value. optional sint32 display_id = 6; - optional int32 classification = 7; - optional uint32 flags = 8; + optional int32 classification = 7 + [(.perfetto.protos.typedef) = "android.view.MotionEvent.Classification"]; + optional uint32 flags = 8 [(.perfetto.protos.typedef) = "android.view.MotionEvent.Flag"]; repeated Pointer pointer = 9; // Field numbers 10-15 are reserved for commonly used fields. @@ -59,9 +65,9 @@ message AndroidMotionEvent { optional int64 down_time_nanos = 17; optional float cursor_position_x = 18; optional float cursor_position_y = 19; - optional int32 action_button = 20; - optional uint32 button_state = 21; - optional uint32 meta_state = 22; + optional int32 action_button = 20 [(.perfetto.protos.typedef) ="android.view.MotionEvent.Button"]; + optional uint32 button_state = 21 [(.perfetto.protos.typedef) ="android.view.MotionEvent.Button"]; + optional uint32 meta_state = 22 [(.perfetto.protos.typedef) ="android.view.KeyEvent.MetaState"]; optional uint32 policy_flags = 23; optional float precision_x = 24; optional float precision_y = 25; @@ -69,6 +75,9 @@ message AndroidMotionEvent { // A representation of an Android KeyEvent. // See: https://developer.android.com/reference/android/view/KeyEvent +// +// The timestamp of the TracePacket that contains this event corresponds to the time at which +// this event was processed by InputDispatcher. message AndroidKeyEvent { // The randomly-generated ID used to track the event through the pipeline. optional fixed32 event_id = 1; @@ -76,20 +85,23 @@ message AndroidKeyEvent { optional int64 event_time_nanos = 2; // The timestamp of the ACTION_DOWN event associated with this gesture. optional int64 down_time_nanos = 3; - optional uint32 source = 4; - optional int32 action = 5; + optional uint32 source = 4 [(.perfetto.protos.typedef) = "android.view.InputDevice.Source"]; + optional int32 action = 5 [(.perfetto.protos.typedef) = "android.view.KeyEvent.Action"]; optional int32 device_id = 6; // Use a signed int for display_id, because -1 (DISPLAY_IS_NONE) is a common value. optional sint32 display_id = 7; optional int32 key_code = 8; optional uint32 scan_code = 9; - optional uint32 meta_state = 10; + optional uint32 meta_state = 10 [(.perfetto.protos.typedef) ="android.view.KeyEvent.MetaState"]; optional int32 repeat_count = 11; - optional uint32 flags = 12; + optional uint32 flags = 12 [(.perfetto.protos.typedef) = "android.view.KeyEvent.Flag"]; optional uint32 policy_flags = 13; } // An event that traces an input event being dispatched by the system to one window. +// +// The timestamp of the TracePacket that contains this event corresponds to the time at which +// this event was dispatched to the target window by InputDispatcher, also known as "delivery time". message AndroidWindowInputDispatchEvent { // Stores x/y values for each pointer sent to the window for events // that are dispatched to a certain location on the screen. This is not relevant @@ -119,6 +131,9 @@ message AndroidWindowInputDispatchEvent { optional uint32 resolved_flags = 5; } +// A wrapper type for input tracing on Android. +// The meaning of the timestamp of the TracePacket that contains this message will depend on +// the event that this message contains. message AndroidInputEvent { oneof event { // Traces input events received by or generated by InputDispatcher diff --git a/protos/perfetto/trace/android/android_track_event.proto b/protos/perfetto/trace/android/android_track_event.proto new file mode 100644 index 0000000000..bb560c483a --- /dev/null +++ b/protos/perfetto/trace/android/android_track_event.proto @@ -0,0 +1,34 @@ +/* + * 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"; + +import public "protos/perfetto/trace/track_event/track_event.proto"; + +package perfetto.protos; + +message AndroidTrackEvent { + // Usable range: [2001, 2999] + // Next id: 2004 + extend TrackEvent { + // The name of a binder service. + optional string binder_service_name = 2001; + // The name of a binder interface. + optional string binder_interface_name = 2002; + // The name of an apex. + optional string apex_name = 2003; + } +} diff --git a/protos/perfetto/trace/android/app/statusbarmanager.proto b/protos/perfetto/trace/android/app/statusbarmanager.proto new file mode 100644 index 0000000000..a9c2937afc --- /dev/null +++ b/protos/perfetto/trace/android/app/statusbarmanager.proto @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto2"; + +package perfetto.protos; + +message StatusBarManagerProto { + enum WindowState { + WINDOW_STATE_SHOWING = 0; + WINDOW_STATE_HIDING = 1; + WINDOW_STATE_HIDDEN = 2; + } + enum TransientWindowState { + TRANSIENT_BAR_NONE = 0; + TRANSIENT_BAR_SHOW_REQUESTED = 1; + TRANSIENT_BAR_SHOWING = 2; + TRANSIENT_BAR_HIDING = 3; + } +} diff --git a/protos/perfetto/trace/android/app/window_configuration.proto b/protos/perfetto/trace/android/app/window_configuration.proto new file mode 100644 index 0000000000..8f86debc87 --- /dev/null +++ b/protos/perfetto/trace/android/app/window_configuration.proto @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2017 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"; + +import "protos/perfetto/trace/android/graphics/rect.proto"; +import "protos/perfetto/trace/android/typedef.proto"; + +package perfetto.protos; + +// Proto representation for WindowConfiguration.java class +message WindowConfigurationProto { + optional RectProto app_bounds = 1; + optional int32 windowing_mode = 2 + [(.perfetto.protos.typedef) = + "android.app.WindowConfiguration.WindowingMode"]; + optional int32 activity_type = 3 + [(.perfetto.protos.typedef) = + "android.app.WindowConfiguration.ActivityType"]; + optional RectProto bounds = 4; + optional RectProto max_bounds = 5; +} diff --git a/protos/perfetto/trace/android/content/activityinfo.proto b/protos/perfetto/trace/android/content/activityinfo.proto new file mode 100644 index 0000000000..0b440350b3 --- /dev/null +++ b/protos/perfetto/trace/android/content/activityinfo.proto @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto2"; + +package perfetto.protos; + +message ActivityInfoProto { + enum ScreenOrientation { + SCREEN_ORIENTATION_UNSET = -2; + SCREEN_ORIENTATION_UNSPECIFIED = -1; + SCREEN_ORIENTATION_LANDSCAPE = 0; + SCREEN_ORIENTATION_PORTRAIT = 1; + SCREEN_ORIENTATION_USER = 2; + SCREEN_ORIENTATION_BEHIND = 3; + SCREEN_ORIENTATION_SENSOR = 4; + SCREEN_ORIENTATION_NOSENSOR = 5; + SCREEN_ORIENTATION_SENSOR_LANDSCAPE = 6; + SCREEN_ORIENTATION_SENSOR_PORTRAIT = 7; + SCREEN_ORIENTATION_REVERSE_LANDSCAPE = 8; + SCREEN_ORIENTATION_REVERSE_PORTRAIT = 9; + SCREEN_ORIENTATION_FULL_SENSOR = 10; + SCREEN_ORIENTATION_USER_LANDSCAPE = 11; + SCREEN_ORIENTATION_USER_PORTRAIT = 12; + SCREEN_ORIENTATION_FULL_USER = 13; + SCREEN_ORIENTATION_LOCKED = 14; + } +} diff --git a/protos/perfetto/trace/android/content/configuration.proto b/protos/perfetto/trace/android/content/configuration.proto new file mode 100644 index 0000000000..dc72e6c0f0 --- /dev/null +++ b/protos/perfetto/trace/android/content/configuration.proto @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2006 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"; + +import "protos/perfetto/trace/android/app/window_configuration.proto"; +import "protos/perfetto/trace/android/content/locale.proto"; +import "protos/perfetto/trace/android/privacy.proto"; + +package perfetto.protos; + +// An android Configuration object +message ConfigurationProto { + optional float font_scale = 1; + optional uint32 mcc = 2; + optional uint32 mnc = 3 [(.perfetto.protos.privacy).dest = DEST_EXPLICIT]; + repeated LocaleProto locales = 4 [deprecated = true]; + optional uint32 screen_layout = 5; + optional uint32 color_mode = 6; + optional uint32 touchscreen = 7; + optional uint32 keyboard = 8; + optional uint32 keyboard_hidden = 9; + optional uint32 hard_keyboard_hidden = 10; + optional uint32 navigation = 11; + optional uint32 navigation_hidden = 12; + optional uint32 orientation = 13; + optional uint32 ui_mode = 14; + optional uint32 screen_width_dp = 15; + optional uint32 screen_height_dp = 16; + optional uint32 smallest_screen_width_dp = 17; + optional uint32 density_dpi = 18; + optional WindowConfigurationProto window_configuration = 19; + optional string locale_list = 20; + optional uint32 font_weight_adjustment = 21; + optional uint32 grammatical_gender = 22; +} + +// All current configuration data used to select resources +message ResourcesConfigurationProto { + optional ConfigurationProto configuration = 1; + + optional uint32 sdk_version = 2; + optional uint32 screen_width_px = 3; + optional uint32 screen_height_px = 4; +} + +// Overall device configuration data +message DeviceConfigurationProto { + optional uint32 stable_screen_width_px = 1; + optional uint32 stable_screen_height_px = 2; + optional uint32 stable_density_dpi = 3; + + optional uint64 total_ram = 4; + optional bool low_ram = 5; + optional uint32 max_cores = 6; + optional bool has_secure_screen_lock = 7; + + optional uint32 opengl_version = 8; + repeated string opengl_extensions = 9; + + repeated string shared_libraries = 10; + repeated string features = 11; + repeated string cpu_architectures = 12; +} + +// All current configuration data device is running with, everything used +// to filter and target apps. +message GlobalConfigurationProto { + optional ResourcesConfigurationProto resources = 1; + optional DeviceConfigurationProto device = 2; +} diff --git a/protos/perfetto/trace/android/content/locale.proto b/protos/perfetto/trace/android/content/locale.proto new file mode 100644 index 0000000000..5e9c9ab3e6 --- /dev/null +++ b/protos/perfetto/trace/android/content/locale.proto @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2016 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 LocaleProto { + option deprecated = true; + + optional string language = 1; + optional string country = 2; + optional string variant = 3; + optional string script = 4; +} diff --git a/protos/perfetto/trace/android/privacy.proto b/protos/perfetto/trace/android/privacy.proto new file mode 100644 index 0000000000..82a68c7e2a --- /dev/null +++ b/protos/perfetto/trace/android/privacy.proto @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2016 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; + +import "google/protobuf/descriptor.proto"; + +enum Destination { + // Fields or messages annotated with DEST_LOCAL must never be + // extracted from the device automatically. They can be accessed + // by tools on the developer's workstation or test lab devices. + DEST_LOCAL = 0; + + // Fields or messages annotated with DEST_EXPLICIT can be sent + // off the device with an explicit user action. + DEST_EXPLICIT = 100; + + // Fields or messages annotated with DEST_AUTOMATIC can be sent by + // automatic means, without per-sending user consent. The user + // still must have previously accepted a consent to share this + // information. + DEST_AUTOMATIC = 200; + + // This is the default value, which could be overridden by other values. + // The reason to pick 255 is it fits into one byte. UNSET fields are treated + // as EXPLICIT. + DEST_UNSET = 255; + + // Currently use 0, 100, 200 and 255, values in between are reserved for futures. +} + +message PrivacyFlags { + optional Destination dest = 1 [ default = DEST_UNSET ]; + + // regex to filter pii sensitive info from a string field type. + repeated string patterns = 2; +} + +extend google.protobuf.FieldOptions { + // Flags used to annotate a field with right privacy level. + optional PrivacyFlags privacy = 102672883; +} + +extend google.protobuf.MessageOptions { + // Flags used to annotate a message which all its unset primitive fields inhert this tag. + optional PrivacyFlags msg_privacy = 102672883; +} + diff --git a/protos/perfetto/trace/android/server/animationadapter.proto b/protos/perfetto/trace/android/server/animationadapter.proto new file mode 100644 index 0000000000..d2f1f7549a --- /dev/null +++ b/protos/perfetto/trace/android/server/animationadapter.proto @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto2"; + +import "protos/perfetto/trace/android/graphics/point.proto"; +import "protos/perfetto/trace/android/view/remote_animation_target.proto"; + +package perfetto.protos; + +message AnimationAdapterProto { + optional LocalAnimationAdapterProto local = 1; + optional RemoteAnimationAdapterWrapperProto remote = 2; +} + +// represents RemoteAnimationAdapterWrapper +message RemoteAnimationAdapterWrapperProto { + optional RemoteAnimationTargetProto target = 1; +} + +// represents LocalAnimationAdapter +message LocalAnimationAdapterProto { + optional AnimationSpecProto animation_spec = 1; +} + +message AnimationSpecProto { + optional WindowAnimationSpecProto window = 1; + optional MoveAnimationSpecProto move = 2; + optional AlphaAnimationSpecProto alpha = 3; + optional RotationAnimationSpecProto rotate = 4; +} + +// represents WindowAnimationSpec +message WindowAnimationSpecProto { + optional string animation = 1; +} + +// represents MoveAnimationSpec +message MoveAnimationSpecProto { + optional PointProto from = 1; + optional PointProto to = 2; + optional int64 duration_ms = 3; +} + +// represents AlphaAnimationSpec +message AlphaAnimationSpecProto { + optional float from = 1; + optional float to = 2; + optional int64 duration_ms = 3; +} + +// represents RotationAnimationSpec +message RotationAnimationSpecProto { + optional float start_luma = 1; + optional float end_luma = 2; + optional int64 duration_ms = 3; +} diff --git a/protos/perfetto/trace/android/server/surfaceanimator.proto b/protos/perfetto/trace/android/server/surfaceanimator.proto new file mode 100644 index 0000000000..70e700de39 --- /dev/null +++ b/protos/perfetto/trace/android/server/surfaceanimator.proto @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto2"; + +import "protos/perfetto/trace/android/server/animationadapter.proto"; +import "protos/perfetto/trace/android/view/surfacecontrol.proto"; + +package perfetto.protos; + +// Represents a {@link com.android.server.wm.SurfaceAnimator} object +message SurfaceAnimatorProto { + optional SurfaceControlProto leash = 1; + optional bool animation_start_delayed = 2; + optional AnimationAdapterProto animation_adapter = 3; +} diff --git a/protos/perfetto/trace/android/server/windowcontainerthumbnail.proto b/protos/perfetto/trace/android/server/windowcontainerthumbnail.proto new file mode 100644 index 0000000000..1d19d2328b --- /dev/null +++ b/protos/perfetto/trace/android/server/windowcontainerthumbnail.proto @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto2"; + +import "protos/perfetto/trace/android/server/surfaceanimator.proto"; + +package perfetto.protos; + +// Represents a {@link com.android.server.wm.WindowContainerThumbnailProto} +// object +message WindowContainerThumbnailProto { + optional int32 width = 1; + optional int32 height = 2; + optional SurfaceAnimatorProto surface_animator = 3; +} diff --git a/protos/perfetto/trace/android/server/windowmanagerservice.proto b/protos/perfetto/trace/android/server/windowmanagerservice.proto new file mode 100644 index 0000000000..dcb45837f4 --- /dev/null +++ b/protos/perfetto/trace/android/server/windowmanagerservice.proto @@ -0,0 +1,590 @@ +/* + * Copyright (C) 2017 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"; + +import "protos/perfetto/trace/android/app/statusbarmanager.proto"; +import "protos/perfetto/trace/android/content/activityinfo.proto"; +import "protos/perfetto/trace/android/content/configuration.proto"; +import "protos/perfetto/trace/android/graphics/rect.proto"; +import "protos/perfetto/trace/android/server/windowcontainerthumbnail.proto"; +import "protos/perfetto/trace/android/server/surfaceanimator.proto"; +import "protos/perfetto/trace/android/view/displaycutout.proto"; +import "protos/perfetto/trace/android/view/displayinfo.proto"; +import "protos/perfetto/trace/android/view/surface.proto"; +import "protos/perfetto/trace/android/view/windowlayoutparams.proto"; +import "protos/perfetto/trace/android/privacy.proto"; +import "protos/perfetto/trace/android/typedef.proto"; + +import "protos/perfetto/trace/android/view/surfacecontrol.proto"; +import "protos/perfetto/trace/android/view/insetssource.proto"; +import "protos/perfetto/trace/android/view/insetssourcecontrol.proto"; + +import "protos/perfetto/trace/android/view/enums.proto"; + +package perfetto.protos; + +message WindowManagerServiceDumpProto { + optional WindowManagerPolicyProto policy = 1; + // window hierarchy root + optional RootWindowContainerProto root_window_container = 2; + optional IdentifierProto focused_window = 3; + optional string focused_app = 4; + optional IdentifierProto input_method_window = 5; + optional bool display_frozen = 6; + optional int32 rotation = 7 [ + (.perfetto.protos.typedef) = "android.view.Surface.Rotation", + deprecated = true + ]; + optional int32 last_orientation = 8 [ + (.perfetto.protos.typedef) = + "android.content.pm.ActivityInfo.ScreenOrientation", + deprecated = true + ]; + optional int32 focused_display_id = 9; + optional bool hard_keyboard_available = 10; + optional bool window_frames_valid = 11; + optional BackNavigationProto back_navigation = 12; +} + +// represents RootWindowContainer object +message RootWindowContainerProto { + optional WindowContainerProto window_container = 1; + repeated DisplayContentProto displays = 2 [deprecated = true]; + // IdentifierProto windows + reserved 3; + // window references in top down z order + repeated WindowStateProto windows = 4 [deprecated = true]; + optional KeyguardControllerProto keyguard_controller = 5; + // Whether or not the home activity is the recents activity. This is needed + // for the CTS tests to know what activity types to check for when invoking + // splitscreen multi-window. + optional bool is_home_recents_component = 6; + repeated IdentifierProto pending_activities = 7 [deprecated = true]; + optional int32 default_min_size_resizable_task = 8 [deprecated = true]; +} + +message BarControllerProto { + optional StatusBarManagerProto.WindowState state = 1; + optional StatusBarManagerProto.TransientWindowState transient_state = 2; +} + +message WindowOrientationListenerProto { + optional bool enabled = 1; + optional SurfaceProto.Rotation rotation = 2; +} + +message KeyguardServiceDelegateProto { + optional bool showing = 1; + optional bool occluded = 2; + optional bool secure = 3; + enum ScreenState { + SCREEN_STATE_OFF = 0; + SCREEN_STATE_TURNING_ON = 1; + SCREEN_STATE_ON = 2; + SCREEN_STATE_TURNING_OFF = 3; + } + optional ScreenState screen_state = 4; + enum InteractiveState { + INTERACTIVE_STATE_SLEEP = 0; + INTERACTIVE_STATE_WAKING = 1; + INTERACTIVE_STATE_AWAKE = 2; + INTERACTIVE_STATE_GOING_TO_SLEEP = 3; + } + optional InteractiveState interactive_state = 5; +} + +message KeyguardControllerProto { + optional bool keyguard_showing = 1; + repeated KeyguardOccludedProto keyguard_occluded_states = 2 + [deprecated = true]; + optional bool aod_showing = 3; + repeated KeyguardPerDisplayProto keyguard_per_display = 4; + optional bool keyguard_going_away = 5; +} + +message KeyguardOccludedProto { + optional int32 display_id = 1; + optional bool keyguard_occluded = 2; +} + +message KeyguardPerDisplayProto { + optional int32 display_id = 1; + optional bool keyguard_showing = 2; + optional bool aod_showing = 3; + optional bool keyguard_occluded = 4; + optional bool keyguard_going_away = 5; +} + +// represents PhoneWindowManager +message WindowManagerPolicyProto { + optional int32 last_system_ui_flags = 1 [deprecated = true]; + enum UserRotationMode { + USER_ROTATION_FREE = 0; + USER_ROTATION_LOCKED = 1; + } + optional UserRotationMode rotation_mode = 2; + optional SurfaceProto.Rotation rotation = 3; + optional ActivityInfoProto.ScreenOrientation orientation = 4; + optional bool screen_on_fully = 5; + optional bool keyguard_draw_complete = 6; + optional bool window_manager_draw_complete = 7; + optional string focused_app_token = 8 [deprecated = true]; + optional IdentifierProto focused_window = 9 [deprecated = true]; + optional IdentifierProto top_fullscreen_opaque_window = 10 + [deprecated = true]; + optional IdentifierProto top_fullscreen_opaque_or_dimming_window = 11 + [deprecated = true]; + optional bool keyguard_occluded = 12; + optional bool keyguard_occluded_changed = 13; + optional bool keyguard_occluded_pending = 14; + optional bool force_status_bar = 15 [deprecated = true]; + optional bool force_status_bar_from_keyguard = 16 [deprecated = true]; + optional BarControllerProto status_bar = 17 [deprecated = true]; + optional BarControllerProto navigation_bar = 18 [deprecated = true]; + optional WindowOrientationListenerProto orientation_listener = 19 + [deprecated = true]; + optional KeyguardServiceDelegateProto keyguard_delegate = 20; +} + +// represents AppTransition +message AppTransitionProto { + enum AppState { + APP_STATE_IDLE = 0; + APP_STATE_READY = 1; + APP_STATE_RUNNING = 2; + APP_STATE_TIMEOUT = 3; + } + optional AppState app_transition_state = 1; + + optional TransitionTypeEnum last_used_app_transition = 2; +} + +// represents DisplayContent object +message DisplayContentProto { + // Use root_display_area instead + optional WindowContainerProto window_container = 1 [deprecated = true]; + optional int32 id = 2; + // RootTasks + reserved 3; + optional DockedTaskDividerControllerProto docked_task_divider_controller = 4 + [deprecated = true]; + // Will be removed soon. + optional PinnedTaskControllerProto pinned_task_controller = 5 + [deprecated = true]; + // non app windows + repeated WindowTokenProto above_app_windows = 6 [deprecated = true]; + repeated WindowTokenProto below_app_windows = 7 [deprecated = true]; + repeated WindowTokenProto ime_windows = 8 [deprecated = true]; + optional int32 dpi = 9; + optional DisplayInfoProto display_info = 10; + optional int32 rotation = 11 [ + (.perfetto.protos.typedef) = "android.view.Surface.Rotation", + deprecated = true + ]; + optional ScreenRotationAnimationProto screen_rotation_animation = 12; + optional DisplayFramesProto display_frames = 13; + optional int32 surface_size = 14 [deprecated = true]; + optional string focused_app = 15; + optional AppTransitionProto app_transition = 16; + repeated IdentifierProto opening_apps = 17; + repeated IdentifierProto closing_apps = 18; + repeated IdentifierProto changing_apps = 19; + repeated WindowTokenProto overlay_windows = 20 [deprecated = true]; + optional DisplayAreaProto root_display_area = 21; + + optional bool single_task_instance = 22 [deprecated = true]; + optional int32 focused_root_task_id = 23; + optional IdentifierProto resumed_activity = 24; + repeated TaskProto tasks = 25 [deprecated = true]; + optional bool display_ready = 26; + optional WindowStateProto input_method_target = 27; + optional WindowStateProto input_method_input_target = 28; + optional WindowStateProto input_method_control_target = 29; + optional WindowStateProto current_focus = 30; + optional ImeInsetsSourceProviderProto ime_insets_source_provider = 31; + optional bool can_show_ime = 32 [deprecated = true]; + + optional DisplayRotationProto display_rotation = 33; + optional int32 ime_policy = 34; + + repeated InsetsSourceProviderProto insets_source_providers = 35; + optional bool is_sleeping = 36; + repeated string sleep_tokens = 37; + repeated RectProto keep_clear_areas = 38; + optional int32 min_size_of_resizeable_task_dp = 39; +} + +// represents DisplayArea object +message DisplayAreaProto { + optional WindowContainerProto window_container = 1; + optional string name = 2 [(.perfetto.protos.privacy).dest = DEST_EXPLICIT]; + repeated DisplayAreaChildProto children = 3 [deprecated = true]; + optional bool is_task_display_area = 4; + optional bool is_root_display_area = 5; + optional int32 feature_id = 6; + optional bool is_organized = 7; + optional bool is_ignoring_orientation_request = 8; +} + +// represents a generic child of a DisplayArea +message DisplayAreaChildProto { + // At most one of the following should be present: + + // represents a DisplayArea child + optional DisplayAreaProto display_area = 1; + // represents a WindowToken child + optional WindowTokenProto window = 2; + // represents an unknown child - the class name is recorded + repeated string unknown = 3; +} + +// represents DisplayFrames +message DisplayFramesProto { + optional RectProto stable_bounds = 1 [deprecated = true]; + optional RectProto dock = 2 [deprecated = true]; + optional RectProto current = 3 [deprecated = true]; +} + +message DisplayRotationProto { + optional int32 rotation = 1 + [(.perfetto.protos.typedef) = "android.view.Surface.Rotation"]; + optional bool frozen_to_user_rotation = 2; + optional int32 user_rotation = 3 + [(.perfetto.protos.typedef) = "android.view.Surface.Rotation"]; + optional int32 fixed_to_user_rotation_mode = 4; + optional int32 last_orientation = 5 + [(.perfetto.protos.typedef) = + "android.content.pm.ActivityInfo.ScreenOrientation"]; + optional bool is_fixed_to_user_rotation = 6; +} + +// represents DockedTaskDividerController +message DockedTaskDividerControllerProto { + optional bool minimized_dock = 1 [deprecated = true]; +} + +// represents PinnedTaskController +message PinnedTaskControllerProto { + optional RectProto default_bounds = 1 [deprecated = true]; + optional RectProto movement_bounds = 2 [deprecated = true]; +} + +// represents Task +message TaskProto { + optional WindowContainerProto window_container = 1 [deprecated = true]; + optional int32 id = 2; + // activity + reserved 3; + optional bool fills_parent = 4; + optional RectProto bounds = 5; + optional RectProto displayed_bounds = 6 [deprecated = true]; + optional bool defer_removal = 7; + optional int32 surface_width = 8; + optional int32 surface_height = 9; + + repeated TaskProto tasks = 10 [deprecated = true]; + repeated ActivityRecordProto activities = 11 [deprecated = true]; + + optional IdentifierProto resumed_activity = 12; + optional string real_activity = 13; + optional string orig_activity = 14; + + optional int32 display_id = 15 [deprecated = true]; + optional int32 root_task_id = 16; + optional int32 activity_type = 17 [ + (.perfetto.protos.typedef) = "android.app.WindowConfiguration.ActivityType", + deprecated = true + ]; + optional int32 resize_mode = 18 + [(.perfetto.protos.typedef) = + "android.appwidget.AppWidgetProviderInfo.ResizeModeFlags"]; + optional int32 min_width = 19 [deprecated = true]; + optional int32 min_height = 20 [deprecated = true]; + + optional RectProto adjusted_bounds = 21; + optional RectProto last_non_fullscreen_bounds = 22; + optional bool adjusted_for_ime = 23; + optional float adjust_ime_amount = 24; + optional float adjust_divider_amount = 25; + optional bool animating_bounds = 26 [deprecated = true]; + optional float minimize_amount = 27; + optional bool created_by_organizer = 28; + optional string affinity = 29; + optional bool has_child_pip_activity = 30; + optional TaskFragmentProto task_fragment = 31; +} + +// represents TaskFragment +message TaskFragmentProto { + optional WindowContainerProto window_container = 1; + optional int32 display_id = 2; + optional int32 activity_type = 3 + [(.perfetto.protos.typedef) = + "android.app.WindowConfiguration.ActivityType"]; + optional int32 min_width = 4; + optional int32 min_height = 5; +} + +// represents ActivityRecordProto +message ActivityRecordProto { + optional string name = 1 [(.perfetto.protos.privacy).dest = DEST_EXPLICIT]; + optional WindowTokenProto window_token = 2; + optional bool last_surface_showing = 3; + optional bool is_waiting_for_transition_start = 4; + optional bool is_animating = 5; + optional WindowContainerThumbnailProto thumbnail = 6; + optional bool fills_parent = 7; + optional bool app_stopped = 8; + optional bool visible_requested = 9; + optional bool client_visible = 10; + optional bool defer_hiding_client = 11; + optional bool reported_drawn = 12; + optional bool reported_visible = 13; + optional int32 num_interesting_windows = 14; + optional int32 num_drawn_windows = 15; + optional bool all_drawn = 16; + optional bool last_all_drawn = 17; + // removed + reserved 18; + optional IdentifierProto starting_window = 19; + optional bool starting_displayed = 20; + optional bool starting_moved = 201; + optional bool visible_set_from_transferred_starting_window = 22; + repeated RectProto frozen_bounds = 23 [deprecated = true]; + optional bool visible = 24; + // configuration_container + reserved 25; + optional IdentifierProto identifier = 26 [deprecated = true]; + optional string state = 27 [(.perfetto.protos.privacy).dest = DEST_EXPLICIT]; + optional bool front_of_task = 28; + optional int32 proc_id = 29; + optional bool translucent = 30; + optional bool pip_auto_enter_enabled = 31; + optional bool in_size_compat_mode = 32; + optional float min_aspect_ratio = 33; + optional bool provides_max_bounds = 34; + optional bool enable_recents_screenshot = 35; + optional int32 last_drop_input_mode = 36; + optional int32 override_orientation = 37 + [(.perfetto.protos.typedef) = + "android.content.pm.ActivityInfo.ScreenOrientation"]; + optional bool should_send_compat_fake_focus = 38; + optional bool should_force_rotate_for_camera_compat = 39; + optional bool should_refresh_activity_for_camera_compat = 40; + optional bool should_refresh_activity_via_pause_for_camera_compat = 41; + optional bool should_override_min_aspect_ratio = 42; + optional bool should_ignore_orientation_request_loop = 43; + optional bool should_override_force_resize_app = 44; + optional bool should_enable_user_aspect_ratio_settings = 45; + optional bool is_user_fullscreen_override_enabled = 46; +} + +// represents WindowToken +message WindowTokenProto { + optional WindowContainerProto window_container = 1; + optional int32 hash_code = 2; + repeated WindowStateProto windows = 3 [deprecated = true]; + optional bool waiting_to_show = 5 [deprecated = true]; + optional bool paused = 6; +} + +// represents WindowState +message WindowStateProto { + optional WindowContainerProto window_container = 1; + optional IdentifierProto identifier = 2 [deprecated = true]; + // Unique identifier of a DisplayContent stack. + optional int32 display_id = 3; + // Unique identifier for the task stack. + optional int32 stack_id = 4; + optional WindowLayoutParamsProto attributes = 5; + optional RectProto given_content_insets = 6; + optional RectProto frame = 7 [deprecated = true]; + optional RectProto containing_frame = 8 [deprecated = true]; + optional RectProto parent_frame = 9 [deprecated = true]; + optional RectProto content_frame = 10 [deprecated = true]; + optional RectProto content_insets = 11 [deprecated = true]; + optional RectProto surface_insets = 12; + optional WindowStateAnimatorProto animator = 13; + optional bool animating_exit = 14; + repeated WindowStateProto child_windows = 15 [deprecated = true]; + optional RectProto surface_position = 16; + optional int32 requested_width = 18; + optional int32 requested_height = 19; + optional int32 view_visibility = 20 + [(.perfetto.protos.typedef) = "android.view.View.Visibility"]; + optional int32 system_ui_visibility = 21 [deprecated = true]; + optional bool has_surface = 22; + optional bool is_ready_for_display = 23; + optional RectProto display_frame = 24 [deprecated = true]; + optional RectProto overscan_frame = 25 [deprecated = true]; + optional RectProto visible_frame = 26 [deprecated = true]; + optional RectProto decor_frame = 27 [deprecated = true]; + optional RectProto outset_frame = 28 [deprecated = true]; + optional RectProto overscan_insets = 29 [deprecated = true]; + optional RectProto visible_insets = 30 [deprecated = true]; + optional RectProto stable_insets = 31 [deprecated = true]; + optional RectProto outsets = 32 [deprecated = true]; + optional DisplayCutoutProto cutout = 33 [deprecated = true]; + optional bool remove_on_exit = 34; + optional bool destroying = 35; + optional bool removed = 36; + optional bool is_on_screen = 37; + optional bool is_visible = 38; + optional bool pending_seamless_rotation = 39; + optional int64 finished_seamless_rotation_frame = 40 [deprecated = true]; + optional WindowFramesProto window_frames = 41; + optional bool force_seamless_rotation = 42; + optional bool has_compat_scale = 43; + optional float global_scale = 44; + repeated RectProto keep_clear_areas = 45; + repeated RectProto unrestricted_keep_clear_areas = 46; + repeated InsetsSourceProto mergedLocalInsetsSources = 47; + optional int32 requested_visible_types = 48; +} + +message IdentifierProto { + optional int32 hash_code = 1; + optional int32 user_id = 2; + // Either a component name/string (eg: "com.android.settings/.FallbackHome") + // or a window title ("NavigationBar"). + optional string title = 3 [(.perfetto.protos.privacy).dest = DEST_EXPLICIT]; +} + +// represents WindowStateAnimator +message WindowStateAnimatorProto { + optional RectProto last_clip_rect = 1; + optional WindowSurfaceControllerProto surface = 2; + enum DrawState { + NO_SURFACE = 0; + DRAW_PENDING = 1; + COMMIT_DRAW_PENDING = 2; + READY_TO_SHOW = 3; + HAS_DRAWN = 4; + } + optional DrawState draw_state = 3; + optional RectProto system_decor_rect = 4; +} + +// represents WindowSurfaceController +message WindowSurfaceControllerProto { + optional bool shown = 1; + optional int32 layer = 2; +} + +// represents ScreenRotationAnimation +message ScreenRotationAnimationProto { + optional bool started = 1; + optional bool animation_running = 2; +} + +// represents WindowContainer +message WindowContainerProto { + optional ConfigurationContainerProto configuration_container = 1; + optional int32 orientation = 2 + [(.perfetto.protos.typedef) = + "android.content.pm.ActivityInfo.ScreenOrientation"]; + optional bool visible = 3; + optional SurfaceAnimatorProto surface_animator = 4; + repeated WindowContainerChildProto children = 5; + optional IdentifierProto identifier = 6; + optional SurfaceControlProto surface_control = 7; +} + +// represents a generic child of a WindowContainer +message WindowContainerChildProto { + // A window container can have multiple children of different types stored as + // a WindowContainerChildProto but each instance of WindowContainerChildProto + // can only contain a single type. + // + // We do not know the derived type and the class is dumped + // as a WindowContainer + optional WindowContainerProto window_container = 2; + // represents a DisplayContent child + optional DisplayContentProto display_content = 3; + // represents a DisplayArea child + optional DisplayAreaProto display_area = 4; + // represents a Task child + optional TaskProto task = 5; + // represents an ActivityRecord child + optional ActivityRecordProto activity = 6; + // represents a WindowToken child + optional WindowTokenProto window_token = 7; + // represents a WindowState child + optional WindowStateProto window = 8; + // represents a WindowState child + optional TaskFragmentProto task_fragment = 9; +} + +// represents ConfigurationContainer +message ConfigurationContainerProto { + optional ConfigurationProto override_configuration = 1; + optional ConfigurationProto full_configuration = 2; + optional ConfigurationProto merged_override_configuration = 3; +} + +// represents WindowFrames +message WindowFramesProto { + optional RectProto containing_frame = 1 [deprecated = true]; + optional RectProto content_frame = 2 [deprecated = true]; + optional RectProto decor_frame = 3 [deprecated = true]; + optional RectProto display_frame = 4; + optional RectProto frame = 5; + optional RectProto outset_frame = 6; + optional RectProto overscan_frame = 7 [deprecated = true]; + optional RectProto parent_frame = 8; + optional RectProto visible_frame = 9 [deprecated = true]; + optional DisplayCutoutProto cutout = 10 [deprecated = true]; + optional RectProto content_insets = 11 [deprecated = true]; + optional RectProto overscan_insets = 12 [deprecated = true]; + optional RectProto visible_insets = 13 [deprecated = true]; + optional RectProto stable_insets = 14 [deprecated = true]; + optional RectProto outsets = 15; + optional RectProto compat_frame = 16; +} + +message InsetsSourceProviderProto { + optional InsetsSourceProto source = 1; + optional RectProto frame = 2; + optional InsetsSourceControlProto fake_control = 3; + optional InsetsSourceControlProto control = 4; + optional WindowStateProto control_target = 5; + optional WindowStateProto pending_control_target = 6; + optional WindowStateProto fake_control_target = 7; + optional SurfaceControlProto captured_leash = 8; + optional RectProto ime_overridden_frame = 9 [deprecated = true]; + optional bool is_leash_ready_for_dispatching = 10; + optional bool client_visible = 11; + optional bool server_visible = 12; + optional bool seamless_rotating = 13; + optional int64 finish_seamless_rotate_frame_number = 14; + optional bool controllable = 15; + optional WindowStateProto source_window_state = 16; +} + +message ImeInsetsSourceProviderProto { + optional InsetsSourceProviderProto insets_source_provider = 1; + optional WindowStateProto ime_target_from_ime = 2; + optional bool is_ime_layout_drawn = 3 [deprecated = true]; +} + +message BackNavigationProto { + optional bool animation_in_progress = 1; + optional int32 last_back_type = 2; + optional bool show_wallpaper = 3; + optional string main_open_activity = 4; + optional bool animation_running = 5; +} \ No newline at end of file diff --git a/protos/perfetto/trace/android/surfaceflinger_common.proto b/protos/perfetto/trace/android/surfaceflinger_common.proto index 72f815971c..4d62935876 100644 --- a/protos/perfetto/trace/android/surfaceflinger_common.proto +++ b/protos/perfetto/trace/android/surfaceflinger_common.proto @@ -86,3 +86,9 @@ message ColorTransformProto { // This will be a 4x4 matrix of float values repeated float val = 1 [packed = true]; } + +enum TrustedOverlay { + UNSET = 0; + DISABLED = 1; + ENABLED = 2; +}; \ No newline at end of file diff --git a/protos/perfetto/trace/android/surfaceflinger_layers.proto b/protos/perfetto/trace/android/surfaceflinger_layers.proto index bdc40d826f..65f81fc7a2 100644 --- a/protos/perfetto/trace/android/surfaceflinger_layers.proto +++ b/protos/perfetto/trace/android/surfaceflinger_layers.proto @@ -233,6 +233,8 @@ message LayerProto { optional RectProto destination_frame = 57; optional uint32 original_id = 58; + + optional TrustedOverlay trusted_overlay = 59; } message PositionProto { diff --git a/protos/perfetto/trace/android/surfaceflinger_transactions.proto b/protos/perfetto/trace/android/surfaceflinger_transactions.proto index bd128d86b7..8b6c0c9240 100644 --- a/protos/perfetto/trace/android/surfaceflinger_transactions.proto +++ b/protos/perfetto/trace/android/surfaceflinger_transactions.proto @@ -285,6 +285,7 @@ message LayerState { optional uint32 fixed_transform_hint = 36; optional uint64 frame_number = 37; optional bool auto_refresh = 38; + // unused optional bool is_trusted_overlay = 39; optional RectProto buffer_crop = 40; optional RectProto destination_frame = 41; @@ -295,6 +296,8 @@ message LayerState { OBSCURED = 2; }; optional DropInputMode drop_input_mode = 42; + + optional TrustedOverlay trusted_overlay = 43; } message DisplayState { diff --git a/protos/perfetto/trace/android/view/displayinfo.proto b/protos/perfetto/trace/android/view/displayinfo.proto new file mode 100644 index 0000000000..c817ba3315 --- /dev/null +++ b/protos/perfetto/trace/android/view/displayinfo.proto @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2017 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"; + +import "protos/perfetto/trace/android/view/displaycutout.proto"; + +package perfetto.protos; + +// Represents DisplayInfo. Describes the characteristics of a particular +// logical display. +message DisplayInfoProto { + optional int32 logical_width = 1; + optional int32 logical_height = 2; + optional int32 app_width = 3; + optional int32 app_height = 4; + // The human-readable name of the display. + // Eg: "Built-in Screen" + optional string name = 5; + optional int32 flags = 6; + optional DisplayCutoutProto cutout = 7; +} diff --git a/protos/perfetto/trace/android/view/enums.proto b/protos/perfetto/trace/android/view/enums.proto new file mode 100644 index 0000000000..5ceca015c0 --- /dev/null +++ b/protos/perfetto/trace/android/view/enums.proto @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto2"; + +package perfetto.protos; + +// Screen states, primarily used by android/view/Display.java. +enum DisplayStateEnum { + // The display state is unknown. + DISPLAY_STATE_UNKNOWN = 0; + // The display state is off. + DISPLAY_STATE_OFF = 1; + // The display state is on. + DISPLAY_STATE_ON = 2; + // The display is dozing in a low power state; it is still on but is + // optimized for showing system-provided content while the device is + // non-interactive. + DISPLAY_STATE_DOZE = 3; + // The display is dozing in a suspended low power state; it is still on + // but is optimized for showing static system-provided content while the + // device is non-interactive. + DISPLAY_STATE_DOZE_SUSPEND = 4; + // The display is on and optimized for VR mode. + DISPLAY_STATE_VR = 5; + // The display is in a suspended full power state; it is still on but the + // CPU is not updating it. + DISPLAY_STATE_ON_SUSPEND = 6; +} + +// Screen state reasons. +enum DisplayStateReason { + // The display state reason is unknown. + DISPLAY_STATE_REASON_UNKNOWN = 0; + // The display state reason is the default policy. + DISPLAY_STATE_REASON_DEFAULT_POLICY = 1; + // The display state is caused by a draw wake lock. + DISPLAY_STATE_REASON_DRAW_WAKE_LOCK = 2; + // The display state is caused by display offloading. + DISPLAY_STATE_REASON_OFFLOAD = 3; + // The display state is caused by a tilt. + DISPLAY_STATE_REASON_TILT = 4; + // The display state is caused by the dream manager. + DISPLAY_STATE_REASON_DREAM_MANAGER = 5; + // The display state is caused by an input key event. + DISPLAY_STATE_REASON_KEY = 6; + // The display state is caused by an input motion event. + DISPLAY_STATE_REASON_MOTION = 7; +} + +// Constants found in android.view.WindowManager. +enum TransitionTypeEnum { + TRANSIT_NONE = 0; + TRANSIT_UNSET = -1; + TRANSIT_ACTIVITY_OPEN = 6; + TRANSIT_ACTIVITY_CLOSE = 7; + TRANSIT_TASK_OPEN = 8; + TRANSIT_TASK_CLOSE = 9; + TRANSIT_TASK_TO_FRONT = 10; + TRANSIT_TASK_TO_BACK = 11; + TRANSIT_WALLPAPER_CLOSE = 12; + TRANSIT_WALLPAPER_OPEN = 13; + TRANSIT_WALLPAPER_INTRA_OPEN = 14; + TRANSIT_WALLPAPER_INTRA_CLOSE = 15; + TRANSIT_TASK_OPEN_BEHIND = 16; + TRANSIT_TASK_IN_PLACE = 17; + TRANSIT_ACTIVITY_RELAUNCH = 18; + TRANSIT_DOCK_TASK_FROM_RECENTS = 19 [deprecated = true]; + TRANSIT_KEYGUARD_GOING_AWAY = 20; + TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER = 21; + TRANSIT_KEYGUARD_OCCLUDE = 22; + TRANSIT_KEYGUARD_UNOCCLUDE = 23; + TRANSIT_TRANSLUCENT_ACTIVITY_OPEN = 24; + TRANSIT_TRANSLUCENT_ACTIVITY_CLOSE = 25; + TRANSIT_CRASHING_ACTIVITY_CLOSE = 26; +} diff --git a/protos/perfetto/trace/android/view/remote_animation_target.proto b/protos/perfetto/trace/android/view/remote_animation_target.proto new file mode 100644 index 0000000000..ab4b004ef0 --- /dev/null +++ b/protos/perfetto/trace/android/view/remote_animation_target.proto @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto2"; + +import "protos/perfetto/trace/android/app/window_configuration.proto"; +import "protos/perfetto/trace/android/graphics/point.proto"; +import "protos/perfetto/trace/android/graphics/rect.proto"; +import "protos/perfetto/trace/android/view/surfacecontrol.proto"; + +package perfetto.protos; + +// Proto representation for android.view.RemoteAnimationTarget.java class +message RemoteAnimationTargetProto { + optional int32 task_id = 1; + optional int32 mode = 2; + optional SurfaceControlProto leash = 3; + optional bool is_translucent = 4; + optional RectProto clip_rect = 5; + optional RectProto content_insets = 6; + optional int32 prefix_order_index = 7; + // The source position of the app, in screen spaces coordinates. If the + // position of the leash is modified from the controlling app, any animation + // transform needs to be offset by this amount. + optional PointProto position = 8; + optional RectProto source_container_bounds = 9; + optional WindowConfigurationProto window_configuration = 10; + optional SurfaceControlProto start_leash = 11; + optional RectProto start_bounds = 12; + optional RectProto local_bounds = 13; + optional RectProto screen_space_bounds = 14; +} diff --git a/protos/perfetto/trace/android/view/surface.proto b/protos/perfetto/trace/android/view/surface.proto new file mode 100644 index 0000000000..571ef1ef3c --- /dev/null +++ b/protos/perfetto/trace/android/view/surface.proto @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto2"; + +package perfetto.protos; + +message SurfaceProto { + enum Rotation { + ROTATION_0 = 0; + ROTATION_90 = 1; + ROTATION_180 = 2; + ROTATION_270 = 3; + } +} diff --git a/protos/perfetto/trace/android/viewcapture.proto b/protos/perfetto/trace/android/viewcapture.proto new file mode 100644 index 0000000000..d5efb6c444 --- /dev/null +++ b/protos/perfetto/trace/android/viewcapture.proto @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto2"; + +package perfetto.protos; + +message ViewCapture { + optional int32 package_name_iid = 1; + optional int32 window_name_iid = 2; + repeated View views = 3; + + message View { + optional int32 id = 1; + optional int32 parent_id = 2; + optional int32 hashcode = 3; + optional int32 view_id_iid = 4; + optional int32 class_name_iid = 5; + + optional int32 left = 6; + optional int32 top = 7; + optional int32 width = 8; + optional int32 height = 9; + optional int32 scroll_x = 10; + optional int32 scroll_y = 11; + + optional float translation_x = 12; + optional float translation_y = 13; + optional float scale_x = 14; + optional float scale_y = 15; + optional float alpha = 16; + + optional bool will_not_draw = 17; + optional bool clip_children = 18; + optional int32 visibility = 19; + + optional float elevation = 20; + } +} diff --git a/protos/perfetto/trace/android/windowmanager.proto b/protos/perfetto/trace/android/windowmanager.proto new file mode 100644 index 0000000000..c508ec2ee8 --- /dev/null +++ b/protos/perfetto/trace/android/windowmanager.proto @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2017 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"; + +import "protos/perfetto/trace/android/server/windowmanagerservice.proto"; + +package perfetto.protos; + +// Represents a file full of window manager trace entries. +// Encoded, it should start with 0x9 0x57 0x49 0x4e 0x54 0x52 0x41 0x43 0x45 +// (.WINTRACE), such that they can be easily identified. +message WindowManagerTraceFileProto { + // constant; MAGIC_NUMBER = (long) MAGIC_NUMBER_H << 32 | + // MagicNumber.MAGIC_NUMBER_L (this is needed because enums have to be 32 bits + // and there's no nice way to put 64bit constants into .proto files. + enum MagicNumber { + INVALID = 0; + // WINT (little-endian ASCII) + MAGIC_NUMBER_L = 0x544e4957; + // RACE (little-endian ASCII) + MAGIC_NUMBER_H = 0x45434152; + } + + // Must be the first field, set to value in MagicNumber + optional fixed64 magic_number = 1; + repeated WindowManagerTraceEntry entry = 2; + + // offset between real-time clock and elapsed time clock in nanoseconds. + // Calculated as: 1000000 * System.currentTimeMillis() - + // SystemClock.elapsedRealtimeNanos() + optional fixed64 real_to_elapsed_time_offset_nanos = 3; +} + +// one window manager trace entry +message WindowManagerTraceEntry { + // required: elapsed realtime in nanos since boot of when this entry was + // logged + optional fixed64 elapsed_realtime_nanos = 1; + + // where the trace originated + optional string where = 2; + + optional WindowManagerServiceDumpProto window_manager_service = 3; +} diff --git a/protos/perfetto/trace/android/winscope.proto b/protos/perfetto/trace/android/winscope.proto index d7927d8744..c71daffa38 100644 --- a/protos/perfetto/trace/android/winscope.proto +++ b/protos/perfetto/trace/android/winscope.proto @@ -18,10 +18,12 @@ syntax = "proto2"; package perfetto.protos; +import "protos/perfetto/trace/android/android_input_event.proto"; import "protos/perfetto/trace/android/protolog.proto"; import "protos/perfetto/trace/android/shell_transition.proto"; import "protos/perfetto/trace/android/surfaceflinger_layers.proto"; import "protos/perfetto/trace/android/surfaceflinger_transactions.proto"; +import "protos/perfetto/trace/android/viewcapture.proto"; import "protos/perfetto/trace/android/winscope_extensions_impl.proto"; // This file is used to generated descriptors for all the winscope protos. @@ -32,4 +34,6 @@ message WinscopeTraceData { optional ShellTransition shell_transition = 3; optional ProtoLogMessage protolog_message = 4; optional WinscopeExtensionsImpl winscope_extensions = 5; + optional ViewCapture viewcapture = 6; + optional AndroidInputEvent android_input_event = 7; } diff --git a/protos/perfetto/trace/android/winscope_extensions.proto b/protos/perfetto/trace/android/winscope_extensions.proto index 76e6c9e48b..76a22519ff 100644 --- a/protos/perfetto/trace/android/winscope_extensions.proto +++ b/protos/perfetto/trace/android/winscope_extensions.proto @@ -19,5 +19,5 @@ syntax = "proto2"; package perfetto.protos; message WinscopeExtensions { - extensions 1 to 3; + extensions 1 to 6; } diff --git a/protos/perfetto/trace/android/winscope_extensions_impl.proto b/protos/perfetto/trace/android/winscope_extensions_impl.proto index bfc18e9773..386d6b0091 100644 --- a/protos/perfetto/trace/android/winscope_extensions_impl.proto +++ b/protos/perfetto/trace/android/winscope_extensions_impl.proto @@ -19,7 +19,10 @@ syntax = "proto2"; package perfetto.protos; import public "protos/perfetto/trace/android/winscope_extensions.proto"; +import "protos/perfetto/trace/android/android_input_event.proto"; import "protos/perfetto/trace/android/inputmethodeditor.proto"; +import "protos/perfetto/trace/android/viewcapture.proto"; +import "protos/perfetto/trace/android/windowmanager.proto"; message WinscopeExtensionsImpl { extend WinscopeExtensions { @@ -27,5 +30,8 @@ message WinscopeExtensionsImpl { optional InputMethodServiceTraceProto inputmethod_service = 2; optional InputMethodManagerServiceTraceProto inputmethod_manager_service = 3; + optional ViewCapture viewcapture = 4; + optional AndroidInputEvent android_input_event = 5; + optional WindowManagerTraceEntry windowmanager = 6; } } diff --git a/protos/perfetto/trace/ftrace/all_protos.gni b/protos/perfetto/trace/ftrace/all_protos.gni index ca78a55e25..1cb60859a7 100644 --- a/protos/perfetto/trace/ftrace/all_protos.gni +++ b/protos/perfetto/trace/ftrace/all_protos.gni @@ -21,6 +21,7 @@ ftrace_proto_names = [ "test_bundle_wrapper.proto", "generic.proto", "android_fs.proto", + "bcl_exynos.proto", "binder.proto", "block.proto", "cgroup.proto", @@ -29,6 +30,7 @@ ftrace_proto_names = [ "compaction.proto", "cpuhp.proto", "cros_ec.proto", + "dcvsh.proto", "dma_fence.proto", "dmabuf_heap.proto", "dpu.proto", @@ -49,6 +51,7 @@ ftrace_proto_names = [ "ion.proto", "ipi.proto", "irq.proto", + "kgsl.proto", "kmem.proto", "kvm.proto", "lowmemorykiller.proto", diff --git a/protos/perfetto/trace/ftrace/bcl_exynos.proto b/protos/perfetto/trace/ftrace/bcl_exynos.proto new file mode 100644 index 0000000000..801a2cac58 --- /dev/null +++ b/protos/perfetto/trace/ftrace/bcl_exynos.proto @@ -0,0 +1,18 @@ +// Autogenerated by: +// ../../src/tools/ftrace_proto_gen/ftrace_proto_gen.cc +// Do not edit. + +syntax = "proto2"; +package perfetto.protos; + +message BclIrqTriggerFtraceEvent { + optional int32 id = 1; + optional int32 throttle = 2; + optional int32 cpu0_limit = 3; + optional int32 cpu1_limit = 4; + optional int32 cpu2_limit = 5; + optional int32 tpu_limit = 6; + optional int32 gpu_limit = 7; + optional int32 voltage = 8; + optional int32 capacity = 9; +} diff --git a/protos/perfetto/trace/ftrace/dcvsh.proto b/protos/perfetto/trace/ftrace/dcvsh.proto new file mode 100644 index 0000000000..450cd7f703 --- /dev/null +++ b/protos/perfetto/trace/ftrace/dcvsh.proto @@ -0,0 +1,11 @@ +// Autogenerated by: +// ../../src/tools/ftrace_proto_gen/ftrace_proto_gen.cc +// Do not edit. + +syntax = "proto2"; +package perfetto.protos; + +message DcvshFreqFtraceEvent { + optional uint64 cpu = 1; + optional uint64 freq = 2; +} diff --git a/protos/perfetto/trace/ftrace/ftrace_event.proto b/protos/perfetto/trace/ftrace/ftrace_event.proto index 16984b7d09..6d9334e3ba 100644 --- a/protos/perfetto/trace/ftrace/ftrace_event.proto +++ b/protos/perfetto/trace/ftrace/ftrace_event.proto @@ -21,6 +21,7 @@ syntax = "proto2"; import "protos/perfetto/trace/ftrace/android_fs.proto"; +import "protos/perfetto/trace/ftrace/bcl_exynos.proto"; import "protos/perfetto/trace/ftrace/binder.proto"; import "protos/perfetto/trace/ftrace/block.proto"; import "protos/perfetto/trace/ftrace/cgroup.proto"; @@ -29,6 +30,7 @@ import "protos/perfetto/trace/ftrace/cma.proto"; import "protos/perfetto/trace/ftrace/compaction.proto"; import "protos/perfetto/trace/ftrace/cpuhp.proto"; import "protos/perfetto/trace/ftrace/cros_ec.proto"; +import "protos/perfetto/trace/ftrace/dcvsh.proto"; import "protos/perfetto/trace/ftrace/dma_fence.proto"; import "protos/perfetto/trace/ftrace/dmabuf_heap.proto"; import "protos/perfetto/trace/ftrace/dpu.proto"; @@ -49,6 +51,7 @@ import "protos/perfetto/trace/ftrace/i2c.proto"; import "protos/perfetto/trace/ftrace/ion.proto"; import "protos/perfetto/trace/ftrace/ipi.proto"; import "protos/perfetto/trace/ftrace/irq.proto"; +import "protos/perfetto/trace/ftrace/kgsl.proto"; import "protos/perfetto/trace/ftrace/kmem.proto"; import "protos/perfetto/trace/ftrace/kvm.proto"; import "protos/perfetto/trace/ftrace/lowmemorykiller.proto"; @@ -628,5 +631,44 @@ message FtraceEvent { ThermalExynosAcpmBulkFtraceEvent thermal_exynos_acpm_bulk = 506; ThermalExynosAcpmHighOverheadFtraceEvent thermal_exynos_acpm_high_overhead = 507; + DcvshFreqFtraceEvent dcvsh_freq = 508; + KgslGpuFrequencyFtraceEvent kgsl_gpu_frequency = 509; + MaliMaliPMMCUHCTLCORESDOWNSCALENOTIFYPENDFtraceEvent + mali_mali_PM_MCU_HCTL_CORES_DOWN_SCALE_NOTIFY_PEND = 510; + MaliMaliPMMCUHCTLCORESNOTIFYPENDFtraceEvent + mali_mali_PM_MCU_HCTL_CORES_NOTIFY_PEND = 511; + MaliMaliPMMCUHCTLCOREINACTIVEPENDFtraceEvent + mali_mali_PM_MCU_HCTL_CORE_INACTIVE_PEND = 512; + MaliMaliPMMCUHCTLMCUONRECHECKFtraceEvent + mali_mali_PM_MCU_HCTL_MCU_ON_RECHECK = 513; + MaliMaliPMMCUHCTLSHADERSCOREOFFPENDFtraceEvent + mali_mali_PM_MCU_HCTL_SHADERS_CORE_OFF_PEND = 514; + MaliMaliPMMCUHCTLSHADERSPENDOFFFtraceEvent + mali_mali_PM_MCU_HCTL_SHADERS_PEND_OFF = 515; + MaliMaliPMMCUHCTLSHADERSPENDONFtraceEvent + mali_mali_PM_MCU_HCTL_SHADERS_PEND_ON = 516; + MaliMaliPMMCUHCTLSHADERSREADYOFFFtraceEvent + mali_mali_PM_MCU_HCTL_SHADERS_READY_OFF = 517; + MaliMaliPMMCUINSLEEPFtraceEvent mali_mali_PM_MCU_IN_SLEEP = 518; + MaliMaliPMMCUOFFFtraceEvent mali_mali_PM_MCU_OFF = 519; + MaliMaliPMMCUONFtraceEvent mali_mali_PM_MCU_ON = 520; + MaliMaliPMMCUONCOREATTRUPDATEPENDFtraceEvent + mali_mali_PM_MCU_ON_CORE_ATTR_UPDATE_PEND = 521; + MaliMaliPMMCUONGLBREINITPENDFtraceEvent + mali_mali_PM_MCU_ON_GLB_REINIT_PEND = 522; + MaliMaliPMMCUONHALTFtraceEvent mali_mali_PM_MCU_ON_HALT = 523; + MaliMaliPMMCUONHWCNTDISABLEFtraceEvent mali_mali_PM_MCU_ON_HWCNT_DISABLE = + 524; + MaliMaliPMMCUONHWCNTENABLEFtraceEvent mali_mali_PM_MCU_ON_HWCNT_ENABLE = + 525; + MaliMaliPMMCUONPENDHALTFtraceEvent mali_mali_PM_MCU_ON_PEND_HALT = 526; + MaliMaliPMMCUONPENDSLEEPFtraceEvent mali_mali_PM_MCU_ON_PEND_SLEEP = 527; + MaliMaliPMMCUONSLEEPINITIATEFtraceEvent mali_mali_PM_MCU_ON_SLEEP_INITIATE = + 528; + MaliMaliPMMCUPENDOFFFtraceEvent mali_mali_PM_MCU_PEND_OFF = 529; + MaliMaliPMMCUPENDONRELOADFtraceEvent mali_mali_PM_MCU_PEND_ON_RELOAD = 530; + MaliMaliPMMCUPOWERDOWNFtraceEvent mali_mali_PM_MCU_POWER_DOWN = 531; + MaliMaliPMMCURESETWAITFtraceEvent mali_mali_PM_MCU_RESET_WAIT = 532; + BclIrqTriggerFtraceEvent bcl_irq_trigger = 533; } } diff --git a/protos/perfetto/trace/ftrace/kgsl.proto b/protos/perfetto/trace/ftrace/kgsl.proto new file mode 100644 index 0000000000..dcb03cab62 --- /dev/null +++ b/protos/perfetto/trace/ftrace/kgsl.proto @@ -0,0 +1,11 @@ +// Autogenerated by: +// ../../src/tools/ftrace_proto_gen/ftrace_proto_gen.cc +// Do not edit. + +syntax = "proto2"; +package perfetto.protos; + +message KgslGpuFrequencyFtraceEvent { + optional uint32 gpu_freq = 1; + optional uint32 gpu_id = 2; +} diff --git a/protos/perfetto/trace/ftrace/mali.proto b/protos/perfetto/trace/ftrace/mali.proto index 9d671666b2..9fccbcd38a 100644 --- a/protos/perfetto/trace/ftrace/mali.proto +++ b/protos/perfetto/trace/ftrace/mali.proto @@ -63,3 +63,118 @@ message MaliMaliCSFINTERRUPTENDFtraceEvent { optional uint32 kctx_id = 2; optional uint64 info_val = 3; } +message MaliMaliPMMCUHCTLCORESDOWNSCALENOTIFYPENDFtraceEvent { + optional int32 kctx_tgid = 1; + optional uint32 kctx_id = 2; + optional uint64 info_val = 3; +} +message MaliMaliPMMCUHCTLCORESNOTIFYPENDFtraceEvent { + optional int32 kctx_tgid = 1; + optional uint32 kctx_id = 2; + optional uint64 info_val = 3; +} +message MaliMaliPMMCUHCTLCOREINACTIVEPENDFtraceEvent { + optional int32 kctx_tgid = 1; + optional uint32 kctx_id = 2; + optional uint64 info_val = 3; +} +message MaliMaliPMMCUHCTLMCUONRECHECKFtraceEvent { + optional int32 kctx_tgid = 1; + optional uint32 kctx_id = 2; + optional uint64 info_val = 3; +} +message MaliMaliPMMCUHCTLSHADERSCOREOFFPENDFtraceEvent { + optional int32 kctx_tgid = 1; + optional uint32 kctx_id = 2; + optional uint64 info_val = 3; +} +message MaliMaliPMMCUHCTLSHADERSPENDOFFFtraceEvent { + optional int32 kctx_tgid = 1; + optional uint32 kctx_id = 2; + optional uint64 info_val = 3; +} +message MaliMaliPMMCUHCTLSHADERSPENDONFtraceEvent { + optional int32 kctx_tgid = 1; + optional uint32 kctx_id = 2; + optional uint64 info_val = 3; +} +message MaliMaliPMMCUHCTLSHADERSREADYOFFFtraceEvent { + optional int32 kctx_tgid = 1; + optional uint32 kctx_id = 2; + optional uint64 info_val = 3; +} +message MaliMaliPMMCUINSLEEPFtraceEvent { + optional int32 kctx_tgid = 1; + optional uint32 kctx_id = 2; + optional uint64 info_val = 3; +} +message MaliMaliPMMCUOFFFtraceEvent { + optional int32 kctx_tgid = 1; + optional uint32 kctx_id = 2; + optional uint64 info_val = 3; +} +message MaliMaliPMMCUONFtraceEvent { + optional int32 kctx_tgid = 1; + optional uint32 kctx_id = 2; + optional uint64 info_val = 3; +} +message MaliMaliPMMCUONCOREATTRUPDATEPENDFtraceEvent { + optional int32 kctx_tgid = 1; + optional uint32 kctx_id = 2; + optional uint64 info_val = 3; +} +message MaliMaliPMMCUONGLBREINITPENDFtraceEvent { + optional int32 kctx_tgid = 1; + optional uint32 kctx_id = 2; + optional uint64 info_val = 3; +} +message MaliMaliPMMCUONHALTFtraceEvent { + optional int32 kctx_tgid = 1; + optional uint32 kctx_id = 2; + optional uint64 info_val = 3; +} +message MaliMaliPMMCUONHWCNTDISABLEFtraceEvent { + optional int32 kctx_tgid = 1; + optional uint32 kctx_id = 2; + optional uint64 info_val = 3; +} +message MaliMaliPMMCUONHWCNTENABLEFtraceEvent { + optional int32 kctx_tgid = 1; + optional uint32 kctx_id = 2; + optional uint64 info_val = 3; +} +message MaliMaliPMMCUONPENDHALTFtraceEvent { + optional int32 kctx_tgid = 1; + optional uint32 kctx_id = 2; + optional uint64 info_val = 3; +} +message MaliMaliPMMCUONPENDSLEEPFtraceEvent { + optional int32 kctx_tgid = 1; + optional uint32 kctx_id = 2; + optional uint64 info_val = 3; +} +message MaliMaliPMMCUONSLEEPINITIATEFtraceEvent { + optional int32 kctx_tgid = 1; + optional uint32 kctx_id = 2; + optional uint64 info_val = 3; +} +message MaliMaliPMMCUPENDOFFFtraceEvent { + optional int32 kctx_tgid = 1; + optional uint32 kctx_id = 2; + optional uint64 info_val = 3; +} +message MaliMaliPMMCUPENDONRELOADFtraceEvent { + optional int32 kctx_tgid = 1; + optional uint32 kctx_id = 2; + optional uint64 info_val = 3; +} +message MaliMaliPMMCUPOWERDOWNFtraceEvent { + optional int32 kctx_tgid = 1; + optional uint32 kctx_id = 2; + optional uint64 info_val = 3; +} +message MaliMaliPMMCURESETWAITFtraceEvent { + optional int32 kctx_tgid = 1; + optional uint32 kctx_id = 2; + optional uint64 info_val = 3; +} diff --git a/protos/perfetto/trace/interned_data/interned_data.proto b/protos/perfetto/trace/interned_data/interned_data.proto index 2cae9c60ec..6c11be8aaa 100644 --- a/protos/perfetto/trace/interned_data/interned_data.proto +++ b/protos/perfetto/trace/interned_data/interned_data.proto @@ -55,7 +55,7 @@ package perfetto.protos; // emitted proactively in advance of referring to them in later packets. // // Next reserved id: 8 (up to 15). -// Next id: 36. +// Next id: 42. message InternedData { // TODO(eseckler): Replace iid fields inside interned messages with // map type fields in InternedData. @@ -137,4 +137,10 @@ message InternedData { repeated InternedString protolog_string_args = 36; // Interned protolog stacktraces. repeated InternedString protolog_stacktrace = 37; + + // viewcapture + repeated InternedString viewcapture_package_name = 38; + repeated InternedString viewcapture_window_name = 39; + repeated InternedString viewcapture_view_id = 40; + repeated InternedString viewcapture_class_name = 41; } diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto index 5df533909a..ad902e8ab8 100644 --- a/protos/perfetto/trace/perfetto_trace.proto +++ b/protos/perfetto/trace/perfetto_trace.proto @@ -367,99 +367,113 @@ message AndroidGameInterventionListConfig { // // Next ID: 5 message AndroidInputEventConfig { - - // Trace modes are tracing presets that are included in the system. - enum TraceMode { - // Preset mode for maximal tracing. - // WARNING: This will bypass all privacy measures on debuggable builds, and will record all - // input events processed by the system, regardless of the context in which they - // were processed. It should only be used for tracing on a local device or for - // tests. It should NEVER be used for field tracing. - TRACE_MODE_TRACE_ALL = 0; - // Use the tracing rules defined in this config to specify what events to trace. - TRACE_MODE_USE_RULES = 1; - } - - // The tracing mode to use. If unspecified, it will default to TRACE_MODE_USE_RULES. - optional TraceMode mode = 1; - - // The level of tracing that should be applied to an event. - enum TraceLevel { - // Do not trace the input event. - TRACE_LEVEL_NONE = 0; - // Trace the event as a redacted event, where certain sensitive fields are omitted from - // the trace, including the coordinates of pointer events and the key/scan codes of key - // events. - TRACE_LEVEL_REDACTED = 1; - // Trace the complete event. - TRACE_LEVEL_COMPLETE = 2; - } - - // A rule that specifies the TraceLevel for an event based on matching conditions. - // All matchers in the rule are optional. To trigger this rule, an event must match all - // of its specified matchers (i.e. the matchers function like a series of conditions connected - // by a logical 'AND' operator). A rule with no specified matchers will match all events. - // Next ID: 6 - message TraceRule { - // The trace level to be used for events that trigger this rule. - // If unspecified, TRACE_LEVEL_NONE will be used by default. - optional TraceLevel trace_level = 1; - - // --- Optional Matchers --- - - // Package matchers - // - // Respectively matches if all or any of the target apps for this event are contained in - // the specified list of package names. - // - // Intended usage: - // - Use match_all_packages to selectively allow tracing for the listed packages. - // - Use match_any_packages to selectively deny tracing for certain packages. - // - // WARNING: Great care must be taken when designing rules for field tracing! - // This is because each event is almost always sent to more than one app. - // For example, when allowing tracing for a package that has a spy window - // over the display (e.g. SystemUI) using match_any_packages, essentially all - // input will be recorded on that display. This is because the events will be sent - // to the spy as well as the foreground app, and regardless of what the foreground - // app is, the event will end up being traced. - // Alternatively, when attempting to block tracing for specific packages using - // match_all_packages, no events will likely be blocked. This is because the event - // will also be sent to other apps (such as, but not limited to, ones with spy - // windows), so the matcher will not match unless all other targets are also - // listed under the match_all_packages list. - repeated string match_all_packages = 2; - repeated string match_any_packages = 3; - - // Matches if the event is secure, which means that at least one of the targets of - // this event is using the window flag FLAG_SECURE. - optional bool match_secure = 4; - - // Matches if there was an active IME connection while this event was being processed. - optional bool match_ime_connection_active = 5; - } - - // The list of rules to use to determine the trace level of events. - // Each event will be traced using the TraceLevel of the first rule that it triggers - // from this list. The rules are evaluated in the order in which they are specified. - // If an event does not match any of the rules, TRACE_LEVEL_NONE will be used by default. - repeated TraceRule rules = 2; - - // --- Control flags --- - - // Trace input events processed by the system as they are being dispatched - // to application windows. All trace rules will apply. - // - If this flag is used without enabling trace_dispatcher_window_dispatch, it will - // trace InputDispatcher's inbound events (which does not include events synthesized - // within InputDispatcher) that match the rules. - // - If used with trace_dispatcher_window_dispatch, all inbound and outbound events - // matching the rules, including all events synthesized within InputDispatcher, - // will be traced. - optional bool trace_dispatcher_input_events = 3; - - // Trace details about which windows the system is sending each input event to. - // All trace rules will apply. - optional bool trace_dispatcher_window_dispatch = 4; + // Trace modes are tracing presets that are included in the system. + enum TraceMode { + // Preset mode for maximal tracing. + // WARNING: This will bypass all privacy measures on debuggable builds, and + // will record all + // input events processed by the system, regardless of the context + // in which they were processed. It should only be used for tracing + // on a local device or for tests. It should NEVER be used for + // field tracing. + TRACE_MODE_TRACE_ALL = 0; + // Use the tracing rules defined in this config to specify what events to + // trace. + TRACE_MODE_USE_RULES = 1; + } + + // The tracing mode to use. If unspecified, it will default to + // TRACE_MODE_USE_RULES. + optional TraceMode mode = 1; + + // The level of tracing that should be applied to an event. + enum TraceLevel { + // Do not trace the input event. + TRACE_LEVEL_NONE = 0; + // Trace the event as a redacted event, where certain sensitive fields are + // omitted from the trace, including the coordinates of pointer events and + // the key/scan codes of key events. + TRACE_LEVEL_REDACTED = 1; + // Trace the complete event. + TRACE_LEVEL_COMPLETE = 2; + } + + // A rule that specifies the TraceLevel for an event based on matching + // conditions. All matchers in the rule are optional. To trigger this rule, an + // event must match all of its specified matchers (i.e. the matchers function + // like a series of conditions connected by a logical 'AND' operator). A rule + // with no specified matchers will match all events. Next ID: 6 + message TraceRule { + // The trace level to be used for events that trigger this rule. + // If unspecified, TRACE_LEVEL_NONE will be used by default. + optional TraceLevel trace_level = 1; + + // --- Optional Matchers --- + + // Package matchers + // + // Respectively matches if all or any of the target apps for this event are + // contained in the specified list of package names. + // + // Intended usage: + // - Use match_all_packages to selectively allow tracing for the listed + // packages. + // - Use match_any_packages to selectively deny tracing for certain + // packages. + // + // WARNING: Great care must be taken when designing rules for field tracing! + // This is because each event is almost always sent to more than + // one app. + // For example, when allowing tracing for a package that has a + // spy window + // over the display (e.g. SystemUI) using match_any_packages, + // essentially all input will be recorded on that display. This is + // because the events will be sent to the spy as well as the + // foreground app, and regardless of what the foreground app is, + // the event will end up being traced. + // Alternatively, when attempting to block tracing for specific + // packages using + // match_all_packages, no events will likely be blocked. This is + // because the event will also be sent to other apps (such as, but + // not limited to, ones with spy windows), so the matcher will not + // match unless all other targets are also listed under the + // match_all_packages list. + repeated string match_all_packages = 2; + repeated string match_any_packages = 3; + + // Matches if the event is secure, which means that at least one of the + // targets of this event is using the window flag FLAG_SECURE. + optional bool match_secure = 4; + + // Matches if there was an active IME connection while this event was being + // processed. + optional bool match_ime_connection_active = 5; + } + + // The list of rules to use to determine the trace level of events. + // Each event will be traced using the TraceLevel of the first rule that it + // triggers from this list. The rules are evaluated in the order in which they + // are specified. If an event does not match any of the rules, + // TRACE_LEVEL_NONE will be used by default. + repeated TraceRule rules = 2; + + // --- Control flags --- + + // Trace input events processed by the system as they are being dispatched + // to application windows. All trace rules will apply. + // - If this flag is used without enabling trace_dispatcher_window_dispatch, + // it will + // trace InputDispatcher's inbound events (which does not include events + // synthesized within InputDispatcher) that match the rules. + // - If used with trace_dispatcher_window_dispatch, all inbound and outbound + // events + // matching the rules, including all events synthesized within + // InputDispatcher, will be traced. + optional bool trace_dispatcher_input_events = 3; + + // Trace details about which windows the system is sending each input event + // to. All trace rules will apply. + optional bool trace_dispatcher_window_dispatch = 4; } // End of protos/perfetto/config/android/android_input_event_config.proto @@ -801,6 +815,39 @@ message SurfaceFlingerTransactionsConfig { // End of protos/perfetto/config/android/surfaceflinger_transactions_config.proto +// Begin of protos/perfetto/config/android/windowmanager_config.proto + +// Custom configuration for the "android.windowmanager" data source. +message WindowManagerConfig { + enum LogFrequency { + LOG_FREQUENCY_UNSPECIFIED = 0; + + // Trace state snapshots when a frame is committed. + LOG_FREQUENCY_FRAME = 1; + + // Trace state snapshots every time a transaction is committed. + LOG_FREQUENCY_TRANSACTION = 2; + } + optional LogFrequency log_frequency = 1; + + enum LogLevel { + LOG_LEVEL_UNSPECIFIED = 0; + + // Logs all elements with maximum amount of information. + LOG_LEVEL_VERBOSE = 1; + + // Logs all elements but doesn't write all configuration data. + LOG_LEVEL_DEBUG = 2; + + // Logs only visible elements, with the minimum amount of performance + // overhead + LOG_LEVEL_CRITICAL = 3; + } + optional LogLevel log_level = 2; +} + +// End of protos/perfetto/config/android/windowmanager_config.proto + // Begin of protos/perfetto/config/chrome/chrome_config.proto message ChromeConfig { @@ -873,13 +920,17 @@ message EtwConfig { // Begin of protos/perfetto/config/ftrace/ftrace_config.proto -// Next id: 28 +// Next id: 29 message FtraceConfig { // Ftrace events to record, example: "sched/sched_switch". repeated string ftrace_events = 1; // Android-specific event categories: repeated string atrace_categories = 2; repeated string atrace_apps = 3; + // Some processes can emit data through atrace or through the perfetto SDK via + // the "track_event" data source. For these categories, the SDK will be + // preferred, if possible, for this config. + repeated string atrace_categories_prefer_sdk = 28; // Size of each per-cpu kernel ftrace ring buffer. // Not guaranteed if there are multiple concurrent tracing sessions, as the @@ -2845,6 +2896,11 @@ enum MeminfoCounters { MEMINFO_VMALLOC_CHUNK = 31; MEMINFO_CMA_TOTAL = 32; MEMINFO_CMA_FREE = 33; + MEMINFO_GPU = 34; + MEMINFO_ZRAM = 35; + MEMINFO_MISC = 36; + MEMINFO_ION_HEAP = 37; + MEMINFO_ION_HEAP_POOL = 38; } // Counter definitions for Linux's /proc/vmstat. @@ -3103,6 +3159,10 @@ message SysStatsConfig { // Polls /proc/pressure/* every X ms, if non-zero. // This is required to be > 10ms to avoid excessive CPU usage. optional uint32 psi_period_ms = 11; + + // Polls /sys/class/thermal/* every X ms, if non-zero. + // This is required to be > 10ms to avoid excessive CPU usage. + optional uint32 thermal_period_ms = 12; } // End of protos/perfetto/config/sys_stats/sys_stats_config.proto @@ -3257,7 +3317,7 @@ message TrackEventConfig { // Begin of protos/perfetto/config/data_source_config.proto // The configuration that is passed to each data source when starting tracing. -// Next id: 130 +// Next id: 131 message DataSourceConfig { enum SessionInitiator { SESSION_INITIATOR_UNSPECIFIED = 0; @@ -3403,11 +3463,15 @@ message DataSourceConfig { optional ProtoLogConfig protolog_config = 126 [lazy = true]; // Data source name: android.input.inputevent - optional AndroidInputEventConfig android_input_event_config = 128 [lazy = true]; + optional AndroidInputEventConfig android_input_event_config = 128 + [lazy = true]; // Data source name: android.pixel.modem optional PixelModemConfig pixel_modem_config = 129 [lazy = true]; + // Data source name: android.windowmanager + optional WindowManagerConfig windowmanager_config = 130 [lazy = true]; + // This is a fallback mechanism to send a free-form text config to the // producer. In theory this should never be needed. All the code that // is part of the platform (i.e. traced service) is supposed to *not* truncate @@ -3433,7 +3497,7 @@ message DataSourceConfig { // It contains the general config for the logging buffer(s) and the configs for // all the data source being enabled. // -// Next id: 39. +// Next id: 40. message TraceConfig { message BufferConfig { optional uint32 size_kb = 1; @@ -4099,6 +4163,51 @@ message TraceConfig { optional uint32 max_delay_ms = 2; } optional CmdTraceStartDelay cmd_trace_start_delay = 35; + + // When non-empty, ensures that for a each semaphore named `name at most + // `max_other_session_count`` *other* sessions (whose value is taken of the + // minimum of all values specified by this config or any already-running + // session) can be be running. + // + // If a semaphore "acquisition" fails, EnableTracing will return an error + // and the tracing session will not be started (or elgible to start in + // the case of deferred sessions). + // + // This is easiest to explain with an example. Suppose the tracing service has + // the following active tracing sessions: + // S1 = [{name=foo, max_other_session_count=2}, + // {name=bar, max_other_session_count=0}] + // S2 = [{name=foo, max_other_session_count=1}, + // {name=baz, max_other_session_count=1}] + // + // Then, for a new session, the following would be the expected behaviour of + // EnableSession given the state of `session_semaphores`. + // Q: session_semaphores = [] + // A: Allowed because it does not specify any semaphores. Will be allowed + // no matter the state of any other tracing session. + // Q: session_semaphores = [{name=baz, max_other_session_count=1}] + // A: Allowed because both S2 and this config specify + // max_other_session_count=1 for baz. + // Q: session_semaphores = [{name=foo, max_other_session_count=3}] + // A: Denied because S2 specified max_other_session_count=1 for foo and S1 + // takes that slot. + // Q: session_semaphores = [{name=bar, max_other_session_count=0}] + // A: Denied because S1 takes the the slot specified by both S1 and + // this config. + // + // Introduced in 24Q3 (Android V). + message SessionSemaphore { + // The name of the semaphore. Acts as a unique identifier across all + // tracing sessions (including the one being started). + optional string name = 1; + + // The maximum number of *other* sesssions which specify the same semaphore + // which can be active. The minimum of this value across all tracing + // sessions and the value specified by the config is used when deciding + // whether the tracing session can be started. + optional uint64 max_other_session_count = 2; + } + repeated SessionSemaphore session_semaphores = 39; } // End of protos/perfetto/config/trace_config.proto @@ -4343,125 +4452,6 @@ message AndroidGameInterventionList { // End of protos/perfetto/trace/android/android_game_intervention_list.proto -// Begin of protos/perfetto/trace/android/android_input_event.proto - -// A representation of an Android MotionEvent. -// See: https://developer.android.com/reference/android/view/MotionEvent -message AndroidMotionEvent { - // A representation of one pointer inside a MotionEvent. - // Each Pointer contains the values present in its corresponding - // MotionEvent.PointerCoords and MotionEvent.PointerProperties. - message Pointer { - message AxisValue { - optional int32 axis = 1; - optional float value = 2; - } - repeated AxisValue axis_value = 1; - optional int32 pointer_id = 2; - optional int32 tool_type = 3; - } - - // The randomly-generated ID used to track the event through the pipeline. - optional fixed32 event_id = 1; - // The event's timestamp - generated by the kernel. - optional int64 event_time_nanos = 2; - optional uint32 source = 3; - optional int32 action = 4; - optional int32 device_id = 5; - // Use a signed int for display_id, because -1 (DISPLAY_IS_NONE) is a common value. - optional sint32 display_id = 6; - optional int32 classification = 7; - optional uint32 flags = 8; - repeated Pointer pointer = 9; - - // Field numbers 10-15 are reserved for commonly used fields. - - // If this event was synthesized as a result of one or more different - // event, original_event_ids are the event_ids associated with the original events. - // For example, if this is an ACTION_HOVER_ENTER event that is synthesized - // due to an ACTION_HOVER_MOVE event entering the bounds of a window, the - // id of the original hover move event will be listed here. - repeated fixed32 original_event_id = 16 [packed = true]; - // The timestamp of the ACTION_DOWN event associated with this gesture. - optional int64 down_time_nanos = 17; - optional float cursor_position_x = 18; - optional float cursor_position_y = 19; - optional int32 action_button = 20; - optional uint32 button_state = 21; - optional uint32 meta_state = 22; - optional uint32 policy_flags = 23; - optional float precision_x = 24; - optional float precision_y = 25; -} - -// A representation of an Android KeyEvent. -// See: https://developer.android.com/reference/android/view/KeyEvent -message AndroidKeyEvent { - // The randomly-generated ID used to track the event through the pipeline. - optional fixed32 event_id = 1; - // The event's timestamp - generated by the kernel. - optional int64 event_time_nanos = 2; - // The timestamp of the ACTION_DOWN event associated with this gesture. - optional int64 down_time_nanos = 3; - optional uint32 source = 4; - optional int32 action = 5; - optional int32 device_id = 6; - // Use a signed int for display_id, because -1 (DISPLAY_IS_NONE) is a common value. - optional sint32 display_id = 7; - optional int32 key_code = 8; - optional uint32 scan_code = 9; - optional uint32 meta_state = 10; - optional int32 repeat_count = 11; - optional uint32 flags = 12; - optional uint32 policy_flags = 13; -} - -// An event that traces an input event being dispatched by the system to one window. -message AndroidWindowInputDispatchEvent { - // Stores x/y values for each pointer sent to the window for events - // that are dispatched to a certain location on the screen. This is not relevant - // for KeyEvents and MotionEvents that are dispatched to focused windows. - message DispatchedPointer { - optional int32 pointer_id = 1; - // The coordinates of the pointer in the logical display space, AKA "raw coordinates". - optional float x_in_display = 2; - optional float y_in_display = 3; - // The axis values for this pointer that were modified by the window transform. - repeated AndroidMotionEvent.Pointer.AxisValue axis_value_in_window = 4; - } - - // The event_id of the event that was dispatched to the window. - optional fixed32 event_id = 1; - // The vsync_id of the frame in which the decision was made to dispatch the event to - // the window. - optional int64 vsync_id = 2; - // The id of the window to which the event was dispatched. - optional int32 window_id = 3; - // Only relevant for MotionEvents that are dispatched to a screen location. - // Each DispatchedPointer has a 1:1 correspondence with the Pointers in the AndroidMotionEvent. - repeated DispatchedPointer dispatched_pointer = 4; - // The event flags that were used when dispatching the event to this window. - // If the same event is dispatched to more than one window, it is possible that they - // were dispatched using different flag values for each window. - optional uint32 resolved_flags = 5; -} - -message AndroidInputEvent { - oneof event { - // Traces input events received by or generated by InputDispatcher - AndroidMotionEvent dispatcher_motion_event = 1; - AndroidMotionEvent dispatcher_motion_event_redacted = 2; - AndroidKeyEvent dispatcher_key_event = 3; - AndroidKeyEvent dispatcher_key_event_redacted = 4; - - // Traces an event being dispatched to a window. - AndroidWindowInputDispatchEvent dispatcher_window_dispatch_event = 5; - AndroidWindowInputDispatchEvent dispatcher_window_dispatch_event_redacted = 6; - } -} - -// End of protos/perfetto/trace/android/android_input_event.proto - // Begin of protos/perfetto/trace/android/android_log.proto message AndroidLogPacket { @@ -5269,6 +5259,11 @@ message ColorTransformProto { repeated float val = 1 [packed = true]; } +enum TrustedOverlay { + UNSET = 0; + DISABLED = 1; + ENABLED = 2; +}; // End of protos/perfetto/trace/android/surfaceflinger_common.proto // Begin of protos/perfetto/trace/android/surfaceflinger_layers.proto @@ -5485,6 +5480,8 @@ message LayerProto { optional RectProto destination_frame = 57; optional uint32 original_id = 58; + + optional TrustedOverlay trusted_overlay = 59; } message PositionProto { @@ -5782,6 +5779,7 @@ message LayerState { optional uint32 fixed_transform_hint = 36; optional uint64 frame_number = 37; optional bool auto_refresh = 38; + // unused optional bool is_trusted_overlay = 39; optional RectProto buffer_crop = 40; optional RectProto destination_frame = 41; @@ -5792,6 +5790,8 @@ message LayerState { OBSCURED = 2; }; optional DropInputMode drop_input_mode = 42; + + optional TrustedOverlay trusted_overlay = 43; } message DisplayState { @@ -5819,7 +5819,7 @@ message DisplayState { // Begin of protos/perfetto/trace/android/winscope_extensions.proto message WinscopeExtensions { - extensions 1 to 3; + extensions 1 to 6; } // End of protos/perfetto/trace/android/winscope_extensions.proto @@ -6863,6 +6863,22 @@ message AndroidFsFsyncStartFtraceEvent { // End of protos/perfetto/trace/ftrace/android_fs.proto +// Begin of protos/perfetto/trace/ftrace/bcl_exynos.proto + +message BclIrqTriggerFtraceEvent { + optional int32 id = 1; + optional int32 throttle = 2; + optional int32 cpu0_limit = 3; + optional int32 cpu1_limit = 4; + optional int32 cpu2_limit = 5; + optional int32 tpu_limit = 6; + optional int32 gpu_limit = 7; + optional int32 voltage = 8; + optional int32 capacity = 9; +} + +// End of protos/perfetto/trace/ftrace/bcl_exynos.proto + // Begin of protos/perfetto/trace/ftrace/binder.proto message BinderTransactionFtraceEvent { @@ -7290,6 +7306,15 @@ message CrosEcSensorhubDataFtraceEvent { // End of protos/perfetto/trace/ftrace/cros_ec.proto +// Begin of protos/perfetto/trace/ftrace/dcvsh.proto + +message DcvshFreqFtraceEvent { + optional uint64 cpu = 1; + optional uint64 freq = 2; +} + +// End of protos/perfetto/trace/ftrace/dcvsh.proto + // Begin of protos/perfetto/trace/ftrace/dma_fence.proto message DmaFenceInitFtraceEvent { @@ -8713,6 +8738,15 @@ message IrqHandlerExitFtraceEvent { // End of protos/perfetto/trace/ftrace/irq.proto +// Begin of protos/perfetto/trace/ftrace/kgsl.proto + +message KgslGpuFrequencyFtraceEvent { + optional uint32 gpu_freq = 1; + optional uint32 gpu_id = 2; +} + +// End of protos/perfetto/trace/ftrace/kgsl.proto + // Begin of protos/perfetto/trace/ftrace/kmem.proto message AllocPagesIommuEndFtraceEvent { @@ -9204,6 +9238,121 @@ message MaliMaliCSFINTERRUPTENDFtraceEvent { optional uint32 kctx_id = 2; optional uint64 info_val = 3; } +message MaliMaliPMMCUHCTLCORESDOWNSCALENOTIFYPENDFtraceEvent { + optional int32 kctx_tgid = 1; + optional uint32 kctx_id = 2; + optional uint64 info_val = 3; +} +message MaliMaliPMMCUHCTLCORESNOTIFYPENDFtraceEvent { + optional int32 kctx_tgid = 1; + optional uint32 kctx_id = 2; + optional uint64 info_val = 3; +} +message MaliMaliPMMCUHCTLCOREINACTIVEPENDFtraceEvent { + optional int32 kctx_tgid = 1; + optional uint32 kctx_id = 2; + optional uint64 info_val = 3; +} +message MaliMaliPMMCUHCTLMCUONRECHECKFtraceEvent { + optional int32 kctx_tgid = 1; + optional uint32 kctx_id = 2; + optional uint64 info_val = 3; +} +message MaliMaliPMMCUHCTLSHADERSCOREOFFPENDFtraceEvent { + optional int32 kctx_tgid = 1; + optional uint32 kctx_id = 2; + optional uint64 info_val = 3; +} +message MaliMaliPMMCUHCTLSHADERSPENDOFFFtraceEvent { + optional int32 kctx_tgid = 1; + optional uint32 kctx_id = 2; + optional uint64 info_val = 3; +} +message MaliMaliPMMCUHCTLSHADERSPENDONFtraceEvent { + optional int32 kctx_tgid = 1; + optional uint32 kctx_id = 2; + optional uint64 info_val = 3; +} +message MaliMaliPMMCUHCTLSHADERSREADYOFFFtraceEvent { + optional int32 kctx_tgid = 1; + optional uint32 kctx_id = 2; + optional uint64 info_val = 3; +} +message MaliMaliPMMCUINSLEEPFtraceEvent { + optional int32 kctx_tgid = 1; + optional uint32 kctx_id = 2; + optional uint64 info_val = 3; +} +message MaliMaliPMMCUOFFFtraceEvent { + optional int32 kctx_tgid = 1; + optional uint32 kctx_id = 2; + optional uint64 info_val = 3; +} +message MaliMaliPMMCUONFtraceEvent { + optional int32 kctx_tgid = 1; + optional uint32 kctx_id = 2; + optional uint64 info_val = 3; +} +message MaliMaliPMMCUONCOREATTRUPDATEPENDFtraceEvent { + optional int32 kctx_tgid = 1; + optional uint32 kctx_id = 2; + optional uint64 info_val = 3; +} +message MaliMaliPMMCUONGLBREINITPENDFtraceEvent { + optional int32 kctx_tgid = 1; + optional uint32 kctx_id = 2; + optional uint64 info_val = 3; +} +message MaliMaliPMMCUONHALTFtraceEvent { + optional int32 kctx_tgid = 1; + optional uint32 kctx_id = 2; + optional uint64 info_val = 3; +} +message MaliMaliPMMCUONHWCNTDISABLEFtraceEvent { + optional int32 kctx_tgid = 1; + optional uint32 kctx_id = 2; + optional uint64 info_val = 3; +} +message MaliMaliPMMCUONHWCNTENABLEFtraceEvent { + optional int32 kctx_tgid = 1; + optional uint32 kctx_id = 2; + optional uint64 info_val = 3; +} +message MaliMaliPMMCUONPENDHALTFtraceEvent { + optional int32 kctx_tgid = 1; + optional uint32 kctx_id = 2; + optional uint64 info_val = 3; +} +message MaliMaliPMMCUONPENDSLEEPFtraceEvent { + optional int32 kctx_tgid = 1; + optional uint32 kctx_id = 2; + optional uint64 info_val = 3; +} +message MaliMaliPMMCUONSLEEPINITIATEFtraceEvent { + optional int32 kctx_tgid = 1; + optional uint32 kctx_id = 2; + optional uint64 info_val = 3; +} +message MaliMaliPMMCUPENDOFFFtraceEvent { + optional int32 kctx_tgid = 1; + optional uint32 kctx_id = 2; + optional uint64 info_val = 3; +} +message MaliMaliPMMCUPENDONRELOADFtraceEvent { + optional int32 kctx_tgid = 1; + optional uint32 kctx_id = 2; + optional uint64 info_val = 3; +} +message MaliMaliPMMCUPOWERDOWNFtraceEvent { + optional int32 kctx_tgid = 1; + optional uint32 kctx_id = 2; + optional uint64 info_val = 3; +} +message MaliMaliPMMCURESETWAITFtraceEvent { + optional int32 kctx_tgid = 1; + optional uint32 kctx_id = 2; + optional uint64 info_val = 3; +} // End of protos/perfetto/trace/ftrace/mali.proto @@ -10841,6 +10990,45 @@ message FtraceEvent { ThermalExynosAcpmBulkFtraceEvent thermal_exynos_acpm_bulk = 506; ThermalExynosAcpmHighOverheadFtraceEvent thermal_exynos_acpm_high_overhead = 507; + DcvshFreqFtraceEvent dcvsh_freq = 508; + KgslGpuFrequencyFtraceEvent kgsl_gpu_frequency = 509; + MaliMaliPMMCUHCTLCORESDOWNSCALENOTIFYPENDFtraceEvent + mali_mali_PM_MCU_HCTL_CORES_DOWN_SCALE_NOTIFY_PEND = 510; + MaliMaliPMMCUHCTLCORESNOTIFYPENDFtraceEvent + mali_mali_PM_MCU_HCTL_CORES_NOTIFY_PEND = 511; + MaliMaliPMMCUHCTLCOREINACTIVEPENDFtraceEvent + mali_mali_PM_MCU_HCTL_CORE_INACTIVE_PEND = 512; + MaliMaliPMMCUHCTLMCUONRECHECKFtraceEvent + mali_mali_PM_MCU_HCTL_MCU_ON_RECHECK = 513; + MaliMaliPMMCUHCTLSHADERSCOREOFFPENDFtraceEvent + mali_mali_PM_MCU_HCTL_SHADERS_CORE_OFF_PEND = 514; + MaliMaliPMMCUHCTLSHADERSPENDOFFFtraceEvent + mali_mali_PM_MCU_HCTL_SHADERS_PEND_OFF = 515; + MaliMaliPMMCUHCTLSHADERSPENDONFtraceEvent + mali_mali_PM_MCU_HCTL_SHADERS_PEND_ON = 516; + MaliMaliPMMCUHCTLSHADERSREADYOFFFtraceEvent + mali_mali_PM_MCU_HCTL_SHADERS_READY_OFF = 517; + MaliMaliPMMCUINSLEEPFtraceEvent mali_mali_PM_MCU_IN_SLEEP = 518; + MaliMaliPMMCUOFFFtraceEvent mali_mali_PM_MCU_OFF = 519; + MaliMaliPMMCUONFtraceEvent mali_mali_PM_MCU_ON = 520; + MaliMaliPMMCUONCOREATTRUPDATEPENDFtraceEvent + mali_mali_PM_MCU_ON_CORE_ATTR_UPDATE_PEND = 521; + MaliMaliPMMCUONGLBREINITPENDFtraceEvent + mali_mali_PM_MCU_ON_GLB_REINIT_PEND = 522; + MaliMaliPMMCUONHALTFtraceEvent mali_mali_PM_MCU_ON_HALT = 523; + MaliMaliPMMCUONHWCNTDISABLEFtraceEvent mali_mali_PM_MCU_ON_HWCNT_DISABLE = + 524; + MaliMaliPMMCUONHWCNTENABLEFtraceEvent mali_mali_PM_MCU_ON_HWCNT_ENABLE = + 525; + MaliMaliPMMCUONPENDHALTFtraceEvent mali_mali_PM_MCU_ON_PEND_HALT = 526; + MaliMaliPMMCUONPENDSLEEPFtraceEvent mali_mali_PM_MCU_ON_PEND_SLEEP = 527; + MaliMaliPMMCUONSLEEPINITIATEFtraceEvent mali_mali_PM_MCU_ON_SLEEP_INITIATE = + 528; + MaliMaliPMMCUPENDOFFFtraceEvent mali_mali_PM_MCU_PEND_OFF = 529; + MaliMaliPMMCUPENDONRELOADFtraceEvent mali_mali_PM_MCU_PEND_ON_RELOAD = 530; + MaliMaliPMMCUPOWERDOWNFtraceEvent mali_mali_PM_MCU_POWER_DOWN = 531; + MaliMaliPMMCURESETWAITFtraceEvent mali_mali_PM_MCU_RESET_WAIT = 532; + BclIrqTriggerFtraceEvent bcl_irq_trigger = 533; } } @@ -12093,7 +12281,10 @@ message ChromeFrameReporter { optional bool has_smooth_input_main = 9; // Whether the frame contained any missing content (i.e. whether there was - // checkerboarding in the frame). + // checkerboarding in the frame). If has_missing_content, + // checkerboarded_needs_raster and checkerboarded_needs_record all have + // values, has_missing_content should equal checkerboarded_needs_raster || + // checkerboarded_needs_record. optional bool has_missing_content = 10; // The id of layer_tree_host that the frame has been produced for. @@ -12115,6 +12306,12 @@ message ChromeFrameReporter { // The breakdown stage of PipelineReporter that is most likely accountable for // high latency. repeated string high_latency_contribution_stage = 14; + + // Whether the frame contained any content that is not fully rastered. + optional bool checkerboarded_needs_raster = 15; + + // Whether the frame contained any content that is not fully recorded. + optional bool checkerboarded_needs_record = 16; } // End of protos/perfetto/trace/track_event/chrome_frame_reporter.proto @@ -12644,8 +12841,11 @@ message TrackEvent { extensions 1000 to 1999; // Extension range reserved for https://b.corp.google.com/issues/301227627. extensions 2000 to 2000; + // Extension range reserved for android: + // protos/perfetto/trace/android/android_track_event.proto + extensions 2001 to 2999; // Extension range for future use. - extensions 2001 to 9899; + extensions 3000 to 9899; // Reserved for Perfetto unit and integration tests. extensions 9900 to 10000; @@ -12820,7 +13020,7 @@ message EventName { // emitted proactively in advance of referring to them in later packets. // // Next reserved id: 8 (up to 15). -// Next id: 36. +// Next id: 42. message InternedData { // TODO(eseckler): Replace iid fields inside interned messages with // map type fields in InternedData. @@ -12902,6 +13102,12 @@ message InternedData { repeated InternedString protolog_string_args = 36; // Interned protolog stacktraces. repeated InternedString protolog_stacktrace = 37; + + // viewcapture + repeated InternedString viewcapture_package_name = 38; + repeated InternedString viewcapture_window_name = 39; + repeated InternedString viewcapture_view_id = 40; + repeated InternedString viewcapture_class_name = 41; } // End of protos/perfetto/trace/interned_data/interned_data.proto @@ -13895,6 +14101,7 @@ message ProcessStats { optional uint64 smr_pss_anon_kb = 18; optional uint64 smr_pss_file_kb = 19; optional uint64 smr_pss_shmem_kb = 20; + optional uint64 smr_swap_pss_kb = 23; // Time spent scheduled in user mode in nanoseconds. Parsed from utime in // /proc/pid/stat. Recorded if record_process_runtime config option is set. @@ -14172,6 +14379,14 @@ message SysStats { } // One entry per PsiResource type. repeated PsiSample psi = 14; + + // Reading from /sys/class/thermal/*. + message ThermalZone { + optional string name = 1; + optional uint64 temp = 2; + optional string type = 3; + } + repeated ThermalZone thermal_zone = 15; } // End of protos/perfetto/trace/sys_stats/sys_stats.proto @@ -14189,6 +14404,12 @@ message SystemInfo { optional Utsname utsname = 1; optional string android_build_fingerprint = 2; + // The SoC model from which trace is collected + optional string android_soc_model = 9; + + // The hardware reversion from android device + optional string android_hardware_revision = 10; + // The version of traced (the same returned by `traced --version`). // This is a human readable string with and its format varies depending on // the build system and the repo (standalone vs AOSP). @@ -14232,6 +14453,10 @@ message CpuInfo { // /sys/devices/system/cpu/cpuX/cpufreq/scaling_available_frequencies // where X is the index of this CPU. repeated uint32 frequencies = 2; + + // Cpu capacity from /sys/devices/system/cpu/cpuX/cpu_capacity where X is + // the index of this CPU. + optional uint32 capacity = 3; } // Describes available CPUs, one entry per CPU. @@ -14748,6 +14973,7 @@ message TranslationTable { ChromeUserEventTranslationTable chrome_user_event = 2; ChromePerformanceMarkTranslationTable chrome_performance_mark = 3; SliceNameTranslationTable slice_name = 4; + ProcessTrackNameTranslationTable process_track_name = 5; } } @@ -14772,6 +14998,11 @@ message SliceNameTranslationTable { map raw_to_deobfuscated_name = 1; }; +// Raw -> deobfuscated process track name translation rules. +message ProcessTrackNameTranslationTable { + map raw_to_deobfuscated_name = 1; +}; + // End of protos/perfetto/trace/translation/translation_table.proto // Begin of protos/perfetto/trace/trigger.proto @@ -14986,10 +15217,6 @@ message TracePacket { V8WasmCode v8_wasm_code = 101; V8RegExpCode v8_reg_exp_code = 102; V8CodeMove v8_code_move = 103; - - // InputFlinger traces - AndroidInputEvent android_input_event = 106; - // Clock synchronization with remote machines. RemoteClockSync remote_clock_sync = 107; @@ -15005,12 +15232,17 @@ message TracePacket { TestEvent for_testing = 900; } + // AndroidInputEvent android_input_event (moved to winscope_extensions) + reserved 106; + // Trusted user id of the producer which generated this packet. Keep in sync // with TrustedPacket.trusted_uid. // // TODO(eseckler): Emit this field in a PacketSequenceDescriptor message // instead. - oneof optional_trusted_uid { int32 trusted_uid = 3; }; + oneof optional_trusted_uid { + int32 trusted_uid = 3; + }; // Service-assigned identifier of the packet sequence this packet belongs to. // Uniquely identifies a producer + writer pair within the tracing session. A diff --git a/protos/perfetto/trace/ps/process_stats.proto b/protos/perfetto/trace/ps/process_stats.proto index 51756257da..f60cb68130 100644 --- a/protos/perfetto/trace/ps/process_stats.proto +++ b/protos/perfetto/trace/ps/process_stats.proto @@ -83,6 +83,7 @@ message ProcessStats { optional uint64 smr_pss_anon_kb = 18; optional uint64 smr_pss_file_kb = 19; optional uint64 smr_pss_shmem_kb = 20; + optional uint64 smr_swap_pss_kb = 23; // Time spent scheduled in user mode in nanoseconds. Parsed from utime in // /proc/pid/stat. Recorded if record_process_runtime config option is set. diff --git a/protos/perfetto/trace/sys_stats/sys_stats.proto b/protos/perfetto/trace/sys_stats/sys_stats.proto index 60883cf559..02ee07a998 100644 --- a/protos/perfetto/trace/sys_stats/sys_stats.proto +++ b/protos/perfetto/trace/sys_stats/sys_stats.proto @@ -159,4 +159,12 @@ message SysStats { } // One entry per PsiResource type. repeated PsiSample psi = 14; + + // Reading from /sys/class/thermal/*. + message ThermalZone { + optional string name = 1; + optional uint64 temp = 2; + optional string type = 3; + } + repeated ThermalZone thermal_zone = 15; } diff --git a/protos/perfetto/trace/system_info.proto b/protos/perfetto/trace/system_info.proto index 9f38848997..63fe2fca3a 100644 --- a/protos/perfetto/trace/system_info.proto +++ b/protos/perfetto/trace/system_info.proto @@ -29,6 +29,12 @@ message SystemInfo { optional Utsname utsname = 1; optional string android_build_fingerprint = 2; + // The SoC model from which trace is collected + optional string android_soc_model = 9; + + // The hardware reversion from android device + optional string android_hardware_revision = 10; + // The version of traced (the same returned by `traced --version`). // This is a human readable string with and its format varies depending on // the build system and the repo (standalone vs AOSP). diff --git a/protos/perfetto/trace/system_info/cpu_info.proto b/protos/perfetto/trace/system_info/cpu_info.proto index 62f17e0392..5e04af2b5e 100644 --- a/protos/perfetto/trace/system_info/cpu_info.proto +++ b/protos/perfetto/trace/system_info/cpu_info.proto @@ -29,6 +29,10 @@ message CpuInfo { // /sys/devices/system/cpu/cpuX/cpufreq/scaling_available_frequencies // where X is the index of this CPU. repeated uint32 frequencies = 2; + + // Cpu capacity from /sys/devices/system/cpu/cpuX/cpu_capacity where X is + // the index of this CPU. + optional uint32 capacity = 3; } // Describes available CPUs, one entry per CPU. diff --git a/protos/perfetto/trace/trace_packet.proto b/protos/perfetto/trace/trace_packet.proto index c7426ee7aa..b6489e0937 100644 --- a/protos/perfetto/trace/trace_packet.proto +++ b/protos/perfetto/trace/trace_packet.proto @@ -20,7 +20,6 @@ import "protos/perfetto/common/trace_stats.proto"; import "protos/perfetto/config/trace_config.proto"; import "protos/perfetto/trace/extension_descriptor.proto"; import "protos/perfetto/trace/android/android_game_intervention_list.proto"; -import "protos/perfetto/trace/android/android_input_event.proto"; import "protos/perfetto/trace/android/android_log.proto"; import "protos/perfetto/trace/android/android_system_property.proto"; import "protos/perfetto/trace/android/camera_event.proto"; @@ -239,10 +238,6 @@ message TracePacket { V8WasmCode v8_wasm_code = 101; V8RegExpCode v8_reg_exp_code = 102; V8CodeMove v8_code_move = 103; - - // InputFlinger traces - AndroidInputEvent android_input_event = 106; - // Clock synchronization with remote machines. RemoteClockSync remote_clock_sync = 107; @@ -258,12 +253,17 @@ message TracePacket { TestEvent for_testing = 900; } + // AndroidInputEvent android_input_event (moved to winscope_extensions) + reserved 106; + // Trusted user id of the producer which generated this packet. Keep in sync // with TrustedPacket.trusted_uid. // // TODO(eseckler): Emit this field in a PacketSequenceDescriptor message // instead. - oneof optional_trusted_uid { int32 trusted_uid = 3; }; + oneof optional_trusted_uid { + int32 trusted_uid = 3; + }; // Service-assigned identifier of the packet sequence this packet belongs to. // Uniquely identifies a producer + writer pair within the tracing session. A diff --git a/protos/perfetto/trace/track_event/chrome_frame_reporter.proto b/protos/perfetto/trace/track_event/chrome_frame_reporter.proto index cc841df8b9..7ce2dbd85e 100644 --- a/protos/perfetto/trace/track_event/chrome_frame_reporter.proto +++ b/protos/perfetto/trace/track_event/chrome_frame_reporter.proto @@ -89,7 +89,10 @@ message ChromeFrameReporter { optional bool has_smooth_input_main = 9; // Whether the frame contained any missing content (i.e. whether there was - // checkerboarding in the frame). + // checkerboarding in the frame). If has_missing_content, + // checkerboarded_needs_raster and checkerboarded_needs_record all have + // values, has_missing_content should equal checkerboarded_needs_raster || + // checkerboarded_needs_record. optional bool has_missing_content = 10; // The id of layer_tree_host that the frame has been produced for. @@ -111,4 +114,10 @@ message ChromeFrameReporter { // The breakdown stage of PipelineReporter that is most likely accountable for // high latency. repeated string high_latency_contribution_stage = 14; + + // Whether the frame contained any content that is not fully rastered. + optional bool checkerboarded_needs_raster = 15; + + // Whether the frame contained any content that is not fully recorded. + optional bool checkerboarded_needs_record = 16; } diff --git a/protos/perfetto/trace/track_event/track_event.proto b/protos/perfetto/trace/track_event/track_event.proto index 2b9d35c255..f7792f6f37 100644 --- a/protos/perfetto/trace/track_event/track_event.proto +++ b/protos/perfetto/trace/track_event/track_event.proto @@ -289,8 +289,11 @@ message TrackEvent { extensions 1000 to 1999; // Extension range reserved for https://b.corp.google.com/issues/301227627. extensions 2000 to 2000; + // Extension range reserved for android: + // protos/perfetto/trace/android/android_track_event.proto + extensions 2001 to 2999; // Extension range for future use. - extensions 2001 to 9899; + extensions 3000 to 9899; // Reserved for Perfetto unit and integration tests. extensions 9900 to 10000; diff --git a/protos/perfetto/trace/translation/translation_table.proto b/protos/perfetto/trace/translation/translation_table.proto index 06cef3128d..b934cf7fbc 100644 --- a/protos/perfetto/trace/translation/translation_table.proto +++ b/protos/perfetto/trace/translation/translation_table.proto @@ -26,6 +26,7 @@ message TranslationTable { ChromeUserEventTranslationTable chrome_user_event = 2; ChromePerformanceMarkTranslationTable chrome_performance_mark = 3; SliceNameTranslationTable slice_name = 4; + ProcessTrackNameTranslationTable process_track_name = 5; } } @@ -49,3 +50,8 @@ message ChromePerformanceMarkTranslationTable { message SliceNameTranslationTable { map raw_to_deobfuscated_name = 1; }; + +// Raw -> deobfuscated process track name translation rules. +message ProcessTrackNameTranslationTable { + map raw_to_deobfuscated_name = 1; +}; diff --git a/protos/third_party/chromium/chrome_track_event.proto b/protos/third_party/chromium/chrome_track_event.proto index e43f424940..e56bca1539 100644 --- a/protos/third_party/chromium/chrome_track_event.proto +++ b/protos/third_party/chromium/chrome_track_event.proto @@ -127,7 +127,9 @@ message RenderFrameImplDeletion { optional uint64 frame_tree_node_id = 4; } -// Matches content::ShouldSwapBrowsingInstance. +// Corresponds to `content::ShouldSwapBrowsingInstance`. +// Note that the enum values here are not equivalent to the content enum and +// must be converted explicitly. See `ShouldSwapBrowsingInstanceToProto`. enum ShouldSwapBrowsingInstance { // Was used for all "no BrowsingInstance swap" scenarios, now broken down in // separate reasons. @@ -163,6 +165,7 @@ enum ShouldSwapBrowsingInstance { SHOULD_SWAP_BROWSING_INSTANCE_NO_UNLOAD_HANDLER_EXISTS_ON_SAME_SITE_NAVIGATION = 21; SHOULD_SWAP_BROWSING_INSTANCE_NO_NOT_PRIMARY_MAIN_FRAME = 22; + SHOULD_SWAP_BROWSING_INSTANCE_NO_INITIATOR_REQUESTED_NO_PROACTIVE_SWAP = 23; } message ShouldSwapBrowsingInstancesResult { @@ -669,6 +672,11 @@ message BackForwardCacheCanStoreDocumentResult { HTTP_AUTH_REQUIRED = 57; COOKIE_FLUSHED = 58; BROADCAST_CHANNEL_ON_MESSAGE = 59; + WEBVIEW_SETTINGS_CHANGED = 60; + WEBVIEW_JAVASCRIPT_OBJECT_CHANGED = 61; + WEBVIEW_MESSAGE_LISTENER_INJECTED = 62; + WEBVIEW_SAFE_BROWSING_ALLOWLIST_CHANGED = 63; + WEBVIEW_DOCUMENT_START_JAVASCRIPT_CHANGED = 64; } optional BackForwardCacheNotRestoredReason @@ -762,6 +770,7 @@ message RendererMainThreadTaskExecution { TASK_TYPE_NETWORKING_UNFREEZABLE_RENDER_BLOCKING_LOADING = 83; TASK_TYPE_MAIN_THREAD_TASK_QUEUE_V8_LOW_PRIORITY = 84; TASK_TYPE_CLIPBOARD = 85; + TASK_TYPE_MACHINE_LEARNING = 86; } enum FrameType { @@ -1550,6 +1559,15 @@ message WebContentInteraction { optional int64 total_duration_ms = 2; } +message ScrollMetrics { + optional int64 frame_count = 1; + optional int64 vsync_count = 2; + optional int64 missed_vsync_max = 3; + optional int64 missed_vsync_sum = 4; + optional int64 delayed_frame_count = 5; + optional int64 predictor_janky_frame_count = 6; +} + // The EventForwarder is a subsystem in android that forwards events from Java // code to Chromium's native implementation. In this case we register how many // events there was, what time this event occurred and the current x & y on the @@ -1604,7 +1622,7 @@ message ViewClassName { // // All non-delta-timestamps are absolute CLOCK_MONOTONIC timestamps. -// Next id: 15 +// Next id: 16 enum ChromeCompositorSchedulerActionV2 { CC_SCHEDULER_ACTION_V2_UNSPECIFIED = 0; CC_SCHEDULER_ACTION_V2_NONE = 1; @@ -1615,6 +1633,7 @@ enum ChromeCompositorSchedulerActionV2 { CC_SCHEDULER_ACTION_V2_DRAW_IF_POSSIBLE = 5; CC_SCHEDULER_ACTION_V2_DRAW_FORCED = 6; CC_SCHEDULER_ACTION_V2_DRAW_ABORT = 7; + CC_SCHEDULER_ACTION_V2_UPDATE_DISPLAY_TREE = 15; CC_SCHEDULER_ACTION_V2_BEGIN_LAYER_TREE_FRAME_SINK_CREATION = 8; CC_SCHEDULER_ACTION_V2_PREPARE_TILES = 9; CC_SCHEDULER_ACTION_V2_INVALIDATE_LAYER_TREE_FRAME_SINK = 10; @@ -1694,7 +1713,7 @@ message ChromeCompositorStateMachineV2 { } optional MajorStateV2 major_state = 1; - // Next id: 47 + // Next id: 48 message MinorStateV2 { enum TreePriority { TREE_PRIORITY_UNSPECIFIED = 0; @@ -1752,6 +1771,7 @@ message ChromeCompositorStateMachineV2 { optional bool processing_animation_worklets_for_active_tree = 44; optional bool processing_animation_worklets_for_pending_tree = 45; optional bool processing_paint_worklets_for_pending_tree = 46; + optional bool should_warm_up = 47; reserved 35; } @@ -1883,11 +1903,19 @@ message AnimationFrameScriptTimingInfo { PROMISE_REJECT = 6; } optional InvokerType invoker_type = 9; + enum ThirdPartyTechnology { + UNSPECIFIED = 0; + NONE = 1; + WORD_PRESS = 2; + GOOGLE_ANALYTICS = 3; + GOOGLE_FONT_API = 4; + } + optional ThirdPartyTechnology third_party_technology = 10; } message ChromeTrackEvent { // Extension range for Chrome: 1000-1999 - // Next ID: 1066 + // Next ID: 1067 extend TrackEvent { optional ChromeAppState chrome_app_state = 1000; @@ -2027,5 +2055,7 @@ message ChromeTrackEvent { optional AnimationFrameScriptTimingInfo animation_frame_script_timing_info = 1065; + + optional ScrollMetrics scroll_metrics = 1066; } } diff --git a/protos/third_party/pprof/BUILD.gn b/protos/third_party/pprof/BUILD.gn index 1c684e7708..f38894c3cf 100644 --- a/protos/third_party/pprof/BUILD.gn +++ b/protos/third_party/pprof/BUILD.gn @@ -17,6 +17,7 @@ import("../../../gn/proto_library.gni") perfetto_proto_library("@TYPE@") { proto_generators = [ "zero", + "cpp", "source_set", ] sources = [ "profile.proto" ] diff --git a/ui/prettier-all b/protos/third_party/simpleperf/BUILD.gn old mode 100755 new mode 100644 similarity index 79% rename from ui/prettier-all rename to protos/third_party/simpleperf/BUILD.gn index f6868fecc9..da934beab5 --- a/ui/prettier-all +++ b/protos/third_party/simpleperf/BUILD.gn @@ -1,4 +1,3 @@ -#!/bin/bash # Copyright (C) 2024 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,7 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -set -e -u -ROOT_DIR="$(dirname $(cd -P ${BASH_SOURCE[0]%/*}; pwd))" +import("../../../gn/proto_library.gni") -exec "$ROOT_DIR/ui/prettier" --write "$ROOT_DIR/ui/**/*.{ts,js,scss}" "$@" +perfetto_proto_library("@TYPE@") { + sources = [ "record_file.proto" ] +} diff --git a/protos/third_party/simpleperf/record_file.proto b/protos/third_party/simpleperf/record_file.proto new file mode 100644 index 0000000000..ce2962e25c --- /dev/null +++ b/protos/third_party/simpleperf/record_file.proto @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2021 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. + */ + +// message types used in perf.data. + +syntax = "proto3"; + +// This is in perfetto.third_party to avoid clashing with potential other +// copies of this proto. +package perfetto.third_party.simpleperf.proto; + +message DebugUnwindFeature { + message File { + string path = 1; + uint64 size = 2; + } + + repeated File file = 1; +} + +message FileFeature { + // This enum is not defined in the original simpleperf proto file. + // We added it here for convenience. Simpleperf uses a regular c++ enum + // instead. See comment in type field. + enum DsoType { + DSO_KERNEL = 0; + DSO_KERNEL_MODULE = 1; + DSO_ELF_FILE = 2; + DSO_DEX_FILE = 3; + DSO_SYMBOL_MAP_FILE = 4; + DSO_UNKNOWN_FILE = 5; + } + string path = 1; + // The original simpleperf proto defines this field as a uint32. We use an + // enum here instead for convenience. enum fields are encoded as if they were + // int32 values, and both uint32 and int32 values have the same proto wire + // format, so using the enum here is fine. + DsoType type = 2; + uint64 min_vaddr = 3; + + message Symbol { + uint64 vaddr = 1; + uint32 len = 2; + string name = 3; + } + repeated Symbol symbol = 4; + + message DexFile { + repeated uint64 dex_file_offset = 1; + } + message ElfFile { + uint64 file_offset_of_min_vaddr = 1; + } + message KernelModule { + uint64 memory_offset_of_min_vaddr = 1; + } + + oneof type_specific_msg { + DexFile dex_file = 5; // Only when type = DSO_DEX_FILE + ElfFile elf_file = 6; // Only when type = DSO_ELF_FILE + KernelModule kernel_module = 7; // Only when type = DSO_KERNEL_MODULE + } +} \ No newline at end of file diff --git a/python/BUILD b/python/BUILD index 52a511d43c..1a97e310ae 100644 --- a/python/BUILD +++ b/python/BUILD @@ -79,6 +79,15 @@ perfetto_py_binary( python_version = "PY3", ) +# GN target: //python:common +perfetto_py_library( + name = "common", + srcs = [ + "perfetto/common/exceptions.py", + "perfetto/common/query_result_iterator.py", + ], +) + # GN target: //python:experimental_slice_breakdown_lib perfetto_py_library( name = "experimental_slice_breakdown_lib", @@ -122,6 +131,7 @@ perfetto_py_library( "perfetto/trace_processor/trace_processor.descriptor", ], deps = [ + ":common", ":trace_uri_resolver", ] + PERFETTO_CONFIG.deps.pandas_py + PERFETTO_CONFIG.deps.protobuf_py + diff --git a/python/BUILD.gn b/python/BUILD.gn index baaa01b523..8608525445 100644 --- a/python/BUILD.gn +++ b/python/BUILD.gn @@ -47,6 +47,13 @@ perfetto_py_binary("trace_processor_py_example") { main = "example.py" } +perfetto_py_library("common") { + sources = [ + "perfetto/common/exceptions.py", + "perfetto/common/query_result_iterator.py", + ] +} + perfetto_py_library("trace_processor_py_no_resolvers") { sources = [ "perfetto/trace_processor/__init__.py", @@ -62,6 +69,7 @@ perfetto_py_library("trace_processor_py_no_resolvers") { "..:trace_processor_shell", ] deps = [ + ":common", ":trace_uri_resolver", "../gn:pandas_py", "../gn:protobuf_py", diff --git a/python/generators/diff_tests/runner.py b/python/generators/diff_tests/runner.py index 0fe0982ff1..43d8540fc6 100644 --- a/python/generators/diff_tests/runner.py +++ b/python/generators/diff_tests/runner.py @@ -131,7 +131,8 @@ def str(self, no_colors: bool, tests_no: int): f"{c.green('[ PASSED ]')} " f"{tests_no - len(self.test_failures)} tests.\n") if len(self.test_failures) > 0: - res += (f"{c.red('[ FAILED ]')} " f"{len(self.test_failures)} tests.\n") + res += (f"{c.red('[ FAILED ]')} " + f"{len(self.test_failures)} tests.\n") for failure in self.test_failures: res += f"{c.red('[ FAILED ]')} {failure}\n" return res @@ -379,6 +380,7 @@ def write_cmdlines(): return res if result.exit_code != 0 or not result.passed: + result.passed = False str += result.stderr if result.exit_code == 0: @@ -450,8 +452,9 @@ def __init__(self, name_filter: str, trace_processor_path: str, override_sql_module_paths)) def run_all_tests(self, metrics_descriptor_paths: List[str], - chrome_extensions: str, test_extensions: str, winscope_extensions: str, - keep_input: bool, rebase: bool) -> TestResults: + chrome_extensions: str, test_extensions: str, + winscope_extensions: str, keep_input: bool, + rebase: bool) -> TestResults: perf_results = [] failures = [] rebased = [] @@ -459,7 +462,8 @@ def run_all_tests(self, metrics_descriptor_paths: List[str], with concurrent.futures.ProcessPoolExecutor() as e: fut = [ - e.submit(test.execute, [chrome_extensions, test_extensions, winscope_extensions], + e.submit(test.execute, + [chrome_extensions, test_extensions, winscope_extensions], metrics_descriptor_paths, keep_input, rebase) for test in self.test_runners ] diff --git a/python/generators/diff_tests/testing.py b/python/generators/diff_tests/testing.py index b90e4cd4a1..291b3c98a4 100644 --- a/python/generators/diff_tests/testing.py +++ b/python/generators/diff_tests/testing.py @@ -266,6 +266,7 @@ def fetch(self) -> List['TestCase']: if method.__name__.startswith('test_') ] + def PrintProfileProto(profile): locations = {l.id: l for l in profile.location} functions = {f.id: f for f in profile.function} diff --git a/python/generators/sql_processing/docs_parse.py b/python/generators/sql_processing/docs_parse.py index 9062973dd9..a0ec31ce2e 100644 --- a/python/generators/sql_processing/docs_parse.py +++ b/python/generators/sql_processing/docs_parse.py @@ -44,23 +44,24 @@ def parse_comment(comment: str) -> str: return ' '.join(line.strip().lstrip('--').lstrip() for line in comment.strip().split('\n')) + def get_module_prefix_error(name: str, path: str, module: str) -> Optional[str]: """Returns error message if the name is not correct, None otherwise.""" - prefix = name.lower().split('_')[0] if module in ["common", "prelude", "deprecated"]: - if prefix == module: + if name.startswith(module): return (f'Names of tables/views/functions in the "{module}" module ' f'should not start with {module}') return None - if prefix == module: + if name.startswith(module): # Module prefix is always allowed. return None allowed_prefixes = [module] - for (path_prefix, allowed_name_prefix) in ALLOWED_PREFIXES.items(): + for (path_prefix, allowed_name_prefixes) in ALLOWED_PREFIXES.items(): if path.startswith(path_prefix): - if prefix == allowed_name_prefix: - return None - allowed_prefixes.append(allowed_name_prefix) + for prefix in allowed_name_prefixes: + if name.startswith(prefix): + return None + allowed_prefixes.extend(allowed_name_prefixes) if path in OBJECT_NAME_ALLOWLIST and name in OBJECT_NAME_ALLOWLIST[path]: return None return ( diff --git a/python/generators/sql_processing/utils.py b/python/generators/sql_processing/utils.py index cf52fd5e46..d8408d7545 100644 --- a/python/generators/sql_processing/utils.py +++ b/python/generators/sql_processing/utils.py @@ -108,11 +108,13 @@ class ObjKind(str, Enum): } ALLOWED_PREFIXES = { - 'counters': 'counter', - 'chrome/util': 'cr', - 'intervals': 'interval', - 'graphs': 'graph', - 'slices': 'slice', + 'android': ['heap_graph', 'memory'], + 'counters': ['counter'], + 'chrome/util': ['cr'], + 'intervals': ['interval'], + 'graphs': ['graph'], + 'slices': ['slice'], + 'linux': ['cpu', 'memory'] } # Allows for nonstandard object names. @@ -122,6 +124,7 @@ class ObjKind(str, Enum): 'slices/cpu_time.sql': ['thread_slice_cpu_time'] } + # Given a regex pattern and a string to match against, returns all the # matching positions. Specifically, it returns a dictionary from the line # number of the match to the regex match object. @@ -189,22 +192,12 @@ def check_banned_words(sql: str, path: str) -> List[str]: # Given SQL string check whether there is (not allowlisted) usage of # CREATE TABLE {name} AS. -def check_banned_create_table_as(sql: str, filename: str, stdlib_path: str, - allowlist: Dict[str, List[str]]) -> List[str]: +def check_banned_create_table_as(sql: str, filename: str, + stdlib_path: str) -> List[str]: errors = [] for _, matches in match_pattern(CREATE_TABLE_AS_PATTERN, sql).items(): name = matches[0] - # Normalize paths before checking presence in the allowlist so it will - # work on Windows for the Chrome stdlib presubmit. - allowlist_normpath = dict( - (os.path.normpath(path), tables) for path, tables in allowlist.items()) - allowlist_key = os.path.normpath(filename[len(stdlib_path):]) - if allowlist_key not in allowlist_normpath: - errors.append(f"CREATE TABLE '{name}' is deprecated. " - "Use CREATE PERFETTO TABLE instead.\n" - f"Offending file: {filename}\n") - continue - if name not in allowlist_normpath[allowlist_key]: + if name != "trace_bounds": errors.append( f"Table '{name}' uses CREATE TABLE which is deprecated " "and this table is not allowlisted. Use CREATE PERFETTO TABLE.\n" diff --git a/python/generators/trace_processor_table/serialize.py b/python/generators/trace_processor_table/serialize.py index 1e613e30cb..fed07666d0 100644 --- a/python/generators/trace_processor_table/serialize.py +++ b/python/generators/trace_processor_table/serialize.py @@ -730,8 +730,8 @@ def serialize_header(ifdef_guard: str, tables: List[ParsedTable], """Serializes a table header file containing the given set of tables.""" # Replace the backslash with forward slash when building on Windows. # Caused b/327985369 without the replace. - include_paths_str = '\n'.join( - [f'#include "{i}"' for i in include_paths]).replace("\\", "/") + include_paths_str = '\n'.join([f'#include "{i}"' for i in include_paths + ]).replace("\\", "/") tables_str = '\n\n'.join([TableSerializer(t).serialize() for t in tables]) return f''' #ifndef {ifdef_guard} diff --git a/python/perfetto/bigtrace/api.py b/python/perfetto/bigtrace/api.py new file mode 100644 index 0000000000..9b67932781 --- /dev/null +++ b/python/perfetto/bigtrace/api.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +# Copyright (C) 2024 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import List + +import grpc +import pandas as pd + +from perfetto.bigtrace.protos.perfetto.bigtrace.orchestrator_pb2 import BigtraceQueryArgs +from perfetto.bigtrace.protos.perfetto.bigtrace.orchestrator_pb2_grpc import BigtraceOrchestratorStub +from perfetto.common.query_result_iterator import QueryResultIterator +from perfetto.common.exceptions import PerfettoException + +class Bigtrace: + + def __init__(self, + orchestrator_address="127.0.0.1:5051", + wait_for_ready_for_testing=False): + channel = grpc.insecure_channel(orchestrator_address) + self.stub = BigtraceOrchestratorStub(channel) + self.wait_for_ready_for_testing = wait_for_ready_for_testing + + def query(self, traces: List[str], sql_query: str): + if not traces: + raise PerfettoException("Trace list cannot be empty") + if not sql_query: + raise PerfettoException("SQL query cannot be empty") + # Query and then convert to pandas + tables = [] + args = BigtraceQueryArgs(traces=traces, sql_query=sql_query) + + responses = self.stub.Query( + args, wait_for_ready=self.wait_for_ready_for_testing) + try: + for response in responses: + repeated_batches = [] + results = response.result + column_names = results[0].column_names + for result in results: + repeated_batches.extend(result.batch) + iterator = QueryResultIterator(column_names, repeated_batches) + df = iterator.as_pandas_dataframe() + # TODO(ivankc) Investigate whether this is the + # best place to insert these addresses for performance + df.insert(0, '_trace_address', response.trace) + tables.append(df) + flattened = pd.concat(tables) + return flattened.reset_index(drop=True) + except grpc.RpcError as e: + raise PerfettoException(f"gRPC {e.code().name} error - {e.details()}") diff --git a/python/perfetto/bigtrace/protos/perfetto/bigtrace/orchestrator_pb2.py b/python/perfetto/bigtrace/protos/perfetto/bigtrace/orchestrator_pb2.py new file mode 100644 index 0000000000..1d5cbf6d92 --- /dev/null +++ b/python/perfetto/bigtrace/protos/perfetto/bigtrace/orchestrator_pb2.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: protos/perfetto/bigtrace/orchestrator.proto +# Protobuf Python Version: 4.25.1 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + +from perfetto.bigtrace.protos.perfetto.trace_processor import trace_processor_pb2 as protos_dot_perfetto_dot_trace__processor_dot_trace__processor__pb2 + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( + b'\n+protos/perfetto/bigtrace/orchestrator.proto\x12\x0fperfetto.protos\x1a\x35protos/perfetto/trace_processor/trace_processor.proto\"I\n\x11\x42igtraceQueryArgs\x12\x0e\n\x06traces\x18\x01 \x03(\t\x12\x16\n\tsql_query\x18\x02 \x01(\tH\x00\x88\x01\x01\x42\x0c\n\n_sql_query\"c\n\x15\x42igtraceQueryResponse\x12\x12\n\x05trace\x18\x01 \x01(\tH\x00\x88\x01\x01\x12,\n\x06result\x18\x02 \x03(\x0b\x32\x1c.perfetto.protos.QueryResultB\x08\n\x06_trace2o\n\x14\x42igtraceOrchestrator\x12W\n\x05Query\x12\".perfetto.protos.BigtraceQueryArgs\x1a&.perfetto.protos.BigtraceQueryResponse\"\x00\x30\x01\x62\x06proto3' +) + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages( + DESCRIPTOR, 'perfetto.bigtrace.protos.perfetto.bigtrace.orchestrator_pb2', + _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + DESCRIPTOR._options = None + _globals['_BIGTRACEQUERYARGS']._serialized_start = 119 + _globals['_BIGTRACEQUERYARGS']._serialized_end = 192 + _globals['_BIGTRACEQUERYRESPONSE']._serialized_start = 194 + _globals['_BIGTRACEQUERYRESPONSE']._serialized_end = 293 + _globals['_BIGTRACEORCHESTRATOR']._serialized_start = 295 + _globals['_BIGTRACEORCHESTRATOR']._serialized_end = 406 +# @@protoc_insertion_point(module_scope) diff --git a/python/perfetto/bigtrace/protos/perfetto/bigtrace/orchestrator_pb2.pyi b/python/perfetto/bigtrace/protos/perfetto/bigtrace/orchestrator_pb2.pyi new file mode 100644 index 0000000000..59fe15ade2 --- /dev/null +++ b/python/perfetto/bigtrace/protos/perfetto/bigtrace/orchestrator_pb2.pyi @@ -0,0 +1,23 @@ +from perfetto.bigtrace.protos.perfetto.trace_processor import trace_processor_pb2 as _trace_processor_pb2 +from google.protobuf.internal import containers as _containers +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Mapping, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class BigtraceQueryArgs(_message.Message): + __slots__ = ("traces", "sql_query") + TRACES_FIELD_NUMBER: _ClassVar[int] + SQL_QUERY_FIELD_NUMBER: _ClassVar[int] + traces: _containers.RepeatedScalarFieldContainer[str] + sql_query: str + def __init__(self, traces: _Optional[_Iterable[str]] = ..., sql_query: _Optional[str] = ...) -> None: ... + +class BigtraceQueryResponse(_message.Message): + __slots__ = ("trace", "result") + TRACE_FIELD_NUMBER: _ClassVar[int] + RESULT_FIELD_NUMBER: _ClassVar[int] + trace: str + result: _containers.RepeatedCompositeFieldContainer[_trace_processor_pb2.QueryResult] + def __init__(self, trace: _Optional[str] = ..., result: _Optional[_Iterable[_Union[_trace_processor_pb2.QueryResult, _Mapping]]] = ...) -> None: ... diff --git a/python/perfetto/bigtrace/protos/perfetto/bigtrace/orchestrator_pb2_grpc.py b/python/perfetto/bigtrace/protos/perfetto/bigtrace/orchestrator_pb2_grpc.py new file mode 100644 index 0000000000..cca603accc --- /dev/null +++ b/python/perfetto/bigtrace/protos/perfetto/bigtrace/orchestrator_pb2_grpc.py @@ -0,0 +1,91 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc + +from perfetto.bigtrace.protos.perfetto.bigtrace import orchestrator_pb2 as protos_dot_perfetto_dot_bigtrace_dot_orchestrator__pb2 + + +class BigtraceOrchestratorStub(object): + """gRPC Interface for a Bigtrace Orchestrator + + Each Bigtrace instance has an orchestrator which is responsible for receiving + requests from the client and loading and querying traces by sharding them + across a set of "Workers" + """ + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.Query = channel.unary_stream( + '/perfetto.protos.BigtraceOrchestrator/Query', + request_serializer=protos_dot_perfetto_dot_bigtrace_dot_orchestrator__pb2 + .BigtraceQueryArgs.SerializeToString, + response_deserializer=protos_dot_perfetto_dot_bigtrace_dot_orchestrator__pb2 + .BigtraceQueryResponse.FromString, + ) + + +class BigtraceOrchestratorServicer(object): + """gRPC Interface for a Bigtrace Orchestrator + + Each Bigtrace instance has an orchestrator which is responsible for receiving + requests from the client and loading and querying traces by sharding them + across a set of "Workers" + """ + + def Query(self, request, context): + """Executes a SQL query on the specified list of traces and returns a stream + of the result of the query for a given trace + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_BigtraceOrchestratorServicer_to_server(servicer, server): + rpc_method_handlers = { + 'Query': + grpc.unary_stream_rpc_method_handler( + servicer.Query, + request_deserializer=protos_dot_perfetto_dot_bigtrace_dot_orchestrator__pb2 + .BigtraceQueryArgs.FromString, + response_serializer=protos_dot_perfetto_dot_bigtrace_dot_orchestrator__pb2 + .BigtraceQueryResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'perfetto.protos.BigtraceOrchestrator', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + + +# This class is part of an EXPERIMENTAL API. +class BigtraceOrchestrator(object): + """gRPC Interface for a Bigtrace Orchestrator + + Each Bigtrace instance has an orchestrator which is responsible for receiving + requests from the client and loading and querying traces by sharding them + across a set of "Workers" + """ + + @staticmethod + def Query(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_stream( + request, target, '/perfetto.protos.BigtraceOrchestrator/Query', + protos_dot_perfetto_dot_bigtrace_dot_orchestrator__pb2.BigtraceQueryArgs + .SerializeToString, + protos_dot_perfetto_dot_bigtrace_dot_orchestrator__pb2 + .BigtraceQueryResponse.FromString, options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, + metadata) diff --git a/python/perfetto/bigtrace/protos/perfetto/common/descriptor_pb2.py b/python/perfetto/bigtrace/protos/perfetto/common/descriptor_pb2.py new file mode 100644 index 0000000000..6bf7487bcd --- /dev/null +++ b/python/perfetto/bigtrace/protos/perfetto/common/descriptor_pb2.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: protos/perfetto/common/descriptor.proto +# Protobuf Python Version: 4.25.1 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( + b'\n\'protos/perfetto/common/descriptor.proto\x12\x0fperfetto.protos\"G\n\x11\x46ileDescriptorSet\x12\x32\n\x04\x66ile\x18\x01 \x03(\x0b\x32$.perfetto.protos.FileDescriptorProto\"\xbf\x02\n\x13\x46ileDescriptorProto\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0f\n\x07package\x18\x02 \x01(\t\x12\x12\n\ndependency\x18\x03 \x03(\t\x12\x19\n\x11public_dependency\x18\n \x03(\x05\x12\x17\n\x0fweak_dependency\x18\x0b \x03(\x05\x12\x36\n\x0cmessage_type\x18\x04 \x03(\x0b\x32 .perfetto.protos.DescriptorProto\x12\x37\n\tenum_type\x18\x05 \x03(\x0b\x32$.perfetto.protos.EnumDescriptorProto\x12\x38\n\textension\x18\x07 \x03(\x0b\x32%.perfetto.protos.FieldDescriptorProtoJ\x04\x08\x06\x10\x07J\x04\x08\x08\x10\tJ\x04\x08\t\x10\nJ\x04\x08\x0c\x10\r\"\xd2\x03\n\x0f\x44\x65scriptorProto\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x34\n\x05\x66ield\x18\x02 \x03(\x0b\x32%.perfetto.protos.FieldDescriptorProto\x12\x38\n\textension\x18\x06 \x03(\x0b\x32%.perfetto.protos.FieldDescriptorProto\x12\x35\n\x0bnested_type\x18\x03 \x03(\x0b\x32 .perfetto.protos.DescriptorProto\x12\x37\n\tenum_type\x18\x04 \x03(\x0b\x32$.perfetto.protos.EnumDescriptorProto\x12\x39\n\noneof_decl\x18\x08 \x03(\x0b\x32%.perfetto.protos.OneofDescriptorProto\x12\x46\n\x0ereserved_range\x18\t \x03(\x0b\x32..perfetto.protos.DescriptorProto.ReservedRange\x12\x15\n\rreserved_name\x18\n \x03(\t\x1a+\n\rReservedRange\x12\r\n\x05start\x18\x01 \x01(\x05\x12\x0b\n\x03\x65nd\x18\x02 \x01(\x05J\x04\x08\x05\x10\x06J\x04\x08\x07\x10\x08\"\x9e\x02\n\x13UninterpretedOption\x12;\n\x04name\x18\x02 \x03(\x0b\x32-.perfetto.protos.UninterpretedOption.NamePart\x12\x18\n\x10identifier_value\x18\x03 \x01(\t\x12\x1a\n\x12positive_int_value\x18\x04 \x01(\x04\x12\x1a\n\x12negative_int_value\x18\x05 \x01(\x03\x12\x14\n\x0c\x64ouble_value\x18\x06 \x01(\x01\x12\x14\n\x0cstring_value\x18\x07 \x01(\x0c\x12\x17\n\x0f\x61ggregate_value\x18\x08 \x01(\t\x1a\x33\n\x08NamePart\x12\x11\n\tname_part\x18\x01 \x01(\t\x12\x14\n\x0cis_extension\x18\x02 \x01(\x08\"c\n\x0c\x46ieldOptions\x12\x0e\n\x06packed\x18\x02 \x01(\x08\x12\x43\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32$.perfetto.protos.UninterpretedOption\"\xaf\x05\n\x14\x46ieldDescriptorProto\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0e\n\x06number\x18\x03 \x01(\x05\x12:\n\x05label\x18\x04 \x01(\x0e\x32+.perfetto.protos.FieldDescriptorProto.Label\x12\x38\n\x04type\x18\x05 \x01(\x0e\x32*.perfetto.protos.FieldDescriptorProto.Type\x12\x11\n\ttype_name\x18\x06 \x01(\t\x12\x10\n\x08\x65xtendee\x18\x02 \x01(\t\x12\x15\n\rdefault_value\x18\x07 \x01(\t\x12.\n\x07options\x18\x08 \x01(\x0b\x32\x1d.perfetto.protos.FieldOptions\x12\x13\n\x0boneof_index\x18\t \x01(\x05\"\xb6\x02\n\x04Type\x12\x0f\n\x0bTYPE_DOUBLE\x10\x01\x12\x0e\n\nTYPE_FLOAT\x10\x02\x12\x0e\n\nTYPE_INT64\x10\x03\x12\x0f\n\x0bTYPE_UINT64\x10\x04\x12\x0e\n\nTYPE_INT32\x10\x05\x12\x10\n\x0cTYPE_FIXED64\x10\x06\x12\x10\n\x0cTYPE_FIXED32\x10\x07\x12\r\n\tTYPE_BOOL\x10\x08\x12\x0f\n\x0bTYPE_STRING\x10\t\x12\x0e\n\nTYPE_GROUP\x10\n\x12\x10\n\x0cTYPE_MESSAGE\x10\x0b\x12\x0e\n\nTYPE_BYTES\x10\x0c\x12\x0f\n\x0bTYPE_UINT32\x10\r\x12\r\n\tTYPE_ENUM\x10\x0e\x12\x11\n\rTYPE_SFIXED32\x10\x0f\x12\x11\n\rTYPE_SFIXED64\x10\x10\x12\x0f\n\x0bTYPE_SINT32\x10\x11\x12\x0f\n\x0bTYPE_SINT64\x10\x12\"C\n\x05Label\x12\x12\n\x0eLABEL_OPTIONAL\x10\x01\x12\x12\n\x0eLABEL_REQUIRED\x10\x02\x12\x12\n\x0eLABEL_REPEATED\x10\x03J\x04\x08\n\x10\x0b\"T\n\x14OneofDescriptorProto\x12\x0c\n\x04name\x18\x01 \x01(\t\x12.\n\x07options\x18\x02 \x01(\x0b\x32\x1d.perfetto.protos.OneofOptions\"\x80\x01\n\x13\x45numDescriptorProto\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x38\n\x05value\x18\x02 \x03(\x0b\x32).perfetto.protos.EnumValueDescriptorProto\x12\x15\n\rreserved_name\x18\x05 \x03(\tJ\x04\x08\x03\x10\x04J\x04\x08\x04\x10\x05\">\n\x18\x45numValueDescriptorProto\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0e\n\x06number\x18\x02 \x01(\x05J\x04\x08\x03\x10\x04\"!\n\x0cOneofOptions*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02J\x06\x08\xe7\x07\x10\xe8\x07' +) + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages( + DESCRIPTOR, 'protos.perfetto.common.descriptor_pb2', _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + DESCRIPTOR._options = None + _globals['_FILEDESCRIPTORSET']._serialized_start = 60 + _globals['_FILEDESCRIPTORSET']._serialized_end = 131 + _globals['_FILEDESCRIPTORPROTO']._serialized_start = 134 + _globals['_FILEDESCRIPTORPROTO']._serialized_end = 453 + _globals['_DESCRIPTORPROTO']._serialized_start = 456 + _globals['_DESCRIPTORPROTO']._serialized_end = 922 + _globals['_DESCRIPTORPROTO_RESERVEDRANGE']._serialized_start = 867 + _globals['_DESCRIPTORPROTO_RESERVEDRANGE']._serialized_end = 910 + _globals['_UNINTERPRETEDOPTION']._serialized_start = 925 + _globals['_UNINTERPRETEDOPTION']._serialized_end = 1211 + _globals['_UNINTERPRETEDOPTION_NAMEPART']._serialized_start = 1160 + _globals['_UNINTERPRETEDOPTION_NAMEPART']._serialized_end = 1211 + _globals['_FIELDOPTIONS']._serialized_start = 1213 + _globals['_FIELDOPTIONS']._serialized_end = 1312 + _globals['_FIELDDESCRIPTORPROTO']._serialized_start = 1315 + _globals['_FIELDDESCRIPTORPROTO']._serialized_end = 2002 + _globals['_FIELDDESCRIPTORPROTO_TYPE']._serialized_start = 1617 + _globals['_FIELDDESCRIPTORPROTO_TYPE']._serialized_end = 1927 + _globals['_FIELDDESCRIPTORPROTO_LABEL']._serialized_start = 1929 + _globals['_FIELDDESCRIPTORPROTO_LABEL']._serialized_end = 1996 + _globals['_ONEOFDESCRIPTORPROTO']._serialized_start = 2004 + _globals['_ONEOFDESCRIPTORPROTO']._serialized_end = 2088 + _globals['_ENUMDESCRIPTORPROTO']._serialized_start = 2091 + _globals['_ENUMDESCRIPTORPROTO']._serialized_end = 2219 + _globals['_ENUMVALUEDESCRIPTORPROTO']._serialized_start = 2221 + _globals['_ENUMVALUEDESCRIPTORPROTO']._serialized_end = 2283 + _globals['_ONEOFOPTIONS']._serialized_start = 2285 + _globals['_ONEOFOPTIONS']._serialized_end = 2318 +# @@protoc_insertion_point(module_scope) diff --git a/python/perfetto/bigtrace/protos/perfetto/common/descriptor_pb2.pyi b/python/perfetto/bigtrace/protos/perfetto/common/descriptor_pb2.pyi new file mode 100644 index 0000000000..6ad45ebd6c --- /dev/null +++ b/python/perfetto/bigtrace/protos/perfetto/common/descriptor_pb2.pyi @@ -0,0 +1,193 @@ +from google.protobuf.internal import containers as _containers +from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper +from google.protobuf.internal import python_message as _python_message +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Mapping, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class FileDescriptorSet(_message.Message): + __slots__ = ("file",) + FILE_FIELD_NUMBER: _ClassVar[int] + file: _containers.RepeatedCompositeFieldContainer[FileDescriptorProto] + def __init__(self, file: _Optional[_Iterable[_Union[FileDescriptorProto, _Mapping]]] = ...) -> None: ... + +class FileDescriptorProto(_message.Message): + __slots__ = ("name", "package", "dependency", "public_dependency", "weak_dependency", "message_type", "enum_type", "extension") + NAME_FIELD_NUMBER: _ClassVar[int] + PACKAGE_FIELD_NUMBER: _ClassVar[int] + DEPENDENCY_FIELD_NUMBER: _ClassVar[int] + PUBLIC_DEPENDENCY_FIELD_NUMBER: _ClassVar[int] + WEAK_DEPENDENCY_FIELD_NUMBER: _ClassVar[int] + MESSAGE_TYPE_FIELD_NUMBER: _ClassVar[int] + ENUM_TYPE_FIELD_NUMBER: _ClassVar[int] + EXTENSION_FIELD_NUMBER: _ClassVar[int] + name: str + package: str + dependency: _containers.RepeatedScalarFieldContainer[str] + public_dependency: _containers.RepeatedScalarFieldContainer[int] + weak_dependency: _containers.RepeatedScalarFieldContainer[int] + message_type: _containers.RepeatedCompositeFieldContainer[DescriptorProto] + enum_type: _containers.RepeatedCompositeFieldContainer[EnumDescriptorProto] + extension: _containers.RepeatedCompositeFieldContainer[FieldDescriptorProto] + def __init__(self, name: _Optional[str] = ..., package: _Optional[str] = ..., dependency: _Optional[_Iterable[str]] = ..., public_dependency: _Optional[_Iterable[int]] = ..., weak_dependency: _Optional[_Iterable[int]] = ..., message_type: _Optional[_Iterable[_Union[DescriptorProto, _Mapping]]] = ..., enum_type: _Optional[_Iterable[_Union[EnumDescriptorProto, _Mapping]]] = ..., extension: _Optional[_Iterable[_Union[FieldDescriptorProto, _Mapping]]] = ...) -> None: ... + +class DescriptorProto(_message.Message): + __slots__ = ("name", "field", "extension", "nested_type", "enum_type", "oneof_decl", "reserved_range", "reserved_name") + class ReservedRange(_message.Message): + __slots__ = ("start", "end") + START_FIELD_NUMBER: _ClassVar[int] + END_FIELD_NUMBER: _ClassVar[int] + start: int + end: int + def __init__(self, start: _Optional[int] = ..., end: _Optional[int] = ...) -> None: ... + NAME_FIELD_NUMBER: _ClassVar[int] + FIELD_FIELD_NUMBER: _ClassVar[int] + EXTENSION_FIELD_NUMBER: _ClassVar[int] + NESTED_TYPE_FIELD_NUMBER: _ClassVar[int] + ENUM_TYPE_FIELD_NUMBER: _ClassVar[int] + ONEOF_DECL_FIELD_NUMBER: _ClassVar[int] + RESERVED_RANGE_FIELD_NUMBER: _ClassVar[int] + RESERVED_NAME_FIELD_NUMBER: _ClassVar[int] + name: str + field: _containers.RepeatedCompositeFieldContainer[FieldDescriptorProto] + extension: _containers.RepeatedCompositeFieldContainer[FieldDescriptorProto] + nested_type: _containers.RepeatedCompositeFieldContainer[DescriptorProto] + enum_type: _containers.RepeatedCompositeFieldContainer[EnumDescriptorProto] + oneof_decl: _containers.RepeatedCompositeFieldContainer[OneofDescriptorProto] + reserved_range: _containers.RepeatedCompositeFieldContainer[DescriptorProto.ReservedRange] + reserved_name: _containers.RepeatedScalarFieldContainer[str] + def __init__(self, name: _Optional[str] = ..., field: _Optional[_Iterable[_Union[FieldDescriptorProto, _Mapping]]] = ..., extension: _Optional[_Iterable[_Union[FieldDescriptorProto, _Mapping]]] = ..., nested_type: _Optional[_Iterable[_Union[DescriptorProto, _Mapping]]] = ..., enum_type: _Optional[_Iterable[_Union[EnumDescriptorProto, _Mapping]]] = ..., oneof_decl: _Optional[_Iterable[_Union[OneofDescriptorProto, _Mapping]]] = ..., reserved_range: _Optional[_Iterable[_Union[DescriptorProto.ReservedRange, _Mapping]]] = ..., reserved_name: _Optional[_Iterable[str]] = ...) -> None: ... + +class UninterpretedOption(_message.Message): + __slots__ = ("name", "identifier_value", "positive_int_value", "negative_int_value", "double_value", "string_value", "aggregate_value") + class NamePart(_message.Message): + __slots__ = ("name_part", "is_extension") + NAME_PART_FIELD_NUMBER: _ClassVar[int] + IS_EXTENSION_FIELD_NUMBER: _ClassVar[int] + name_part: str + is_extension: bool + def __init__(self, name_part: _Optional[str] = ..., is_extension: bool = ...) -> None: ... + NAME_FIELD_NUMBER: _ClassVar[int] + IDENTIFIER_VALUE_FIELD_NUMBER: _ClassVar[int] + POSITIVE_INT_VALUE_FIELD_NUMBER: _ClassVar[int] + NEGATIVE_INT_VALUE_FIELD_NUMBER: _ClassVar[int] + DOUBLE_VALUE_FIELD_NUMBER: _ClassVar[int] + STRING_VALUE_FIELD_NUMBER: _ClassVar[int] + AGGREGATE_VALUE_FIELD_NUMBER: _ClassVar[int] + name: _containers.RepeatedCompositeFieldContainer[UninterpretedOption.NamePart] + identifier_value: str + positive_int_value: int + negative_int_value: int + double_value: float + string_value: bytes + aggregate_value: str + def __init__(self, name: _Optional[_Iterable[_Union[UninterpretedOption.NamePart, _Mapping]]] = ..., identifier_value: _Optional[str] = ..., positive_int_value: _Optional[int] = ..., negative_int_value: _Optional[int] = ..., double_value: _Optional[float] = ..., string_value: _Optional[bytes] = ..., aggregate_value: _Optional[str] = ...) -> None: ... + +class FieldOptions(_message.Message): + __slots__ = ("packed", "uninterpreted_option") + PACKED_FIELD_NUMBER: _ClassVar[int] + UNINTERPRETED_OPTION_FIELD_NUMBER: _ClassVar[int] + packed: bool + uninterpreted_option: _containers.RepeatedCompositeFieldContainer[UninterpretedOption] + def __init__(self, packed: bool = ..., uninterpreted_option: _Optional[_Iterable[_Union[UninterpretedOption, _Mapping]]] = ...) -> None: ... + +class FieldDescriptorProto(_message.Message): + __slots__ = ("name", "number", "label", "type", "type_name", "extendee", "default_value", "options", "oneof_index") + class Type(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + TYPE_DOUBLE: _ClassVar[FieldDescriptorProto.Type] + TYPE_FLOAT: _ClassVar[FieldDescriptorProto.Type] + TYPE_INT64: _ClassVar[FieldDescriptorProto.Type] + TYPE_UINT64: _ClassVar[FieldDescriptorProto.Type] + TYPE_INT32: _ClassVar[FieldDescriptorProto.Type] + TYPE_FIXED64: _ClassVar[FieldDescriptorProto.Type] + TYPE_FIXED32: _ClassVar[FieldDescriptorProto.Type] + TYPE_BOOL: _ClassVar[FieldDescriptorProto.Type] + TYPE_STRING: _ClassVar[FieldDescriptorProto.Type] + TYPE_GROUP: _ClassVar[FieldDescriptorProto.Type] + TYPE_MESSAGE: _ClassVar[FieldDescriptorProto.Type] + TYPE_BYTES: _ClassVar[FieldDescriptorProto.Type] + TYPE_UINT32: _ClassVar[FieldDescriptorProto.Type] + TYPE_ENUM: _ClassVar[FieldDescriptorProto.Type] + TYPE_SFIXED32: _ClassVar[FieldDescriptorProto.Type] + TYPE_SFIXED64: _ClassVar[FieldDescriptorProto.Type] + TYPE_SINT32: _ClassVar[FieldDescriptorProto.Type] + TYPE_SINT64: _ClassVar[FieldDescriptorProto.Type] + TYPE_DOUBLE: FieldDescriptorProto.Type + TYPE_FLOAT: FieldDescriptorProto.Type + TYPE_INT64: FieldDescriptorProto.Type + TYPE_UINT64: FieldDescriptorProto.Type + TYPE_INT32: FieldDescriptorProto.Type + TYPE_FIXED64: FieldDescriptorProto.Type + TYPE_FIXED32: FieldDescriptorProto.Type + TYPE_BOOL: FieldDescriptorProto.Type + TYPE_STRING: FieldDescriptorProto.Type + TYPE_GROUP: FieldDescriptorProto.Type + TYPE_MESSAGE: FieldDescriptorProto.Type + TYPE_BYTES: FieldDescriptorProto.Type + TYPE_UINT32: FieldDescriptorProto.Type + TYPE_ENUM: FieldDescriptorProto.Type + TYPE_SFIXED32: FieldDescriptorProto.Type + TYPE_SFIXED64: FieldDescriptorProto.Type + TYPE_SINT32: FieldDescriptorProto.Type + TYPE_SINT64: FieldDescriptorProto.Type + class Label(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + LABEL_OPTIONAL: _ClassVar[FieldDescriptorProto.Label] + LABEL_REQUIRED: _ClassVar[FieldDescriptorProto.Label] + LABEL_REPEATED: _ClassVar[FieldDescriptorProto.Label] + LABEL_OPTIONAL: FieldDescriptorProto.Label + LABEL_REQUIRED: FieldDescriptorProto.Label + LABEL_REPEATED: FieldDescriptorProto.Label + NAME_FIELD_NUMBER: _ClassVar[int] + NUMBER_FIELD_NUMBER: _ClassVar[int] + LABEL_FIELD_NUMBER: _ClassVar[int] + TYPE_FIELD_NUMBER: _ClassVar[int] + TYPE_NAME_FIELD_NUMBER: _ClassVar[int] + EXTENDEE_FIELD_NUMBER: _ClassVar[int] + DEFAULT_VALUE_FIELD_NUMBER: _ClassVar[int] + OPTIONS_FIELD_NUMBER: _ClassVar[int] + ONEOF_INDEX_FIELD_NUMBER: _ClassVar[int] + name: str + number: int + label: FieldDescriptorProto.Label + type: FieldDescriptorProto.Type + type_name: str + extendee: str + default_value: str + options: FieldOptions + oneof_index: int + def __init__(self, name: _Optional[str] = ..., number: _Optional[int] = ..., label: _Optional[_Union[FieldDescriptorProto.Label, str]] = ..., type: _Optional[_Union[FieldDescriptorProto.Type, str]] = ..., type_name: _Optional[str] = ..., extendee: _Optional[str] = ..., default_value: _Optional[str] = ..., options: _Optional[_Union[FieldOptions, _Mapping]] = ..., oneof_index: _Optional[int] = ...) -> None: ... + +class OneofDescriptorProto(_message.Message): + __slots__ = ("name", "options") + NAME_FIELD_NUMBER: _ClassVar[int] + OPTIONS_FIELD_NUMBER: _ClassVar[int] + name: str + options: OneofOptions + def __init__(self, name: _Optional[str] = ..., options: _Optional[_Union[OneofOptions, _Mapping]] = ...) -> None: ... + +class EnumDescriptorProto(_message.Message): + __slots__ = ("name", "value", "reserved_name") + NAME_FIELD_NUMBER: _ClassVar[int] + VALUE_FIELD_NUMBER: _ClassVar[int] + RESERVED_NAME_FIELD_NUMBER: _ClassVar[int] + name: str + value: _containers.RepeatedCompositeFieldContainer[EnumValueDescriptorProto] + reserved_name: _containers.RepeatedScalarFieldContainer[str] + def __init__(self, name: _Optional[str] = ..., value: _Optional[_Iterable[_Union[EnumValueDescriptorProto, _Mapping]]] = ..., reserved_name: _Optional[_Iterable[str]] = ...) -> None: ... + +class EnumValueDescriptorProto(_message.Message): + __slots__ = ("name", "number") + NAME_FIELD_NUMBER: _ClassVar[int] + NUMBER_FIELD_NUMBER: _ClassVar[int] + name: str + number: int + def __init__(self, name: _Optional[str] = ..., number: _Optional[int] = ...) -> None: ... + +class OneofOptions(_message.Message): + __slots__ = () + Extensions: _python_message._ExtensionDict + def __init__(self) -> None: ... diff --git a/python/perfetto/bigtrace/protos/perfetto/trace_processor/metatrace_categories_pb2.py b/python/perfetto/bigtrace/protos/perfetto/trace_processor/metatrace_categories_pb2.py new file mode 100644 index 0000000000..ead7f155e0 --- /dev/null +++ b/python/perfetto/bigtrace/protos/perfetto/trace_processor/metatrace_categories_pb2.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: protos/perfetto/trace_processor/metatrace_categories.proto +# Protobuf Python Version: 4.25.1 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( + b'\n:protos/perfetto/trace_processor/metatrace_categories.proto\x12\x0fperfetto.protos*}\n\x13MetatraceCategories\x12\x12\n\x0eQUERY_TIMELINE\x10\x01\x12\x12\n\x0eQUERY_DETAILED\x10\x02\x12\x11\n\rFUNCTION_CALL\x10\x04\x12\x06\n\x02\x44\x42\x10\x08\x12\x10\n\x0c\x41PI_TIMELINE\x10\x10\x12\x08\n\x04NONE\x10\x00\x12\x07\n\x03\x41LL\x10\x1f' +) + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages( + DESCRIPTOR, 'protos.perfetto.trace_processor.metatrace_categories_pb2', + _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + DESCRIPTOR._options = None + _globals['_METATRACECATEGORIES']._serialized_start = 79 + _globals['_METATRACECATEGORIES']._serialized_end = 204 +# @@protoc_insertion_point(module_scope) diff --git a/python/perfetto/bigtrace/protos/perfetto/trace_processor/metatrace_categories_pb2.pyi b/python/perfetto/bigtrace/protos/perfetto/trace_processor/metatrace_categories_pb2.pyi new file mode 100644 index 0000000000..296fbbb1e1 --- /dev/null +++ b/python/perfetto/bigtrace/protos/perfetto/trace_processor/metatrace_categories_pb2.pyi @@ -0,0 +1,22 @@ +from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper +from google.protobuf import descriptor as _descriptor +from typing import ClassVar as _ClassVar + +DESCRIPTOR: _descriptor.FileDescriptor + +class MetatraceCategories(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + QUERY_TIMELINE: _ClassVar[MetatraceCategories] + QUERY_DETAILED: _ClassVar[MetatraceCategories] + FUNCTION_CALL: _ClassVar[MetatraceCategories] + DB: _ClassVar[MetatraceCategories] + API_TIMELINE: _ClassVar[MetatraceCategories] + NONE: _ClassVar[MetatraceCategories] + ALL: _ClassVar[MetatraceCategories] +QUERY_TIMELINE: MetatraceCategories +QUERY_DETAILED: MetatraceCategories +FUNCTION_CALL: MetatraceCategories +DB: MetatraceCategories +API_TIMELINE: MetatraceCategories +NONE: MetatraceCategories +ALL: MetatraceCategories diff --git a/python/perfetto/bigtrace/protos/perfetto/trace_processor/trace_processor_pb2.py b/python/perfetto/bigtrace/protos/perfetto/trace_processor/trace_processor_pb2.py new file mode 100644 index 0000000000..0c7fc1e422 --- /dev/null +++ b/python/perfetto/bigtrace/protos/perfetto/trace_processor/trace_processor_pb2.py @@ -0,0 +1,84 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: protos/perfetto/trace_processor/trace_processor.proto +# Protobuf Python Version: 4.25.1 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + +from perfetto.bigtrace.protos.perfetto.common import descriptor_pb2 as protos_dot_perfetto_dot_common_dot_descriptor__pb2 +from perfetto.bigtrace.protos.perfetto.trace_processor import metatrace_categories_pb2 as protos_dot_perfetto_dot_trace__processor_dot_metatrace__categories__pb2 + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( + b'\n5protos/perfetto/trace_processor/trace_processor.proto\x12\x0fperfetto.protos\x1a\'protos/perfetto/common/descriptor.proto\x1a:protos/perfetto/trace_processor/metatrace_categories.proto\"J\n\x17TraceProcessorRpcStream\x12/\n\x03msg\x18\x01 \x03(\x0b\x32\".perfetto.protos.TraceProcessorRpc\"\xc1\n\n\x11TraceProcessorRpc\x12\x0b\n\x03seq\x18\x01 \x01(\x03\x12\x13\n\x0b\x66\x61tal_error\x18\x05 \x01(\t\x12J\n\x07request\x18\x02 \x01(\x0e\x32\x37.perfetto.protos.TraceProcessorRpc.TraceProcessorMethodH\x00\x12K\n\x08response\x18\x03 \x01(\x0e\x32\x37.perfetto.protos.TraceProcessorRpc.TraceProcessorMethodH\x00\x12R\n\x0finvalid_request\x18\x04 \x01(\x0e\x32\x37.perfetto.protos.TraceProcessorRpc.TraceProcessorMethodH\x00\x12\x1b\n\x11\x61ppend_trace_data\x18\x65 \x01(\x0cH\x01\x12\x30\n\nquery_args\x18g \x01(\x0b\x32\x1a.perfetto.protos.QueryArgsH\x01\x12\x41\n\x13\x63ompute_metric_args\x18i \x01(\x0b\x32\".perfetto.protos.ComputeMetricArgsH\x01\x12\x45\n\x15\x65nable_metatrace_args\x18j \x01(\x0b\x32$.perfetto.protos.EnableMetatraceArgsH\x01\x12N\n\x1areset_trace_processor_args\x18k \x01(\x0b\x32(.perfetto.protos.ResetTraceProcessorArgsH\x01\x12@\n\rappend_result\x18\xc9\x01 \x01(\x0b\x32&.perfetto.protos.AppendTraceDataResultH\x01\x12\x35\n\x0cquery_result\x18\xcb\x01 \x01(\x0b\x32\x1c.perfetto.protos.QueryResultH\x01\x12>\n\rmetric_result\x18\xcd\x01 \x01(\x0b\x32$.perfetto.protos.ComputeMetricResultH\x01\x12=\n\x12metric_descriptors\x18\xce\x01 \x01(\x0b\x32\x1e.perfetto.protos.DescriptorSetH\x01\x12\x44\n\tmetatrace\x18\xd1\x01 \x01(\x0b\x32..perfetto.protos.DisableAndReadMetatraceResultH\x01\x12\x30\n\x06status\x18\xd2\x01 \x01(\x0b\x32\x1d.perfetto.protos.StatusResultH\x01\"\xe5\x02\n\x14TraceProcessorMethod\x12\x13\n\x0fTPM_UNSPECIFIED\x10\x00\x12\x19\n\x15TPM_APPEND_TRACE_DATA\x10\x01\x12\x1b\n\x17TPM_FINALIZE_TRACE_DATA\x10\x02\x12\x17\n\x13TPM_QUERY_STREAMING\x10\x03\x12\x16\n\x12TPM_COMPUTE_METRIC\x10\x05\x12\x1e\n\x1aTPM_GET_METRIC_DESCRIPTORS\x10\x06\x12\x1e\n\x1aTPM_RESTORE_INITIAL_TABLES\x10\x07\x12\x18\n\x14TPM_ENABLE_METATRACE\x10\x08\x12\"\n\x1eTPM_DISABLE_AND_READ_METATRACE\x10\t\x12\x12\n\x0eTPM_GET_STATUS\x10\n\x12\x1d\n\x19TPM_RESET_TRACE_PROCESSOR\x10\x0b\"\x04\x08\x04\x10\x04*\x18TPM_QUERY_RAW_DEPRECATEDB\x06\n\x04typeB\x06\n\x04\x61rgsJ\x04\x08h\x10iJ\x06\x08\xcc\x01\x10\xcd\x01\"B\n\x15\x41ppendTraceDataResult\x12\x1a\n\x12total_bytes_parsed\x18\x01 \x01(\x03\x12\r\n\x05\x65rror\x18\x02 \x01(\t\"1\n\tQueryArgs\x12\x11\n\tsql_query\x18\x01 \x01(\t\x12\x0b\n\x03tag\x18\x03 \x01(\tJ\x04\x08\x02\x10\x03\"\x84\x04\n\x0bQueryResult\x12\x14\n\x0c\x63olumn_names\x18\x01 \x03(\t\x12\r\n\x05\x65rror\x18\x02 \x01(\t\x12\x36\n\x05\x62\x61tch\x18\x03 \x03(\x0b\x32\'.perfetto.protos.QueryResult.CellsBatch\x12\x17\n\x0fstatement_count\x18\x04 \x01(\r\x12#\n\x1bstatement_with_output_count\x18\x05 \x01(\r\x12\x1a\n\x12last_statement_sql\x18\x06 \x01(\t\x1a\xbd\x02\n\nCellsBatch\x12\x43\n\x05\x63\x65lls\x18\x01 \x03(\x0e\x32\x30.perfetto.protos.QueryResult.CellsBatch.CellTypeB\x02\x10\x01\x12\x18\n\x0cvarint_cells\x18\x02 \x03(\x03\x42\x02\x10\x01\x12\x19\n\rfloat64_cells\x18\x03 \x03(\x01\x42\x02\x10\x01\x12\x12\n\nblob_cells\x18\x04 \x03(\x0c\x12\x14\n\x0cstring_cells\x18\x05 \x01(\t\x12\x15\n\ris_last_batch\x18\x06 \x01(\x08\"n\n\x08\x43\x65llType\x12\x10\n\x0c\x43\x45LL_INVALID\x10\x00\x12\r\n\tCELL_NULL\x10\x01\x12\x0f\n\x0b\x43\x45LL_VARINT\x10\x02\x12\x10\n\x0c\x43\x45LL_FLOAT64\x10\x03\x12\x0f\n\x0b\x43\x45LL_STRING\x10\x04\x12\r\n\tCELL_BLOB\x10\x05J\x04\x08\x07\x10\x08\"\x0c\n\nStatusArgs\"t\n\x0cStatusResult\x12\x19\n\x11loaded_trace_name\x18\x01 \x01(\t\x12\x1e\n\x16human_readable_version\x18\x02 \x01(\t\x12\x13\n\x0b\x61pi_version\x18\x03 \x01(\x05\x12\x14\n\x0cversion_code\x18\x04 \x01(\t\"\xa8\x01\n\x11\x43omputeMetricArgs\x12\x14\n\x0cmetric_names\x18\x01 \x03(\t\x12?\n\x06\x66ormat\x18\x02 \x01(\x0e\x32/.perfetto.protos.ComputeMetricArgs.ResultFormat\"<\n\x0cResultFormat\x12\x13\n\x0f\x42INARY_PROTOBUF\x10\x00\x12\r\n\tTEXTPROTO\x10\x01\x12\x08\n\x04JSON\x10\x02\"|\n\x13\x43omputeMetricResult\x12\x11\n\x07metrics\x18\x01 \x01(\x0cH\x00\x12\x1e\n\x14metrics_as_prototext\x18\x03 \x01(\tH\x00\x12\x19\n\x0fmetrics_as_json\x18\x04 \x01(\tH\x00\x12\r\n\x05\x65rror\x18\x02 \x01(\tB\x08\n\x06result\"O\n\x13\x45nableMetatraceArgs\x12\x38\n\ncategories\x18\x01 \x01(\x0e\x32$.perfetto.protos.MetatraceCategories\"\x17\n\x15\x45nableMetatraceResult\"\x1d\n\x1b\x44isableAndReadMetatraceArgs\"A\n\x1d\x44isableAndReadMetatraceResult\x12\x11\n\tmetatrace\x18\x01 \x01(\x0c\x12\r\n\x05\x65rror\x18\x02 \x01(\t\"F\n\rDescriptorSet\x12\x35\n\x0b\x64\x65scriptors\x18\x01 \x03(\x0b\x32 .perfetto.protos.DescriptorProto\"\xc1\x02\n\x17ResetTraceProcessorArgs\x12g\n\x1c\x64rop_track_event_data_before\x18\x01 \x01(\x0e\x32\x41.perfetto.protos.ResetTraceProcessorArgs.DropTrackEventDataBefore\x12\"\n\x1aingest_ftrace_in_raw_table\x18\x02 \x01(\x08\x12#\n\x1b\x61nalyze_trace_proto_content\x18\x03 \x01(\x08\x12(\n ftrace_drop_until_all_cpus_valid\x18\x04 \x01(\x08\"J\n\x18\x44ropTrackEventDataBefore\x12\x0b\n\x07NO_DROP\x10\x00\x12!\n\x1dTRACK_EVENT_RANGE_OF_INTEREST\x10\x01*C\n\x18TraceProcessorApiVersion\x12\'\n#TRACE_PROCESSOR_CURRENT_API_VERSION\x10\x0c' +) + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages( + DESCRIPTOR, + 'perfetto.bigtrace.protos.perfetto.trace_processor.trace_processor_pb2', + _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + DESCRIPTOR._options = None + _globals['_QUERYRESULT_CELLSBATCH'].fields_by_name['cells']._options = None + _globals['_QUERYRESULT_CELLSBATCH'].fields_by_name[ + 'cells']._serialized_options = b'\020\001' + _globals['_QUERYRESULT_CELLSBATCH'].fields_by_name[ + 'varint_cells']._options = None + _globals['_QUERYRESULT_CELLSBATCH'].fields_by_name[ + 'varint_cells']._serialized_options = b'\020\001' + _globals['_QUERYRESULT_CELLSBATCH'].fields_by_name[ + 'float64_cells']._options = None + _globals['_QUERYRESULT_CELLSBATCH'].fields_by_name[ + 'float64_cells']._serialized_options = b'\020\001' + _globals['_TRACEPROCESSORAPIVERSION']._serialized_start = 3266 + _globals['_TRACEPROCESSORAPIVERSION']._serialized_end = 3333 + _globals['_TRACEPROCESSORRPCSTREAM']._serialized_start = 175 + _globals['_TRACEPROCESSORRPCSTREAM']._serialized_end = 249 + _globals['_TRACEPROCESSORRPC']._serialized_start = 252 + _globals['_TRACEPROCESSORRPC']._serialized_end = 1597 + _globals['_TRACEPROCESSORRPC_TRACEPROCESSORMETHOD']._serialized_start = 1210 + _globals['_TRACEPROCESSORRPC_TRACEPROCESSORMETHOD']._serialized_end = 1567 + _globals['_APPENDTRACEDATARESULT']._serialized_start = 1599 + _globals['_APPENDTRACEDATARESULT']._serialized_end = 1665 + _globals['_QUERYARGS']._serialized_start = 1667 + _globals['_QUERYARGS']._serialized_end = 1716 + _globals['_QUERYRESULT']._serialized_start = 1719 + _globals['_QUERYRESULT']._serialized_end = 2235 + _globals['_QUERYRESULT_CELLSBATCH']._serialized_start = 1918 + _globals['_QUERYRESULT_CELLSBATCH']._serialized_end = 2235 + _globals['_QUERYRESULT_CELLSBATCH_CELLTYPE']._serialized_start = 2119 + _globals['_QUERYRESULT_CELLSBATCH_CELLTYPE']._serialized_end = 2229 + _globals['_STATUSARGS']._serialized_start = 2237 + _globals['_STATUSARGS']._serialized_end = 2249 + _globals['_STATUSRESULT']._serialized_start = 2251 + _globals['_STATUSRESULT']._serialized_end = 2367 + _globals['_COMPUTEMETRICARGS']._serialized_start = 2370 + _globals['_COMPUTEMETRICARGS']._serialized_end = 2538 + _globals['_COMPUTEMETRICARGS_RESULTFORMAT']._serialized_start = 2478 + _globals['_COMPUTEMETRICARGS_RESULTFORMAT']._serialized_end = 2538 + _globals['_COMPUTEMETRICRESULT']._serialized_start = 2540 + _globals['_COMPUTEMETRICRESULT']._serialized_end = 2664 + _globals['_ENABLEMETATRACEARGS']._serialized_start = 2666 + _globals['_ENABLEMETATRACEARGS']._serialized_end = 2745 + _globals['_ENABLEMETATRACERESULT']._serialized_start = 2747 + _globals['_ENABLEMETATRACERESULT']._serialized_end = 2770 + _globals['_DISABLEANDREADMETATRACEARGS']._serialized_start = 2772 + _globals['_DISABLEANDREADMETATRACEARGS']._serialized_end = 2801 + _globals['_DISABLEANDREADMETATRACERESULT']._serialized_start = 2803 + _globals['_DISABLEANDREADMETATRACERESULT']._serialized_end = 2868 + _globals['_DESCRIPTORSET']._serialized_start = 2870 + _globals['_DESCRIPTORSET']._serialized_end = 2940 + _globals['_RESETTRACEPROCESSORARGS']._serialized_start = 2943 + _globals['_RESETTRACEPROCESSORARGS']._serialized_end = 3264 + _globals[ + '_RESETTRACEPROCESSORARGS_DROPTRACKEVENTDATABEFORE']._serialized_start = 3190 + _globals[ + '_RESETTRACEPROCESSORARGS_DROPTRACKEVENTDATABEFORE']._serialized_end = 3264 +# @@protoc_insertion_point(module_scope) diff --git a/python/perfetto/bigtrace/protos/perfetto/trace_processor/trace_processor_pb2.pyi b/python/perfetto/bigtrace/protos/perfetto/trace_processor/trace_processor_pb2.pyi new file mode 100644 index 0000000000..7f55693cbe --- /dev/null +++ b/python/perfetto/bigtrace/protos/perfetto/trace_processor/trace_processor_pb2.pyi @@ -0,0 +1,231 @@ +from perfetto.bigtrace.protos.perfetto.common import descriptor_pb2 as _descriptor_pb2 +from perfetto.bigtrace.protos.perfetto.trace_processor import metatrace_categories_pb2 as _metatrace_categories_pb2 +from google.protobuf.internal import containers as _containers +from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Mapping, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class TraceProcessorApiVersion(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + TRACE_PROCESSOR_CURRENT_API_VERSION: _ClassVar[TraceProcessorApiVersion] +TRACE_PROCESSOR_CURRENT_API_VERSION: TraceProcessorApiVersion + +class TraceProcessorRpcStream(_message.Message): + __slots__ = ("msg",) + MSG_FIELD_NUMBER: _ClassVar[int] + msg: _containers.RepeatedCompositeFieldContainer[TraceProcessorRpc] + def __init__(self, msg: _Optional[_Iterable[_Union[TraceProcessorRpc, _Mapping]]] = ...) -> None: ... + +class TraceProcessorRpc(_message.Message): + __slots__ = ("seq", "fatal_error", "request", "response", "invalid_request", "append_trace_data", "query_args", "compute_metric_args", "enable_metatrace_args", "reset_trace_processor_args", "append_result", "query_result", "metric_result", "metric_descriptors", "metatrace", "status") + class TraceProcessorMethod(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + TPM_UNSPECIFIED: _ClassVar[TraceProcessorRpc.TraceProcessorMethod] + TPM_APPEND_TRACE_DATA: _ClassVar[TraceProcessorRpc.TraceProcessorMethod] + TPM_FINALIZE_TRACE_DATA: _ClassVar[TraceProcessorRpc.TraceProcessorMethod] + TPM_QUERY_STREAMING: _ClassVar[TraceProcessorRpc.TraceProcessorMethod] + TPM_COMPUTE_METRIC: _ClassVar[TraceProcessorRpc.TraceProcessorMethod] + TPM_GET_METRIC_DESCRIPTORS: _ClassVar[TraceProcessorRpc.TraceProcessorMethod] + TPM_RESTORE_INITIAL_TABLES: _ClassVar[TraceProcessorRpc.TraceProcessorMethod] + TPM_ENABLE_METATRACE: _ClassVar[TraceProcessorRpc.TraceProcessorMethod] + TPM_DISABLE_AND_READ_METATRACE: _ClassVar[TraceProcessorRpc.TraceProcessorMethod] + TPM_GET_STATUS: _ClassVar[TraceProcessorRpc.TraceProcessorMethod] + TPM_RESET_TRACE_PROCESSOR: _ClassVar[TraceProcessorRpc.TraceProcessorMethod] + TPM_UNSPECIFIED: TraceProcessorRpc.TraceProcessorMethod + TPM_APPEND_TRACE_DATA: TraceProcessorRpc.TraceProcessorMethod + TPM_FINALIZE_TRACE_DATA: TraceProcessorRpc.TraceProcessorMethod + TPM_QUERY_STREAMING: TraceProcessorRpc.TraceProcessorMethod + TPM_COMPUTE_METRIC: TraceProcessorRpc.TraceProcessorMethod + TPM_GET_METRIC_DESCRIPTORS: TraceProcessorRpc.TraceProcessorMethod + TPM_RESTORE_INITIAL_TABLES: TraceProcessorRpc.TraceProcessorMethod + TPM_ENABLE_METATRACE: TraceProcessorRpc.TraceProcessorMethod + TPM_DISABLE_AND_READ_METATRACE: TraceProcessorRpc.TraceProcessorMethod + TPM_GET_STATUS: TraceProcessorRpc.TraceProcessorMethod + TPM_RESET_TRACE_PROCESSOR: TraceProcessorRpc.TraceProcessorMethod + SEQ_FIELD_NUMBER: _ClassVar[int] + FATAL_ERROR_FIELD_NUMBER: _ClassVar[int] + REQUEST_FIELD_NUMBER: _ClassVar[int] + RESPONSE_FIELD_NUMBER: _ClassVar[int] + INVALID_REQUEST_FIELD_NUMBER: _ClassVar[int] + APPEND_TRACE_DATA_FIELD_NUMBER: _ClassVar[int] + QUERY_ARGS_FIELD_NUMBER: _ClassVar[int] + COMPUTE_METRIC_ARGS_FIELD_NUMBER: _ClassVar[int] + ENABLE_METATRACE_ARGS_FIELD_NUMBER: _ClassVar[int] + RESET_TRACE_PROCESSOR_ARGS_FIELD_NUMBER: _ClassVar[int] + APPEND_RESULT_FIELD_NUMBER: _ClassVar[int] + QUERY_RESULT_FIELD_NUMBER: _ClassVar[int] + METRIC_RESULT_FIELD_NUMBER: _ClassVar[int] + METRIC_DESCRIPTORS_FIELD_NUMBER: _ClassVar[int] + METATRACE_FIELD_NUMBER: _ClassVar[int] + STATUS_FIELD_NUMBER: _ClassVar[int] + seq: int + fatal_error: str + request: TraceProcessorRpc.TraceProcessorMethod + response: TraceProcessorRpc.TraceProcessorMethod + invalid_request: TraceProcessorRpc.TraceProcessorMethod + append_trace_data: bytes + query_args: QueryArgs + compute_metric_args: ComputeMetricArgs + enable_metatrace_args: EnableMetatraceArgs + reset_trace_processor_args: ResetTraceProcessorArgs + append_result: AppendTraceDataResult + query_result: QueryResult + metric_result: ComputeMetricResult + metric_descriptors: DescriptorSet + metatrace: DisableAndReadMetatraceResult + status: StatusResult + def __init__(self, seq: _Optional[int] = ..., fatal_error: _Optional[str] = ..., request: _Optional[_Union[TraceProcessorRpc.TraceProcessorMethod, str]] = ..., response: _Optional[_Union[TraceProcessorRpc.TraceProcessorMethod, str]] = ..., invalid_request: _Optional[_Union[TraceProcessorRpc.TraceProcessorMethod, str]] = ..., append_trace_data: _Optional[bytes] = ..., query_args: _Optional[_Union[QueryArgs, _Mapping]] = ..., compute_metric_args: _Optional[_Union[ComputeMetricArgs, _Mapping]] = ..., enable_metatrace_args: _Optional[_Union[EnableMetatraceArgs, _Mapping]] = ..., reset_trace_processor_args: _Optional[_Union[ResetTraceProcessorArgs, _Mapping]] = ..., append_result: _Optional[_Union[AppendTraceDataResult, _Mapping]] = ..., query_result: _Optional[_Union[QueryResult, _Mapping]] = ..., metric_result: _Optional[_Union[ComputeMetricResult, _Mapping]] = ..., metric_descriptors: _Optional[_Union[DescriptorSet, _Mapping]] = ..., metatrace: _Optional[_Union[DisableAndReadMetatraceResult, _Mapping]] = ..., status: _Optional[_Union[StatusResult, _Mapping]] = ...) -> None: ... + +class AppendTraceDataResult(_message.Message): + __slots__ = ("total_bytes_parsed", "error") + TOTAL_BYTES_PARSED_FIELD_NUMBER: _ClassVar[int] + ERROR_FIELD_NUMBER: _ClassVar[int] + total_bytes_parsed: int + error: str + def __init__(self, total_bytes_parsed: _Optional[int] = ..., error: _Optional[str] = ...) -> None: ... + +class QueryArgs(_message.Message): + __slots__ = ("sql_query", "tag") + SQL_QUERY_FIELD_NUMBER: _ClassVar[int] + TAG_FIELD_NUMBER: _ClassVar[int] + sql_query: str + tag: str + def __init__(self, sql_query: _Optional[str] = ..., tag: _Optional[str] = ...) -> None: ... + +class QueryResult(_message.Message): + __slots__ = ("column_names", "error", "batch", "statement_count", "statement_with_output_count", "last_statement_sql") + class CellsBatch(_message.Message): + __slots__ = ("cells", "varint_cells", "float64_cells", "blob_cells", "string_cells", "is_last_batch") + class CellType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + CELL_INVALID: _ClassVar[QueryResult.CellsBatch.CellType] + CELL_NULL: _ClassVar[QueryResult.CellsBatch.CellType] + CELL_VARINT: _ClassVar[QueryResult.CellsBatch.CellType] + CELL_FLOAT64: _ClassVar[QueryResult.CellsBatch.CellType] + CELL_STRING: _ClassVar[QueryResult.CellsBatch.CellType] + CELL_BLOB: _ClassVar[QueryResult.CellsBatch.CellType] + CELL_INVALID: QueryResult.CellsBatch.CellType + CELL_NULL: QueryResult.CellsBatch.CellType + CELL_VARINT: QueryResult.CellsBatch.CellType + CELL_FLOAT64: QueryResult.CellsBatch.CellType + CELL_STRING: QueryResult.CellsBatch.CellType + CELL_BLOB: QueryResult.CellsBatch.CellType + CELLS_FIELD_NUMBER: _ClassVar[int] + VARINT_CELLS_FIELD_NUMBER: _ClassVar[int] + FLOAT64_CELLS_FIELD_NUMBER: _ClassVar[int] + BLOB_CELLS_FIELD_NUMBER: _ClassVar[int] + STRING_CELLS_FIELD_NUMBER: _ClassVar[int] + IS_LAST_BATCH_FIELD_NUMBER: _ClassVar[int] + cells: _containers.RepeatedScalarFieldContainer[QueryResult.CellsBatch.CellType] + varint_cells: _containers.RepeatedScalarFieldContainer[int] + float64_cells: _containers.RepeatedScalarFieldContainer[float] + blob_cells: _containers.RepeatedScalarFieldContainer[bytes] + string_cells: str + is_last_batch: bool + def __init__(self, cells: _Optional[_Iterable[_Union[QueryResult.CellsBatch.CellType, str]]] = ..., varint_cells: _Optional[_Iterable[int]] = ..., float64_cells: _Optional[_Iterable[float]] = ..., blob_cells: _Optional[_Iterable[bytes]] = ..., string_cells: _Optional[str] = ..., is_last_batch: bool = ...) -> None: ... + COLUMN_NAMES_FIELD_NUMBER: _ClassVar[int] + ERROR_FIELD_NUMBER: _ClassVar[int] + BATCH_FIELD_NUMBER: _ClassVar[int] + STATEMENT_COUNT_FIELD_NUMBER: _ClassVar[int] + STATEMENT_WITH_OUTPUT_COUNT_FIELD_NUMBER: _ClassVar[int] + LAST_STATEMENT_SQL_FIELD_NUMBER: _ClassVar[int] + column_names: _containers.RepeatedScalarFieldContainer[str] + error: str + batch: _containers.RepeatedCompositeFieldContainer[QueryResult.CellsBatch] + statement_count: int + statement_with_output_count: int + last_statement_sql: str + def __init__(self, column_names: _Optional[_Iterable[str]] = ..., error: _Optional[str] = ..., batch: _Optional[_Iterable[_Union[QueryResult.CellsBatch, _Mapping]]] = ..., statement_count: _Optional[int] = ..., statement_with_output_count: _Optional[int] = ..., last_statement_sql: _Optional[str] = ...) -> None: ... + +class StatusArgs(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + +class StatusResult(_message.Message): + __slots__ = ("loaded_trace_name", "human_readable_version", "api_version", "version_code") + LOADED_TRACE_NAME_FIELD_NUMBER: _ClassVar[int] + HUMAN_READABLE_VERSION_FIELD_NUMBER: _ClassVar[int] + API_VERSION_FIELD_NUMBER: _ClassVar[int] + VERSION_CODE_FIELD_NUMBER: _ClassVar[int] + loaded_trace_name: str + human_readable_version: str + api_version: int + version_code: str + def __init__(self, loaded_trace_name: _Optional[str] = ..., human_readable_version: _Optional[str] = ..., api_version: _Optional[int] = ..., version_code: _Optional[str] = ...) -> None: ... + +class ComputeMetricArgs(_message.Message): + __slots__ = ("metric_names", "format") + class ResultFormat(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + BINARY_PROTOBUF: _ClassVar[ComputeMetricArgs.ResultFormat] + TEXTPROTO: _ClassVar[ComputeMetricArgs.ResultFormat] + JSON: _ClassVar[ComputeMetricArgs.ResultFormat] + BINARY_PROTOBUF: ComputeMetricArgs.ResultFormat + TEXTPROTO: ComputeMetricArgs.ResultFormat + JSON: ComputeMetricArgs.ResultFormat + METRIC_NAMES_FIELD_NUMBER: _ClassVar[int] + FORMAT_FIELD_NUMBER: _ClassVar[int] + metric_names: _containers.RepeatedScalarFieldContainer[str] + format: ComputeMetricArgs.ResultFormat + def __init__(self, metric_names: _Optional[_Iterable[str]] = ..., format: _Optional[_Union[ComputeMetricArgs.ResultFormat, str]] = ...) -> None: ... + +class ComputeMetricResult(_message.Message): + __slots__ = ("metrics", "metrics_as_prototext", "metrics_as_json", "error") + METRICS_FIELD_NUMBER: _ClassVar[int] + METRICS_AS_PROTOTEXT_FIELD_NUMBER: _ClassVar[int] + METRICS_AS_JSON_FIELD_NUMBER: _ClassVar[int] + ERROR_FIELD_NUMBER: _ClassVar[int] + metrics: bytes + metrics_as_prototext: str + metrics_as_json: str + error: str + def __init__(self, metrics: _Optional[bytes] = ..., metrics_as_prototext: _Optional[str] = ..., metrics_as_json: _Optional[str] = ..., error: _Optional[str] = ...) -> None: ... + +class EnableMetatraceArgs(_message.Message): + __slots__ = ("categories",) + CATEGORIES_FIELD_NUMBER: _ClassVar[int] + categories: _metatrace_categories_pb2.MetatraceCategories + def __init__(self, categories: _Optional[_Union[_metatrace_categories_pb2.MetatraceCategories, str]] = ...) -> None: ... + +class EnableMetatraceResult(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + +class DisableAndReadMetatraceArgs(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + +class DisableAndReadMetatraceResult(_message.Message): + __slots__ = ("metatrace", "error") + METATRACE_FIELD_NUMBER: _ClassVar[int] + ERROR_FIELD_NUMBER: _ClassVar[int] + metatrace: bytes + error: str + def __init__(self, metatrace: _Optional[bytes] = ..., error: _Optional[str] = ...) -> None: ... + +class DescriptorSet(_message.Message): + __slots__ = ("descriptors",) + DESCRIPTORS_FIELD_NUMBER: _ClassVar[int] + descriptors: _containers.RepeatedCompositeFieldContainer[_descriptor_pb2.DescriptorProto] + def __init__(self, descriptors: _Optional[_Iterable[_Union[_descriptor_pb2.DescriptorProto, _Mapping]]] = ...) -> None: ... + +class ResetTraceProcessorArgs(_message.Message): + __slots__ = ("drop_track_event_data_before", "ingest_ftrace_in_raw_table", "analyze_trace_proto_content", "ftrace_drop_until_all_cpus_valid") + class DropTrackEventDataBefore(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + NO_DROP: _ClassVar[ResetTraceProcessorArgs.DropTrackEventDataBefore] + TRACK_EVENT_RANGE_OF_INTEREST: _ClassVar[ResetTraceProcessorArgs.DropTrackEventDataBefore] + NO_DROP: ResetTraceProcessorArgs.DropTrackEventDataBefore + TRACK_EVENT_RANGE_OF_INTEREST: ResetTraceProcessorArgs.DropTrackEventDataBefore + DROP_TRACK_EVENT_DATA_BEFORE_FIELD_NUMBER: _ClassVar[int] + INGEST_FTRACE_IN_RAW_TABLE_FIELD_NUMBER: _ClassVar[int] + ANALYZE_TRACE_PROTO_CONTENT_FIELD_NUMBER: _ClassVar[int] + FTRACE_DROP_UNTIL_ALL_CPUS_VALID_FIELD_NUMBER: _ClassVar[int] + drop_track_event_data_before: ResetTraceProcessorArgs.DropTrackEventDataBefore + ingest_ftrace_in_raw_table: bool + analyze_trace_proto_content: bool + ftrace_drop_until_all_cpus_valid: bool + def __init__(self, drop_track_event_data_before: _Optional[_Union[ResetTraceProcessorArgs.DropTrackEventDataBefore, str]] = ..., ingest_ftrace_in_raw_table: bool = ..., analyze_trace_proto_content: bool = ..., ftrace_drop_until_all_cpus_valid: bool = ...) -> None: ... diff --git a/infra/ci/controller/appengine_config.py b/python/perfetto/common/exceptions.py similarity index 78% rename from infra/ci/controller/appengine_config.py rename to python/perfetto/common/exceptions.py index 91ff5bdf4c..b01dea73e1 100644 --- a/infra/ci/controller/appengine_config.py +++ b/python/perfetto/common/exceptions.py @@ -1,4 +1,4 @@ -# Copyright (C) 2019 The Android Open Source Project +# Copyright (C) 2024 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,5 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -from google.appengine.ext import vendor -vendor.add('lib') \ No newline at end of file + +class PerfettoException(Exception): + + def __init__(self, message): + super().__init__(message) \ No newline at end of file diff --git a/python/perfetto/common/query_result_iterator.py b/python/perfetto/common/query_result_iterator.py new file mode 100644 index 0000000000..83c7a5993c --- /dev/null +++ b/python/perfetto/common/query_result_iterator.py @@ -0,0 +1,161 @@ +# 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. + +from perfetto.common.exceptions import PerfettoException + + +# Provides a Python interface to operate on the contents of QueryResult protos +class QueryResultIterator: + # Values of these constants correspond to the QueryResponse message at + # protos/perfetto/trace_processor/trace_processor.proto + QUERY_CELL_INVALID_FIELD_ID = 0 + QUERY_CELL_NULL_FIELD_ID = 1 + QUERY_CELL_VARINT_FIELD_ID = 2 + QUERY_CELL_FLOAT64_FIELD_ID = 3 + QUERY_CELL_STRING_FIELD_ID = 4 + QUERY_CELL_BLOB_FIELD_ID = 5 + + # This is the class returned to the user and contains one row of the + # resultant query. Each column name is stored as an attribute of this + # class, with the value corresponding to the column name and row in + # the query results table. + class Row(object): + # Required for pytype to correctly infer attributes from Row objects + _HAS_DYNAMIC_ATTRIBUTES = True + + def __str__(self): + return str(self.__dict__) + + def __repr__(self): + return self.__dict__ + + def __init__(self, column_names, batches): + self.__column_names = list(column_names) + self.__column_count = 0 + self.__count = 0 + self.__cells = [] + self.__data_lists = [[], [], [], [], [], []] + self.__data_lists_index = [0, 0, 0, 0, 0, 0] + self.__current_index = 0 + + # Iterate over all the batches and collect their + # contents into lists based on the type of the batch + batch_index = 0 + while True: + # It's possible on some occasions that there are non UTF-8 characters + # in the string_cells field. If this is the case, string_cells is + # a bytestring which needs to be decoded (but passing ignore so that + # we don't fail in decoding). + strings_batch_str = batches[batch_index].string_cells + try: + strings_batch_str = strings_batch_str.decode('utf-8', 'ignore') + except AttributeError: + # AttributeError can occur when |strings_batch_str| is an str which + # happens when everything in it is UTF-8 (protobuf automatically + # does the conversion if it can). + pass + + # Null-terminated strings in a batch are concatenated + # into a single large byte array, so we split on the + # null-terminator to get the individual strings + strings_batch = strings_batch_str.split('\0')[:-1] + self.__data_lists[QueryResultIterator.QUERY_CELL_STRING_FIELD_ID].extend( + strings_batch) + self.__data_lists[QueryResultIterator.QUERY_CELL_VARINT_FIELD_ID].extend( + batches[batch_index].varint_cells) + self.__data_lists[QueryResultIterator.QUERY_CELL_FLOAT64_FIELD_ID].extend( + batches[batch_index].float64_cells) + self.__data_lists[QueryResultIterator.QUERY_CELL_BLOB_FIELD_ID].extend( + batches[batch_index].blob_cells) + self.__cells.extend(batches[batch_index].cells) + + if batches[batch_index].is_last_batch: + break + + batch_index += 1 + + # If there are no rows in the query result, don't bother updating the + # counts to avoid dealing with / 0 errors. + if len(self.__cells) == 0: + return + + # The count we collected so far was a count of all individual columns + # in the query result, so we divide by the number of columns in a row + # to get the number of rows + self.__column_count = len(self.__column_names) + self.__count = int(len(self.__cells) / self.__column_count) + + # Data integrity check - see that we have the expected amount of cells + # for the number of rows that we need to return + if len(self.__cells) % self.__column_count != 0: + raise PerfettoException("Cell count " + str(len(self.__cells)) + + " is not a multiple of column count " + + str(len(self.__column_names))) + + # To use the query result as a populated Pandas dataframe, this + # function must be called directly after calling query inside + # TraceProcessor / Bigtrace. + def as_pandas_dataframe(self): + try: + import pandas as pd + + # Populate the dataframe with the query results + rows = [] + for i in range(0, self.__count): + row = [] + base_cell_index = i * self.__column_count + for num in range(len(self.__column_names)): + col_type = self.__cells[base_cell_index + num] + if col_type == QueryResultIterator.QUERY_CELL_INVALID_FIELD_ID: + raise PerfettoException('Invalid cell type') + + if col_type == QueryResultIterator.QUERY_CELL_NULL_FIELD_ID: + row.append(None) + else: + col_index = self.__data_lists_index[col_type] + self.__data_lists_index[col_type] += 1 + row.append(self.__data_lists[col_type][col_index]) + rows.append(row) + + df = pd.DataFrame(rows, columns=self.__column_names) + return df.astype(object).where(df.notnull(), None).reset_index(drop=True) + + except ModuleNotFoundError: + raise PerfettoException( + 'Python dependencies missing. Please pip3 install pandas numpy') + + def __len__(self): + return self.__count + + def __iter__(self): + return self + + def __next__(self): + if self.__current_index == self.__count: + raise StopIteration + result = QueryResultIterator.Row() + base_cell_index = self.__current_index * self.__column_count + for num, column_name in enumerate(self.__column_names): + col_type = self.__cells[base_cell_index + num] + if col_type == QueryResultIterator.QUERY_CELL_INVALID_FIELD_ID: + raise PerfettoException('Invalid cell type') + if col_type != QueryResultIterator.QUERY_CELL_NULL_FIELD_ID: + col_index = self.__data_lists_index[col_type] + self.__data_lists_index[col_type] += 1 + setattr(result, column_name, self.__data_lists[col_type][col_index]) + else: + setattr(result, column_name, None) + + self.__current_index += 1 + return result diff --git a/python/perfetto/prebuilts/manifests/trace_processor_shell.py b/python/perfetto/prebuilts/manifests/trace_processor_shell.py index 6cd94c87ac..4e08d827c0 100755 --- a/python/perfetto/prebuilts/manifests/trace_processor_shell.py +++ b/python/perfetto/prebuilts/manifests/trace_processor_shell.py @@ -1,15 +1,15 @@ -# This file has been generated by: tools/roll-prebuilts v45.0 +# This file has been generated by: tools/roll-prebuilts v46.0 TRACE_PROCESSOR_SHELL_MANIFEST = [{ 'arch': 'mac-amd64', 'file_name': 'trace_processor_shell', 'file_size': - 9092856, + 9503800, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/mac-amd64/trace_processor_shell', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/mac-amd64/trace_processor_shell', 'sha256': - '13b45c29743b296221ebdda630913a8630f66c1848b6d894ba489545c8098575', + 'c979a616c2aeada9ff5e0807f15089c7415de7e1f19670945792be5953c05f87', 'platform': 'darwin', 'machine': ['x86_64'] @@ -19,11 +19,11 @@ 'file_name': 'trace_processor_shell', 'file_size': - 8476632, + 8873912, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/mac-arm64/trace_processor_shell', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/mac-arm64/trace_processor_shell', 'sha256': - 'aefb6a85618716b172c6d81cf7d35b0f71e6e67f6a3601685548a378bb9278f0', + '8f88e9ec002fb0e95bcfdd655e1a9632487010876a234bc545c85c44d0019294', 'platform': 'darwin', 'machine': ['arm64'] @@ -33,11 +33,11 @@ 'file_name': 'trace_processor_shell', 'file_size': - 9253528, + 9675688, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/linux-amd64/trace_processor_shell', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/linux-amd64/trace_processor_shell', 'sha256': - 'e8b2e9507ec80145121014c9f3e50c1e0e5a896e8d925dbe7b95f69b1d1e9214', + '70adf26bc45d6de8327b1e46e98f64d4fe8ccf9fd31a1391bb0916c31aaa9df8', 'platform': 'linux', 'machine': ['x86_64'] @@ -47,11 +47,11 @@ 'file_name': 'trace_processor_shell', 'file_size': - 6781500, + 7098492, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/linux-arm/trace_processor_shell', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/linux-arm/trace_processor_shell', 'sha256': - '9ecd4f09f22270aff1926c80518b62425466c57c95ce94f912cfe3b325d1d0a9', + 'b9d5abb196a99d1ade08db3d97bff3c71e8ee4ccd6fedb6332effa4d5d50c21d', 'platform': 'linux', 'machine': ['armv6l', 'armv7l', 'armv8l'] @@ -61,11 +61,11 @@ 'file_name': 'trace_processor_shell', 'file_size': - 8912968, + 9297416, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/linux-arm64/trace_processor_shell', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/linux-arm64/trace_processor_shell', 'sha256': - 'd3165c02bf665fe7513acb7de3b0186a91610328cf01afa5cfce437fdf589e36', + 'b0495e1ba864f3a315a31353e02f25fe8bcfc993ec6cdae1545f12f33f008c8c', 'platform': 'linux', 'machine': ['aarch64'] @@ -75,55 +75,55 @@ 'file_name': 'trace_processor_shell', 'file_size': - 6786364, + 7111084, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/android-arm/trace_processor_shell', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/android-arm/trace_processor_shell', 'sha256': - 'f25fdc4021c34bd20248359cdfe974c84413b877d7c55886eb37310028b950d3' + 'd8aa4b6460aca2e166755952fa14010d1f80ddb6b6dc406ca2b5246f9885b153' }, { 'arch': 'android-arm64', 'file_name': 'trace_processor_shell', 'file_size': - 8809584, + 9200536, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/android-arm64/trace_processor_shell', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/android-arm64/trace_processor_shell', 'sha256': - '458501bf14813679333bc40e1bf6275d54bbba20b4ae18dbc7aa212d7d3ef3ec' + '1c1843ec2e127b5f7aa8eb9e8d2c216e4cc99f681ecf437ff09f7abc56191cef' }, { 'arch': 'android-x86', 'file_name': 'trace_processor_shell', 'file_size': - 9682456, + 10094664, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/android-x86/trace_processor_shell', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/android-x86/trace_processor_shell', 'sha256': - '3ff5752895dc89bf245e8541dda809b7de27cebb8854835041f00fc549e685c1' + '4501c9aa2eaaec62155aac914a35b3c61e9d434e765b20fcefa49b7d01f978f0' }, { 'arch': 'android-x64', 'file_name': 'trace_processor_shell', 'file_size': - 9070288, + 9464288, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/android-x64/trace_processor_shell', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/android-x64/trace_processor_shell', 'sha256': - '2f431c7bc515d7d0d5fb49fdead6628c7133e0b03b2b2397a874009a80739ba0' + 'fb54ee018ca1d4895ad3e1192e2e585a3709b58864ef3beab4368af3ae17e5fb' }, { 'arch': 'windows-amd64', 'file_name': 'trace_processor_shell.exe', 'file_size': - 9165312, + 9548288, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/windows-amd64/trace_processor_shell.exe', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/windows-amd64/trace_processor_shell.exe', 'sha256': - '3a5432bebe30520ddceeafd2f77b2d44608e5942c06d1d50f908c5921375944a', + '41a3353caf59d4df14e7d5ba185861fdc8e41776c1720a6fc1eddc6a99afb6c0', 'platform': 'win32', 'machine': ['amd64'] diff --git a/python/perfetto/prebuilts/manifests/tracebox.py b/python/perfetto/prebuilts/manifests/tracebox.py index b849211e87..eb4fd85d83 100755 --- a/python/perfetto/prebuilts/manifests/tracebox.py +++ b/python/perfetto/prebuilts/manifests/tracebox.py @@ -1,15 +1,15 @@ -# This file has been generated by: tools/roll-prebuilts v45.0 +# This file has been generated by: tools/roll-prebuilts v46.0 TRACEBOX_MANIFEST = [{ 'arch': 'mac-amd64', 'file_name': 'tracebox', 'file_size': - 1581024, + 1597432, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/mac-amd64/tracebox', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/mac-amd64/tracebox', 'sha256': - '79661ca8986fb81a1ed8b5759b5b5259d581ec3324c9e9ff7ccd0d7d06b7dd80', + 'fda9aa1a57fc6bd85a7f332a436ae0ba8629eac81f5fd0e21a72fe3673b2d609', 'platform': 'darwin', 'machine': ['x86_64'] @@ -19,11 +19,11 @@ 'file_name': 'tracebox', 'file_size': - 1459096, + 1459128, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/mac-arm64/tracebox', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/mac-arm64/tracebox', 'sha256': - '232709e7fd1afd745543880edc721eed3817c2ca61dcfbd5083a812082e651a2', + '9a7ee198c0b2ca41edd73afc313193e6f643aaa1a88cafb1e515b76d6dcc47dd', 'platform': 'darwin', 'machine': ['arm64'] @@ -33,11 +33,11 @@ 'file_name': 'tracebox', 'file_size': - 2308416, + 2333576, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/linux-amd64/tracebox', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/linux-amd64/tracebox', 'sha256': - '4e07b5cf49e61d3c8c323eaa1c3b4b6da93f1c57c8470cad6fb64536c46cb0a5', + '3567682e999c9bc36c9c757fe1fc56067963e226573377da21747b7686238012', 'platform': 'linux', 'machine': ['x86_64'] @@ -47,11 +47,11 @@ 'file_name': 'tracebox', 'file_size': - 1411304, + 1422204, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/linux-arm/tracebox', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/linux-arm/tracebox', 'sha256': - '5b5b2bed6c7893c2ff868ced6f5de939f860a807bc45cf3705a78e1cacc9ea0b', + '66890d26ab8f88241b3608ce099e45dee7179ddeca3966dab6e7c1ade78963e3', 'platform': 'linux', 'machine': ['armv6l', 'armv7l', 'armv8l'] @@ -61,11 +61,11 @@ 'file_name': 'tracebox', 'file_size': - 2215800, + 2229176, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/linux-arm64/tracebox', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/linux-arm64/tracebox', 'sha256': - 'd86b27f9359862ae38cb9964938d87fc189c2f61415a5d8136ab00d987fc736c', + '425d7a8e88054b5b314bea3db884722a6646d9d7e8230b8ebebc044e57207739', 'platform': 'linux', 'machine': ['aarch64'] @@ -75,42 +75,42 @@ 'file_name': 'tracebox', 'file_size': - 1301920, + 1314720, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/android-arm/tracebox', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/android-arm/tracebox', 'sha256': - 'd8221b091a5f0b41168aa922cc12ea9cda9e3b85183086d84eecfc5b904fe494' + 'e3a6905bf8db5af2bd2b83512a745cfd3d86cf842fc81b1d1b3a05f4fb9f15d0' }, { 'arch': 'android-arm64', 'file_name': 'tracebox', 'file_size': - 2071440, + 2086288, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/android-arm64/tracebox', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/android-arm64/tracebox', 'sha256': - '34de36eb41091d72c5bc6dcba6886f57f7d7d3a6a1d10d38c9516f2517ff2437' + 'b506b076e19470afa4ca3f625d596f894a3778b3fe2fd7ad9f97f9e136f25542' }, { 'arch': 'android-x86', 'file_name': 'tracebox', 'file_size': - 2245480, + 2264088, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/android-x86/tracebox', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/android-x86/tracebox', 'sha256': - '5b4bc326f6dcf3b6a0ea970a6ba8da88f053f93f666ca20e15f1d8a35ac7b3fb' + '953cb01053d8094a5e39e05acc2f5b81a73836e699e4e0a7469c0cfa7c820364' }, { 'arch': 'android-x64', 'file_name': 'tracebox', 'file_size': - 2097064, + 2114328, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/android-x64/tracebox', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/android-x64/tracebox', 'sha256': - '444d0da4e68b4440f3be114152f8942d29c938dacc1cdf1a497d59d37cd2a203' + '570d475684bcc93ae8b6b8a5566887337d94aff91dea2270a0feb1c0bec00a8b' }] diff --git a/python/perfetto/prebuilts/manifests/traceconv.py b/python/perfetto/prebuilts/manifests/traceconv.py index ba8df26a9b..982faee85a 100755 --- a/python/perfetto/prebuilts/manifests/traceconv.py +++ b/python/perfetto/prebuilts/manifests/traceconv.py @@ -1,15 +1,15 @@ -# This file has been generated by: tools/roll-prebuilts v45.0 +# This file has been generated by: tools/roll-prebuilts v46.0 TRACECONV_MANIFEST = [{ 'arch': 'mac-amd64', 'file_name': 'traceconv', 'file_size': - 8299680, + 8743272, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/mac-amd64/traceconv', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/mac-amd64/traceconv', 'sha256': - '80291c46aea5bf3b1007a385fc6442a99865bfdd70ea627b3e9a2e19d6d62de1', + 'e0abc72fc69d3be68f16b198253ea9a802c266f7c2609debe561976b627d5d67', 'platform': 'darwin', 'machine': ['x86_64'] @@ -19,11 +19,11 @@ 'file_name': 'traceconv', 'file_size': - 7761304, + 8158456, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/mac-arm64/traceconv', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/mac-arm64/traceconv', 'sha256': - '53b78c7f9ee26a766b2d8f28e8033367f697d669b133383dfb116fd44f7ffa8a', + '9cd1635898536bcdcff8b944b05bf2ffc84fc0c55ba270746203a4fa0bae9f01', 'platform': 'darwin', 'machine': ['arm64'] @@ -33,11 +33,11 @@ 'file_name': 'traceconv', 'file_size': - 8353608, + 8809008, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/linux-amd64/traceconv', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/linux-amd64/traceconv', 'sha256': - '57e1cf04dc25fa6a6f7ae3482158f2dfb363269baeab1673343974312551a753', + '63e8599c19125db25bb1d1d39b0bba6b30ea30760ebfce87d0d9508458cad8ba', 'platform': 'linux', 'machine': ['x86_64'] @@ -47,11 +47,11 @@ 'file_name': 'traceconv', 'file_size': - 6317844, + 6665536, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/linux-arm/traceconv', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/linux-arm/traceconv', 'sha256': - 'e361944a78b55b83bc072043dd983b8f5debcf5a875b79accfcb02d864f97a85', + 'c60d071015bed4da8b9411727e5c35c8429dd4698c92533bea82bf927f1f6966', 'platform': 'linux', 'machine': ['armv6l', 'armv7l', 'armv8l'] @@ -61,11 +61,11 @@ 'file_name': 'traceconv', 'file_size': - 8061520, + 8477496, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/linux-arm64/traceconv', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/linux-arm64/traceconv', 'sha256': - '80e8842b22e9c92b4d44f0e16d6b65b0d013d7c769e853961a0f222287898850', + 'd675922ce9900d4f50105ea8bfcd1edd7fc92ffc56c3e0e7f86bf2af41c5b3db', 'platform': 'linux', 'machine': ['aarch64'] @@ -75,55 +75,55 @@ 'file_name': 'traceconv', 'file_size': - 6320968, + 6680736, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/android-arm/traceconv', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/android-arm/traceconv', 'sha256': - '4d17f00e48f256cc9a725db1e4ba7eddd1fc4ab96e7e67cade07338ac74eab88' + '7148b59e5328ab3d6200d52ceb7dd97fe4d82e873dfabd2da3f2f5be54e8e726' }, { 'arch': 'android-arm64', 'file_name': 'traceconv', 'file_size': - 7998792, + 8424248, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/android-arm64/traceconv', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/android-arm64/traceconv', 'sha256': - '230e91c8bfa59094d69b0dad0fb3328b9c2134a3d34653d6d4fa2a05ed2db4b2' + 'fc7830c47a662a38cee0a11b3c0e9959cffcd4b9b6260e38ef45284371f6e4df' }, { 'arch': 'android-x86', 'file_name': 'traceconv', 'file_size': - 8670248, + 9115416, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/android-x86/traceconv', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/android-x86/traceconv', 'sha256': - 'e59e7a34fd0af9cd0db84ae104bdb79cbf7efb5d71ab11f3b31f6045c1621c5b' + '4f7829506c6bcb249615b87aa40440e45824d4ce378c5f086c317e55241b9b7c' }, { 'arch': 'android-x64', 'file_name': 'traceconv', 'file_size': - 8229048, + 8657048, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/android-x64/traceconv', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/android-x64/traceconv', 'sha256': - '277eab28583824c02159b4b0611d4cb4480305da477f607a90b161c834a21e6b' + '1799a71ad7a743989456ad50b25fdfc01324d4a8f3d9eb97e0799494a3d7ed1c' }, { 'arch': 'windows-amd64', 'file_name': 'traceconv.exe', 'file_size': - 8119296, + 8527872, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/windows-amd64/traceconv.exe', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/windows-amd64/traceconv.exe', 'sha256': - '3fffac0a1d3bea94924e219a8390c96aef729cd0dfa9f216a2c20eb56ece9e86', + '9e7afe063caefe8ae5164014d0180933c66688f0af6618ed3ac1179454d2c23c', 'platform': 'win32', 'machine': ['amd64'] diff --git a/python/perfetto/prebuilts/perfetto_prebuilts.py b/python/perfetto/prebuilts/perfetto_prebuilts.py index 61283cc40b..0e9683e76c 100644 --- a/python/perfetto/prebuilts/perfetto_prebuilts.py +++ b/python/perfetto/prebuilts/perfetto_prebuilts.py @@ -44,11 +44,9 @@ import hashlib import os import platform +import random import subprocess import sys -import threading - -DOWNLOAD_LOCK = threading.Lock() def download_or_get_cached(file_name, url, sha256): @@ -65,36 +63,30 @@ def download_or_get_cached(file_name, url, sha256): sha256_path = os.path.join(dir, file_name + '.sha256') needs_download = True - 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() + # 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: # The file doesn't exist or the SHA256 doesn't match. + # Use a unique random file to guard against concurrent executions. + # See https://github.com/google/perfetto/issues/786 . + tmp_path = '%s.%d.tmp' % (bin_path, random.randint(0, 100000)) + 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(tmp_path, 'w') as f: + f.write(sha256) + os.replace(tmp_path, sha256_path) return bin_path diff --git a/python/perfetto/trace_processor/api.py b/python/perfetto/trace_processor/api.py index 2cf3f032d8..ebb5640e72 100644 --- a/python/perfetto/trace_processor/api.py +++ b/python/perfetto/trace_processor/api.py @@ -16,6 +16,8 @@ from urllib.parse import urlparse from typing import List, Optional +from perfetto.common.exceptions import PerfettoException +from perfetto.common.query_result_iterator import QueryResultIterator from perfetto.trace_processor.http import TraceProcessorHttp from perfetto.trace_processor.platform import PlatformDelegate from perfetto.trace_processor.protos import ProtoFactory @@ -34,11 +36,7 @@ # Custom exception raised if any trace_processor functions return a # response with an error defined -class TraceProcessorException(Exception): - - def __init__(self, message): - super().__init__(message) - +TraceProcessorException = PerfettoException @dc.dataclass class TraceProcessorConfig: @@ -65,151 +63,8 @@ def __init__(self, class TraceProcessor: - - # Values of these constants correspond to the QueryResponse message at - # protos/perfetto/trace_processor/trace_processor.proto - QUERY_CELL_INVALID_FIELD_ID = 0 - QUERY_CELL_NULL_FIELD_ID = 1 - QUERY_CELL_VARINT_FIELD_ID = 2 - QUERY_CELL_FLOAT64_FIELD_ID = 3 - QUERY_CELL_STRING_FIELD_ID = 4 - QUERY_CELL_BLOB_FIELD_ID = 5 - - # This is the class returned to the user and contains one row of the - # resultant query. Each column name is stored as an attribute of this - # class, with the value corresponding to the column name and row in - # the query results table. - class Row(object): - # Required for pytype to correctly infer attributes from Row objects - _HAS_DYNAMIC_ATTRIBUTES = True - - def __str__(self): - return str(self.__dict__) - - def __repr__(self): - return self.__dict__ - - class QueryResultIterator: - - def __init__(self, column_names, batches): - self.__column_names = list(column_names) - self.__column_count = 0 - self.__count = 0 - self.__cells = [] - self.__data_lists = [[], [], [], [], [], []] - self.__data_lists_index = [0, 0, 0, 0, 0, 0] - self.__current_index = 0 - - # Iterate over all the batches and collect their - # contents into lists based on the type of the batch - batch_index = 0 - while True: - # It's possible on some occasions that there are non UTF-8 characters - # in the string_cells field. If this is the case, string_cells is - # a bytestring which needs to be decoded (but passing ignore so that - # we don't fail in decoding). - strings_batch_str = batches[batch_index].string_cells - try: - strings_batch_str = strings_batch_str.decode('utf-8', 'ignore') - except AttributeError: - # AttributeError can occur when |strings_batch_str| is an str which - # happens when everything in it is UTF-8 (protobuf automatically - # does the conversion if it can). - pass - - # Null-terminated strings in a batch are concatenated - # into a single large byte array, so we split on the - # null-terminator to get the individual strings - strings_batch = strings_batch_str.split('\0')[:-1] - self.__data_lists[TraceProcessor.QUERY_CELL_STRING_FIELD_ID].extend( - strings_batch) - self.__data_lists[TraceProcessor.QUERY_CELL_VARINT_FIELD_ID].extend( - batches[batch_index].varint_cells) - self.__data_lists[TraceProcessor.QUERY_CELL_FLOAT64_FIELD_ID].extend( - batches[batch_index].float64_cells) - self.__data_lists[TraceProcessor.QUERY_CELL_BLOB_FIELD_ID].extend( - batches[batch_index].blob_cells) - self.__cells.extend(batches[batch_index].cells) - - if batches[batch_index].is_last_batch: - break - batch_index += 1 - - # If there are no rows in the query result, don't bother updating the - # counts to avoid dealing with / 0 errors. - if len(self.__cells) == 0: - return - - # The count we collected so far was a count of all individual columns - # in the query result, so we divide by the number of columns in a row - # to get the number of rows - self.__column_count = len(self.__column_names) - self.__count = int(len(self.__cells) / self.__column_count) - - # Data integrity check - see that we have the expected amount of cells - # for the number of rows that we need to return - if len(self.__cells) % self.__column_count != 0: - raise TraceProcessorException("Cell count " + str(len(self.__cells)) + - " is not a multiple of column count " + - str(len(self.__column_names))) - - # To use the query result as a populated Pandas dataframe, this - # function must be called directly after calling query inside - # TraceProcesor. - def as_pandas_dataframe(self): - try: - import pandas as pd - - # Populate the dataframe with the query results - rows = [] - for i in range(0, self.__count): - row = [] - base_cell_index = i * self.__column_count - for num in range(len(self.__column_names)): - col_type = self.__cells[base_cell_index + num] - if col_type == TraceProcessor.QUERY_CELL_INVALID_FIELD_ID: - raise TraceProcessorException('Invalid cell type') - - if col_type == TraceProcessor.QUERY_CELL_NULL_FIELD_ID: - row.append(None) - else: - col_index = self.__data_lists_index[col_type] - self.__data_lists_index[col_type] += 1 - row.append(self.__data_lists[col_type][col_index]) - rows.append(row) - - df = pd.DataFrame(rows, columns=self.__column_names) - return df.astype(object).where(df.notnull(), - None).reset_index(drop=True) - - except ModuleNotFoundError: - raise TraceProcessorException( - 'Python dependencies missing. Please pip3 install pandas numpy') - - def __len__(self): - return self.__count - - def __iter__(self): - return self - - def __next__(self): - if self.__current_index == self.__count: - raise StopIteration - result = TraceProcessor.Row() - base_cell_index = self.__current_index * self.__column_count - for num, column_name in enumerate(self.__column_names): - col_type = self.__cells[base_cell_index + num] - if col_type == TraceProcessor.QUERY_CELL_INVALID_FIELD_ID: - raise TraceProcessorException('Invalid cell type') - if col_type != TraceProcessor.QUERY_CELL_NULL_FIELD_ID: - col_index = self.__data_lists_index[col_type] - self.__data_lists_index[col_type] += 1 - setattr(result, column_name, self.__data_lists[col_type][col_index]) - else: - setattr(result, column_name, None) - - self.__current_index += 1 - return result + QueryResultIterator = QueryResultIterator + Row = QueryResultIterator.Row def __init__(self, trace: Optional[TraceReference] = None, diff --git a/python/perfetto/trace_processor/metrics.descriptor b/python/perfetto/trace_processor/metrics.descriptor index ef138160e4..ecba0deb17 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 @@ -46,7 +46,11 @@ $full_trace_process_start_aggregation ( 2:.perfetto.protos.AndroidBootMetric. :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 +post_boot_oom_adj_duration_agg ( 2<.perfetto.protos.AndroidBootMetric.OomAdjDurationAggregationRpostBootOomAdjDurationAgg˜ ++post_boot_broadcast_process_count_by_intent ( 2<.perfetto.protos.AndroidBootMetric.BroadcastCountAggregationR%postBootBroadcastProcessCountByIntent‹ +$post_boot_broadcast_count_by_process ( 2<.perfetto.protos.AndroidBootMetric.BroadcastCountAggregationRpostBootBroadcastCountByProcess— +)post_boot_brodcast_duration_agg_by_intent ( 2?.perfetto.protos.AndroidBootMetric.BroadcastDurationAggregationR#postBootBrodcastDurationAggByIntent™ +*post_boot_brodcast_duration_agg_by_process ( 2?.perfetto.protos.AndroidBootMetric.BroadcastDurationAggregationR$postBootBrodcastDurationAggByProcess9 LauncherBreakdown$ cold_start_dur (R coldStartDur™ ProcessStartAggregation& @@ -83,7 +87,15 @@ destBucket max_oom_adj_dur (R maxOomAdjDur% avg_oom_adj_dur (R avgOomAdjDur- oom_adj_event_count (RoomAdjEventCount$ -oom_adj_reason ( R oomAdjReason +oom_adj_reason ( R oomAdjReasonE +BroadcastCountAggregation +name ( Rname +count (Rcount› +BroadcastDurationAggregation +name ( Rname! + avg_duration (R avgDuration! + max_duration (R maxDuration! + sum_duration (R sumDuration Ž ?protos/perfetto/metrics/android/app_process_starts_metric.protoperfetto.protos"¹ AndroidAppProcessStartsMetricV @@ -182,20 +194,19 @@ Rprotos/perfetto/metrics/android/sysui_update_notif_on_ui_mode_changed_metric.pr name ( Rname dur_ms (RdurMs dur_ns (RdurNs -Ò -6protos/perfetto/metrics/android/process_metadata.protoperfetto.protos"† +ý +6protos/perfetto/metrics/android/process_metadata.protoperfetto.protos"± AndroidProcessMetadata name ( Rname uid (RuidI -package ( 2/.perfetto.protos.AndroidProcessMetadata.PackageRpackageY -packages_for_uid ( 2/.perfetto.protos.AndroidProcessMetadata.PackageRpackagesForUid +package ( 2/.perfetto.protos.AndroidProcessMetadata.PackageRpackage pid (Rpidv Package! package_name ( R packageName( apk_version_code (RapkVersionCode debuggable (R -debuggableJJJJ +debuggableJJJJJ Ù Cprotos/perfetto/metrics/android/android_frame_timeline_metric.protoperfetto.protos6protos/perfetto/metrics/android/process_metadata.proto"È AndroidFrameTimelineMetric! @@ -271,8 +282,8 @@ current_ua (R currentUa$ timestamp_ns (R timestampNs duration_ns (R durationNs -È -;protos/perfetto/metrics/android/android_blocking_call.protoperfetto.protos"÷ +„ +;protos/perfetto/metrics/android/android_blocking_call.protoperfetto.protos"³ AndroidBlockingCall name ( Rname cnt (Rcnt @@ -287,7 +298,12 @@ totalDurNs max_dur_ns (RmaxDurNs -min_dur_ns (RminDurNs +min_dur_ns (RminDurNs + +avg_dur_ms (RavgDurMs + +avg_dur_ns + (RavgDurNs ¤ Wprotos/perfetto/metrics/android/android_sysui_notifications_blocking_calls_metric.protoperfetto.protos;protos/perfetto/metrics/android/android_blocking_call.proto"{ ,AndroidSysUINotificationsBlockingCallsMetricK @@ -402,13 +418,19 @@ totalDurMs avg_dur_ms (RavgDurMsM UpdatePowerState3 avg_runtime_micro_secs ( RavgRuntimeMicroSecsJ -ˆ -5protos/perfetto/metrics/android/dma_heap_metric.protoperfetto.protos"½ +á +5protos/perfetto/metrics/android/dma_heap_metric.protoperfetto.protos"– AndroidDmaHeapMetric$ avg_size_bytes (R avgSizeBytes$ min_size_bytes (R minSizeBytes$ max_size_bytes (R maxSizeBytes3 -total_alloc_size_bytes (RtotalAllocSizeBytes +total_alloc_size_bytes (RtotalAllocSizeBytes* +total_delta_bytes (RtotalDeltaBytesW + process_stats ( 22.perfetto.protos.AndroidDmaHeapMetric.ProcessStatsR processStatsR + ProcessStats! + process_name ( R processName + delta_bytes (R +deltaBytes ¥ 1protos/perfetto/metrics/android/dvfs_metric.protoperfetto.protos"Þ AndroidDvfsMetric` @@ -715,12 +737,13 @@ type_count ( 2,.perfetto.protos.JavaHeapHistogram.TypeCountR typeCount upid ( RupidA process ( 2'.perfetto.protos.AndroidProcessMetadataRprocessC samples ( 2).perfetto.protos.JavaHeapHistogram.SampleRsamples -ã -;protos/perfetto/metrics/android/java_heap_class_stats.protoperfetto.protos6protos/perfetto/metrics/android/process_metadata.proto"Ú +˜ +;protos/perfetto/metrics/android/java_heap_class_stats.protoperfetto.protos6protos/perfetto/metrics/android/process_metadata.proto" JavaHeapClassStatsX -instance_stats ( 21.perfetto.protos.JavaHeapClassStats.InstanceStatsR instanceStatsÒ +instance_stats ( 21.perfetto.protos.JavaHeapClassStats.InstanceStatsR instanceStats‡ TypeCount - type_name ( RtypeName + type_name ( RtypeName- +is_libcore_or_array (RisLibcoreOrArray obj_count (RobjCount size_bytes (R sizeBytes* @@ -731,7 +754,7 @@ size_bytes (R sizeBytes* dominated_obj_count (RdominatedObjCount0 dominated_size_bytes (RdominatedSizeBytes= dominated_native_size_bytes - (RdominatedNativeSizeBytesf + (RdominatedNativeSizeBytesJ  f Sample ts (RtsL @@ -992,8 +1015,8 @@ maxRuntime$ name ( RnameY threads ( 2?.perfetto.protos.AndroidSimpleperfMetric.PerfEventMetric.ThreadRthreads total (Rtotal -‰B -4protos/perfetto/metrics/android/startup_metric.protoperfetto.protos6protos/perfetto/metrics/android/process_metadata.proto"‡A +þD +4protos/perfetto/metrics/android/startup_metric.protoperfetto.protos6protos/perfetto/metrics/android/process_metadata.proto"üC AndroidStartupMetricG startup ( 2-.perfetto.protos.AndroidStartupMetric.StartupRstartupó TaskStateBreakdown$ @@ -1084,10 +1107,12 @@ firstFrame dex2oat_dur_ns (R dex2oatDurNsK SlowStartReasonDetailed reason ( Rreason -details ( RdetailsÆ +details ( Rdetailsô SlowStartReason[ reason_id (2>.perfetto.protos.AndroidStartupMetric.SlowStartReason.ReasonIdRreasonId -reason ( Rreason[ +reason ( Rreason_ +severity + (2C.perfetto.protos.AndroidStartupMetric.SlowStartReason.SeverityLevelRseverity[ expected_value ( 24.perfetto.protos.AndroidStartupMetric.ThresholdValueR expectedValueT actual_value ( 21.perfetto.protos.AndroidStartupMetric.ActualValueR actualValue @@ -1122,29 +1147,39 @@ launch_dur (R launchDur BROADCAST_DISPATCHED_COUNT BROADCAST_RECEIVED_COUNT STARTUP_RUNNING_CONCURRENT+ -'MAIN_THREAD_BINDER_TRANSCATIONS_BLOCKEDƒ +'MAIN_THREAD_BINDER_TRANSCATIONS_BLOCKED"K + SeverityLevel +SEVERITY_UNSPECIFIED +ERROR +WARNING +INFOŽ ThresholdValue value (RvalueV unit (2B.perfetto.protos.AndroidStartupMetric.ThresholdValue.ThresholdUnitRunit' -higher_expected (RhigherExpected"Z +higher_expected (RhigherExpected"e ThresholdUnit THRESHOLD_UNIT_UNSPECIFIED NS PERCENTAGE - TRUE_OR_FALSE5 + TRUE_OR_FALSE +COUNT5 ActualValue value (Rvalue -dur (Rdur| +dur (Rdur› TraceSliceSection' start_timestamp (RstartTimestamp# end_timestamp (R endTimestamp -slice_id ( RsliceIdƒ +slice_id ( RsliceId + +slice_name ( R sliceName¤ TraceThreadSection' start_timestamp (RstartTimestamp# end_timestamp (R endTimestamp thread_utid ( R -threadUtidô +threadUtid + thread_name ( R +threadNameï Startup startup_id ( R startupId! @@ -1171,7 +1206,8 @@ dlopenFile? startup_concurrent_to_launch ( RstartupConcurrentToLaunchT system_state ( 21.perfetto.protos.AndroidStartupMetric.SystemStateR systemState* slow_start_reason ( RslowStartReasonz -slow_start_reason_detailed ( 2=.perfetto.protos.AndroidStartupMetric.SlowStartReasonDetailedRslowStartReasonDetailedJ +slow_start_reason_detailed ( 2=.perfetto.protos.AndroidStartupMetric.SlowStartReasonDetailedRslowStartReasonDetailedy +slow_start_reason_with_details ( 25.perfetto.protos.AndroidStartupMetric.SlowStartReasonRslowStartReasonWithDetailsJ  Ñ 4protos/perfetto/metrics/android/surfaceflinger.protoperfetto.protos"‡ @@ -1357,8 +1393,39 @@ destBucket avg_oom_adj_dur (R avgOomAdjDur- oom_adj_event_count (RoomAdjEventCount$ oom_adj_reason ( R oomAdjReason -‡J -%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.proto1protos/perfetto/metrics/android/auto_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.proto;protos/perfetto/metrics/android/java_heap_class_stats.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"î +í +?protos/perfetto/metrics/android/android_broadcasts_metric.protoperfetto.protos"˜ +AndroidBroadcastsMetricy +process_count_by_intent ( 2B.perfetto.protos.AndroidBroadcastsMetric.BroadcastCountAggregationRprocessCountByIntent +broadcast_count_by_process ( 2B.perfetto.protos.AndroidBroadcastsMetric.BroadcastCountAggregationRbroadcastCountByProcess‹ +brodcast_duration_agg_by_intent ( 2E.perfetto.protos.AndroidBroadcastsMetric.BroadcastDurationAggregationRbrodcastDurationAggByIntent + brodcast_duration_agg_by_process ( 2E.perfetto.protos.AndroidBroadcastsMetric.BroadcastDurationAggregationRbrodcastDurationAggByProcessE +BroadcastCountAggregation +name ( Rname +count (Rcount› +BroadcastDurationAggregation +name ( Rname! + avg_duration (R avgDuration! + max_duration (R maxDuration! + sum_duration (R sumDuration +• +9protos/perfetto/metrics/android/wattson_app_startup.protoperfetto.protos"• +AndroidWattsonTimePeriodMetric% +metric_version (R metricVersionL + period_info ( 2+.perfetto.protos.AndroidWattsonEstimateInfoR +periodInfo"™ +AndroidWattsonEstimateInfo + period_id (RperiodId + +period_dur (R periodDur? +rail ( 2+.perfetto.protos.AndroidWattsonRailEstimateRrail"’ +AndroidWattsonRailEstimate +name ( Rname + estimate_mw (R +estimateMw? +rail ( 2+.perfetto.protos.AndroidWattsonRailEstimateRrail +½L +%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.proto1protos/perfetto/metrics/android/auto_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.proto;protos/perfetto/metrics/android/java_heap_class_stats.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?protos/perfetto/metrics/android/android_broadcasts_metric.proto9protos/perfetto/metrics/android/wattson_app_startup.proto"î TraceMetadata* trace_duration_ns (RtraceDurationNs @@ -1388,7 +1455,7 @@ trace_uuid ( R traceUuid: Source SOURCE_UNKNOWN SOURCE_TRACE -SOURCE_ANALYSIS"ð( +SOURCE_ANALYSIS"ª* TraceMetricsH android_batt ( 2%.perfetto.protos.AndroidBatteryMetricR androidBattB android_cpu ( 2!.perfetto.protos.AndroidCpuMetricR @@ -1455,5 +1522,7 @@ androidAnrw android_garbage_collection_unagg? ( 24.perfetto.protos.AndroidGarbageCollectionUnaggMetricRandroidGarbageCollectionUnagga android_auto_multiuser@ ( 2+.perfetto.protos.AndroidAutoMultiuserMetricRandroidAutoMultiuserk android_blocking_calls_unaggA ( 2*.perfetto.protos.AndroidBlockingCallsUnaggRandroidBlockingCallsUnagg[ -android_oom_adjusterB ( 2).perfetto.protos.AndroidOomAdjusterMetricRandroidOomAdjuster*Âô*ôé*éÑ*ÑÅJJ +android_oom_adjusterB ( 2).perfetto.protos.AndroidOomAdjusterMetricRandroidOomAdjusterW +android_broadcastsD ( 2(.perfetto.protos.AndroidBroadcastsMetricRandroidBroadcasts_ +wattson_app_startupE ( 2/.perfetto.protos.AndroidWattsonTimePeriodMetricRwattsonAppStartup*Âô*ôé*éÑ*ÑÅJJ  J JJJJJJ \ No newline at end of file diff --git a/python/run_tests.py b/python/run_tests.py index b0a03c9e75..5a53a24f8c 100755 --- a/python/run_tests.py +++ b/python/run_tests.py @@ -18,8 +18,9 @@ import sys import unittest -from test import api_unittest from test import api_integrationtest +from test import bigtrace_api_integrationtest +from test import query_result_iterator_unittest from test import resolver_unittest from test import stdlib_unittest @@ -38,18 +39,23 @@ def main(): # Set paths to trace_processor_shell and root directory as environment # variables parser = argparse.ArgumentParser() - parser.add_argument("shell", type=str) - os.environ["SHELL_PATH"] = parser.parse_args().shell + parser.add_argument("host_out_path", type=str) + host_out_path = parser.parse_args().host_out_path + os.environ["SHELL_PATH"] = f"{host_out_path}/trace_processor_shell" + os.environ["ORCHESTRATOR_PATH"] = f"{host_out_path}/orchestrator_main" + os.environ["WORKER_PATH"] = f"{host_out_path}/worker_main" os.environ["ROOT_DIR"] = ROOT_DIR loader = unittest.TestLoader() suite = unittest.TestSuite() # Add all relevant tests to test suite - suite.addTests(loader.loadTestsFromModule(api_unittest)) + suite.addTests(loader.loadTestsFromModule(query_result_iterator_unittest)) suite.addTests(loader.loadTestsFromModule(resolver_unittest)) suite.addTests(loader.loadTestsFromModule(api_integrationtest)) suite.addTests(loader.loadTestsFromModule(stdlib_unittest)) + if os.path.exists(os.environ["WORKER_PATH"]): + suite.addTests(loader.loadTestsFromModule(bigtrace_api_integrationtest)) # Initialise runner to run all tests in suite runner = unittest.TextTestRunner(verbosity=3) diff --git a/python/test/bigtrace_api_integrationtest.py b/python/test/bigtrace_api_integrationtest.py new file mode 100644 index 0000000000..73bd852ce1 --- /dev/null +++ b/python/test/bigtrace_api_integrationtest.py @@ -0,0 +1,84 @@ +# 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 os +import perfetto.bigtrace.api +import subprocess +import unittest + +from perfetto.common.exceptions import PerfettoException + +class BigtraceTest(unittest.TestCase): + + @classmethod + def setUpClass(self): + self.root_dir = os.environ["ROOT_DIR"] + self.worker_1 = subprocess.Popen( + [os.environ["WORKER_PATH"], "-s", "127.0.0.1:5052"]) + self.worker_2 = subprocess.Popen( + [os.environ["WORKER_PATH"], "-s", "127.0.0.1:5053"]) + self.worker_3 = subprocess.Popen( + [os.environ["WORKER_PATH"], "-s", "127.0.0.1:5054"]) + self.orchestrator = subprocess.Popen([ + os.environ["ORCHESTRATOR_PATH"], "-n", "3", "-w", "127.0.0.1", "-p", + "5052" + ]) + self.client = perfetto.bigtrace.api.Bigtrace( + wait_for_ready_for_testing=True) + + @classmethod + def tearDownClass(self): + self.worker_1.kill() + self.worker_1.wait() + self.worker_2.kill() + self.worker_2.wait() + self.worker_3.kill() + self.worker_3.wait() + self.orchestrator.kill() + self.orchestrator.wait() + del self.client + + def test_valid_traces(self): + result = self.client.query([ + f"{self.root_dir}/test/data/api24_startup_cold.perfetto-trace", + f"{self.root_dir}/test/data/api24_startup_hot.perfetto-trace" + ], "SELECT count(1) as count FROM slice LIMIT 5") + + self.assertEqual( + result.loc[ + result['_trace_address'] == + f"{self.root_dir}/test/data/api24_startup_cold.perfetto-trace", + 'count'].iloc[0], 9726) + self.assertEqual( + result.loc[ + result['_trace_address'] == + f"{self.root_dir}/test/data/api24_startup_hot.perfetto-trace", + 'count'].iloc[0], 5726) + + def test_empty_traces(self): + with self.assertRaises(PerfettoException): + result = self.client.query([], "SELECT count(1) FROM slice LIMIT 5") + + def test_empty_sql_string(self): + with self.assertRaises(PerfettoException): + result = self.client.query([ + f"{self.root_dir}/test/data/api24_startup_cold.perfetto-trace", + f"{self.root_dir}/test/data/api24_startup_hot.perfetto-trace" + ], "") + + def test_message_limit_exceeded(self): + with self.assertRaises(PerfettoException): + result = self.client.query( + [f"{self.root_dir}/test/data/long_task_tracking_trace"], + "SELECT * FROM slice") diff --git a/python/test/api_unittest.py b/python/test/query_result_iterator_unittest.py similarity index 85% rename from python/test/api_unittest.py rename to python/test/query_result_iterator_unittest.py index 23e4a233a0..3971ca4291 100755 --- a/python/test/api_unittest.py +++ b/python/test/query_result_iterator_unittest.py @@ -14,14 +14,13 @@ import unittest -from perfetto.trace_processor.api import TraceProcessor -from perfetto.trace_processor.api import TraceProcessorException +from perfetto.common.exceptions import PerfettoException +from perfetto.common.query_result_iterator import QueryResultIterator from perfetto.trace_processor.api import PLATFORM_DELEGATE from perfetto.trace_processor.protos import ProtoFactory PROTO_FACTORY = ProtoFactory(PLATFORM_DELEGATE()) - class TestQueryResultIterator(unittest.TestCase): # The numbers input into cells correspond the CellType enum values # defined under trace_processor.proto @@ -47,8 +46,8 @@ def test_one_batch(self): batch.string_cells = "\0".join(str_values) + "\0" batch.is_last_batch = True - qr_iterator = TraceProcessor.QueryResultIterator( - ['foo_id', 'foo_num', 'foo_null'], [batch]) + qr_iterator = QueryResultIterator(['foo_id', 'foo_num', 'foo_null'], + [batch]) for num, row in enumerate(qr_iterator): self.assertEqual(row.foo_id, str_values[num]) @@ -85,8 +84,8 @@ def test_many_batches(self): batch_2.string_cells = "\0".join(str_values[2:]) + "\0" batch_2.is_last_batch = True - qr_iterator = TraceProcessor.QueryResultIterator( - ['foo_id', 'foo_num', 'foo_null'], [batch_1, batch_2]) + qr_iterator = QueryResultIterator(['foo_id', 'foo_num', 'foo_null'], + [batch_1, batch_2]) for num, row in enumerate(qr_iterator): self.assertEqual(row.foo_id, str_values[num]) @@ -97,7 +96,7 @@ def test_empty_batch(self): batch = PROTO_FACTORY.CellsBatch() batch.is_last_batch = True - qr_iterator = TraceProcessor.QueryResultIterator([], [batch]) + qr_iterator = QueryResultIterator([], [batch]) for num, row in enumerate(qr_iterator): self.assertIsNone(row.foo_id) @@ -109,7 +108,7 @@ def test_invalid_batch(self): # Since the batch isn't defined as the last batch, the QueryResultsIterator # expects another batch and thus raises IndexError as no next batch exists. with self.assertRaises(IndexError): - qr_iterator = TraceProcessor.QueryResultIterator([], [batch]) + qr_iterator = QueryResultIterator([], [batch]) def test_null_cells(self): int_values = [100, 200, 300, 500, 600] @@ -131,8 +130,8 @@ def test_null_cells(self): batch.string_cells = "\0".join(str_values) + "\0" batch.is_last_batch = True - qr_iterator = TraceProcessor.QueryResultIterator( - ['foo_id', 'foo_num', 'foo_num_2'], [batch]) + qr_iterator = QueryResultIterator(['foo_id', 'foo_num', 'foo_num_2'], + [batch]) # Any cell (and thus column in a row) can be set to null # In this query result, foo_num_2 of row 2 was set to null @@ -155,8 +154,7 @@ def test_incorrect_cells_batch(self): batch.string_cells = "\0".join(str_values) + "\0" batch.is_last_batch = True - qr_iterator = TraceProcessor.QueryResultIterator(['foo_id', 'foo_num'], - [batch]) + qr_iterator = QueryResultIterator(['foo_id', 'foo_num'], [batch]) # The batch specifies there ought to be 2 cells of type VARINT and 2 cells # of type STRING, but there are no string cells defined in the batch. Thus @@ -175,9 +173,10 @@ def test_incorrect_columns_batch(self): # It's always the case that the number of cells is a multiple of the number # of columns. However, here this is clearly not the case, so raise a - # TraceProcessorException during the data integrity check in the constructor - with self.assertRaises(TraceProcessorException): - qr_iterator = TraceProcessor.QueryResultIterator( + # PerfettoException during the data integrity check in + # the constructor + with self.assertRaises(PerfettoException): + qr_iterator = QueryResultIterator( ['foo_id', 'foo_num', 'foo_dur', 'foo_ms'], [batch]) def test_invalid_cell_type(self): @@ -189,13 +188,12 @@ def test_invalid_cell_type(self): batch.varint_cells.extend([100, 200]) batch.is_last_batch = True - qr_iterator = TraceProcessor.QueryResultIterator(['foo_id', 'foo_num'], - [batch]) + qr_iterator = QueryResultIterator(['foo_id', 'foo_num'], [batch]) # In this batch we declare the columns types to be CELL_INVALID, # CELL_VARINT but that doesn't match the data which are both ints* - # so we should raise a TraceProcessorException. - with self.assertRaises(TraceProcessorException): + # so we should raise a PerfettoException. + with self.assertRaises(PerfettoException): for row in qr_iterator: pass @@ -216,8 +214,8 @@ def test_one_batch_as_pandas(self): batch.string_cells = "\0".join(str_values) + "\0" batch.is_last_batch = True - qr_iterator = TraceProcessor.QueryResultIterator( - ['foo_id', 'foo_num', 'foo_null'], [batch]) + qr_iterator = QueryResultIterator(['foo_id', 'foo_num', 'foo_null'], + [batch]) qr_df = qr_iterator.as_pandas_dataframe() for num, row in qr_df.iterrows(): @@ -255,8 +253,8 @@ def test_many_batches_as_pandas(self): batch_2.string_cells = "\0".join(str_values[2:]) + "\0" batch_2.is_last_batch = True - qr_iterator = TraceProcessor.QueryResultIterator( - ['foo_id', 'foo_num', 'foo_null'], [batch_1, batch_2]) + qr_iterator = QueryResultIterator(['foo_id', 'foo_num', 'foo_null'], + [batch_1, batch_2]) qr_df = qr_iterator.as_pandas_dataframe() for num, row in qr_df.iterrows(): @@ -268,7 +266,7 @@ def test_empty_batch_as_pandas(self): batch = PROTO_FACTORY.CellsBatch() batch.is_last_batch = True - qr_iterator = TraceProcessor.QueryResultIterator([], [batch]) + qr_iterator = QueryResultIterator([], [batch]) qr_df = qr_iterator.as_pandas_dataframe() for num, row in qr_df.iterrows(): @@ -295,8 +293,8 @@ def test_null_cells_as_pandas(self): batch.string_cells = "\0".join(str_values) + "\0" batch.is_last_batch = True - qr_iterator = TraceProcessor.QueryResultIterator( - ['foo_id', 'foo_num', 'foo_num_2'], [batch]) + qr_iterator = QueryResultIterator(['foo_id', 'foo_num', 'foo_num_2'], + [batch]) qr_df = qr_iterator.as_pandas_dataframe() # Any cell (and thus column in a row) can be set to null @@ -320,8 +318,7 @@ def test_incorrect_cells_batch_as_pandas(self): batch.string_cells = "\0".join(str_values) + "\0" batch.is_last_batch = True - qr_iterator = TraceProcessor.QueryResultIterator(['foo_id', 'foo_num'], - [batch]) + qr_iterator = QueryResultIterator(['foo_id', 'foo_num'], [batch]) # The batch specifies there ought to be 2 cells of type VARINT and 2 cells # of type STRING, but there are no string cells defined in the batch. Thus @@ -338,11 +335,10 @@ def test_invalid_cell_type_as_pandas(self): batch.varint_cells.extend([100, 200]) batch.is_last_batch = True - qr_iterator = TraceProcessor.QueryResultIterator(['foo_id', 'foo_num'], - [batch]) + qr_iterator = QueryResultIterator(['foo_id', 'foo_num'], [batch]) # In this batch we declare the columns types to be CELL_INVALID, # CELL_VARINT but that doesn't match the data which are both ints* - # so we should raise a TraceProcessorException. - with self.assertRaises(TraceProcessorException): + # so we should raise a PerfettoException. + with self.assertRaises(PerfettoException): _ = qr_iterator.as_pandas_dataframe() diff --git a/python/test/stdlib_unittest.py b/python/test/stdlib_unittest.py index 6002e8341f..34725cc614 100644 --- a/python/test/stdlib_unittest.py +++ b/python/test/stdlib_unittest.py @@ -292,7 +292,8 @@ def test_multiline_desc(self): self.assertListEqual(res.errors, []) fn = res.functions[0] - self.assertEqual(fn.desc, 'This\n is\n\n a\n very\n\n long\n\n description.') + self.assertEqual(fn.desc, + 'This\n is\n\n a\n very\n\n long\n\n description.') def test_multiline_arg_desc(self): res = parse_file( @@ -588,7 +589,6 @@ def test_create_or_replace_macro_smoke(self): self.assertEqual(macro.return_type, 'TableOrSubquery') self.assertEqual(macro.return_desc, 'Exists.') - def test_create_or_replace_macro_banned(self): res = parse_file( 'foo/bar.sql', f''' diff --git a/python/tools/check_imports.py b/python/tools/check_imports.py index a114982383..7f732a823b 100755 --- a/python/tools/check_imports.py +++ b/python/tools/check_imports.py @@ -227,6 +227,21 @@ def __str__(self): r'/core/.*', 'instead plugins should depend on the API exposed at ui/src/public.', ), + NoDirectDep( + r"/frontend/.*", + r"/core_plugins/.*", + "core code should not depend on plugins.", + ), + NoDirectDep( + r"/core/.*", + r"/core_plugins/.*", + "core code should not depend on plugins.", + ), + NoDirectDep( + r"/base/.*", + r"/core_plugins/.*", + "core code should not depend on plugins.", + ), #NoDirectDep( # r'/tracks/.*', # r'/core/.*', diff --git a/python/tools/check_ratchet.py b/python/tools/check_ratchet.py index e8240d33d4..77150a4b43 100755 --- a/python/tools/check_ratchet.py +++ b/python/tools/check_ratchet.py @@ -36,7 +36,7 @@ from dataclasses import dataclass -EXPECTED_ANY_COUNT = 73 +EXPECTED_ANY_COUNT = 65 EXPECTED_RUN_METRIC_COUNT = 5 ROOT_DIR = os.path.dirname( diff --git a/python/tools/heap_profile.py b/python/tools/heap_profile.py index 463f1b120e..f81802f6cd 100644 --- a/python/tools/heap_profile.py +++ b/python/tools/heap_profile.py @@ -156,7 +156,10 @@ def print_options(parser): def main(argv): - parser = argparse.ArgumentParser() + parser = argparse.ArgumentParser(description="""Collect a heap profile + + The PERFETTO_PROGUARD_MAP=packagename=map_filename.txt[:packagename=map_filename.txt...] environment variable can be used to pass proguard deobfuscation maps for different packages""", formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument( "-i", "--interval", diff --git a/python/tools/record_android_trace.py b/python/tools/record_android_trace.py index 7dbca530e6..2d4a1896ec 100755 --- a/python/tools/record_android_trace.py +++ b/python/tools/record_android_trace.py @@ -80,7 +80,7 @@ def do_POST(self): self.send_error(404, "File not found") -def main(): +def setup_arguments(): atexit.register(kill_all_subprocs_on_exit) default_out_dir_str = '~/traces/' default_out_dir = os.path.expanduser(default_out_dir_str) @@ -209,6 +209,10 @@ def main(): parser.print_help() sys.exit(1) + return args + + +def start_trace(args, print_log=True): perfetto_cmd = 'perfetto' device_dir = '/data/misc/perfetto-traces/' @@ -310,7 +314,8 @@ def main(): shutil.os.makedirs(host_dir) with open(on_host_config or os.devnull, 'rb') as f: - print('Running ' + ' '.join(cmd)) + if print_log: + print('Running ' + ' '.join(cmd)) proc = adb('shell', *cmd, stdin=f, stdout=subprocess.PIPE) proc_out = proc.communicate()[0].decode().strip() if on_device_config is not None: @@ -333,8 +338,11 @@ def main(): sys.exit(1) prt('Trace started. Press CTRL+C to stop', ANSI.BLACK + ANSI.BG_BLUE) - logcat = adb('logcat', '-v', 'brief', '-s', 'perfetto', '-b', 'main', '-T', - '1') + log_level = "-v" + if not print_log: + log_level = "-e" + logcat = adb('logcat', log_level, 'brief', '-s', 'perfetto', '-b', 'main', + '-T', '1') ctrl_c_count = 0 adb_failure_count = 0 @@ -368,14 +376,16 @@ def main(): except KeyboardInterrupt: sig = 'TERM' if ctrl_c_count == 0 else 'KILL' ctrl_c_count += 1 - prt('Stopping the trace (SIG%s)' % sig, ANSI.BLACK + ANSI.BG_YELLOW) + if print_log: + prt('Stopping the trace (SIG%s)' % sig, ANSI.BLACK + ANSI.BG_YELLOW) adb('shell', 'kill -%s %s' % (sig, bg_pid)).wait() logcat.kill() logcat.wait() if args.reporter_api: - prt('Waiting a few seconds to allow reporter to copy trace') + if print_log: + prt('Waiting a few seconds to allow reporter to copy trace') time.sleep(5) ret = adb( @@ -386,17 +396,26 @@ def main(): prt('Failed to extract reporter trace', ANSI.RED) sys.exit(1) - prt('\n') - prt('Pulling into %s' % host_file, ANSI.BOLD) + if print_log: + prt('\n') + prt('Pulling into %s' % host_file, ANSI.BOLD) adb('pull', device_file, host_file).wait() adb('shell', 'rm -f ' + device_file).wait() if not args.no_open: - prt('\n') - prt('Opening the trace (%s) in the browser' % host_file) + if print_log: + prt('\n') + prt('Opening the trace (%s) in the browser' % host_file) open_browser = not args.no_open_browser open_trace_in_browser(host_file, open_browser, args.origin) + return host_file + + +def main(): + args = setup_arguments() + start_trace(args) + def prt(msg, colors=ANSI.END): print(colors + msg + ANSI.END) @@ -444,14 +463,15 @@ def open_trace_in_browser(path, open_browser, origin): httpd.handle_request() -def adb(*args, stdin=devnull, stdout=None): +def adb(*args, stdin=devnull, stdout=None, stderr=None): cmd = [adb_path, *args] setpgrp = None if os.name != 'nt': # On Linux/Mac, start a new process group so all child processes are killed # on exit. Unsupported on Windows. setpgrp = lambda: os.setpgrp() - proc = subprocess.Popen(cmd, stdin=stdin, stdout=stdout, preexec_fn=setpgrp) + proc = subprocess.Popen( + cmd, stdin=stdin, stdout=stdout, stderr=stderr, preexec_fn=setpgrp) procs.append(proc) return proc diff --git a/src/android_internal/statsd_logging.cc b/src/android_internal/statsd_logging.cc index a164adf261..09b55a2194 100644 --- a/src/android_internal/statsd_logging.cc +++ b/src/android_internal/statsd_logging.cc @@ -16,12 +16,11 @@ #include "src/android_internal/statsd_logging.h" -#include +#include #include -namespace perfetto { -namespace android_internal { +namespace perfetto::android_internal { void StatsdLogUploadEvent(PerfettoStatsdAtom atom, int64_t uuid_lsb, @@ -35,5 +34,4 @@ void StatsdLogTriggerEvent(PerfettoTriggerAtom atom, const char* trigger_name) { stats_write(PERFETTO_TRIGGER, static_cast(atom), trigger_name); } -} // namespace android_internal -} // namespace perfetto +} // namespace perfetto::android_internal diff --git a/src/android_stats/perfetto_atoms.h b/src/android_stats/perfetto_atoms.h index 1981a770b9..3a3308e230 100644 --- a/src/android_stats/perfetto_atoms.h +++ b/src/android_stats/perfetto_atoms.h @@ -27,6 +27,8 @@ enum class PerfettoStatsdAtom { // Checkpoints inside perfetto_cmd before tracing is finished. kTraceBegin = 1, kBackgroundTraceBegin = 2, + kCloneTraceBegin = 55, + kCloneTriggerTraceBegin = 56, kOnConnect = 3, // Guardrails inside perfetto_cmd before tracing is finished. @@ -70,6 +72,7 @@ enum class PerfettoStatsdAtom { kTracedEnableTracingOobTargetBuffer = 48, kTracedEnableTracingInvalidTriggerMode = 52, kTracedEnableTracingInvalidBrFilename = 54, + kTracedEnableTracingFailedSessionSemaphoreCheck = 57, // Checkpoints inside perfetto_cmd after tracing has finished. kOnTracingDisabled = 4, @@ -105,7 +108,7 @@ enum class PerfettoStatsdAtom { // longer supports uploading traces using Dropbox. // reserved 5, 6, 7; - // Contained status of guardrail state initalization and upload limit in + // Contained status of guardrail state initialization and upload limit in // perfetto_cmd. Removed as perfetto no longer manages stateful guardrails // reserved 44, 45, 46; }; diff --git a/src/android_stats/statsd_logging_helper.cc b/src/android_stats/statsd_logging_helper.cc index c9430277bb..1f5011a17c 100644 --- a/src/android_stats/statsd_logging_helper.cc +++ b/src/android_stats/statsd_logging_helper.cc @@ -16,10 +16,12 @@ #include "src/android_stats/statsd_logging_helper.h" +#include #include +#include #include "perfetto/base/build_config.h" -#include "perfetto/base/compiler.h" +#include "src/android_stats/perfetto_atoms.h" #if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) && \ PERFETTO_BUILDFLAG(PERFETTO_ANDROID_BUILD) @@ -27,8 +29,7 @@ #include "src/android_internal/statsd_logging.h" // nogncheck #endif -namespace perfetto { -namespace android_stats { +namespace perfetto::android_stats { // Make sure we don't accidentally log on non-Android tree build. Note that even // removing this ifdef still doesn't make uploads work on OS_ANDROID. @@ -75,5 +76,4 @@ void MaybeLogTriggerEvents(PerfettoTriggerAtom, const std::vector&) {} #endif -} // namespace android_stats -} // namespace perfetto +} // namespace perfetto::android_stats diff --git a/src/android_stats/statsd_logging_helper.h b/src/android_stats/statsd_logging_helper.h index 5f0911f382..0ba2d65526 100644 --- a/src/android_stats/statsd_logging_helper.h +++ b/src/android_stats/statsd_logging_helper.h @@ -18,6 +18,7 @@ #define SRC_ANDROID_STATS_STATSD_LOGGING_HELPER_H_ #include +#include #include #include diff --git a/src/base/http/http_server.cc b/src/base/http/http_server.cc index bf99e25d0e..d478f22460 100644 --- a/src/base/http/http_server.cc +++ b/src/base/http/http_server.cc @@ -361,8 +361,6 @@ size_t HttpServer::ParseOneWebsocketFrame(HttpServerConnection* conn) { uint8_t h0 = *(rd++); uint8_t h1 = *(rd++); - const bool fin = !!(h0 & 0x80); // This bit is set if this frame is the last - // data to complete this message. const uint8_t opcode = h0 & 0x0F; const bool has_mask = !!(h1 & 0x80); @@ -407,11 +405,6 @@ size_t HttpServer::ParseOneWebsocketFrame(HttpServerConnection* conn) { memcpy(mask, rd, sizeof(mask)); rd += sizeof(mask); - PERFETTO_DLOG( - "[HTTP] Websocket fin=%d opcode=%u, payload_len=%zu (avail=%zu), " - "mask=%02x%02x%02x%02x", - fin, opcode, payload_len, avail(), mask[0], mask[1], mask[2], mask[3]); - if (avail() < payload_len) return 0; // Not enouh data to read the payload. uint8_t* const payload_start = rd; diff --git a/src/base/task_runner_unittest.cc b/src/base/task_runner_unittest.cc index 934b8c0eea..810ebc63a3 100644 --- a/src/base/task_runner_unittest.cc +++ b/src/base/task_runner_unittest.cc @@ -343,6 +343,8 @@ TEST_F(TaskRunnerTest, RunsTasksOnCurrentThread) { }); second_tr.Run(); }); + main_tr.PostTask([&]() { main_tr.Quit(); }); + main_tr.Run(); thread.join(); } diff --git a/src/base/unix_task_runner.cc b/src/base/unix_task_runner.cc index 9fc8f3c7c0..ba2828fa93 100644 --- a/src/base/unix_task_runner.cc +++ b/src/base/unix_task_runner.cc @@ -52,7 +52,7 @@ void UnixTaskRunner::WakeUp() { void UnixTaskRunner::Run() { PERFETTO_DCHECK_THREAD(thread_checker_); - created_thread_id_ = GetThreadId(); + created_thread_id_.store(GetThreadId(), std::memory_order_relaxed); quit_ = false; for (;;) { int poll_timeout_ms; @@ -299,7 +299,7 @@ void UnixTaskRunner::RemoveFileDescriptorWatch(PlatformHandle fd) { } bool UnixTaskRunner::RunsTasksOnCurrentThread() const { - return GetThreadId() == created_thread_id_; + return GetThreadId() == created_thread_id_.load(std::memory_order_relaxed); } } // namespace base diff --git a/src/base/utils.cc b/src/base/utils.cc index ab3f390866..0d9318c136 100644 --- a/src/base/utils.cc +++ b/src/base/utils.cc @@ -38,6 +38,25 @@ #include #endif +#if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \ + PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) +#include +#include + +#ifndef PR_GET_TAGGED_ADDR_CTRL +#define PR_GET_TAGGED_ADDR_CTRL 56 +#endif + +#ifndef PR_TAGGED_ADDR_ENABLE +#define PR_TAGGED_ADDR_ENABLE (1UL << 0) +#endif + +#ifndef PR_MTE_TCF_SYNC +#define PR_MTE_TCF_SYNC (1UL << 1) +#endif + +#endif // OS_LINUX | OS_ANDROID + #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) #include #include @@ -318,6 +337,23 @@ void AlignedFree(void* ptr) { #endif } +bool IsSyncMemoryTaggingEnabled() { +#if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \ + PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) + // Compute only once per lifetime of the process. + static bool cached_value = [] { + const int res = prctl(PR_GET_TAGGED_ADDR_CTRL, 0, 0, 0, 0); + if (res < 0) + return false; + const uint32_t actl = static_cast(res); + return (actl & PR_TAGGED_ADDR_ENABLE) && (actl & PR_MTE_TCF_SYNC); + }(); + return cached_value; +#else + return false; +#endif +} + std::string HexDump(const void* data_void, size_t len, size_t bytes_per_line) { const char* data = reinterpret_cast(data_void); std::string res; diff --git a/src/base/watchdog_posix.cc b/src/base/watchdog_posix.cc index fcbf713ec2..00934f0db5 100644 --- a/src/base/watchdog_posix.cc +++ b/src/base/watchdog_posix.cc @@ -283,7 +283,7 @@ void Watchdog::ThreadMain() { bool threshold_exceeded = false; guard.lock(); - if (CheckMemory_Locked(rss_bytes)) { + if (CheckMemory_Locked(rss_bytes) && !IsSyncMemoryTaggingEnabled()) { threshold_exceeded = true; crash_reason = WatchdogCrashReason::kMemGuardrail; } else if (CheckCpu_Locked(cpu_time)) { diff --git a/src/bigtrace/compose.yaml b/src/bigtrace/compose.yaml new file mode 100644 index 0000000000..03cc2f66eb --- /dev/null +++ b/src/bigtrace/compose.yaml @@ -0,0 +1,31 @@ +# 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. + +services: + worker: + deploy: + mode: replicated + replicas: 3 + build: + context: ./worker + dockerfile: Dockerfile + orchestrator: + container_name: orchestrator + build: + context: ./orchestrator + dockerfile: Dockerfile + ports: + - "5051:5051" + depends_on: + - worker \ No newline at end of file diff --git a/src/bigtrace/orchestrator-deployment.yaml b/src/bigtrace/orchestrator-deployment.yaml new file mode 100644 index 0000000000..ba1db870bd --- /dev/null +++ b/src/bigtrace/orchestrator-deployment.yaml @@ -0,0 +1,35 @@ +# Copyright (C) 2024 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: orchestrator-deployment +spec: + replicas: 1 + selector: + matchLabels: + app: orchestrator-deployment + template: + metadata: + labels: + app: orchestrator-deployment + spec: + containers: + - image: docker.io/library/orchestrator_image:latest + imagePullPolicy: Never + name: orchestrator + ports: + - containerPort: 5051 + protocol: TCP diff --git a/src/bigtrace/orchestrator-service.yaml b/src/bigtrace/orchestrator-service.yaml new file mode 100644 index 0000000000..23009f67cb --- /dev/null +++ b/src/bigtrace/orchestrator-service.yaml @@ -0,0 +1,27 @@ +# 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. + +apiVersion: v1 +kind: Service +metadata: + name: orchestrator +spec: + type: NodePort + ports: + - protocol: TCP + nodePort: 30051 + port: 5051 + targetPort: 5051 + selector: + app: orchestrator-deployment diff --git a/src/bigtrace/orchestrator/BUILD.gn b/src/bigtrace/orchestrator/BUILD.gn new file mode 100644 index 0000000000..8a0aff61fe --- /dev/null +++ b/src/bigtrace/orchestrator/BUILD.gn @@ -0,0 +1,40 @@ +# Copyright (C) 2024 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("../../../gn/perfetto.gni") +import("../../../gn/test.gni") + +assert( + enable_perfetto_trace_processor && enable_perfetto_trace_processor_sqlite) + +if (enable_perfetto_grpc) { + executable("orchestrator_main") { + sources = [ + "orchestrator_impl.cc", + "orchestrator_impl.h", + "orchestrator_main.cc", + ] + deps = [ + "../../../gn:default_deps", + "../../../gn:grpc", + "../../../protos/perfetto/bigtrace:orchestrator_grpc", + "../../../protos/perfetto/bigtrace:orchestrator_lite", + "../../../protos/perfetto/bigtrace:worker_grpc", + "../../../protos/perfetto/bigtrace:worker_lite", + "../../../src/trace_processor/util", + "../../base", + "../../base/threading", + ] + } +} diff --git a/src/bigtrace/orchestrator/Dockerfile b/src/bigtrace/orchestrator/Dockerfile new file mode 100644 index 0000000000..2ca09051aa --- /dev/null +++ b/src/bigtrace/orchestrator/Dockerfile @@ -0,0 +1,29 @@ +# 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. + +FROM debian:12 + +RUN apt update && apt install -y git python3 curl build-essential +RUN git clone --depth 1 https://android.googlesource.com/platform/external/perfetto/ +WORKDIR /perfetto +RUN tools/install-build-deps --grpc +RUN tools/gn gen out/dist '--args=is_clang=true enable_perfetto_grpc=true' +RUN tools/ninja -C out/dist orchestrator_main + +CMD [ \ +"/perfetto/out/dist/orchestrator_main", \ +"-l", "worker:5052", \ +"-r", "dns:///", \ +"-s", "0.0.0.0:5051" \ +] \ No newline at end of file diff --git a/src/bigtrace/orchestrator/orchestrator_impl.cc b/src/bigtrace/orchestrator/orchestrator_impl.cc new file mode 100644 index 0000000000..076020be48 --- /dev/null +++ b/src/bigtrace/orchestrator/orchestrator_impl.cc @@ -0,0 +1,88 @@ +/* + * 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/bigtrace/orchestrator/orchestrator_impl.h" +#include "perfetto/base/logging.h" +#include "perfetto/ext/base/threading/thread_pool.h" +#include "perfetto/ext/base/waitable_event.h" + +namespace perfetto::bigtrace { + +OrchestratorImpl::OrchestratorImpl( + std::unique_ptr stub, + uint32_t pool_size) + : stub_(std::move(stub)), + pool_(std::make_unique(pool_size)), + semaphore_(pool_size) {} + +grpc::Status OrchestratorImpl::Query( + grpc::ServerContext*, + const protos::BigtraceQueryArgs* args, + grpc::ServerWriter* writer) { + grpc::Status query_status; + std::mutex status_lock; + base::WaitableEvent pool_completion; + const std::string& sql_query = args->sql_query(); + + for (const std::string& trace : args->traces()) { + semaphore_.Acquire(); + pool_->PostTask([&]() { + grpc::ClientContext client_context; + protos::BigtraceQueryTraceArgs trace_args; + protos::BigtraceQueryTraceResponse trace_response; + + trace_args.set_sql_query(sql_query); + trace_args.set_trace(trace); + grpc::Status status = + stub_->QueryTrace(&client_context, trace_args, &trace_response); + if (!status.ok()) { + PERFETTO_ELOG("QueryTrace returned an error status %s", + status.error_message().c_str()); + std::lock_guard status_guard(status_lock); + query_status = status; + } else { + protos::BigtraceQueryResponse response; + response.set_trace(trace_response.trace()); + for (const protos::QueryResult& query_result : + trace_response.result()) { + response.add_result()->CopyFrom(query_result); + } + std::lock_guard write_guard(write_lock_); + writer->Write(response); + } + pool_completion.Notify(); + semaphore_.Release(); + }); + } + pool_completion.Wait(static_cast(args->traces_size())); + return query_status; +} + +void OrchestratorImpl::Semaphore::Acquire() { + std::unique_lock lk(mutex_); + while (!count_) { + cv_.wait(lk); + } + --count_; +} + +void OrchestratorImpl::Semaphore::Release() { + std::lock_guard lk(mutex_); + ++count_; + cv_.notify_one(); +} + +} // namespace perfetto::bigtrace diff --git a/src/bigtrace/orchestrator/orchestrator_impl.h b/src/bigtrace/orchestrator/orchestrator_impl.h new file mode 100644 index 0000000000..65d162d802 --- /dev/null +++ b/src/bigtrace/orchestrator/orchestrator_impl.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "perfetto/ext/base/threading/thread_pool.h" +#include "protos/perfetto/bigtrace/orchestrator.grpc.pb.h" +#include "protos/perfetto/bigtrace/worker.grpc.pb.h" + +#ifndef SRC_BIGTRACE_ORCHESTRATOR_ORCHESTRATOR_IMPL_H_ +#define SRC_BIGTRACE_ORCHESTRATOR_ORCHESTRATOR_IMPL_H_ + +namespace perfetto::bigtrace { + +class OrchestratorImpl final : public protos::BigtraceOrchestrator::Service { + public: + explicit OrchestratorImpl(std::unique_ptr stub, + uint32_t pool_size); + grpc::Status Query( + grpc::ServerContext*, + const protos::BigtraceQueryArgs* args, + grpc::ServerWriter* writer) override; + + private: + class Semaphore { + public: + explicit Semaphore(uint32_t count) : count_(count) {} + void Acquire(); + void Release(); + + private: + std::mutex mutex_; + std::condition_variable cv_; + uint32_t count_; + }; + std::unique_ptr stub_; + std::unique_ptr pool_; + std::mutex write_lock_; + // Used to interleave requests to the Orchestrator to distribute jobs more + // fairly + Semaphore semaphore_; +}; + +} // namespace perfetto::bigtrace + +#endif // SRC_BIGTRACE_ORCHESTRATOR_ORCHESTRATOR_IMPL_H_ diff --git a/src/bigtrace/orchestrator/orchestrator_main.cc b/src/bigtrace/orchestrator/orchestrator_main.cc new file mode 100644 index 0000000000..7e12a278e3 --- /dev/null +++ b/src/bigtrace/orchestrator/orchestrator_main.cc @@ -0,0 +1,213 @@ +/* + * 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 +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "perfetto/base/status.h" +#include "perfetto/ext/base/getopt.h" +#include "perfetto/ext/base/status_or.h" +#include "perfetto/ext/base/string_utils.h" +#include "protos/perfetto/bigtrace/worker.grpc.pb.h" +#include "src/bigtrace/orchestrator/orchestrator_impl.h" +#include "src/trace_processor/util/status_macros.h" + +namespace perfetto::bigtrace { +namespace { + +struct CommandLineOptions { + std::string server_socket; + std::string worker_address; + uint16_t worker_port = 0; + uint64_t worker_count = 0; + std::string worker_address_list; + std::string name_resolution_scheme; + uint32_t pool_size = 0; +}; + +void PrintUsage(char** argv) { + PERFETTO_ELOG(R"( +Orchestrator main executable. +Usage: %s [OPTIONS] +Options: + -h, --help Prints this guide. + -s, --server_socket ADDRESS:PORT Input the socket for the + gRPC service to run on + -w, --worker_address ADDRESS Input the address of the workers + (for single address and + incrementing port) + -p --worker_port PORT Input the starting port of + the workers + -n --worker_count NUM_WORKERS Input the number of workers + (this will specify workers + starting from the worker_port + and counting up) + -l --worker_list SOCKET1,SOCKET2,... Input a string of comma a separated + worker sockets + (use either -l or + -w -p -n EXCLUSIVELY) + -r --name_resolution_scheme SCHEME Specify the name resolution + scheme for gRPC (e.g. ipv4:, dns://) + -t -thread_pool_size POOL_SIZE Specify the size of the thread pool + which determines number of concurrent + gRPCs from the Orchestrator + )", + argv[0]); +} + +base::StatusOr ParseCommandLineOptions(int argc, + char** argv) { + CommandLineOptions command_line_options; + + static option long_options[] = { + {"help", optional_argument, nullptr, 'h'}, + {"server_socket", optional_argument, nullptr, 's'}, + {"worker_address", optional_argument, nullptr, 'w'}, + {"worker_port", optional_argument, nullptr, 'p'}, + {"worker_count", optional_argument, nullptr, 'n'}, + {"worker_list", optional_argument, nullptr, 'l'}, + {"name_resolution_scheme", optional_argument, nullptr, 'r'}, + {"thread_pool_size", optional_argument, nullptr, 't'}, + {nullptr, 0, nullptr, 0}}; + int c; + while ((c = getopt_long(argc, argv, "s:p:w:q:n:l:r:t:h", long_options, + nullptr)) != -1) { + switch (c) { + case 's': + command_line_options.server_socket = optarg; + break; + case 'w': + command_line_options.worker_address = optarg; + break; + case 'p': + command_line_options.worker_port = static_cast(atoi(optarg)); + break; + case 'n': + command_line_options.worker_count = static_cast(atoi(optarg)); + break; + case 'l': + command_line_options.worker_address_list = optarg; + break; + case 'r': + command_line_options.name_resolution_scheme = optarg; + break; + case 't': + command_line_options.pool_size = static_cast(atoi(optarg)); + break; + default: + PrintUsage(argv); + exit(c == 'h' ? 0 : 1); + } + } + + bool has_worker_address_port_and_count = + command_line_options.worker_count && command_line_options.worker_port && + !command_line_options.worker_address.empty(); + + bool has_worker_list = !command_line_options.worker_address_list.empty(); + + if (has_worker_address_port_and_count == has_worker_list) { + return base::ErrStatus( + "Error: You must specify a worker address, port and count OR a worker " + "list"); + } + + if (command_line_options.worker_count <= 0 && !has_worker_list) { + return base::ErrStatus( + "Error: You must specify a worker count greater than 0 OR a worker " + "list"); + } + + return command_line_options; +} + +base::Status OrchestratorMain(int argc, char** argv) { + ASSIGN_OR_RETURN(base::StatusOr options, + ParseCommandLineOptions(argc, argv)); + std::string server_socket = options->server_socket.empty() + ? "127.0.0.1:5051" + : options->server_socket; + std::string worker_address = + options->worker_address.empty() ? "127.0.0.1" : options->worker_address; + uint16_t worker_port = + options->worker_port == 0 ? 5052 : options->worker_port; + std::string worker_address_list = options->worker_address_list; + uint64_t worker_count = options->worker_count; + + // TODO(ivankc) Replace with DNS resolver + std::string target_address = options->name_resolution_scheme.empty() + ? "ipv4:" + : options->name_resolution_scheme; + + uint32_t pool_size = options->pool_size == 0 + ? std::thread::hardware_concurrency() + : options->pool_size; + + PERFETTO_DCHECK(pool_size); + + if (worker_address_list.empty()) { + // Use a set of n workers incrementing from a starting port + PERFETTO_DCHECK(worker_count > 0 && !worker_address.empty()); + std::vector worker_addresses; + for (uint64_t i = 0; i < worker_count; ++i) { + std::string address = + worker_address + ":" + std::to_string(worker_port + i); + worker_addresses.push_back(address); + } + target_address += base::Join(worker_addresses, ","); + } else { + // Use a list of workers passed as an option + target_address += worker_address_list; + } + grpc::ChannelArguments args; + args.SetLoadBalancingPolicyName("round_robin"); + auto channel = grpc::CreateCustomChannel( + target_address, grpc::InsecureChannelCredentials(), args); + auto stub = protos::BigtraceWorker::NewStub(channel); + auto service = std::make_unique(std::move(stub), pool_size); + + // Setup the Orchestrator Server + grpc::ServerBuilder builder; + builder.AddListeningPort(server_socket, grpc::InsecureServerCredentials()); + builder.RegisterService(service.get()); + std::unique_ptr server(builder.BuildAndStart()); + PERFETTO_LOG("Orchestrator server listening on %s", server_socket.c_str()); + + server->Wait(); + + return base::OkStatus(); +} + +} // namespace +} // namespace perfetto::bigtrace + +int main(int argc, char** argv) { + auto status = perfetto::bigtrace::OrchestratorMain(argc, argv); + if (!status.ok()) { + fprintf(stderr, "%s\n", status.c_message()); + return 1; + } + return 0; +} diff --git a/src/bigtrace/worker-deployment.yaml b/src/bigtrace/worker-deployment.yaml new file mode 100644 index 0000000000..d9b66ad58f --- /dev/null +++ b/src/bigtrace/worker-deployment.yaml @@ -0,0 +1,34 @@ +# 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. + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: worker +spec: + replicas: 3 + selector: + matchLabels: + app: worker + template: + metadata: + labels: + app: worker + spec: + containers: + - name: worker + image: docker.io/library/worker_image:latest + imagePullPolicy: Never + ports: + - containerPort: 5052 diff --git a/src/bigtrace/worker-service.yaml b/src/bigtrace/worker-service.yaml new file mode 100644 index 0000000000..a6abf3d64d --- /dev/null +++ b/src/bigtrace/worker-service.yaml @@ -0,0 +1,26 @@ +# 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. + +apiVersion: v1 +kind: Service +metadata: + name: worker +spec: + type: NodePort + ports: + - protocol: TCP + port: 5052 + targetPort: 5052 + selector: + app: worker diff --git a/src/bigtrace/worker/BUILD.gn b/src/bigtrace/worker/BUILD.gn new file mode 100644 index 0000000000..2f91816920 --- /dev/null +++ b/src/bigtrace/worker/BUILD.gn @@ -0,0 +1,39 @@ +# 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("../../../gn/perfetto.gni") +import("../../../gn/test.gni") + +assert( + enable_perfetto_trace_processor && enable_perfetto_trace_processor_sqlite) + +if (enable_perfetto_grpc) { + executable("worker_main") { + sources = [ + "worker_impl.cc", + "worker_impl.h", + "worker_main.cc", + ] + deps = [ + "../../../gn:default_deps", + "../../../gn:grpc", + "../../../include/perfetto/ext/trace_processor/rpc:query_result_serializer", + "../../../protos/perfetto/bigtrace:worker_grpc", + "../../../protos/perfetto/bigtrace:worker_lite", + "../../../src/trace_processor", + "../../../src/trace_processor/rpc:rpc", + "../../base", + ] + } +} diff --git a/src/bigtrace/worker/Dockerfile b/src/bigtrace/worker/Dockerfile new file mode 100644 index 0000000000..65e22fc24b --- /dev/null +++ b/src/bigtrace/worker/Dockerfile @@ -0,0 +1,26 @@ +# 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. + +FROM debian:12 + +RUN apt update && apt install -y git python3 curl build-essential +RUN git clone --depth 1 https://android.googlesource.com/platform/external/perfetto/ +WORKDIR /perfetto +RUN tools/install-build-deps --grpc +RUN tools/gn gen out/dist '--args=is_clang=true \ + enable_perfetto_grpc=true \ + is_debug=false' +RUN tools/ninja -C out/dist worker_main + +CMD ["/perfetto/out/dist/worker_main", "-s", "0.0.0.0:5052"] \ No newline at end of file diff --git a/src/bigtrace/worker/worker_impl.cc b/src/bigtrace/worker/worker_impl.cc new file mode 100644 index 0000000000..53f8606446 --- /dev/null +++ b/src/bigtrace/worker/worker_impl.cc @@ -0,0 +1,54 @@ +/* + * 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/bigtrace/worker/worker_impl.h" +#include "perfetto/ext/trace_processor/rpc/query_result_serializer.h" +#include "perfetto/trace_processor/read_trace.h" +#include "perfetto/trace_processor/trace_processor.h" + +namespace perfetto::bigtrace { + +grpc::Status WorkerImpl::QueryTrace( + grpc::ServerContext*, + const protos::BigtraceQueryTraceArgs* args, + protos::BigtraceQueryTraceResponse* response) { + trace_processor::Config config; + std::unique_ptr tp = + trace_processor::TraceProcessor::CreateInstance(config); + + base::Status status = + trace_processor::ReadTrace(tp.get(), args->trace().c_str()); + if (!status.ok()) { + const std::string& error_message = status.c_message(); + return grpc::Status(grpc::StatusCode::INTERNAL, error_message); + } + auto iter = tp->ExecuteQuery(args->sql_query()); + trace_processor::QueryResultSerializer serializer = + trace_processor::QueryResultSerializer(std::move(iter)); + + std::vector serialized; + for (bool has_more = true; has_more;) { + serialized.clear(); + has_more = serializer.Serialize(&serialized); + response->add_result()->ParseFromArray(serialized.data(), + static_cast(serialized.size())); + } + response->set_trace(args->trace()); + + return grpc::Status::OK; +} + +} // namespace perfetto::bigtrace diff --git a/src/bigtrace/worker/worker_impl.h b/src/bigtrace/worker/worker_impl.h new file mode 100644 index 0000000000..b5a9664ddd --- /dev/null +++ b/src/bigtrace/worker/worker_impl.h @@ -0,0 +1,34 @@ +/* + * 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 "protos/perfetto/bigtrace/worker.grpc.pb.h" +#include "protos/perfetto/bigtrace/worker.pb.h" + +#ifndef SRC_BIGTRACE_WORKER_WORKER_IMPL_H_ +#define SRC_BIGTRACE_WORKER_WORKER_IMPL_H_ + +namespace perfetto::bigtrace { + +class WorkerImpl final : public protos::BigtraceWorker::Service { + public: + grpc::Status QueryTrace( + grpc::ServerContext*, + const protos::BigtraceQueryTraceArgs* args, + protos::BigtraceQueryTraceResponse* response) override; +}; + +} // namespace perfetto::bigtrace + +#endif // SRC_BIGTRACE_WORKER_WORKER_IMPL_H_ diff --git a/src/bigtrace/worker/worker_main.cc b/src/bigtrace/worker/worker_main.cc new file mode 100644 index 0000000000..b985f69b69 --- /dev/null +++ b/src/bigtrace/worker/worker_main.cc @@ -0,0 +1,78 @@ +/* + * 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 +#include +#include +#include + +#include "perfetto/base/status.h" +#include "perfetto/ext/base/getopt.h" +#include "src/bigtrace/worker/worker_impl.h" + +namespace perfetto::bigtrace { +namespace { + +struct CommandLineOptions { + std::string socket; +}; + +CommandLineOptions ParseCommandLineOptions(int argc, char** argv) { + CommandLineOptions command_line_options; + static option long_options[] = {{"socket", required_argument, nullptr, 's'}, + {nullptr, 0, nullptr, 0}}; + int c; + while ((c = getopt_long(argc, argv, "s:", long_options, nullptr)) != -1) { + switch (c) { + case 's': + command_line_options.socket = optarg; + break; + default: + PERFETTO_ELOG("Usage: %s --socket=address:port", argv[0]); + break; + } + } + return command_line_options; +} + +base::Status WorkerMain(int argc, char** argv) { + // Setup the Worker Server + CommandLineOptions options = ParseCommandLineOptions(argc, argv); + std::string socket = + options.socket.empty() ? "127.0.0.1:5052" : options.socket; + auto service = std::make_unique(); + grpc::ServerBuilder builder; + builder.RegisterService(service.get()); + builder.AddListeningPort(socket, grpc::InsecureServerCredentials()); + std::unique_ptr server(builder.BuildAndStart()); + PERFETTO_LOG("Worker server listening on %s", socket.c_str()); + + server->Wait(); + + return base::OkStatus(); +} + +} // namespace +} // namespace perfetto::bigtrace + +int main(int argc, char** argv) { + auto status = perfetto::bigtrace::WorkerMain(argc, argv); + if (!status.ok()) { + fprintf(stderr, "%s\n", status.c_message()); + return 1; + } + return 0; +} diff --git a/src/perfetto_cmd/perfetto_cmd.cc b/src/perfetto_cmd/perfetto_cmd.cc index 2ccd24ea65..726b2660d3 100644 --- a/src/perfetto_cmd/perfetto_cmd.cc +++ b/src/perfetto_cmd/perfetto_cmd.cc @@ -16,46 +16,47 @@ #include "src/perfetto_cmd/perfetto_cmd.h" -#include "perfetto/base/build_config.h" -#include "perfetto/base/proc_utils.h" -#include "perfetto/ext/base/scoped_file.h" -#include "perfetto/ext/base/string_splitter.h" - #include #include #include #include #include -// For dup() (and _setmode() on windows). -#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) -#include -#include -#else -#include -#endif - +#include +#include #include #include -#include +#include +#include +#include +#include +#include #include #include +#include #include +#include #include -#include +#include #include +#include +#include +#include "perfetto/base/build_config.h" #include "perfetto/base/compiler.h" #include "perfetto/base/logging.h" -#include "perfetto/base/time.h" -#include "perfetto/ext/base/android_utils.h" +#include "perfetto/base/proc_utils.h" // IWYU pragma: keep +#include "perfetto/ext/base/android_utils.h" // IWYU pragma: keep #include "perfetto/ext/base/ctrl_c_handler.h" #include "perfetto/ext/base/file_utils.h" -#include "perfetto/ext/base/getopt.h" +#include "perfetto/ext/base/getopt.h" // IWYU pragma: keep #include "perfetto/ext/base/no_destructor.h" #include "perfetto/ext/base/pipe.h" +#include "perfetto/ext/base/scoped_file.h" +#include "perfetto/ext/base/string_splitter.h" +#include "perfetto/ext/base/string_utils.h" #include "perfetto/ext/base/string_view.h" -#include "perfetto/ext/base/temp_file.h" +#include "perfetto/ext/base/thread_task_runner.h" #include "perfetto/ext/base/thread_utils.h" #include "perfetto/ext/base/utils.h" #include "perfetto/ext/base/uuid.h" @@ -64,11 +65,14 @@ #include "perfetto/ext/traced/traced.h" #include "perfetto/ext/tracing/core/basic_types.h" #include "perfetto/ext/tracing/core/trace_packet.h" -#include "perfetto/protozero/proto_utils.h" -#include "perfetto/tracing/core/data_source_descriptor.h" +#include "perfetto/ext/tracing/core/tracing_service.h" +#include "perfetto/ext/tracing/ipc/consumer_ipc_client.h" +#include "perfetto/tracing/core/flush_flags.h" +#include "perfetto/tracing/core/forward_decls.h" #include "perfetto/tracing/core/trace_config.h" -#include "perfetto/tracing/core/tracing_service_state.h" #include "perfetto/tracing/default_socket.h" +#include "protos/perfetto/common/data_source_descriptor.gen.h" +#include "src/android_stats/perfetto_atoms.h" #include "src/android_stats/statsd_logging_helper.h" #include "src/perfetto_cmd/bugreport_path.h" #include "src/perfetto_cmd/config.h" @@ -81,6 +85,14 @@ #include "protos/perfetto/common/tracing_service_state.gen.h" #include "protos/perfetto/common/track_event_descriptor.gen.h" +// For dup() (and _setmode() on windows). +#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) +#include +#include +#else +#include +#endif + namespace perfetto { namespace { @@ -92,7 +104,7 @@ const uint32_t kCloneTimeoutMs = 30000; class LoggingErrorReporter : public ErrorReporter { public: LoggingErrorReporter(std::string file_name, const char* config) - : file_name_(file_name), config_(config) {} + : file_name_(std::move(file_name)), config_(config) {} void AddError(size_t row, size_t column, @@ -171,30 +183,6 @@ std::optional ConvertRateLimiterResponseToAtom( PERFETTO_FATAL("For GCC"); } -#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) -// Reports trace with `uuid` being finalized to the trace marker. -// -// This reimplements parts of android libcutils, because: -// * libcutils is not exposed to the NDK and cannot be used from standalone -// perfetto -// * libcutils atrace uses properties to enable tags, which are not required in -// this case. -void ReportFinalizeTraceUuidToAtrace(const base::Uuid& uuid) { - base::ScopedFile file = - base::OpenFile("/sys/kernel/tracing/trace_marker", O_WRONLY); - if (!file) { - file = base::OpenFile("/sys/kernel/debug/tracing/trace_marker", O_WRONLY); - if (!file) { - return; - } - } - base::StackString<100> uuid_slice("N|%d|OtherTraces|finalize-uuid-%s", - base::GetProcessId(), - uuid.ToPrettyString().c_str()); - PERFETTO_EINTR(write(file.get(), uuid_slice.c_str(), uuid_slice.len())); -} -#endif // PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) - void ArgsAppend(std::string* str, const std::string& arg) { str->append(arg); str->append("\0", 1); @@ -1014,7 +1002,14 @@ int PerfettoCmd::ConnectToServiceAndRun() { std::this_thread::sleep_for(std::chrono::milliseconds(dist(minstd))); } - if (trace_config_->trigger_config().trigger_timeout_ms() == 0) { + if (clone_tsid_) { + if (snapshot_trigger_name_.empty()) { + LogUploadEvent(PerfettoStatsdAtom::kCloneTraceBegin); + } else { + LogUploadEvent(PerfettoStatsdAtom::kCloneTriggerTraceBegin, + snapshot_trigger_name_); + } + } else if (trace_config_->trigger_config().trigger_timeout_ms() == 0) { LogUploadEvent(PerfettoStatsdAtom::kTraceBegin); } else { LogUploadEvent(PerfettoStatsdAtom::kBackgroundTraceBegin); @@ -1269,12 +1264,6 @@ void PerfettoCmd::FinalizeTraceAndExit() { } } -#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) - // When multiple traces are being recorded at the same time, this is used to - // correlate one trace with another. - ReportFinalizeTraceUuidToAtrace(base::Uuid(uuid_)); -#endif - tracing_succeeded_ = true; task_runner_.Quit(); } @@ -1533,11 +1522,15 @@ void PerfettoCmd::OnObservableEvents( } if (observable_events.has_clone_trigger_hit()) { int64_t tsid = observable_events.clone_trigger_hit().tracing_session_id(); - OnCloneSnapshotTriggerReceived(static_cast(tsid)); + std::string trigger_name = + observable_events.clone_trigger_hit().trigger_name(); + OnCloneSnapshotTriggerReceived(static_cast(tsid), + std::move(trigger_name)); } } -void PerfettoCmd::OnCloneSnapshotTriggerReceived(TracingSessionID tsid) { +void PerfettoCmd::OnCloneSnapshotTriggerReceived(TracingSessionID tsid, + std::string trigger_name) { std::string cmdline; cmdline.reserve(128); ArgsAppend(&cmdline, "perfetto"); @@ -1555,13 +1548,15 @@ void PerfettoCmd::OnCloneSnapshotTriggerReceived(TracingSessionID tsid) { } else { PERFETTO_FATAL("Cannot use CLONE_SNAPSHOT with the current cmdline args"); } - CloneSessionOnThread(tsid, cmdline, kSingleExtraThread, nullptr); + CloneSessionOnThread(tsid, cmdline, kSingleExtraThread, + std::move(trigger_name), nullptr); } void PerfettoCmd::CloneSessionOnThread( TracingSessionID tsid, const std::string& cmdline, CloneThreadMode thread_mode, + std::string trigger_name, std::function on_clone_callback) { PERFETTO_DLOG("Creating snapshot for tracing session %" PRIu64, tsid); @@ -1583,7 +1578,7 @@ void PerfettoCmd::CloneSessionOnThread( std::string trace_config_copy = trace_config_->SerializeAsString(); snapshot_threads_.back().PostTask( - [tsid, cmdline, trace_config_copy, on_clone_callback] { + [tsid, cmdline, trace_config_copy, trigger_name, on_clone_callback] { int argc = 0; char* argv[32]; // `splitter` needs to live on the stack for the whole scope as it owns @@ -1595,6 +1590,7 @@ void PerfettoCmd::CloneSessionOnThread( } perfetto::PerfettoCmd cmd; cmd.snapshot_config_ = std::move(trace_config_copy); + cmd.snapshot_trigger_name_ = std::move(trigger_name); cmd.on_session_cloned_ = on_clone_callback; auto cmdline_res = cmd.ParseCmdlineAndMaybeDaemonize(argc, argv); PERFETTO_CHECK(!cmdline_res.has_value()); // No daemonization expected. @@ -1686,7 +1682,7 @@ void PerfettoCmd::CloneAllBugreportTraces( ArgsAppend(&cmdline, "--clone-for-bugreport"); ArgsAppend(&cmdline, "--out"); ArgsAppend(&cmdline, out_path); - CloneSessionOnThread(it->tsid, cmdline, kNewThreadPerRequest, sync_fn); + CloneSessionOnThread(it->tsid, cmdline, kNewThreadPerRequest, "", sync_fn); } // for(sessions) PERFETTO_DLOG("Issuing %zu CloneSession requests", num_sessions); @@ -1720,6 +1716,15 @@ void PerfettoCmd::LogUploadEvent(PerfettoStatsdAtom atom) { android_stats::MaybeLogUploadEvent(atom, uuid.lsb(), uuid.msb()); } +void PerfettoCmd::LogUploadEvent(PerfettoStatsdAtom atom, + const std::string& trigger_name) { + if (!statsd_logging_) + return; + base::Uuid uuid(uuid_); + android_stats::MaybeLogUploadEvent(atom, uuid.lsb(), uuid.msb(), + trigger_name); +} + void PerfettoCmd::LogTriggerEvents( PerfettoTriggerAtom atom, const std::vector& trigger_names) { diff --git a/src/perfetto_cmd/perfetto_cmd.h b/src/perfetto_cmd/perfetto_cmd.h index 844afaf826..160a7015dc 100644 --- a/src/perfetto_cmd/perfetto_cmd.h +++ b/src/perfetto_cmd/perfetto_cmd.h @@ -17,8 +17,7 @@ #ifndef SRC_PERFETTO_CMD_PERFETTO_CMD_H_ #define SRC_PERFETTO_CMD_PERFETTO_CMD_H_ -#include - +#include #include #include #include @@ -32,9 +31,12 @@ #include "perfetto/ext/base/scoped_file.h" #include "perfetto/ext/base/thread_task_runner.h" #include "perfetto/ext/base/unix_task_runner.h" +#include "perfetto/ext/base/uuid.h" #include "perfetto/ext/base/weak_ptr.h" +#include "perfetto/ext/tracing/core/basic_types.h" #include "perfetto/ext/tracing/core/consumer.h" #include "perfetto/ext/tracing/ipc/consumer_ipc_client.h" +#include "perfetto/tracing/core/forward_decls.h" #include "src/android_stats/perfetto_atoms.h" #include "src/perfetto_cmd/packet_writer.h" @@ -86,6 +88,7 @@ class PerfettoCmd : public Consumer { void CloneSessionOnThread(TracingSessionID, const std::string& cmdline, // \0 separated. CloneThreadMode, + std::string clone_trigger_name, std::function on_clone_callback); void OnTimeout(); bool is_detach() const { return !detach_key_.empty(); } @@ -126,7 +129,8 @@ class PerfettoCmd : public Consumer { // will have no effect. void NotifyBgProcessPipe(BgProcessStatus status); - void OnCloneSnapshotTriggerReceived(TracingSessionID); + void OnCloneSnapshotTriggerReceived(TracingSessionID, + std::string trigger_name); #if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) static base::ScopedFile CreateUnlinkedTmpFile(); @@ -135,6 +139,7 @@ class PerfettoCmd : public Consumer { void ReportTraceToAndroidFrameworkOrCrash(); #endif void LogUploadEvent(PerfettoStatsdAtom atom); + void LogUploadEvent(PerfettoStatsdAtom atom, const std::string& trigger_name); void LogTriggerEvents(PerfettoTriggerAtom atom, const std::vector& trigger_names); @@ -185,6 +190,7 @@ class PerfettoCmd : public Consumer { std::list snapshot_threads_; int snapshot_count_ = 0; std::string snapshot_config_; + std::string snapshot_trigger_name_; base::WeakPtrFactory weak_factory_{this}; }; diff --git a/src/protozero/protoc_plugin/protozero_c_plugin.cc b/src/protozero/protoc_plugin/protozero_c_plugin.cc index 804968eef0..de3a99acbb 100644 --- a/src/protozero/protoc_plugin/protozero_c_plugin.cc +++ b/src/protozero/protoc_plugin/protozero_c_plugin.cc @@ -94,6 +94,8 @@ class GeneratorJob { GenerateEnumDescriptor(enumeration); for (const Descriptor* message : messages_) GenerateMessageDescriptor(message); + for (const auto& [name, descriptors] : extensions_) + GenerateExtension(name, descriptors); GenerateEpilogue(); return error_.empty(); } @@ -240,7 +242,8 @@ class GeneratorJob { // As the support for extensions in protozero is limited, the code // assumes that extend blocks are located inside a wrapper message and // name of this message is used to group them. - std::string extension_name = extension->extension_scope()->name(); + std::string extension_name = + GetCppClassName(extension->extension_scope()); extensions_[extension_name].push_back(extension); } } else { @@ -449,7 +452,7 @@ class GeneratorJob { // Packed repeated fields are encoded as a length-delimited field on the wire, // where the payload is the concatenation of invidually encoded elements. - void GeneratePackedRepeatedFieldDescriptor( + void GeneratePackedRepeatedFieldDescriptorArgs( const std::string& message_cpp_type, const FieldDescriptor* field) { std::map setter; @@ -457,13 +460,29 @@ class GeneratorJob { setter["name"] = field->lowercase_name(); setter["class"] = message_cpp_type; setter["buffer_type"] = FieldTypeToPackedBufferType(field->type()); - stub_h_->Print( - setter, - "PERFETTO_PB_FIELD($class$, PACKED, $buffer_type$, $name$, $id$);\n"); + stub_h_->Print(setter, "$class$, PACKED, $buffer_type$, $name$, $id$\n"); } - void GenerateSimpleFieldDescriptor(const std::string& message_cpp_type, - const FieldDescriptor* field) { + void GeneratePackedRepeatedFieldDescriptor( + const std::string& message_cpp_type, + const FieldDescriptor* field) { + stub_h_->Print("PERFETTO_PB_FIELD("); + GeneratePackedRepeatedFieldDescriptorArgs(message_cpp_type, field); + stub_h_->Print(");\n"); + } + + void GeneratePackedRepeatedFieldDescriptorForExtension( + const std::string& field_cpp_prefix, + const std::string& message_cpp_type, + const FieldDescriptor* field) { + stub_h_->Print("PERFETTO_PB_EXTENSION_FIELD($prefix$, ", "prefix", + field_cpp_prefix); + GeneratePackedRepeatedFieldDescriptorArgs(message_cpp_type, field); + stub_h_->Print(");\n"); + } + + void GenerateSimpleFieldDescriptorArgs(const std::string& message_cpp_type, + const FieldDescriptor* field) { std::map setter; setter["id"] = std::to_string(field->number()); setter["name"] = field->lowercase_name(); @@ -473,9 +492,7 @@ class GeneratorJob { switch (field->type()) { case FieldDescriptor::TYPE_BYTES: case FieldDescriptor::TYPE_STRING: - stub_h_->Print( - setter, - "PERFETTO_PB_FIELD($class$, STRING, const char*, $name$, $id$);\n"); + stub_h_->Print(setter, "$class$, STRING, const char*, $name$, $id$"); break; case FieldDescriptor::TYPE_UINT64: case FieldDescriptor::TYPE_UINT32: @@ -483,29 +500,21 @@ class GeneratorJob { case FieldDescriptor::TYPE_INT32: case FieldDescriptor::TYPE_BOOL: case FieldDescriptor::TYPE_ENUM: - stub_h_->Print( - setter, - "PERFETTO_PB_FIELD($class$, VARINT, $ctype$, $name$, $id$);\n"); + stub_h_->Print(setter, "$class$, VARINT, $ctype$, $name$, $id$"); break; case FieldDescriptor::TYPE_SINT64: case FieldDescriptor::TYPE_SINT32: - stub_h_->Print( - setter, - "PERFETTO_PB_FIELD($class$, ZIGZAG, $ctype$, $name$, $id$);\n"); + stub_h_->Print(setter, "$class$, ZIGZAG, $ctype$, $name$, $id$"); break; case FieldDescriptor::TYPE_SFIXED32: case FieldDescriptor::TYPE_FIXED32: case FieldDescriptor::TYPE_FLOAT: - stub_h_->Print( - setter, - "PERFETTO_PB_FIELD($class$, FIXED32, $ctype$, $name$, $id$);\n"); + stub_h_->Print(setter, "$class$, FIXED32, $ctype$, $name$, $id$"); break; case FieldDescriptor::TYPE_SFIXED64: case FieldDescriptor::TYPE_FIXED64: case FieldDescriptor::TYPE_DOUBLE: - stub_h_->Print( - setter, - "PERFETTO_PB_FIELD($class$, FIXED64, $ctype$, $name$, $id$);\n"); + stub_h_->Print(setter, "$class$, FIXED64, $ctype$, $name$, $id$"); break; case FieldDescriptor::TYPE_MESSAGE: case FieldDescriptor::TYPE_GROUP: @@ -514,6 +523,23 @@ class GeneratorJob { } } + void GenerateSimpleFieldDescriptor(const std::string& message_cpp_type, + const FieldDescriptor* field) { + stub_h_->Print("PERFETTO_PB_FIELD("); + GenerateSimpleFieldDescriptorArgs(message_cpp_type, field); + stub_h_->Print(");\n"); + } + + void GenerateSimpleFieldDescriptorForExtension( + const std::string& field_cpp_prefix, + const std::string& message_cpp_type, + const FieldDescriptor* field) { + stub_h_->Print("PERFETTO_PB_EXTENSION_FIELD($prefix$, ", "prefix", + field_cpp_prefix); + GenerateSimpleFieldDescriptorArgs(message_cpp_type, field); + stub_h_->Print(");\n"); + } + void GenerateNestedMessageFieldDescriptor(const std::string& message_cpp_type, const FieldDescriptor* field) { std::string inner_class = GetCppClassName(field->message_type()); @@ -523,6 +549,19 @@ class GeneratorJob { "name", field->lowercase_name(), "inner_class", inner_class); } + void GenerateNestedMessageFieldDescriptorForExtension( + const std::string& field_cpp_prefix, + const std::string& message_cpp_type, + const FieldDescriptor* field) { + std::string inner_class = GetCppClassName(field->message_type()); + stub_h_->Print( + "PERFETTO_PB_EXTENSION_FIELD($prefix$, $class$, MSG, $inner_class$, " + "$name$, $id$);\n", + "prefix", field_cpp_prefix, "class", message_cpp_type, "id", + std::to_string(field->number()), "name", field->lowercase_name(), + "inner_class", inner_class); + } + void GenerateMessageDescriptor(const Descriptor* message) { stub_h_->Print("PERFETTO_PB_MSG($name$);\n", "name", GetCppClassName(message)); @@ -546,6 +585,60 @@ class GeneratorJob { } } + void GenerateExtensionFieldDescriptor(const std::string& field_cpp_prefix, + const std::string& message_cpp_type, + const FieldDescriptor* field) { + // GenerateFieldMetadata(message_cpp_type, field); + if (field->is_packed()) { + GeneratePackedRepeatedFieldDescriptorForExtension( + field_cpp_prefix, message_cpp_type, field); + } else if (field->type() != FieldDescriptor::TYPE_MESSAGE) { + GenerateSimpleFieldDescriptorForExtension(field_cpp_prefix, + message_cpp_type, field); + } else { + GenerateNestedMessageFieldDescriptorForExtension(field_cpp_prefix, + message_cpp_type, field); + } + } + + // Generate extension class for a group of FieldDescriptor instances + // representing one "extend" block in proto definition. For example: + // + // message SpecificExtension { + // extend GeneralThing { + // optional Fizz fizz = 101; + // optional Buzz buzz = 102; + // } + // } + // + // This is going to be passed as a vector of two elements, "fizz" and + // "buzz". Wrapping message is used to provide a name for generated + // extension class. + // + // In the example above, generated code is going to look like: + // + // class SpecificExtension : public GeneralThing { + // Fizz* set_fizz(); + // Buzz* set_buzz(); + // } + void GenerateExtension( + const std::string& extension_name, + const std::vector& descriptors) { + // Use an arbitrary descriptor in order to get generic information not + // specific to any of them. + const FieldDescriptor* descriptor = descriptors[0]; + const Descriptor* base_message = descriptor->containing_type(); + + for (const FieldDescriptor* field : descriptors) { + if (field->containing_type() != base_message) { + Abort("one wrapper should extend only one message"); + return; + } + GenerateExtensionFieldDescriptor(extension_name, + GetCppClassName(base_message), field); + } + } + void GenerateEpilogue() { stub_h_->Print("#endif // $guard$\n", "guard", GenerateGuard()); } diff --git a/src/protozero/test/example_proto/test_messages.proto b/src/protozero/test/example_proto/test_messages.proto index b54d8831f0..ea861442d7 100644 --- a/src/protozero/test/example_proto/test_messages.proto +++ b/src/protozero/test/example_proto/test_messages.proto @@ -86,7 +86,9 @@ message EveryField { message NestedA { message NestedB { - message NestedC { optional int32 value_c = 1; } + message NestedC { + optional int32 value_c = 1; + } optional NestedC value_b = 1; } repeated NestedB repeated_a = 2; diff --git a/src/shared_lib/producer.cc b/src/shared_lib/producer.cc index 96e7b68f55..06599f97e1 100644 --- a/src/shared_lib/producer.cc +++ b/src/shared_lib/producer.cc @@ -37,15 +37,39 @@ void ResetForTesting() { } // namespace shlib } // namespace perfetto -void PerfettoProducerInProcessInit() { +struct PerfettoProducerBackendInitArgs { + uint32_t shmem_size_hint_kb = 0; +}; + +struct PerfettoProducerBackendInitArgs* +PerfettoProducerBackendInitArgsCreate() { + return new PerfettoProducerBackendInitArgs(); +} + +void PerfettoProducerBackendInitArgsSetShmemSizeHintKb( + struct PerfettoProducerBackendInitArgs* backend_args, + uint32_t size) { + backend_args->shmem_size_hint_kb = size; +} + +void PerfettoProducerBackendInitArgsDestroy( + struct PerfettoProducerBackendInitArgs* backend_args) { + delete backend_args; +} + +void PerfettoProducerInProcessInit( + const struct PerfettoProducerBackendInitArgs* backend_args) { perfetto::TracingInitArgs args; args.backends = perfetto::kInProcessBackend; + args.shmem_size_hint_kb = backend_args->shmem_size_hint_kb; perfetto::Tracing::Initialize(args); } -void PerfettoProducerSystemInit() { +void PerfettoProducerSystemInit( + const struct PerfettoProducerBackendInitArgs* backend_args) { perfetto::TracingInitArgs args; args.backends = perfetto::kSystemBackend; + args.shmem_size_hint_kb = backend_args->shmem_size_hint_kb; perfetto::Tracing::Initialize(args); } diff --git a/src/shared_lib/test/api_integrationtest.cc b/src/shared_lib/test/api_integrationtest.cc index 1fa627875f..1b581bd16c 100644 --- a/src/shared_lib/test/api_integrationtest.cc +++ b/src/shared_lib/test/api_integrationtest.cc @@ -40,6 +40,7 @@ #include "test/gtest_and_gmock.h" #include "src/shared_lib/reset_for_testing.h" +#include "src/shared_lib/test/protos/extensions.pzc.h" #include "src/shared_lib/test/protos/test_messages.pzc.h" #include "src/shared_lib/test/utils.h" @@ -396,6 +397,42 @@ TEST_F(SharedLibProtozeroSerializationTest, NestedMessages) { VarIntField(1000))))))); } +TEST_F(SharedLibProtozeroSerializationTest, Extensions) { + struct protozero_test_protos_RealFakeEvent base; + PerfettoPbMsgInit(&base.msg, &writer); + + { + struct protozero_test_protos_SystemA msg_a; + protozero_test_protos_BrowserExtension_begin_extension_a(&base, &msg_a); + protozero_test_protos_SystemA_set_cstr_string_a(&msg_a, "str_a"); + protozero_test_protos_BrowserExtension_end_extension_a(&base, &msg_a); + } + { + struct protozero_test_protos_SystemB msg_b; + protozero_test_protos_BrowserExtension_begin_extension_b(&base, &msg_b); + protozero_test_protos_SystemB_set_cstr_string_b(&msg_b, "str_b"); + protozero_test_protos_BrowserExtension_end_extension_b(&base, &msg_b); + } + + protozero_test_protos_RealFakeEvent_set_cstr_base_string(&base, "str"); + + EXPECT_THAT( + FieldView(GetData()), + ElementsAre( + PbField( + protozero_test_protos_BrowserExtension_extension_a_field_number, + MsgField(ElementsAre( + PbField(protozero_test_protos_SystemA_string_a_field_number, + StringField("str_a"))))), + PbField( + protozero_test_protos_BrowserExtension_extension_b_field_number, + MsgField(ElementsAre( + PbField(protozero_test_protos_SystemB_string_b_field_number, + StringField("str_b"))))), + PbField(protozero_test_protos_RealFakeEvent_base_string_field_number, + StringField("str")))); +} + TEST_F(SharedLibProtozeroSerializationTest, PackedRepeatedMsgVarInt) { struct protozero_test_protos_PackedRepeatedFields msg; PerfettoPbMsgInit(&msg.msg, &writer); @@ -932,7 +969,7 @@ TEST_F(SharedLibDataSourceTest, IncrementalState) { class SharedLibProducerTest : public testing::Test { protected: void SetUp() override { - struct PerfettoProducerInitArgs args = {0}; + struct PerfettoProducerInitArgs args = PERFETTO_PRODUCER_INIT_ARGS_INIT(); args.backends = PERFETTO_BACKEND_IN_PROCESS; PerfettoProducerInit(args); } @@ -1005,7 +1042,7 @@ TEST_F(SharedLibProducerTest, ActivateTriggers) { class SharedLibTrackEventTest : public testing::Test { protected: void SetUp() override { - struct PerfettoProducerInitArgs args = {0}; + struct PerfettoProducerInitArgs args = PERFETTO_PRODUCER_INIT_ARGS_INIT(); args.backends = PERFETTO_BACKEND_IN_PROCESS; PerfettoProducerInit(args); PerfettoTeInit(); @@ -1521,4 +1558,383 @@ TEST_F(SharedLibTrackEventTest, TrackEventHlRegisteredCounter) { ElementsAre(VarIntField(kExpectedUuid)))))))))); } +TEST_F(SharedLibTrackEventTest, Scoped) { + TracingSession tracing_session = TracingSession::Builder() + .set_data_source_name("track_event") + .add_enabled_category("*") + .Build(); + + { + PERFETTO_TE_SCOPED(cat1, PERFETTO_TE_SLICE("slice"), + PERFETTO_TE_ARG_UINT64("arg_name", 42)); + PERFETTO_TE(cat1, PERFETTO_TE_INSTANT("event")); + } + + tracing_session.StopBlocking(); + std::vector data = tracing_session.ReadBlocking(); + auto trace_view = FieldView(data); + auto it = trace_view.begin(); + for (; it != trace_view.end(); it++) { + struct PerfettoPbDecoderField trace_field = *it; + ASSERT_THAT(trace_field, PbField(perfetto_protos_Trace_packet_field_number, + MsgField(_))); + IdFieldView track_event( + trace_field, perfetto_protos_TracePacket_track_event_field_number); + if (track_event.size() == 0) { + continue; + } + + ASSERT_THAT(track_event, + ElementsAre(AllFieldsWithId( + perfetto_protos_TrackEvent_type_field_number, + ElementsAre(VarIntField( + perfetto_protos_TrackEvent_TYPE_SLICE_BEGIN))))); + IdFieldView name_iid_fields( + track_event.front(), perfetto_protos_TrackEvent_name_iid_field_number); + ASSERT_THAT(name_iid_fields, ElementsAre(VarIntField(_))); + uint64_t name_iid = name_iid_fields.front().value.integer64; + IdFieldView debug_annot_fields( + track_event.front(), + perfetto_protos_TrackEvent_debug_annotations_field_number); + ASSERT_THAT( + debug_annot_fields, + ElementsAre(MsgField(UnorderedElementsAre( + PbField(perfetto_protos_DebugAnnotation_name_iid_field_number, + VarIntField(_)), + PbField(perfetto_protos_DebugAnnotation_uint_value_field_number, + VarIntField(42)))))); + uint64_t arg_name_iid = + IdFieldView(debug_annot_fields.front(), + perfetto_protos_DebugAnnotation_name_iid_field_number) + .front() + .value.integer64; + EXPECT_THAT( + trace_field, + AllFieldsWithId( + perfetto_protos_TracePacket_interned_data_field_number, + ElementsAre(AllOf( + AllFieldsWithId( + perfetto_protos_InternedData_event_names_field_number, + ElementsAre(MsgField(UnorderedElementsAre( + PbField(perfetto_protos_EventName_iid_field_number, + VarIntField(name_iid)), + PbField(perfetto_protos_EventName_name_field_number, + StringField("slice")))))), + AllFieldsWithId( + perfetto_protos_InternedData_debug_annotation_names_field_number, + ElementsAre(MsgField(UnorderedElementsAre( + PbField( + perfetto_protos_DebugAnnotationName_iid_field_number, + VarIntField(arg_name_iid)), + PbField( + perfetto_protos_DebugAnnotationName_name_field_number, + StringField("arg_name")))))))))); + it++; + break; + } + ASSERT_NE(it, trace_view.end()); + for (; it != trace_view.end(); it++) { + struct PerfettoPbDecoderField trace_field = *it; + ASSERT_THAT(trace_field, PbField(perfetto_protos_Trace_packet_field_number, + MsgField(_))); + IdFieldView track_event( + trace_field, perfetto_protos_TracePacket_track_event_field_number); + if (track_event.size() == 0) { + continue; + } + ASSERT_THAT(track_event, + ElementsAre(AllFieldsWithId( + perfetto_protos_TrackEvent_type_field_number, + ElementsAre(VarIntField( + perfetto_protos_TrackEvent_TYPE_INSTANT))))); + IdFieldView name_iid_fields( + track_event.front(), perfetto_protos_TrackEvent_name_iid_field_number); + ASSERT_THAT(name_iid_fields, ElementsAre(VarIntField(_))); + uint64_t name_iid = name_iid_fields.front().value.integer64; + EXPECT_THAT(trace_field, + AllFieldsWithId( + perfetto_protos_TracePacket_interned_data_field_number, + ElementsAre(AllFieldsWithId( + perfetto_protos_InternedData_event_names_field_number, + ElementsAre(MsgField(UnorderedElementsAre( + PbField(perfetto_protos_EventName_iid_field_number, + VarIntField(name_iid)), + PbField(perfetto_protos_EventName_name_field_number, + StringField("event"))))))))); + it++; + break; + } + ASSERT_NE(it, trace_view.end()); + for (; it != trace_view.end(); it++) { + struct PerfettoPbDecoderField trace_field = *it; + ASSERT_THAT(trace_field, PbField(perfetto_protos_Trace_packet_field_number, + MsgField(_))); + IdFieldView track_event( + trace_field, perfetto_protos_TracePacket_track_event_field_number); + if (track_event.size() == 0) { + continue; + } + ASSERT_THAT(track_event, + ElementsAre(AllFieldsWithId( + perfetto_protos_TrackEvent_type_field_number, + ElementsAre(VarIntField( + perfetto_protos_TrackEvent_TYPE_SLICE_END))))); + IdFieldView debug_annot_fields( + track_event.front(), + perfetto_protos_TrackEvent_debug_annotations_field_number); + ASSERT_THAT(debug_annot_fields, ElementsAre()); + it++; + break; + } +} + +TEST_F(SharedLibTrackEventTest, ScopedDisabled) { + TracingSession tracing_session = TracingSession::Builder() + .set_data_source_name("track_event") + .add_disabled_category("cat1") + .Build(); + // Check that the PERFETTO_TE_SCOPED macro does not have any effect if the + // category is disabled. + { PERFETTO_TE_SCOPED(cat1, PERFETTO_TE_SLICE("slice")); } + + tracing_session.StopBlocking(); + std::vector data = tracing_session.ReadBlocking(); + auto trace_view = FieldView(data); + auto it = trace_view.begin(); + for (; it != trace_view.end(); it++) { + struct PerfettoPbDecoderField trace_field = *it; + ASSERT_THAT(trace_field, PbField(perfetto_protos_Trace_packet_field_number, + MsgField(_))); + IdFieldView track_event( + trace_field, perfetto_protos_TracePacket_track_event_field_number); + ASSERT_EQ(track_event.size(), 0u); + } +} + +TEST_F(SharedLibTrackEventTest, ScopedSingleLine) { + TracingSession tracing_session = TracingSession::Builder() + .set_data_source_name("track_event") + .add_enabled_category("*") + .Build(); + + // Check that the PERFETTO_TE_SCOPED macro is expanded into a single + // statement. Emitting the end event should not escape + if (false) + PERFETTO_TE_SCOPED(cat1, PERFETTO_TE_SLICE("slice")); + + tracing_session.StopBlocking(); + std::vector data = tracing_session.ReadBlocking(); + auto trace_view = FieldView(data); + auto it = trace_view.begin(); + for (; it != trace_view.end(); it++) { + struct PerfettoPbDecoderField trace_field = *it; + ASSERT_THAT(trace_field, PbField(perfetto_protos_Trace_packet_field_number, + MsgField(_))); + IdFieldView track_event( + trace_field, perfetto_protos_TracePacket_track_event_field_number); + ASSERT_EQ(track_event.size(), 0u); + } +} + +TEST_F(SharedLibTrackEventTest, ScopedCapture) { + TracingSession tracing_session = TracingSession::Builder() + .set_data_source_name("track_event") + .add_enabled_category("*") + .Build(); + + // Check that the PERFETTO_TE_SCOPED macro can capture variables. + uint64_t value = 42; + { + PERFETTO_TE_SCOPED(cat1, PERFETTO_TE_SLICE("slice"), + PERFETTO_TE_ARG_UINT64("arg_name", value)); + } + + tracing_session.StopBlocking(); + std::vector data = tracing_session.ReadBlocking(); + auto trace_view = FieldView(data); + auto it = trace_view.begin(); + for (; it != trace_view.end(); it++) { + struct PerfettoPbDecoderField trace_field = *it; + ASSERT_THAT(trace_field, PbField(perfetto_protos_Trace_packet_field_number, + MsgField(_))); + IdFieldView track_event( + trace_field, perfetto_protos_TracePacket_track_event_field_number); + if (track_event.size() == 0) { + continue; + } + + ASSERT_THAT(track_event, + ElementsAre(AllFieldsWithId( + perfetto_protos_TrackEvent_type_field_number, + ElementsAre(VarIntField( + perfetto_protos_TrackEvent_TYPE_SLICE_BEGIN))))); + IdFieldView name_iid_fields( + track_event.front(), perfetto_protos_TrackEvent_name_iid_field_number); + ASSERT_THAT(name_iid_fields, ElementsAre(VarIntField(_))); + uint64_t name_iid = name_iid_fields.front().value.integer64; + IdFieldView debug_annot_fields( + track_event.front(), + perfetto_protos_TrackEvent_debug_annotations_field_number); + ASSERT_THAT( + debug_annot_fields, + ElementsAre(MsgField(UnorderedElementsAre( + PbField(perfetto_protos_DebugAnnotation_name_iid_field_number, + VarIntField(_)), + PbField(perfetto_protos_DebugAnnotation_uint_value_field_number, + VarIntField(42)))))); + uint64_t arg_name_iid = + IdFieldView(debug_annot_fields.front(), + perfetto_protos_DebugAnnotation_name_iid_field_number) + .front() + .value.integer64; + EXPECT_THAT( + trace_field, + AllFieldsWithId( + perfetto_protos_TracePacket_interned_data_field_number, + ElementsAre(AllOf( + AllFieldsWithId( + perfetto_protos_InternedData_event_names_field_number, + ElementsAre(MsgField(UnorderedElementsAre( + PbField(perfetto_protos_EventName_iid_field_number, + VarIntField(name_iid)), + PbField(perfetto_protos_EventName_name_field_number, + StringField("slice")))))), + AllFieldsWithId( + perfetto_protos_InternedData_debug_annotation_names_field_number, + ElementsAre(MsgField(UnorderedElementsAre( + PbField( + perfetto_protos_DebugAnnotationName_iid_field_number, + VarIntField(arg_name_iid)), + PbField( + perfetto_protos_DebugAnnotationName_name_field_number, + StringField("arg_name")))))))))); + it++; + break; + } + ASSERT_NE(it, trace_view.end()); + for (; it != trace_view.end(); it++) { + struct PerfettoPbDecoderField trace_field = *it; + ASSERT_THAT(trace_field, PbField(perfetto_protos_Trace_packet_field_number, + MsgField(_))); + IdFieldView track_event( + trace_field, perfetto_protos_TracePacket_track_event_field_number); + if (track_event.size() == 0) { + continue; + } + ASSERT_THAT(track_event, + ElementsAre(AllFieldsWithId( + perfetto_protos_TrackEvent_type_field_number, + ElementsAre(VarIntField( + perfetto_protos_TrackEvent_TYPE_SLICE_END))))); + IdFieldView debug_annot_fields( + track_event.front(), + perfetto_protos_TrackEvent_debug_annotations_field_number); + ASSERT_THAT(debug_annot_fields, ElementsAre()); + it++; + break; + } +} + +TEST_F(SharedLibTrackEventTest, ScopedFunc) { + TracingSession tracing_session = TracingSession::Builder() + .set_data_source_name("track_event") + .add_enabled_category("*") + .Build(); + + // Check that using __func__ works as expected. + { PERFETTO_TE_SCOPED(cat1, PERFETTO_TE_SLICE(__func__)); } + + tracing_session.StopBlocking(); + std::vector data = tracing_session.ReadBlocking(); + auto trace_view = FieldView(data); + auto it = trace_view.begin(); + for (; it != trace_view.end(); it++) { + struct PerfettoPbDecoderField trace_field = *it; + ASSERT_THAT(trace_field, PbField(perfetto_protos_Trace_packet_field_number, + MsgField(_))); + IdFieldView track_event( + trace_field, perfetto_protos_TracePacket_track_event_field_number); + if (track_event.size() == 0) { + continue; + } + + ASSERT_THAT(track_event, + ElementsAre(AllFieldsWithId( + perfetto_protos_TrackEvent_type_field_number, + ElementsAre(VarIntField( + perfetto_protos_TrackEvent_TYPE_SLICE_BEGIN))))); + IdFieldView name_iid_fields( + track_event.front(), perfetto_protos_TrackEvent_name_iid_field_number); + ASSERT_THAT(name_iid_fields, ElementsAre(VarIntField(_))); + uint64_t name_iid = name_iid_fields.front().value.integer64; + EXPECT_THAT(trace_field, + AllFieldsWithId( + perfetto_protos_TracePacket_interned_data_field_number, + ElementsAre(AllFieldsWithId( + perfetto_protos_InternedData_event_names_field_number, + ElementsAre(MsgField(UnorderedElementsAre( + PbField(perfetto_protos_EventName_iid_field_number, + VarIntField(name_iid)), + PbField(perfetto_protos_EventName_name_field_number, + StringField(__func__))))))))); + it++; + break; + } + ASSERT_NE(it, trace_view.end()); + for (; it != trace_view.end(); it++) { + struct PerfettoPbDecoderField trace_field = *it; + ASSERT_THAT(trace_field, PbField(perfetto_protos_Trace_packet_field_number, + MsgField(_))); + IdFieldView track_event( + trace_field, perfetto_protos_TracePacket_track_event_field_number); + if (track_event.size() == 0) { + continue; + } + ASSERT_THAT(track_event, + ElementsAre(AllFieldsWithId( + perfetto_protos_TrackEvent_type_field_number, + ElementsAre(VarIntField( + perfetto_protos_TrackEvent_TYPE_SLICE_END))))); + IdFieldView debug_annot_fields( + track_event.front(), + perfetto_protos_TrackEvent_debug_annotations_field_number); + ASSERT_THAT(debug_annot_fields, ElementsAre()); + it++; + break; + } +} + +TEST_F(SharedLibTrackEventTest, TrackEventHlProtoFieldString) { + TracingSession tracing_session = TracingSession::Builder() + .set_data_source_name("track_event") + .add_enabled_category("*") + .Build(); + + PERFETTO_TE( + cat1, PERFETTO_TE_INSTANT("event"), + PERFETTO_TE_PROTO_FIELDS(PERFETTO_TE_PROTO_FIELD_NESTED( + perfetto_protos_TrackEvent_debug_annotations_field_number, + PERFETTO_TE_PROTO_FIELD_CSTR( + perfetto_protos_DebugAnnotation_name_field_number, "name"), + PERFETTO_TE_PROTO_FIELD_VARINT( + perfetto_protos_DebugAnnotation_uint_value_field_number, 42)))); + + tracing_session.StopBlocking(); + std::vector data = tracing_session.ReadBlocking(); + EXPECT_THAT( + FieldView(data), + Contains(PbField( + perfetto_protos_Trace_packet_field_number, + AllFieldsWithId( + perfetto_protos_TracePacket_track_event_field_number, + ElementsAre(AllFieldsWithId( + perfetto_protos_TrackEvent_debug_annotations_field_number, + ElementsAre(MsgField(UnorderedElementsAre( + PbField(perfetto_protos_DebugAnnotation_name_field_number, + StringField("name")), + PbField( + perfetto_protos_DebugAnnotation_uint_value_field_number, + VarIntField(42))))))))))); +} + } // namespace diff --git a/src/shared_lib/test/benchmark.cc b/src/shared_lib/test/benchmark.cc index 1b02f33085..5d2a2fb724 100644 --- a/src/shared_lib/test/benchmark.cc +++ b/src/shared_lib/test/benchmark.cc @@ -152,8 +152,10 @@ void BM_Shlib_TeDisabled(benchmark::State& state) { void BM_Shlib_TeBasic(benchmark::State& state) { EnsureInitialized(); - TracingSession tracing_session = - TracingSession::Builder().set_data_source_name("track_event").Build(); + TracingSession tracing_session = TracingSession::Builder() + .set_data_source_name("track_event") + .add_enabled_category("*") + .Build(); while (state.KeepRunning()) { PERFETTO_TE(benchmark_cat, PERFETTO_TE_SLICE_BEGIN("Event")); @@ -163,8 +165,10 @@ void BM_Shlib_TeBasic(benchmark::State& state) { void BM_Shlib_TeBasicNoIntern(benchmark::State& state) { EnsureInitialized(); - TracingSession tracing_session = - TracingSession::Builder().set_data_source_name("track_event").Build(); + TracingSession tracing_session = TracingSession::Builder() + .set_data_source_name("track_event") + .add_enabled_category("*") + .Build(); while (state.KeepRunning()) { PERFETTO_TE(benchmark_cat, PERFETTO_TE_SLICE_BEGIN("Event"), @@ -175,8 +179,10 @@ void BM_Shlib_TeBasicNoIntern(benchmark::State& state) { void BM_Shlib_TeDebugAnnotations(benchmark::State& state) { EnsureInitialized(); - TracingSession tracing_session = - TracingSession::Builder().set_data_source_name("track_event").Build(); + TracingSession tracing_session = TracingSession::Builder() + .set_data_source_name("track_event") + .add_enabled_category("*") + .Build(); while (state.KeepRunning()) { PERFETTO_TE(benchmark_cat, PERFETTO_TE_SLICE_BEGIN("Event"), @@ -185,10 +191,32 @@ void BM_Shlib_TeDebugAnnotations(benchmark::State& state) { } } +void BM_Shlib_TeCustomProto(benchmark::State& state) { + EnsureInitialized(); + TracingSession tracing_session = TracingSession::Builder() + .set_data_source_name("track_event") + .add_enabled_category("*") + .Build(); + + while (state.KeepRunning()) { + PERFETTO_TE( + benchmark_cat, PERFETTO_TE_SLICE_BEGIN("Event"), + PERFETTO_TE_PROTO_FIELDS(PERFETTO_TE_PROTO_FIELD_NESTED( + perfetto_protos_TrackEvent_debug_annotations_field_number, + PERFETTO_TE_PROTO_FIELD_CSTR( + perfetto_protos_DebugAnnotation_name_field_number, "value"), + PERFETTO_TE_PROTO_FIELD_VARINT( + perfetto_protos_DebugAnnotation_uint_value_field_number, 42)))); + benchmark::ClobberMemory(); + } +} + void BM_Shlib_TeLlBasic(benchmark::State& state) { EnsureInitialized(); - TracingSession tracing_session = - TracingSession::Builder().set_data_source_name("track_event").Build(); + TracingSession tracing_session = TracingSession::Builder() + .set_data_source_name("track_event") + .add_enabled_category("*") + .Build(); while (state.KeepRunning()) { if (PERFETTO_UNLIKELY(PERFETTO_ATOMIC_LOAD_EXPLICIT( @@ -239,8 +267,10 @@ void BM_Shlib_TeLlBasic(benchmark::State& state) { void BM_Shlib_TeLlBasicNoIntern(benchmark::State& state) { EnsureInitialized(); - TracingSession tracing_session = - TracingSession::Builder().set_data_source_name("track_event").Build(); + TracingSession tracing_session = TracingSession::Builder() + .set_data_source_name("track_event") + .add_enabled_category("*") + .Build(); while (state.KeepRunning()) { if (PERFETTO_UNLIKELY(PERFETTO_ATOMIC_LOAD_EXPLICIT( @@ -288,8 +318,10 @@ void BM_Shlib_TeLlBasicNoIntern(benchmark::State& state) { void BM_Shlib_TeLlDebugAnnotations(benchmark::State& state) { EnsureInitialized(); - TracingSession tracing_session = - TracingSession::Builder().set_data_source_name("track_event").Build(); + TracingSession tracing_session = TracingSession::Builder() + .set_data_source_name("track_event") + .add_enabled_category("*") + .Build(); while (state.KeepRunning()) { if (PERFETTO_UNLIKELY(PERFETTO_ATOMIC_LOAD_EXPLICIT( @@ -349,6 +381,68 @@ void BM_Shlib_TeLlDebugAnnotations(benchmark::State& state) { } } +void BM_Shlib_TeLlCustomProto(benchmark::State& state) { + EnsureInitialized(); + TracingSession tracing_session = TracingSession::Builder() + .set_data_source_name("track_event") + .add_enabled_category("*") + .Build(); + + while (state.KeepRunning()) { + if (PERFETTO_UNLIKELY(PERFETTO_ATOMIC_LOAD_EXPLICIT( + benchmark_cat.enabled, PERFETTO_MEMORY_ORDER_RELAXED))) { + struct PerfettoTeTimestamp timestamp = PerfettoTeGetTimestamp(); + int32_t type = PERFETTO_TE_TYPE_SLICE_BEGIN; + const char* name = "Event"; + for (struct PerfettoTeLlIterator ctx = + PerfettoTeLlBeginSlowPath(&benchmark_cat, timestamp); + ctx.impl.ds.tracer != nullptr; + PerfettoTeLlNext(&benchmark_cat, timestamp, &ctx)) { + uint64_t name_iid; + { + struct PerfettoDsRootTracePacket trace_packet; + PerfettoTeLlPacketBegin(&ctx, &trace_packet); + PerfettoTeLlWriteTimestamp(&trace_packet.msg, ×tamp); + perfetto_protos_TracePacket_set_sequence_flags( + &trace_packet.msg, + perfetto_protos_TracePacket_SEQ_NEEDS_INCREMENTAL_STATE); + { + struct PerfettoTeLlInternContext intern_ctx; + PerfettoTeLlInternContextInit(&intern_ctx, ctx.impl.incr, + &trace_packet.msg); + PerfettoTeLlInternRegisteredCat(&intern_ctx, &benchmark_cat); + name_iid = PerfettoTeLlInternEventName(&intern_ctx, name); + PerfettoTeLlInternContextDestroy(&intern_ctx); + } + { + struct perfetto_protos_TrackEvent te_msg; + perfetto_protos_TracePacket_begin_track_event(&trace_packet.msg, + &te_msg); + perfetto_protos_TrackEvent_set_type( + &te_msg, + static_cast(type)); + PerfettoTeLlWriteRegisteredCat(&te_msg, &benchmark_cat); + PerfettoTeLlWriteInternedEventName(&te_msg, name_iid); + { + struct perfetto_protos_DebugAnnotation dbg_arg; + perfetto_protos_TrackEvent_begin_debug_annotations(&te_msg, + &dbg_arg); + perfetto_protos_DebugAnnotation_set_cstr_name(&dbg_arg, "value"); + perfetto_protos_DebugAnnotation_set_uint_value(&dbg_arg, 42); + perfetto_protos_TrackEvent_end_debug_annotations(&te_msg, + &dbg_arg); + } + perfetto_protos_TracePacket_end_track_event(&trace_packet.msg, + &te_msg); + } + PerfettoTeLlPacketEnd(&ctx, &trace_packet); + } + } + } + benchmark::ClobberMemory(); + } +} + } // namespace BENCHMARK(BM_Shlib_DataSource_Disabled); @@ -357,6 +451,8 @@ BENCHMARK(BM_Shlib_TeDisabled); BENCHMARK(BM_Shlib_TeBasic); BENCHMARK(BM_Shlib_TeBasicNoIntern); BENCHMARK(BM_Shlib_TeDebugAnnotations); +BENCHMARK(BM_Shlib_TeCustomProto); BENCHMARK(BM_Shlib_TeLlBasic); BENCHMARK(BM_Shlib_TeLlBasicNoIntern); BENCHMARK(BM_Shlib_TeLlDebugAnnotations); +BENCHMARK(BM_Shlib_TeLlCustomProto); diff --git a/src/shared_lib/test/protos/BUILD.gn b/src/shared_lib/test/protos/BUILD.gn index 434f4a5218..21fdeafec3 100644 --- a/src/shared_lib/test/protos/BUILD.gn +++ b/src/shared_lib/test/protos/BUILD.gn @@ -15,6 +15,7 @@ source_set("protos") { testonly = true sources = [ + "extensions.pzc.h", "library.pzc.h", "library_internals/galaxies.pzc.h", "test_messages.pzc.h", diff --git a/src/shared_lib/test/protos/extensions.pzc.h b/src/shared_lib/test/protos/extensions.pzc.h new file mode 100644 index 0000000000..996a11a386 --- /dev/null +++ b/src/shared_lib/test/protos/extensions.pzc.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Autogenerated by the ProtoZero C compiler plugin. +// Invoked by tools/gen_c_protos +// DO NOT EDIT. +#ifndef SRC_SHARED_LIB_TEST_PROTOS_EXTENSIONS_PZC_H_ +#define SRC_SHARED_LIB_TEST_PROTOS_EXTENSIONS_PZC_H_ + +#include +#include + +#include "perfetto/public/pb_macros.h" + +PERFETTO_PB_MSG(protozero_test_protos_SystemB); +PERFETTO_PB_FIELD(protozero_test_protos_SystemB, VARINT, uint32_t, int_b, 1); +PERFETTO_PB_FIELD(protozero_test_protos_SystemB, + STRING, + const char*, + string_b, + 2); + +PERFETTO_PB_MSG(protozero_test_protos_SystemA); +PERFETTO_PB_FIELD(protozero_test_protos_SystemA, VARINT, uint32_t, int_a, 1); +PERFETTO_PB_FIELD(protozero_test_protos_SystemA, + STRING, + const char*, + string_a, + 2); + +PERFETTO_PB_MSG(protozero_test_protos_RealFakeEvent); +PERFETTO_PB_FIELD(protozero_test_protos_RealFakeEvent, + VARINT, + uint32_t, + base_int, + 1); +PERFETTO_PB_FIELD(protozero_test_protos_RealFakeEvent, + STRING, + const char*, + base_string, + 2); + +PERFETTO_PB_EXTENSION_FIELD(protozero_test_protos_BrowserExtension, + protozero_test_protos_RealFakeEvent, + MSG, + protozero_test_protos_SystemA, + extension_a, + 10); +PERFETTO_PB_EXTENSION_FIELD(protozero_test_protos_BrowserExtension, + protozero_test_protos_RealFakeEvent, + MSG, + protozero_test_protos_SystemB, + extension_b, + 11); +#endif // SRC_SHARED_LIB_TEST_PROTOS_EXTENSIONS_PZC_H_ diff --git a/src/shared_lib/track_event.cc b/src/shared_lib/track_event.cc index bbafde73db..e3115ba2dc 100644 --- a/src/shared_lib/track_event.cc +++ b/src/shared_lib/track_event.cc @@ -634,12 +634,63 @@ void ResetIncrementalStateIfRequired( } } +// Appends the fields described by `fields` to `msg`. +void AppendHlProtoFields(protozero::Message* msg, + PerfettoTeHlProtoField* const* fields) { + for (PerfettoTeHlProtoField* const* p = fields; *p != nullptr; p++) { + switch ((*p)->type) { + case PERFETTO_TE_HL_PROTO_TYPE_CSTR: { + auto field = reinterpret_cast(*p); + msg->AppendString(field->header.id, field->str); + break; + } + case PERFETTO_TE_HL_PROTO_TYPE_BYTES: { + auto field = reinterpret_cast(*p); + msg->AppendBytes(field->header.id, field->buf, field->len); + break; + } + case PERFETTO_TE_HL_PROTO_TYPE_NESTED: { + auto field = reinterpret_cast(*p); + auto* nested = + msg->BeginNestedMessage(field->header.id); + AppendHlProtoFields(nested, field->fields); + break; + } + case PERFETTO_TE_HL_PROTO_TYPE_VARINT: { + auto field = reinterpret_cast(*p); + msg->AppendVarInt(field->header.id, field->value); + break; + } + case PERFETTO_TE_HL_PROTO_TYPE_FIXED64: { + auto field = reinterpret_cast(*p); + msg->AppendFixed(field->header.id, field->value); + break; + } + case PERFETTO_TE_HL_PROTO_TYPE_FIXED32: { + auto field = reinterpret_cast(*p); + msg->AppendFixed(field->header.id, field->value); + break; + } + case PERFETTO_TE_HL_PROTO_TYPE_DOUBLE: { + auto field = reinterpret_cast(*p); + msg->AppendFixed(field->header.id, field->value); + break; + } + case PERFETTO_TE_HL_PROTO_TYPE_FLOAT: { + auto field = reinterpret_cast(*p); + msg->AppendFixed(field->header.id, field->value); + break; + } + } + } +} + void WriteTrackEvent(perfetto::shlib::TrackEventIncrementalState* incr, perfetto::protos::pbzero::TrackEvent* event, PerfettoTeCategoryImpl* cat, perfetto::protos::pbzero::TrackEvent::Type type, const char* name, - const PerfettoTeHlExtra* extra_data, + const PerfettoTeHlExtra* const* extra_data, std::optional track_uuid, const PerfettoTeCategoryDescriptor* dynamic_cat, bool use_interning) { @@ -692,59 +743,66 @@ void WriteTrackEvent(perfetto::shlib::TrackEventIncrementalState* incr, event->set_track_uuid(*track_uuid); } - for (const auto* it = extra_data; it; it = it->next) { - if (it->type == PERFETTO_TE_HL_EXTRA_TYPE_COUNTER_INT64 && + for (const auto* it = extra_data; *it != nullptr; it++) { + const struct PerfettoTeHlExtra& extra = **it; + if (extra.type == PERFETTO_TE_HL_EXTRA_TYPE_COUNTER_INT64 && type == perfetto::protos::pbzero::TrackEvent::TYPE_COUNTER) { event->set_counter_value( - reinterpret_cast(it) - ->value); - } else if (it->type == PERFETTO_TE_HL_EXTRA_TYPE_COUNTER_DOUBLE) { + reinterpret_cast(extra) + .value); + } else if (extra.type == PERFETTO_TE_HL_EXTRA_TYPE_COUNTER_DOUBLE) { event->set_double_counter_value( - reinterpret_cast(it) - ->value); + reinterpret_cast(extra) + .value); } } - for (const auto* it = extra_data; it; it = it->next) { - if (it->type == PERFETTO_TE_HL_EXTRA_TYPE_DEBUG_ARG_BOOL || - it->type == PERFETTO_TE_HL_EXTRA_TYPE_DEBUG_ARG_UINT64 || - it->type == PERFETTO_TE_HL_EXTRA_TYPE_DEBUG_ARG_INT64 || - it->type == PERFETTO_TE_HL_EXTRA_TYPE_DEBUG_ARG_DOUBLE || - it->type == PERFETTO_TE_HL_EXTRA_TYPE_DEBUG_ARG_STRING || - it->type == PERFETTO_TE_HL_EXTRA_TYPE_DEBUG_ARG_POINTER) { + for (const auto* it = extra_data; *it != nullptr; it++) { + const struct PerfettoTeHlExtra& extra = **it; + if (extra.type == PERFETTO_TE_HL_EXTRA_TYPE_DEBUG_ARG_BOOL || + extra.type == PERFETTO_TE_HL_EXTRA_TYPE_DEBUG_ARG_UINT64 || + extra.type == PERFETTO_TE_HL_EXTRA_TYPE_DEBUG_ARG_INT64 || + extra.type == PERFETTO_TE_HL_EXTRA_TYPE_DEBUG_ARG_DOUBLE || + extra.type == PERFETTO_TE_HL_EXTRA_TYPE_DEBUG_ARG_STRING || + extra.type == PERFETTO_TE_HL_EXTRA_TYPE_DEBUG_ARG_POINTER) { auto* dbg = event->add_debug_annotations(); const char* arg_name = nullptr; - if (it->type == PERFETTO_TE_HL_EXTRA_TYPE_DEBUG_ARG_BOOL) { - auto* arg = - reinterpret_cast(it); - dbg->set_bool_value(arg->value); - arg_name = arg->name; - } else if (it->type == PERFETTO_TE_HL_EXTRA_TYPE_DEBUG_ARG_UINT64) { - auto* arg = - reinterpret_cast(it); - dbg->set_uint_value(arg->value); - arg_name = arg->name; - } else if (it->type == PERFETTO_TE_HL_EXTRA_TYPE_DEBUG_ARG_INT64) { - auto* arg = - reinterpret_cast(it); - dbg->set_int_value(arg->value); - arg_name = arg->name; - } else if (it->type == PERFETTO_TE_HL_EXTRA_TYPE_DEBUG_ARG_DOUBLE) { - auto* arg = - reinterpret_cast(it); - dbg->set_double_value(arg->value); - arg_name = arg->name; - } else if (it->type == PERFETTO_TE_HL_EXTRA_TYPE_DEBUG_ARG_STRING) { - auto* arg = - reinterpret_cast(it); - dbg->set_string_value(arg->value); - arg_name = arg->name; - } else if (it->type == PERFETTO_TE_HL_EXTRA_TYPE_DEBUG_ARG_POINTER) { - auto* arg = - reinterpret_cast( - it); - dbg->set_pointer_value(arg->value); - arg_name = arg->name; + if (extra.type == PERFETTO_TE_HL_EXTRA_TYPE_DEBUG_ARG_BOOL) { + const auto& arg = + reinterpret_cast( + extra); + dbg->set_bool_value(arg.value); + arg_name = arg.name; + } else if (extra.type == PERFETTO_TE_HL_EXTRA_TYPE_DEBUG_ARG_UINT64) { + const auto& arg = + reinterpret_cast( + extra); + dbg->set_uint_value(arg.value); + arg_name = arg.name; + } else if (extra.type == PERFETTO_TE_HL_EXTRA_TYPE_DEBUG_ARG_INT64) { + const auto& arg = + reinterpret_cast( + extra); + dbg->set_int_value(arg.value); + arg_name = arg.name; + } else if (extra.type == PERFETTO_TE_HL_EXTRA_TYPE_DEBUG_ARG_DOUBLE) { + const auto& arg = + reinterpret_cast( + extra); + dbg->set_double_value(arg.value); + arg_name = arg.name; + } else if (extra.type == PERFETTO_TE_HL_EXTRA_TYPE_DEBUG_ARG_STRING) { + const auto& arg = + reinterpret_cast( + extra); + dbg->set_string_value(arg.value); + arg_name = arg.name; + } else if (extra.type == PERFETTO_TE_HL_EXTRA_TYPE_DEBUG_ARG_POINTER) { + const auto& arg = + reinterpret_cast( + extra); + dbg->set_pointer_value(arg.value); + arg_name = arg.name; } if (arg_name != nullptr) { @@ -765,17 +823,29 @@ void WriteTrackEvent(perfetto::shlib::TrackEventIncrementalState* incr, } } - for (const auto* it = extra_data; it; it = it->next) { - if (it->type == PERFETTO_TE_HL_EXTRA_TYPE_FLOW) { + for (const auto* it = extra_data; *it != nullptr; it++) { + const struct PerfettoTeHlExtra& extra = **it; + if (extra.type == PERFETTO_TE_HL_EXTRA_TYPE_FLOW) { event->add_flow_ids( - reinterpret_cast(it)->id); + reinterpret_cast(extra).id); } } - for (const auto* it = extra_data; it; it = it->next) { - if (it->type == PERFETTO_TE_HL_EXTRA_TYPE_TERMINATING_FLOW) { + for (const auto* it = extra_data; *it != nullptr; it++) { + const struct PerfettoTeHlExtra& extra = **it; + if (extra.type == PERFETTO_TE_HL_EXTRA_TYPE_TERMINATING_FLOW) { event->add_terminating_flow_ids( - reinterpret_cast(it)->id); + reinterpret_cast(extra).id); + } + } + + for (const auto* it = extra_data; *it != nullptr; it++) { + const struct PerfettoTeHlExtra& extra = **it; + if (extra.type == PERFETTO_TE_HL_EXTRA_TYPE_PROTO_FIELDS) { + const auto* fields = + reinterpret_cast(extra) + .fields; + AppendHlProtoFields(event, fields); } } } @@ -887,7 +957,7 @@ static void InstanceOp( struct PerfettoTeCategoryImpl* cat, perfetto::protos::pbzero::TrackEvent::Type type, const char* name, - const struct PerfettoTeHlExtra* extra_data) { + struct PerfettoTeHlExtra* const* extra_data) { if (!ii->instance) { return; } @@ -901,34 +971,40 @@ static void InstanceOp( std::optional int_counter; std::optional double_counter; bool use_interning = true; + bool flush = false; - for (const auto* it = extra_data; it; it = it->next) { - if (it->type == PERFETTO_TE_HL_EXTRA_TYPE_REGISTERED_TRACK) { - auto* cast = - reinterpret_cast(it); - registered_track = cast->track; + for (const auto* it = extra_data; *it != nullptr; it++) { + const struct PerfettoTeHlExtra& extra = **it; + if (extra.type == PERFETTO_TE_HL_EXTRA_TYPE_REGISTERED_TRACK) { + const auto& cast = + reinterpret_cast( + extra); + registered_track = cast.track; named_track = nullptr; - } else if (it->type == PERFETTO_TE_HL_EXTRA_TYPE_NAMED_TRACK) { + } else if (extra.type == PERFETTO_TE_HL_EXTRA_TYPE_NAMED_TRACK) { registered_track = nullptr; named_track = - reinterpret_cast(it); - } else if (it->type == PERFETTO_TE_HL_EXTRA_TYPE_TIMESTAMP) { + &reinterpret_cast(extra); + } else if (extra.type == PERFETTO_TE_HL_EXTRA_TYPE_TIMESTAMP) { custom_timestamp = - reinterpret_cast(it); - } else if (it->type == PERFETTO_TE_HL_EXTRA_TYPE_DYNAMIC_CATEGORY) { + &reinterpret_cast(extra); + } else if (extra.type == PERFETTO_TE_HL_EXTRA_TYPE_DYNAMIC_CATEGORY) { dynamic_cat = - reinterpret_cast(it) - ->desc; - } else if (it->type == PERFETTO_TE_HL_EXTRA_TYPE_COUNTER_INT64) { + reinterpret_cast( + extra) + .desc; + } else if (extra.type == PERFETTO_TE_HL_EXTRA_TYPE_COUNTER_INT64) { int_counter = - reinterpret_cast(it) - ->value; - } else if (it->type == PERFETTO_TE_HL_EXTRA_TYPE_COUNTER_DOUBLE) { + reinterpret_cast(extra) + .value; + } else if (extra.type == PERFETTO_TE_HL_EXTRA_TYPE_COUNTER_DOUBLE) { double_counter = - reinterpret_cast(it) - ->value; - } else if (it->type == PERFETTO_TE_HL_EXTRA_TYPE_NO_INTERN) { + reinterpret_cast(extra) + .value; + } else if (extra.type == PERFETTO_TE_HL_EXTRA_TYPE_NO_INTERN) { use_interning = false; + } else if (extra.type == PERFETTO_TE_HL_EXTRA_TYPE_FLUSH) { + flush = true; } } @@ -999,12 +1075,6 @@ static void InstanceOp( } } - bool flush = false; - for (const auto* it = extra_data; it; it = it->next) { - if (it->type == PERFETTO_TE_HL_EXTRA_TYPE_FLUSH) { - flush = true; - } - } if (PERFETTO_UNLIKELY(flush)) { trace_writer->Flush(); } @@ -1013,7 +1083,7 @@ static void InstanceOp( void PerfettoTeHlEmitImpl(struct PerfettoTeCategoryImpl* cat, int32_t type, const char* name, - const struct PerfettoTeHlExtra* extra_data) { + struct PerfettoTeHlExtra* const* extra_data) { uint32_t cached_instances = perfetto::shlib::TracePointTraits::GetActiveInstances({cat})->load( std::memory_order_relaxed); diff --git a/src/tools/ftrace_proto_gen/event_list b/src/tools/ftrace_proto_gen/event_list index a019827768..8d1463c11e 100644 --- a/src/tools/ftrace_proto_gen/event_list +++ b/src/tools/ftrace_proto_gen/event_list @@ -502,3 +502,29 @@ power/device_pm_callback_start power/device_pm_callback_end thermal_exynos/thermal_exynos_acpm_bulk thermal_exynos/thermal_exynos_acpm_high_overhead +dcvsh/dcvsh_freq +kgsl/gpu_frequency +mali/mali_PM_MCU_HCTL_CORES_DOWN_SCALE_NOTIFY_PEND +mali/mali_PM_MCU_HCTL_CORES_NOTIFY_PEND +mali/mali_PM_MCU_HCTL_CORE_INACTIVE_PEND +mali/mali_PM_MCU_HCTL_MCU_ON_RECHECK +mali/mali_PM_MCU_HCTL_SHADERS_CORE_OFF_PEND +mali/mali_PM_MCU_HCTL_SHADERS_PEND_OFF +mali/mali_PM_MCU_HCTL_SHADERS_PEND_ON +mali/mali_PM_MCU_HCTL_SHADERS_READY_OFF +mali/mali_PM_MCU_IN_SLEEP +mali/mali_PM_MCU_OFF +mali/mali_PM_MCU_ON +mali/mali_PM_MCU_ON_CORE_ATTR_UPDATE_PEND +mali/mali_PM_MCU_ON_GLB_REINIT_PEND +mali/mali_PM_MCU_ON_HALT +mali/mali_PM_MCU_ON_HWCNT_DISABLE +mali/mali_PM_MCU_ON_HWCNT_ENABLE +mali/mali_PM_MCU_ON_PEND_HALT +mali/mali_PM_MCU_ON_PEND_SLEEP +mali/mali_PM_MCU_ON_SLEEP_INITIATE +mali/mali_PM_MCU_PEND_OFF +mali/mali_PM_MCU_PEND_ON_RELOAD +mali/mali_PM_MCU_POWER_DOWN +mali/mali_PM_MCU_RESET_WAIT +bcl_exynos/bcl_irq_trigger diff --git a/src/tools/ftrace_proto_gen/ftrace_proto_gen.cc b/src/tools/ftrace_proto_gen/ftrace_proto_gen.cc index 789d5bff75..36e8726b58 100644 --- a/src/tools/ftrace_proto_gen/ftrace_proto_gen.cc +++ b/src/tools/ftrace_proto_gen/ftrace_proto_gen.cc @@ -59,7 +59,8 @@ std::string EventNameToProtoFieldName(const std::string& group, // These groups have events where the name alone conflicts with an existing // proto: if (group == "sde" || group == "g2d" || group == "dpu" || group == "mali" || - group == "lwis" || group == "samsung") { + group == "lwis" || group == "samsung" || + (group == "kgsl" && name == "gpu_frequency")) { event_name = group + "_" + event_name; } return event_name; diff --git a/src/trace_processor/BUILD.gn b/src/trace_processor/BUILD.gn index 84c09ce23a..bddffc0a43 100644 --- a/src/trace_processor/BUILD.gn +++ b/src/trace_processor/BUILD.gn @@ -109,6 +109,8 @@ source_set("storage_minimal") { "trace_processor_storage.cc", "trace_processor_storage_impl.cc", "trace_processor_storage_impl.h", + "trace_reader_registry.cc", + "trace_reader_registry.h", "virtual_destructors.cc", ] deps = [ @@ -121,6 +123,8 @@ source_set("storage_minimal") { "importers/ftrace:minimal", "importers/fuchsia:fuchsia_record", "importers/memory_tracker:graph_processor", + "importers/perf:tracker", + "importers/proto:gen_cc_android_track_event_descriptor", "importers/proto:gen_cc_chrome_track_event_descriptor", "importers/proto:gen_cc_track_event_descriptor", "importers/proto:minimal", @@ -132,6 +136,7 @@ source_set("storage_minimal") { "util:descriptors", "util:gzip", "util:proto_to_args_parser", + "util:trace_type", ] public_deps = [ "../../include/perfetto/trace_processor:storage" ] } @@ -173,6 +178,7 @@ if (enable_perfetto_trace_processor_sqlite) { "importers/proto:full", "importers/proto:minimal", "importers/systrace:full", + "importers/zip:full", "metrics", "perfetto_sql/engine", "perfetto_sql/intrinsics/functions", @@ -190,6 +196,7 @@ if (enable_perfetto_trace_processor_sqlite) { "util:protozero_to_text", "util:regex", "util:stdlib", + "util:trace_type", ] public_deps = [ "../../gn:sqlite", # iterator_impl.h includes sqlite3.h. @@ -247,6 +254,7 @@ perfetto_unittest_source_set("top_level_unittests") { "../../gn:default_deps", "../../gn:gtest_and_gmock", "../../include/perfetto/trace_processor", + "util:trace_type", ] if (enable_perfetto_trace_processor_json && !is_win) { diff --git a/src/trace_processor/containers/bit_vector.cc b/src/trace_processor/containers/bit_vector.cc index 01c5d0834b..28fe96669a 100644 --- a/src/trace_processor/containers/bit_vector.cc +++ b/src/trace_processor/containers/bit_vector.cc @@ -437,6 +437,32 @@ BitVector BitVector::FromSortedIndexVector( return {words, counts, size}; } +BitVector BitVector::FromUnsortedIndexVector( + const std::vector& indices) { + // The rest of the algorithm depends on |indices| being non empty. + if (indices.empty()) { + return {}; + } + + std::vector words; + uint32_t max_idx = 0; + for (const uint32_t i : indices) { + auto word_idx = static_cast(i / kBitsInWord); + max_idx = std::max(max_idx, i); + if (word_idx >= words.size()) { + words.resize(word_idx + 1); + } + auto in_word_idx = static_cast(i % kBitsInWord); + BitVector::BitWord(&words[word_idx]).Set(in_word_idx); + } + + auto block_count = BlockCount(max_idx + 1); + words.resize(block_count * Block::kWords); + std::vector counts(block_count); + UpdateCounts(words, counts); + return {words, counts, max_idx + 1}; +} + BitVector BitVector::IntersectRange(uint32_t range_start, uint32_t range_end) const { // We should skip all bits until the index of first set bit bigger than diff --git a/src/trace_processor/containers/bit_vector.h b/src/trace_processor/containers/bit_vector.h index bcbf1cef47..00cec5952a 100644 --- a/src/trace_processor/containers/bit_vector.h +++ b/src/trace_processor/containers/bit_vector.h @@ -353,6 +353,11 @@ class BitVector { PERFETTO_WARN_UNUSED_RESULT static BitVector FromSortedIndexVector( const std::vector&); + // Creates BitVector from a vector of unsorted indices. Set bits in the + // resulting BitVector are values from the index vector. + PERFETTO_WARN_UNUSED_RESULT static BitVector FromUnsortedIndexVector( + const std::vector&); + // Creates a BitVector of size `min(range_end, size())` with bits between // |start| and |end| filled with corresponding bits from |this| BitVector. PERFETTO_WARN_UNUSED_RESULT BitVector diff --git a/src/trace_processor/containers/bit_vector_unittest.cc b/src/trace_processor/containers/bit_vector_unittest.cc index d6487bfaa2..f5c6d1ca2b 100644 --- a/src/trace_processor/containers/bit_vector_unittest.cc +++ b/src/trace_processor/containers/bit_vector_unittest.cc @@ -633,14 +633,14 @@ TEST(BitVectorUnittest, BuilderStressTest) { ASSERT_TRUE(bv.IsSet(8 * 1024)); } -TEST(BitVectorUnittest, FromIndexVectorEmpty) { +TEST(BitVectorUnittest, FromSortedIndexVectorEmpty) { std::vector indices{}; BitVector bv = BitVector::FromSortedIndexVector(indices); ASSERT_EQ(bv.size(), 0u); } -TEST(BitVectorUnittest, FromIndexVector) { +TEST(BitVectorUnittest, FromSortedIndexVector) { std::vector indices{0, 100, 200, 2000}; BitVector bv = BitVector::FromSortedIndexVector(indices); @@ -652,7 +652,7 @@ TEST(BitVectorUnittest, FromIndexVector) { ASSERT_TRUE(bv.IsSet(2000)); } -TEST(BitVectorUnittest, FromIndexVectorStressTestLargeValues) { +TEST(BitVectorUnittest, FromSortedIndexVectorStressTestLargeValues) { std::vector indices{0, 1 << 2, 1 << 10, 1 << 20, 1 << 30}; BitVector bv = BitVector::FromSortedIndexVector(indices); @@ -665,6 +665,38 @@ TEST(BitVectorUnittest, FromIndexVectorStressTestLargeValues) { ASSERT_TRUE(bv.IsSet(1 << 30)); } +TEST(BitVectorUnittest, FromUnsortedIndexVectorEmpty) { + std::vector indices{}; + BitVector bv = BitVector::FromUnsortedIndexVector(indices); + + ASSERT_EQ(bv.size(), 0u); +} + +TEST(BitVectorUnittest, FromUnsortedIndexVector) { + std::vector indices{0, 2000, 200, 100}; + BitVector bv = BitVector::FromUnsortedIndexVector(indices); + + ASSERT_EQ(bv.size(), 2001u); + ASSERT_EQ(bv.CountSetBits(), 4u); + ASSERT_TRUE(bv.IsSet(0)); + ASSERT_TRUE(bv.IsSet(100)); + ASSERT_TRUE(bv.IsSet(200)); + ASSERT_TRUE(bv.IsSet(2000)); +} + +TEST(BitVectorUnittest, FromUnsortedIndexVectorStressTestLargeValues) { + std::vector indices{0, 1 << 30, 1 << 10, 1 << 2, 1 << 20}; + BitVector bv = BitVector::FromUnsortedIndexVector(indices); + + ASSERT_EQ(bv.size(), (1 << 30) + 1u); + ASSERT_EQ(bv.CountSetBits(), 5u); + ASSERT_TRUE(bv.IsSet(0)); + ASSERT_TRUE(bv.IsSet(1 << 2)); + ASSERT_TRUE(bv.IsSet(1 << 10)); + ASSERT_TRUE(bv.IsSet(1 << 20)); + ASSERT_TRUE(bv.IsSet(1 << 30)); +} + TEST(BitVectorUnittest, Not) { BitVector bv(10); bv.Set(2); diff --git a/src/trace_processor/containers/interval_tree.h b/src/trace_processor/containers/interval_tree.h index c083da053a..f21266a6a7 100644 --- a/src/trace_processor/containers/interval_tree.h +++ b/src/trace_processor/containers/interval_tree.h @@ -17,117 +17,165 @@ #ifndef SRC_TRACE_PROCESSOR_CONTAINERS_INTERVAL_TREE_H_ #define SRC_TRACE_PROCESSOR_CONTAINERS_INTERVAL_TREE_H_ +#include +#include #include #include -#include +#include #include +#include "perfetto/base/logging.h" +#include "perfetto/ext/base/small_vector.h" + namespace perfetto::trace_processor { -// An implementation of an interval tree data structure, designed to efficiently -// perform overlap queries on a set of intervals. Used by `interval_intersect`, -// where one set of intervals (generally the bigger one) has interval tree -// created based on it, as another queries `FindOverlaps` function for each -// interval. -// As interval tree is build on sorted (by `start`) set of N intervals, the -// complexity of creating a tree goes down from O(N*logN) to O(N) and the -// created tree is optimally balanced. Each call to `FindOverlaps` is O(logN). +using Ts = uint64_t; +using Id = uint32_t; + +// An implementation of a centered interval tree data structure, designed to +// efficiently find all overlap queries on a set of intervals. Centered interval +// tree has a build complexity of O(N*logN) and a query time of O(logN + k), +// where k is the number of overlaps in the dataset. class IntervalTree { public: + // Maps to one trace processor slice. struct Interval { - uint32_t start; - uint32_t end; - uint32_t id; + Ts start; + Ts end; + Id id; }; - // Takes vector of sorted intervals. - explicit IntervalTree(std::vector& sorted_intervals) { - tree_root_ = BuildFromSortedIntervals( - sorted_intervals, 0, static_cast(sorted_intervals.size() - 1)); + // Creates an interval tree from the vector of intervals. + explicit IntervalTree(const std::vector& sorted_intervals) { + nodes_.reserve(sorted_intervals.size()); + Node root_node(sorted_intervals.data(), sorted_intervals.size(), nodes_); + nodes_.emplace_back(std::move(root_node)); + root_ = nodes_.size() - 1; } - // Modifies |overlaps| to contain ids of all intervals in the interval tree - // that overlap with |interval|. - void FindOverlaps(Interval interval, std::vector& overlaps) const { - if (tree_root_) { - FindOverlaps(*tree_root_, interval, overlaps); - } - } - - private: - struct Node { - Interval interval; - uint32_t max; - std::unique_ptr left; - std::unique_ptr right; - - explicit Node(Interval i) : interval(i), max(i.end) {} - }; - - static std::unique_ptr Insert(std::unique_ptr root, Interval i) { - if (root == nullptr) { - return std::make_unique(i); - } - - if (i.start < root->interval.start) { - root->left = Insert(std::move(root->left), i); - } else { - root->right = Insert(std::move(root->right), i); - } - - if (root->max < i.end) { - root->max = i.end; + // Modifies |res| to contain Interval::Id of all intervals that overlap + // interval (s, e). Has a complexity of O(log(size of tree) + (number of + // overlaps)). + void FindOverlaps(uint64_t s, uint64_t e, std::vector& res) const { + std::vector stack{nodes_.data() + root_}; + while (!stack.empty()) { + const Node* n = stack.back(); + stack.pop_back(); + + for (const Interval& i : n->intervals_) { + // As we know that each interval overlaps the center, if the interval + // starts after the |end| we know [start,end] can't intersect the + // center. + if (i.start > e) { + break; + } + + if (e > i.start && s < i.end) { + res.push_back(i.id); + } + } + + if (e > n->center_ && + n->right_node_ != std::numeric_limits::max()) { + stack.push_back(&nodes_[n->right_node_]); + } + if (s < n->center_ && + n->left_node_ != std::numeric_limits::max()) { + stack.push_back(&nodes_[n->left_node_]); + } } - - return root; } - static std::unique_ptr BuildFromSortedIntervals( - const std::vector& is, - int32_t start, - int32_t end) { - // |start == end| happens if there is one element so we need to check for - // |start > end| that happens in the next recursive call. - if (start > end) { - return nullptr; + // Modifies |res| to contain all overlaps (as Intervals) that overlap interval + // (s, e). Has a complexity of O(log(size of tree) + (number of overlaps)). + void FindOverlaps(Ts s, Ts e, std::vector& res) const { + std::vector stack{nodes_.data() + root_}; + while (!stack.empty()) { + const Node* n = stack.back(); + stack.pop_back(); + + for (const Interval& i : n->intervals_) { + // As we know that each interval overlaps the center, if the interval + // starts after the |end| we know [start,end] can't intersect the + // center. + if (i.start > e) { + break; + } + + if (e > i.start && s < i.end) { + Interval new_int; + new_int.start = std::max(s, i.start); + new_int.end = std::min(e, i.end); + new_int.id = i.id; + res.push_back(new_int); + } + } + + if (e > n->center_ && + n->right_node_ != std::numeric_limits::max()) { + stack.push_back(&nodes_[n->right_node_]); + } + if (s < n->center_ && + n->left_node_ != std::numeric_limits::max()) { + stack.push_back(&nodes_[n->left_node_]); + } } - - int32_t mid = start + (end - start) / 2; - auto node = std::make_unique(is[static_cast(mid)]); - - node->left = BuildFromSortedIntervals(is, start, mid - 1); - node->right = BuildFromSortedIntervals(is, mid + 1, end); - - uint32_t max_from_children = std::max( - node->left ? node->left->max : std::numeric_limits::min(), - node->right ? node->right->max : std::numeric_limits::min()); - - node->max = std::max(node->interval.end, max_from_children); - - return node; } - static void FindOverlaps(const Node& node, - const Interval& i, - std::vector& overlaps) { - // Intervals overlap if one starts before the other ends and ends after it - // starts. - if (node.interval.start < i.end && node.interval.end > i.start) { - overlaps.push_back(node.interval.id); + private: + struct Node { + base::SmallVector intervals_; + uint64_t center_ = 0; + size_t left_node_ = std::numeric_limits::max(); + size_t right_node_ = std::numeric_limits::max(); + + explicit Node(const Interval* intervals, + size_t intervals_size, + std::vector& nodes) { + const Interval& mid_interval = intervals[intervals_size / 2]; + center_ = (mid_interval.start + mid_interval.end) / 2; + + // Find intervals that overlap the center_ and intervals that belong to + // the left node (finish before the center_). If an interval starts after + // the center break and assign all remining intervals to the right node. + // We can do this as the provided intervals are in sorted order. + std::vector left; + for (uint32_t i = 0; i < intervals_size; i++) { + const Interval& inter = intervals[i]; + // Starts after the center. As intervals are sorted on timestamp we + // know the rest of intervals will go to the right node. + if (inter.start > center_) { + Node n(intervals + i, intervals_size - i, nodes); + nodes.emplace_back(std::move(n)); + right_node_ = nodes.size() - 1; + break; + } + + // Finishes before the center. + if (inter.end < center_) { + left.push_back(intervals[i]); + } else { + // Overlaps the center. + intervals_.emplace_back(intervals[i]); + } + } + + if (!left.empty()) { + Node n(left.data(), left.size(), nodes); + nodes.emplace_back(std::move(n)); + left_node_ = nodes.size() - 1; + } } - // Try to find overlaps with left. - if (i.start <= node.interval.start && node.left) { - FindOverlaps(*node.left, i, overlaps); - } + Node(const Node&) = delete; + Node& operator=(const Node&) = delete; - // Try to find overlaps with right. - if (i.start < node.max && node.right) { - FindOverlaps(*node.right, i, overlaps); - } - } + Node(Node&&) = default; + Node& operator=(Node&&) = default; + }; - std::unique_ptr tree_root_; + size_t root_; + std::vector nodes_; }; } // namespace perfetto::trace_processor diff --git a/src/trace_processor/containers/interval_tree_unittest.cc b/src/trace_processor/containers/interval_tree_unittest.cc index 61023d422d..ed4509b52d 100644 --- a/src/trace_processor/containers/interval_tree_unittest.cc +++ b/src/trace_processor/containers/interval_tree_unittest.cc @@ -54,7 +54,7 @@ TEST(IntervalTree, Trivial) { std::vector interval({{10, 20, 5}}); IntervalTree tree(interval); std::vector overlaps; - tree.FindOverlaps({5, 30, 0}, overlaps); + tree.FindOverlaps(5, 30, overlaps); ASSERT_THAT(overlaps, UnorderedElementsAre(5)); } @@ -63,7 +63,7 @@ TEST(IntervalTree, Simple) { auto intervals = CreateIntervals({{0, 10}, {5, 20}, {30, 40}}); IntervalTree tree(intervals); std::vector overlaps; - tree.FindOverlaps({4, 30, 0}, overlaps); + tree.FindOverlaps(4, 30, overlaps); ASSERT_THAT(overlaps, UnorderedElementsAre(0, 1)); } @@ -74,13 +74,13 @@ TEST(IntervalTree, SinglePointOverlap) { std::vector overlaps; // Overlaps at the start point only - tree.FindOverlaps({10, 10, 0}, overlaps); + tree.FindOverlaps(10, 10, overlaps); ASSERT_THAT(overlaps, IsEmpty()); overlaps.clear(); // Overlaps at the end point only - tree.FindOverlaps({20, 20, 0}, overlaps); + tree.FindOverlaps(20, 20, overlaps); ASSERT_THAT(overlaps, IsEmpty()); } @@ -90,17 +90,17 @@ TEST(IntervalTree, NoOverlaps) { std::vector overlaps; // Before all intervals - tree.FindOverlaps({5, 9, 0}, overlaps); + tree.FindOverlaps(5, 9, overlaps); ASSERT_THAT(overlaps, IsEmpty()); overlaps.clear(); // Between intervals - tree.FindOverlaps({21, 29, 0}, overlaps); + tree.FindOverlaps(21, 29, overlaps); ASSERT_THAT(overlaps, IsEmpty()); overlaps.clear(); // After all intervals - tree.FindOverlaps({41, 50, 0}, overlaps); + tree.FindOverlaps(41, 50, overlaps); ASSERT_THAT(overlaps, IsEmpty()); } @@ -108,7 +108,7 @@ TEST(IntervalTree, IdenticalIntervals) { auto intervals = CreateIntervals({{10, 20}, {10, 20}}); IntervalTree tree(intervals); std::vector overlaps; - tree.FindOverlaps({10, 20, 0}, overlaps); + tree.FindOverlaps(10, 20, overlaps); ASSERT_THAT(overlaps, UnorderedElementsAre(0, 1)); } @@ -118,17 +118,17 @@ TEST(IntervalTree, MultipleOverlapsVariousPositions) { std::vector overlaps; /// Starts before, ends within - tree.FindOverlaps({9, 11, 0}, overlaps); + tree.FindOverlaps(9, 11, overlaps); ASSERT_THAT(overlaps, UnorderedElementsAre(0, 1)); overlaps.clear(); // Starts within, ends within - tree.FindOverlaps({13, 21, 0}, overlaps); - ASSERT_THAT(overlaps, UnorderedElementsAre(1, 2)); + tree.FindOverlaps(13, 21, overlaps); + ASSERT_THAT(overlaps, UnorderedElementsAre(0, 1, 2)); overlaps.clear(); // Starts within, ends after - tree.FindOverlaps({18, 26, 0}, overlaps); + tree.FindOverlaps(18, 26, overlaps); ASSERT_THAT(overlaps, UnorderedElementsAre(1, 2, 3)); } @@ -137,7 +137,7 @@ TEST(IntervalTree, OverlappingEndpoints) { IntervalTree tree(intervals); std::vector overlaps; - tree.FindOverlaps({19, 21, 0}, overlaps); + tree.FindOverlaps(19, 21, overlaps); ASSERT_THAT(overlaps, UnorderedElementsAre(0, 1)); } @@ -153,10 +153,9 @@ TEST(IntervalTree, Stress) { {prev_max, prev_max + (static_cast(rng()) % 100)}); } auto intervals = CreateIntervals(periods); - Interval query_i{periods.front().first, periods.back().first + 1, 5}; IntervalTree tree(intervals); std::vector overlaps; - tree.FindOverlaps(query_i, overlaps); + tree.FindOverlaps(periods.front().first, periods.back().first + 1, overlaps); EXPECT_EQ(overlaps.size(), kCount); } diff --git a/src/trace_processor/containers/row_map.cc b/src/trace_processor/containers/row_map.cc index 3ae6ee72a2..5855b44e2f 100644 --- a/src/trace_processor/containers/row_map.cc +++ b/src/trace_processor/containers/row_map.cc @@ -153,11 +153,14 @@ RowMap Select(const std::vector& iv, return RowMap(row_map_algorithms::SelectIvWithIv(iv, selector)); } +// O(N), but 64 times faster than doing it bit by bit, as we compare words in +// BitVectors. Variant IntersectInternal(BitVector& first, const BitVector& second) { first.And(second); return std::move(first); } +// O(1) complexity. Variant IntersectInternal(Range first, Range second) { // If both RowMaps have ranges, we can just take the smallest intersection // of them as the new RowMap. @@ -168,6 +171,8 @@ Variant IntersectInternal(Range first, Range second) { return Range{start, end}; } +// O(N + k) complexity, where N is the size of |second| and k is the number of +// elements that have to be removed from |first|. Variant IntersectInternal(std::vector& first, const std::vector& second) { std::unordered_set lookup(second.begin(), second.end()); @@ -179,6 +184,7 @@ Variant IntersectInternal(std::vector& first, return std::move(first); } +// O(1) complexity. Variant IntersectInternal(Range range, const BitVector& bv) { return bv.IntersectRange(range.start, range.end); } diff --git a/src/trace_processor/containers/row_map.h b/src/trace_processor/containers/row_map.h index 88fe7a1a19..44433082c7 100644 --- a/src/trace_processor/containers/row_map.h +++ b/src/trace_processor/containers/row_map.h @@ -360,16 +360,8 @@ class RowMap { return SelectRowsSlow(selector); } - // Intersects the range [start_index, end_index) with |this| writing the - // result into |this|. By "intersect", we mean to keep only the indices - // present in both this RowMap and in the Range [start_index, end_index). The - // order of the preserved indices will be the same as |this|. - // - // Conceptually, we are performing the following algorithm: - // for (idx : this) - // if (start_index <= idx && idx < end_index) - // continue; - // Remove(idx) + // Intersects |this| with |second| independent of underlying structure of both + // RowMaps. Modifies |this| to only contain indices present in |second|. void Intersect(const RowMap& second); // Intersects this RowMap with |index|. If this RowMap contained |index|, then diff --git a/src/trace_processor/containers/string_pool.h b/src/trace_processor/containers/string_pool.h index 75a57a24de..2e035924b5 100644 --- a/src/trace_processor/containers/string_pool.h +++ b/src/trace_processor/containers/string_pool.h @@ -17,21 +17,25 @@ #ifndef SRC_TRACE_PROCESSOR_CONTAINERS_STRING_POOL_H_ #define SRC_TRACE_PROCESSOR_CONTAINERS_STRING_POOL_H_ -#include -#include - +#include +#include +#include #include +#include #include +#include +#include #include +#include "perfetto/base/logging.h" #include "perfetto/ext/base/flat_hash_map.h" #include "perfetto/ext/base/hash.h" #include "perfetto/ext/base/paged_memory.h" +#include "perfetto/ext/base/string_view.h" #include "perfetto/protozero/proto_utils.h" #include "src/trace_processor/containers/null_term_string_view.h" -namespace perfetto { -namespace trace_processor { +namespace perfetto::trace_processor { // Interns strings in a string pool and hands out compact StringIds which can // be used to retrieve the string in O(1). @@ -40,34 +44,38 @@ class StringPool { struct Id { Id() = default; - bool operator==(const Id& other) const { return other.id == id; } - bool operator!=(const Id& other) const { return !(other == *this); } - bool operator<(const Id& other) const { return id < other.id; } + constexpr bool operator==(const Id& other) const { return other.id == id; } + constexpr bool operator!=(const Id& other) const { + return !(other == *this); + } + constexpr bool operator<(const Id& other) const { return id < other.id; } - bool is_null() const { return id == 0u; } + constexpr bool is_null() const { return id == 0u; } - bool is_large_string() const { return id & kLargeStringFlagBitMask; } + constexpr bool is_large_string() const { + return id & kLargeStringFlagBitMask; + } - uint32_t block_offset() const { return id & kBlockOffsetBitMask; } + constexpr uint32_t block_offset() const { return id & kBlockOffsetBitMask; } - uint32_t block_index() const { + constexpr uint32_t block_index() const { return (id & kBlockIndexBitMask) >> kNumBlockOffsetBits; } - uint32_t large_string_index() const { + constexpr uint32_t large_string_index() const { PERFETTO_DCHECK(is_large_string()); return id & ~kLargeStringFlagBitMask; } - uint32_t raw_id() const { return id; } + constexpr uint32_t raw_id() const { return id; } - static Id LargeString(size_t index) { + static constexpr Id LargeString(size_t index) { PERFETTO_DCHECK(index <= static_cast(index)); PERFETTO_DCHECK(!(index & kLargeStringFlagBitMask)); return Id(kLargeStringFlagBitMask | static_cast(index)); } - static Id BlockString(size_t index, uint32_t offset) { + static constexpr Id BlockString(size_t index, uint32_t offset) { PERFETTO_DCHECK(index < (1u << (kNumBlockIndexBits + 1))); PERFETTO_DCHECK(offset < (1u << (kNumBlockOffsetBits + 1))); return Id(~kLargeStringFlagBitMask & @@ -80,7 +88,7 @@ class StringPool { static constexpr Id Null() { return Id(0u); } private: - constexpr Id(uint32_t i) : id(i) {} + constexpr explicit Id(uint32_t i) : id(i) {} uint32_t id; }; @@ -88,7 +96,7 @@ class StringPool { // Iterator over the strings in the pool. class Iterator { public: - Iterator(const StringPool*); + explicit Iterator(const StringPool*); explicit operator bool() const; Iterator& operator++(); @@ -278,7 +286,7 @@ class StringPool { static NullTermStringView GetFromBlockPtr(const uint8_t* ptr) { uint32_t size = 0; const uint8_t* str_ptr = ReadSize(ptr, &size); - return NullTermStringView(reinterpret_cast(str_ptr), size); + return {reinterpret_cast(str_ptr), size}; } // Lookup a string in the |large_strings_| vector. |id| should have the MSB @@ -288,7 +296,7 @@ class StringPool { size_t index = id.large_string_index(); PERFETTO_DCHECK(index < large_strings_.size()); const std::string* str = large_strings_[index].get(); - return NullTermStringView(str->c_str(), str->size()); + return {str->c_str(), str->size()}; } // The actual memory storing the strings. @@ -308,8 +316,7 @@ class StringPool { string_index_{/*initial_capacity=*/4096u}; }; -} // namespace trace_processor -} // namespace perfetto +} // namespace perfetto::trace_processor template <> struct std::hash<::perfetto::trace_processor::StringPool::Id> { diff --git a/src/trace_processor/db/column.h b/src/trace_processor/db/column.h index d6e21c59cc..0bbd9296e5 100644 --- a/src/trace_processor/db/column.h +++ b/src/trace_processor/db/column.h @@ -278,9 +278,6 @@ class ColumnLegacy { // Returns the name of the column. const char* name() const { return name_; } - // Returns the type of this Column in terms of SqlValue::Type. - SqlValue::Type type() const { return ToSqlValueType(type_); } - // Returns the type of this Column in terms of ColumnType. ColumnType col_type() const { return type_; } @@ -383,6 +380,23 @@ class ColumnLegacy { const ColumnStorageBase& storage_base() const { return *storage_; } + static SqlValue::Type ToSqlValueType(ColumnType type) { + switch (type) { + case ColumnType::kInt32: + case ColumnType::kUint32: + case ColumnType::kInt64: + case ColumnType::kId: + return SqlValue::Type::kLong; + case ColumnType::kDouble: + return SqlValue::Type::kDouble; + case ColumnType::kString: + return SqlValue::Type::kString; + case ColumnType::kDummy: + PERFETTO_FATAL("ToSqlValueType not allowed on dummy column"); + } + PERFETTO_FATAL("For GCC"); + } + protected: // Returns the backing sparse vector cast to contain data of type T. // Should only be called when |type_| == ToColumnType(). @@ -487,23 +501,6 @@ class ColumnLegacy { return IsSorted(flags) && !IsNullable(flags) && type == ColumnType::kUint32; } - static SqlValue::Type ToSqlValueType(ColumnType type) { - switch (type) { - case ColumnType::kInt32: - case ColumnType::kUint32: - case ColumnType::kInt64: - case ColumnType::kId: - return SqlValue::Type::kLong; - case ColumnType::kDouble: - return SqlValue::Type::kDouble; - case ColumnType::kString: - return SqlValue::Type::kString; - case ColumnType::kDummy: - PERFETTO_FATAL("ToSqlValueType not allowed on dummy column"); - } - PERFETTO_FATAL("For GCC"); - } - // Returns the string at the index |idx|. // Should only be called when |type_| == ColumnType::kString. NullTermStringView GetStringPoolStringAtIdx(uint32_t idx) const { diff --git a/src/trace_processor/db/column/BUILD.gn b/src/trace_processor/db/column/BUILD.gn index d53e7ae239..f77887f130 100644 --- a/src/trace_processor/db/column/BUILD.gn +++ b/src/trace_processor/db/column/BUILD.gn @@ -43,6 +43,7 @@ source_set("column") { "utils.h", ] deps = [ + "..:compare", "../..:metatrace", "../../../../gn:default_deps", "../../../../include/perfetto/trace_processor", diff --git a/src/trace_processor/db/column/arrangement_overlay.cc b/src/trace_processor/db/column/arrangement_overlay.cc index c31d7c26b4..5f638165fd 100644 --- a/src/trace_processor/db/column/arrangement_overlay.cc +++ b/src/trace_processor/db/column/arrangement_overlay.cc @@ -68,12 +68,21 @@ RangeOrBitVector ArrangementOverlay::ChainImpl::SearchValidated( if (does_arrangement_order_storage_ && op != FilterOp::kGlob && op != FilterOp::kRegex) { - Range inner_res = inner_->OrderedIndexSearchValidated( - op, sql_val, - OrderedIndices{arrangement_->data() + in.start, in.size(), - arrangement_state_}); + OrderedIndices indices{arrangement_->data() + in.start, in.size(), + arrangement_state_}; + if (op == FilterOp::kNe) { + // Do an equality search and "invert" the range. + Range inner_res = + inner_->OrderedIndexSearchValidated(FilterOp::kEq, sql_val, indices); + BitVector bv(in.start); + bv.Resize(in.start + inner_res.start, true); + bv.Resize(in.start + inner_res.end, false); + bv.Resize(in.end, true); + return RangeOrBitVector(std::move(bv)); + } + Range inner_res = inner_->OrderedIndexSearchValidated(op, sql_val, indices); return RangeOrBitVector( - Range(inner_res.start + in.start, inner_res.end + in.start)); + Range(in.start + inner_res.start, in.start + inner_res.end)); } const auto& arrangement = *arrangement_; @@ -139,10 +148,10 @@ void ArrangementOverlay::ChainImpl::IndexSearchValidated( return inner_->IndexSearchValidated(op, sql_val, indices); } -void ArrangementOverlay::ChainImpl::StableSort(SortToken* start, - SortToken* end, +void ArrangementOverlay::ChainImpl::StableSort(Token* start, + Token* end, SortDirection direction) const { - for (SortToken* it = start; it != end; ++it) { + for (Token* it = start; it != end; ++it) { it->index = (*arrangement_)[it->index]; } inner_->StableSort(start, end, direction); @@ -196,6 +205,19 @@ std::optional ArrangementOverlay::ChainImpl::MinElement( return inner_->MinElement(indices); } +std::unique_ptr ArrangementOverlay::ChainImpl::Flatten( + std::vector& indices) const { + for (auto& i : indices) { + i = (*arrangement_)[i]; + } + return inner_->Flatten(indices); +} + +SqlValue ArrangementOverlay::ChainImpl::Get_AvoidUsingBecauseSlow( + uint32_t index) const { + return inner_->Get_AvoidUsingBecauseSlow((*arrangement_)[index]); +} + void ArrangementOverlay::ChainImpl::Serialize(StorageProto* storage) const { auto* arrangement_overlay = storage->set_arrangement_overlay(); arrangement_overlay->set_values( diff --git a/src/trace_processor/db/column/arrangement_overlay.h b/src/trace_processor/db/column/arrangement_overlay.h index 3656b07add..c3edb713c8 100644 --- a/src/trace_processor/db/column/arrangement_overlay.h +++ b/src/trace_processor/db/column/arrangement_overlay.h @@ -19,10 +19,10 @@ #include #include +#include #include #include -#include "perfetto/base/logging.h" #include "perfetto/trace_processor/basic_types.h" #include "src/trace_processor/db/column/data_layer.h" #include "src/trace_processor/db/column/types.h" @@ -61,16 +61,7 @@ class ArrangementOverlay final : public DataLayer { void IndexSearchValidated(FilterOp, SqlValue, Indices&) const override; - Range OrderedIndexSearchValidated(FilterOp, - SqlValue, - const OrderedIndices&) const override { - PERFETTO_FATAL( - "OrderedIndexSearch can't be called on ArrangementOverlay"); - } - - void StableSort(SortToken* start, - SortToken* end, - SortDirection) const override; + void StableSort(Token* start, Token* end, SortDirection) const override; void Distinct(Indices&) const override; @@ -78,6 +69,10 @@ class ArrangementOverlay final : public DataLayer { std::optional MinElement(Indices&) const override; + std::unique_ptr Flatten(std::vector&) const override; + + SqlValue Get_AvoidUsingBecauseSlow(uint32_t index) const override; + void Serialize(StorageProto*) const override; uint32_t size() const override { diff --git a/src/trace_processor/db/column/arrangement_overlay_unittest.cc b/src/trace_processor/db/column/arrangement_overlay_unittest.cc index 55f310b50a..8dfe47433f 100644 --- a/src/trace_processor/db/column/arrangement_overlay_unittest.cc +++ b/src/trace_processor/db/column/arrangement_overlay_unittest.cc @@ -108,15 +108,20 @@ TEST(ArrangementOverlay, IndexSearch) { } TEST(ArrangementOverlay, OrderingSearch) { - std::vector arrangement{0, 2, 4, 1, 3}; - auto fake = FakeStorageChain::SearchSubset(5, BitVector({0, 1, 0, 1, 0})); + std::vector numeric_data{0, 1, 2, 0, 1, 0}; + NumericStorage numeric(&numeric_data, ColumnType::kUint32, false); + + std::vector arrangement{0, 3, 5, 1, 4, 2}; ArrangementOverlay storage(&arrangement, Indices::State::kNonmonotonic); - auto chain = - storage.MakeChain(std::move(fake), DataLayer::ChainCreationArgs(true)); + auto chain = storage.MakeChain(numeric.MakeChain(), + DataLayer::ChainCreationArgs(true)); RangeOrBitVector res = - chain->Search(FilterOp::kGe, SqlValue::Long(0u), Range(0, 5)); + chain->Search(FilterOp::kGe, SqlValue::Long(1u), Range(0, 5)); ASSERT_THAT(utils::ToIndexVectorForTests(res), ElementsAre(3, 4)); + + res = chain->Search(FilterOp::kNe, SqlValue::Long(1u), Range(1, 6)); + ASSERT_THAT(utils::ToIndexVectorForTests(res), ElementsAre(1, 2, 5)); } TEST(ArrangementOverlay, StableSort) { @@ -128,14 +133,10 @@ TEST(ArrangementOverlay, StableSort) { auto chain = storage.MakeChain(numeric.MakeChain()); std::vector tokens{ - column::DataLayerChain::SortToken{0, 0}, - column::DataLayerChain::SortToken{1, 1}, - column::DataLayerChain::SortToken{2, 2}, - column::DataLayerChain::SortToken{3, 3}, - column::DataLayerChain::SortToken{4, 4}, + Token{0, 0}, Token{1, 1}, Token{2, 2}, Token{3, 3}, Token{4, 4}, }; chain->StableSort(tokens.data(), tokens.data() + tokens.size(), - column::DataLayerChain::SortDirection::kAscending); + SortDirection::kAscending); ASSERT_THAT(utils::ExtractPayloadForTesting(tokens), ElementsAre(0, 3, 1, 4, 2)); } @@ -156,5 +157,16 @@ TEST(ArrangementOverlay, Distinct) { ASSERT_THAT(utils::ExtractPayloadForTesting(indices), ElementsAre(0, 2)); } +TEST(ArrangementOverlay, Flatten) { + std::vector arrangement{1, 1, 2, 2, 3, 3, 4, 4, 1, 1}; + auto fake = FakeStorageChain::SearchAll(10); + ArrangementOverlay storage(&arrangement, Indices::State::kNonmonotonic); + auto chain = storage.MakeChain(std::move(fake)); + + std::vector indices{0, 2, 4, 6, 8, 1, 3}; + chain->Flatten(indices); + ASSERT_THAT(indices, ElementsAre(1, 2, 3, 4, 1, 1, 2)); +} + } // namespace } // namespace perfetto::trace_processor::column diff --git a/src/trace_processor/db/column/data_layer.cc b/src/trace_processor/db/column/data_layer.cc index 634cbce291..a570415b92 100644 --- a/src/trace_processor/db/column/data_layer.cc +++ b/src/trace_processor/db/column/data_layer.cc @@ -16,12 +16,15 @@ #include "src/trace_processor/db/column/data_layer.h" +#include #include +#include #include #include #include #include "perfetto/base/logging.h" +#include "perfetto/trace_processor/basic_types.h" #include "src/trace_processor/containers/bit_vector.h" #include "src/trace_processor/containers/string_pool.h" #include "src/trace_processor/db/column/arrangement_overlay.h" @@ -35,6 +38,7 @@ #include "src/trace_processor/db/column/set_id_storage.h" #include "src/trace_processor/db/column/string_storage.h" #include "src/trace_processor/db/column/types.h" +#include "src/trace_processor/db/compare.h" namespace perfetto::trace_processor::column { @@ -113,6 +117,53 @@ std::unique_ptr DataLayer::MakeChain( PERFETTO_FATAL("For GCC"); } +Range DataLayerChain::OrderedIndexSearchValidated( + FilterOp op, + SqlValue value, + const OrderedIndices& indices) const { + auto lb = [&]() { + return static_cast(std::distance( + indices.data, + std::lower_bound(indices.data, indices.data + indices.size, value, + [this](uint32_t idx, const SqlValue& v) { + return compare::SqlValueComparator( + Get_AvoidUsingBecauseSlow(idx), v); + }))); + }; + auto ub = [&]() { + return static_cast(std::distance( + indices.data, + std::upper_bound(indices.data, indices.data + indices.size, value, + [this](const SqlValue& v, uint32_t idx) { + return compare::SqlValueComparator( + v, Get_AvoidUsingBecauseSlow(idx)); + }))); + }; + switch (op) { + case FilterOp::kEq: + return {lb(), ub()}; + case FilterOp::kLe: + return {0, ub()}; + case FilterOp::kLt: + return {0, lb()}; + case FilterOp::kGe: + return {lb(), indices.size}; + case FilterOp::kGt: + return {ub(), indices.size}; + case FilterOp::kIsNull: + PERFETTO_CHECK(value.is_null()); + return {0, ub()}; + case FilterOp::kIsNotNull: + PERFETTO_CHECK(value.is_null()); + return {ub(), indices.size}; + case FilterOp::kNe: + case FilterOp::kGlob: + case FilterOp::kRegex: + PERFETTO_FATAL("Wrong filtering operation"); + } + PERFETTO_FATAL("For GCC"); +} + ArrangementOverlay::ArrangementOverlay( const std::vector* arrangement, DataLayerChain::Indices::State arrangement_state) diff --git a/src/trace_processor/db/column/data_layer.h b/src/trace_processor/db/column/data_layer.h index 5e34539df0..4ef8c567ee 100644 --- a/src/trace_processor/db/column/data_layer.h +++ b/src/trace_processor/db/column/data_layer.h @@ -92,22 +92,6 @@ class DataLayer : public RefCounted { // functionality for querying the transformed data of the entire chain. class DataLayerChain { public: - // Indicates the direction of the sort on a single chain. - enum class SortDirection { - kAscending, - kDescending, - }; - // Struct wrapping indices to elements of this chain. Passed to sorting - // functions. - struct SortToken { - // An index pointing to an element in this chain. Indicates the element - // at this index should be compared. - uint32_t index; - - // An opaque value which can be set to some value meaningful to the - // caller. Implementations *should not* read at this value. - uint32_t payload; - }; using StorageProto = protos::pbzero::SerializedColumn_Storage; // Index vector related data required to Filter using IndexSearch. @@ -249,20 +233,20 @@ class DataLayerChain { PERFETTO_FATAL("For GCC"); } - // Stable sorts an array of SortToken elements between |start| and |end| + // Stable sorts an array of Token elements between |start| and |end| // using a comparator defined by looking up the elements in this chain using - // the index given by SortToken::index. |direction| indicates the direction of + // the index given by Token::index. |direction| indicates the direction of // the sort (ascending or descending). // // In simple terms the expectation is for implementations do something like: // ``` - // std::stable_sort(start, index, [](const SortToken& a, const SortToken& b) { + // std::stable_sort(start, index, [](const Token& a, const Token& b) { // return Get(a.index) < Get(b.index); // }); // ``` // with |Get| being a function to lookup the element in this chain. - virtual void StableSort(SortToken* start, - SortToken* end, + virtual void StableSort(Token* start, + Token* end, SortDirection direction) const = 0; // Removes all indices pointing to values that are duplicates, as a result the @@ -321,9 +305,33 @@ class DataLayerChain { // Post-validated implementation of |OrderedIndexSearch|. See // |OrderedIndexSearch|'s documentation. - virtual Range OrderedIndexSearchValidated(FilterOp, - SqlValue, - const OrderedIndices&) const = 0; + Range OrderedIndexSearchValidated(FilterOp op, + SqlValue value, + const OrderedIndices& indices) const; + + // Returns the pointer to storage DataLayer and modifies indices so that it + // maps the data in the column. + // If |indices[i] == std::numeric_limits::max()| the index points to + // null value. + virtual std::unique_ptr Flatten( + std::vector& indices) const = 0; + + // Returns the SqlValue representing the value at a given index. + // + // This function might be very tempting to use as it appears cheap on the + // surface but because of how DataLayerChains might be layered on top of each + // other, this might require *several* virtual function calls per index. + // If you're tempted to use this, please consider instead create a new + // "vectorized" function instead and only using this as a last resort. + // + // The correct "class" of algorithms to use this function are cases where you + // have a set of indices you want to lookup and based on the value returned + // you will only use a fraction of them. In this case, it might be worth + // paying the non-vectorized lookup to vastly reduce how many indices need + // to be translated. + // + // An example of such an algorithm is binary search on indices. + virtual SqlValue Get_AvoidUsingBecauseSlow(uint32_t index) const = 0; }; } // namespace perfetto::trace_processor::column diff --git a/src/trace_processor/db/column/dense_null_overlay.cc b/src/trace_processor/db/column/dense_null_overlay.cc index 79ca3c0f84..0cecd503b6 100644 --- a/src/trace_processor/db/column/dense_null_overlay.cc +++ b/src/trace_processor/db/column/dense_null_overlay.cc @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -95,6 +96,9 @@ SearchValidationResult DenseNullOverlay::ChainImpl::ValidateSearchConstraints( if (op == FilterOp::kIsNull || op == FilterOp::kIsNotNull) { return SearchValidationResult::kOk; } + if (sql_val.is_null()) { + return SearchValidationResult::kNoData; + } return inner_->ValidateSearchConstraints(op, sql_val); } @@ -219,56 +223,12 @@ void DenseNullOverlay::ChainImpl::IndexSearchValidated(FilterOp op, inner_->IndexSearchValidated(op, sql_val, indices); } -Range DenseNullOverlay::ChainImpl::OrderedIndexSearchValidated( - FilterOp op, - SqlValue sql_val, - const OrderedIndices& indices) const { - // For NOT EQUAL the further analysis needs to be done by the caller. - PERFETTO_CHECK(op != FilterOp::kNe); - - PERFETTO_TP_TRACE(metatrace::Category::DB, - "DenseNullOverlay::ChainImpl::OrderedIndexSearch"); - - // We assume all NULLs are ordered to be in the front. We are looking for the - // first index that points to non NULL value. - const uint32_t* first_non_null = - std::partition_point(indices.data, indices.data + indices.size, - [this](uint32_t i) { return !non_null_->IsSet(i); }); - - auto non_null_offset = - static_cast(std::distance(indices.data, first_non_null)); - auto non_null_size = static_cast( - std::distance(first_non_null, indices.data + indices.size)); - - if (op == FilterOp::kIsNull) { - return {0, non_null_offset}; - } - - if (op == FilterOp::kIsNotNull) { - switch (inner_->ValidateSearchConstraints(op, sql_val)) { - case SearchValidationResult::kNoData: - return {}; - case SearchValidationResult::kAllData: - return {non_null_offset, indices.size}; - case SearchValidationResult::kOk: - break; - } - } - - Range inner_range = inner_->OrderedIndexSearchValidated( - op, sql_val, - OrderedIndices{first_non_null, non_null_size, - Indices::State::kNonmonotonic}); - return {inner_range.start + non_null_offset, - inner_range.end + non_null_offset}; -} - -void DenseNullOverlay::ChainImpl::StableSort(SortToken* start, - SortToken* end, +void DenseNullOverlay::ChainImpl::StableSort(Token* start, + Token* end, SortDirection direction) const { - SortToken* it = std::stable_partition( - start, end, - [this](const SortToken& idx) { return !non_null_->IsSet(idx.index); }); + Token* it = std::stable_partition(start, end, [this](const Token& idx) { + return !non_null_->IsSet(idx.index); + }); inner_->StableSort(it, end, direction); if (direction == SortDirection::kDescending) { std::rotate(start, it, end); @@ -314,6 +274,22 @@ std::optional DenseNullOverlay::ChainImpl::MinElement( : *first_null_it; } +std::unique_ptr DenseNullOverlay::ChainImpl::Flatten( + std::vector& indices) const { + for (auto& i : indices) { + if (!non_null_->IsSet(i)) { + i = std::numeric_limits::max(); + } + } + return inner_->Flatten(indices); +} + +SqlValue DenseNullOverlay::ChainImpl::Get_AvoidUsingBecauseSlow( + uint32_t index) const { + return non_null_->IsSet(index) ? inner_->Get_AvoidUsingBecauseSlow(index) + : SqlValue(); +} + void DenseNullOverlay::ChainImpl::Serialize(StorageProto* storage) const { auto* null_overlay = storage->set_dense_null_overlay(); non_null_->Serialize(null_overlay->set_bit_vector()); diff --git a/src/trace_processor/db/column/dense_null_overlay.h b/src/trace_processor/db/column/dense_null_overlay.h index e253400367..1a0c8129a9 100644 --- a/src/trace_processor/db/column/dense_null_overlay.h +++ b/src/trace_processor/db/column/dense_null_overlay.h @@ -19,6 +19,7 @@ #include #include +#include #include #include "perfetto/trace_processor/basic_types.h" @@ -56,13 +57,7 @@ class DenseNullOverlay final : public DataLayer { void IndexSearchValidated(FilterOp, SqlValue, Indices&) const override; - Range OrderedIndexSearchValidated(FilterOp, - SqlValue, - const OrderedIndices&) const override; - - void StableSort(SortToken* start, - SortToken* end, - SortDirection) const override; + void StableSort(Token* start, Token* end, SortDirection) const override; void Distinct(Indices&) const override; @@ -70,6 +65,10 @@ class DenseNullOverlay final : public DataLayer { std::optional MinElement(Indices&) const override; + std::unique_ptr Flatten(std::vector&) const override; + + SqlValue Get_AvoidUsingBecauseSlow(uint32_t index) const override; + void Serialize(StorageProto*) const override; uint32_t size() const override { return non_null_->size(); } diff --git a/src/trace_processor/db/column/dense_null_overlay_unittest.cc b/src/trace_processor/db/column/dense_null_overlay_unittest.cc index 24b1eeeff0..0efa8cc49d 100644 --- a/src/trace_processor/db/column/dense_null_overlay_unittest.cc +++ b/src/trace_processor/db/column/dense_null_overlay_unittest.cc @@ -17,6 +17,7 @@ #include "src/trace_processor/db/column/dense_null_overlay.h" #include +#include #include #include #include @@ -129,11 +130,12 @@ TEST(DenseNullOverlay, IsNullIndexSearch) { } TEST(DenseNullOverlay, OrderedIndexSearch) { - auto fake = FakeStorageChain::SearchSubset(6, BitVector({0, 1, 0, 1, 0, 1})); + std::vector numeric_data{0, 1, 0, 1, 0, 1}; + NumericStorage numeric(&numeric_data, ColumnType::kUint32, false); BitVector bv{0, 1, 0, 1, 0, 1}; DenseNullOverlay storage(&bv); - auto chain = storage.MakeChain(std::move(fake)); + auto chain = storage.MakeChain(numeric.MakeChain()); std::vector indices_vec({0, 2, 4, 1, 3, 5}); OrderedIndices indices{indices_vec.data(), 6, Indices::State::kNonmonotonic}; @@ -146,25 +148,25 @@ TEST(DenseNullOverlay, OrderedIndexSearch) { ASSERT_EQ(res.start, 3u); ASSERT_EQ(res.end, 6u); - res = chain->OrderedIndexSearch(FilterOp::kEq, SqlValue::Long(3), indices); + res = chain->OrderedIndexSearch(FilterOp::kEq, SqlValue::Long(1), indices); ASSERT_EQ(res.start, 3u); ASSERT_EQ(res.end, 6u); - res = chain->OrderedIndexSearch(FilterOp::kGt, SqlValue::Long(3), indices); + res = chain->OrderedIndexSearch(FilterOp::kGt, SqlValue::Long(0), indices); ASSERT_EQ(res.start, 3u); ASSERT_EQ(res.end, 6u); - res = chain->OrderedIndexSearch(FilterOp::kGe, SqlValue::Long(3), indices); + res = chain->OrderedIndexSearch(FilterOp::kGe, SqlValue::Long(1), indices); ASSERT_EQ(res.start, 3u); ASSERT_EQ(res.end, 6u); - res = chain->OrderedIndexSearch(FilterOp::kLt, SqlValue::Long(3), indices); - ASSERT_EQ(res.start, 3u); - ASSERT_EQ(res.end, 6u); + res = chain->OrderedIndexSearch(FilterOp::kLt, SqlValue::Long(1), indices); + ASSERT_EQ(res.start, 0u); + ASSERT_EQ(res.end, 3u); - res = chain->OrderedIndexSearch(FilterOp::kLe, SqlValue::Long(3), indices); - ASSERT_EQ(res.start, 3u); - ASSERT_EQ(res.end, 6u); + res = chain->OrderedIndexSearch(FilterOp::kLe, SqlValue::Long(0), indices); + ASSERT_EQ(res.start, 0u); + ASSERT_EQ(res.end, 3u); } TEST(DenseNullOverlay, SingleSearch) { @@ -213,26 +215,21 @@ TEST(DenseNullOverlay, StableSort) { auto make_tokens = []() { return std::vector{ - column::DataLayerChain::SortToken{0, 0}, - column::DataLayerChain::SortToken{1, 1}, - column::DataLayerChain::SortToken{2, 2}, - column::DataLayerChain::SortToken{3, 3}, - column::DataLayerChain::SortToken{4, 4}, - column::DataLayerChain::SortToken{5, 5}, - column::DataLayerChain::SortToken{6, 6}, + Token{0, 0}, Token{1, 1}, Token{2, 2}, Token{3, 3}, + Token{4, 4}, Token{5, 5}, Token{6, 6}, }; }; { auto tokens = make_tokens(); chain->StableSort(tokens.data(), tokens.data() + tokens.size(), - column::DataLayerChain::SortDirection::kAscending); + SortDirection::kAscending); ASSERT_THAT(utils::ExtractPayloadForTesting(tokens), ElementsAre(0, 2, 4, 3, 5, 1, 6)); } { auto tokens = make_tokens(); chain->StableSort(tokens.data(), tokens.data() + tokens.size(), - column::DataLayerChain::SortDirection::kDescending); + SortDirection::kDescending); ASSERT_THAT(utils::ExtractPayloadForTesting(tokens), ElementsAre(6, 1, 5, 3, 4, 0, 2)); } @@ -255,5 +252,15 @@ TEST(DenseNullOverlay, Distinct) { UnorderedElementsAre(0, 1, 2)); } +TEST(DenseNullOverlay, Flatten) { + BitVector bv{0, 1, 0, 1, 1, 1}; + DenseNullOverlay storage(&bv); + auto chain = storage.MakeChain(FakeStorageChain::SearchAll(6)); + std::vector indices{0, 1, 2, 3, 1}; + chain->Flatten(indices); + ASSERT_THAT(indices, ElementsAre(std::numeric_limits::max(), 1, + std::numeric_limits::max(), 3, 1)); +} + } // namespace } // namespace perfetto::trace_processor::column diff --git a/src/trace_processor/db/column/dummy_storage.cc b/src/trace_processor/db/column/dummy_storage.cc index 631cbb75f6..9fbab013ad 100644 --- a/src/trace_processor/db/column/dummy_storage.cc +++ b/src/trace_processor/db/column/dummy_storage.cc @@ -17,6 +17,8 @@ #include "src/trace_processor/db/column/dummy_storage.h" #include +#include +#include #include "perfetto/base/logging.h" #include "perfetto/trace_processor/basic_types.h" @@ -49,16 +51,7 @@ void DummyStorage::ChainImpl::IndexSearchValidated(FilterOp, PERFETTO_FATAL("Shouldn't be called"); } -Range DummyStorage::ChainImpl::OrderedIndexSearchValidated( - FilterOp, - SqlValue, - const OrderedIndices&) const { - PERFETTO_FATAL("Shouldn't be called"); -} - -void DummyStorage::ChainImpl::StableSort(SortToken*, - SortToken*, - SortDirection) const { +void DummyStorage::ChainImpl::StableSort(Token*, Token*, SortDirection) const { PERFETTO_FATAL("Shouldn't be called"); } @@ -78,6 +71,15 @@ std::optional DummyStorage::ChainImpl::MinElement(Indices&) const { PERFETTO_FATAL("Shouldn't be called"); } +std::unique_ptr DummyStorage::ChainImpl::Flatten( + std::vector&) const { + PERFETTO_FATAL("Shouldn't be called"); +} + +SqlValue DummyStorage::ChainImpl::Get_AvoidUsingBecauseSlow(uint32_t) const { + PERFETTO_FATAL("Shouldn't be called"); +} + void DummyStorage::ChainImpl::Serialize(StorageProto*) const { PERFETTO_FATAL("Shouldn't be called"); } diff --git a/src/trace_processor/db/column/dummy_storage.h b/src/trace_processor/db/column/dummy_storage.h index ffa0c7359d..bb4efcc614 100644 --- a/src/trace_processor/db/column/dummy_storage.h +++ b/src/trace_processor/db/column/dummy_storage.h @@ -18,6 +18,7 @@ #include #include +#include #include #include "perfetto/trace_processor/basic_types.h" @@ -45,13 +46,7 @@ class DummyStorage final : public DataLayer { void IndexSearchValidated(FilterOp, SqlValue, Indices&) const override; - Range OrderedIndexSearchValidated(FilterOp, - SqlValue, - const OrderedIndices&) const override; - - void StableSort(SortToken* start, - SortToken* end, - SortDirection) const override; + void StableSort(Token* start, Token* end, SortDirection) const override; void Distinct(Indices&) const override; @@ -59,6 +54,10 @@ class DummyStorage final : public DataLayer { std::optional MinElement(Indices&) const override; + std::unique_ptr Flatten(std::vector&) const override; + + SqlValue Get_AvoidUsingBecauseSlow(uint32_t index) const override; + void Serialize(StorageProto*) const override; uint32_t size() const override; diff --git a/src/trace_processor/db/column/fake_storage.cc b/src/trace_processor/db/column/fake_storage.cc index 5a269615c1..d4b546cd0b 100644 --- a/src/trace_processor/db/column/fake_storage.cc +++ b/src/trace_processor/db/column/fake_storage.cc @@ -19,6 +19,8 @@ #include #include #include +#include +#include #include #include "perfetto/base/logging.h" @@ -112,43 +114,6 @@ void FakeStorageChain::IndexSearchValidated(FilterOp, PERFETTO_FATAL("For GCC"); } -Range FakeStorageChain::OrderedIndexSearchValidated( - FilterOp, - SqlValue, - const OrderedIndices& indices) const { - 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(std::distance(indices.data, first_in_range)), - static_cast( - 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(std::distance(indices.data, first_set)), - static_cast(std::distance(indices.data, first_non_set))}; - } - PERFETTO_FATAL("For GCC"); -} - void FakeStorageChain::Distinct(Indices&) const { // Fake storage shouldn't implement Distinct as it's not a binary (this index // passes or not) operation on a column. @@ -162,7 +127,16 @@ std::optional FakeStorageChain::MinElement(Indices&) const { PERFETTO_FATAL("Not implemented"); } -void FakeStorageChain::StableSort(SortToken*, SortToken*, SortDirection) const { +void FakeStorageChain::StableSort(Token*, Token*, SortDirection) const { + PERFETTO_FATAL("Not implemented"); +} + +std::unique_ptr FakeStorageChain::Flatten( + std::vector&) const { + return std::unique_ptr(); +} + +SqlValue FakeStorageChain::Get_AvoidUsingBecauseSlow(uint32_t) const { PERFETTO_FATAL("Not implemented"); } diff --git a/src/trace_processor/db/column/fake_storage.h b/src/trace_processor/db/column/fake_storage.h index 7adf78641b..553b8064de 100644 --- a/src/trace_processor/db/column/fake_storage.h +++ b/src/trace_processor/db/column/fake_storage.h @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -84,19 +85,18 @@ class FakeStorageChain : public DataLayerChain { void IndexSearchValidated(FilterOp, SqlValue, Indices&) const override; - Range OrderedIndexSearchValidated(FilterOp, - SqlValue, - const OrderedIndices&) const override; - - void StableSort(SortToken* start, - SortToken* end, - SortDirection) const override; + void StableSort(Token* start, Token* end, SortDirection) const override; void Distinct(Indices&) const override; std::optional MaxElement(Indices&) const override; + std::optional MinElement(Indices&) const override; + std::unique_ptr Flatten(std::vector&) const override; + + SqlValue Get_AvoidUsingBecauseSlow(uint32_t index) const override; + void Serialize(StorageProto*) const override; uint32_t size() const override { return size_; } diff --git a/src/trace_processor/db/column/fake_storage_unittest.cc b/src/trace_processor/db/column/fake_storage_unittest.cc index 908b7e021a..28c497f847 100644 --- a/src/trace_processor/db/column/fake_storage_unittest.cc +++ b/src/trace_processor/db/column/fake_storage_unittest.cc @@ -43,7 +43,6 @@ using testing::ElementsAre; using testing::IsEmpty; using Indices = DataLayerChain::Indices; -using OrderedIndices = DataLayerChain::OrderedIndices; TEST(FakeStorage, ValidateSearchConstraints) { { @@ -197,48 +196,6 @@ TEST(FakeStorage, IndexSearchValidated) { } } -TEST(FakeStorage, OrderedIndexSearchValidated) { - std::vector 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{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/id_storage.cc b/src/trace_processor/db/column/id_storage.cc index ea1e909919..bad2d8e371 100644 --- a/src/trace_processor/db/column/id_storage.cc +++ b/src/trace_processor/db/column/id_storage.cc @@ -60,12 +60,6 @@ SearchValidationResult IdStorage::ChainImpl::ValidateSearchConstraints( if (op == FilterOp::kIsNotNull) { return SearchValidationResult::kAllData; } - if (op == FilterOp::kIsNull) { - return SearchValidationResult::kNoData; - } - PERFETTO_DFATAL( - "Invalid filter operation. NULL should only be compared with 'IS NULL' " - "and 'IS NOT NULL'"); return SearchValidationResult::kNoData; } @@ -246,47 +240,6 @@ void IdStorage::ChainImpl::IndexSearchValidated(FilterOp op, PERFETTO_FATAL("FilterOp not matched"); } -Range IdStorage::ChainImpl::OrderedIndexSearchValidated( - FilterOp op, - SqlValue sql_val, - const OrderedIndices& indices) const { - PERFETTO_DCHECK(op != FilterOp::kNe); - - PERFETTO_TP_TRACE( - metatrace::Category::DB, "IdStorage::ChainImpl::OrderedIndexSearch", - [indices, op](metatrace::Record* r) { - r->AddArg("Count", std::to_string(indices.size)); - r->AddArg("Op", std::to_string(static_cast(op))); - }); - - // It's a valid filter operation if |sql_val| is a double, although it - // requires special logic. - if (sql_val.type == SqlValue::kDouble) { - switch (utils::CompareIntColumnWithDouble(op, &sql_val)) { - case SearchValidationResult::kOk: - break; - case SearchValidationResult::kAllData: - return {0, indices.size}; - case SearchValidationResult::kNoData: - return {}; - } - } - auto val = static_cast(sql_val.AsLong()); - - // OrderedIndices are monotonic non contiguous values if OrderedIndexSearch - // was called. Look for the first and last index and find the result of - // looking for this range in IdStorage. - Range indices_range(indices.data[0], indices.data[indices.size - 1] + 1); - Range bin_search_ret = BinarySearchIntrinsic(op, val, indices_range); - - const auto* start_ptr = std::lower_bound( - indices.data, indices.data + indices.size, bin_search_ret.start); - const auto* end_ptr = std::lower_bound(start_ptr, indices.data + indices.size, - bin_search_ret.end); - return {static_cast(std::distance(indices.data, start_ptr)), - static_cast(std::distance(indices.data, end_ptr))}; -} - Range IdStorage::ChainImpl::BinarySearchIntrinsic(FilterOp op, Id val, Range range) { @@ -311,19 +264,19 @@ Range IdStorage::ChainImpl::BinarySearchIntrinsic(FilterOp op, PERFETTO_FATAL("FilterOp not matched"); } -void IdStorage::ChainImpl::StableSort(SortToken* start, - SortToken* end, +void IdStorage::ChainImpl::StableSort(Token* start, + Token* end, SortDirection direction) const { PERFETTO_TP_TRACE(metatrace::Category::DB, "IdStorage::ChainImpl::StableSort"); switch (direction) { case SortDirection::kAscending: - std::stable_sort(start, end, [](const SortToken& a, const SortToken& b) { + std::stable_sort(start, end, [](const Token& a, const Token& b) { return a.index < b.index; }); return; case SortDirection::kDescending: - std::stable_sort(start, end, [](const SortToken& a, const SortToken& b) { + std::stable_sort(start, end, [](const Token& a, const Token& b) { return a.index > b.index; }); return; @@ -363,6 +316,10 @@ std::optional IdStorage::ChainImpl::MinElement(Indices& indices) const { return *tok; } +SqlValue IdStorage::ChainImpl::Get_AvoidUsingBecauseSlow(uint32_t index) const { + return SqlValue::Long(index); +} + void IdStorage::ChainImpl::Serialize(StorageProto* storage) const { storage->set_id_storage(); } diff --git a/src/trace_processor/db/column/id_storage.h b/src/trace_processor/db/column/id_storage.h index 420568d64f..f003119e97 100644 --- a/src/trace_processor/db/column/id_storage.h +++ b/src/trace_processor/db/column/id_storage.h @@ -19,7 +19,9 @@ #include #include #include +#include #include +#include #include "perfetto/trace_processor/basic_types.h" #include "src/trace_processor/containers/bit_vector.h" @@ -55,13 +57,7 @@ class IdStorage final : public DataLayer { void IndexSearchValidated(FilterOp, SqlValue, Indices&) const override; - Range OrderedIndexSearchValidated(FilterOp, - SqlValue, - const OrderedIndices&) const override; - - void StableSort(SortToken* start, - SortToken* end, - SortDirection) const override; + void StableSort(Token* start, Token* end, SortDirection) const override; void Distinct(Indices&) const override; @@ -69,6 +65,12 @@ class IdStorage final : public DataLayer { std::optional MinElement(Indices&) const override; + std::unique_ptr Flatten(std::vector&) const override { + return std::unique_ptr(new IdStorage()); + } + + SqlValue Get_AvoidUsingBecauseSlow(uint32_t index) const override; + void Serialize(StorageProto*) const override; uint32_t size() const override { diff --git a/src/trace_processor/db/column/id_storage_unittest.cc b/src/trace_processor/db/column/id_storage_unittest.cc index 3e00ff41af..a5e233be01 100644 --- a/src/trace_processor/db/column/id_storage_unittest.cc +++ b/src/trace_processor/db/column/id_storage_unittest.cc @@ -356,19 +356,15 @@ TEST(IdStorage, StableSort) { IdStorage storage; auto chain = storage.MakeChain(); std::vector tokens{ - column::DataLayerChain::SortToken{0, 0}, - column::DataLayerChain::SortToken{1, 1}, - column::DataLayerChain::SortToken{2, 2}, - column::DataLayerChain::SortToken{3, 3}, - column::DataLayerChain::SortToken{4, 4}, + Token{0, 0}, Token{1, 1}, Token{2, 2}, Token{3, 3}, Token{4, 4}, }; chain->StableSort(tokens.data(), tokens.data() + tokens.size(), - column::DataLayerChain::SortDirection::kAscending); + SortDirection::kAscending); ASSERT_THAT(utils::ExtractPayloadForTesting(tokens), ElementsAre(0, 1, 2, 3, 4)); chain->StableSort(tokens.data(), tokens.data() + tokens.size(), - column::DataLayerChain::SortDirection::kDescending); + SortDirection::kDescending); ASSERT_THAT(utils::ExtractPayloadForTesting(tokens), ElementsAre(4, 3, 2, 1, 0)); } diff --git a/src/trace_processor/db/column/null_overlay.cc b/src/trace_processor/db/column/null_overlay.cc index 128d3ff004..09069f25c1 100644 --- a/src/trace_processor/db/column/null_overlay.cc +++ b/src/trace_processor/db/column/null_overlay.cc @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -148,6 +149,9 @@ SearchValidationResult NullOverlay::ChainImpl::ValidateSearchConstraints( if (op == FilterOp::kIsNull || op == FilterOp::kIsNotNull) { return SearchValidationResult::kOk; } + if (sql_val.is_null()) { + return SearchValidationResult::kNoData; + } return inner_->ValidateSearchConstraints(op, sql_val); } @@ -256,65 +260,15 @@ void NullOverlay::ChainImpl::IndexSearchValidated(FilterOp op, inner_->IndexSearchValidated(op, sql_val, indices); } -Range NullOverlay::ChainImpl::OrderedIndexSearchValidated( - FilterOp op, - SqlValue sql_val, - const OrderedIndices& indices) const { - // For NOT EQUAL the translation or results from EQUAL needs to be done by the - // caller. - PERFETTO_CHECK(op != FilterOp::kNe); - - PERFETTO_TP_TRACE(metatrace::Category::DB, - "NullOverlay::ChainImpl::OrderedIndexSearch"); - - // We assume all NULLs are ordered to be in the front. We are looking for the - // first index that points to non NULL value. - const uint32_t* first_non_null = - std::partition_point(indices.data, indices.data + indices.size, - [this](uint32_t i) { return !non_null_->IsSet(i); }); - auto non_null_offset = - static_cast(std::distance(indices.data, first_non_null)); - auto non_null_size = static_cast( - std::distance(first_non_null, indices.data + indices.size)); - - if (op == FilterOp::kIsNull) { - return {0, non_null_offset}; - } - - if (op == FilterOp::kIsNotNull) { - switch (inner_->ValidateSearchConstraints(op, sql_val)) { - case SearchValidationResult::kNoData: - return {}; - case SearchValidationResult::kAllData: - return {non_null_offset, indices.size}; - case SearchValidationResult::kOk: - break; - } - } - - std::vector storage_iv; - storage_iv.reserve(non_null_size); - for (const uint32_t* it = first_non_null; - it != first_non_null + non_null_size; it++) { - storage_iv.push_back(non_null_->CountSetBits(*it)); - } - - Range inner_range = inner_->OrderedIndexSearchValidated( - op, sql_val, - OrderedIndices{storage_iv.data(), non_null_size, indices.state}); - return {inner_range.start + non_null_offset, - inner_range.end + non_null_offset}; -} - -void NullOverlay::ChainImpl::StableSort(SortToken* start, - SortToken* end, +void NullOverlay::ChainImpl::StableSort(Token* start, + Token* end, SortDirection direction) const { PERFETTO_TP_TRACE(metatrace::Category::DB, "NullOverlay::ChainImpl::StableSort"); - SortToken* middle = std::stable_partition( - start, end, - [this](const SortToken& idx) { return !non_null_->IsSet(idx.index); }); - for (SortToken* it = middle; it != end; ++it) { + Token* middle = std::stable_partition(start, end, [this](const Token& idx) { + return !non_null_->IsSet(idx.index); + }); + for (Token* it = middle; it != end; ++it) { it->index = non_null_->CountSetBits(it->index); } inner_->StableSort(middle, end, direction); @@ -369,6 +323,25 @@ std::optional NullOverlay::ChainImpl::MinElement( return inner_->MinElement(indices); } +std::unique_ptr NullOverlay::ChainImpl::Flatten( + std::vector& indices) const { + for (auto& i : indices) { + if (non_null_->IsSet(i)) { + i = non_null_->CountSetBits(i); + } else { + i = std::numeric_limits::max(); + } + } + return inner_->Flatten(indices); +} + +SqlValue NullOverlay::ChainImpl::Get_AvoidUsingBecauseSlow( + uint32_t index) const { + return non_null_->IsSet(index) + ? inner_->Get_AvoidUsingBecauseSlow(non_null_->CountSetBits(index)) + : SqlValue(); +} + void NullOverlay::ChainImpl::Serialize(StorageProto* storage) const { auto* null_storage = storage->set_null_overlay(); non_null_->Serialize(null_storage->set_bit_vector()); diff --git a/src/trace_processor/db/column/null_overlay.h b/src/trace_processor/db/column/null_overlay.h index 187c056c8a..da5b0d20f8 100644 --- a/src/trace_processor/db/column/null_overlay.h +++ b/src/trace_processor/db/column/null_overlay.h @@ -19,6 +19,7 @@ #include #include +#include #include #include "perfetto/trace_processor/basic_types.h" @@ -55,13 +56,7 @@ class NullOverlay final : public DataLayer { void IndexSearchValidated(FilterOp, SqlValue, Indices&) const override; - Range OrderedIndexSearchValidated(FilterOp, - SqlValue, - const OrderedIndices&) const override; - - void StableSort(SortToken* start, - SortToken* end, - SortDirection) const override; + void StableSort(Token* start, Token* end, SortDirection) const override; void Distinct(Indices&) const override; @@ -69,6 +64,10 @@ class NullOverlay final : public DataLayer { std::optional MinElement(Indices&) const override; + std::unique_ptr Flatten(std::vector&) const override; + + SqlValue Get_AvoidUsingBecauseSlow(uint32_t index) const override; + void Serialize(StorageProto*) const override; uint32_t size() const override { return non_null_->size(); } diff --git a/src/trace_processor/db/column/null_overlay_unittest.cc b/src/trace_processor/db/column/null_overlay_unittest.cc index 981c7bdc40..e484269bd6 100644 --- a/src/trace_processor/db/column/null_overlay_unittest.cc +++ b/src/trace_processor/db/column/null_overlay_unittest.cc @@ -200,15 +200,15 @@ TEST(NullOverlay, IndexSearchIsNotNullOp) { } TEST(NullOverlay, OrderedIndexSearch) { + std::vector numeric_data{1, 0, 1, 0}; + NumericStorage numeric(&numeric_data, ColumnType::kUint32, false); + BitVector bv{0, 1, 1, 1, 0, 1}; - // Passing values in final storage (on normal operations) - // 0, 1, 0, 1, 0, 0 - auto fake = FakeStorageChain::SearchSubset(4, BitVector{1, 0, 1, 0}); NullOverlay storage(&bv); - auto chain = storage.MakeChain(std::move(fake)); + auto chain = storage.MakeChain(numeric.MakeChain()); // Passing values on final data - // NULL, NULL, 0, 1, 1 + // NULL, NULL, 0, 0, 1, 1 std::vector table_idx{0, 4, 5, 1, 3}; OrderedIndices indices{table_idx.data(), uint32_t(table_idx.size()), Indices::State::kNonmonotonic}; @@ -218,28 +218,28 @@ TEST(NullOverlay, OrderedIndexSearch) { ASSERT_EQ(res.end, 2u); res = chain->OrderedIndexSearch(FilterOp::kIsNotNull, SqlValue(), indices); - ASSERT_EQ(res.start, 3u); + ASSERT_EQ(res.start, 2u); ASSERT_EQ(res.end, 5u); - res = chain->OrderedIndexSearch(FilterOp::kEq, SqlValue::Long(3), indices); + res = chain->OrderedIndexSearch(FilterOp::kEq, SqlValue::Long(1), indices); ASSERT_EQ(res.start, 3u); ASSERT_EQ(res.end, 5u); - res = chain->OrderedIndexSearch(FilterOp::kGt, SqlValue::Long(3), indices); + res = chain->OrderedIndexSearch(FilterOp::kGt, SqlValue::Long(0), indices); ASSERT_EQ(res.start, 3u); ASSERT_EQ(res.end, 5u); - res = chain->OrderedIndexSearch(FilterOp::kGe, SqlValue::Long(3), indices); + res = chain->OrderedIndexSearch(FilterOp::kGe, SqlValue::Long(1), indices); ASSERT_EQ(res.start, 3u); ASSERT_EQ(res.end, 5u); - res = chain->OrderedIndexSearch(FilterOp::kLt, SqlValue::Long(3), indices); - ASSERT_EQ(res.start, 3u); - ASSERT_EQ(res.end, 5u); + res = chain->OrderedIndexSearch(FilterOp::kLt, SqlValue::Long(1), indices); + ASSERT_EQ(res.start, 0u); + ASSERT_EQ(res.end, 3u); - res = chain->OrderedIndexSearch(FilterOp::kLe, SqlValue::Long(3), indices); - ASSERT_EQ(res.start, 3u); - ASSERT_EQ(res.end, 5u); + res = chain->OrderedIndexSearch(FilterOp::kLe, SqlValue::Long(0), indices); + ASSERT_EQ(res.start, 0u); + ASSERT_EQ(res.end, 3u); } TEST(NullOverlay, StableSort) { @@ -252,26 +252,21 @@ TEST(NullOverlay, StableSort) { auto make_tokens = []() { return std::vector{ - column::DataLayerChain::SortToken{0, 0}, - column::DataLayerChain::SortToken{1, 1}, - column::DataLayerChain::SortToken{2, 2}, - column::DataLayerChain::SortToken{3, 3}, - column::DataLayerChain::SortToken{4, 4}, - column::DataLayerChain::SortToken{5, 5}, - column::DataLayerChain::SortToken{6, 6}, + Token{0, 0}, Token{1, 1}, Token{2, 2}, Token{3, 3}, + Token{4, 4}, Token{5, 5}, Token{6, 6}, }; }; { auto tokens = make_tokens(); chain->StableSort(tokens.data(), tokens.data() + tokens.size(), - column::DataLayerChain::SortDirection::kAscending); + SortDirection::kAscending); ASSERT_THAT(utils::ExtractPayloadForTesting(tokens), ElementsAre(0, 2, 4, 3, 5, 1, 6)); } { auto tokens = make_tokens(); chain->StableSort(tokens.data(), tokens.data() + tokens.size(), - column::DataLayerChain::SortDirection::kDescending); + SortDirection::kDescending); ASSERT_THAT(utils::ExtractPayloadForTesting(tokens), ElementsAre(6, 1, 5, 3, 4, 0, 2)); } @@ -294,5 +289,17 @@ TEST(NullOverlay, Distinct) { UnorderedElementsAre(0, 1, 2, 4)); } +TEST(NullOverlay, Flatten) { + BitVector bv{0, 1, 1, 0, 0, 1, 1, 0}; + NullOverlay storage(&bv); + auto chain = storage.MakeChain(FakeStorageChain::SearchAll(4u)); + + std::vector indices{0, 1, 3, 6, 0}; + chain->Flatten(indices); + ASSERT_THAT(indices, ElementsAre(std::numeric_limits::max(), 0, + std::numeric_limits::max(), 3, + std::numeric_limits::max())); +} + } // namespace } // namespace perfetto::trace_processor::column diff --git a/src/trace_processor/db/column/numeric_storage.cc b/src/trace_processor/db/column/numeric_storage.cc index b39b4cb913..768c087cbe 100644 --- a/src/trace_processor/db/column/numeric_storage.cc +++ b/src/trace_processor/db/column/numeric_storage.cc @@ -137,60 +137,6 @@ uint32_t UpperBoundIntrinsic(const void* vector_ptr, val); } -template -uint32_t TypedLowerBoundExtrinsic(T val, - const T* data, - OrderedIndices indices) { - const auto* lower = std::lower_bound( - indices.data, indices.data + indices.size, val, - [data](uint32_t index, T value) { return data[index] < value; }); - return static_cast(std::distance(indices.data, lower)); -} - -uint32_t LowerBoundExtrinsic(const void* vector_ptr, - NumericValue val, - OrderedIndices indices) { - if (const auto* u32 = std::get_if(&val)) { - const auto* start = - static_cast*>(vector_ptr)->data(); - return TypedLowerBoundExtrinsic(*u32, start, indices); - } - if (const auto* i64 = std::get_if(&val)) { - const auto* start = - static_cast*>(vector_ptr)->data(); - return TypedLowerBoundExtrinsic(*i64, start, indices); - } - if (const auto* i32 = std::get_if(&val)) { - const auto* start = - static_cast*>(vector_ptr)->data(); - return TypedLowerBoundExtrinsic(*i32, start, indices); - } - if (const auto* db = std::get_if(&val)) { - const auto* start = - static_cast*>(vector_ptr)->data(); - return TypedLowerBoundExtrinsic(*db, start, indices); - } - PERFETTO_FATAL("Type not handled"); -} - -uint32_t UpperBoundExtrinsic(const void* vector_ptr, - NumericValue val, - OrderedIndices indices) { - return std::visit( - [vector_ptr, indices](auto val_data) { - using T = decltype(val_data); - const T* typed_start = - static_cast*>(vector_ptr)->data(); - const auto* upper = - std::upper_bound(indices.data, indices.data + indices.size, - val_data, [typed_start](T value, uint32_t index) { - return value < typed_start[index]; - }); - return static_cast(std::distance(indices.data, upper)); - }, - val); -} - template void TypedLinearSearch(T typed_val, const T* start, @@ -311,12 +257,7 @@ SearchValidationResult NumericStorageBase::ChainImpl::ValidateSearchConstraints( if (op == FilterOp::kIsNotNull) { return SearchValidationResult::kAllData; } - if (op == FilterOp::kIsNull) { - return SearchValidationResult::kNoData; - } - PERFETTO_FATAL( - "Invalid path. NULL should only be compared with 'IS NULL' and 'IS NOT " - "NULL'"); + return SearchValidationResult::kNoData; } // FilterOp checks. Switch so that we get a warning if new FilterOp is not @@ -504,79 +445,6 @@ void NumericStorageBase::ChainImpl::IndexSearchValidated( val); } -Range NumericStorageBase::ChainImpl::OrderedIndexSearchValidated( - FilterOp op, - SqlValue sql_val, - const OrderedIndices& indices) const { - PERFETTO_TP_TRACE( - metatrace::Category::DB, "NumericStorage::ChainImpl::OrderedIndexSearch", - [indices, op](metatrace::Record* r) { - r->AddArg("Count", std::to_string(indices.size)); - r->AddArg("Op", std::to_string(static_cast(op))); - }); - - // Mismatched types - value is double and column is int. - if (sql_val.type == SqlValue::kDouble && - storage_type_ != ColumnType::kDouble) { - if (auto ret = utils::CanReturnEarly(IntColumnWithDouble(op, &sql_val), - indices.size); - ret) { - return *ret; - } - } - - // Mismatched types - column is double and value is int. - if (sql_val.type != SqlValue::kDouble && - storage_type_ == ColumnType::kDouble) { - if (auto ret = utils::CanReturnEarly(DoubleColumnWithInt(op, &sql_val), - indices.size); - ret) { - return *ret; - } - } - - NumericValue val; - switch (storage_type_) { - case ColumnType::kDouble: - val = sql_val.AsDouble(); - break; - case ColumnType::kInt64: - val = sql_val.AsLong(); - break; - case ColumnType::kInt32: - val = static_cast(sql_val.AsLong()); - break; - case ColumnType::kUint32: - val = static_cast(sql_val.AsLong()); - break; - case ColumnType::kString: - case ColumnType::kDummy: - case ColumnType::kId: - PERFETTO_FATAL("Invalid type"); - } - - switch (op) { - case FilterOp::kEq: - return {LowerBoundExtrinsic(vector_ptr_, val, indices), - UpperBoundExtrinsic(vector_ptr_, val, indices)}; - case FilterOp::kLe: - return {0, UpperBoundExtrinsic(vector_ptr_, val, indices)}; - case FilterOp::kLt: - return {0, LowerBoundExtrinsic(vector_ptr_, val, indices)}; - case FilterOp::kGe: - return {LowerBoundExtrinsic(vector_ptr_, val, indices), indices.size}; - case FilterOp::kGt: - return {UpperBoundExtrinsic(vector_ptr_, val, indices), indices.size}; - case FilterOp::kNe: - case FilterOp::kIsNull: - case FilterOp::kIsNotNull: - case FilterOp::kGlob: - case FilterOp::kRegex: - PERFETTO_FATAL("Wrong filtering operation"); - } - PERFETTO_FATAL("For GCC"); -} - BitVector NumericStorageBase::ChainImpl::LinearSearchInternal( FilterOp op, NumericValue val, diff --git a/src/trace_processor/db/column/numeric_storage.h b/src/trace_processor/db/column/numeric_storage.h index fd27d26932..c0fdaeca70 100644 --- a/src/trace_processor/db/column/numeric_storage.h +++ b/src/trace_processor/db/column/numeric_storage.h @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -49,14 +50,14 @@ class NumericStorageBase : public DataLayer { void IndexSearchValidated(FilterOp, SqlValue, Indices&) const override; - Range OrderedIndexSearchValidated(FilterOp, - SqlValue, - const OrderedIndices&) const override; - void Serialize(StorageProto*) const override; std::string DebugString() const override { return "NumericStorage"; } + bool is_sorted() const { return is_sorted_; } + + ColumnType column_type() const { return storage_type_; } + protected: ChainImpl(const void* vector_ptr, ColumnType type, bool is_sorted); @@ -145,22 +146,32 @@ class NumericStorage final : public NumericStorageBase { return *tok; } - void StableSort(SortToken* start, - SortToken* end, + std::unique_ptr Flatten(std::vector&) const override { + return std::unique_ptr( + new NumericStorage(vector_, column_type(), is_sorted())); + } + + SqlValue Get_AvoidUsingBecauseSlow(uint32_t index) const override { + if constexpr (std::is_same_v) { + return SqlValue::Double((*vector_)[index]); + } + return SqlValue::Long((*vector_)[index]); + } + + void StableSort(Token* start, + Token* end, SortDirection direction) const override { const T* base = vector_->data(); switch (direction) { case SortDirection::kAscending: - std::stable_sort(start, end, - [base](const SortToken& a, const SortToken& b) { - return base[a.index] < base[b.index]; - }); + std::stable_sort(start, end, [base](const Token& a, const Token& b) { + return base[a.index] < base[b.index]; + }); break; case SortDirection::kDescending: - std::stable_sort(start, end, - [base](const SortToken& a, const SortToken& b) { - return base[a.index] > base[b.index]; - }); + std::stable_sort(start, end, [base](const Token& a, const Token& b) { + return base[a.index] > base[b.index]; + }); break; } } diff --git a/src/trace_processor/db/column/numeric_storage_unittest.cc b/src/trace_processor/db/column/numeric_storage_unittest.cc index e80c6347da..0b31e7255c 100644 --- a/src/trace_processor/db/column/numeric_storage_unittest.cc +++ b/src/trace_processor/db/column/numeric_storage_unittest.cc @@ -743,24 +743,20 @@ TEST(NumericStorage, StableSort) { auto chain = storage.MakeChain(); auto make_tokens = []() { return std::vector{ - column::DataLayerChain::SortToken{0, 0}, - column::DataLayerChain::SortToken{1, 1}, - column::DataLayerChain::SortToken{2, 2}, - column::DataLayerChain::SortToken{3, 3}, - column::DataLayerChain::SortToken{4, 4}, + Token{0, 0}, Token{1, 1}, Token{2, 2}, Token{3, 3}, Token{4, 4}, }; }; { auto tokens = make_tokens(); chain->StableSort(tokens.data(), tokens.data() + tokens.size(), - column::DataLayerChain::SortDirection::kAscending); + SortDirection::kAscending); ASSERT_THAT(utils::ExtractPayloadForTesting(tokens), ElementsAre(1, 0, 2, 4, 3)); } { auto tokens = make_tokens(); chain->StableSort(tokens.data(), tokens.data() + tokens.size(), - column::DataLayerChain::SortDirection::kDescending); + SortDirection::kDescending); ASSERT_THAT(utils::ExtractPayloadForTesting(tokens), ElementsAre(3, 2, 4, 0, 1)); } diff --git a/src/trace_processor/db/column/range_overlay.cc b/src/trace_processor/db/column/range_overlay.cc index 81bb7ae5e5..b44dbf7224 100644 --- a/src/trace_processor/db/column/range_overlay.cc +++ b/src/trace_processor/db/column/range_overlay.cc @@ -18,6 +18,7 @@ #include #include +#include #include #include @@ -32,6 +33,16 @@ namespace perfetto::trace_processor::column { +namespace { + +void AddOffsetToTokenIndex(DataLayerChain::Indices& indices, uint32_t offset) { + for (auto& token : indices.tokens) { + token.index += offset; + } +} + +} // namespace + using Range = Range; RangeOverlay::ChainImpl::ChainImpl(std::unique_ptr inner, @@ -50,6 +61,10 @@ SingleSearchResult RangeOverlay::ChainImpl::SingleSearch(FilterOp op, SearchValidationResult RangeOverlay::ChainImpl::ValidateSearchConstraints( FilterOp op, SqlValue sql_val) const { + if (sql_val.is_null() && + !(op == FilterOp::kIsNotNull || op == FilterOp::kIsNull)) { + return SearchValidationResult::kNoData; + } return inner_->ValidateSearchConstraints(op, sql_val); } @@ -112,32 +127,14 @@ void RangeOverlay::ChainImpl::IndexSearchValidated(FilterOp op, SqlValue sql_val, Indices& indices) const { PERFETTO_TP_TRACE(metatrace::Category::DB, "RangeOverlay::IndexSearch"); - for (auto& token : indices.tokens) { - token.index += range_->start; - } + AddOffsetToTokenIndex(indices, range_->start); inner_->IndexSearchValidated(op, sql_val, indices); } -Range RangeOverlay::ChainImpl::OrderedIndexSearchValidated( - FilterOp op, - SqlValue sql_val, - const OrderedIndices& indices) const { - PERFETTO_TP_TRACE(metatrace::Category::DB, "RangeOverlay::IndexSearch"); - - // Should be SIMD optimized. - std::vector storage_iv(indices.size); - for (uint32_t i = 0; i < indices.size; ++i) { - storage_iv[i] = indices.data[i] + range_->start; - } - return inner_->OrderedIndexSearchValidated( - op, sql_val, - OrderedIndices{storage_iv.data(), indices.size, indices.state}); -} - -void RangeOverlay::ChainImpl::StableSort(SortToken* start, - SortToken* end, +void RangeOverlay::ChainImpl::StableSort(Token* start, + Token* end, SortDirection direction) const { - for (SortToken* it = start; it != end; ++it) { + for (Token* it = start; it != end; ++it) { it->index += range_->start; } inner_->StableSort(start, end, direction); @@ -145,30 +142,37 @@ void RangeOverlay::ChainImpl::StableSort(SortToken* start, void RangeOverlay::ChainImpl::Distinct(Indices& indices) const { PERFETTO_TP_TRACE(metatrace::Category::DB, "RangeOverlay::Distinct"); - for (auto& token : indices.tokens) { - token.index += range_->start; - } + AddOffsetToTokenIndex(indices, range_->start); inner_->Distinct(indices); } std::optional RangeOverlay::ChainImpl::MaxElement( Indices& indices) const { PERFETTO_TP_TRACE(metatrace::Category::DB, "RangeOverlay::MaxElement"); - for (auto& token : indices.tokens) { - token.index += range_->start; - } + AddOffsetToTokenIndex(indices, range_->start); return inner_->MaxElement(indices); } std::optional RangeOverlay::ChainImpl::MinElement( Indices& indices) const { PERFETTO_TP_TRACE(metatrace::Category::DB, "RangeOverlay::MinElement"); - for (auto& token : indices.tokens) { - token.index += range_->start; - } + AddOffsetToTokenIndex(indices, range_->start); return inner_->MinElement(indices); } +std::unique_ptr RangeOverlay::ChainImpl::Flatten( + std::vector& indices) const { + for (auto& i : indices) { + i += range_->start; + } + return inner_->Flatten(indices); +} + +SqlValue RangeOverlay::ChainImpl::Get_AvoidUsingBecauseSlow( + uint32_t index) const { + return inner_->Get_AvoidUsingBecauseSlow(index + range_->start); +} + void RangeOverlay::ChainImpl::Serialize(StorageProto*) const { PERFETTO_FATAL("Not implemented"); } diff --git a/src/trace_processor/db/column/range_overlay.h b/src/trace_processor/db/column/range_overlay.h index f15e8dce4f..9824b04e71 100644 --- a/src/trace_processor/db/column/range_overlay.h +++ b/src/trace_processor/db/column/range_overlay.h @@ -19,6 +19,7 @@ #include #include +#include #include #include "perfetto/trace_processor/basic_types.h" @@ -52,13 +53,7 @@ class RangeOverlay final : public DataLayer { void IndexSearchValidated(FilterOp p, SqlValue, Indices&) const override; - Range OrderedIndexSearchValidated(FilterOp, - SqlValue, - const OrderedIndices&) const override; - - void StableSort(SortToken* start, - SortToken* end, - SortDirection) const override; + void StableSort(Token* start, Token* end, SortDirection) const override; void Distinct(Indices&) const override; @@ -66,6 +61,10 @@ class RangeOverlay final : public DataLayer { std::optional MinElement(Indices&) const override; + std::unique_ptr Flatten(std::vector&) const override; + + SqlValue Get_AvoidUsingBecauseSlow(uint32_t index) const override; + void Serialize(StorageProto*) const override; uint32_t size() const override { return range_->size(); } diff --git a/src/trace_processor/db/column/range_overlay_unittest.cc b/src/trace_processor/db/column/range_overlay_unittest.cc index 22ea1b0fe2..dcf820cbfd 100644 --- a/src/trace_processor/db/column/range_overlay_unittest.cc +++ b/src/trace_processor/db/column/range_overlay_unittest.cc @@ -117,12 +117,12 @@ TEST(RangeOverlay, StableSort) { auto chain = storage.MakeChain(numeric.MakeChain()); std::vector tokens{ - column::DataLayerChain::SortToken{0, 0}, - column::DataLayerChain::SortToken{1, 1}, - column::DataLayerChain::SortToken{2, 2}, + Token{0, 0}, + Token{1, 1}, + Token{2, 2}, }; chain->StableSort(tokens.data(), tokens.data() + tokens.size(), - column::DataLayerChain::SortDirection::kAscending); + SortDirection::kAscending); ASSERT_THAT(utils::ExtractPayloadForTesting(tokens), ElementsAre(1, 2, 0)); } @@ -141,5 +141,16 @@ TEST(RangeOverlay, Distinct) { ASSERT_THAT(utils::ExtractPayloadForTesting(indices), ElementsAre(0)); } +TEST(RangeOverlay, Flatten) { + Range range(3, 8); + RangeOverlay storage(&range); + auto fake = FakeStorageChain::SearchAll(10); + auto chain = storage.MakeChain(FakeStorageChain::SearchAll(10)); + + std::vector indices{0, 1, 3, 5}; + chain->Flatten(indices); + ASSERT_THAT(indices, ElementsAre(3, 4, 6, 8)); +} + } // namespace } // namespace perfetto::trace_processor::column diff --git a/src/trace_processor/db/column/selector_overlay.cc b/src/trace_processor/db/column/selector_overlay.cc index b2cbc43592..1da736e125 100644 --- a/src/trace_processor/db/column/selector_overlay.cc +++ b/src/trace_processor/db/column/selector_overlay.cc @@ -16,9 +16,9 @@ #include "src/trace_processor/db/column/selector_overlay.h" -#include #include #include +#include #include #include @@ -35,7 +35,7 @@ namespace perfetto::trace_processor::column { namespace { -static constexpr uint32_t kIndexOfNthSetRatio = 32; +constexpr uint32_t kIndexOfNthSetRatio = 32; } // namespace @@ -52,6 +52,10 @@ SingleSearchResult SelectorOverlay::ChainImpl::SingleSearch(FilterOp op, SearchValidationResult SelectorOverlay::ChainImpl::ValidateSearchConstraints( FilterOp op, SqlValue sql_val) const { + if (sql_val.is_null() && + !(op == FilterOp::kIsNotNull || op == FilterOp::kIsNull)) { + return SearchValidationResult::kNoData; + } return inner_->ValidateSearchConstraints(op, sql_val); } @@ -61,7 +65,7 @@ RangeOrBitVector SelectorOverlay::ChainImpl::SearchValidated(FilterOp op, PERFETTO_TP_TRACE(metatrace::Category::DB, "SelectorOverlay::ChainImpl::Search"); - // Figure out the bounds of the OrderedIndices in the underlying storage and + // Figure out the bounds of the indicess in the underlying storage and // search it. uint32_t start_idx = selector_->IndexOfNthSet(in.start); uint32_t end_idx = selector_->IndexOfNthSet(in.end - 1) + 1; @@ -97,29 +101,12 @@ void SelectorOverlay::ChainImpl::IndexSearchValidated(FilterOp op, return inner_->IndexSearchValidated(op, sql_val, indices); } -Range SelectorOverlay::ChainImpl::OrderedIndexSearchValidated( - FilterOp op, - SqlValue sql_val, - const OrderedIndices& indices) const { - // To go from TableIndexVector to StorageIndexVector we need to find index in - // |selector_| by looking only into set bits. - std::vector inner_indices(indices.size); - for (uint32_t i = 0; i < indices.size; ++i) { - inner_indices[i] = selector_->IndexOfNthSet(indices.data[i]); - } - return inner_->OrderedIndexSearchValidated( - op, sql_val, - OrderedIndices{inner_indices.data(), - static_cast(inner_indices.size()), - indices.state}); -} - -void SelectorOverlay::ChainImpl::StableSort(SortToken* start, - SortToken* end, +void SelectorOverlay::ChainImpl::StableSort(Token* start, + Token* end, SortDirection direction) const { PERFETTO_TP_TRACE(metatrace::Category::DB, "SelectorOverlay::ChainImpl::StableSort"); - for (SortToken* it = start; it != end; ++it) { + for (Token* it = start; it != end; ++it) { it->index = selector_->IndexOfNthSet(it->index); } inner_->StableSort(start, end, direction); @@ -148,6 +135,19 @@ std::optional SelectorOverlay::ChainImpl::MinElement( return inner_->MinElement(indices); } +std::unique_ptr SelectorOverlay::ChainImpl::Flatten( + std::vector& indices) const { + for (auto& i : indices) { + i = selector_->IndexOfNthSet(i); + } + return inner_->Flatten(indices); +} + +SqlValue SelectorOverlay::ChainImpl::Get_AvoidUsingBecauseSlow( + uint32_t index) const { + return inner_->Get_AvoidUsingBecauseSlow(selector_->IndexOfNthSet(index)); +} + void SelectorOverlay::ChainImpl::Serialize(StorageProto* storage) const { auto* selector_overlay = storage->set_selector_overlay(); inner_->Serialize(selector_overlay->set_storage()); @@ -164,15 +164,15 @@ void SelectorOverlay::ChainImpl::TranslateToInnerIndices( for (auto& token : indices.tokens) { token.index = selector_->IndexOfNthSet(token.index); } - } else { - // TODO(mayzner): once we have a reverse index for IndexOfNthSet in - // BitVector, this should no longer be necessary. - std::vector lookup = selector_->GetSetBitIndices(); - for (auto& token : indices.tokens) { - token.index = lookup[token.index]; - } + return; + } + + // TODO(mayzner): once we have a reverse index for IndexOfNthSet in + // BitVector, this should no longer be necessary. + std::vector lookup = selector_->GetSetBitIndices(); + for (auto& token : indices.tokens) { + token.index = lookup[token.index]; } - return; } } // namespace perfetto::trace_processor::column diff --git a/src/trace_processor/db/column/selector_overlay.h b/src/trace_processor/db/column/selector_overlay.h index d54bdf59b9..24ba378139 100644 --- a/src/trace_processor/db/column/selector_overlay.h +++ b/src/trace_processor/db/column/selector_overlay.h @@ -19,6 +19,7 @@ #include #include +#include #include #include "perfetto/trace_processor/basic_types.h" @@ -56,13 +57,7 @@ class SelectorOverlay final : public DataLayer { void IndexSearchValidated(FilterOp p, SqlValue, Indices&) const override; - Range OrderedIndexSearchValidated(FilterOp, - SqlValue, - const OrderedIndices&) const override; - - void StableSort(SortToken* start, - SortToken* end, - SortDirection) const override; + void StableSort(Token* start, Token* end, SortDirection) const override; void Distinct(Indices&) const override; @@ -70,6 +65,10 @@ class SelectorOverlay final : public DataLayer { std::optional MinElement(Indices&) const override; + std::unique_ptr Flatten(std::vector&) const override; + + SqlValue Get_AvoidUsingBecauseSlow(uint32_t index) const override; + void Serialize(StorageProto*) const override; uint32_t size() const override { return selector_->size(); } diff --git a/src/trace_processor/db/column/selector_overlay_unittest.cc b/src/trace_processor/db/column/selector_overlay_unittest.cc index 0abae4c623..81d2686551 100644 --- a/src/trace_processor/db/column/selector_overlay_unittest.cc +++ b/src/trace_processor/db/column/selector_overlay_unittest.cc @@ -17,6 +17,7 @@ #include "src/trace_processor/db/column/selector_overlay.h" #include +#include #include #include "data_layer.h" @@ -103,33 +104,21 @@ TEST(SelectorOverlay, IndexSearch) { ASSERT_THAT(utils::ExtractPayloadForTesting(indices), ElementsAre(1u)); } -TEST(SelectorOverlay, OrderedIndexSearchTrivial) { - BitVector selector{1, 0, 1, 0, 1}; - auto fake = FakeStorageChain::SearchAll(5); - SelectorOverlay storage(&selector); - auto chain = storage.MakeChain(std::move(fake)); - - std::vector table_idx{1u, 0u, 2u}; - Range res = chain->OrderedIndexSearch( - FilterOp::kGe, SqlValue::Long(0u), - OrderedIndices{table_idx.data(), static_cast(table_idx.size()), - Indices::State::kNonmonotonic}); - ASSERT_EQ(res.start, 0u); - ASSERT_EQ(res.end, 3u); -} +TEST(SelectorOverlay, OrderedIndexSearch) { + std::vector numeric_data{1, 0, 0, 1, 1}; + NumericStorage numeric(&numeric_data, ColumnType::kUint32, false); -TEST(SelectorOverlay, OrderedIndexSearchNone) { BitVector selector{1, 0, 1, 0, 1}; - auto fake = FakeStorageChain::SearchNone(5); SelectorOverlay storage(&selector); - auto chain = storage.MakeChain(std::move(fake)); + auto chain = storage.MakeChain(numeric.MakeChain()); std::vector table_idx{1u, 0u, 2u}; Range res = chain->OrderedIndexSearch( - FilterOp::kGe, SqlValue::Long(0u), + FilterOp::kGe, SqlValue::Long(1u), OrderedIndices{table_idx.data(), static_cast(table_idx.size()), Indices::State::kNonmonotonic}); - ASSERT_EQ(res.size(), 0u); + ASSERT_EQ(res.start, 1u); + ASSERT_EQ(res.end, 3u); } TEST(SelectorOverlay, StableSort) { @@ -142,29 +131,35 @@ TEST(SelectorOverlay, StableSort) { auto make_tokens = []() { return std::vector{ - column::DataLayerChain::SortToken{0, 0}, - column::DataLayerChain::SortToken{1, 1}, - column::DataLayerChain::SortToken{2, 2}, - column::DataLayerChain::SortToken{3, 3}, - column::DataLayerChain::SortToken{4, 4}, - column::DataLayerChain::SortToken{5, 5}, + Token{0, 0}, Token{1, 1}, Token{2, 2}, + Token{3, 3}, Token{4, 4}, Token{5, 5}, }; }; { auto tokens = make_tokens(); chain->StableSort(tokens.data(), tokens.data() + tokens.size(), - column::DataLayerChain::SortDirection::kAscending); + SortDirection::kAscending); ASSERT_THAT(utils::ExtractPayloadForTesting(tokens), ElementsAre(1, 0, 2, 4, 3, 5)); } { auto tokens = make_tokens(); chain->StableSort(tokens.data(), tokens.data() + tokens.size(), - column::DataLayerChain::SortDirection::kDescending); + SortDirection::kDescending); ASSERT_THAT(utils::ExtractPayloadForTesting(tokens), ElementsAre(3, 5, 4, 2, 0, 1)); } } +TEST(SelectorOverlay, Flatten) { + BitVector selector{0, 1, 1, 0, 0, 1, 1, 0}; + SelectorOverlay storage(&selector); + auto chain = storage.MakeChain(FakeStorageChain::SearchAll(8)); + + std::vector indices{1, 2, 3}; + chain->Flatten(indices); + ASSERT_THAT(indices, ElementsAre(2, 5, 6)); +} + } // namespace } // namespace perfetto::trace_processor::column diff --git a/src/trace_processor/db/column/set_id_storage.cc b/src/trace_processor/db/column/set_id_storage.cc index 207b649b70..ff8bfd58a1 100644 --- a/src/trace_processor/db/column/set_id_storage.cc +++ b/src/trace_processor/db/column/set_id_storage.cc @@ -19,10 +19,8 @@ #include #include #include -#include -#include #include -#include +#include #include #include #include @@ -85,12 +83,7 @@ SearchValidationResult SetIdStorage::ChainImpl::ValidateSearchConstraints( if (op == FilterOp::kIsNotNull) { return SearchValidationResult::kAllData; } - if (op == FilterOp::kIsNull) { - return SearchValidationResult::kNoData; - } - PERFETTO_FATAL( - "Invalid filter operation. NULL should only be compared with 'IS NULL' " - "and 'IS NOT NULL'"); + return SearchValidationResult::kNoData; } // FilterOp checks. Switch so that we get a warning if new FilterOp is not @@ -250,38 +243,17 @@ void SetIdStorage::ChainImpl::IndexSearchValidated(FilterOp op, } } -Range SetIdStorage::ChainImpl::OrderedIndexSearchValidated( - FilterOp op, - SqlValue sql_val, - const OrderedIndices& indices) const { - PERFETTO_TP_TRACE(metatrace::Category::DB, - "SetIdStorage::ChainImpl::OrderedIndexSearch"); - // OrderedIndices are monotonic non-contiguous values. - auto res = SearchValidated( - op, sql_val, Range(indices.data[0], indices.data[indices.size - 1] + 1)); - PERFETTO_CHECK(res.IsRange()); - Range res_range = std::move(res).TakeIfRange(); - - const auto* start_ptr = std::lower_bound( - indices.data, indices.data + indices.size, res_range.start); - const auto* end_ptr = - std::lower_bound(start_ptr, indices.data + indices.size, res_range.end); - - return {static_cast(std::distance(indices.data, start_ptr)), - static_cast(std::distance(indices.data, end_ptr))}; -} - Range SetIdStorage::ChainImpl::BinarySearchIntrinsic(FilterOp op, SetId val, Range range) const { switch (op) { case FilterOp::kEq: { - if (values_->data()[val] != val) { - return Range(); + if ((*values_)[val] != val) { + return {}; } uint32_t start = std::max(val, range.start); uint32_t end = UpperBoundIntrinsic(values_->data(), val, range); - return Range(std::min(start, end), end); + return {std::min(start, end), end}; } case FilterOp::kLe: { return {range.start, UpperBoundIntrinsic(values_->data(), val, range)}; @@ -304,23 +276,21 @@ Range SetIdStorage::ChainImpl::BinarySearchIntrinsic(FilterOp op, return {}; } -void SetIdStorage::ChainImpl::StableSort(SortToken* start, - SortToken* end, +void SetIdStorage::ChainImpl::StableSort(Token* start, + Token* end, SortDirection direction) const { PERFETTO_TP_TRACE(metatrace::Category::DB, "SetIdStorage::ChainImpl::StableSort"); switch (direction) { case SortDirection::kAscending: - std::stable_sort(start, end, - [this](const SortToken& a, const SortToken& b) { - return (*values_)[a.index] < (*values_)[b.index]; - }); + std::stable_sort(start, end, [this](const Token& a, const Token& b) { + return (*values_)[a.index] < (*values_)[b.index]; + }); break; case SortDirection::kDescending: - std::stable_sort(start, end, - [this](const SortToken& a, const SortToken& b) { - return (*values_)[a.index] > (*values_)[b.index]; - }); + std::stable_sort(start, end, [this](const Token& a, const Token& b) { + return (*values_)[a.index] > (*values_)[b.index]; + }); break; } } @@ -370,6 +340,16 @@ std::optional SetIdStorage::ChainImpl::MinElement( return *tok; } +std::unique_ptr SetIdStorage::ChainImpl::Flatten( + std::vector&) const { + return std::unique_ptr(new SetIdStorage(values_)); +} + +SqlValue SetIdStorage::ChainImpl::Get_AvoidUsingBecauseSlow( + uint32_t index) const { + return SqlValue::Long((*values_)[index]); +} + void SetIdStorage::ChainImpl::Serialize(StorageProto* msg) const { auto* vec_msg = msg->set_set_id_storage(); vec_msg->set_values(reinterpret_cast(values_->data()), diff --git a/src/trace_processor/db/column/set_id_storage.h b/src/trace_processor/db/column/set_id_storage.h index 2fa6c81bb8..f9185fdeee 100644 --- a/src/trace_processor/db/column/set_id_storage.h +++ b/src/trace_processor/db/column/set_id_storage.h @@ -18,6 +18,7 @@ #include #include +#include #include #include @@ -54,12 +55,8 @@ class SetIdStorage final : public DataLayer { void IndexSearchValidated(FilterOp, SqlValue, Indices&) const override; - Range OrderedIndexSearchValidated(FilterOp, - SqlValue, - const OrderedIndices&) const override; - - void StableSort(SortToken* start, - SortToken* end, + void StableSort(Token* start, + Token* end, SortDirection direction) const override; void Serialize(StorageProto*) const override; @@ -70,6 +67,10 @@ class SetIdStorage final : public DataLayer { std::optional MinElement(Indices&) const override; + std::unique_ptr Flatten(std::vector&) const override; + + SqlValue Get_AvoidUsingBecauseSlow(uint32_t index) const override; + uint32_t size() const override { return static_cast(values_->size()); } diff --git a/src/trace_processor/db/column/set_id_storage_unittest.cc b/src/trace_processor/db/column/set_id_storage_unittest.cc index 2589a3f327..9f2401f9df 100644 --- a/src/trace_processor/db/column/set_id_storage_unittest.cc +++ b/src/trace_processor/db/column/set_id_storage_unittest.cc @@ -394,24 +394,20 @@ TEST(SetIdStorage, StableSort) { auto chain = storage.MakeChain(); auto make_tokens = []() { return std::vector{ - column::DataLayerChain::SortToken{3, 3}, - column::DataLayerChain::SortToken{2, 2}, - column::DataLayerChain::SortToken{1, 1}, - column::DataLayerChain::SortToken{0, 0}, - column::DataLayerChain::SortToken{4, 4}, + Token{3, 3}, Token{2, 2}, Token{1, 1}, Token{0, 0}, Token{4, 4}, }; }; { auto tokens = make_tokens(); chain->StableSort(tokens.data(), tokens.data() + tokens.size(), - column::DataLayerChain::SortDirection::kAscending); + SortDirection::kAscending); ASSERT_THAT(utils::ExtractPayloadForTesting(tokens), ElementsAre(2, 1, 0, 3, 4)); } { auto tokens = make_tokens(); chain->StableSort(tokens.data(), tokens.data() + tokens.size(), - column::DataLayerChain::SortDirection::kDescending); + SortDirection::kDescending); ASSERT_THAT(utils::ExtractPayloadForTesting(tokens), ElementsAre(3, 4, 2, 1, 0)); } diff --git a/src/trace_processor/db/column/string_storage.cc b/src/trace_processor/db/column/string_storage.cc index adf3eda069..1fa2ff2a54 100644 --- a/src/trace_processor/db/column/string_storage.cc +++ b/src/trace_processor/db/column/string_storage.cc @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -163,36 +164,6 @@ uint32_t UpperBoundIntrinsic(StringPool* pool, return static_cast(std::distance(data, upper)); } -uint32_t LowerBoundExtrinsic(StringPool* pool, - const StringPool::Id* data, - NullTermStringView val, - const uint32_t* indices, - uint32_t indices_count, - uint32_t offset) { - Less comp{pool}; - const auto* lower = - std::lower_bound(indices + offset, indices + indices_count, val, - [comp, data](uint32_t index, NullTermStringView val) { - return comp(data[index], val); - }); - return static_cast(std::distance(indices, lower)); -} - -uint32_t UpperBoundExtrinsic(StringPool* pool, - const StringPool::Id* data, - NullTermStringView val, - const uint32_t* indices, - uint32_t indices_count, - uint32_t offset) { - Greater comp{pool}; - const auto* upper = - std::upper_bound(indices + offset, indices + indices_count, val, - [comp, data](NullTermStringView val, uint32_t index) { - return comp(data[index], val); - }); - return static_cast(std::distance(indices, upper)); -} - } // namespace StringStorage::ChainImpl::ChainImpl(StringPool* string_pool, @@ -284,6 +255,10 @@ SearchValidationResult StringStorage::ChainImpl::ValidateSearchConstraints( // Type checks. switch (val.type) { case SqlValue::kNull: + if (op != FilterOp::kIsNotNull && op != FilterOp::kIsNull) { + return SearchValidationResult::kNoData; + } + break; case SqlValue::kString: break; case SqlValue::kLong: @@ -518,64 +493,6 @@ BitVector StringStorage::ChainImpl::LinearSearch(FilterOp op, return std::move(builder).Build(); } -Range StringStorage::ChainImpl::OrderedIndexSearchValidated( - FilterOp op, - SqlValue sql_val, - const OrderedIndices& indices) const { - PERFETTO_TP_TRACE(metatrace::Category::DB, - "StringStorage::ChainImpl::OrderedIndexSearch"); - StringPool::Id val = - (op == FilterOp::kIsNull || op == FilterOp::kIsNotNull) - ? StringPool::Id::Null() - : string_pool_->InternString(base::StringView(sql_val.AsString())); - NullTermStringView val_str = string_pool_->Get(val); - - auto first_non_null = static_cast(std::distance( - indices.data, - std::partition_point(indices.data, indices.data + indices.size, - [this](uint32_t i) { - return (*data_)[i] == StringPool::Id::Null(); - }))); - - switch (op) { - case FilterOp::kEq: - return {LowerBoundExtrinsic(string_pool_, data_->data(), val_str, - indices.data, indices.size, first_non_null), - UpperBoundExtrinsic(string_pool_, data_->data(), val_str, - indices.data, indices.size, first_non_null)}; - case FilterOp::kLe: - return {first_non_null, - UpperBoundExtrinsic(string_pool_, data_->data(), val_str, - indices.data, indices.size, first_non_null)}; - case FilterOp::kLt: - return {first_non_null, - LowerBoundExtrinsic(string_pool_, data_->data(), val_str, - indices.data, indices.size, first_non_null)}; - case FilterOp::kGe: - return {LowerBoundExtrinsic(string_pool_, data_->data(), val_str, - indices.data, indices.size, first_non_null), - indices.size}; - case FilterOp::kGt: - return {UpperBoundExtrinsic(string_pool_, data_->data(), val_str, - indices.data, indices.size, first_non_null), - indices.size}; - case FilterOp::kIsNull: { - // Assuming nulls are at the front. - return Range(0, first_non_null); - } - case FilterOp::kIsNotNull: { - // Assuming nulls are at the front. - return Range(first_non_null, indices.size); - } - - case FilterOp::kNe: - case FilterOp::kGlob: - case FilterOp::kRegex: - PERFETTO_FATAL("Not supported for OrderedIndexSearch"); - } - PERFETTO_FATAL("For GCC"); -} - Range StringStorage::ChainImpl::BinarySearchIntrinsic( FilterOp op, SqlValue sql_val, @@ -618,55 +535,53 @@ Range StringStorage::ChainImpl::BinarySearchIntrinsic( PERFETTO_FATAL("For GCC"); } -void StringStorage::ChainImpl::StableSort(SortToken* start, - SortToken* end, +void StringStorage::ChainImpl::StableSort(Token* start, + Token* end, SortDirection direction) const { PERFETTO_TP_TRACE(metatrace::Category::DB, "StringStorage::ChainImpl::StableSort"); switch (direction) { case SortDirection::kAscending: { - std::stable_sort(start, end, - [this](const SortToken& lhs, const SortToken& rhs) { - // If RHS is NULL, we know that LHS is not less than - // NULL, as nothing is less then null. This check is - // only required to keep the stability of the sort. - if ((*data_)[rhs.index] == StringPool::Id::Null()) { - return false; - } - - // If LHS is NULL, it will always be smaller than any - // RHS value. - if ((*data_)[lhs.index] == StringPool::Id::Null()) { - return true; - } - - // If neither LHS or RHS are NULL, we have to simply - // check which string is smaller. - return string_pool_->Get((*data_)[lhs.index]) < - string_pool_->Get((*data_)[rhs.index]); - }); + std::stable_sort(start, end, [this](const Token& lhs, const Token& rhs) { + // If RHS is NULL, we know that LHS is not less than + // NULL, as nothing is less then null. This check is + // only required to keep the stability of the sort. + if ((*data_)[rhs.index] == StringPool::Id::Null()) { + return false; + } + + // If LHS is NULL, it will always be smaller than any + // RHS value. + if ((*data_)[lhs.index] == StringPool::Id::Null()) { + return true; + } + + // If neither LHS or RHS are NULL, we have to simply + // check which string is smaller. + return string_pool_->Get((*data_)[lhs.index]) < + string_pool_->Get((*data_)[rhs.index]); + }); return; } case SortDirection::kDescending: { - std::stable_sort(start, end, - [this](const SortToken& lhs, const SortToken& rhs) { - // If LHS is NULL, we know that it's not greater than - // any RHS. This check is only required to keep the - // stability of the sort. - if ((*data_)[lhs.index] == StringPool::Id::Null()) { - return false; - } - - // If RHS is NULL, everything will be greater from it. - if ((*data_)[rhs.index] == StringPool::Id::Null()) { - return true; - } - - // If neither LHS or RHS are NULL, we have to simply - // check which string is smaller. - return string_pool_->Get((*data_)[lhs.index]) > - string_pool_->Get((*data_)[rhs.index]); - }); + std::stable_sort(start, end, [this](const Token& lhs, const Token& rhs) { + // If LHS is NULL, we know that it's not greater than + // any RHS. This check is only required to keep the + // stability of the sort. + if ((*data_)[lhs.index] == StringPool::Id::Null()) { + return false; + } + + // If RHS is NULL, everything will be greater from it. + if ((*data_)[rhs.index] == StringPool::Id::Null()) { + return true; + } + + // If neither LHS or RHS are NULL, we have to simply + // check which string is smaller. + return string_pool_->Get((*data_)[lhs.index]) > + string_pool_->Get((*data_)[rhs.index]); + }); return; } } @@ -715,6 +630,20 @@ std::optional StringStorage::ChainImpl::MinElement( return *tok; } +std::unique_ptr StringStorage::ChainImpl::Flatten( + std::vector&) const { + return std::unique_ptr( + new StringStorage(string_pool_, data_, is_sorted_)); +} + +SqlValue StringStorage::ChainImpl::Get_AvoidUsingBecauseSlow( + uint32_t index) const { + StringPool::Id id = (*data_)[index]; + return id == StringPool::Id::Null() + ? SqlValue() + : SqlValue::String(string_pool_->Get(id).c_str()); +} + void StringStorage::ChainImpl::Serialize(StorageProto* msg) const { auto* string_storage_msg = msg->set_string_storage(); string_storage_msg->set_is_sorted(is_sorted_); diff --git a/src/trace_processor/db/column/string_storage.h b/src/trace_processor/db/column/string_storage.h index 470ccd1d75..7f528bd4c6 100644 --- a/src/trace_processor/db/column/string_storage.h +++ b/src/trace_processor/db/column/string_storage.h @@ -18,6 +18,7 @@ #include #include +#include #include #include @@ -57,12 +58,8 @@ class StringStorage final : public DataLayer { void IndexSearchValidated(FilterOp, SqlValue, Indices&) const override; - Range OrderedIndexSearchValidated(FilterOp, - SqlValue, - const OrderedIndices&) const override; - - void StableSort(SortToken* start, - SortToken* end, + void StableSort(Token* start, + Token* end, SortDirection direction) const override; void Distinct(Indices&) const override; @@ -71,6 +68,10 @@ class StringStorage final : public DataLayer { std::optional MinElement(Indices&) const override; + std::unique_ptr Flatten(std::vector&) const override; + + SqlValue Get_AvoidUsingBecauseSlow(uint32_t index) const override; + void Serialize(StorageProto*) const override; uint32_t size() const override { diff --git a/src/trace_processor/db/column/string_storage_unittest.cc b/src/trace_processor/db/column/string_storage_unittest.cc index 4474ea9fd1..b93937d503 100644 --- a/src/trace_processor/db/column/string_storage_unittest.cc +++ b/src/trace_processor/db/column/string_storage_unittest.cc @@ -383,12 +383,12 @@ TEST(StringStorage, OrderedIndexSearch) { op = FilterOp::kLt; res = chain->OrderedIndexSearch(op, val, indices); - ASSERT_EQ(res.start, 1u); + ASSERT_EQ(res.start, 0u); ASSERT_EQ(res.end, 4u); op = FilterOp::kLe; res = chain->OrderedIndexSearch(op, val, indices); - ASSERT_EQ(res.start, 1u); + ASSERT_EQ(res.start, 0u); ASSERT_EQ(res.end, 5u); op = FilterOp::kGt; @@ -402,6 +402,7 @@ TEST(StringStorage, OrderedIndexSearch) { ASSERT_EQ(res.end, 6u); op = FilterOp::kIsNull; + val = SqlValue(); res = chain->OrderedIndexSearch(op, val, indices); ASSERT_EQ(res.start, 0u); ASSERT_EQ(res.end, 1u); @@ -476,28 +477,21 @@ TEST(StringStorage, StableSort) { auto chain = storage.MakeChain(); auto make_tokens = []() { return std::vector{ - column::DataLayerChain::SortToken{0, 0}, - column::DataLayerChain::SortToken{1, 1}, - column::DataLayerChain::SortToken{2, 2}, - column::DataLayerChain::SortToken{3, 3}, - column::DataLayerChain::SortToken{4, 4}, - column::DataLayerChain::SortToken{5, 5}, - column::DataLayerChain::SortToken{6, 6}, - column::DataLayerChain::SortToken{7, 7}, - column::DataLayerChain::SortToken{8, 8}, + Token{0, 0}, Token{1, 1}, Token{2, 2}, Token{3, 3}, Token{4, 4}, + Token{5, 5}, Token{6, 6}, Token{7, 7}, Token{8, 8}, }; }; { auto tokens = make_tokens(); chain->StableSort(tokens.data(), tokens.data() + tokens.size(), - column::DataLayerChain::SortDirection::kAscending); + SortDirection::kAscending); ASSERT_THAT(utils::ExtractPayloadForTesting(tokens), ElementsAre(0, 1, 2, 3, 8, 7, 4, 6, 5)); } { auto tokens = make_tokens(); chain->StableSort(tokens.data(), tokens.data() + tokens.size(), - column::DataLayerChain::SortDirection::kDescending); + SortDirection::kDescending); ASSERT_THAT(utils::ExtractPayloadForTesting(tokens), ElementsAre(5, 6, 4, 7, 8, 3, 0, 1, 2)); } diff --git a/src/trace_processor/db/column/types.h b/src/trace_processor/db/column/types.h index fc9faa5379..7ffe017472 100644 --- a/src/trace_processor/db/column/types.h +++ b/src/trace_processor/db/column/types.h @@ -17,6 +17,7 @@ #define SRC_TRACE_PROCESSOR_DB_COLUMN_TYPES_H_ #include +#include #include #include #include @@ -26,6 +27,7 @@ #include "perfetto/trace_processor/basic_types.h" #include "src/trace_processor/containers/bit_vector.h" #include "src/trace_processor/containers/row_map.h" +#include "src/trace_processor/containers/string_pool.h" namespace perfetto::trace_processor { @@ -94,7 +96,7 @@ struct Constraint { // Represents an order by operation on a column. struct Order { uint32_t col_idx; - bool desc; + bool desc = false; }; // Structured data used to determine what Trace Processor will query using @@ -162,6 +164,12 @@ struct Token { }; }; +// Indicates the direction of the sort on a single chain. +enum class SortDirection { + kAscending, + kDescending, +}; + } // namespace perfetto::trace_processor #endif // SRC_TRACE_PROCESSOR_DB_COLUMN_TYPES_H_ diff --git a/src/trace_processor/db/column/utils.cc b/src/trace_processor/db/column/utils.cc index e24f3bf4ef..430aa660dd 100644 --- a/src/trace_processor/db/column/utils.cc +++ b/src/trace_processor/db/column/utils.cc @@ -88,8 +88,7 @@ std::vector ExtractPayloadForTesting( return payload; } -std::vector ExtractPayloadForTesting( - std::vector& tokens) { +std::vector ExtractPayloadForTesting(std::vector& tokens) { std::vector payload; payload.reserve(tokens.size()); for (const auto& token : tokens) { diff --git a/src/trace_processor/db/column/utils.h b/src/trace_processor/db/column/utils.h index 64b4801aec..55fc2ce3ec 100644 --- a/src/trace_processor/db/column/utils.h +++ b/src/trace_processor/db/column/utils.h @@ -157,8 +157,7 @@ std::optional CanReturnEarly(SearchValidationResult, bool CanReturnEarly(SearchValidationResult res, DataLayerChain::Indices& indices); -std::vector ExtractPayloadForTesting( - std::vector&); +std::vector ExtractPayloadForTesting(std::vector&); std::vector ToIndexVectorForTests(RangeOrBitVector&); diff --git a/src/trace_processor/db/column_storage.h b/src/trace_processor/db/column_storage.h index bc30a0d261..597408110b 100644 --- a/src/trace_processor/db/column_storage.h +++ b/src/trace_processor/db/column_storage.h @@ -62,6 +62,12 @@ class ColumnStorage final : public ColumnStorageBase { T Get(uint32_t idx) const { return vector_[idx]; } void Append(T val) { vector_.emplace_back(val); } + void Append(const std::vector& vals) { + vector_.insert(vector_.end(), vals.begin(), vals.end()); + } + void AppendMultiple(T val, uint32_t count) { + vector_.insert(vector_.end(), count, val); + } void Set(uint32_t idx, T val) { vector_[idx] = val; } PERFETTO_NO_INLINE void ShrinkToFit() { vector_.shrink_to_fit(); } const std::vector& vector() const { return vector_; } @@ -121,6 +127,16 @@ class ColumnStorage> final : public ColumnStorageBase { AppendNull(); } } + void AppendMultipleNulls(uint32_t count) { + if (mode_ == Mode::kDense) { + data_.resize(data_.size() + static_cast(count)); + } + valid_.Resize(valid_.size() + static_cast(count), false); + } + void AppendMultiple(T val, uint32_t count) { + data_.insert(data_.end(), count, val); + valid_.Resize(valid_.size() + static_cast(count), true); + } void Set(uint32_t idx, T val) { if (mode_ == Mode::kDense) { valid_.Set(idx); diff --git a/src/trace_processor/db/query_executor.cc b/src/trace_processor/db/query_executor.cc index 51917f1f8a..5c5f39ef41 100644 --- a/src/trace_processor/db/query_executor.cc +++ b/src/trace_processor/db/query_executor.cc @@ -16,12 +16,15 @@ #include #include +#include +#include #include #include #include #include "perfetto/base/logging.h" #include "perfetto/trace_processor/basic_types.h" +#include "src/trace_processor/containers/bit_vector.h" #include "src/trace_processor/containers/row_map.h" #include "src/trace_processor/db/column/data_layer.h" #include "src/trace_processor/db/column/types.h" @@ -33,6 +36,30 @@ namespace perfetto::trace_processor { namespace { using Range = RowMap::Range; +using Indices = column::DataLayerChain::Indices; +using OrderedIndices = column::DataLayerChain::OrderedIndices; + +static constexpr uint32_t kIndexVectorThreshold = 1024; + +// Returns if |op| is an operation that can use the fact that the data is +// sorted. +bool IsSortingOp(FilterOp op) { + switch (op) { + case FilterOp::kEq: + case FilterOp::kLe: + case FilterOp::kLt: + case FilterOp::kGe: + case FilterOp::kGt: + case FilterOp::kIsNotNull: + case FilterOp::kIsNull: + return true; + case FilterOp::kGlob: + case FilterOp::kRegex: + case FilterOp::kNe: + return false; + } + PERFETTO_FATAL("For GCC"); +} } // namespace @@ -57,12 +84,14 @@ void QueryExecutor::FilterColumn(const Constraint& c, } } - // Comparison of NULL with any operation apart from |IS_NULL| and - // |IS_NOT_NULL| should return no rows. - if (c.value.is_null() && c.op != FilterOp::kIsNull && - c.op != FilterOp::kIsNotNull) { - rm->Clear(); - return; + switch (chain.ValidateSearchConstraints(c.op, c.value)) { + case SearchValidationResult::kNoData: + rm->Clear(); + return; + case SearchValidationResult::kAllData: + return; + case SearchValidationResult::kOk: + break; } uint32_t rm_last = rm->Get(rm_size - 1); @@ -116,7 +145,6 @@ void QueryExecutor::IndexSearch(const Constraint& c, // Create outmost TableIndexVector. std::vector table_indices = std::move(*rm).TakeAsIndexVector(); - using Indices = column::DataLayerChain::Indices; Indices indices = Indices::Create(table_indices, Indices::State::kMonotonic); chain.IndexSearch(c.op, c.value, indices); @@ -132,9 +160,72 @@ void QueryExecutor::IndexSearch(const Constraint& c, RowMap QueryExecutor::FilterLegacy(const Table* table, const std::vector& c_vec) { RowMap rm(0, table->row_count()); - for (const auto& c : c_vec) { + + // Prework - use indexes if possible and decide which one. + std::vector maybe_idx_cols; + + for (uint32_t i = 0; i < c_vec.size(); i++) { + const Constraint& c = c_vec[i]; + // Id columns shouldn't use index. + if (table->columns()[c.col_idx].IsId()) { + break; + } + + // The operation has to support sorting. + if (!IsSortingOp(c.op)) { + break; + } + + maybe_idx_cols.push_back(c.col_idx); + + // For the next col to be able to use index, all previous constraints have + // to be equality. + if (c.op != FilterOp::kEq) { + break; + } + } + + OrderedIndices o_idxs; + while (!maybe_idx_cols.empty()) { + if (auto maybe_idx = table->GetIndex(maybe_idx_cols)) { + o_idxs = std::move(*maybe_idx); + break; + } + maybe_idx_cols.pop_back(); + } + + // If we can't use the index just filter in a standard way. + if (maybe_idx_cols.empty()) { + for (const auto& c : c_vec) { + FilterColumn(c, table->ChainForColumn(c.col_idx), &rm); + } + return rm; + } + + for (uint32_t i = 0; i < maybe_idx_cols.size(); i++) { + const Constraint& c = c_vec[i]; + + Range r = table->ChainForColumn(c.col_idx).OrderedIndexSearch(c.op, c.value, + o_idxs); + o_idxs.data += r.start; + o_idxs.size = r.size(); + } + + std::vector res_vec(o_idxs.data, o_idxs.data + o_idxs.size); + if (res_vec.size() < kIndexVectorThreshold) { + std::sort(res_vec.begin(), res_vec.end()); + rm = RowMap(std::move(res_vec)); + } else { + rm = RowMap(BitVector::FromUnsortedIndexVector(std::move(res_vec))); + } + + // Filter the rest of constraints in a standard way. + for (uint32_t i = static_cast(maybe_idx_cols.size()); + i < c_vec.size(); i++) { + const Constraint& c = c_vec[i]; FilterColumn(c, table->ChainForColumn(c.col_idx), &rm); } + return rm; } @@ -144,7 +235,7 @@ void QueryExecutor::SortLegacy(const Table* table, // Setup the sort token payload to match the input vector of indices. The // value of the payload will be untouched by the algorithm even while the // order changes to match the ordering defined by the input constraint set. - std::vector rows(out.size()); + std::vector rows(out.size()); for (uint32_t i = 0; i < out.size(); ++i) { rows[i].payload = out[i]; } @@ -172,22 +263,22 @@ void QueryExecutor::SortLegacy(const Table* table, // the sort is stable). // // TODO(lalitm): it is possible that we could sort the last constraint (i.e. - // the first constraint in the below loop) in a non-stable way. However, this - // is more subtle than it appears as we would then need special handling where - // there are order bys on a column which is already sorted (e.g. ts, id). - // Investigate whether the performance gains from this are worthwhile. This - // also needs changes to the constraint modification logic in DbSqliteTable - // which currently eliminates constraints on sorted columns. + // the first constraint in the below loop) in a non-stable way. However, + // this is more subtle than it appears as we would then need special + // handling where there are order bys on a column which is already sorted + // (e.g. ts, id). Investigate whether the performance gains from this are + // worthwhile. This also needs changes to the constraint modification logic + // in DbSqliteTable which currently eliminates constraints on sorted + // columns. for (auto it = ob.rbegin(); it != ob.rend(); ++it) { // Reset the index to the payload at the start of each iote for (uint32_t i = 0; i < out.size(); ++i) { rows[i].index = rows[i].payload; } table->ChainForColumn(it->col_idx) - .StableSort(rows.data(), rows.data() + rows.size(), - it->desc - ? column::DataLayerChain::SortDirection::kDescending - : column::DataLayerChain::SortDirection::kAscending); + .StableSort( + rows.data(), rows.data() + rows.size(), + it->desc ? SortDirection::kDescending : SortDirection::kAscending); } // Recapture the payload from each of the sort tokens whose order now diff --git a/src/trace_processor/db/query_executor_benchmark.cc b/src/trace_processor/db/query_executor_benchmark.cc index d6cc57f7ed..95b64ac798 100644 --- a/src/trace_processor/db/query_executor_benchmark.cc +++ b/src/trace_processor/db/query_executor_benchmark.cc @@ -198,12 +198,12 @@ struct FtraceEventTableForBenchmark { while (cur_idx < idx) { std::vector raw_row = SplitCSVLine(raw_rows[cur_idx + 1]); RawTable::Row r; - r.cpu = *base::StringToUInt32(raw_row[1]); + r.ucpu = tables::CpuTable::Id(*base::StringToUInt32(raw_row[1])); raw_.Insert(r); cur_idx++; } FtraceEventTable::Row row; - row.cpu = *base::StringToUInt32(row_vec[1]); + row.ucpu = tables::CpuTable::Id(*base::StringToUInt32(row_vec[1])); table_.Insert(row); } } @@ -374,7 +374,7 @@ BENCHMARK(BM_QEFilterWithSparseSelector); void BM_QEFilterWithDenseSelector(benchmark::State& state) { FtraceEventTableForBenchmark table(state); Query q; - q.constraints = {table.table_.cpu().eq(4)}; + q.constraints = {table.table_.ucpu().eq(4)}; BenchmarkFtraceEventTableQuery(state, table, q); } BENCHMARK(BM_QEFilterWithDenseSelector); @@ -513,6 +513,29 @@ void BM_QEFilterOrderedArrangement(benchmark::State& state) { } BENCHMARK(BM_QEFilterOrderedArrangement); +void BM_QEFilterNullOrderedArrangement(benchmark::State& state) { + SliceTableForBenchmark table(state); + Order order{table.table_.parent_id().index_in_table(), false}; + Table slice_sorted_with_parent_id = table.table_.Sort({order}); + + Constraint c{table.table_.parent_id().index_in_table(), FilterOp::kGt, + SqlValue::Long(26091)}; + Query q; + q.constraints = {c}; + for (auto _ : state) { + benchmark::DoNotOptimize(slice_sorted_with_parent_id.QueryToRowMap(q)); + } + state.counters["s/row"] = benchmark::Counter( + static_cast(slice_sorted_with_parent_id.row_count()), + benchmark::Counter::kIsIterationInvariantRate | + benchmark::Counter::kInvert); + state.counters["s/out"] = benchmark::Counter( + static_cast(table.table_.QueryToRowMap(q).size()), + benchmark::Counter::kIsIterationInvariantRate | + benchmark::Counter::kInvert); +} +BENCHMARK(BM_QEFilterNullOrderedArrangement); + void BM_QESliceFilterIndexSearchOneElement(benchmark::State& state) { SliceTableForBenchmark table(state); BenchmarkSliceTableFilter( @@ -543,14 +566,15 @@ BENCHMARK(BM_QESliceSortNullNumericAsc); void BM_QEFtraceEventSortSelectorNumericAsc(benchmark::State& state) { FtraceEventTableForBenchmark table(state); - BenchmarkFtraceEventTableSort(state, table, {table.table_.cpu().ascending()}); + BenchmarkFtraceEventTableSort(state, table, + {table.table_.ucpu().ascending()}); } BENCHMARK(BM_QEFtraceEventSortSelectorNumericAsc); void BM_QEFtraceEventSortSelectorNumericDesc(benchmark::State& state) { FtraceEventTableForBenchmark table(state); BenchmarkFtraceEventTableSort(state, table, - {table.table_.cpu().descending()}); + {table.table_.ucpu().descending()}); } BENCHMARK(BM_QEFtraceEventSortSelectorNumericDesc); @@ -567,7 +591,7 @@ void BM_QEDistinctWithDenseSelector(benchmark::State& state) { FtraceEventTableForBenchmark table(state); Query q; q.order_type = Query::OrderType::kDistinct; - q.orders = {table.table_.cpu().descending()}; + q.orders = {table.table_.ucpu().descending()}; BenchmarkFtraceEventTableQuery(state, table, q); } BENCHMARK(BM_QEDistinctWithDenseSelector); @@ -585,7 +609,7 @@ void BM_QEDistinctSortedWithDenseSelector(benchmark::State& state) { FtraceEventTableForBenchmark table(state); Query q; q.order_type = Query::OrderType::kDistinctAndSort; - q.orders = {table.table_.cpu().descending()}; + q.orders = {table.table_.ucpu().descending()}; BenchmarkFtraceEventTableQuery(state, table, q); } BENCHMARK(BM_QEDistinctSortedWithDenseSelector); diff --git a/src/trace_processor/db/runtime_table.cc b/src/trace_processor/db/runtime_table.cc index 321cff386d..102f0034e0 100644 --- a/src/trace_processor/db/runtime_table.cc +++ b/src/trace_processor/db/runtime_table.cc @@ -19,21 +19,17 @@ #include #include #include -#include #include #include #include -#include #include #include #include #include -#include "perfetto/base/compiler.h" #include "perfetto/base/logging.h" #include "perfetto/base/status.h" #include "perfetto/ext/base/status_or.h" -#include "perfetto/trace_processor/iterator.h" #include "perfetto/trace_processor/ref_counted.h" #include "src/trace_processor/containers/bit_vector.h" #include "src/trace_processor/containers/string_pool.h" @@ -42,7 +38,6 @@ #include "src/trace_processor/db/column/id_storage.h" #include "src/trace_processor/db/column/null_overlay.h" #include "src/trace_processor/db/column/numeric_storage.h" -#include "src/trace_processor/db/column/range_overlay.h" #include "src/trace_processor/db/column/selector_overlay.h" #include "src/trace_processor/db/column/string_storage.h" #include "src/trace_processor/db/column/types.h" @@ -66,11 +61,6 @@ bool IsPerfectlyRepresentableAsDouble(int64_t res) { return res >= -kMaxDoubleRepresentible && res <= kMaxDoubleRepresentible; } -bool IsStorageNotIntNorDouble(const RuntimeTable::VariantStorage& col) { - return std::get_if(&col) == nullptr && - std::get_if(&col) == nullptr; -} - void CreateNonNullableIntsColumn( uint32_t col_idx, const char* col_name, @@ -113,13 +103,21 @@ void CreateNonNullableIntsColumn( // The column is an Id column. storage_layers[col_idx].reset(new column::IdStorage()); - legacy_overlays.emplace_back(BitVector::FromSortedIndexVector(values)); - overlay_layers.emplace_back().reset(new column::SelectorOverlay( - legacy_overlays.back().row_map().GetIfBitVector())); - - legacy_columns.push_back(ColumnLegacy::IdColumn( - col_idx, static_cast(legacy_overlays.size() - 1), col_name, - ColumnLegacy::kIdFlags)); + // If the id is dense (i.e. the start is zero and the size equals the last + // value) then there's no need for an overlay. + bool is_dense = values.front() == 0 && + static_cast(values.back()) == values.size() - 1; + if (is_dense) { + legacy_columns.push_back( + ColumnLegacy::IdColumn(col_idx, 0, col_name, ColumnLegacy::kIdFlags)); + } else { + legacy_overlays.emplace_back(BitVector::FromSortedIndexVector(values)); + overlay_layers.emplace_back().reset(new column::SelectorOverlay( + legacy_overlays.back().row_map().GetIfBitVector())); + legacy_columns.push_back(ColumnLegacy::IdColumn( + col_idx, static_cast(legacy_overlays.size() - 1), col_name, + ColumnLegacy::kIdFlags)); + } return; } @@ -150,16 +148,45 @@ RuntimeTable::RuntimeTable( RuntimeTable::~RuntimeTable() = default; RuntimeTable::Builder::Builder(StringPool* pool, - std::vector col_names) - : string_pool_(pool), col_names_(std::move(col_names)) { - for (uint32_t i = 0; i < col_names_.size(); i++) { - storage_.emplace_back(std::make_unique()); + const std::vector& col_names) + : Builder(pool, + col_names, + std::vector(col_names.size(), kNull)) {} + +RuntimeTable::Builder::Builder(StringPool* pool, + const std::vector& col_names, + const std::vector& col_types) + : string_pool_(pool), col_names_(col_names) { + for (BuilderColumnType type : col_types) { + switch (type) { + case kNull: + storage_.emplace_back(std::make_unique()); + break; + case kInt: + storage_.emplace_back(std::make_unique(IntStorage())); + break; + case kNullInt: + storage_.emplace_back( + std::make_unique(NullIntStorage())); + break; + case kDouble: + storage_.emplace_back( + std::make_unique(DoubleStorage())); + break; + case kNullDouble: + storage_.emplace_back( + std::make_unique(NullDoubleStorage())); + break; + case kString: + storage_.emplace_back( + std::make_unique(StringStorage())); + break; + } } } base::Status RuntimeTable::Builder::AddNull(uint32_t idx) { auto* col = storage_[idx].get(); - PERFETTO_DCHECK(IsStorageNotIntNorDouble(*col)); if (auto* leading_nulls = std::get_if(col)) { (*leading_nulls)++; } else if (auto* ints = std::get_if(col)) { @@ -176,7 +203,6 @@ base::Status RuntimeTable::Builder::AddNull(uint32_t idx) { base::Status RuntimeTable::Builder::AddInteger(uint32_t idx, int64_t res) { auto* col = storage_[idx].get(); - PERFETTO_DCHECK(IsStorageNotIntNorDouble(*col)); if (auto* leading_nulls_ptr = std::get_if(col)) { *col = Fill(*leading_nulls_ptr, std::nullopt); } @@ -200,7 +226,6 @@ base::Status RuntimeTable::Builder::AddInteger(uint32_t idx, int64_t res) { base::Status RuntimeTable::Builder::AddFloat(uint32_t idx, double res) { auto* col = storage_[idx].get(); - PERFETTO_DCHECK(IsStorageNotIntNorDouble(*col)); if (auto* leading_nulls_ptr = std::get_if(col)) { *col = Fill(*leading_nulls_ptr, std::nullopt); } @@ -232,7 +257,6 @@ base::Status RuntimeTable::Builder::AddFloat(uint32_t idx, double res) { base::Status RuntimeTable::Builder::AddText(uint32_t idx, const char* ptr) { auto* col = storage_[idx].get(); - PERFETTO_DCHECK(IsStorageNotIntNorDouble(*col)); if (auto* leading_nulls_ptr = std::get_if(col)) { *col = Fill(*leading_nulls_ptr, StringPool::Id::Null()); } @@ -245,6 +269,102 @@ base::Status RuntimeTable::Builder::AddText(uint32_t idx, const char* ptr) { return base::OkStatus(); } +base::Status RuntimeTable::Builder::AddIntegers(uint32_t idx, + int64_t res, + uint32_t count) { + auto* col = storage_[idx].get(); + if (auto* leading_nulls_ptr = std::get_if(col)) { + *col = Fill(*leading_nulls_ptr, std::nullopt); + } + if (auto* doubles = std::get_if(col)) { + if (!IsPerfectlyRepresentableAsDouble(res)) { + return base::ErrStatus("Column %s contains %" PRId64 + " which cannot be represented as a double", + col_names_[idx].c_str(), res); + } + doubles->AppendMultiple(static_cast(res), count); + return base::OkStatus(); + } + auto* ints = std::get_if(col); + if (!ints) { + return base::ErrStatus("Column %s does not have consistent types", + col_names_[idx].c_str()); + } + ints->AppendMultiple(res, count); + return base::OkStatus(); +} + +base::Status RuntimeTable::Builder::AddFloats(uint32_t idx, + double res, + uint32_t count) { + auto* col = storage_[idx].get(); + if (auto* leading_nulls_ptr = std::get_if(col)) { + *col = Fill(*leading_nulls_ptr, std::nullopt); + } + if (auto* ints = std::get_if(col)) { + NullDoubleStorage storage; + for (uint32_t i = 0; i < ints->size(); ++i) { + std::optional int_val = ints->Get(i); + if (!int_val) { + storage.AppendMultipleNulls(count); + continue; + } + if (int_val && !IsPerfectlyRepresentableAsDouble(*int_val)) { + return base::ErrStatus("Column %s contains %" PRId64 + " which cannot be represented as a double", + col_names_[idx].c_str(), *int_val); + } + storage.AppendMultiple(static_cast(*int_val), count); + } + *col = std::move(storage); + } + auto* doubles = std::get_if(col); + if (!doubles) { + return base::ErrStatus("Column %s does not have consistent types", + col_names_[idx].c_str()); + } + doubles->AppendMultiple(res, count); + return base::OkStatus(); +} + +base::Status RuntimeTable::Builder::AddTexts(uint32_t idx, + const char* ptr, + uint32_t count) { + auto* col = storage_[idx].get(); + if (auto* leading_nulls_ptr = std::get_if(col)) { + *col = Fill(*leading_nulls_ptr, StringPool::Id::Null()); + } + auto* strings = std::get_if(col); + if (!strings) { + return base::ErrStatus("Column %s does not have consistent types", + col_names_[idx].c_str()); + } + strings->AppendMultiple(string_pool_->InternString(ptr), count); + return base::OkStatus(); +} + +base::Status RuntimeTable::Builder::AddNulls(uint32_t idx, uint32_t count) { + auto* col = storage_[idx].get(); + if (auto* leading_nulls = std::get_if(col)) { + (*leading_nulls)++; + } else if (auto* ints = std::get_if(col)) { + ints->AppendMultipleNulls(count); + } else if (auto* strings = std::get_if(col)) { + strings->AppendMultiple(StringPool::Id::Null(), count); + } else if (auto* doubles = std::get_if(col)) { + doubles->AppendMultipleNulls(count); + } else { + PERFETTO_FATAL("Unexpected column type"); + } + return base::OkStatus(); +} + +void RuntimeTable::Builder::AddNonNullIntegersUnchecked( + uint32_t idx, + const std::vector& res) { + std::get(*storage_[idx]).Append(res); +} + base::StatusOr> RuntimeTable::Builder::Build( uint32_t rows) && { std::vector> storage_layers(col_names_.size() + 1); @@ -265,37 +385,42 @@ base::StatusOr> RuntimeTable::Builder::Build( for (uint32_t i = 0; i < col_names_.size(); ++i) { auto* col = storage_[i].get(); std::unique_ptr chain; - PERFETTO_DCHECK(IsStorageNotIntNorDouble(*col)); if (auto* leading_nulls = std::get_if(col)) { PERFETTO_CHECK(*leading_nulls == rows); *col = Fill(*leading_nulls, std::nullopt); } - if (auto* ints = std::get_if(col)) { + if (auto* null_ints = std::get_if(col)) { // The `ints` column - PERFETTO_CHECK(ints->size() == rows); + PERFETTO_CHECK(null_ints->size() == rows); - if (ints->non_null_size() == ints->size()) { + if (null_ints->non_null_size() == null_ints->size()) { // The column doesn't have any nulls so we construct a new nonnullable // column. - *col = IntStorage::CreateFromAssertNonNull(std::move(*ints)); + *col = IntStorage::CreateFromAssertNonNull(std::move(*null_ints)); CreateNonNullableIntsColumn( i, col_names_[i].c_str(), std::get_if(col), storage_layers, overlay_layers, legacy_columns, legacy_overlays); } else { // Nullable ints column. - legacy_columns.emplace_back(col_names_[i].c_str(), ints, + legacy_columns.emplace_back(col_names_[i].c_str(), null_ints, ColumnLegacy::Flag::kNoFlag, i, 0); storage_layers[i].reset(new column::NumericStorage( - &ints->non_null_vector(), ColumnType::kInt64, false)); + &null_ints->non_null_vector(), ColumnType::kInt64, false)); null_layers[i].reset( - new column::NullOverlay(&ints->non_null_bit_vector())); + new column::NullOverlay(&null_ints->non_null_bit_vector())); } - // The doubles column. + } else if (auto* ints = std::get_if(col)) { + // The `ints` column for tables where column types was provided before. + PERFETTO_CHECK(ints->size() == rows); + CreateNonNullableIntsColumn( + i, col_names_[i].c_str(), std::get_if(col), + storage_layers, overlay_layers, legacy_columns, legacy_overlays); + } else if (auto* doubles = std::get_if(col)) { + // The doubles column. PERFETTO_CHECK(doubles->size() == rows); - if (doubles->non_null_size() == doubles->size()) { // The column is not nullable. *col = DoubleStorage::CreateFromAssertNonNull(std::move(*doubles)); @@ -310,7 +435,6 @@ base::StatusOr> RuntimeTable::Builder::Build( flags, i, 0); storage_layers[i].reset(new column::NumericStorage( &non_null_doubles->vector(), ColumnType::kDouble, is_sorted)); - } else { // The column is nullable. legacy_columns.emplace_back(col_names_[i].c_str(), doubles, @@ -328,11 +452,11 @@ base::StatusOr> RuntimeTable::Builder::Build( ColumnLegacy::Flag::kNonNull, i, 0); storage_layers[i].reset( new column::StringStorage(string_pool_, &strings->vector())); - } else { PERFETTO_FATAL("Unexpected column type"); } } + legacy_columns.push_back(ColumnLegacy::IdColumn( static_cast(legacy_columns.size()), 0, "_auto_id", ColumnLegacy::kIdFlags | ColumnLegacy::Flag::kHidden)); @@ -346,9 +470,15 @@ base::StatusOr> RuntimeTable::Builder::Build( table->col_names_ = std::move(col_names_); table->schema_.columns.reserve(table->columns().size()); - for (const auto& col : table->columns()) { + for (size_t i = 0; i < table->columns().size(); ++i) { + const auto& col = table->columns()[i]; + SqlValue::Type column_type = + col.col_type() != ColumnType::kId && + col.storage_base().non_null_size() == 0 + ? SqlValue::kNull + : ColumnLegacy::ToSqlValueType(col.col_type()); table->schema_.columns.emplace_back( - Schema::Column{col.name(), col.type(), col.IsId(), col.IsSorted(), + Schema::Column{col.name(), column_type, col.IsId(), col.IsSorted(), col.IsHidden(), col.IsSetId()}); } return {std::move(table)}; diff --git a/src/trace_processor/db/runtime_table.h b/src/trace_processor/db/runtime_table.h index 9fea3d9944..42fdb60395 100644 --- a/src/trace_processor/db/runtime_table.h +++ b/src/trace_processor/db/runtime_table.h @@ -51,14 +51,36 @@ class RuntimeTable : public Table { StringStorage, DoubleStorage, NullDoubleStorage>; + enum BuilderColumnType { + kNull, + kInt, + kNullInt, + kString, + kDouble, + kNullDouble + }; + class Builder { public: - Builder(StringPool* pool, std::vector col_names); + Builder(StringPool* pool, const std::vector& col_names); + Builder(StringPool* pool, + const std::vector& col_names, + const std::vector& col_types); base::Status AddNull(uint32_t idx); base::Status AddInteger(uint32_t idx, int64_t res); base::Status AddFloat(uint32_t idx, double res); base::Status AddText(uint32_t idx, const char* ptr); + base::Status AddIntegers(uint32_t idx, int64_t res, uint32_t count); + base::Status AddFloats(uint32_t idx, double res, uint32_t count); + base::Status AddTexts(uint32_t idx, const char* ptr, uint32_t count); + base::Status AddNulls(uint32_t idx, uint32_t count); + + void AddNonNullIntegerUnchecked(uint32_t idx, int64_t res) { + std::get(*storage_[idx]).Append(res); + } + void AddNonNullIntegersUnchecked(uint32_t idx, + const std::vector& res); base::StatusOr> Build(uint32_t rows) &&; diff --git a/src/trace_processor/db/table.cc b/src/trace_processor/db/table.cc index 6ef8b453cc..e3151d9dab 100644 --- a/src/trace_processor/db/table.cc +++ b/src/trace_processor/db/table.cc @@ -61,6 +61,7 @@ Table& Table::operator=(Table&& other) noexcept { overlays_ = std::move(other.overlays_); columns_ = std::move(other.columns_); + indexes_ = std::move(other.indexes_); storage_layers_ = std::move(other.storage_layers_); null_layers_ = std::move(other.null_layers_); diff --git a/src/trace_processor/db/table.h b/src/trace_processor/db/table.h index 7ee4819a8a..0da6e84d00 100644 --- a/src/trace_processor/db/table.h +++ b/src/trace_processor/db/table.h @@ -17,13 +17,19 @@ #ifndef SRC_TRACE_PROCESSOR_DB_TABLE_H_ #define SRC_TRACE_PROCESSOR_DB_TABLE_H_ +#include #include #include +#include #include +#include #include #include #include "perfetto/base/logging.h" +#include "perfetto/base/status.h" +#include "perfetto/ext/base/flat_hash_map.h" +#include "perfetto/ext/base/status_or.h" #include "perfetto/trace_processor/basic_types.h" #include "perfetto/trace_processor/ref_counted.h" #include "src/trace_processor/containers/row_map.h" @@ -35,6 +41,17 @@ namespace perfetto::trace_processor { +namespace { +using OrderedIndices = column::DataLayerChain::OrderedIndices; + +OrderedIndices OrderedIndicesFromIndex(const std::vector& index) { + OrderedIndices o; + o.data = index.data(); + o.size = static_cast(index.size()); + return o; +} +} // namespace + // Represents a table of data with named, strongly typed columns. class Table { public: @@ -138,6 +155,58 @@ class Table { return Iterator(this, std::move(rm)); } + std::optional GetIndex( + const std::vector& cols) const { + for (const auto& idx : indexes_) { + if (cols.size() >= idx.index.size()) { + continue; + } + + if (std::equal(cols.begin(), cols.end(), idx.columns.begin())) { + return OrderedIndicesFromIndex(idx.index); + } + } + return std::nullopt; + } + + // Adds an index onto column. + // Returns an error if index already exists and `!replace`. + base::Status SetIndex(const std::string& name, + std::vector col_idxs, + std::vector index, + bool replace = false) { + for (auto& idx : indexes_) { + if (idx.name == name) { + if (replace) { + idx.columns = std::move(col_idxs); + idx.index = std::move(index); + return base::OkStatus(); + } + return base::ErrStatus( + "Index of this name already exists on this table."); + } + } + + ColumnIndex idx; + idx.name = name; + idx.columns = std::move(col_idxs); + idx.index = std::move(index); + indexes_.push_back(std::move(idx)); + return base::OkStatus(); + } + + // Removes index from the table. + // Returns an error if index doesn't exist. + base::Status DropIndex(const std::string& name) { + for (uint32_t i = 0; i < indexes_.size(); i++) { + if (indexes_[i].name == name) { + indexes_.erase(indexes_.begin() + i); + return base::OkStatus(); + } + } + return base::ErrStatus("Index '%s' not found.", name.c_str()); + } + // Sorts the table using the specified order by constraints. Table Sort(const std::vector&) const; @@ -147,6 +216,19 @@ class Table { // Creates a copy of this table. Table Copy() const; + // Looks for a column in a table. + // TODO(mayzner): This is not a long term function, it should be used with + // caution. + std::optional ColumnIdxFromName(const std::string& col_name) const { + auto x = std::find_if(columns_.begin(), columns_.end(), + [col_name](const ColumnLegacy& col) { + return col_name.compare(col.name()) == 0; + }); + + return (x == columns_.end()) ? std::nullopt + : std::make_optional(x->index_in_table()); + } + uint32_t row_count() const { return row_count_; } StringPool* string_pool() const { return string_pool_; } const std::vector& columns() const { return columns_; } @@ -194,6 +276,12 @@ class Table { private: friend class ColumnLegacy; + struct ColumnIndex { + std::string name; + std::vector columns; + std::vector index; + }; + void CreateChains() const; Table CopyExceptOverlays() const; @@ -210,6 +298,8 @@ class Table { std::vector> null_layers_; std::vector> overlay_layers_; mutable std::vector> chains_; + + std::vector indexes_; }; } // namespace perfetto::trace_processor diff --git a/src/trace_processor/export_json_unittest.cc b/src/trace_processor/export_json_unittest.cc index a8f75644a8..90677c2f6e 100644 --- a/src/trace_processor/export_json_unittest.cc +++ b/src/trace_processor/export_json_unittest.cc @@ -27,8 +27,11 @@ #include "perfetto/ext/base/string_utils.h" #include "perfetto/ext/base/temp_file.h" #include "src/trace_processor/importers/common/args_tracker.h" +#include "src/trace_processor/importers/common/cpu_tracker.h" #include "src/trace_processor/importers/common/event_tracker.h" +#include "src/trace_processor/importers/common/machine_tracker.h" #include "src/trace_processor/importers/common/metadata_tracker.h" +#include "src/trace_processor/importers/common/process_track_translation_table.h" #include "src/trace_processor/importers/common/process_tracker.h" #include "src/trace_processor/importers/common/track_tracker.h" #include "src/trace_processor/importers/proto/track_event_tracker.h" @@ -76,9 +79,13 @@ class ExportJsonTest : public ::testing::Test { context_.args_tracker.reset(new ArgsTracker(&context_)); context_.event_tracker.reset(new EventTracker(&context_)); context_.track_tracker.reset(new TrackTracker(&context_)); + context_.machine_tracker.reset(new MachineTracker(&context_, 0)); + context_.cpu_tracker.reset(new CpuTracker(&context_)); context_.metadata_tracker.reset( new MetadataTracker(context_.storage.get())); context_.process_tracker.reset(new ProcessTracker(&context_)); + context_.process_track_translation_table.reset( + new ProcessTrackTranslationTable(context_.storage.get())); } std::string ToJson(ArgumentFilterPredicate argument_filter = nullptr, @@ -391,10 +398,11 @@ TEST_F(ExportJsonTest, StorageWithChromeMetadata) { TraceStorage* storage = context_.storage.get(); - RawId id = - storage->mutable_raw_table() - ->Insert({0, storage->InternString("chrome_event.metadata"), 0, 0}) - .id; + auto ucpu = context_.cpu_tracker->GetOrCreateCpu(0); + RawId id = storage->mutable_raw_table() + ->Insert({0, storage->InternString("chrome_event.metadata"), 0, + 0, 0, ucpu}) + .id; StringId name1_id = storage->InternString(base::StringView(kName1)); StringId name2_id = storage->InternString(base::StringView(kName2)); @@ -1388,9 +1396,10 @@ TEST_F(ExportJsonTest, RawEvent) { UniquePid upid = context_.process_tracker->GetOrCreateProcess(kProcessID); context_.storage->mutable_thread_table()->mutable_upid()->Set(utid, upid); + auto ucpu = context_.cpu_tracker->GetOrCreateCpu(0); auto id_and_row = storage->mutable_raw_table()->Insert( - {kTimestamp, storage->InternString("track_event.legacy_event"), /*cpu=*/0, - utid}); + {kTimestamp, storage->InternString("track_event.legacy_event"), utid, 0, + 0, ucpu}); auto inserter = context_.args_tracker->AddArgsTo(id_and_row.id); auto add_arg = [&](const char* key, Variadic value) { diff --git a/src/trace_processor/forwarding_trace_parser.cc b/src/trace_processor/forwarding_trace_parser.cc index 466ee96dc8..4a600c53a7 100644 --- a/src/trace_processor/forwarding_trace_parser.cc +++ b/src/trace_processor/forwarding_trace_parser.cc @@ -16,29 +16,25 @@ #include "src/trace_processor/forwarding_trace_parser.h" +#include +#include + #include "perfetto/base/logging.h" -#include "perfetto/ext/base/string_utils.h" +#include "perfetto/base/status.h" +#include "perfetto/ext/base/status_or.h" +#include "src/trace_processor/importers/common/chunked_trace_reader.h" #include "src/trace_processor/importers/common/process_tracker.h" #include "src/trace_processor/importers/proto/proto_trace_reader.h" #include "src/trace_processor/sorter/trace_sorter.h" +#include "src/trace_processor/trace_reader_registry.h" #include "src/trace_processor/types/trace_processor_context.h" +#include "src/trace_processor/util/status_macros.h" +#include "src/trace_processor/util/trace_type.h" namespace perfetto { namespace trace_processor { namespace { -const char kNoZlibErr[] = - "Cannot open compressed trace. zlib not enabled in the build config"; - -inline bool isspace(unsigned char c) { - return ::isspace(c); -} - -std::string RemoveWhitespace(std::string str) { - str.erase(std::remove_if(str.begin(), str.end(), isspace), str.end()); - return str; -} - TraceSorter::SortingMode ConvertSortingMode(SortingMode sorting_mode) { switch (sorting_mode) { case SortingMode::kDefaultHeuristics: @@ -50,10 +46,37 @@ TraceSorter::SortingMode ConvertSortingMode(SortingMode sorting_mode) { PERFETTO_FATAL("For GCC"); } -// Fuchsia traces have a magic number as documented here: -// https://fuchsia.googlesource.com/fuchsia/+/HEAD/docs/development/tracing/trace-format/README.md#magic-number-record-trace-info-type-0 -constexpr uint64_t kFuchsiaMagicNumber = 0x0016547846040010; -constexpr char kPerfMagic[] = "PERFILE2"; +std::optional GetMinimumSortingMode( + TraceType trace_type, + const TraceProcessorContext& context) { + switch (trace_type) { + case kNinjaLogTraceType: + case kSystraceTraceType: + case kGzipTraceType: + case kCtraceTraceType: + return std::nullopt; + + case kPerfDataTraceType: + return TraceSorter::SortingMode::kDefault; + + case kUnknownTraceType: + case kJsonTraceType: + case kFuchsiaTraceType: + case kZipFile: + case kAndroidLogcatTraceType: + return TraceSorter::SortingMode::kFullSort; + + case kProtoTraceType: + case kSymbolsTraceType: + return ConvertSortingMode(context.config.sorting_mode); + + case kAndroidDumpstateTraceType: + case kAndroidBugreportTraceType: + PERFETTO_FATAL( + "This trace type should be handled at the ZipParser level"); + } + PERFETTO_FATAL("For GCC"); +} } // namespace @@ -62,104 +85,67 @@ ForwardingTraceParser::ForwardingTraceParser(TraceProcessorContext* context) ForwardingTraceParser::~ForwardingTraceParser() {} +base::Status ForwardingTraceParser::Init(const TraceBlobView& blob) { + PERFETTO_CHECK(!reader_); + + { + auto scoped_trace = context_->storage->TraceExecutionTimeIntoStats( + stats::guess_trace_type_duration_ns); + trace_type_ = GuessTraceType(blob.data(), blob.size()); + } + if (trace_type_ == kUnknownTraceType) { + // If renaming this error message don't remove the "(ERR:fmt)" part. + // The UI's error_dialog.ts uses it to make the dialog more graceful. + return base::ErrStatus("Unknown trace type provided (ERR:fmt)"); + } + + base::StatusOr> reader_or = + context_->reader_registry->CreateTraceReader(trace_type_); + if (!reader_or.ok()) { + return reader_or.status(); + } + reader_ = std::move(*reader_or); + + PERFETTO_DLOG("%s trace detected", TraceTypeToString(trace_type_)); + UpdateSorterForTraceType(trace_type_); + + // TODO(b/334978369) Make sure kProtoTraceType and kSystraceTraceType are + // parsed first so that we do not get issues with + // SetPidZeroIsUpidZeroIdleProcess() + if (trace_type_ == kProtoTraceType || trace_type_ == kSystraceTraceType) { + context_->process_tracker->SetPidZeroIsUpidZeroIdleProcess(); + } + + return base::OkStatus(); +} + +void ForwardingTraceParser::UpdateSorterForTraceType(TraceType trace_type) { + std::optional minimum_sorting_mode = + GetMinimumSortingMode(trace_type, *context_); + if (!minimum_sorting_mode.has_value()) { + return; + } + + if (!context_->sorter) { + context_->sorter.reset(new TraceSorter(context_, *minimum_sorting_mode)); + } + + switch (context_->sorter->sorting_mode()) { + case TraceSorter::SortingMode::kDefault: + PERFETTO_CHECK(minimum_sorting_mode == + TraceSorter::SortingMode::kDefault); + break; + case TraceSorter::SortingMode::kFullSort: + break; + } +} + base::Status ForwardingTraceParser::Parse(TraceBlobView blob) { // If this is the first Parse() call, guess the trace type and create the // appropriate parser. if (!reader_) { - TraceType trace_type; - { - auto scoped_trace = context_->storage->TraceExecutionTimeIntoStats( - stats::guess_trace_type_duration_ns); - trace_type = GuessTraceType(blob.data(), blob.size()); - context_->trace_type = trace_type; - } - switch (trace_type) { - case kJsonTraceType: { - PERFETTO_DLOG("JSON trace detected"); - if (context_->json_trace_tokenizer && context_->json_trace_parser) { - reader_ = std::move(context_->json_trace_tokenizer); - - // JSON traces have no guarantees about the order of events in them. - context_->sorter.reset( - new TraceSorter(context_, TraceSorter::SortingMode::kFullSort)); - break; - } - return base::ErrStatus("JSON support is disabled"); - } - case kProtoTraceType: { - PERFETTO_DLOG("Proto trace detected"); - auto sorting_mode = ConvertSortingMode(context_->config.sorting_mode); - reader_.reset(new ProtoTraceReader(context_)); - context_->sorter.reset(new TraceSorter(context_, sorting_mode)); - context_->process_tracker->SetPidZeroIsUpidZeroIdleProcess(); - break; - } - case kNinjaLogTraceType: { - PERFETTO_DLOG("Ninja log detected"); - if (context_->ninja_log_parser) { - reader_ = std::move(context_->ninja_log_parser); - break; - } - return base::ErrStatus("Ninja support is disabled"); - } - case kFuchsiaTraceType: { - PERFETTO_DLOG("Fuchsia trace detected"); - if (context_->fuchsia_record_parser && - context_->fuchsia_trace_tokenizer) { - reader_ = std::move(context_->fuchsia_trace_tokenizer); - - // Fuschia traces can have massively out of order events. - context_->sorter.reset( - new TraceSorter(context_, TraceSorter::SortingMode::kFullSort)); - break; - } - return base::ErrStatus("Fuchsia support is disabled"); - } - case kSystraceTraceType: - PERFETTO_DLOG("Systrace trace detected"); - context_->process_tracker->SetPidZeroIsUpidZeroIdleProcess(); - if (context_->systrace_trace_parser) { - reader_ = std::move(context_->systrace_trace_parser); - break; - } - return base::ErrStatus("Systrace support is disabled"); - case kGzipTraceType: - case kCtraceTraceType: - if (trace_type == kGzipTraceType) { - PERFETTO_DLOG("gzip trace detected"); - } else { - PERFETTO_DLOG("ctrace trace detected"); - } - if (context_->gzip_trace_parser) { - reader_ = std::move(context_->gzip_trace_parser); - break; - } - return base::ErrStatus(kNoZlibErr); - case kAndroidBugreportTraceType: - PERFETTO_DLOG("Android Bugreport detected"); - if (context_->android_bugreport_parser) { - reader_ = std::move(context_->android_bugreport_parser); - break; - } - return base::ErrStatus("Android Bugreport support is disabled. %s", - kNoZlibErr); - case kPerfDataTraceType: - PERFETTO_DLOG("perf data detected"); - if (context_->perf_data_trace_tokenizer && - context_->perf_record_parser) { - reader_ = std::move(context_->perf_data_trace_tokenizer); - context_->sorter.reset( - new TraceSorter(context_, TraceSorter::SortingMode::kDefault)); - break; - } - return base::ErrStatus("perf.data parsing support is disabled."); - case kUnknownTraceType: - // If renaming this error message don't remove the "(ERR:fmt)" part. - // The UI's error_dialog.ts uses it to make the dialog more graceful. - return base::ErrStatus("Unknown trace type provided (ERR:fmt)"); - } + RETURN_IF_ERROR(Init(blob)); } - return reader_->Parse(std::move(blob)); } @@ -167,71 +153,5 @@ void ForwardingTraceParser::NotifyEndOfFile() { reader_->NotifyEndOfFile(); } -TraceType GuessTraceType(const uint8_t* data, size_t size) { - if (size == 0) - return kUnknownTraceType; - std::string start(reinterpret_cast(data), - std::min(size, kGuessTraceMaxLookahead)); - if (size >= 8) { - uint64_t first_word; - memcpy(&first_word, data, sizeof(first_word)); - if (first_word == kFuchsiaMagicNumber) - return kFuchsiaTraceType; - } - if (base::StartsWith(start, kPerfMagic)) { - return kPerfDataTraceType; - } - std::string start_minus_white_space = RemoveWhitespace(start); - if (base::StartsWith(start_minus_white_space, "{\"")) - return kJsonTraceType; - if (base::StartsWith(start_minus_white_space, "[{\"")) - return kJsonTraceType; - - // Systrace with header but no leading HTML. - if (base::Contains(start, "# tracer")) - return kSystraceTraceType; - - // Systrace with leading HTML. - // Both: and have been observed. - std::string lower_start = base::ToLower(start); - if (base::StartsWith(lower_start, "") || - base::StartsWith(lower_start, "")) - return kSystraceTraceType; - - // Traces obtained from atrace -z (compress). - // They all have the string "TRACE:" followed by 78 9C which is a zlib header - // for "deflate, default compression, window size=32K" (see b/208691037) - if (base::Contains(start, "TRACE:\n\x78\x9c")) - return kCtraceTraceType; - - // Traces obtained from atrace without -z (no compression). - if (base::Contains(start, "TRACE:\n")) - return kSystraceTraceType; - - // Ninja's build log (.ninja_log). - if (base::StartsWith(start, "# ninja log")) - return kNinjaLogTraceType; - - // Systrace with no header or leading HTML. - if (base::StartsWith(start, " ")) - return kSystraceTraceType; - - // gzip'ed trace containing one of the other formats. - if (base::StartsWith(start, "\x1f\x8b")) - return kGzipTraceType; - - if (base::StartsWith(start, "\x0a")) - return kProtoTraceType; - - // Android bugreport.zip - // TODO(primiano). For now we assume any .zip file is a bugreport. In future, - // if we want to support different trace formats based on a .zip arachive we - // will need an extra layer similar to what we did kGzipTraceType. - if (base::StartsWith(start, "PK\x03\x04")) - return kAndroidBugreportTraceType; - - return kUnknownTraceType; -} - } // namespace trace_processor } // namespace perfetto diff --git a/src/trace_processor/forwarding_trace_parser.h b/src/trace_processor/forwarding_trace_parser.h index e63a272d41..59f0a04cab 100644 --- a/src/trace_processor/forwarding_trace_parser.h +++ b/src/trace_processor/forwarding_trace_parser.h @@ -17,16 +17,16 @@ #ifndef SRC_TRACE_PROCESSOR_FORWARDING_TRACE_PARSER_H_ #define SRC_TRACE_PROCESSOR_FORWARDING_TRACE_PARSER_H_ -#include "src/trace_processor/importers/common/chunked_trace_reader.h" - -#include "src/trace_processor/types/trace_processor_context.h" +#include -namespace perfetto { -namespace trace_processor { +#include "perfetto/base/status.h" +#include "perfetto/trace_processor/trace_blob_view.h" +#include "src/trace_processor/importers/common/chunked_trace_reader.h" +#include "src/trace_processor/util/trace_type.h" -constexpr size_t kGuessTraceMaxLookahead = 64; +namespace perfetto::trace_processor { -TraceType GuessTraceType(const uint8_t* data, size_t size); +class TraceProcessorContext; class ForwardingTraceParser : public ChunkedTraceReader { public: @@ -34,15 +34,19 @@ class ForwardingTraceParser : public ChunkedTraceReader { ~ForwardingTraceParser() override; // ChunkedTraceReader implementation - util::Status Parse(TraceBlobView) override; + base::Status Parse(TraceBlobView) override; void NotifyEndOfFile() override; + TraceType trace_type() const { return trace_type_; } + private: + base::Status Init(const TraceBlobView&); + void UpdateSorterForTraceType(TraceType trace_type); TraceProcessorContext* const context_; std::unique_ptr reader_; + TraceType trace_type_ = kUnknownTraceType; }; -} // namespace trace_processor -} // namespace perfetto +} // namespace perfetto::trace_processor #endif // SRC_TRACE_PROCESSOR_FORWARDING_TRACE_PARSER_H_ diff --git a/src/trace_processor/forwarding_trace_parser_unittest.cc b/src/trace_processor/forwarding_trace_parser_unittest.cc index 74cb631247..42d577d783 100644 --- a/src/trace_processor/forwarding_trace_parser_unittest.cc +++ b/src/trace_processor/forwarding_trace_parser_unittest.cc @@ -16,6 +16,7 @@ #include "src/trace_processor/forwarding_trace_parser.h" +#include "src/trace_processor/util/trace_type.h" #include "test/gtest_and_gmock.h" namespace perfetto { diff --git a/src/trace_processor/importers/android_bugreport/BUILD.gn b/src/trace_processor/importers/android_bugreport/BUILD.gn index f5721ef75b..045a609209 100644 --- a/src/trace_processor/importers/android_bugreport/BUILD.gn +++ b/src/trace_processor/importers/android_bugreport/BUILD.gn @@ -15,36 +15,64 @@ import("../../../../gn/perfetto.gni") import("../../../../gn/test.gni") +source_set("android_log_event") { + sources = [ + "android_log_event.cc", + "android_log_event.h", + ] + deps = [ + "../../../../gn:default_deps", + "../../../../include/perfetto/ext/base:base", + "../../containers", + ] +} + source_set("android_bugreport") { sources = [ - "android_bugreport_parser.cc", - "android_bugreport_parser.h", - "android_log_parser.cc", - "android_log_parser.h", + "android_bugreport_reader.cc", + "android_bugreport_reader.h", + "android_dumpstate_reader.cc", + "android_dumpstate_reader.h", + "android_log_event_parser_impl.cc", + "android_log_event_parser_impl.h", + "android_log_reader.cc", + "android_log_reader.h", + "chunked_line_reader.cc", + "chunked_line_reader.h", ] deps = [ + ":android_log_event", "../../../../gn:default_deps", "../../../../include/perfetto/trace_processor", "../../../../include/perfetto/trace_processor:basic_types", "../../../../protos/perfetto/common:zero", "../../../../protos/perfetto/trace:zero", "../../../base", + "../../sorter:sorter", "../../storage", + "../../tables:tables_python", "../../types", + "../../util:trace_type", "../../util:zip_reader", "../common", ] } perfetto_unittest_source_set("unittests") { - sources = [ "android_log_parser_unittest.cc" ] + sources = [ "android_log_unittest.cc" ] testonly = true deps = [ ":android_bugreport", + ":android_log_event", "../../../../gn:default_deps", "../../../../gn:gtest_and_gmock", + "../../../../include/perfetto/trace_processor:storage", "../../../../protos/perfetto/common:zero", + "../../../../protos/perfetto/trace:zero", "../../../base", + "../../sorter", "../../storage", + "../../types", + "../common", ] } diff --git a/src/trace_processor/importers/android_bugreport/android_bugreport_parser.cc b/src/trace_processor/importers/android_bugreport/android_bugreport_parser.cc deleted file mode 100644 index 87aa0f5f9a..0000000000 --- a/src/trace_processor/importers/android_bugreport/android_bugreport_parser.cc +++ /dev/null @@ -1,266 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/trace_processor/importers/android_bugreport/android_bugreport_parser.h" - -#include -#include - -#include "perfetto/base/logging.h" -#include "perfetto/ext/base/string_utils.h" -#include "perfetto/trace_processor/trace_blob.h" -#include "perfetto/trace_processor/trace_blob_view.h" -#include "src/trace_processor/importers/android_bugreport/android_log_parser.h" -#include "src/trace_processor/importers/common/clock_tracker.h" -#include "src/trace_processor/importers/common/process_tracker.h" -#include "src/trace_processor/importers/common/track_tracker.h" -#include "src/trace_processor/types/trace_processor_context.h" -#include "src/trace_processor/util/zip_reader.h" - -#include "protos/perfetto/common/builtin_clock.pbzero.h" - -namespace perfetto { -namespace trace_processor { - -AndroidBugreportParser::AndroidBugreportParser(TraceProcessorContext* ctx) - : context_(ctx), zip_reader_(new util::ZipReader()) {} - -AndroidBugreportParser::~AndroidBugreportParser() = default; - -util::Status AndroidBugreportParser::Parse(TraceBlobView tbv) { - if (!first_chunk_seen_) { - first_chunk_seen_ = true; - // All logs in Android bugreports use wall time (which creates problems - // in case of early boot events before NTP kicks in, which get emitted as - // 1970), but that is the state of affairs. - context_->clock_tracker->SetTraceTimeClock( - protos::pbzero::BUILTIN_CLOCK_REALTIME); - } - - return zip_reader_->Parse(tbv.data(), tbv.size()); -} - -void AndroidBugreportParser::NotifyEndOfFile() { - if (!DetectYearAndBrFilename()) { - context_->storage->IncrementStats(stats::android_br_parse_errors); - return; - } - - ParsePersistentLogcat(); - ParseDumpstateTxt(); - SortAndStoreLogcat(); -} - -void AndroidBugreportParser::ParseDumpstateTxt() { - // Dumpstate is organized in a two level hierarchy, beautifully flattened into - // one text file with load bearing ----- markers: - // 1. Various dumpstate sections, examples: - // ``` - // ------ DUMPSYS CRITICAL (/system/bin/dumpsys) ------ - // ... - // ------ SYSTEM LOG (logcat -v threadtime -v printable -v uid) ------ - // ... - // ------ IPTABLES (iptables -L -nvx) ------ - // ... - // ------ DUMPSYS HIGH (/system/bin/dumpsys) ------ - // ... - // ------ DUMPSYS (/system/bin/dumpsys) ------ - // ``` - // - // 2. Within the "------ DUMPSYS" section (note dumpsys != dumpstate), there - // are multiple services. Note that there are at least 3 DUMPSYS sections - // (CRITICAL, HIGH and default), with multiple services in each: - // ``` - // ------ DUMPSYS (/system/bin/dumpsys) ------ - // DUMP OF SERVICE activity: - // ... - // --------------------------------------------------------------------------- - // DUMP OF SERVICE input_method: - // ... - // --------------------------------------------------------------------------- - // ``` - // Here we put each line in a dedicated table, android_dumpstate, keeping - // track of the dumpstate `section` and dumpsys `service`. - AndroidLogParser log_parser(br_year_, context_->storage.get()); - util::ZipFile* zf = zip_reader_->Find(dumpstate_fname_); - StringId section_id = StringId::Null(); // The current dumpstate section. - StringId service_id = StringId::Null(); // The current dumpsys service. - static constexpr size_t npos = base::StringView::npos; - enum { OTHER = 0, DUMPSYS, LOG } cur_sect = OTHER; - zf->DecompressLines([&](const std::vector& lines) { - // Optimization for ParseLogLines() below. Avoids ctor/dtor-ing a new vector - // on every line. - std::vector log_line(1); - for (const base::StringView& line : lines) { - if (line.StartsWith("------ ") && line.EndsWith(" ------")) { - // These lines mark the beginning and end of dumpstate sections: - // ------ DUMPSYS CRITICAL (/system/bin/dumpsys) ------ - // ------ 0.356s was the duration of 'DUMPSYS CRITICAL' ------ - base::StringView section = line.substr(7); - section = section.substr(0, section.size() - 7); - bool end_marker = section.find("was the duration of") != npos; - service_id = StringId::Null(); - if (end_marker) { - section_id = StringId::Null(); - } else { - section_id = context_->storage->InternString(section); - cur_sect = OTHER; - if (section.StartsWith("DUMPSYS")) { - cur_sect = DUMPSYS; - } else if (section.StartsWith("SYSTEM LOG") || - section.StartsWith("EVENT LOG") || - section.StartsWith("RADIO LOG")) { - // KERNEL LOG is deliberately omitted because SYSTEM LOG is a - // superset. KERNEL LOG contains all dupes. - cur_sect = LOG; - } else if (section.StartsWith("BLOCK STAT")) { - // Coalesce all the block stats into one section. Otherwise they - // pollute the table with one section per block device. - section_id = context_->storage->InternString("BLOCK STAT"); - } - } - continue; - } - // Skip end marker lines for dumpsys sections. - if (cur_sect == DUMPSYS && line.StartsWith("--------- ") && - line.find("was the duration of dumpsys") != npos) { - service_id = StringId::Null(); - continue; - } - if (cur_sect == DUMPSYS && service_id.is_null() && - line.StartsWith("----------------------------------------------")) { - continue; - } - if (cur_sect == DUMPSYS && line.StartsWith("DUMP OF SERVICE")) { - // DUMP OF SERVICE [CRITICAL|HIGH] ServiceName: - base::StringView svc = line.substr(line.rfind(' ') + 1); - svc = svc.substr(0, svc.size() - 1); - service_id = context_->storage->InternString(svc); - } else if (cur_sect == LOG) { - // Parse the non-persistent logcat and append to `log_events_`, together - // with the persistent one previously parsed by ParsePersistentLogcat(). - // Skips entries that are already seen in the persistent logcat, - // handling us vs ms truncation. - PERFETTO_DCHECK(log_line.size() == 1); - log_line[0] = line; - log_parser.ParseLogLines(log_line, &log_events_, - log_events_last_sorted_idx_); - } - - if (build_fpr_.empty() && line.StartsWith("Build fingerprint:")) { - build_fpr_ = line.substr(20, line.size() - 20).ToStdString(); - } - - // Append the line to the android_dumpstate table. - context_->storage->mutable_android_dumpstate_table()->Insert( - {section_id, service_id, context_->storage->InternString(line)}); - } - }); -} - -void AndroidBugreportParser::ParsePersistentLogcat() { - // 1. List logcat files in reverse timestmap order (old to most recent). - // 2. Decode events from log lines into a vector. Dedupe and intern strings. - // 3. Globally sort all extracted events. - // 4. Insert into the android_logs table. - - AndroidLogParser log_parser(br_year_, context_->storage.get()); - - // Sort files to ease the job of the subsequent line-based sort. Unfortunately - // lines within each file are not 100% timestamp-ordered, due to things like - // kernel messages where log time != event time. - std::vector> log_paths; - for (const util::ZipFile& zf : zip_reader_->files()) { - if (base::StartsWith(zf.name(), "FS/data/misc/logd/logcat") && - !base::EndsWith(zf.name(), "logcat.id")) { - log_paths.emplace_back(std::make_pair(zf.GetDatetime(), zf.name())); - } - } - std::sort(log_paths.begin(), log_paths.end()); - - // Push all events into the AndroidLogParser. It will take care of string - // interning into the pool. Appends entries into `log_events`. - for (const auto& kv : log_paths) { - util::ZipFile* zf = zip_reader_->Find(kv.second); - zf->DecompressLines([&](const std::vector& lines) { - log_parser.ParseLogLines(lines, &log_events_); - }); - } - - // Do an initial sorting pass. This is not the final sorting because we - // haven't ingested the latest logs from dumpstate yet. But we need this sort - // to be able to de-dupe the same lines showing both in dumpstate and in the - // persistent log. - SortLogEvents(); -} - -void AndroidBugreportParser::SortAndStoreLogcat() { - // Sort the union of all log events parsed from both /data/misc/logd - // (persistent logcat on disk) and the dumpstate file (last in-memory logcat). - // Before the std::stable_sort, entries in `log_events_` are already "mostly" - // sorted, because we processed files in order (see notes above about kernel - // logs on why we need a final sort here). - // We need stable-sort to preserve FIFO-ness of events emitted at the same - // time, logcat is not granular enough (us for persistent, ms for dumpstate). - SortLogEvents(); - - // Insert the globally sorted events into the android_logs table. - for (const auto& e : log_events_) { - UniquePid utid = context_->process_tracker->UpdateThread(e.tid, e.pid); - context_->storage->mutable_android_log_table()->Insert( - {e.ts, utid, e.prio, e.tag, e.msg}); - } -} - -// Populates the `year_` field from the bugreport-xxx.txt file name. -// This is because logcat events have only the month and day. -// This is obviously bugged for cases of bugreports collected across new year -// but we'll live with that. -bool AndroidBugreportParser::DetectYearAndBrFilename() { - const util::ZipFile* br_file = nullptr; - for (const auto& zf : zip_reader_->files()) { - if (base::StartsWith(zf.name(), "bugreport-") && - base::EndsWith(zf.name(), ".txt")) { - br_file = &zf; - break; - } - } - - if (!br_file) { - PERFETTO_ELOG("Could not find bugreport-*.txt in the zip file"); - return false; - } - - // Typical name: "bugreport-product-TP1A.220623.001-2022-06-24-16-24-37.txt". - auto year_str = br_file->name().substr( - br_file->name().size() - strlen("2022-12-31-23-59-00.txt"), 4); - std::optional year = base::StringToInt32(year_str); - if (!year.has_value()) { - PERFETTO_ELOG("Could not parse the year from %s", br_file->name().c_str()); - return false; - } - br_year_ = *year; - dumpstate_fname_ = br_file->name(); - return true; -} - -void AndroidBugreportParser::SortLogEvents() { - std::stable_sort(log_events_.begin(), log_events_.end()); - log_events_last_sorted_idx_ = log_events_.size(); -} - -} // namespace trace_processor -} // namespace perfetto diff --git a/src/trace_processor/importers/android_bugreport/android_bugreport_parser.h b/src/trace_processor/importers/android_bugreport/android_bugreport_parser.h deleted file mode 100644 index 99691598f1..0000000000 --- a/src/trace_processor/importers/android_bugreport/android_bugreport_parser.h +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_ANDROID_BUGREPORT_ANDROID_BUGREPORT_PARSER_H_ -#define SRC_TRACE_PROCESSOR_IMPORTERS_ANDROID_BUGREPORT_ANDROID_BUGREPORT_PARSER_H_ - -#include "src/trace_processor/importers/common/chunked_trace_reader.h" -#include "src/trace_processor/storage/trace_storage.h" - -namespace perfetto { -namespace trace_processor { - -namespace util { -class ZipReader; -} - -struct AndroidLogEvent; -class TraceProcessorContext; - -// Trace importer for Android bugreport.zip archives. -class AndroidBugreportParser : public ChunkedTraceReader { - public: - explicit AndroidBugreportParser(TraceProcessorContext*); - ~AndroidBugreportParser() override; - - // ChunkedTraceReader implementation. - util::Status Parse(TraceBlobView) override; - void NotifyEndOfFile() override; - - private: - bool DetectYearAndBrFilename(); - void ParsePersistentLogcat(); - void ParseDumpstateTxt(); - void SortAndStoreLogcat(); - void SortLogEvents(); - - TraceProcessorContext* const context_; - int br_year_ = 0; // The year when the bugreport has been taken. - std::string dumpstate_fname_; // The name of bugreport-xxx-2022-08-04....txt - std::string build_fpr_; - bool first_chunk_seen_ = false; - std::unique_ptr zip_reader_; - std::vector log_events_; - size_t log_events_last_sorted_idx_ = 0; -}; - -} // namespace trace_processor -} // namespace perfetto - -#endif // SRC_TRACE_PROCESSOR_IMPORTERS_ANDROID_BUGREPORT_ANDROID_BUGREPORT_PARSER_H_ diff --git a/src/trace_processor/importers/android_bugreport/android_bugreport_reader.cc b/src/trace_processor/importers/android_bugreport/android_bugreport_reader.cc new file mode 100644 index 0000000000..abded5f57a --- /dev/null +++ b/src/trace_processor/importers/android_bugreport/android_bugreport_reader.cc @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "src/trace_processor/importers/android_bugreport/android_bugreport_reader.h" + +#include +#include +#include +#include +#include +#include + +#include "perfetto/base/logging.h" +#include "perfetto/base/status.h" +#include "perfetto/ext/base/string_utils.h" +#include "protos/perfetto/common/builtin_clock.pbzero.h" +#include "src/trace_processor/importers/android_bugreport/android_dumpstate_reader.h" +#include "src/trace_processor/importers/android_bugreport/android_log_reader.h" +#include "src/trace_processor/importers/common/clock_tracker.h" +#include "src/trace_processor/importers/common/trace_file_tracker.h" +#include "src/trace_processor/types/trace_processor_context.h" +#include "src/trace_processor/util/status_macros.h" +#include "src/trace_processor/util/trace_type.h" +#include "src/trace_processor/util/zip_reader.h" + +namespace perfetto::trace_processor { +namespace { +const util::ZipFile* FindBugReportFile( + const std::vector& zip_file_entries) { + for (const auto& zf : zip_file_entries) { + if (base::StartsWith(zf.name(), "bugreport-") && + base::EndsWith(zf.name(), ".txt")) { + return &zf; + } + } + return nullptr; +} + +std::optional ExtractYearFromBugReportFilename( + const std::string& filename) { + // Typical name: "bugreport-product-TP1A.220623.001-2022-06-24-16-24-37.txt". + auto year_str = + filename.substr(filename.size() - strlen("2022-12-31-23-59-00.txt"), 4); + return base::StringToInt32(year_str); +} + +} // namespace + +// static +bool AndroidBugreportReader::IsAndroidBugReport( + const std::vector& zip_file_entries) { + if (const util::ZipFile* file = FindBugReportFile(zip_file_entries); + file != nullptr) { + return ExtractYearFromBugReportFilename(file->name()).has_value(); + } + + return false; +} + +// static +util::Status AndroidBugreportReader::Parse( + TraceProcessorContext* context, + std::vector zip_file_entries) { + if (!IsAndroidBugReport(zip_file_entries)) { + return base::ErrStatus("Not a bug report"); + } + return AndroidBugreportReader(context, std::move(zip_file_entries)) + .ParseImpl(); +} + +AndroidBugreportReader::AndroidBugreportReader( + TraceProcessorContext* context, + std::vector zip_file_entries) + : context_(context), zip_file_entries_(std::move(zip_file_entries)) {} + +AndroidBugreportReader::~AndroidBugreportReader() = default; + +util::Status AndroidBugreportReader::ParseImpl() { + // All logs in Android bugreports use wall time (which creates problems + // in case of early boot events before NTP kicks in, which get emitted as + // 1970), but that is the state of affairs. + context_->clock_tracker->SetTraceTimeClock( + protos::pbzero::BUILTIN_CLOCK_REALTIME); + if (!DetectYearAndBrFilename()) { + context_->storage->IncrementStats(stats::android_br_parse_errors); + return base::ErrStatus("Zip file does not contain bugreport file."); + } + + ASSIGN_OR_RETURN(std::vector logcat_events, + ParsePersistentLogcat()); + return ParseDumpstateTxt(std::move(logcat_events)); +} + +base::Status AndroidBugreportReader::ParseDumpstateTxt( + std::vector logcat_events) { + PERFETTO_CHECK(dumpstate_file_); + ScopedActiveTraceFile trace_file = context_->trace_file_tracker->StartNewFile( + dumpstate_file_->name(), kAndroidDumpstateTraceType, + dumpstate_file_->uncompressed_size()); + AndroidDumpstateReader reader(context_, br_year_, std::move(logcat_events)); + return dumpstate_file_->DecompressLines( + [&](const std::vector& lines) { + for (const base::StringView& line : lines) { + reader.ParseLine(line); + } + }); +} + +base::StatusOr> +AndroidBugreportReader::ParsePersistentLogcat() { + BufferingAndroidLogReader log_reader(context_, br_year_); + + // Sort files to ease the job of the subsequent line-based sort. Unfortunately + // lines within each file are not 100% timestamp-ordered, due to things like + // kernel messages where log time != event time. + std::vector> log_files; + for (const util::ZipFile& zf : zip_file_entries_) { + if (base::StartsWith(zf.name(), "FS/data/misc/logd/logcat") && + !base::EndsWith(zf.name(), "logcat.id")) { + log_files.push_back(std::make_pair(zf.GetDatetime(), &zf)); + } + } + + std::sort(log_files.begin(), log_files.end()); + + // Push all events into the AndroidLogParser. It will take care of string + // interning into the pool. Appends entries into `log_events`. + for (const auto& log_file : log_files) { + ScopedActiveTraceFile trace_file = + context_->trace_file_tracker->StartNewFile( + log_file.second->name(), kAndroidLogcatTraceType, + log_file.second->uncompressed_size()); + RETURN_IF_ERROR(log_file.second->DecompressLines( + [&](const std::vector& lines) { + for (const auto& line : lines) { + log_reader.ParseLine(line); + } + })); + } + + return std::move(log_reader).ConsumeBufferedEvents(); +} + +// Populates the `year_` field from the bugreport-xxx.txt file name. +// This is because logcat events have only the month and day. +// This is obviously bugged for cases of bugreports collected across new year +// but we'll live with that. +bool AndroidBugreportReader::DetectYearAndBrFilename() { + const util::ZipFile* br_file = FindBugReportFile(zip_file_entries_); + if (!br_file) { + PERFETTO_ELOG("Could not find bugreport-*.txt in the zip file"); + return false; + } + + std::optional year = + ExtractYearFromBugReportFilename(br_file->name()); + if (!year.has_value()) { + PERFETTO_ELOG("Could not parse the year from %s", br_file->name().c_str()); + return false; + } + br_year_ = *year; + dumpstate_file_ = br_file; + return true; +} + +} // namespace perfetto::trace_processor diff --git a/src/trace_processor/importers/android_bugreport/android_bugreport_reader.h b/src/trace_processor/importers/android_bugreport/android_bugreport_reader.h new file mode 100644 index 0000000000..ddcec9a7aa --- /dev/null +++ b/src/trace_processor/importers/android_bugreport/android_bugreport_reader.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_ANDROID_BUGREPORT_ANDROID_BUGREPORT_READER_H_ +#define SRC_TRACE_PROCESSOR_IMPORTERS_ANDROID_BUGREPORT_ANDROID_BUGREPORT_READER_H_ + +#include +#include +#include + +#include "perfetto/base/status.h" +#include "perfetto/ext/base/status_or.h" +#include "perfetto/trace_processor/status.h" +#include "src/trace_processor/importers/android_bugreport/android_log_reader.h" +#include "src/trace_processor/util/zip_reader.h" + +namespace perfetto ::trace_processor { + +namespace util { +class ZipReader; +} + +class TraceProcessorContext; + +// Trace importer for Android bugreport.zip archives. +class AndroidBugreportReader { + public: + static bool IsAndroidBugReport( + const std::vector& zip_file_entries); + static util::Status Parse(TraceProcessorContext* context, + std::vector zip_file_entries); + + private: + AndroidBugreportReader(TraceProcessorContext* context, + std::vector zip_file_entries); + ~AndroidBugreportReader(); + util::Status ParseImpl(); + + bool DetectYearAndBrFilename(); + base::StatusOr> + ParsePersistentLogcat(); + base::Status ParseDumpstateTxt(std::vector); + + TraceProcessorContext* const context_; + std::vector zip_file_entries_; + int32_t br_year_ = 0; // The year when the bugreport has been taken. + const util::ZipFile* dumpstate_file_ = + nullptr; // The bugreport-xxx-2022-08-04....txt file + std::string build_fpr_; +}; + +} // namespace perfetto::trace_processor + +#endif // SRC_TRACE_PROCESSOR_IMPORTERS_ANDROID_BUGREPORT_ANDROID_BUGREPORT_READER_H_ diff --git a/src/trace_processor/importers/android_bugreport/android_dumpstate_reader.cc b/src/trace_processor/importers/android_bugreport/android_dumpstate_reader.cc new file mode 100644 index 0000000000..48d9135f6e --- /dev/null +++ b/src/trace_processor/importers/android_bugreport/android_dumpstate_reader.cc @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "src/trace_processor/importers/android_bugreport/android_dumpstate_reader.h" + +#include "perfetto/base/status.h" +#include "src/trace_processor/types/trace_processor_context.h" +#include "src/trace_processor/util/status_macros.h" + +namespace perfetto::trace_processor { + +AndroidDumpstateReader::AndroidDumpstateReader( + TraceProcessorContext* context, + int32_t year, + std::vector logcat_events) + : context_(context), + log_reader_(context, std::move(year), std::move(logcat_events)) {} + +AndroidDumpstateReader::~AndroidDumpstateReader() = default; + +util::Status AndroidDumpstateReader::ParseLine(base::StringView line) { + // Dumpstate is organized in a two level hierarchy, beautifully flattened into + // one text file with load bearing ----- markers: + // 1. Various dumpstate sections, examples: + // ``` + // ------ DUMPSYS CRITICAL (/system/bin/dumpsys) ------ + // ... + // ------ SYSTEM LOG (logcat -v threadtime -v printable -v uid) ------ + // ... + // ------ IPTABLES (iptables -L -nvx) ------ + // ... + // ------ DUMPSYS HIGH (/system/bin/dumpsys) ------ + // ... + // ------ DUMPSYS (/system/bin/dumpsys) ------ + // ``` + // + // 2. Within the "------ DUMPSYS" section (note dumpsys != dumpstate), there + // are multiple services. Note that there are at least 3 DUMPSYS sections + // (CRITICAL, HIGH and default), with multiple services in each: + // ``` + // ------ DUMPSYS (/system/bin/dumpsys) ------ + // DUMP OF SERVICE activity: + // ... + // --------------------------------------------------------------------------- + // DUMP OF SERVICE input_method: + // ... + // --------------------------------------------------------------------------- + // ``` + // Here we put each line in a dedicated table, android_dumpstate, keeping + // track of the dumpstate `section` and dumpsys `service`. + static constexpr size_t npos = base::StringView::npos; + if (line.StartsWith("------ ") && line.EndsWith(" ------")) { + // These lines mark the beginning and end of dumpstate sections: + // ------ DUMPSYS CRITICAL (/system/bin/dumpsys) ------ + // ------ 0.356s was the duration of 'DUMPSYS CRITICAL' ------ + base::StringView section = line.substr(7); + section = section.substr(0, section.size() - 7); + bool end_marker = section.find("was the duration of") != npos; + current_service_id_ = StringId::Null(); + if (end_marker) { + current_section_id_ = StringId::Null(); + } else { + current_section_id_ = context_->storage->InternString(section); + current_section_ = Section::kOther; + if (section.StartsWith("DUMPSYS")) { + current_section_ = Section::kDumpsys; + } else if (section.StartsWith("SYSTEM LOG") || + section.StartsWith("EVENT LOG") || + section.StartsWith("RADIO LOG")) { + // KERNEL LOG is deliberately omitted because SYSTEM LOG is a + // superset. KERNEL LOG contains all dupes. + current_section_ = Section::kLog; + } else if (section.StartsWith("BLOCK STAT")) { + // Coalesce all the block stats into one section. Otherwise they + // pollute the table with one section per block device. + current_section_id_ = context_->storage->InternString("BLOCK STAT"); + } + } + return base::OkStatus(); + } + // Skip end marker lines for dumpsys sections. + if (current_section_ == Section::kDumpsys && line.StartsWith("--------- ") && + line.find("was the duration of dumpsys") != npos) { + current_service_id_ = StringId::Null(); + return base::OkStatus(); + } + if (current_section_ == Section::kDumpsys && current_service_id_.is_null() && + line.StartsWith("----------------------------------------------")) { + return base::OkStatus(); + } + if (current_section_ == Section::kDumpsys && + line.StartsWith("DUMP OF SERVICE")) { + // DUMP OF SERVICE [CRITICAL|HIGH] ServiceName: + base::StringView svc = line.substr(line.rfind(' ') + 1); + svc = svc.substr(0, svc.size() - 1); + current_service_id_ = context_->storage->InternString(svc); + } else if (current_section_ == Section::kLog) { + RETURN_IF_ERROR(log_reader_.ParseLine(line)); + } + + // Append the line to the android_dumpstate table. + context_->storage->mutable_android_dumpstate_table()->Insert( + {current_section_id_, current_service_id_, + context_->storage->InternString(line)}); + + return base::OkStatus(); +} + +void AndroidDumpstateReader::EndOfStream(base::StringView) {} + +} // namespace perfetto::trace_processor diff --git a/src/trace_processor/importers/android_bugreport/android_dumpstate_reader.h b/src/trace_processor/importers/android_bugreport/android_dumpstate_reader.h new file mode 100644 index 0000000000..32abd85360 --- /dev/null +++ b/src/trace_processor/importers/android_bugreport/android_dumpstate_reader.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_ANDROID_BUGREPORT_ANDROID_DUMPSTATE_READER_H_ +#define SRC_TRACE_PROCESSOR_IMPORTERS_ANDROID_BUGREPORT_ANDROID_DUMPSTATE_READER_H_ + +#include "src/trace_processor/importers/android_bugreport/android_log_reader.h" +#include "src/trace_processor/importers/android_bugreport/chunked_line_reader.h" +#include "src/trace_processor/storage/trace_storage.h" + +namespace perfetto::trace_processor { + +class TraceProcessorContext; + +// Trace importer for Android dumpstate files. +class AndroidDumpstateReader : public ChunkedLineReader { + public: + // Create a reader that will only forward events that are not present in the + // given list. + AndroidDumpstateReader(TraceProcessorContext* context, + int32_t year, + std::vector logcat_events); + ~AndroidDumpstateReader() override; + + util::Status ParseLine(base::StringView line) override; + void EndOfStream(base::StringView leftovers) override; + + private: + enum class Section { kOther = 0, kDumpsys, kLog }; + TraceProcessorContext* const context_; + DedupingAndroidLogReader log_reader_; + Section current_section_ = Section::kOther; + StringId current_section_id_ = StringId::Null(); + StringId current_service_id_ = StringId::Null(); +}; + +} // namespace perfetto::trace_processor + +#endif // SRC_TRACE_PROCESSOR_IMPORTERS_ANDROID_BUGREPORT_ANDROID_DUMPSTATE_READER_H_ diff --git a/src/trace_processor/importers/android_bugreport/android_log_event.cc b/src/trace_processor/importers/android_bugreport/android_log_event.cc new file mode 100644 index 0000000000..6f01065840 --- /dev/null +++ b/src/trace_processor/importers/android_bugreport/android_log_event.cc @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "src/trace_processor/importers/android_bugreport/android_log_event.h" + +#include + +#include "perfetto/ext/base/string_utils.h" + +namespace perfetto::trace_processor { + +// static +std::optional AndroidLogEvent::DetectFormat( + base::StringView line) { + auto p = base::SplitString(line.ToStdString(), " "); + if (p.size() < 5) + return std::nullopt; + + if (p[0].size() != 5 || p[0][2] != '-') + return std::nullopt; + + if (p[1].size() < 10 || p[1][2] != ':' || p[1][5] != ':' || p[1][8] != '.') + return std::nullopt; + + if (p[4].size() == 1 && p[4][0] >= 'A' && p[4][0] <= 'Z') + return Format::kPersistentLog; + + if (p[5].size() == 1 && p[5][0] >= 'A' && p[5][0] <= 'Z') + return Format::kBugreport; + + return std::nullopt; +} + +} // namespace perfetto::trace_processor diff --git a/src/trace_processor/importers/android_bugreport/android_log_event.h b/src/trace_processor/importers/android_bugreport/android_log_event.h new file mode 100644 index 0000000000..d8eeb69299 --- /dev/null +++ b/src/trace_processor/importers/android_bugreport/android_log_event.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_ANDROID_BUGREPORT_ANDROID_LOG_EVENT_H_ +#define SRC_TRACE_PROCESSOR_IMPORTERS_ANDROID_BUGREPORT_ANDROID_LOG_EVENT_H_ + +#include +#include +#include + +#include "perfetto/ext/base/string_view.h" +#include "src/trace_processor/containers/string_pool.h" + +namespace perfetto ::trace_processor { + +struct alignas(8) AndroidLogEvent { + enum class Format : int32_t { + kPersistentLog, + kBugreport, + }; + + static std::optional DetectFormat(base::StringView line); + + inline static bool IsAndroidLogEvent(base::StringView line) { + return DetectFormat(line).has_value(); + } + + bool operator==(const AndroidLogEvent& o) const { + return pid == o.pid && tid == o.tid && prio == o.prio && tag == o.tag && + msg == o.msg; + } + + uint32_t pid; + uint32_t tid; + uint32_t prio; // Refer to enum ::protos::pbzero::AndroidLogPriority. + StringPool::Id tag; + StringPool::Id msg; +}; + +} // namespace perfetto::trace_processor + +#endif // SRC_TRACE_PROCESSOR_IMPORTERS_ANDROID_BUGREPORT_ANDROID_LOG_EVENT_H_ diff --git a/src/trace_processor/importers/android_bugreport/android_log_event_parser_impl.cc b/src/trace_processor/importers/android_bugreport/android_log_event_parser_impl.cc new file mode 100644 index 0000000000..d629620172 --- /dev/null +++ b/src/trace_processor/importers/android_bugreport/android_log_event_parser_impl.cc @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "src/trace_processor/importers/android_bugreport/android_log_event_parser_impl.h" + +#include +#include + +#include "src/trace_processor/importers/android_bugreport/android_log_event.h" +#include "src/trace_processor/importers/common/process_tracker.h" +#include "src/trace_processor/tables/android_tables_py.h" +#include "src/trace_processor/types/trace_processor_context.h" + +namespace perfetto::trace_processor { + +AndroidLogEventParserImpl::~AndroidLogEventParserImpl() = default; + +void AndroidLogEventParserImpl::ParseAndroidLogEvent(int64_t ts, + AndroidLogEvent event) { + tables::AndroidLogTable::Row row; + row.ts = ts; + row.utid = context_->process_tracker->UpdateThread(event.tid, event.pid); + row.prio = event.prio; + row.tag = event.tag; + row.msg = event.msg; + context_->storage->mutable_android_log_table()->Insert(std::move(row)); +} + +} // namespace perfetto::trace_processor diff --git a/src/trace_processor/importers/android_bugreport/android_log_event_parser_impl.h b/src/trace_processor/importers/android_bugreport/android_log_event_parser_impl.h new file mode 100644 index 0000000000..3c33115fd9 --- /dev/null +++ b/src/trace_processor/importers/android_bugreport/android_log_event_parser_impl.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_ANDROID_BUGREPORT_ANDROID_LOG_EVENT_PARSER_IMPL_H_ +#define SRC_TRACE_PROCESSOR_IMPORTERS_ANDROID_BUGREPORT_ANDROID_LOG_EVENT_PARSER_IMPL_H_ + +#include + +#include "src/trace_processor/importers/android_bugreport/android_log_event.h" +#include "src/trace_processor/importers/common/trace_parser.h" + +namespace perfetto ::trace_processor { + +class TraceProcessorContext; + +class AndroidLogEventParserImpl : public AndroidLogEventParser { + public: + explicit AndroidLogEventParserImpl(TraceProcessorContext* context) + : context_(context) {} + ~AndroidLogEventParserImpl() override; + + void ParseAndroidLogEvent(int64_t, AndroidLogEvent) override; + + private: + TraceProcessorContext* const context_; +}; + +} // namespace perfetto::trace_processor + +#endif // SRC_TRACE_PROCESSOR_IMPORTERS_ANDROID_BUGREPORT_ANDROID_LOG_EVENT_PARSER_IMPL_H_ diff --git a/src/trace_processor/importers/android_bugreport/android_log_parser.cc b/src/trace_processor/importers/android_bugreport/android_log_parser.cc deleted file mode 100644 index b8258621c9..0000000000 --- a/src/trace_processor/importers/android_bugreport/android_log_parser.cc +++ /dev/null @@ -1,251 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/trace_processor/importers/android_bugreport/android_log_parser.h" - -#include -#include - -#include "perfetto/base/logging.h" -#include "perfetto/base/time.h" -#include "perfetto/ext/base/string_utils.h" -#include "src/trace_processor/types/trace_processor_context.h" - -#include "protos/perfetto/common/android_log_constants.pbzero.h" - -namespace perfetto { -namespace trace_processor { - -namespace { - -// Reads a base-10 number and advances the passed StringView beyond the *last* -// instance of `sep`. Example: -// Input: it="1234 bar". -// Output: it="bar", ret=1234. -// -// `decimal_scale` is used to parse decimals and defines the output resolution. -// E.g. input="1", decimal_scale=1000 -> res=100 -// input="12", decimal_scale=1000 -> res=120 -// input="123", decimal_scale=1000 -> res=123 -// input="1234", decimal_scale=1000 -> res=123 -// input="1234", decimal_scale=1000000 -> res=123400 -std::optional ReadNumAndAdvance(base::StringView* it, - char sep, - int decimal_scale = 0) { - int num = 0; - bool sep_found = false; - size_t next_it = 0; - bool invalid_chars_found = false; - for (size_t i = 0; i < it->size(); i++) { - char c = it->at(i); - if (c == sep) { - next_it = i + 1; - sep_found = true; - continue; - } - if (sep_found) - break; - if (c >= '0' && c <= '9') { - int digit = static_cast(c - '0'); - if (!decimal_scale) { - num = num * 10 + digit; - } else { - decimal_scale /= 10; - num += digit * decimal_scale; - } - continue; - } - // We found something that is not a digit. Keep looking for the next `sep` - // but flag the current token as invalid. - invalid_chars_found = true; - } - if (!sep_found) - return std::nullopt; - // If we find non-digit characters, we want to still skip the token but return - // std::nullopt. The parser below relies on token skipping to deal with cases - // where the uid (which we don't care about) is literal ("root" rather than - // 0). - *it = it->substr(next_it); - if (invalid_chars_found) - return std::nullopt; - return num; -} - -enum class LogcatFormat { - kUnknown = 0, - - // 01-02 03:04:05.678901 1000 2000 V Tag: Message - kPersistentLog, - - // 06-24 15:57:11.346 1000 1493 1918 D Tag: Message - // or also - // 07-28 14:25:22.181 root 0 0 I Tag : Message - kBugreport -}; - -LogcatFormat DetectFormat(base::StringView line) { - auto p = base::SplitString(line.ToStdString(), " "); - if (p.size() < 5) - return LogcatFormat::kUnknown; - - if (p[0].size() != 5 || p[0][2] != '-') - return LogcatFormat::kUnknown; - - if (p[1].size() < 10 || p[1][2] != ':' || p[1][5] != ':' || p[1][8] != '.') - return LogcatFormat::kUnknown; - - if (p[4].size() == 1 && p[4][0] >= 'A' && p[4][0] <= 'Z') - return LogcatFormat::kPersistentLog; - - if (p[5].size() == 1 && p[5][0] >= 'A' && p[5][0] <= 'Z') - return LogcatFormat::kBugreport; - - return LogcatFormat::kUnknown; -} - -} // namespace - -// Parses a bunch of logcat lines and appends broken down events into -// `log_events`. -void AndroidLogParser::ParseLogLines(std::vector lines, - std::vector* log_events, - size_t dedupe_idx) { - int parse_failures = 0; - LogcatFormat fmt = LogcatFormat::kUnknown; - for (auto line : lines) { - if (line.size() < 30 || - (line.at(0) == '-' && line.at(1) == '-' && line.at(2) == '-')) { - // These are markers like "--------- switch to radio" which we ignore. - // The smallest valid logcat line has around 30 chars, as follows: - // "06-24 23:10:00.123 1 1 D : ..." - continue; - } - if (fmt == LogcatFormat::kUnknown) { - fmt = DetectFormat(line); - if (fmt == LogcatFormat::kUnknown) { - PERFETTO_DLOG("Could not detect logcat format for: |%s|", - line.ToStdString().c_str()); - storage_->IncrementStats(stats::android_log_format_invalid); - return; - } - } - - base::StringView it = line; - // 06-24 16:24:23.441532 23153 23153 I wm_on_stop_called: message ... - // 07-28 14:25:13.506 root 0 0 I x86/fpu : Supporting XSAVE feature - // 0x002: 'SSE registers' - std::optional month = ReadNumAndAdvance(&it, '-'); - std::optional day = ReadNumAndAdvance(&it, ' '); - std::optional hour = ReadNumAndAdvance(&it, ':'); - std::optional minute = ReadNumAndAdvance(&it, ':'); - std::optional sec = ReadNumAndAdvance(&it, '.'); - std::optional ns = ReadNumAndAdvance(&it, ' ', 1000 * 1000 * 1000); - - if (fmt == LogcatFormat::kBugreport) - ReadNumAndAdvance(&it, ' '); // Skip the UID column. - - std::optional pid = ReadNumAndAdvance(&it, ' '); - std::optional tid = ReadNumAndAdvance(&it, ' '); - - if (!month || !day || !hour || !minute || !sec || !ns || !pid || !tid) { - ++parse_failures; - continue; - } - - if (it.size() < 4 || it.at(1) != ' ') { - ++parse_failures; - continue; - } - - char prio_str = it.at(0); - int prio = protos::pbzero::AndroidLogPriority::PRIO_UNSPECIFIED; - if ('V' == prio_str) { - prio = protos::pbzero::AndroidLogPriority::PRIO_VERBOSE; - } else if ('D' == prio_str) { - prio = protos::pbzero::AndroidLogPriority::PRIO_DEBUG; - } else if ('I' == prio_str) { - prio = protos::pbzero::AndroidLogPriority::PRIO_INFO; - } else if ('W' == prio_str) { - prio = protos::pbzero::AndroidLogPriority::PRIO_WARN; - } else if ('E' == prio_str) { - prio = protos::pbzero::AndroidLogPriority::PRIO_ERROR; - } else if ('F' == prio_str) { - prio = protos::pbzero::AndroidLogPriority::PRIO_FATAL; - } - - it = it.substr(2); - - // Find the ': ' that defines the boundary between the tag and message. - // We can't just look for ':' because various HALs emit tags with a ':'. - base::StringView cat; - for (size_t i = 0; i < it.size() - 1; ++i) { - if (it.at(i) == ':' && it.at(i + 1) == ' ') { - cat = it.substr(0, i); - it = it.substr(i + 2); - break; - } - } - // Trim trailing spaces, happens in kernel events (e.g. "init :"). - while (!cat.empty() && cat.at(cat.size() - 1) == ' ') - cat = cat.substr(0, cat.size() - 1); - - base::StringView msg = it; // The rest is the log message. - - int64_t secs = base::MkTime(year_, *month, *day, *hour, *minute, *sec); - int64_t ts = secs * 1000000000ll + *ns; - - AndroidLogEvent evt{ts, - static_cast(*pid), - static_cast(*tid), - static_cast(prio), - storage_->InternString(cat), - storage_->InternString(msg)}; - - if (dedupe_idx > 0) { - // Search for dupes before inserting. - // Events in the [0, dedupe_idx] range are sorted by timestamp with ns - // resolution. Here we search for dupes within the same millisecond of - // the event we are trying to insert. The /1000000*1000000 is to deal with - // the fact that events coming from the persistent log have us resolution, - // while events from dumpstate (which are often dupes of persistent ones) - // have only ms resolution. Here we consider an event a dupe if it has - // the same ms-truncated solution, same pid, tid and message. - AndroidLogEvent etrunc = evt; - etrunc.ts = etrunc.ts / 1000000 * 1000000; - auto begin = log_events->begin(); - auto end = log_events->begin() + static_cast(dedupe_idx); - bool dupe_found = false; - for (auto eit = std::lower_bound(begin, end, etrunc); eit < end; ++eit) { - if (eit->ts / 1000000 * 1000000 != etrunc.ts) - break; - if (eit->msg == evt.msg && eit->tag == evt.tag && eit->tid == evt.tid && - eit->pid == evt.pid) { - dupe_found = true; - break; - } - } - if (dupe_found) { - continue; // Skip the current line. - } - } // if (dedupe_idx) - - log_events->emplace_back(std::move(evt)); - } // for (line : lines) - storage_->IncrementStats(stats::android_log_num_failed, parse_failures); -} - -} // namespace trace_processor -} // namespace perfetto diff --git a/src/trace_processor/importers/android_bugreport/android_log_parser.h b/src/trace_processor/importers/android_bugreport/android_log_parser.h deleted file mode 100644 index 2609bda8cb..0000000000 --- a/src/trace_processor/importers/android_bugreport/android_log_parser.h +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_ANDROID_BUGREPORT_ANDROID_LOG_PARSER_H_ -#define SRC_TRACE_PROCESSOR_IMPORTERS_ANDROID_BUGREPORT_ANDROID_LOG_PARSER_H_ - -#include - -#include - -#include "perfetto/ext/base/string_view.h" -#include "src/trace_processor/storage/trace_storage.h" - -namespace perfetto { -namespace trace_processor { - -struct AndroidLogEvent { - int64_t ts; // Nanoseconds since Epoch. - uint32_t pid; - uint32_t tid; - uint32_t prio; // Refer to enum ::protos::pbzero::AndroidLogPriority. - StringId tag; - StringId msg; - - // For std::sort(). - bool operator<(const AndroidLogEvent& o) const { return ts < o.ts; } - - // For gtest. - bool operator==(const AndroidLogEvent& o) const { - return std::tie(ts, pid, tid, prio, tag, msg) == - std::tie(o.ts, o.pid, o.tid, o.prio, o.tag, o.msg); - } -}; - -// Parses log lines coming from persistent logcat (FS/data/misc/logd), interns -// string in the TP string pools and populates a vector of AndroidLogEvent -// structs. Does NOT insert log events into any table (for testing isolation), -// the caller is in charge to do that. -// It supports the following formats (auto-detected): -// 1) 12-31 23:59:00.123456 I tag: message -// This is typically found in persistent logcat (FS/data/misc/logd/) -// 2) 06-24 15:57:11.346 D Tag: Message -// This is typically found in the recent logcat dump in bugreport-xxx.txt -class AndroidLogParser { - public: - explicit AndroidLogParser(int year, TraceStorage* storage) - : storage_(storage), year_(year) {} - ~AndroidLogParser() = default; - - // Decodes logcat events for the input `lines` and appends them into - // `log_events`. If `dedupe_idx` is != 0, it checks for duplicate entries - // before inserting and skips the insertion if a dupe is found. Dupes are - // searched in the first `dedupe_idx` entries of `log_events`. In practice - // `dedupe_idx` is the log_events.size() for the last std::sort() call. - // The de-duping logic truncates timestamps to millisecond resolution, to - // handle the mismatching resolution of dumpstate (ms) vs persistent log (us). - void ParseLogLines(std::vector lines, - std::vector* log_events, - size_t dedupe_idx = 0); - - private: - TraceStorage* const storage_; - int year_ = 0; -}; - -} // namespace trace_processor -} // namespace perfetto - -#endif // SRC_TRACE_PROCESSOR_IMPORTERS_ANDROID_BUGREPORT_ANDROID_LOG_PARSER_H_ diff --git a/src/trace_processor/importers/android_bugreport/android_log_parser_unittest.cc b/src/trace_processor/importers/android_bugreport/android_log_parser_unittest.cc deleted file mode 100644 index 227934eac1..0000000000 --- a/src/trace_processor/importers/android_bugreport/android_log_parser_unittest.cc +++ /dev/null @@ -1,192 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/trace_processor/importers/android_bugreport/android_log_parser.h" - -#include "perfetto/base/time.h" -#include "perfetto/ext/base/string_utils.h" -#include "src/trace_processor/storage/trace_storage.h" -#include "test/gtest_and_gmock.h" - -#include "protos/perfetto/common/android_log_constants.pbzero.h" - -namespace perfetto { -namespace trace_processor { - -const int64_t kStoNs = 1000000000LL; - -inline std::ostream& operator<<(std::ostream& stream, - const AndroidLogEvent& e) { - char tms[32]; - time_t secs = static_cast(e.ts / kStoNs); - int ns = static_cast(e.ts - secs * kStoNs); - strftime(tms, sizeof(tms), "%Y-%m-%d %H:%M:%S", gmtime(&secs)); - base::StackString<64> tss("%s.%d", tms, ns); - - stream << "{ts=" << tss.c_str() << ", pid=" << e.pid << ", tid=" << e.tid - << ", prio=" << e.prio << ", tag=" << e.tag.raw_id() - << ", msg=" << e.msg.raw_id() << "}"; - return stream; -} - -namespace { - -using ::testing::ElementsAreArray; - -TEST(AndroidLogParserTest, PersistentLogFormat) { - TraceStorage storage; - AndroidLogParser alp(2020, &storage); - auto S = [&](const char* str) { return storage.InternString(str); }; - using P = ::perfetto::protos::pbzero::AndroidLogPriority; - - std::vector events; - alp.ParseLogLines( - { - "01-02 03:04:05.678901 1000 2000 D Tag: message", - "01-02 03:04:05.678901 1000 2000 V Tag: message", - "12-31 23:59:00.123456 1 2 I [tag:with:colon]: moar long message", - "12-31 23:59:00.123 1 2 W [tag:with:colon]: moar long message", - "12-31 23:59:00.1 1 2 E [tag:with:colon]: moar long message", - "12-31 23:59:00.01 1 2 F [tag:with:colon]: moar long message", - }, - &events); - - EXPECT_EQ(storage.stats()[stats::android_log_num_failed].value, 0); - ASSERT_THAT( - events, - ElementsAreArray({ - AndroidLogEvent{ - base::MkTime(2020, 1, 2, 3, 4, 5) * kStoNs + 678901000, 1000, - 2000, P::PRIO_DEBUG, S("Tag"), S("message")}, - AndroidLogEvent{ - base::MkTime(2020, 1, 2, 3, 4, 5) * kStoNs + 678901000, 1000, - 2000, P::PRIO_VERBOSE, S("Tag"), S("message")}, - AndroidLogEvent{ - base::MkTime(2020, 12, 31, 23, 59, 0) * kStoNs + 123456000, 1, 2, - P::PRIO_INFO, S("[tag:with:colon]"), S("moar long message")}, - AndroidLogEvent{ - base::MkTime(2020, 12, 31, 23, 59, 0) * kStoNs + 123000000, 1, 2, - P::PRIO_WARN, S("[tag:with:colon]"), S("moar long message")}, - AndroidLogEvent{ - base::MkTime(2020, 12, 31, 23, 59, 0) * kStoNs + 100000000, 1, 2, - P::PRIO_ERROR, S("[tag:with:colon]"), S("moar long message")}, - AndroidLogEvent{ - base::MkTime(2020, 12, 31, 23, 59, 0) * kStoNs + 10000000, 1, 2, - P::PRIO_FATAL, S("[tag:with:colon]"), S("moar long message")}, - })); -} - -TEST(AndroidLogParserTest, BugreportFormat) { - TraceStorage storage; - AndroidLogParser alp(2020, &storage); - auto S = [&](const char* str) { return storage.InternString(str); }; - using P = ::perfetto::protos::pbzero::AndroidLogPriority; - - std::vector events; - alp.ParseLogLines( - { - "07-28 14:25:20.355 0 1 2 I init : Loaded kernel module", - "07-28 14:25:54.876 1000 643 644 D PackageManager: No files", - "08-24 23:39:12.272 root 0 1 I : c0 11835 binder: 1", - "08-24 23:39:12.421 radio 2532 2533 D TelephonyProvider: Using old", - }, - &events); - - EXPECT_EQ(storage.stats()[stats::android_log_num_failed].value, 0); - ASSERT_THAT( - events, - ElementsAreArray({ - AndroidLogEvent{ - base::MkTime(2020, 7, 28, 14, 25, 20) * kStoNs + 355000000, 1, 2, - P::PRIO_INFO, S("init"), S("Loaded kernel module")}, - AndroidLogEvent{ - base::MkTime(2020, 7, 28, 14, 25, 54) * kStoNs + 876000000, 643, - 644, P::PRIO_DEBUG, S("PackageManager"), S("No files")}, - AndroidLogEvent{ - base::MkTime(2020, 8, 24, 23, 39, 12) * kStoNs + 272000000, 0, 1, - P::PRIO_INFO, S(""), S("c0 11835 binder: 1")}, - AndroidLogEvent{ - base::MkTime(2020, 8, 24, 23, 39, 12) * kStoNs + 421000000, 2532, - 2533, P::PRIO_DEBUG, S("TelephonyProvider"), S("Using old")}, - })); -} - -// Tests the deduping logic. This is used when parsing events first from the -// persistent logcat (which has us resolution) and then from dumpstate (which -// has ms resolution and sometimes contains dupes of the persistent entries). -TEST(AndroidLogParserTest, Dedupe) { - TraceStorage storage; - AndroidLogParser alp(2020, &storage); - auto S = [&](const char* str) { return storage.InternString(str); }; - using P = ::perfetto::protos::pbzero::AndroidLogPriority; - std::vector events; - - // Parse some initial events without any deduping. - alp.ParseLogLines( - { - "01-01 00:00:01.100000 0 1 1 I tag : M1", - "01-01 00:00:01.100111 0 1 1 I tag : M2", - "01-01 00:00:01.100111 0 1 1 I tag : M3", - "01-01 00:00:01.100222 0 1 1 I tag : M4", - "01-01 00:00:01.101000 0 1 1 I tag : M5", - }, - &events); - - ASSERT_EQ(events.size(), 5u); - - // Add a batch of events with truncated timestamps, some of which are dupes. - alp.ParseLogLines( - { - "01-01 00:00:01.100 0 1 1 I tag : M1", // Dupe - "01-01 00:00:01.100 0 1 1 I tag : M1.5", - "01-01 00:00:01.100 0 1 1 I tag : M3", // Dupe - "01-01 00:00:01.100 0 1 1 I tag : M4", // Dupe - "01-01 00:00:01.101 0 1 1 I tag : M5", // Dupe - "01-01 00:00:01.101 0 1 1 I tag : M6", - }, - &events, /*dedupe_idx=*/5); - EXPECT_EQ(storage.stats()[stats::android_log_num_failed].value, 0); - - std::stable_sort(events.begin(), events.end()); - ASSERT_THAT(events, - ElementsAreArray({ - AndroidLogEvent{ - base::MkTime(2020, 1, 1, 0, 0, 1) * kStoNs + 100000000, 1, - 1, P::PRIO_INFO, S("tag"), S("M1")}, - AndroidLogEvent{ - base::MkTime(2020, 1, 1, 0, 0, 1) * kStoNs + 100000000, 1, - 1, P::PRIO_INFO, S("tag"), S("M1.5")}, - AndroidLogEvent{ - base::MkTime(2020, 1, 1, 0, 0, 1) * kStoNs + 100111000, 1, - 1, P::PRIO_INFO, S("tag"), S("M2")}, - AndroidLogEvent{ - base::MkTime(2020, 1, 1, 0, 0, 1) * kStoNs + 100111000, 1, - 1, P::PRIO_INFO, S("tag"), S("M3")}, - AndroidLogEvent{ - base::MkTime(2020, 1, 1, 0, 0, 1) * kStoNs + 100222000, 1, - 1, P::PRIO_INFO, S("tag"), S("M4")}, - AndroidLogEvent{ - base::MkTime(2020, 1, 1, 0, 0, 1) * kStoNs + 101000000, 1, - 1, P::PRIO_INFO, S("tag"), S("M5")}, - AndroidLogEvent{ - base::MkTime(2020, 1, 1, 0, 0, 1) * kStoNs + 101000000, 1, - 1, P::PRIO_INFO, S("tag"), S("M6")}, - })); -} - -} // namespace -} // namespace trace_processor -} // namespace perfetto diff --git a/src/trace_processor/importers/android_bugreport/android_log_reader.cc b/src/trace_processor/importers/android_bugreport/android_log_reader.cc new file mode 100644 index 0000000000..148f6437fd --- /dev/null +++ b/src/trace_processor/importers/android_bugreport/android_log_reader.cc @@ -0,0 +1,261 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "src/trace_processor/importers/android_bugreport/android_log_reader.h" + +#include +#include +#include +#include +#include + +#include "perfetto/base/status.h" +#include "protos/perfetto/common/android_log_constants.pbzero.h" +#include "protos/perfetto/trace/clock_snapshot.pbzero.h" +#include "src/trace_processor/importers/android_bugreport/android_log_event.h" +#include "src/trace_processor/importers/common/clock_tracker.h" +#include "src/trace_processor/sorter/trace_sorter.h" +#include "src/trace_processor/storage/stats.h" +#include "src/trace_processor/types/trace_processor_context.h" +#include "src/trace_processor/util/status_macros.h" + +namespace perfetto::trace_processor { + +namespace { + +// Reads a base-10 number and advances the passed StringView beyond the *last* +// instance of `sep`. Example: +// Input: it="1234 bar". +// Output: it="bar", ret=1234. +// +// `decimal_scale` is used to parse decimals and defines the output resolution. +// E.g. input="1", decimal_scale=1000 -> res=100 +// input="12", decimal_scale=1000 -> res=120 +// input="123", decimal_scale=1000 -> res=123 +// input="1234", decimal_scale=1000 -> res=123 +// input="1234", decimal_scale=1000000 -> res=123400 +std::optional ReadNumAndAdvance(base::StringView* it, + char sep, + int decimal_scale = 0) { + int num = 0; + bool sep_found = false; + size_t next_it = 0; + bool invalid_chars_found = false; + for (size_t i = 0; i < it->size(); i++) { + char c = it->at(i); + if (c == sep) { + next_it = i + 1; + sep_found = true; + continue; + } + if (sep_found) + break; + if (c >= '0' && c <= '9') { + int digit = static_cast(c - '0'); + if (!decimal_scale) { + num = num * 10 + digit; + } else { + decimal_scale /= 10; + num += digit * decimal_scale; + } + continue; + } + // We found something that is not a digit. Keep looking for the next `sep` + // but flag the current token as invalid. + invalid_chars_found = true; + } + if (!sep_found) + return std::nullopt; + // If we find non-digit characters, we want to still skip the token but return + // std::nullopt. The parser below relies on token skipping to deal with cases + // where the uid (which we don't care about) is literal ("root" rather than + // 0). + *it = it->substr(next_it); + if (invalid_chars_found) + return std::nullopt; + return num; +} + +int32_t GuessYear(TraceProcessorContext* context) { + constexpr int64_t one_second_in_ns = 1LL * 1000LL * 1000LL * 1000LL; + int64_t s = context->sorter->max_timestamp() / one_second_in_ns; + time_t time_s = static_cast(s); + struct tm* time_tm = gmtime(&time_s); + return time_tm->tm_year + 1900; +} + +} // namespace +AndroidLogReader::AndroidLogReader(TraceProcessorContext* context) + : context_(context), year_(GuessYear(context_)) {} + +AndroidLogReader::AndroidLogReader(TraceProcessorContext* context, int32_t year) + : context_(context), year_(year) {} + +AndroidLogReader::~AndroidLogReader() = default; + +util::Status AndroidLogReader::ParseLine(base::StringView line) { + if (line.size() < 30 || + (line.at(0) == '-' && line.at(1) == '-' && line.at(2) == '-')) { + // These are markers like "--------- switch to radio" which we ignore. + // The smallest valid logcat line has around 30 chars, as follows: + // "06-24 23:10:00.123 1 1 D : ..." + return base::OkStatus(); + } + + if (!format_.has_value()) { + format_ = AndroidLogEvent::DetectFormat(line); + if (!format_.has_value()) { + PERFETTO_DLOG("Could not detect logcat format for: |%s|", + line.ToStdString().c_str()); + context_->storage->IncrementStats(stats::android_log_format_invalid); + return base::OkStatus(); + } + } + + base::StringView it = line; + // 06-24 16:24:23.441532 23153 23153 I wm_on_stop_called: message ... + // 07-28 14:25:13.506 root 0 0 I x86/fpu : Supporting XSAVE feature + // 0x002: 'SSE registers' + std::optional month = ReadNumAndAdvance(&it, '-'); + std::optional day = ReadNumAndAdvance(&it, ' '); + std::optional hour = ReadNumAndAdvance(&it, ':'); + std::optional minute = ReadNumAndAdvance(&it, ':'); + std::optional sec = ReadNumAndAdvance(&it, '.'); + std::optional ns = ReadNumAndAdvance(&it, ' ', 1000 * 1000 * 1000); + + if (format_ == AndroidLogEvent::Format::kBugreport) + ReadNumAndAdvance(&it, ' '); // Skip the UID column. + + std::optional pid = ReadNumAndAdvance(&it, ' '); + std::optional tid = ReadNumAndAdvance(&it, ' '); + + if (!month || !day || !hour || !minute || !sec || !ns || !pid || !tid) { + context_->storage->IncrementStats(stats::android_log_num_failed); + return base::OkStatus(); + } + + if (it.size() < 4 || it.at(1) != ' ') { + context_->storage->IncrementStats(stats::android_log_num_failed); + return base::OkStatus(); + } + + char prio_str = it.at(0); + int prio = protos::pbzero::AndroidLogPriority::PRIO_UNSPECIFIED; + if ('V' == prio_str) { + prio = protos::pbzero::AndroidLogPriority::PRIO_VERBOSE; + } else if ('D' == prio_str) { + prio = protos::pbzero::AndroidLogPriority::PRIO_DEBUG; + } else if ('I' == prio_str) { + prio = protos::pbzero::AndroidLogPriority::PRIO_INFO; + } else if ('W' == prio_str) { + prio = protos::pbzero::AndroidLogPriority::PRIO_WARN; + } else if ('E' == prio_str) { + prio = protos::pbzero::AndroidLogPriority::PRIO_ERROR; + } else if ('F' == prio_str) { + prio = protos::pbzero::AndroidLogPriority::PRIO_FATAL; + } + + it = it.substr(2); + + // Find the ': ' that defines the boundary between the tag and message. + // We can't just look for ':' because various HALs emit tags with a ':'. + base::StringView cat; + for (size_t i = 0; i < it.size() - 1; ++i) { + if (it.at(i) == ':' && it.at(i + 1) == ' ') { + cat = it.substr(0, i); + it = it.substr(i + 2); + break; + } + } + // Trim trailing spaces, happens in kernel events (e.g. "init :"). + while (!cat.empty() && cat.at(cat.size() - 1) == ' ') + cat = cat.substr(0, cat.size() - 1); + + base::StringView msg = it; // The rest is the log message. + + int64_t secs = base::MkTime(year_, *month, *day, *hour, *minute, *sec); + int64_t event_ts = secs * 1000000000ll + *ns; + + AndroidLogEvent event; + event.pid = static_cast(*pid); + event.tid = static_cast(*tid); + event.prio = static_cast(prio); + event.tag = context_->storage->InternString(cat); + event.msg = context_->storage->InternString(msg); + + return ProcessEvent(event_ts, std::move(event)); +} + +util::Status AndroidLogReader::ProcessEvent(int64_t event_ts_ns, + AndroidLogEvent event) { + return SendToSorter(event_ts_ns, std::move(event)); +} + +util::Status AndroidLogReader::SendToSorter(int64_t event_ts_ns, + AndroidLogEvent event) { + ASSIGN_OR_RETURN( + int64_t trace_ts, + context_->clock_tracker->ToTraceTime( + protos::pbzero::ClockSnapshot::Clock::REALTIME, event_ts_ns)); + context_->sorter->PushAndroidLogEvent(trace_ts, std::move(event)); + return base::OkStatus(); +} + +void AndroidLogReader::EndOfStream(base::StringView) {} + +BufferingAndroidLogReader::~BufferingAndroidLogReader() = default; + +base::Status BufferingAndroidLogReader::ProcessEvent(int64_t event_ts, + AndroidLogEvent event) { + RETURN_IF_ERROR(SendToSorter(event_ts, event)); + events_.push_back( + TimestampedAndroidLogEvent{event_ts / 1000000, std::move(event), false}); + return base::OkStatus(); +} + +DedupingAndroidLogReader::DedupingAndroidLogReader( + TraceProcessorContext* context, + int32_t year, + std::vector events) + : AndroidLogReader(context, year), events_(std::move(events)) { + std::sort(events_.begin(), events_.end()); +} + +DedupingAndroidLogReader::~DedupingAndroidLogReader() {} + +base::Status DedupingAndroidLogReader::ProcessEvent(int64_t event_ts, + AndroidLogEvent event) { + const auto comp = [](const TimestampedAndroidLogEvent& lhs, + int64_t rhs_time_ms) { + return lhs.time_ms < rhs_time_ms; + }; + const auto event_ms = event_ts / 1000000; + + for (auto it = + std::lower_bound(events_.begin(), events_.end(), event_ms, comp); + it != events_.end() && it->time_ms == event_ms; ++it) { + // Duplicate found + if (!it->matched && it->event == event) { + // "Remove" the entry from the list + it->matched = true; + return base::OkStatus(); + } + } + + return SendToSorter(event_ts, std::move(event)); +} + +} // namespace perfetto::trace_processor diff --git a/src/trace_processor/importers/android_bugreport/android_log_reader.h b/src/trace_processor/importers/android_bugreport/android_log_reader.h new file mode 100644 index 0000000000..e06742dbf1 --- /dev/null +++ b/src/trace_processor/importers/android_bugreport/android_log_reader.h @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_ANDROID_BUGREPORT_ANDROID_LOG_READER_H_ +#define SRC_TRACE_PROCESSOR_IMPORTERS_ANDROID_BUGREPORT_ANDROID_LOG_READER_H_ + +#include +#include + +#include "perfetto/base/status.h" +#include "perfetto/ext/base/string_view.h" +#include "src/trace_processor/importers/android_bugreport/android_log_event.h" +#include "src/trace_processor/importers/android_bugreport/chunked_line_reader.h" + +namespace perfetto ::trace_processor { + +class TraceProcessorContext; + +// Parses log lines coming from persistent logcat (FS/data/misc/logd), interns +// string in the TP string pools and populates a vector of AndroidLogEvent +// structs. Does NOT insert log events into any table (for testing isolation), +// the caller is in charge to do that. +// It supports the following formats (auto-detected): +// 1) 12-31 23:59:00.123456 I tag: message +// This is typically found in persistent logcat (FS/data/misc/logd/) +// 2) 06-24 15:57:11.346 D Tag: Message +// This is typically found in the recent logcat dump in bugreport-xxx.txt +class AndroidLogReader : public ChunkedLineReader { + public: + // Log cat will not write year into the trace so the caller needs to figure it + // out. If not provided the reader will make a best guess. + explicit AndroidLogReader(TraceProcessorContext* context); + AndroidLogReader(TraceProcessorContext* context, int32_t year); + + ~AndroidLogReader() override; + + base::Status ParseLine(base::StringView line) override; + void EndOfStream(base::StringView leftovers) override; + + // Called for each event parsed from the stream. + // `event_ts_ns` is the ts of the event as read from the log. + // Default implementation just calls `SendToSorter`. + virtual base::Status ProcessEvent(int64_t event_ts, AndroidLogEvent event); + + protected: + // Sends the given event to the sorting stage. + // `event_ts_ns` is the ts of the event as read from the log and will be + // converted to a trace_ts (with necessary clock conversions applied) + base::Status SendToSorter(int64_t event_ts_ns, AndroidLogEvent event); + + private: + TraceProcessorContext* const context_; + std::optional format_; + int32_t year_; +}; + +// Helper struct to deduplicate events. +// When reading bug reports log data will be present in a dumpstate file and in +// the log cat files. +struct TimestampedAndroidLogEvent { + // Log timestamp. We use ms resolution because dumpstate files only write at + // this resolution. + int64_t time_ms; + AndroidLogEvent event; + // Flag to track whether a given event was already matched by the + // deduplication logic. When set to true we will no longer consider this event + // as a candidate for deduplication. + bool matched; + + // Only sort by time to find duplicates at the same ts. + bool operator<(const TimestampedAndroidLogEvent& other) const { + return time_ms < other.time_ms; + } +}; + +// Same as AndroidLogReader (sends events to sorter), but also stores them in a +// vector that can later be feed to a `DedupingAndroidLogReader` instance. +class BufferingAndroidLogReader : public AndroidLogReader { + public: + BufferingAndroidLogReader(TraceProcessorContext* context, int32_t year) + : AndroidLogReader(context, year) {} + ~BufferingAndroidLogReader() override; + + base::Status ProcessEvent(int64_t event_ts, AndroidLogEvent event) override; + + std::vector ConsumeBufferedEvents() && { + return std::move(events_); + } + + private: + std::vector events_; +}; + +// Similar to `AndroidLogReader` but this class will not forward duplicate +// events. These are events already present in a given vector of events. +class DedupingAndroidLogReader : public AndroidLogReader { + public: + // Creates a reader that will not forward events already present in the given + // vector. Note that entries in the vector will only be matched once. That is + // when a match is found in the vector the event is not send to the sorter, + // but the event is removed from the vector (seen flag is set to true) so that + // subsequent event will not match that entry. + DedupingAndroidLogReader(TraceProcessorContext* context, + int32_t year, + std::vector events); + ~DedupingAndroidLogReader() override; + + base::Status ProcessEvent(int64_t event_ts, AndroidLogEvent event) override; + + private: + std::vector events_; +}; + +} // namespace perfetto::trace_processor + +#endif // SRC_TRACE_PROCESSOR_IMPORTERS_ANDROID_BUGREPORT_ANDROID_LOG_READER_H_ diff --git a/src/trace_processor/importers/android_bugreport/android_log_unittest.cc b/src/trace_processor/importers/android_bugreport/android_log_unittest.cc new file mode 100644 index 0000000000..10a7b2a0b8 --- /dev/null +++ b/src/trace_processor/importers/android_bugreport/android_log_unittest.cc @@ -0,0 +1,244 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include + +#include "perfetto/base/status.h" +#include "perfetto/base/time.h" +#include "perfetto/ext/base/string_utils.h" +#include "perfetto/trace_processor/trace_blob.h" +#include "perfetto/trace_processor/trace_blob_view.h" +#include "protos/perfetto/trace/clock_snapshot.pbzero.h" +#include "src/trace_processor/importers/android_bugreport/android_bugreport_reader.h" +#include "src/trace_processor/importers/android_bugreport/android_log_event.h" +#include "src/trace_processor/importers/android_bugreport/android_log_reader.h" +#include "src/trace_processor/importers/common/clock_tracker.h" +#include "src/trace_processor/importers/common/metadata_tracker.h" +#include "src/trace_processor/importers/common/trace_parser.h" +#include "src/trace_processor/sorter/trace_sorter.h" +#include "src/trace_processor/storage/trace_storage.h" +#include "src/trace_processor/types/trace_processor_context.h" +#include "test/gtest_and_gmock.h" + +#include "protos/perfetto/common/android_log_constants.pbzero.h" + +namespace perfetto::trace_processor { + +static void PrintTo(const AndroidLogEvent& event, std::ostream* os) { + *os << "(pid: " << event.pid << ", " + << "tid: " << event.tid << ", " + << "prio: " << event.prio << ", " + << "tag_id: " << event.tag.raw_id() << ", " + << "msg_id: " << event.msg.raw_id() << ")"; +} + +namespace { +const int64_t kStoNs = 1000000000LL; + +class EventParserMock : public AndroidLogEventParser { + public: + ~EventParserMock() override = default; + MOCK_METHOD(void, + ParseAndroidLogEvent, + (int64_t, AndroidLogEvent), + (override)); +}; + +class AndroidLogReaderTest : public ::testing::Test { + public: + AndroidLogReaderTest() { + context_.storage = std::make_shared(); + context_.clock_tracker = std::make_unique(&context_); + context_.metadata_tracker = + std::make_unique(context_.storage.get()); + context_.clock_tracker->SetTraceTimeClock( + protos::pbzero::ClockSnapshot::Clock::REALTIME); + context_.sorter = std::make_unique( + &context_, TraceSorter::SortingMode::kDefault); + mock_parser_ = new EventParserMock(); + context_.android_log_event_parser.reset(mock_parser_); + } + + using P = ::perfetto::protos::pbzero::AndroidLogPriority; + + StringId S(const char* str) { return context_.storage->InternString(str); } + EventParserMock& mock_parser() { return *mock_parser_; } + + TraceProcessorContext* context() { return &context_; } + + private: + TraceProcessorContext context_; + EventParserMock* mock_parser_; +}; + +TEST_F(AndroidLogReaderTest, PersistentLogFormat) { + constexpr char kInput[] = + "01-02 03:04:05.678901 1000 2000 D Tag: message\n" + "12-31 23:59:00.123456 1 2 I [tag:with:colon]: moar long message\n" + "12-31 23:59:00.123 1 2 W [tag:with:colon]: moar long message\n" + "12-31 23:59:00.1 1 2 E [tag:with:colon]: moar long message\n" + "12-31 23:59:00.01 1 2 F [tag:with:colon]: moar long message\n"; + + AndroidLogReader reader(context(), 2020); + + EXPECT_CALL( + mock_parser(), + ParseAndroidLogEvent( + base::MkTime(2020, 1, 2, 3, 4, 5) * kStoNs + 678901000, + AndroidLogEvent{1000, 2000, P::PRIO_DEBUG, S("Tag"), S("message")})); + + EXPECT_CALL(mock_parser(), + ParseAndroidLogEvent( + base::MkTime(2020, 12, 31, 23, 59, 0) * kStoNs + 123456000, + AndroidLogEvent{1, 2, P::PRIO_INFO, S("[tag:with:colon]"), + S("moar long message")})); + EXPECT_CALL(mock_parser(), + ParseAndroidLogEvent( + base::MkTime(2020, 12, 31, 23, 59, 0) * kStoNs + 123000000, + AndroidLogEvent{1, 2, P::PRIO_WARN, S("[tag:with:colon]"), + S("moar long message")})); + EXPECT_CALL(mock_parser(), + ParseAndroidLogEvent( + base::MkTime(2020, 12, 31, 23, 59, 0) * kStoNs + 100000000, + AndroidLogEvent{1, 2, P::PRIO_ERROR, S("[tag:with:colon]"), + S("moar long message")})); + EXPECT_CALL(mock_parser(), + ParseAndroidLogEvent( + base::MkTime(2020, 12, 31, 23, 59, 0) * kStoNs + 10000000, + AndroidLogEvent{1, 2, P::PRIO_FATAL, S("[tag:with:colon]"), + S("moar long message")})); + + EXPECT_TRUE( + reader.Parse(TraceBlobView(TraceBlob::CopyFrom(kInput, sizeof(kInput)))) + .ok()); + EXPECT_EQ(context()->storage->stats()[stats::android_log_num_failed].value, + 0); + + context()->sorter->ExtractEventsForced(); +} + +TEST_F(AndroidLogReaderTest, BugreportFormat) { + constexpr char kInput[] = + "07-28 14:25:20.355 0 1 2 I init : Loaded kernel module\n" + "07-28 14:25:54.876 1000 643 644 D PackageManager: No files\n" + "08-24 23:39:12.272 root 0 1 I : c0 11835 binder: 1\n" + "08-24 23:39:12.421 radio 2532 2533 D TelephonyProvider: Using old\n"; + + AndroidLogReader reader(context(), 2020); + + EXPECT_CALL(mock_parser(), + ParseAndroidLogEvent( + base::MkTime(2020, 7, 28, 14, 25, 20) * kStoNs + 355000000, + AndroidLogEvent{1, 2, P::PRIO_INFO, S("init"), + S("Loaded kernel module")})); + EXPECT_CALL(mock_parser(), + ParseAndroidLogEvent( + base::MkTime(2020, 7, 28, 14, 25, 54) * kStoNs + 876000000, + AndroidLogEvent{643, 644, P::PRIO_DEBUG, S("PackageManager"), + S("No files")})); + EXPECT_CALL(mock_parser(), + ParseAndroidLogEvent( + base::MkTime(2020, 8, 24, 23, 39, 12) * kStoNs + 272000000, + AndroidLogEvent{0, 1, P::PRIO_INFO, S(""), + S("c0 11835 binder: 1")})); + EXPECT_CALL(mock_parser(), + ParseAndroidLogEvent( + base::MkTime(2020, 8, 24, 23, 39, 12) * kStoNs + 421000000, + AndroidLogEvent{2532, 2533, P::PRIO_DEBUG, + S("TelephonyProvider"), S("Using old")})); + + EXPECT_TRUE( + reader.Parse(TraceBlobView(TraceBlob::CopyFrom(kInput, sizeof(kInput)))) + .ok()); + EXPECT_EQ(context()->storage->stats()[stats::android_log_num_failed].value, + 0); + + context()->sorter->ExtractEventsForced(); +} + +// Tests the deduping logic. This is used when parsing events first from the +// persistent logcat (which has us resolution) and then from dumpstate (which +// has ms resolution and sometimes contains dupes of the persistent entries). +TEST_F(AndroidLogReaderTest, Dedupe) { + constexpr char kLogcatInput[] = + "01-01 00:00:01.100000 0 1 1 I tag : M1\n" + "01-01 00:00:01.100111 0 1 1 I tag : M2\n" + "01-01 00:00:01.100111 0 1 1 I tag : M3\n" + "01-01 00:00:01.100222 0 1 1 I tag : M4\n" + "01-01 00:00:01.101000 0 1 1 I tag : M5\n"; + constexpr char kDumpstateInput[] = + "01-01 00:00:01.100 0 1 1 I tag : M1\n" // Dupe + "01-01 00:00:01.100 0 1 1 I tag : M1\n" // Not a dupe + "01-01 00:00:01.100 0 1 1 I tag : M1.5\n" + "01-01 00:00:01.100 0 1 1 I tag : M3\n" // Dupe + "01-01 00:00:01.100 0 1 1 I tag : M4\n" // Dupe + "01-01 00:00:01.101 0 1 1 I tag : M5\n" // Dupe + "01-01 00:00:01.101 0 1 1 I tag : M6\n"; + + EXPECT_CALL(mock_parser(), + ParseAndroidLogEvent( + base::MkTime(2020, 1, 1, 0, 0, 1) * kStoNs + 100000000, + AndroidLogEvent{1, 1, P::PRIO_INFO, S("tag"), S("M1")})) + .Times(2); + EXPECT_CALL(mock_parser(), + ParseAndroidLogEvent( + base::MkTime(2020, 1, 1, 0, 0, 1) * kStoNs + 100000000, + AndroidLogEvent{1, 1, P::PRIO_INFO, S("tag"), S("M1.5")})); + EXPECT_CALL(mock_parser(), + ParseAndroidLogEvent( + base::MkTime(2020, 1, 1, 0, 0, 1) * kStoNs + 100111000, + AndroidLogEvent{1, 1, P::PRIO_INFO, S("tag"), S("M2")})); + EXPECT_CALL(mock_parser(), + ParseAndroidLogEvent( + base::MkTime(2020, 1, 1, 0, 0, 1) * kStoNs + 100111000, + AndroidLogEvent{1, 1, P::PRIO_INFO, S("tag"), S("M3")})); + EXPECT_CALL(mock_parser(), + ParseAndroidLogEvent( + base::MkTime(2020, 1, 1, 0, 0, 1) * kStoNs + 100222000, + AndroidLogEvent{1, 1, P::PRIO_INFO, S("tag"), S("M4")})); + EXPECT_CALL(mock_parser(), + ParseAndroidLogEvent( + base::MkTime(2020, 1, 1, 0, 0, 1) * kStoNs + 101000000, + AndroidLogEvent{1, 1, P::PRIO_INFO, S("tag"), S("M5")})); + EXPECT_CALL(mock_parser(), + ParseAndroidLogEvent( + base::MkTime(2020, 1, 1, 0, 0, 1) * kStoNs + 101000000, + AndroidLogEvent{1, 1, P::PRIO_INFO, S("tag"), S("M6")})); + + BufferingAndroidLogReader logcat_reader(context(), 2020); + + EXPECT_TRUE(logcat_reader + .Parse(TraceBlobView( + TraceBlob::CopyFrom(kLogcatInput, sizeof(kLogcatInput)))) + .ok()); + + DedupingAndroidLogReader dumstate_reader( + context(), 2020, std::move(logcat_reader).ConsumeBufferedEvents()); + EXPECT_TRUE(dumstate_reader + .Parse(TraceBlobView(TraceBlob::CopyFrom( + kDumpstateInput, sizeof(kDumpstateInput)))) + .ok()); + EXPECT_EQ(context()->storage->stats()[stats::android_log_num_failed].value, + 0); + + context()->sorter->ExtractEventsForced(); +} + +} // namespace +} // namespace perfetto::trace_processor diff --git a/src/trace_processor/importers/android_bugreport/chunked_line_reader.cc b/src/trace_processor/importers/android_bugreport/chunked_line_reader.cc new file mode 100644 index 0000000000..67c4234388 --- /dev/null +++ b/src/trace_processor/importers/android_bugreport/chunked_line_reader.cc @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "src/trace_processor/importers/android_bugreport/chunked_line_reader.h" + +#include +#include +#include +#include + +#include "perfetto/base/logging.h" +#include "perfetto/base/status.h" +#include "perfetto/ext/base/status_or.h" +#include "perfetto/ext/base/string_view.h" +#include "perfetto/public/compiler.h" +#include "perfetto/trace_processor/trace_blob.h" +#include "perfetto/trace_processor/trace_blob_view.h" +#include "src/trace_processor/util/status_macros.h" + +namespace perfetto ::trace_processor { + +namespace { + +TraceBlobView Append(const TraceBlobView& head, const TraceBlobView& tail) { + size_t size = head.size() + tail.size(); + if (size == 0) { + return TraceBlobView(); + } + auto blob = TraceBlob::Allocate(size); + memcpy(blob.data(), head.data(), head.size()); + memcpy(blob.data() + head.size(), tail.data(), tail.size()); + return TraceBlobView(std::move(blob)); +} + +struct SpliceResult { + // Full line including '\n'. Empty if no full line was found. + TraceBlobView line; + // Any leftovers + TraceBlobView leftovers; +}; + +SpliceResult SpliceAtNewLine(TraceBlobView data) { + for (size_t i = 0; i < data.size(); ++i) { + if (data.data()[i] == '\n') { + return {data.slice_off(0, i), data.slice_off(i + 1, data.size() - i - 1)}; + } + } + return {TraceBlobView(), std::move(data)}; +} +} // namespace + +base::Status ChunkedLineReader::OnLine(const TraceBlobView& data) { + return ParseLine(base::StringView(reinterpret_cast(data.data()), + data.size())); +} + +base::StatusOr ChunkedLineReader::SpliceLoop( + TraceBlobView data) { + while (true) { + SpliceResult res = SpliceAtNewLine(std::move(data)); + if (res.line.size() == 0) { + return std::move(res.leftovers); + } + RETURN_IF_ERROR(OnLine(res.line)); + data = std::move(res.leftovers); + } +} + +base::Status ChunkedLineReader::Parse(TraceBlobView data) { + if (data.size() == 0) { + return base::OkStatus(); + } + + if (PERFETTO_LIKELY(buffer_.size() == 0)) { + ASSIGN_OR_RETURN(buffer_, SpliceLoop(std::move(data))); + return base::OkStatus(); + } + + SpliceResult res = SpliceAtNewLine(std::move(data)); + if (res.line.size() == 0) { + buffer_ = Append(buffer_, res.leftovers); + return base::OkStatus(); + } + + buffer_ = Append(buffer_, res.line); + RETURN_IF_ERROR(OnLine(buffer_)); + ASSIGN_OR_RETURN(buffer_, SpliceLoop(std::move(res.leftovers))); + return base::OkStatus(); +} + +void ChunkedLineReader::NotifyEndOfFile() { + EndOfStream(base::StringView(reinterpret_cast(buffer_.data()), + buffer_.size())); +} + +} // namespace perfetto::trace_processor diff --git a/src/trace_processor/importers/android_bugreport/chunked_line_reader.h b/src/trace_processor/importers/android_bugreport/chunked_line_reader.h new file mode 100644 index 0000000000..c64d55f89b --- /dev/null +++ b/src/trace_processor/importers/android_bugreport/chunked_line_reader.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_ANDROID_BUGREPORT_CHUNKED_LINE_READER_H_ +#define SRC_TRACE_PROCESSOR_IMPORTERS_ANDROID_BUGREPORT_CHUNKED_LINE_READER_H_ + +#include "perfetto/base/status.h" +#include "perfetto/ext/base/status_or.h" +#include "perfetto/ext/base/string_view.h" +#include "perfetto/trace_processor/trace_blob_view.h" +#include "src/trace_processor/importers/common/chunked_trace_reader.h" + +namespace perfetto ::trace_processor { + +// Adapter on top of `ChunkedTraceReader` that performs line by line parsing. +class ChunkedLineReader : public ChunkedTraceReader { + public: + base::Status Parse(TraceBlobView) final; + void NotifyEndOfFile() final; + + // Called for each line in the input. Each line is terminated by a '\n' + // character. The new line character will be included the `line`. + virtual base::Status ParseLine(base::StringView line) = 0; + + // Similar to `NotifyEndOfFile` but this also provides any leftovers. That + // would heppen if the last line in a stream is not terminated by the newline + // character. + virtual void EndOfStream(base::StringView leftovers) = 0; + + private: + base::StatusOr SpliceLoop(TraceBlobView data); + base::Status OnLine(const TraceBlobView& data); + + // Buffers any leftovers from a previous call to `Parse`; + TraceBlobView buffer_; +}; + +} // namespace perfetto::trace_processor +#endif // SRC_TRACE_PROCESSOR_IMPORTERS_ANDROID_BUGREPORT_CHUNKED_LINE_READER_H_ diff --git a/src/trace_processor/importers/common/BUILD.gn b/src/trace_processor/importers/common/BUILD.gn index 43dfb2345d..1a222925be 100644 --- a/src/trace_processor/importers/common/BUILD.gn +++ b/src/trace_processor/importers/common/BUILD.gn @@ -28,6 +28,8 @@ source_set("common") { "clock_converter.h", "clock_tracker.cc", "clock_tracker.h", + "cpu_tracker.cc", + "cpu_tracker.h", "create_mapping_params.h", "deobfuscation_mapping_table.cc", "deobfuscation_mapping_table.h", @@ -45,11 +47,15 @@ source_set("common") { "mapping_tracker.h", "metadata_tracker.cc", "metadata_tracker.h", + "process_track_translation_table.cc", + "process_track_translation_table.h", "process_tracker.cc", "process_tracker.h", "sched_event_state.h", "sched_event_tracker.cc", "sched_event_tracker.h", + "scoped_active_trace_file.cc", + "scoped_active_trace_file.h", "slice_tracker.cc", "slice_tracker.h", "slice_translation_table.cc", @@ -60,6 +66,8 @@ source_set("common") { "system_info_tracker.h", "thread_state_tracker.cc", "thread_state_tracker.h", + "trace_file_tracker.cc", + "trace_file_tracker.h", "trace_parser.cc", "track_tracker.cc", "track_tracker.h", @@ -86,7 +94,9 @@ source_set("common") { "../../types", "../../util:build_id", "../../util:profiler_util", + "../../util:trace_type", "../fuchsia:fuchsia_record", + "../perf:record", "../systrace:systrace_line", ] } @@ -116,6 +126,7 @@ source_set("unittests") { "deobfuscation_mapping_table_unittest.cc", "event_tracker_unittest.cc", "flow_tracker_unittest.cc", + "process_track_translation_table_unittest.cc", "process_tracker_unittest.cc", "slice_tracker_unittest.cc", "slice_translation_table_unittest.cc", diff --git a/src/trace_processor/importers/common/address_range.h b/src/trace_processor/importers/common/address_range.h index 5d7f430ca4..c7f074cf7e 100644 --- a/src/trace_processor/importers/common/address_range.h +++ b/src/trace_processor/importers/common/address_range.h @@ -18,8 +18,8 @@ #define SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_ADDRESS_RANGE_H_ #include - #include +#include #include #include #include @@ -274,6 +274,104 @@ class AddressRangeMap { const_iterator end() const { return ranges_.end(); } iterator erase(const_iterator pos) { return ranges_.erase(pos); } + // Emplaces a new value into the map by first trimming all overlapping + // intervals, deleting them if the overlap is fully contained in the new + // range, and splitting into two entries pointing to the same value if a + // single entry fully contains the new range. Returns true on success, fails + // if the new range is empty (as there is effectively no key to map from). + template + bool TrimOverlapsAndEmplace(AddressRange range, Args&&... args) { + if (range.empty()) { + return false; + } + auto it = ranges_.upper_bound(range.start()); + PERFETTO_DCHECK(it == ranges_.end() || range.start() < it->first.end()); + + // First check if we need to trim the first overlapping range, if any. + if (it != ranges_.end() && it->first.start() < range.start()) { + // Range starts after `it->first` starts, but before `it->first` ends, and + // so overlaps it: + // it->first: |-----------? + // range: |------? + PERFETTO_DCHECK(it->first.Overlaps(range)); + + // Cache it->first since we'll be mutating it in TrimEntryRange. + AddressRange existing_range = it->first; + + // Trim the first overlap to end at the start of the range. + // it->first: |----| + // range: |------? + it = TrimEntryRange(it, + AddressRange(existing_range.start(), range.start())); + + if (range.end() < existing_range.end()) { + // Range also ends before existing_range, thus strictly containing it. + // existing_range: |-----------| (previously it->first) + // range: |----| + PERFETTO_DCHECK(existing_range.Contains(range)); + + // In this special case, we need to split existing_range into two + // ranges, with the same value, and insert the new range between them: + // it->first: |----| + // range: |----| + // tail: |-| + // We've already trimmed existing_range (as it->first), so just add + // the tail now. + + AddressRange tail(range.end(), existing_range.end()); + ranges_.emplace_hint(std::next(it, 1), tail, Value(it->second)); + } + + // After trimming, the current iterated range is now before the new + // range. This means it no longer ends after the new range starts, and we + // need to advance the iterator to the new upper_bound. + ++it; + PERFETTO_DCHECK(it == ranges_.upper_bound(range.start())); + } + + // Now, check for any ranges which are _fully_ contained inside + // the existing range. + while (it != ranges_.end() && it->first.end() <= range.end()) { + // Range fully contains `it->first`: + // it->first: |----| + // range: |-----------| + // + // We're testing for whether it ends after it->first, and we know it + // starts before it->first (because we've already handled the first + // overlap), so this existing range is fully contained inside the new + // range + PERFETTO_DCHECK(range.Contains(it->first)); + it = ranges_.erase(it); + } + + // Finally, check if we need to trim the last range. We know that it + // ends after the new range, but it might also start after the new + // range, or we might have reached the end, so this is really a check for + // overlap. + if (it != ranges_.end() && it->first.start() < range.end()) { + // Range overlaps with it->first, and ends before `it->first`: + // it->first: |----------| + // range: |-----| + PERFETTO_DCHECK(range.Overlaps(it->first)); + + // Trim this overlap to end after the end of the range, and insert it + // after where the range will be inserted. + // range: |-----| + // it->first: |-----| + it = TrimEntryRange(it, AddressRange(range.end(), it->first.end())); + + // `it` now points to the newly trimmed range, which is _after_ where + // we want to insert the new range. This is what we want as an insertion + // hint, so keep it as is. + } + + ranges_.emplace_hint(it, std::piecewise_construct, + std::forward_as_tuple(range), + std::forward_as_tuple(std::forward(args)...)); + + return true; + } + // Emplaces a new value into the map by first deleting all overlapping // intervals. It takes an optional (set to nullptr to ignore) callback `cb` // that will be called for each deleted map entry. @@ -300,15 +398,6 @@ class AddressRangeMap { return true; } - // Same as above but without a callback. - template - bool DeleteOverlapsAndEmplace(AddressRange range, Args&&... args) { - struct NoOp { - void operator()(std::pair&) {} - }; - return DeleteOverlapsAndEmplace(NoOp(), range, std::forward(args)...); - } - // Calls `cb` for each entry in the map that overlaps the given `range`. That // is, there is a point so for which `AddressRange::Contains` returns true for // both the entry and the given `range' @@ -324,6 +413,24 @@ class AddressRangeMap { } private: + // Trim an entry's address range to a new value. The new value must be fully + // contained inside the existing range's value, to guarantee that the ranges + // stay in the same order. Returns a new iterator to the trimmed entry, since + // the trimming process invalidates the iterator. + typename Impl::iterator TrimEntryRange(typename Impl::iterator it, + AddressRange new_range) { + PERFETTO_DCHECK(it->first.Contains(new_range)); + PERFETTO_DCHECK(!new_range.empty()); + + // Advance the iterator so that it stays valid -- it now also conveniently + // points to the entry after the current entry, which is exactly the hint we + // want for re-inserting in the same place. + auto extracted = ranges_.extract(it++); + extracted.key() = new_range; + // Reinsert in the same place, using the advanced iterator as a hint. + return ranges_.insert(it, std::move(extracted)); + } + // Invariants: // * There are no overlapping ranges. // * There are no empty ranges. diff --git a/src/trace_processor/importers/common/address_range_unittest.cc b/src/trace_processor/importers/common/address_range_unittest.cc index 35f67ae6c9..85ae4682a2 100644 --- a/src/trace_processor/importers/common/address_range_unittest.cc +++ b/src/trace_processor/importers/common/address_range_unittest.cc @@ -55,6 +55,11 @@ MATCHER_P2(IteratorPointsTo, container, matcher, "") { ExplainMatchResult(matcher, *arg, result_listener); } +template +auto MapEntry(uint64_t start, uint64_t end, Value value) { + return Pair(AddressRange(start, end), value); +} + auto AppendRangesTo(std::vector& ranges) { return [&ranges](std::pair& e) { ranges.push_back(e.first); @@ -146,7 +151,7 @@ TEST(AddressRangeMap, EmplaceFailsForOverlaps) { EXPECT_FALSE(map.Emplace(AddressRange(11, 19))); EXPECT_FALSE(map.Emplace(AddressRange(0, 11))); EXPECT_FALSE(map.Emplace(AddressRange(19, 30))); - EXPECT_THAT(map, ElementsAre(Pair(AddressRange(10, 20), 42))); + EXPECT_THAT(map, ElementsAre(MapEntry(10, 20, 42))); } TEST(AddressRangeMap, EmplaceSucceedsForNonOverlaps) { @@ -174,7 +179,7 @@ TEST(AddressRangeMap, DeleteOverlapsAndEmplaceFailsForEmptyRange) { EXPECT_FALSE(map.Emplace(AddressRange(0, 0))); EXPECT_FALSE(map.Emplace(AddressRange(100, 100))); - EXPECT_THAT(map, ElementsAre(Pair(AddressRange(0, 10), 42))); + EXPECT_THAT(map, ElementsAre(MapEntry(0, 10, 42))); } TEST(AddressRangeMap, FindAddress) { @@ -214,9 +219,9 @@ TEST(AddressRangeMap, FindRangeThatContains) { map.Emplace(AddressRange(10, 20), 1); map.Emplace(AddressRange(25, 30), 2); - auto match_1 = IteratorPointsTo(map, Pair(AddressRange(0, 10), 0)); - auto match_2 = IteratorPointsTo(map, Pair(AddressRange(10, 20), 1)); - auto match_3 = IteratorPointsTo(map, Pair(AddressRange(25, 30), 2)); + auto match_1 = IteratorPointsTo(map, MapEntry(0, 10, 0)); + auto match_2 = IteratorPointsTo(map, MapEntry(10, 20, 1)); + auto match_3 = IteratorPointsTo(map, MapEntry(25, 30, 2)); EXPECT_THAT(map.FindRangeThatContains({0, 10}), match_1); EXPECT_THAT(map.FindRangeThatContains({0, 1}), match_1); @@ -239,6 +244,64 @@ TEST(AddressRangeMap, FindRangeThatContains) { EXPECT_THAT(map.FindRangeThatContains({14, 27}), Eq(map.end())); } +TEST(AddressRangeMap, TrimOverlapsAndEmplace) { + const AddressRangeMap entries = []() { + AddressRangeMap map; + map.Emplace(AddressRange(0, 10), 0); + map.Emplace(AddressRange(10, 20), 1); + map.Emplace(AddressRange(25, 30), 2); + return map; + }(); + + { + AddressRangeMap map = entries; + map.TrimOverlapsAndEmplace({30, 100}, 5); + EXPECT_THAT(map, ElementsAre(MapEntry(0, 10, 0), MapEntry(10, 20, 1), + MapEntry(25, 30, 2), MapEntry(30, 100, 5))); + } + + { + AddressRangeMap map = entries; + map.TrimOverlapsAndEmplace({9, 10}, 5); + EXPECT_THAT(map, ElementsAre(MapEntry(0, 9, 0), MapEntry(9, 10, 5), + MapEntry(10, 20, 1), MapEntry(25, 30, 2))); + } + + { + AddressRangeMap map = entries; + map.TrimOverlapsAndEmplace({5, 11}, 5); + EXPECT_THAT(map, ElementsAre(MapEntry(0, 5, 0), MapEntry(5, 11, 5), + MapEntry(11, 20, 1), MapEntry(25, 30, 2))); + } + + { + AddressRangeMap map = entries; + map.TrimOverlapsAndEmplace({5, 25}, 5); + EXPECT_THAT(map, ElementsAre(MapEntry(0, 5, 0), MapEntry(5, 25, 5), + MapEntry(25, 30, 2))); + } + + { + AddressRangeMap map = entries; + map.TrimOverlapsAndEmplace({5, 31}, 5); + EXPECT_THAT(map, ElementsAre(MapEntry(0, 5, 0), MapEntry(5, 31, 5))); + } + + { + AddressRangeMap map = entries; + map.TrimOverlapsAndEmplace({0, 100}, 5); + EXPECT_THAT(map, ElementsAre(MapEntry(0, 100, 5))); + } + + { + AddressRangeMap map = entries; + map.TrimOverlapsAndEmplace({3, 7}, 5); + EXPECT_THAT(map, ElementsAre(MapEntry(0, 3, 0), MapEntry(3, 7, 5), + MapEntry(7, 10, 0), MapEntry(10, 20, 1), + MapEntry(25, 30, 2))); + } +} + TEST(AddressRangeMap, DeleteOverlapsAndEmplace) { const AddressRangeMap entries = []() { AddressRangeMap map; @@ -247,17 +310,14 @@ TEST(AddressRangeMap, DeleteOverlapsAndEmplace) { map.Emplace(AddressRange(25, 30), 2); return map; }(); - auto entry = [](uint64_t start, uint64_t end, int value) { - return std::make_pair(AddressRange(start, end), value); - }; { AddressRangeMap map = entries; std::vector deleted; map.DeleteOverlapsAndEmplace(AppendRangesTo(deleted), {30, 100}, 5); EXPECT_THAT(deleted, ElementsAre()); - EXPECT_THAT(map, ElementsAre(entry(0, 10, 0), entry(10, 20, 1), - entry(25, 30, 2), entry(30, 100, 5))); + EXPECT_THAT(map, ElementsAre(MapEntry(0, 10, 0), MapEntry(10, 20, 1), + MapEntry(25, 30, 2), MapEntry(30, 100, 5))); } { @@ -265,8 +325,8 @@ TEST(AddressRangeMap, DeleteOverlapsAndEmplace) { std::vector deleted; map.DeleteOverlapsAndEmplace(AppendRangesTo(deleted), {9, 10}, 5); EXPECT_THAT(deleted, ElementsAre(AddressRange(0, 10))); - EXPECT_THAT( - map, ElementsAre(entry(9, 10, 5), entry(10, 20, 1), entry(25, 30, 2))); + EXPECT_THAT(map, ElementsAre(MapEntry(9, 10, 5), MapEntry(10, 20, 1), + MapEntry(25, 30, 2))); } { @@ -275,7 +335,7 @@ TEST(AddressRangeMap, DeleteOverlapsAndEmplace) { map.DeleteOverlapsAndEmplace(AppendRangesTo(deleted), {5, 11}, 5); EXPECT_THAT(deleted, ElementsAre(AddressRange(0, 10), AddressRange(10, 20))); - EXPECT_THAT(map, ElementsAre(entry(5, 11, 5), entry(25, 30, 2))); + EXPECT_THAT(map, ElementsAre(MapEntry(5, 11, 5), MapEntry(25, 30, 2))); } { @@ -284,7 +344,7 @@ TEST(AddressRangeMap, DeleteOverlapsAndEmplace) { map.DeleteOverlapsAndEmplace(AppendRangesTo(deleted), {5, 25}, 5); EXPECT_THAT(deleted, ElementsAre(AddressRange(0, 10), AddressRange(10, 20))); - EXPECT_THAT(map, ElementsAre(entry(5, 25, 5), entry(25, 30, 2))); + EXPECT_THAT(map, ElementsAre(MapEntry(5, 25, 5), MapEntry(25, 30, 2))); } { @@ -293,7 +353,7 @@ TEST(AddressRangeMap, DeleteOverlapsAndEmplace) { map.DeleteOverlapsAndEmplace(AppendRangesTo(deleted), {5, 31}, 5); EXPECT_THAT(deleted, ElementsAre(AddressRange(0, 10), AddressRange(10, 20), AddressRange(25, 30))); - EXPECT_THAT(map, ElementsAre(entry(5, 31, 5))); + EXPECT_THAT(map, ElementsAre(MapEntry(5, 31, 5))); } { @@ -302,28 +362,10 @@ TEST(AddressRangeMap, DeleteOverlapsAndEmplace) { map.DeleteOverlapsAndEmplace(AppendRangesTo(deleted), {0, 100}, 5); EXPECT_THAT(deleted, ElementsAre(AddressRange(0, 10), AddressRange(10, 20), AddressRange(25, 30))); - EXPECT_THAT(map, ElementsAre(entry(0, 100, 5))); + EXPECT_THAT(map, ElementsAre(MapEntry(0, 100, 5))); } } -// No need to test all cases as the impl calls the one with callback underneath -// and this has already been tested. This test is more about making sure the -// template code is correct and it can be instantiated. -TEST(AddressRangeMap, DeleteOverlapsAndEmplaceWithoutCallback) { - auto entry = [](uint64_t start, uint64_t end, int value) { - return std::make_pair(AddressRange(start, end), value); - }; - AddressRangeMap map; - map.Emplace(AddressRange(0, 10), 0); - map.Emplace(AddressRange(10, 20), 1); - map.Emplace(AddressRange(25, 30), 2); - - std::vector deleted; - map.DeleteOverlapsAndEmplace(AppendRangesTo(deleted), {5, 11}, 5); - EXPECT_THAT(deleted, ElementsAre(AddressRange(0, 10), AddressRange(10, 20))); - EXPECT_THAT(map, ElementsAre(entry(5, 11, 5), entry(25, 30, 2))); -} - TEST(AddressRangeMap, ForOverlapsEmptyRangeDoesNothing) { AddressRangeMap map; map.Emplace(AddressRange(0, 10), 0); @@ -345,9 +387,9 @@ TEST(AddressRangeMap, ForOverlaps) { map.Emplace(AddressRange(40, 50), 4); MockFunction::value_type&)> cb; - EXPECT_CALL(cb, Call(Pair(AddressRange(10, 20), 1))); - EXPECT_CALL(cb, Call(Pair(AddressRange(20, 30), 2))); - EXPECT_CALL(cb, Call(Pair(AddressRange(35, 40), 3))); + EXPECT_CALL(cb, Call(MapEntry(10, 20, 1))); + EXPECT_CALL(cb, Call(MapEntry(20, 30, 2))); + EXPECT_CALL(cb, Call(MapEntry(35, 40, 3))); map.ForOverlaps(AddressRange(15, 36), cb.AsStdFunction()); } diff --git a/src/trace_processor/importers/common/args_tracker.cc b/src/trace_processor/importers/common/args_tracker.cc index 36b50b141c..d02d9ce68b 100644 --- a/src/trace_processor/importers/common/args_tracker.cc +++ b/src/trace_processor/importers/common/args_tracker.cc @@ -56,35 +56,82 @@ void ArgsTracker::Flush() { if (args_.empty()) return; - // We sort here because a single packet may add multiple args with different - // rowids. - auto comparator = [](const Arg& f, const Arg& s) { - // We only care that all args for a specific arg set appear in a contiguous - // block and that args within one arg set are sorted by key, but not about - // the relative order of one block to another. The simplest way to achieve - // that is to sort by table column pointer & row, which identify the arg - // set, and then by key. - if (f.column == s.column && f.row == s.row) - return f.key < s.key; - if (f.column == s.column) - return f.row < s.row; - return f.column < s.column; + // We need to ensure that the args with the same arg set (arg_set_id + row) + // and key are grouped together. This is important for joining the args from + // different events (e.g. trace event begin and trace event end might both + // have arguments). + // + // To achieve that (and do it quickly) we do two steps: + // - First, group all of the values within the same key together and compute + // the smallest index for each key. + // - Then we sort the args by column, row, smallest_index_for_key (to group + // keys) and index (to preserve the original ordering). + + struct Entry { + size_t index; + StringId key; + size_t smallest_index_for_key = 0; + + Entry(size_t i, StringId k) : index(i), key(k) {} }; - std::stable_sort(args_.begin(), args_.end(), comparator); + base::SmallVector entries; + for (const auto& arg : args_) { + entries.emplace_back(entries.size(), arg.key); + } + + // Step 1: Compute the `smallest_index_for_key`. + std::sort(entries.begin(), entries.end(), [](const Entry& a, const Entry& b) { + return std::tie(a.key, a.index) < std::tie(b.key, b.index); + }); + + // As the data is sorted by (`key`, `index`) now, then the objects with the + // same key will be contiguous and within this block it will be sorted by + // index. That means that `smallest_index_for_key` for the entire block should + // be the value of the first index in the block. + entries[0].smallest_index_for_key = entries[0].index; + for (size_t i = 1; i < entries.size(); ++i) { + entries[i].smallest_index_for_key = + entries[i].key == entries[i - 1].key + ? entries[i - 1].smallest_index_for_key + : entries[i].index; + } + + // Step 2: sort in the desired order: grouping by arg set first (column, row), + // then ensuring that the args with the same key are grouped together + // (smallest_index_for_key) and then preserving the original order within + // these group (index). + std::sort( + entries.begin(), entries.end(), [&](const Entry& a, const Entry& b) { + const Arg& first_arg = args_[a.index]; + const Arg& second_arg = args_[b.index]; + return std::tie(first_arg.column, first_arg.row, + a.smallest_index_for_key, + a.index) < std::tie(second_arg.column, second_arg.row, + b.smallest_index_for_key, b.index); + }); + + // Apply permutation of entries[].index to args. + base::SmallVector sorted_args; + for (uint32_t i = 0; i < entries.size(); i++) { + sorted_args.emplace_back(args_[entries[i].index]); + } + + // Insert args. for (uint32_t i = 0; i < args_.size();) { - const GlobalArgsTracker::Arg& arg = args_[i]; + const GlobalArgsTracker::Arg& arg = sorted_args[i]; auto* col = arg.column; uint32_t row = arg.row; uint32_t next_rid_idx = i + 1; - while (next_rid_idx < args_.size() && col == args_[next_rid_idx].column && - row == args_[next_rid_idx].row) { + while (next_rid_idx < sorted_args.size() && + col == sorted_args[next_rid_idx].column && + row == sorted_args[next_rid_idx].row) { next_rid_idx++; } - ArgSetId set_id = - context_->global_args_tracker->AddArgSet(&args_[0], i, next_rid_idx); + ArgSetId set_id = context_->global_args_tracker->AddArgSet(&sorted_args[0], + i, next_rid_idx); if (col->IsNullable()) { TypedColumn>::FromColumn(col)->Set(row, set_id); } else { diff --git a/src/trace_processor/importers/common/args_tracker.h b/src/trace_processor/importers/common/args_tracker.h index 964a6ad0ec..81d19f6709 100644 --- a/src/trace_processor/importers/common/args_tracker.h +++ b/src/trace_processor/importers/common/args_tracker.h @@ -154,12 +154,35 @@ class ArgsTracker { context_->storage->mutable_surfaceflinger_transactions_table(), id); } + BoundInserter AddArgsTo(tables::ViewCaptureTable::Id id) { + return AddArgsTo(context_->storage->mutable_viewcapture_table(), id); + } + + BoundInserter AddArgsTo(tables::WindowManagerTable::Id id) { + return AddArgsTo(context_->storage->mutable_windowmanager_table(), id); + } + BoundInserter AddArgsTo(tables::WindowManagerShellTransitionsTable::Id id) { return AddArgsTo( context_->storage->mutable_window_manager_shell_transitions_table(), id); } + BoundInserter AddArgsTo(tables::AndroidKeyEventsTable::Id id) { + return AddArgsTo(context_->storage->mutable_android_key_events_table(), + id); + } + + BoundInserter AddArgsTo(tables::AndroidMotionEventsTable::Id id) { + return AddArgsTo(context_->storage->mutable_android_motion_events_table(), + id); + } + + BoundInserter AddArgsTo(tables::AndroidInputEventDispatchTable::Id id) { + return AddArgsTo( + context_->storage->mutable_android_input_event_dispatch_table(), id); + } + BoundInserter AddArgsTo(MetadataId id) { auto* table = context_->storage->mutable_metadata_table(); uint32_t row = *table->id().IndexOf(id); diff --git a/src/trace_processor/importers/common/async_track_set_tracker_unittest.cc b/src/trace_processor/importers/common/async_track_set_tracker_unittest.cc index b10aec8214..173fd8921a 100644 --- a/src/trace_processor/importers/common/async_track_set_tracker_unittest.cc +++ b/src/trace_processor/importers/common/async_track_set_tracker_unittest.cc @@ -18,6 +18,7 @@ #include "src/trace_processor/importers/common/args_tracker.h" #include "src/trace_processor/importers/common/global_args_tracker.h" +#include "src/trace_processor/importers/common/process_track_translation_table.h" #include "src/trace_processor/importers/common/track_tracker.h" #include "src/trace_processor/types/trace_processor_context.h" #include "test/gtest_and_gmock.h" @@ -34,6 +35,8 @@ class AsyncTrackSetTrackerUnittest : public testing::Test { context_.args_tracker.reset(new ArgsTracker(&context_)); context_.track_tracker.reset(new TrackTracker(&context_)); context_.async_track_set_tracker.reset(new AsyncTrackSetTracker(&context_)); + context_.process_track_translation_table.reset( + new ProcessTrackTranslationTable(context_.storage.get())); storage_ = context_.storage.get(); tracker_ = context_.async_track_set_tracker.get(); diff --git a/src/trace_processor/importers/common/chunked_trace_reader.h b/src/trace_processor/importers/common/chunked_trace_reader.h index a86ae138ac..b8ae8aabc8 100644 --- a/src/trace_processor/importers/common/chunked_trace_reader.h +++ b/src/trace_processor/importers/common/chunked_trace_reader.h @@ -17,16 +17,11 @@ #ifndef SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_CHUNKED_TRACE_READER_H_ #define SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_CHUNKED_TRACE_READER_H_ -#include -#include +#include -#include +#include "perfetto/base/status.h" -#include "perfetto/trace_processor/basic_types.h" -#include "perfetto/trace_processor/status.h" - -namespace perfetto { -namespace trace_processor { +namespace perfetto::trace_processor { class TraceBlobView; @@ -40,13 +35,12 @@ class ChunkedTraceReader { // caller to match line/protos boundaries. The parser class has to deal with // intermediate buffering lines/protos that span across different chunks. // The buffer size is guaranteed to be > 0. - virtual util::Status Parse(TraceBlobView) = 0; + virtual base::Status Parse(TraceBlobView) = 0; // Called after the last Parse() call. virtual void NotifyEndOfFile() = 0; }; -} // namespace trace_processor -} // namespace perfetto +} // namespace perfetto::trace_processor #endif // SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_CHUNKED_TRACE_READER_H_ diff --git a/src/trace_processor/importers/common/clock_tracker.h b/src/trace_processor/importers/common/clock_tracker.h index 117e0e1da5..babfca6e4f 100644 --- a/src/trace_processor/importers/common/clock_tracker.h +++ b/src/trace_processor/importers/common/clock_tracker.h @@ -28,9 +28,12 @@ #include #include "perfetto/base/logging.h" +#include "perfetto/ext/base/flat_hash_map.h" #include "perfetto/ext/base/status_or.h" +#include "perfetto/public/compiler.h" #include "src/trace_processor/importers/common/metadata_tracker.h" #include "src/trace_processor/types/trace_processor_context.h" +#include "src/trace_processor/util/status_macros.h" namespace perfetto { namespace trace_processor { @@ -159,6 +162,27 @@ class ClockTracker { // Returns the internal snapshot id of this set of clocks. base::StatusOr AddSnapshot(const std::vector&); + // Sets clock offset for the given clock domain to convert to the host trace + // time. This is typically called by the code that reads the RemoteClockSync + // packet. Typically only the offset of |trace_time_clock_id_| (which is + // CLOCK_BOOTTIME) is used. + void SetClockOffset(ClockId clock_id, int64_t offset) { + clock_offsets_[clock_id] = offset; + } + + // Apply the clock offset to convert remote trace times to host trace time. + int64_t ToHostTraceTime(int64_t timestamp) { + if (PERFETTO_LIKELY(!context_->machine_id())) { + // No need to convert host timestamps. + return timestamp; + } + + // Find the offset for |trace_time_clock_id_| and apply the offset, or + // default offset 0 if not offset is found for |trace_time_clock_id_|. + int64_t clock_offset = clock_offsets_[trace_time_clock_id_]; + return timestamp - clock_offset; + } + base::StatusOr ToTraceTime(ClockId clock_id, int64_t timestamp) { if (PERFETTO_UNLIKELY(!trace_time_clock_id_used_for_conversion_)) { context_->metadata_tracker->SetMetadata( @@ -167,9 +191,13 @@ class ClockTracker { trace_time_clock_id_used_for_conversion_ = true; } trace_time_clock_id_used_for_conversion_ = true; + if (clock_id == trace_time_clock_id_) - return timestamp; - return Convert(clock_id, timestamp, trace_time_clock_id_); + return ToHostTraceTime(timestamp); + + ASSIGN_OR_RETURN(int64_t ts, + Convert(clock_id, timestamp, trace_time_clock_id_)); + return ToHostTraceTime(ts); } // If trace clock and source clock are available in the snapshot will return @@ -196,6 +224,10 @@ class ClockTracker { cache_lookups_disabled_for_testing_ = v; } + const base::FlatHashMap& clock_offsets_for_testing() { + return clock_offsets_; + } + private: using SnapshotHash = uint32_t; @@ -339,6 +371,7 @@ class ClockTracker { std::minstd_rand rnd_; // For cache eviction. uint32_t cur_snapshot_id_ = 0; bool trace_time_clock_id_used_for_conversion_ = false; + base::FlatHashMap clock_offsets_; }; } // namespace trace_processor diff --git a/src/trace_processor/importers/common/clock_tracker_unittest.cc b/src/trace_processor/importers/common/clock_tracker_unittest.cc index 51bd020d13..f94c58a2eb 100644 --- a/src/trace_processor/importers/common/clock_tracker_unittest.cc +++ b/src/trace_processor/importers/common/clock_tracker_unittest.cc @@ -19,6 +19,7 @@ #include #include +#include "src/trace_processor/importers/common/machine_tracker.h" #include "src/trace_processor/importers/common/metadata_tracker.h" #include "src/trace_processor/storage/trace_storage.h" #include "src/trace_processor/types/trace_processor_context.h" @@ -303,6 +304,119 @@ TEST_F(ClockTrackerTest, CacheDoesntAffectResults) { } } +// Test clock conversion with offset to the host. +TEST_F(ClockTrackerTest, ClockOffset) { + EXPECT_FALSE(ct_.ToTraceTime(REALTIME, 0).ok()); + + context_.machine_tracker = + std::make_unique(&context_, 0x1001); + + // Client-to-host BOOTTIME offset is -10000 ns. + ct_.SetClockOffset(BOOTTIME, -10000); + + ct_.AddSnapshot({{REALTIME, 10}, {BOOTTIME, 10010}}); + ct_.AddSnapshot({{REALTIME, 20}, {BOOTTIME, 20220}}); + ct_.AddSnapshot({{REALTIME, 30}, {BOOTTIME, 30030}}); + ct_.AddSnapshot({{MONOTONIC, 1000}, {BOOTTIME, 100000}}); + + auto seq_clock_1 = ct_.SequenceToGlobalClock(1, 64); + auto seq_clock_2 = ct_.SequenceToGlobalClock(2, 64); + ct_.AddSnapshot({{MONOTONIC, 2000}, {seq_clock_1, 1200}}); + ct_.AddSnapshot({{seq_clock_1, 1300}, {seq_clock_2, 2000, 10, false}}); + + EXPECT_EQ(*ct_.ToTraceTime(REALTIME, 0), 20000); + EXPECT_EQ(*ct_.ToTraceTime(REALTIME, 1), 20001); + EXPECT_EQ(*ct_.ToTraceTime(REALTIME, 9), 20009); + EXPECT_EQ(*ct_.ToTraceTime(REALTIME, 10), 20010); + EXPECT_EQ(*ct_.ToTraceTime(REALTIME, 11), 20011); + EXPECT_EQ(*ct_.ToTraceTime(REALTIME, 19), 20019); + EXPECT_EQ(*ct_.ToTraceTime(REALTIME, 20), 30220); + EXPECT_EQ(*ct_.ToTraceTime(REALTIME, 21), 30221); + EXPECT_EQ(*ct_.ToTraceTime(REALTIME, 29), 30229); + EXPECT_EQ(*ct_.ToTraceTime(REALTIME, 30), 40030); + EXPECT_EQ(*ct_.ToTraceTime(REALTIME, 40), 40040); + + EXPECT_EQ(*ct_.ToTraceTime(MONOTONIC, 0), 100000 - 1000 + 10000); + EXPECT_EQ(*ct_.ToTraceTime(MONOTONIC, 999), 100000 - 1 + 10000); + EXPECT_EQ(*ct_.ToTraceTime(MONOTONIC, 1000), 100000 + 10000); + EXPECT_EQ(*ct_.ToTraceTime(MONOTONIC, 1e6), + static_cast(100000 - 1000 + 1e6 + 10000)); + + // seq_clock_1 -> MONOTONIC -> BOOTTIME -> apply offset. + EXPECT_EQ(*ct_.ToTraceTime(seq_clock_1, 1100), -100 + 1000 + 100000 + 10000); + // seq_clock_2 -> seq_clock_1 -> MONOTONIC -> BOOTTIME -> apply offset. + EXPECT_EQ(*ct_.ToTraceTime(seq_clock_2, 2100), + 100 * 10 + 100 + 1000 + 100000 + 10000); +} + +// Test conversion of remote machine timestamps without offset. This can happen +// if timestamp conversion for remote machines is done by trace data +// post-processing. +TEST_F(ClockTrackerTest, RemoteNoClockOffset) { + context_.machine_tracker = + std::make_unique(&context_, 0x1001); + + ct_.AddSnapshot({{REALTIME, 10}, {BOOTTIME, 10010}}); + ct_.AddSnapshot({{REALTIME, 20}, {BOOTTIME, 20220}}); + ct_.AddSnapshot({{MONOTONIC, 1000}, {BOOTTIME, 100000}}); + + auto seq_clock_1 = ct_.SequenceToGlobalClock(1, 64); + auto seq_clock_2 = ct_.SequenceToGlobalClock(2, 64); + ct_.AddSnapshot({{MONOTONIC, 2000}, {seq_clock_1, 1200}}); + ct_.AddSnapshot({{seq_clock_1, 1300}, {seq_clock_2, 2000, 10, false}}); + + EXPECT_EQ(*ct_.ToTraceTime(REALTIME, 0), 10000); + EXPECT_EQ(*ct_.ToTraceTime(REALTIME, 9), 10009); + EXPECT_EQ(*ct_.ToTraceTime(REALTIME, 10), 10010); + EXPECT_EQ(*ct_.ToTraceTime(REALTIME, 11), 10011); + EXPECT_EQ(*ct_.ToTraceTime(REALTIME, 19), 10019); + EXPECT_EQ(*ct_.ToTraceTime(REALTIME, 20), 20220); + EXPECT_EQ(*ct_.ToTraceTime(REALTIME, 21), 20221); + + EXPECT_EQ(*ct_.ToTraceTime(MONOTONIC, 0), 100000 - 1000); + EXPECT_EQ(*ct_.ToTraceTime(MONOTONIC, 999), 100000 - 1); + EXPECT_EQ(*ct_.ToTraceTime(MONOTONIC, 1000), 100000); + EXPECT_EQ(*ct_.ToTraceTime(MONOTONIC, 1e6), + static_cast(100000 - 1000 + 1e6)); + + // seq_clock_1 -> MONOTONIC -> BOOTTIME. + EXPECT_EQ(*ct_.ToTraceTime(seq_clock_1, 1100), -100 + 1000 + 100000); + // seq_clock_2 -> seq_clock_1 -> MONOTONIC -> BOOTTIME. + EXPECT_EQ(*ct_.ToTraceTime(seq_clock_2, 2100), + 100 * 10 + 100 + 1000 + 100000); +} + +// Test clock offset of non-defualt trace time clock domain. +TEST_F(ClockTrackerTest, NonDefaultTraceTimeClock) { + context_.machine_tracker = + std::make_unique(&context_, 0x1001); + + ct_.SetTraceTimeClock(MONOTONIC); + ct_.SetClockOffset(MONOTONIC, -2000); + ct_.SetClockOffset(BOOTTIME, -10000); // This doesn't take effect. + + ct_.AddSnapshot({{REALTIME, 10}, {BOOTTIME, 10010}}); + ct_.AddSnapshot({{MONOTONIC, 1000}, {BOOTTIME, 100000}}); + + auto seq_clock_1 = ct_.SequenceToGlobalClock(1, 64); + ct_.AddSnapshot({{MONOTONIC, 2000}, {seq_clock_1, 1200}}); + + int64_t realtime_to_trace_time_delta = -10 + 10010 - 100000 + 1000 - (-2000); + EXPECT_EQ(*ct_.ToTraceTime(REALTIME, 9), 9 + realtime_to_trace_time_delta); + EXPECT_EQ(*ct_.ToTraceTime(REALTIME, 10), 10 + realtime_to_trace_time_delta); + EXPECT_EQ(*ct_.ToTraceTime(REALTIME, 20), 20 + realtime_to_trace_time_delta); + + int64_t mono_to_trace_time_delta = -2000; + EXPECT_EQ(*ct_.ToTraceTime(MONOTONIC, 0), 0 - mono_to_trace_time_delta); + EXPECT_EQ(*ct_.ToTraceTime(MONOTONIC, 999), 999 - mono_to_trace_time_delta); + EXPECT_EQ(*ct_.ToTraceTime(MONOTONIC, 1000), 1000 - mono_to_trace_time_delta); + EXPECT_EQ(*ct_.ToTraceTime(MONOTONIC, 1e6), + static_cast(1e6) - mono_to_trace_time_delta); + + // seq_clock_1 -> MONOTONIC. + EXPECT_EQ(*ct_.ToTraceTime(seq_clock_1, 1100), 1100 - 1200 + 2000 - (-2000)); +} + } // namespace } // namespace trace_processor } // namespace perfetto diff --git a/src/trace_processor/importers/common/cpu_tracker.cc b/src/trace_processor/importers/common/cpu_tracker.cc new file mode 100644 index 0000000000..b679eae768 --- /dev/null +++ b/src/trace_processor/importers/common/cpu_tracker.cc @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "src/trace_processor/importers/common/cpu_tracker.h" + +#include "perfetto/base/logging.h" +#include "perfetto/public/compiler.h" + +#include "src/trace_processor/importers/common/machine_tracker.h" + +namespace perfetto::trace_processor { + +CpuTracker::CpuTracker(TraceProcessorContext* context) : context_(context) { + // Preallocate ucpu of this machine for maintaining the relative order between + // ucpu and cpu. + auto machine_id = context_->machine_tracker->machine_id(); + if (machine_id.has_value()) + ucpu_offset_ = machine_id->value * kMaxCpusPerMachine; + + for (auto id = 0u; id < kMaxCpusPerMachine; id++) { + // Only populate the |machine_id| column. The |cpu| column is update only + // when the CPU is present. + tables::CpuTable::Row cpu_row; + cpu_row.machine_id = machine_id; + context_->storage->mutable_cpu_table()->Insert(cpu_row); + } +} + +CpuTracker::~CpuTracker() = default; + +tables::CpuTable::Id CpuTracker::SetCpuInfo(uint32_t cpu, + base::StringView processor, + uint32_t cluster_id, + std::optional capacity) { + auto cpu_id = GetOrCreateCpu(cpu); + + auto cpu_row = context_->storage->mutable_cpu_table()->FindById(cpu_id); + PERFETTO_DCHECK(cpu_row.has_value()); + + if (!processor.empty()) { + auto string_id = context_->storage->InternString(processor); + cpu_row->set_processor(string_id); + } + cpu_row->set_cluster_id(cluster_id); + if (capacity) { + cpu_row->set_capacity(*capacity); + } + return cpu_id; +} + +} // namespace perfetto::trace_processor diff --git a/src/trace_processor/importers/common/cpu_tracker.h b/src/trace_processor/importers/common/cpu_tracker.h new file mode 100644 index 0000000000..3e2edc5e48 --- /dev/null +++ b/src/trace_processor/importers/common/cpu_tracker.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_CPU_TRACKER_H_ +#define SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_CPU_TRACKER_H_ + +#include + +#include "src/trace_processor/storage/trace_storage.h" +#include "src/trace_processor/tables/metadata_tables_py.h" +#include "src/trace_processor/types/trace_processor_context.h" + +namespace perfetto::trace_processor { + +class TraceProcessorContext; + +class CpuTracker { + public: + // The CPU table Id serves as the 'ucpu' in sched_slice and related for + // joining with this table. To optimize for single-machine traces, this class + // assumes a maximum of |kMaxCpusPerMachine| CPUs per machine to maintain a + // relative order of |cpu| and |ucpu| by pre-allocating |kMaxCpusPerMachine| + // records in the CPU table. The mapping between |ucpu| and |cpu| becomes + // |cpu| = |ucpu| % |kMaxCpusPerMachine|. + static constexpr uint32_t kMaxCpusPerMachine = 4096; + + explicit CpuTracker(TraceProcessorContext*); + ~CpuTracker(); + + tables::CpuTable::Id GetOrCreateCpu(uint32_t cpu) { + // CPU core number is in the range of 0..kMaxCpusPerMachine-1. + PERFETTO_CHECK(cpu < kMaxCpusPerMachine); + auto ucpu = ucpu_offset_ + cpu; + if (PERFETTO_LIKELY(cpu_ids_[cpu])) + return tables::CpuTable::Id(ucpu); + + cpu_ids_.set(cpu); + // Populate the optional |cpu| column. + context_->storage->mutable_cpu_table()->mutable_cpu()->Set(ucpu, cpu); + return tables::CpuTable::Id(ucpu); + } + + // Sets or updates the information for the specified CPU in the CpuTable. + tables::CpuTable::Id SetCpuInfo(uint32_t cpu, + base::StringView processor, + uint32_t cluster_id, + std::optional capacity); + + private: + TraceProcessorContext* const context_; + + // Tracks the mapping of CPU number to CpuTable::Id of the current + // machine. + std::bitset cpu_ids_; + uint32_t ucpu_offset_ = 0; +}; + +} // namespace perfetto::trace_processor + +#endif // SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_CPU_TRACKER_H_ diff --git a/src/trace_processor/importers/common/event_tracker.cc b/src/trace_processor/importers/common/event_tracker.cc index 43cf71051e..a38d776247 100644 --- a/src/trace_processor/importers/common/event_tracker.cc +++ b/src/trace_processor/importers/common/event_tracker.cc @@ -16,20 +16,18 @@ #include "src/trace_processor/importers/common/event_tracker.h" -#include +#include +#include #include #include "perfetto/base/logging.h" -#include "perfetto/ext/base/utils.h" #include "src/trace_processor/importers/common/args_tracker.h" -#include "src/trace_processor/importers/common/process_tracker.h" #include "src/trace_processor/importers/common/track_tracker.h" #include "src/trace_processor/storage/stats.h" +#include "src/trace_processor/storage/trace_storage.h" #include "src/trace_processor/types/trace_processor_context.h" -#include "src/trace_processor/types/variadic.h" -namespace perfetto { -namespace trace_processor { +namespace perfetto::trace_processor { EventTracker::EventTracker(TraceProcessorContext* context) : context_(context) {} @@ -65,9 +63,7 @@ std::optional EventTracker::PushCounter(int64_t timestamp, max_timestamp_ = timestamp; auto* counter_values = context_->storage->mutable_counter_table(); - return counter_values - ->Insert({timestamp, track_id, value, {}, context_->machine_id()}) - .id; + return counter_values->Insert({timestamp, track_id, value, {}}).id; } std::optional EventTracker::PushCounter( @@ -106,5 +102,4 @@ void EventTracker::FlushPendingEvents() { pending_upid_resolution_counter_.clear(); } -} // namespace trace_processor -} // namespace perfetto +} // namespace perfetto::trace_processor diff --git a/src/trace_processor/importers/common/event_tracker_unittest.cc b/src/trace_processor/importers/common/event_tracker_unittest.cc index c35c7d7151..da559b026d 100644 --- a/src/trace_processor/importers/common/event_tracker_unittest.cc +++ b/src/trace_processor/importers/common/event_tracker_unittest.cc @@ -18,6 +18,8 @@ #include "perfetto/base/logging.h" #include "src/trace_processor/importers/common/args_tracker.h" +#include "src/trace_processor/importers/common/cpu_tracker.h" +#include "src/trace_processor/importers/common/machine_tracker.h" #include "src/trace_processor/importers/common/process_tracker.h" #include "src/trace_processor/importers/common/track_tracker.h" #include "test/gtest_and_gmock.h" @@ -40,6 +42,8 @@ class EventTrackerTest : public ::testing::Test { context.process_tracker.reset(new ProcessTracker(&context)); context.event_tracker.reset(new EventTracker(&context)); context.track_tracker.reset(new TrackTracker(&context)); + context.machine_tracker.reset(new MachineTracker(&context, 0)); + context.cpu_tracker.reset(new CpuTracker(&context)); } protected: diff --git a/src/trace_processor/importers/common/global_args_tracker.h b/src/trace_processor/importers/common/global_args_tracker.h index 47e3bbdcbc..f94d2de887 100644 --- a/src/trace_processor/importers/common/global_args_tracker.h +++ b/src/trace_processor/importers/common/global_args_tracker.h @@ -94,7 +94,8 @@ class GlobalArgsTracker { explicit GlobalArgsTracker(TraceStorage* storage); - // Assumes that the interval [begin, end) of |args| is sorted by keys. + // Assumes that the interval [begin, end) of |args| has args with the same key + // grouped together. ArgSetId AddArgSet(const Arg* args, uint32_t begin, uint32_t end) { base::SmallVector valid_indexes; diff --git a/src/trace_processor/importers/common/mapping_tracker.cc b/src/trace_processor/importers/common/mapping_tracker.cc index 0dec3e5ca1..02976f910a 100644 --- a/src/trace_processor/importers/common/mapping_tracker.cc +++ b/src/trace_processor/importers/common/mapping_tracker.cc @@ -69,8 +69,8 @@ KernelMemoryMapping& MappingTracker::CreateKernelMemoryMapping( new KernelMemoryMapping(context_, std::move(params))); if (is_module) { - kernel_modules_.DeleteOverlapsAndEmplace(mapping->memory_range(), - mapping.get()); + kernel_modules_.TrimOverlapsAndEmplace(mapping->memory_range(), + mapping.get()); } else { kernel_ = mapping.get(); } @@ -81,14 +81,11 @@ KernelMemoryMapping& MappingTracker::CreateKernelMemoryMapping( UserMemoryMapping& MappingTracker::CreateUserMemoryMapping( UniquePid upid, CreateMappingParams params) { - // TODO(carlscab): Guess build_id if not provided. Some tools like simpleperf - // add a mapping file_name ->build_id that we could use here - const AddressRange mapping_range = params.memory_range; std::unique_ptr mapping( new UserMemoryMapping(context_, upid, std::move(params))); - user_memory_[upid].DeleteOverlapsAndEmplace(mapping_range, mapping.get()); + user_memory_[upid].TrimOverlapsAndEmplace(mapping_range, mapping.get()); jit_caches_[upid].ForOverlaps( mapping_range, [&](std::pair& entry) { @@ -156,7 +153,7 @@ void MappingTracker::AddJitRange(UniquePid upid, AddressRange jit_range, JitCache* jit_cache) { // TODO(carlscab): Deal with overlaps - jit_caches_[upid].DeleteOverlapsAndEmplace(jit_range, jit_cache); + jit_caches_[upid].TrimOverlapsAndEmplace(jit_range, jit_cache); user_memory_[upid].ForOverlaps( jit_range, [&](std::pair& entry) { PERFETTO_CHECK(jit_range.Contains(entry.first)); @@ -164,5 +161,15 @@ void MappingTracker::AddJitRange(UniquePid upid, }); } +VirtualMemoryMapping* MappingTracker::GetDummyMapping() { + if (!dummy_mapping_) { + CreateMappingParams params; + params.memory_range = + AddressRange::FromStartAndSize(0, std::numeric_limits::max()); + dummy_mapping_ = &InternMemoryMapping(params); + } + return dummy_mapping_; +} + } // namespace trace_processor } // namespace perfetto diff --git a/src/trace_processor/importers/common/mapping_tracker.h b/src/trace_processor/importers/common/mapping_tracker.h index bc45befe82..4791dba62c 100644 --- a/src/trace_processor/importers/common/mapping_tracker.h +++ b/src/trace_processor/importers/common/mapping_tracker.h @@ -91,6 +91,10 @@ class MappingTracker { // Jitted ranges will only be applied to UserMemoryMappings void AddJitRange(UniquePid upid, AddressRange range, JitCache* jit_cache); + // Sometimes we just need a mapping and we are lacking trace data to create a + // proper one. Use this mapping in those cases. + VirtualMemoryMapping* GetDummyMapping(); + private: template MappingImpl& AddMapping(std::unique_ptr mapping); @@ -136,6 +140,8 @@ class MappingTracker { KernelMemoryMapping* kernel_ = nullptr; base::FlatHashMap> jit_caches_; + + VirtualMemoryMapping* dummy_mapping_ = nullptr; }; } // namespace trace_processor diff --git a/src/trace_processor/importers/common/process_track_translation_table.cc b/src/trace_processor/importers/common/process_track_translation_table.cc new file mode 100644 index 0000000000..7cd5424737 --- /dev/null +++ b/src/trace_processor/importers/common/process_track_translation_table.cc @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "src/trace_processor/importers/common/process_track_translation_table.h" + +namespace perfetto::trace_processor { + +ProcessTrackTranslationTable::ProcessTrackTranslationTable( + TraceStorage* storage) + : storage_(storage) {} + +} // namespace perfetto::trace_processor diff --git a/src/trace_processor/importers/common/process_track_translation_table.h b/src/trace_processor/importers/common/process_track_translation_table.h new file mode 100644 index 0000000000..30f5f56d51 --- /dev/null +++ b/src/trace_processor/importers/common/process_track_translation_table.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_PROCESS_TRACK_TRANSLATION_TABLE_H_ +#define SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_PROCESS_TRACK_TRANSLATION_TABLE_H_ + +#include + +#include "perfetto/ext/base/flat_hash_map.h" +#include "perfetto/ext/base/string_view.h" +#include "src/trace_processor/storage/trace_storage.h" + +namespace perfetto::trace_processor { + +// Tracks and stores slice translation rules. It allows Trace Processor +// to for example deobfuscate slice names. +class ProcessTrackTranslationTable { + public: + ProcessTrackTranslationTable(TraceStorage* storage); + + // If the name is not mapped to anything, assumes that no translation is + // necessry, and returns the raw_name. + StringId TranslateName(StringId raw_name) const { + const auto* mapped_name = raw_to_deobfuscated_name_.Find(raw_name); + return mapped_name ? *mapped_name : raw_name; + } + + void AddNameTranslationRule(base::StringView raw, + base::StringView deobfuscated) { + const StringId raw_id = storage_->InternString(raw); + const StringId deobfuscated_id = storage_->InternString(deobfuscated); + raw_to_deobfuscated_name_[raw_id] = deobfuscated_id; + } + + private: + TraceStorage* storage_; + base::FlatHashMap raw_to_deobfuscated_name_; +}; + +} // namespace perfetto::trace_processor + +#endif // SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_PROCESS_TRACK_TRANSLATION_TABLE_H_ diff --git a/src/trace_processor/importers/common/process_track_translation_table_unittest.cc b/src/trace_processor/importers/common/process_track_translation_table_unittest.cc new file mode 100644 index 0000000000..a947204ba7 --- /dev/null +++ b/src/trace_processor/importers/common/process_track_translation_table_unittest.cc @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "src/trace_processor/importers/common/process_track_translation_table.h" +#include "test/gtest_and_gmock.h" + +namespace perfetto::trace_processor { +namespace { + +TEST(ProcessTrackTranslationTable, UnknownName) { + TraceStorage storage; + ProcessTrackTranslationTable table(&storage); + const StringId raw_name = storage.InternString("name1"); + EXPECT_EQ(raw_name, table.TranslateName(raw_name)); +} + +TEST(ProcessTrackTranslationTable, MappedName) { + TraceStorage storage; + ProcessTrackTranslationTable table(&storage); + table.AddNameTranslationRule("raw_name1", "mapped_name1"); + const StringId raw_name = storage.InternString("raw_name1"); + const StringId mapped_name = storage.InternString("mapped_name1"); + EXPECT_EQ(mapped_name, table.TranslateName(raw_name)); +} + +} // namespace +} // namespace perfetto::trace_processor diff --git a/src/trace_processor/importers/common/process_tracker.cc b/src/trace_processor/importers/common/process_tracker.cc index 3fc95fcbdf..e292801741 100644 --- a/src/trace_processor/importers/common/process_tracker.cc +++ b/src/trace_processor/importers/common/process_tracker.cc @@ -356,6 +356,17 @@ UniquePid ProcessTracker::SetProcessMetadata(uint32_t pid, UniquePid upid = GetOrCreateProcess(pid); auto* process_table = context_->storage->mutable_process_table(); + // If we both know the previous and current parent pid and the two are not + // matching, we must have died and restarted: create a new process. + if (pupid) { + std::optional prev_parent_upid = + process_table->parent_upid()[upid]; + if (prev_parent_upid && prev_parent_upid != pupid) { + upid = StartNewProcess(std::nullopt, ppid, pid, kNullStringId, + ThreadNamePriority::kOther); + } + } + StringId proc_name_id = context_->storage->InternString(name); process_table->mutable_name()->Set(upid, proc_name_id); process_table->mutable_cmdline()->Set( diff --git a/src/trace_processor/importers/common/sched_event_tracker.h b/src/trace_processor/importers/common/sched_event_tracker.h index 12aa4c02d0..ff4f537097 100644 --- a/src/trace_processor/importers/common/sched_event_tracker.h +++ b/src/trace_processor/importers/common/sched_event_tracker.h @@ -19,6 +19,7 @@ #include "src/trace_processor/importers/common/event_tracker.h" +#include "src/trace_processor/importers/common/cpu_tracker.h" #include "src/trace_processor/storage/trace_storage.h" #include "src/trace_processor/types/destructible.h" #include "src/trace_processor/types/trace_processor_context.h" @@ -45,9 +46,10 @@ class SchedEventTracker : public Destructible { // just switched to. Set the duration to -1, to indicate that the event is // not finished. Duration will be updated later after event finish. auto* sched = context_->storage->mutable_sched_slice_table(); - auto row_and_id = - sched->Insert({ts, /* duration */ -1, cpu, next_utid, kNullStringId, - next_prio, context_->machine_id()}); + // Get the unique CPU Id over all machines from the CPU table. + auto ucpu = context_->cpu_tracker->GetOrCreateCpu(cpu); + auto row_and_id = sched->Insert( + {ts, /* duration */ -1, next_utid, kNullStringId, next_prio, ucpu}); SchedId sched_id = row_and_id.id; return *sched->id().IndexOf(sched_id); } diff --git a/src/trace_processor/importers/common/scoped_active_trace_file.cc b/src/trace_processor/importers/common/scoped_active_trace_file.cc new file mode 100644 index 0000000000..7249261c03 --- /dev/null +++ b/src/trace_processor/importers/common/scoped_active_trace_file.cc @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "src/trace_processor/importers/common/scoped_active_trace_file.h" + +#include +#include +#include + +#include "perfetto/ext/base/string_view.h" +#include "src/trace_processor/importers/common/trace_file_tracker.h" +#include "src/trace_processor/storage/trace_storage.h" +#include "src/trace_processor/tables/metadata_tables_py.h" +#include "src/trace_processor/types/trace_processor_context.h" + +namespace perfetto::trace_processor { +ScopedActiveTraceFile::~ScopedActiveTraceFile() { + if (is_valid_) { + context_->trace_file_tracker->EndFile(row_); + } +} + +void ScopedActiveTraceFile::SetName(const std::string& name) { + row_.set_name(context_->storage->InternString(base::StringView(name))); +} + +void ScopedActiveTraceFile::SetTraceType(TraceType type) { + row_.set_trace_type(context_->storage->InternString(TraceTypeToString(type))); +} + +void ScopedActiveTraceFile::SetSize(size_t size) { + row_.set_size(static_cast(size)); +} + +void ScopedActiveTraceFile::AddSize(size_t size) { + row_.set_size(static_cast(size) + row_.size()); +} + +} // namespace perfetto::trace_processor diff --git a/src/trace_processor/importers/common/scoped_active_trace_file.h b/src/trace_processor/importers/common/scoped_active_trace_file.h new file mode 100644 index 0000000000..0006fcfda0 --- /dev/null +++ b/src/trace_processor/importers/common/scoped_active_trace_file.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_SCOPED_ACTIVE_TRACE_FILE_H_ +#define SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_SCOPED_ACTIVE_TRACE_FILE_H_ + +#include + +#include "src/trace_processor/tables/metadata_tables_py.h" +#include "src/trace_processor/util/trace_type.h" + +namespace perfetto::trace_processor { + +class TraceProcessorContext; + +// RAII like object that represents a file currently being parsed. When +// instances of this object go out of scope they will notify the +// TraceFileTracker that we are done parsing the file. +// This class also acts a handler for setting file related properties. +class ScopedActiveTraceFile { + public: + ~ScopedActiveTraceFile(); + + ScopedActiveTraceFile(const ScopedActiveTraceFile&) = delete; + ScopedActiveTraceFile& operator=(const ScopedActiveTraceFile&) = delete; + + ScopedActiveTraceFile(ScopedActiveTraceFile&& o) + : context_(o.context_), row_(o.row_), is_valid_(o.is_valid_) { + o.is_valid_ = false; + } + + ScopedActiveTraceFile& operator=(ScopedActiveTraceFile&& o) { + context_ = o.context_; + row_ = o.row_; + is_valid_ = o.is_valid_; + o.is_valid_ = false; + return *this; + } + + void SetTraceType(TraceType type); + + // For streamed files this method can be called for each chunk to update the + // file size incrementally. + void AddSize(size_t delta); + + private: + friend class TraceFileTracker; + ScopedActiveTraceFile(TraceProcessorContext* context, + tables::TraceFileTable::RowReference row) + : context_(context), row_(row), is_valid_(true) {} + + // Sets the file name. If this method is not called (sometimes we do not know + // the file name, e.g. streaming data) the name is set to null. + void SetName(const std::string& name); + void SetSize(size_t size); + + TraceProcessorContext* context_; + tables::TraceFileTable::RowReference row_; + bool is_valid_; +}; + +} // namespace perfetto::trace_processor + +#endif // SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_SCOPED_ACTIVE_TRACE_FILE_H_ diff --git a/src/trace_processor/importers/common/thread_state_tracker.cc b/src/trace_processor/importers/common/thread_state_tracker.cc index 2d98ffb1aa..a0d98ce74d 100644 --- a/src/trace_processor/importers/common/thread_state_tracker.cc +++ b/src/trace_processor/importers/common/thread_state_tracker.cc @@ -19,6 +19,7 @@ #include #include +#include "src/trace_processor/importers/common/cpu_tracker.h" #include "src/trace_processor/importers/common/process_tracker.h" namespace perfetto { @@ -137,12 +138,12 @@ void ThreadStateTracker::AddOpenState(int64_t ts, // Insert row with unfinished state tables::ThreadStateTable::Row row; row.ts = ts; - row.cpu = cpu; row.waker_utid = waker_utid; row.dur = -1; row.utid = utid; row.state = state; - row.machine_id = context_->machine_id(); + if (cpu) + row.ucpu = context_->cpu_tracker->GetOrCreateCpu(*cpu); if (common_flags.has_value()) { row.irq_context = CommonFlagsToIrqContext(*common_flags); } diff --git a/src/trace_processor/importers/common/thread_state_tracker_unittest.cc b/src/trace_processor/importers/common/thread_state_tracker_unittest.cc index 153b02ba8a..12381413e9 100644 --- a/src/trace_processor/importers/common/thread_state_tracker_unittest.cc +++ b/src/trace_processor/importers/common/thread_state_tracker_unittest.cc @@ -19,7 +19,9 @@ #include #include "src/trace_processor/importers/common/args_tracker.h" +#include "src/trace_processor/importers/common/cpu_tracker.h" #include "src/trace_processor/importers/common/global_args_tracker.h" +#include "src/trace_processor/importers/common/machine_tracker.h" #include "src/trace_processor/importers/common/process_tracker.h" #include "src/trace_processor/types/trace_processor_context.h" #include "test/gtest_and_gmock.h" @@ -45,6 +47,8 @@ class ThreadStateTrackerUnittest : public testing::Test { context_.process_tracker.reset(new ProcessTracker(&context_)); context_.global_args_tracker.reset( new GlobalArgsTracker(context_.storage.get())); + context_.machine_tracker.reset(new MachineTracker(&context_, 0)); + context_.cpu_tracker.reset(new CpuTracker(&context_)); context_.args_tracker.reset(new ArgsTracker(&context_)); tracker_.reset(new ThreadStateTracker(&context_)); } @@ -72,12 +76,12 @@ class ThreadStateTrackerUnittest : public testing::Test { ASSERT_EQ(it.utid(), utid); if (state == kRunning) { if (cpu.has_value()) { - ASSERT_EQ(it.cpu(), cpu); + ASSERT_EQ(it.ucpu().value().value, cpu); } else { - ASSERT_EQ(it.cpu(), CPU_A); + ASSERT_EQ(it.ucpu().value().value, CPU_A); } } else { - ASSERT_EQ(it.cpu(), std::nullopt); + ASSERT_EQ(it.ucpu(), std::nullopt); } ASSERT_STREQ(context_.storage->GetString(it.state()).c_str(), state); ASSERT_EQ(it.io_wait(), io_wait); diff --git a/src/trace_processor/importers/common/trace_file_tracker.cc b/src/trace_processor/importers/common/trace_file_tracker.cc new file mode 100644 index 0000000000..e51371a021 --- /dev/null +++ b/src/trace_processor/importers/common/trace_file_tracker.cc @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "src/trace_processor/importers/common/trace_file_tracker.h" + +#include +#include +#include + +#include "src/trace_processor/importers/common/metadata_tracker.h" +#include "src/trace_processor/storage/metadata.h" +#include "src/trace_processor/storage/trace_storage.h" +#include "src/trace_processor/tables/metadata_tables_py.h" +#include "src/trace_processor/types/trace_processor_context.h" +#include "src/trace_processor/util/trace_type.h" + +namespace perfetto::trace_processor { + +ScopedActiveTraceFile TraceFileTracker::StartNewFile() { + tables::TraceFileTable::Row row; + if (!ancestors_.empty()) { + row.parent_id = ancestors_.back(); + } + + row.size = 0; + row.trace_type = + context_->storage->InternString(TraceTypeToString(kUnknownTraceType)); + + auto ref = + context_->storage->mutable_trace_file_table()->Insert(row).row_reference; + + ancestors_.push_back(ref.id()); + return ScopedActiveTraceFile(context_, std::move(ref)); +} + +ScopedActiveTraceFile TraceFileTracker::StartNewFile(const std::string& name, + TraceType type, + size_t size) { + auto file = StartNewFile(); + file.SetName(name); + file.SetTraceType(type); + file.SetSize(size); + return file; +} + +void TraceFileTracker::EndFile( + const tables::TraceFileTable::ConstRowReference& row) { + PERFETTO_CHECK(!ancestors_.empty()); + PERFETTO_CHECK(ancestors_.back() == row.id()); + + // First file (root) + if (row.id().value == 0) { + context_->metadata_tracker->SetMetadata(metadata::trace_size_bytes, + Variadic::Integer(row.size())); + context_->metadata_tracker->SetMetadata(metadata::trace_type, + Variadic::String(row.trace_type())); + } + ancestors_.pop_back(); +} + +} // namespace perfetto::trace_processor diff --git a/src/trace_processor/importers/common/trace_file_tracker.h b/src/trace_processor/importers/common/trace_file_tracker.h new file mode 100644 index 0000000000..0e2f5f9973 --- /dev/null +++ b/src/trace_processor/importers/common/trace_file_tracker.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_TRACE_FILE_TRACKER_H_ +#define SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_TRACE_FILE_TRACKER_H_ + +#include +#include + +#include "src/trace_processor/importers/common/scoped_active_trace_file.h" +#include "src/trace_processor/tables/metadata_tables_py.h" +#include "src/trace_processor/util/trace_type.h" + +namespace perfetto::trace_processor { + +class TraceProcessorContext; + +// This class keeps track of the file currently being parsed and metadata about +// it. Files can be nested into other files (zip or gzip files) and this class +// also keeps track of those relations. +class TraceFileTracker { + public: + explicit TraceFileTracker(TraceProcessorContext* context) + : context_(context) {} + + // Notifies the start of a new file that we are about to parse. It returns a + // RAII like object that will notify the end of processing when it goes out of + // scope. + // NOTE: Files must be ended in reverse order of being started. + ScopedActiveTraceFile StartNewFile(); + + // Convenience version of the above that should be used when all the file + // properties are known upfront. + ScopedActiveTraceFile StartNewFile(const std::string& name, + TraceType type, + size_t size); + + private: + void EndFile(const tables::TraceFileTable::ConstRowReference& row); + + friend class ScopedActiveTraceFile; + TraceProcessorContext* const context_; + std::vector ancestors_; +}; + +} // namespace perfetto::trace_processor + +#endif // SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_TRACE_FILE_TRACKER_H_ diff --git a/src/trace_processor/importers/common/trace_parser.cc b/src/trace_processor/importers/common/trace_parser.cc index c79f29e561..f49727805e 100644 --- a/src/trace_processor/importers/common/trace_parser.cc +++ b/src/trace_processor/importers/common/trace_parser.cc @@ -23,6 +23,7 @@ ProtoTraceParser::~ProtoTraceParser() = default; JsonTraceParser::~JsonTraceParser() = default; FuchsiaRecordParser::~FuchsiaRecordParser() = default; PerfRecordParser::~PerfRecordParser() = default; +AndroidLogEventParser::~AndroidLogEventParser() = default; } // namespace trace_processor } // namespace perfetto diff --git a/src/trace_processor/importers/common/trace_parser.h b/src/trace_processor/importers/common/trace_parser.h index bf90c95fbe..87fd8ff1ab 100644 --- a/src/trace_processor/importers/common/trace_parser.h +++ b/src/trace_processor/importers/common/trace_parser.h @@ -20,9 +20,12 @@ #include #include -namespace perfetto { -namespace trace_processor { +namespace perfetto::trace_processor { +namespace perf_importer { +struct Record; +} +struct AndroidLogEvent; class PacketSequenceStateGeneration; class TraceBlobView; struct InlineSchedSwitch; @@ -59,10 +62,15 @@ class FuchsiaRecordParser { class PerfRecordParser { public: virtual ~PerfRecordParser(); - virtual void ParsePerfRecord(int64_t, TraceBlobView) = 0; + virtual void ParsePerfRecord(int64_t, perf_importer::Record) = 0; }; -} // namespace trace_processor -} // namespace perfetto +class AndroidLogEventParser { + public: + virtual ~AndroidLogEventParser(); + virtual void ParseAndroidLogEvent(int64_t, AndroidLogEvent) = 0; +}; + +} // namespace perfetto::trace_processor #endif // SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_TRACE_PARSER_H_ diff --git a/src/trace_processor/importers/common/track_tracker.cc b/src/trace_processor/importers/common/track_tracker.cc index 5026391525..5f74ad50dd 100644 --- a/src/trace_processor/importers/common/track_tracker.cc +++ b/src/trace_processor/importers/common/track_tracker.cc @@ -19,7 +19,11 @@ #include #include "src/trace_processor/importers/common/args_tracker.h" +#include "src/trace_processor/importers/common/cpu_tracker.h" +#include "src/trace_processor/importers/common/process_track_translation_table.h" #include "src/trace_processor/storage/trace_storage.h" +#include "src/trace_processor/tables/profiler_tables_py.h" +#include "src/trace_processor/types/trace_processor_context.h" namespace perfetto { namespace trace_processor { @@ -44,6 +48,8 @@ const char* GetNameForGroup(TrackTracker::Group group) { return "Thermals"; case TrackTracker::Group::kClockFrequency: return "Clock Freqeuncy"; + case TrackTracker::Group::kBatteryMitigation: + return "Battery Mitigation"; case TrackTracker::Group::kSizeSentinel: PERFETTO_FATAL("Unexpected size passed as group"); } @@ -103,7 +109,7 @@ TrackId TrackTracker::InternCpuTrack(StringId name, uint32_t cpu) { } tables::CpuTrackTable::Row row(name); - row.cpu = cpu; + row.ucpu = context_->cpu_tracker->GetOrCreateCpu(cpu); row.machine_id = context_->machine_id(); auto id = context_->storage->mutable_cpu_track_table()->Insert(row).id; cpu_tracks_[std::make_pair(name, cpu)] = id; @@ -140,7 +146,7 @@ TrackId TrackTracker::InternGpuWorkPeriodTrack( } TrackId TrackTracker::InternLegacyChromeAsyncTrack( - StringId name, + StringId raw_name, uint32_t upid, int64_t trace_id, bool trace_id_is_process_scoped, @@ -151,6 +157,8 @@ TrackId TrackTracker::InternLegacyChromeAsyncTrack( tuple.trace_id = trace_id; tuple.source_scope = source_scope; + const StringId name = + context_->process_track_translation_table->TranslateName(raw_name); auto it = chrome_tracks_.find(tuple); if (it != chrome_tracks_.end()) { if (name != kNullStringId) { @@ -194,9 +202,11 @@ TrackId TrackTracker::CreateGlobalAsyncTrack(StringId name, StringId source) { return id; } -TrackId TrackTracker::CreateProcessAsyncTrack(StringId name, +TrackId TrackTracker::CreateProcessAsyncTrack(StringId raw_name, UniquePid upid, StringId source) { + const StringId name = + context_->process_track_translation_table->TranslateName(raw_name); tables::ProcessTrackTable::Row row(name); row.upid = upid; row.machine_id = context_->machine_id(); @@ -293,7 +303,7 @@ TrackId TrackTracker::InternCpuCounterTrack(StringId name, uint32_t cpu) { } tables::CpuCounterTrackTable::Row row(name); - row.cpu = cpu; + row.ucpu = context_->cpu_tracker->GetOrCreateCpu(cpu); row.machine_id = context_->machine_id(); TrackId track = @@ -318,10 +328,12 @@ TrackId TrackTracker::InternThreadCounterTrack(StringId name, UniqueTid utid) { return track; } -TrackId TrackTracker::InternProcessCounterTrack(StringId name, +TrackId TrackTracker::InternProcessCounterTrack(StringId raw_name, UniquePid upid, StringId unit, StringId description) { + const StringId name = + context_->process_track_translation_table->TranslateName(raw_name); auto it = upid_counter_tracks_.find(std::make_pair(name, upid)); if (it != upid_counter_tracks_.end()) { return it->second; @@ -447,10 +459,11 @@ TrackId TrackTracker::CreateGpuCounterTrack(StringId name, return context_->storage->mutable_gpu_counter_track_table()->Insert(row).id; } -TrackId TrackTracker::CreatePerfCounterTrack(StringId name, - uint32_t perf_session_id, - uint32_t cpu, - bool is_timebase) { +TrackId TrackTracker::CreatePerfCounterTrack( + StringId name, + tables::PerfSessionTable::Id perf_session_id, + uint32_t cpu, + bool is_timebase) { tables::PerfCounterTrackTable::Row row(name); row.perf_session_id = perf_session_id; row.cpu = cpu; diff --git a/src/trace_processor/importers/common/track_tracker.h b/src/trace_processor/importers/common/track_tracker.h index 0fec299abe..261b562798 100644 --- a/src/trace_processor/importers/common/track_tracker.h +++ b/src/trace_processor/importers/common/track_tracker.h @@ -18,8 +18,10 @@ #define SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_TRACK_TRACKER_H_ #include + #include "src/trace_processor/importers/common/args_tracker.h" #include "src/trace_processor/storage/trace_storage.h" +#include "src/trace_processor/tables/profiler_tables_py.h" #include "src/trace_processor/types/trace_processor_context.h" namespace perfetto { @@ -41,6 +43,7 @@ class TrackTracker { kDeviceState, kThermals, kClockFrequency, + kBatteryMitigation, // Keep this last. kSizeSentinel, @@ -145,7 +148,7 @@ class TrackTracker { // Creates a counter track for values within perf samples. // The tracks themselves are managed by PerfSampleTracker. TrackId CreatePerfCounterTrack(StringId name, - uint32_t perf_session_id, + tables::PerfSessionTable::Id perf_session_id, uint32_t cpu, bool is_timebase); diff --git a/src/trace_processor/importers/ftrace/ftrace_descriptors.cc b/src/trace_processor/importers/ftrace/ftrace_descriptors.cc index 64ae4d574b..3fd5e92f00 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 descriptors{{ +std::array descriptors{{ {nullptr, 0, {}}, {nullptr, 0, {}}, {nullptr, 0, {}}, @@ -5619,6 +5619,270 @@ std::array descriptors{{ {"k_i", ProtoSchemaType::kInt32}, }, }, + { + "dcvsh_freq", + 2, + { + {}, + {"cpu", ProtoSchemaType::kUint64}, + {"freq", ProtoSchemaType::kUint64}, + }, + }, + { + "kgsl_gpu_frequency", + 2, + { + {}, + {"gpu_freq", ProtoSchemaType::kUint32}, + {"gpu_id", ProtoSchemaType::kUint32}, + }, + }, + { + "mali_mali_PM_MCU_HCTL_CORES_DOWN_SCALE_NOTIFY_PEND", + 3, + { + {}, + {"kctx_tgid", ProtoSchemaType::kInt32}, + {"kctx_id", ProtoSchemaType::kUint32}, + {"info_val", ProtoSchemaType::kUint64}, + }, + }, + { + "mali_mali_PM_MCU_HCTL_CORES_NOTIFY_PEND", + 3, + { + {}, + {"kctx_tgid", ProtoSchemaType::kInt32}, + {"kctx_id", ProtoSchemaType::kUint32}, + {"info_val", ProtoSchemaType::kUint64}, + }, + }, + { + "mali_mali_PM_MCU_HCTL_CORE_INACTIVE_PEND", + 3, + { + {}, + {"kctx_tgid", ProtoSchemaType::kInt32}, + {"kctx_id", ProtoSchemaType::kUint32}, + {"info_val", ProtoSchemaType::kUint64}, + }, + }, + { + "mali_mali_PM_MCU_HCTL_MCU_ON_RECHECK", + 3, + { + {}, + {"kctx_tgid", ProtoSchemaType::kInt32}, + {"kctx_id", ProtoSchemaType::kUint32}, + {"info_val", ProtoSchemaType::kUint64}, + }, + }, + { + "mali_mali_PM_MCU_HCTL_SHADERS_CORE_OFF_PEND", + 3, + { + {}, + {"kctx_tgid", ProtoSchemaType::kInt32}, + {"kctx_id", ProtoSchemaType::kUint32}, + {"info_val", ProtoSchemaType::kUint64}, + }, + }, + { + "mali_mali_PM_MCU_HCTL_SHADERS_PEND_OFF", + 3, + { + {}, + {"kctx_tgid", ProtoSchemaType::kInt32}, + {"kctx_id", ProtoSchemaType::kUint32}, + {"info_val", ProtoSchemaType::kUint64}, + }, + }, + { + "mali_mali_PM_MCU_HCTL_SHADERS_PEND_ON", + 3, + { + {}, + {"kctx_tgid", ProtoSchemaType::kInt32}, + {"kctx_id", ProtoSchemaType::kUint32}, + {"info_val", ProtoSchemaType::kUint64}, + }, + }, + { + "mali_mali_PM_MCU_HCTL_SHADERS_READY_OFF", + 3, + { + {}, + {"kctx_tgid", ProtoSchemaType::kInt32}, + {"kctx_id", ProtoSchemaType::kUint32}, + {"info_val", ProtoSchemaType::kUint64}, + }, + }, + { + "mali_mali_PM_MCU_IN_SLEEP", + 3, + { + {}, + {"kctx_tgid", ProtoSchemaType::kInt32}, + {"kctx_id", ProtoSchemaType::kUint32}, + {"info_val", ProtoSchemaType::kUint64}, + }, + }, + { + "mali_mali_PM_MCU_OFF", + 3, + { + {}, + {"kctx_tgid", ProtoSchemaType::kInt32}, + {"kctx_id", ProtoSchemaType::kUint32}, + {"info_val", ProtoSchemaType::kUint64}, + }, + }, + { + "mali_mali_PM_MCU_ON", + 3, + { + {}, + {"kctx_tgid", ProtoSchemaType::kInt32}, + {"kctx_id", ProtoSchemaType::kUint32}, + {"info_val", ProtoSchemaType::kUint64}, + }, + }, + { + "mali_mali_PM_MCU_ON_CORE_ATTR_UPDATE_PEND", + 3, + { + {}, + {"kctx_tgid", ProtoSchemaType::kInt32}, + {"kctx_id", ProtoSchemaType::kUint32}, + {"info_val", ProtoSchemaType::kUint64}, + }, + }, + { + "mali_mali_PM_MCU_ON_GLB_REINIT_PEND", + 3, + { + {}, + {"kctx_tgid", ProtoSchemaType::kInt32}, + {"kctx_id", ProtoSchemaType::kUint32}, + {"info_val", ProtoSchemaType::kUint64}, + }, + }, + { + "mali_mali_PM_MCU_ON_HALT", + 3, + { + {}, + {"kctx_tgid", ProtoSchemaType::kInt32}, + {"kctx_id", ProtoSchemaType::kUint32}, + {"info_val", ProtoSchemaType::kUint64}, + }, + }, + { + "mali_mali_PM_MCU_ON_HWCNT_DISABLE", + 3, + { + {}, + {"kctx_tgid", ProtoSchemaType::kInt32}, + {"kctx_id", ProtoSchemaType::kUint32}, + {"info_val", ProtoSchemaType::kUint64}, + }, + }, + { + "mali_mali_PM_MCU_ON_HWCNT_ENABLE", + 3, + { + {}, + {"kctx_tgid", ProtoSchemaType::kInt32}, + {"kctx_id", ProtoSchemaType::kUint32}, + {"info_val", ProtoSchemaType::kUint64}, + }, + }, + { + "mali_mali_PM_MCU_ON_PEND_HALT", + 3, + { + {}, + {"kctx_tgid", ProtoSchemaType::kInt32}, + {"kctx_id", ProtoSchemaType::kUint32}, + {"info_val", ProtoSchemaType::kUint64}, + }, + }, + { + "mali_mali_PM_MCU_ON_PEND_SLEEP", + 3, + { + {}, + {"kctx_tgid", ProtoSchemaType::kInt32}, + {"kctx_id", ProtoSchemaType::kUint32}, + {"info_val", ProtoSchemaType::kUint64}, + }, + }, + { + "mali_mali_PM_MCU_ON_SLEEP_INITIATE", + 3, + { + {}, + {"kctx_tgid", ProtoSchemaType::kInt32}, + {"kctx_id", ProtoSchemaType::kUint32}, + {"info_val", ProtoSchemaType::kUint64}, + }, + }, + { + "mali_mali_PM_MCU_PEND_OFF", + 3, + { + {}, + {"kctx_tgid", ProtoSchemaType::kInt32}, + {"kctx_id", ProtoSchemaType::kUint32}, + {"info_val", ProtoSchemaType::kUint64}, + }, + }, + { + "mali_mali_PM_MCU_PEND_ON_RELOAD", + 3, + { + {}, + {"kctx_tgid", ProtoSchemaType::kInt32}, + {"kctx_id", ProtoSchemaType::kUint32}, + {"info_val", ProtoSchemaType::kUint64}, + }, + }, + { + "mali_mali_PM_MCU_POWER_DOWN", + 3, + { + {}, + {"kctx_tgid", ProtoSchemaType::kInt32}, + {"kctx_id", ProtoSchemaType::kUint32}, + {"info_val", ProtoSchemaType::kUint64}, + }, + }, + { + "mali_mali_PM_MCU_RESET_WAIT", + 3, + { + {}, + {"kctx_tgid", ProtoSchemaType::kInt32}, + {"kctx_id", ProtoSchemaType::kUint32}, + {"info_val", ProtoSchemaType::kUint64}, + }, + }, + { + "bcl_exynos_bcl_irq_trigger", + 9, + { + {}, + {"id", ProtoSchemaType::kInt32}, + {"throttle", ProtoSchemaType::kInt32}, + {"cpu0_limit", ProtoSchemaType::kInt32}, + {"cpu1_limit", ProtoSchemaType::kInt32}, + {"cpu2_limit", ProtoSchemaType::kInt32}, + {"tpu_limit", ProtoSchemaType::kInt32}, + {"gpu_limit", ProtoSchemaType::kInt32}, + {"voltage", ProtoSchemaType::kInt32}, + {"capacity", ProtoSchemaType::kInt32}, + }, + }, }}; } // namespace diff --git a/src/trace_processor/importers/ftrace/ftrace_parser.cc b/src/trace_processor/importers/ftrace/ftrace_parser.cc index 406f30199f..40d1730b85 100644 --- a/src/trace_processor/importers/ftrace/ftrace_parser.cc +++ b/src/trace_processor/importers/ftrace/ftrace_parser.cc @@ -24,6 +24,7 @@ #include "perfetto/trace_processor/basic_types.h" #include "src/trace_processor/importers/common/args_tracker.h" #include "src/trace_processor/importers/common/async_track_set_tracker.h" +#include "src/trace_processor/importers/common/cpu_tracker.h" #include "src/trace_processor/importers/common/metadata_tracker.h" #include "src/trace_processor/importers/common/parser_types.h" #include "src/trace_processor/importers/common/process_tracker.h" @@ -45,10 +46,12 @@ #include "protos/perfetto/common/gpu_counter_descriptor.pbzero.h" #include "protos/perfetto/trace/ftrace/android_fs.pbzero.h" +#include "protos/perfetto/trace/ftrace/bcl_exynos.pbzero.h" #include "protos/perfetto/trace/ftrace/binder.pbzero.h" #include "protos/perfetto/trace/ftrace/cma.pbzero.h" #include "protos/perfetto/trace/ftrace/cpuhp.pbzero.h" #include "protos/perfetto/trace/ftrace/cros_ec.pbzero.h" +#include "protos/perfetto/trace/ftrace/dcvsh.pbzero.h" #include "protos/perfetto/trace/ftrace/dmabuf_heap.pbzero.h" #include "protos/perfetto/trace/ftrace/dpu.pbzero.h" #include "protos/perfetto/trace/ftrace/fastrpc.pbzero.h" @@ -63,6 +66,7 @@ #include "protos/perfetto/trace/ftrace/i2c.pbzero.h" #include "protos/perfetto/trace/ftrace/ion.pbzero.h" #include "protos/perfetto/trace/ftrace/irq.pbzero.h" +#include "protos/perfetto/trace/ftrace/kgsl.pbzero.h" #include "protos/perfetto/trace/ftrace/kmem.pbzero.h" #include "protos/perfetto/trace/ftrace/lwis.pbzero.h" #include "protos/perfetto/trace/ftrace/mali.pbzero.h" @@ -317,6 +321,8 @@ FtraceParser::FtraceParser(TraceProcessorContext* context) sched_waking_name_id_(context->storage->InternString("sched_waking")), cpu_id_(context->storage->InternString("cpu")), cpu_freq_name_id_(context->storage->InternString("cpufreq")), + cpu_freq_throttle_name_id_( + context->storage->InternString("cpufreq_throttle")), gpu_freq_name_id_(context->storage->InternString("gpufreq")), cpu_idle_name_id_(context->storage->InternString("cpuidle")), suspend_resume_name_id_( @@ -333,9 +339,19 @@ FtraceParser::FtraceParser(TraceProcessorContext* context) dma_heap_change_id_( context->storage->InternString("mem.dma_heap_change")), dma_buffer_id_(context->storage->InternString("mem.dma_buffer")), + inode_arg_id_(context->storage->InternString("inode")), ion_total_unknown_id_(context->storage->InternString("mem.ion.unknown")), ion_change_unknown_id_( context->storage->InternString("mem.ion_change.unknown")), + bcl_irq_id_(context_->storage->InternString("bcl_irq_id")), + bcl_irq_throttle_(context_->storage->InternString("bcl_irq_throttle")), + bcl_irq_cpu0_(context_->storage->InternString("bcl_irq_cpu0")), + bcl_irq_cpu1_(context_->storage->InternString("bcl_irq_cpu1")), + bcl_irq_cpu2_(context_->storage->InternString("bcl_irq_cpu2")), + bcl_irq_tpu_(context_->storage->InternString("bcl_irq_tpu")), + bcl_irq_gpu_(context_->storage->InternString("bcl_irq_gpu")), + bcl_irq_voltage_(context_->storage->InternString("bcl_irq_voltage")), + bcl_irq_capacity_(context_->storage->InternString("bcl_irq_capacity")), signal_generate_id_(context->storage->InternString("signal_generate")), signal_deliver_id_(context->storage->InternString("signal_deliver")), oom_score_adj_id_(context->storage->InternString("oom_score_adj")), @@ -761,10 +777,18 @@ base::Status FtraceParser::ParseFtraceEvent(uint32_t cpu, ParseCpuFreq(ts, fld_bytes); break; } + case FtraceEvent::kDcvshFreqFieldNumber: { + ParseCpuFreqThrottle(ts, fld_bytes); + break; + } case FtraceEvent::kGpuFrequencyFieldNumber: { ParseGpuFreq(ts, fld_bytes); break; } + case FtraceEvent::kKgslGpuFrequencyFieldNumber: { + ParseKgslGpuFreq(ts, fld_bytes); + break; + } case FtraceEvent::kCpuIdleFieldNumber: { ParseCpuIdle(ts, fld_bytes); break; @@ -951,8 +975,7 @@ base::Status FtraceParser::ParseFtraceEvent(uint32_t cpu, break; } case FtraceEvent::kThermalExynosAcpmHighOverheadFieldNumber: { - thermal_tracker_.ParseThermalExynosAcpmHighOverhead( - ts, fld_bytes); + thermal_tracker_.ParseThermalExynosAcpmHighOverhead(ts, fld_bytes); break; } case FtraceEvent::kCdevUpdateFieldNumber: { @@ -1206,6 +1229,32 @@ base::Status FtraceParser::ParseFtraceEvent(uint32_t cpu, fld_bytes); break; } + case FtraceEvent::kMaliMaliPMMCUHCTLCORESDOWNSCALENOTIFYPENDFieldNumber: + case FtraceEvent::kMaliMaliPMMCUHCTLCORESNOTIFYPENDFieldNumber: + case FtraceEvent::kMaliMaliPMMCUHCTLCOREINACTIVEPENDFieldNumber: + case FtraceEvent::kMaliMaliPMMCUHCTLMCUONRECHECKFieldNumber: + case FtraceEvent::kMaliMaliPMMCUHCTLSHADERSCOREOFFPENDFieldNumber: + case FtraceEvent::kMaliMaliPMMCUHCTLSHADERSPENDOFFFieldNumber: + case FtraceEvent::kMaliMaliPMMCUHCTLSHADERSPENDONFieldNumber: + case FtraceEvent::kMaliMaliPMMCUHCTLSHADERSREADYOFFFieldNumber: + case FtraceEvent::kMaliMaliPMMCUINSLEEPFieldNumber: + case FtraceEvent::kMaliMaliPMMCUOFFFieldNumber: + case FtraceEvent::kMaliMaliPMMCUONFieldNumber: + case FtraceEvent::kMaliMaliPMMCUONCOREATTRUPDATEPENDFieldNumber: + case FtraceEvent::kMaliMaliPMMCUONGLBREINITPENDFieldNumber: + case FtraceEvent::kMaliMaliPMMCUONHALTFieldNumber: + case FtraceEvent::kMaliMaliPMMCUONHWCNTDISABLEFieldNumber: + case FtraceEvent::kMaliMaliPMMCUONHWCNTENABLEFieldNumber: + case FtraceEvent::kMaliMaliPMMCUONPENDHALTFieldNumber: + case FtraceEvent::kMaliMaliPMMCUONPENDSLEEPFieldNumber: + case FtraceEvent::kMaliMaliPMMCUONSLEEPINITIATEFieldNumber: + case FtraceEvent::kMaliMaliPMMCUPENDOFFFieldNumber: + case FtraceEvent::kMaliMaliPMMCUPENDONRELOADFieldNumber: + case FtraceEvent::kMaliMaliPMMCUPOWERDOWNFieldNumber: + case FtraceEvent::kMaliMaliPMMCURESETWAITFieldNumber: { + mali_gpu_event_tracker_.ParseMaliGpuMcuStateEvent(ts, fld.id()); + break; + } case FtraceEvent::kTracingMarkWriteFieldNumber: { ParseMdssTracingMarkWrite(ts, pid, fld_bytes); break; @@ -1246,6 +1295,10 @@ base::Status FtraceParser::ParseFtraceEvent(uint32_t cpu, ParseDevicePmCallbackEnd(ts, fld_bytes); break; } + case FtraceEvent::kBclIrqTriggerFieldNumber: { + ParseBclIrq(ts, fld_bytes); + break; + } default: break; } @@ -1362,10 +1415,10 @@ void FtraceParser::ParseGenericFtrace(int64_t ts, protos::pbzero::GenericFtraceEvent::Decoder evt(blob.data, blob.size); StringId event_id = context_->storage->InternString(evt.event_name()); UniqueTid utid = context_->process_tracker->GetOrCreateThread(tid); - RawId id = - context_->storage->mutable_ftrace_event_table() - ->Insert({ts, event_id, cpu, utid, {}, {}, context_->machine_id()}) - .id; + auto ucpu = context_->cpu_tracker->GetOrCreateCpu(cpu); + RawId id = context_->storage->mutable_ftrace_event_table() + ->Insert({ts, event_id, utid, {}, {}, ucpu}) + .id; auto inserter = context_->args_tracker->AddArgsTo(id); for (auto it = evt.field(); it; ++it) { @@ -1404,15 +1457,12 @@ void FtraceParser::ParseTypedFtraceToRaw( FtraceMessageDescriptor* m = GetMessageDescriptorForId(ftrace_id); const auto& message_strings = ftrace_message_strings_[ftrace_id]; UniqueTid utid = context_->process_tracker->GetOrCreateThread(tid); - RawId id = context_->storage->mutable_ftrace_event_table() - ->Insert({timestamp, - message_strings.message_name_id, - cpu, - utid, - {}, - {}, - context_->machine_id()}) - .id; + auto ucpu = context_->cpu_tracker->GetOrCreateCpu(cpu); + RawId id = + context_->storage->mutable_ftrace_event_table() + ->Insert( + {timestamp, message_strings.message_name_id, utid, {}, {}, ucpu}) + .id; auto inserter = context_->args_tracker->AddArgsTo(id); for (auto fld = decoder.ReadField(); fld.valid(); fld = decoder.ReadField()) { @@ -1536,10 +1586,19 @@ void FtraceParser::ParseSchedProcessFree(int64_t timestamp, ConstBytes blob) { void FtraceParser::ParseCpuFreq(int64_t timestamp, ConstBytes blob) { protos::pbzero::CpuFrequencyFtraceEvent::Decoder freq(blob.data, blob.size); uint32_t cpu = freq.cpu_id(); - uint32_t new_freq = freq.state(); + uint32_t new_freq_khz = freq.state(); TrackId track = context_->track_tracker->InternCpuCounterTrack(cpu_freq_name_id_, cpu); - context_->event_tracker->PushCounter(timestamp, new_freq, track); + context_->event_tracker->PushCounter(timestamp, new_freq_khz, track); +} + +void FtraceParser::ParseCpuFreqThrottle(int64_t timestamp, ConstBytes blob) { + protos::pbzero::DcvshFreqFtraceEvent::Decoder freq(blob.data, blob.size); + uint32_t cpu = static_cast(freq.cpu()); + double new_freq_khz = static_cast(freq.freq()); + TrackId track = context_->track_tracker->InternCpuCounterTrack( + cpu_freq_throttle_name_id_, cpu); + context_->event_tracker->PushCounter(timestamp, new_freq_khz, track); } void FtraceParser::ParseGpuFreq(int64_t timestamp, ConstBytes blob) { @@ -1551,6 +1610,17 @@ void FtraceParser::ParseGpuFreq(int64_t timestamp, ConstBytes blob) { context_->event_tracker->PushCounter(timestamp, new_freq, track); } +void FtraceParser::ParseKgslGpuFreq(int64_t timestamp, ConstBytes blob) { + protos::pbzero::KgslGpuFrequencyFtraceEvent::Decoder freq(blob.data, + blob.size); + uint32_t gpu = freq.gpu_id(); + // Source data is frequency / 1000, so we correct that here: + double new_freq = static_cast(freq.gpu_freq()) * 1000.0; + TrackId track = + context_->track_tracker->InternGpuCounterTrack(gpu_freq_name_id_, gpu); + context_->event_tracker->PushCounter(timestamp, new_freq, track); +} + void FtraceParser::ParseCpuIdle(int64_t timestamp, ConstBytes blob) { protos::pbzero::CpuIdleFtraceEvent::Decoder idle(blob.data, blob.size); uint32_t cpu = idle.cpu_id(); @@ -1819,6 +1889,59 @@ void FtraceParser::ParseIonStat(int64_t timestamp, } } +void FtraceParser::ParseBclIrq(int64_t ts, protozero::ConstBytes data) { + protos::pbzero::BclIrqTriggerFtraceEvent::Decoder bcl(data.data, data.size); + int throttle = bcl.throttle(); + // id + TrackId track = context_->track_tracker->InternGlobalCounterTrack( + TrackTracker::Group::kBatteryMitigation, bcl_irq_id_); + context_->event_tracker->PushCounter(ts, + throttle ? bcl.id() : -1, + track); + // throttle + track = context_->track_tracker->InternGlobalCounterTrack( + TrackTracker::Group::kBatteryMitigation, bcl_irq_throttle_); + context_->event_tracker->PushCounter(ts, throttle, track); + // cpu0_limit + track = context_->track_tracker->InternGlobalCounterTrack( + TrackTracker::Group::kBatteryMitigation, bcl_irq_cpu0_); + context_->event_tracker->PushCounter(ts, + throttle ? bcl.cpu0_limit() : 0, + track); + // cpu1_limit + track = context_->track_tracker->InternGlobalCounterTrack( + TrackTracker::Group::kBatteryMitigation, bcl_irq_cpu1_); + context_->event_tracker->PushCounter(ts, + throttle ? bcl.cpu1_limit() : 0, + track); + // cpu2_limit + track = context_->track_tracker->InternGlobalCounterTrack( + TrackTracker::Group::kBatteryMitigation, bcl_irq_cpu2_); + context_->event_tracker->PushCounter(ts, + throttle ? bcl.cpu2_limit() : 0, + track); + // tpu_limit + track = context_->track_tracker->InternGlobalCounterTrack( + TrackTracker::Group::kBatteryMitigation, bcl_irq_tpu_); + context_->event_tracker->PushCounter(ts, + throttle ? bcl.tpu_limit(): 0, + track); + // gpu_limit + track = context_->track_tracker->InternGlobalCounterTrack( + TrackTracker::Group::kBatteryMitigation, bcl_irq_gpu_); + context_->event_tracker->PushCounter(ts, + throttle ? bcl.gpu_limit() : 0, + track); + // voltage + track = context_->track_tracker->InternGlobalCounterTrack( + TrackTracker::Group::kBatteryMitigation, bcl_irq_voltage_); + context_->event_tracker->PushCounter(ts, bcl.voltage(), track); + // capacity + track = context_->track_tracker->InternGlobalCounterTrack( + TrackTracker::Group::kBatteryMitigation, bcl_irq_capacity_); + context_->event_tracker->PushCounter(ts, bcl.capacity(), track); +} + void FtraceParser::ParseDmaHeapStat(int64_t timestamp, uint32_t pid, protozero::ConstBytes data) { @@ -1835,8 +1958,13 @@ void FtraceParser::ParseDmaHeapStat(int64_t timestamp, UniqueTid utid = context_->process_tracker->GetOrCreateThread(pid); track = context_->track_tracker->InternThreadCounterTrack(dma_heap_change_id_, utid); - context_->event_tracker->PushCounter( + + auto opt_counter_id = context_->event_tracker->PushCounter( timestamp, static_cast(dma_heap.len()), track); + if (opt_counter_id) { + context_->args_tracker->AddArgsTo(*opt_counter_id) + .AddArg(inode_arg_id_, Variadic::UnsignedInteger(dma_heap.inode())); + } // Global track for individual buffer tracking auto async_track = diff --git a/src/trace_processor/importers/ftrace/ftrace_parser.h b/src/trace_processor/importers/ftrace/ftrace_parser.h index d8bafde945..ad121daf73 100644 --- a/src/trace_processor/importers/ftrace/ftrace_parser.h +++ b/src/trace_processor/importers/ftrace/ftrace_parser.h @@ -74,7 +74,9 @@ class FtraceParser { void ParseSchedWaking(int64_t timestamp, uint32_t pid, protozero::ConstBytes); void ParseSchedProcessFree(int64_t timestamp, protozero::ConstBytes); void ParseCpuFreq(int64_t timestamp, protozero::ConstBytes); + void ParseCpuFreqThrottle(int64_t timestamp, protozero::ConstBytes); void ParseGpuFreq(int64_t timestamp, protozero::ConstBytes); + void ParseKgslGpuFreq(int64_t timestamp, protozero::ConstBytes); void ParseCpuIdle(int64_t timestamp, protozero::ConstBytes); void ParsePrint(int64_t timestamp, uint32_t pid, protozero::ConstBytes); void ParseZero(int64_t timestamp, uint32_t pid, protozero::ConstBytes); @@ -104,6 +106,7 @@ class FtraceParser { protozero::ConstBytes, bool grow); void ParseIonStat(int64_t timestamp, uint32_t pid, protozero::ConstBytes); + void ParseBclIrq(int64_t timestamp, protozero::ConstBytes); void ParseDmaHeapStat(int64_t timestamp, uint32_t pid, protozero::ConstBytes); void ParseSignalGenerate(int64_t timestamp, protozero::ConstBytes); void ParseSignalDeliver(int64_t timestamp, @@ -319,6 +322,7 @@ class FtraceParser { const StringId sched_waking_name_id_; const StringId cpu_id_; const StringId cpu_freq_name_id_; + const StringId cpu_freq_throttle_name_id_; const StringId gpu_freq_name_id_; const StringId cpu_idle_name_id_; const StringId suspend_resume_name_id_; @@ -331,8 +335,18 @@ class FtraceParser { const StringId dma_heap_total_id_; const StringId dma_heap_change_id_; const StringId dma_buffer_id_; + const StringId inode_arg_id_; const StringId ion_total_unknown_id_; const StringId ion_change_unknown_id_; + const StringId bcl_irq_id_; + const StringId bcl_irq_throttle_; + const StringId bcl_irq_cpu0_; + const StringId bcl_irq_cpu1_; + const StringId bcl_irq_cpu2_; + const StringId bcl_irq_tpu_; + const StringId bcl_irq_gpu_; + const StringId bcl_irq_voltage_; + const StringId bcl_irq_capacity_; const StringId signal_generate_id_; const StringId signal_deliver_id_; const StringId oom_score_adj_id_; diff --git a/src/trace_processor/importers/ftrace/ftrace_sched_event_tracker.cc b/src/trace_processor/importers/ftrace/ftrace_sched_event_tracker.cc index a7b1ced406..feb616c311 100644 --- a/src/trace_processor/importers/ftrace/ftrace_sched_event_tracker.cc +++ b/src/trace_processor/importers/ftrace/ftrace_sched_event_tracker.cc @@ -238,10 +238,9 @@ void FtraceSchedEventTracker::PushSchedWakingCompact(uint32_t cpu, tables::FtraceEventTable::Row row; row.ts = ts; row.name = sched_waking_id_; - row.cpu = cpu; row.utid = curr_utid; row.common_flags = common_flags; - row.machine_id = context_->machine_id(); + row.ucpu = context_->cpu_tracker->GetOrCreateCpu(cpu); // Add an entry to the raw table. RawId id = context_->storage->mutable_ftrace_event_table()->Insert(row).id; @@ -280,14 +279,9 @@ void FtraceSchedEventTracker::AddRawSchedSwitchEvent(uint32_t cpu, if (PERFETTO_LIKELY(context_->config.ingest_ftrace_in_raw_table)) { // Push the raw event - this is done as the raw ftrace event codepath does // not insert sched_switch. + auto ucpu = context_->cpu_tracker->GetOrCreateCpu(cpu); RawId id = context_->storage->mutable_ftrace_event_table() - ->Insert({ts, - sched_switch_id_, - cpu, - prev_utid, - {}, - {}, - context_->machine_id()}) + ->Insert({ts, sched_switch_id_, prev_utid, {}, {}, ucpu}) .id; // Note: this ordering is important. The events should be pushed in the same diff --git a/src/trace_processor/importers/ftrace/ftrace_sched_event_tracker_unittest.cc b/src/trace_processor/importers/ftrace/ftrace_sched_event_tracker_unittest.cc index a085078cda..8becd2ed97 100644 --- a/src/trace_processor/importers/ftrace/ftrace_sched_event_tracker_unittest.cc +++ b/src/trace_processor/importers/ftrace/ftrace_sched_event_tracker_unittest.cc @@ -18,9 +18,11 @@ #include "perfetto/base/logging.h" #include "src/trace_processor/importers/common/args_tracker.h" +#include "src/trace_processor/importers/common/cpu_tracker.h" #include "src/trace_processor/importers/common/event_tracker.h" -#include "src/trace_processor/importers/common/sched_event_tracker.h" +#include "src/trace_processor/importers/common/machine_tracker.h" #include "src/trace_processor/importers/common/process_tracker.h" +#include "src/trace_processor/importers/common/sched_event_tracker.h" #include "test/gtest_and_gmock.h" namespace perfetto { @@ -40,6 +42,8 @@ class SchedEventTrackerTest : public ::testing::Test { context.args_tracker.reset(new ArgsTracker(&context)); context.event_tracker.reset(new EventTracker(&context)); context.process_tracker.reset(new ProcessTracker(&context)); + context.machine_tracker.reset(new MachineTracker(&context, 0)); + context.cpu_tracker.reset(new CpuTracker(&context)); context.sched_event_tracker.reset(new SchedEventTracker(&context)); sched_tracker = FtraceSchedEventTracker::GetOrCreate(&context); } diff --git a/src/trace_processor/importers/ftrace/ftrace_tokenizer.cc b/src/trace_processor/importers/ftrace/ftrace_tokenizer.cc index 02209863b1..39c01aba8e 100644 --- a/src/trace_processor/importers/ftrace/ftrace_tokenizer.cc +++ b/src/trace_processor/importers/ftrace/ftrace_tokenizer.cc @@ -56,7 +56,8 @@ PERFETTO_ALWAYS_INLINE base::StatusOr ResolveTraceTime( ClockTracker::ClockId clock_id, int64_t ts) { // On most traces (i.e. P+), the clock should be BOOTTIME. - if (PERFETTO_LIKELY(clock_id == BuiltinClock::BUILTIN_CLOCK_BOOTTIME)) + if (PERFETTO_LIKELY(clock_id == BuiltinClock::BUILTIN_CLOCK_BOOTTIME && + !context->machine_id())) return ts; return context->clock_tracker->ToTraceTime(clock_id, ts); } @@ -271,7 +272,8 @@ void FtraceTokenizer::TokenizeFtraceEvent( } else if (PERFETTO_UNLIKELY(event_id == protos::pbzero::FtraceEvent:: kThermalExynosAcpmBulkFieldNumber)) { - TokenizeFtraceThermalExynosAcpmBulk(cpu, std::move(event), std::move(state)); + TokenizeFtraceThermalExynosAcpmBulk(cpu, std::move(event), + std::move(state)); return; } @@ -475,7 +477,8 @@ void FtraceTokenizer::TokenizeFtraceGpuWorkPeriod( } void FtraceTokenizer::TokenizeFtraceThermalExynosAcpmBulk( - uint32_t cpu, TraceBlobView event, + uint32_t cpu, + TraceBlobView event, RefPtr state) { // Special handling of valid thermal_exynos_acpm_bulk tracepoint events which // contains the right timestamp value nested inside the event data. diff --git a/src/trace_processor/importers/ftrace/ftrace_tokenizer.h b/src/trace_processor/importers/ftrace/ftrace_tokenizer.h index e0b4f7c959..6aff47da11 100644 --- a/src/trace_processor/importers/ftrace/ftrace_tokenizer.h +++ b/src/trace_processor/importers/ftrace/ftrace_tokenizer.h @@ -64,9 +64,10 @@ class FtraceTokenizer { void TokenizeFtraceGpuWorkPeriod(uint32_t cpu, TraceBlobView event, RefPtr state); - void TokenizeFtraceThermalExynosAcpmBulk(uint32_t cpu, - TraceBlobView event, - RefPtr state); + void TokenizeFtraceThermalExynosAcpmBulk( + uint32_t cpu, + TraceBlobView event, + RefPtr state); void DlogWithLimit(const base::Status& status) { static std::atomic dlog_count(0); diff --git a/src/trace_processor/importers/ftrace/mali_gpu_event_tracker.cc b/src/trace_processor/importers/ftrace/mali_gpu_event_tracker.cc index 6b929c9ae4..ad33a20702 100644 --- a/src/trace_processor/importers/ftrace/mali_gpu_event_tracker.cc +++ b/src/trace_processor/importers/ftrace/mali_gpu_event_tracker.cc @@ -17,7 +17,6 @@ #include "src/trace_processor/importers/ftrace/mali_gpu_event_tracker.h" #include "perfetto/ext/base/string_utils.h" -#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h" #include "protos/perfetto/trace/ftrace/mali.pbzero.h" #include "src/trace_processor/importers/common/async_track_set_tracker.h" #include "src/trace_processor/importers/common/process_tracker.h" @@ -40,7 +39,63 @@ MaliGpuEventTracker::MaliGpuEventTracker(TraceProcessorContext* context) mali_CSF_INTERRUPT_id_( context->storage->InternString("mali_CSF_INTERRUPT")), mali_CSF_INTERRUPT_info_val_id_( - context->storage->InternString("info_val")) {} + context->storage->InternString("info_val")), + current_mcu_state_name_(kNullStringId), + mcu_state_track_name_(context->storage->InternString("Mali MCU state")) { + using protos::pbzero::FtraceEvent; + + mcu_state_names_.fill(kNullStringId); + RegisterMcuState< + FtraceEvent::kMaliMaliPMMCUHCTLCORESDOWNSCALENOTIFYPENDFieldNumber>( + "HCTL_CORES_DOWN_SCALE_NOTIFY_PEND"); + RegisterMcuState( + "HCTL_CORES_NOTIFY_PEND"); + RegisterMcuState( + "HCTL_CORE_INACTIVE_PEND"); + RegisterMcuState( + "HCTL_MCU_ON_RECHECK"); + RegisterMcuState< + FtraceEvent::kMaliMaliPMMCUHCTLSHADERSCOREOFFPENDFieldNumber>( + "HCTL_SHADERS_CORE_OFF_PEND"); + RegisterMcuState( + "HCTL_SHADERS_PEND_OFF"); + RegisterMcuState( + "HCTL_SHADERS_PEND_ON"); + RegisterMcuState( + "HCTL_SHADERS_READY_OFF"); + RegisterMcuState("IN_SLEEP"); + RegisterMcuState("OFF"); + RegisterMcuState("ON"); + RegisterMcuState( + "ON_CORE_ATTR_UPDATE_PEND"); + RegisterMcuState( + "ON_GLB_REINIT_PEND"); + RegisterMcuState("ON_HALT"); + RegisterMcuState( + "ON_HWCNT_DISABLE"); + RegisterMcuState( + "ON_HWCNT_ENABLE"); + RegisterMcuState( + "ON_PEND_HALT"); + RegisterMcuState( + "ON_PEND_SLEEP"); + RegisterMcuState( + "ON_SLEEP_INITIATE"); + RegisterMcuState("PEND_OFF"); + RegisterMcuState( + "PEND_ON_RELOAD"); + RegisterMcuState( + "POWER_DOWN"); + RegisterMcuState( + "RESET_WAIT"); +} + +template +void MaliGpuEventTracker::RegisterMcuState(const char* state_name) { + static_assert(FieldId >= kFirstMcuStateId && FieldId <= kLastMcuStateId); + mcu_state_names_[FieldId - kFirstMcuStateId] = + context_->storage->InternString(state_name); +} void MaliGpuEventTracker::ParseMaliGpuEvent(int64_t ts, uint32_t field_id, @@ -120,6 +175,31 @@ void MaliGpuEventTracker::ParseMaliGpuIrqEvent(int64_t ts, } } +void MaliGpuEventTracker::ParseMaliGpuMcuStateEvent(int64_t timestamp, + uint32_t field_id) { + tables::GpuTrackTable::Row track_info(mcu_state_track_name_); + TrackId track_id = context_->track_tracker->InternGpuTrack(track_info); + + if (field_id < kFirstMcuStateId || field_id > kLastMcuStateId) { + PERFETTO_FATAL("Mali MCU state ID out of range"); + } + + StringId state_name = mcu_state_names_[field_id - kFirstMcuStateId]; + if (state_name == kNullStringId) { + context_->storage->IncrementStats(stats::mali_unknown_mcu_state_id); + return; + } + + if (current_mcu_state_name_ != kNullStringId) { + context_->slice_tracker->End(timestamp, track_id, kNullStringId, + current_mcu_state_name_); + } + + context_->slice_tracker->Begin(timestamp, track_id, kNullStringId, + state_name); + current_mcu_state_name_ = state_name; +} + void MaliGpuEventTracker::ParseMaliKcpuCqsSet(int64_t timestamp, TrackId track_id) { context_->slice_tracker->Scoped(timestamp, track_id, kNullStringId, diff --git a/src/trace_processor/importers/ftrace/mali_gpu_event_tracker.h b/src/trace_processor/importers/ftrace/mali_gpu_event_tracker.h index 2c1f7bcc5d..0a4e5610a7 100644 --- a/src/trace_processor/importers/ftrace/mali_gpu_event_tracker.h +++ b/src/trace_processor/importers/ftrace/mali_gpu_event_tracker.h @@ -17,6 +17,7 @@ #ifndef SRC_TRACE_PROCESSOR_IMPORTERS_FTRACE_MALI_GPU_EVENT_TRACKER_H_ #define SRC_TRACE_PROCESSOR_IMPORTERS_FTRACE_MALI_GPU_EVENT_TRACKER_H_ +#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h" #include "src/trace_processor/storage/trace_storage.h" #include "src/trace_processor/util/descriptors.h" @@ -33,15 +34,9 @@ class MaliGpuEventTracker { uint32_t field_id, uint32_t cpu, protozero::ConstBytes blob); + void ParseMaliGpuMcuStateEvent(int64_t timestamp, uint32_t field_id); private: - TraceProcessorContext* context_; - StringId mali_KCPU_CQS_SET_id_; - StringId mali_KCPU_CQS_WAIT_id_; - StringId mali_KCPU_FENCE_SIGNAL_id_; - StringId mali_KCPU_FENCE_WAIT_id_; - StringId mali_CSF_INTERRUPT_id_; - StringId mali_CSF_INTERRUPT_info_val_id_; void ParseMaliKcpuFenceSignal(int64_t timestamp, TrackId track_id); void ParseMaliKcpuFenceWaitStart(int64_t timestamp, TrackId track_id); void ParseMaliKcpuFenceWaitEnd(int64_t timestamp, TrackId track_id); @@ -54,6 +49,27 @@ class MaliGpuEventTracker { void ParseMaliCSFInterruptEnd(int64_t timestamp, TrackId track_id, protozero::ConstBytes blob); + + template + void RegisterMcuState(const char* state_name); + + static constexpr uint32_t kFirstMcuStateId = protos::pbzero::FtraceEvent:: + kMaliMaliPMMCUHCTLCORESDOWNSCALENOTIFYPENDFieldNumber; + static constexpr uint32_t kLastMcuStateId = + protos::pbzero::FtraceEvent::kMaliMaliPMMCURESETWAITFieldNumber; + + TraceProcessorContext* context_; + StringId mali_KCPU_CQS_SET_id_; + StringId mali_KCPU_CQS_WAIT_id_; + StringId mali_KCPU_FENCE_SIGNAL_id_; + StringId mali_KCPU_FENCE_WAIT_id_; + StringId mali_CSF_INTERRUPT_id_; + StringId mali_CSF_INTERRUPT_info_val_id_; + + std::array + mcu_state_names_; + StringId current_mcu_state_name_; + StringId mcu_state_track_name_; }; } // namespace trace_processor diff --git a/src/trace_processor/importers/ftrace/thermal_tracker.cc b/src/trace_processor/importers/ftrace/thermal_tracker.cc index 4e1e367f56..f2fe7bb160 100644 --- a/src/trace_processor/importers/ftrace/thermal_tracker.cc +++ b/src/trace_processor/importers/ftrace/thermal_tracker.cc @@ -14,12 +14,12 @@ * limitations under the License. */ +#include "src/trace_processor/importers/ftrace/thermal_tracker.h" #include "perfetto/ext/base/string_utils.h" #include "protos/perfetto/trace/ftrace/thermal.pbzero.h" #include "protos/perfetto/trace/ftrace/thermal_exynos.pbzero.h" #include "src/trace_processor/importers/common/event_tracker.h" #include "src/trace_processor/importers/common/track_tracker.h" -#include "src/trace_processor/importers/ftrace/thermal_tracker.h" namespace perfetto { namespace trace_processor { @@ -84,7 +84,8 @@ void ThermalTracker::ParseThermalExynosAcpmBulk(protozero::ConstBytes blob) { } void ThermalTracker::ParseThermalExynosAcpmHighOverhead( - int64_t timestamp, protozero::ConstBytes blob) { + int64_t timestamp, + protozero::ConstBytes blob) { protos::pbzero::ThermalExynosAcpmHighOverheadFtraceEvent::Decoder event(blob); auto tz_id = static_cast(event.tz_id()); if (tz_id >= kAcpmThermalZones) { @@ -100,7 +101,8 @@ void ThermalTracker::ParseThermalExynosAcpmHighOverhead( static_cast(event.cdev_state())); } -void ThermalTracker::PushCounter(int64_t timestamp, StringId counter_id, +void ThermalTracker::PushCounter(int64_t timestamp, + StringId counter_id, double value) { TrackId track = context_->track_tracker->InternGlobalCounterTrack( TrackTracker::Group::kThermals, counter_id); diff --git a/src/trace_processor/importers/fuchsia/fuchsia_parser_unittest.cc b/src/trace_processor/importers/fuchsia/fuchsia_parser_unittest.cc index ca02bdcb0b..ec29509daf 100644 --- a/src/trace_processor/importers/fuchsia/fuchsia_parser_unittest.cc +++ b/src/trace_processor/importers/fuchsia/fuchsia_parser_unittest.cc @@ -14,25 +14,32 @@ * limitations under the License. */ -#include "src/trace_processor/importers/common/trace_parser.h" #include "src/trace_processor/importers/fuchsia/fuchsia_trace_parser.h" -#include "src/trace_processor/importers/fuchsia/fuchsia_trace_tokenizer.h" + +#include #include "perfetto/base/logging.h" #include "perfetto/ext/base/string_view.h" #include "perfetto/protozero/scattered_heap_buffer.h" #include "perfetto/trace_processor/trace_blob.h" + #include "src/trace_processor/importers/common/args_tracker.h" #include "src/trace_processor/importers/common/args_translation_table.h" +#include "src/trace_processor/importers/common/chunked_trace_reader.h" #include "src/trace_processor/importers/common/clock_tracker.h" +#include "src/trace_processor/importers/common/cpu_tracker.h" #include "src/trace_processor/importers/common/event_tracker.h" #include "src/trace_processor/importers/common/flow_tracker.h" +#include "src/trace_processor/importers/common/machine_tracker.h" #include "src/trace_processor/importers/common/metadata_tracker.h" +#include "src/trace_processor/importers/common/process_track_translation_table.h" #include "src/trace_processor/importers/common/process_tracker.h" #include "src/trace_processor/importers/common/slice_tracker.h" #include "src/trace_processor/importers/common/stack_profile_tracker.h" +#include "src/trace_processor/importers/common/trace_parser.h" #include "src/trace_processor/importers/common/track_tracker.h" #include "src/trace_processor/importers/ftrace/ftrace_sched_event_tracker.h" +#include "src/trace_processor/importers/fuchsia/fuchsia_trace_tokenizer.h" #include "src/trace_processor/importers/proto/additional_modules.h" #include "src/trace_processor/importers/proto/default_modules.h" #include "src/trace_processor/importers/proto/proto_trace_parser_impl.h" @@ -242,12 +249,16 @@ class FuchsiaTraceParserTest : public ::testing::Test { context_.args_translation_table.reset(new ArgsTranslationTable(storage_)); context_.metadata_tracker.reset( new MetadataTracker(context_.storage.get())); + context_.machine_tracker.reset(new MachineTracker(&context_, 0)); + context_.cpu_tracker.reset(new CpuTracker(&context_)); event_ = new MockEventTracker(&context_); context_.event_tracker.reset(event_); sched_ = new MockSchedEventTracker(&context_); context_.ftrace_sched_tracker.reset(sched_); process_ = new NiceMock(&context_); context_.process_tracker.reset(process_); + context_.process_track_translation_table.reset( + new ProcessTrackTranslationTable(storage_)); slice_ = new NiceMock(&context_); context_.slice_tracker.reset(slice_); context_.slice_translation_table.reset(new SliceTranslationTable(storage_)); @@ -278,8 +289,9 @@ class FuchsiaTraceParserTest : public ::testing::Test { const size_t num_bytes = trace_bytes_.size() * sizeof(uint64_t); std::unique_ptr raw_trace(new uint8_t[num_bytes]); memcpy(raw_trace.get(), trace_bytes_.data(), num_bytes); - context_.chunk_reader.reset(new FuchsiaTraceTokenizer(&context_)); - auto status = context_.chunk_reader->Parse(TraceBlobView( + context_.chunk_readers.push_back( + std::make_unique(&context_)); + auto status = context_.chunk_readers.back()->Parse(TraceBlobView( TraceBlob::TakeOwnership(std::move(raw_trace), num_bytes))); ResetTraceBuffers(); diff --git a/src/trace_processor/importers/fuchsia/fuchsia_trace_tokenizer.cc b/src/trace_processor/importers/fuchsia/fuchsia_trace_tokenizer.cc index 0cbd4f787e..a668c42d3e 100644 --- a/src/trace_processor/importers/fuchsia/fuchsia_trace_tokenizer.cc +++ b/src/trace_processor/importers/fuchsia/fuchsia_trace_tokenizer.cc @@ -22,6 +22,7 @@ #include "perfetto/base/logging.h" #include "perfetto/ext/base/string_view.h" #include "perfetto/trace_processor/trace_blob.h" +#include "src/trace_processor/importers/common/cpu_tracker.h" #include "src/trace_processor/importers/common/process_tracker.h" #include "src/trace_processor/importers/common/slice_tracker.h" #include "src/trace_processor/importers/fuchsia/fuchsia_record.h" @@ -257,7 +258,7 @@ void FuchsiaTraceTokenizer::SwitchFrom(Thread* thread, // state. tables::ThreadStateTable::Row state_row; state_row.ts = ts; - state_row.cpu = cpu; + state_row.ucpu = context_->cpu_tracker->GetOrCreateCpu(cpu); state_row.dur = -1; state_row.state = state; state_row.utid = utid; @@ -287,10 +288,11 @@ void FuchsiaTraceTokenizer::SwitchTo(Thread* thread, thread->last_state_row.reset(); } + auto ucpu = context_->cpu_tracker->GetOrCreateCpu(cpu); // Open a new slice record for this thread. tables::SchedSliceTable::Row slice_row; slice_row.ts = ts; - slice_row.cpu = cpu; + slice_row.ucpu = ucpu; slice_row.dur = -1; slice_row.utid = utid; slice_row.priority = weight; @@ -301,7 +303,7 @@ void FuchsiaTraceTokenizer::SwitchTo(Thread* thread, // Open a new state record for this thread. tables::ThreadStateTable::Row state_row; state_row.ts = ts; - state_row.cpu = cpu; + state_row.ucpu = context_->cpu_tracker->GetOrCreateCpu(cpu); state_row.dur = -1; state_row.state = running_string_id_; state_row.utid = utid; @@ -331,7 +333,7 @@ void FuchsiaTraceTokenizer::Wake(Thread* thread, int64_t ts, uint32_t cpu) { // Open a new state record for this thread. tables::ThreadStateTable::Row state_row; state_row.ts = ts; - state_row.cpu = cpu; + state_row.ucpu = context_->cpu_tracker->GetOrCreateCpu(cpu); state_row.dur = -1; state_row.state = waking_string_id_; state_row.utid = utid; diff --git a/src/trace_processor/importers/gzip/gzip_trace_parser.h b/src/trace_processor/importers/gzip/gzip_trace_parser.h index 6689939ce8..1f731a5388 100644 --- a/src/trace_processor/importers/gzip/gzip_trace_parser.h +++ b/src/trace_processor/importers/gzip/gzip_trace_parser.h @@ -17,11 +17,15 @@ #ifndef SRC_TRACE_PROCESSOR_IMPORTERS_GZIP_GZIP_TRACE_PARSER_H_ #define SRC_TRACE_PROCESSOR_IMPORTERS_GZIP_GZIP_TRACE_PARSER_H_ +#include +#include +#include + +#include "perfetto/base/status.h" #include "src/trace_processor/importers/common/chunked_trace_reader.h" #include "src/trace_processor/util/gzip_utils.h" -namespace perfetto { -namespace trace_processor { +namespace perfetto::trace_processor { class TraceProcessorContext; @@ -32,10 +36,10 @@ class GzipTraceParser : public ChunkedTraceReader { ~GzipTraceParser() override; // ChunkedTraceReader implementation - util::Status Parse(TraceBlobView) override; + base::Status Parse(TraceBlobView) override; void NotifyEndOfFile() override; - util::Status ParseUnowned(const uint8_t*, size_t); + base::Status ParseUnowned(const uint8_t*, size_t); bool needs_more_input() const { return needs_more_input_; } @@ -51,7 +55,6 @@ class GzipTraceParser : public ChunkedTraceReader { bool needs_more_input_ = false; }; -} // namespace trace_processor -} // namespace perfetto +} // namespace perfetto::trace_processor #endif // SRC_TRACE_PROCESSOR_IMPORTERS_GZIP_GZIP_TRACE_PARSER_H_ diff --git a/src/trace_processor/importers/ninja/ninja_log_parser.h b/src/trace_processor/importers/ninja/ninja_log_parser.h index b05627a8de..28344c33b6 100644 --- a/src/trace_processor/importers/ninja/ninja_log_parser.h +++ b/src/trace_processor/importers/ninja/ninja_log_parser.h @@ -17,17 +17,15 @@ #ifndef SRC_TRACE_PROCESSOR_IMPORTERS_NINJA_NINJA_LOG_PARSER_H_ #define SRC_TRACE_PROCESSOR_IMPORTERS_NINJA_NINJA_LOG_PARSER_H_ -#include - -#include +#include #include #include +#include "perfetto/base/status.h" #include "src/trace_processor/importers/common/chunked_trace_reader.h" #include "src/trace_processor/importers/common/trace_parser.h" -namespace perfetto { -namespace trace_processor { +namespace perfetto::trace_processor { class TraceProcessorContext; @@ -50,7 +48,7 @@ class NinjaLogParser : public ChunkedTraceReader { NinjaLogParser& operator=(const NinjaLogParser&) = delete; // ChunkedTraceReader implementation - util::Status Parse(TraceBlobView) override; + base::Status Parse(TraceBlobView) override; void NotifyEndOfFile() override; private: @@ -73,7 +71,6 @@ class NinjaLogParser : public ChunkedTraceReader { std::vector log_; }; -} // namespace trace_processor -} // namespace perfetto +} // namespace perfetto::trace_processor #endif // SRC_TRACE_PROCESSOR_IMPORTERS_NINJA_NINJA_LOG_PARSER_H_ diff --git a/src/trace_processor/importers/perf/BUILD.gn b/src/trace_processor/importers/perf/BUILD.gn index 376f9b4209..5fd5d25c23 100644 --- a/src/trace_processor/importers/perf/BUILD.gn +++ b/src/trace_processor/importers/perf/BUILD.gn @@ -16,6 +16,8 @@ import("../../../../gn/test.gni") source_set("record") { sources = [ + "perf_counter.cc", + "perf_counter.h", "perf_event.h", "perf_event_attr.cc", "perf_event_attr.h", @@ -26,38 +28,64 @@ source_set("record") { ] deps = [ "../../../../gn:default_deps", + "../../../../include/perfetto/ext/base:base", + "../../../../include/perfetto/trace_processor:trace_processor", "../../../../protos/perfetto/trace/profiling:zero", - "../../sorter", "../../storage", "../../tables:tables_python", "../../types", - "../common", + "../../util:build_id", "../common:parser_types", ] } + +source_set("tracker") { + sources = [ + "dso_tracker.cc", + "dso_tracker.h", + ] + deps = [ + "../../../../gn:default_deps", + "../../../../include/perfetto/ext/base:base", + "../../../../protos/third_party/simpleperf:zero", + "../..//storage:storage", + "../..//tables:tables", + "../..//types:types", + "../common:common", + ] +} + source_set("perf") { sources = [ - "perf_data_parser.cc", - "perf_data_parser.h", - "perf_data_reader.cc", - "perf_data_reader.h", + "attrs_section_reader.cc", + "attrs_section_reader.h", + "features.cc", + "features.h", + "mmap_record.cc", + "mmap_record.h", "perf_data_tokenizer.cc", "perf_data_tokenizer.h", - "perf_data_tracker.cc", - "perf_data_tracker.h", "perf_file.h", - "reader.h", + "record_parser.cc", + "record_parser.h", + "sample.cc", + "sample.h", ] public_deps = [ ":record" ] deps = [ + ":tracker", "../../../../gn:default_deps", + "../../../../protos/perfetto/trace:zero", "../../../../protos/perfetto/trace/profiling:zero", + "../../../../protos/third_party/simpleperf:zero", "../../sorter", "../../storage", "../../tables:tables_python", "../../types", - "../common", - "../common:parser_types", + "../../util:build_id", + "../../util:trace_blob_view_reader", + "../../util:util", + "../common:common", "../proto:minimal", ] } @@ -65,8 +93,6 @@ source_set("perf") { perfetto_unittest_source_set("unittests") { testonly = true sources = [ - "perf_data_reader_unittest.cc", - "perf_data_tracker_unittest.cc", "perf_session_unittest.cc", "reader_unittest.cc", ] @@ -76,6 +102,8 @@ perfetto_unittest_source_set("unittests") { "../../../../gn:gtest_and_gmock", "../../../../protos/perfetto/trace/profiling:zero", "../../../base", - "../../importers/common", + "../../storage", + "../../types", + "../common", ] } diff --git a/src/trace_processor/importers/perf/attrs_section_reader.cc b/src/trace_processor/importers/perf/attrs_section_reader.cc new file mode 100644 index 0000000000..ddf358858a --- /dev/null +++ b/src/trace_processor/importers/perf/attrs_section_reader.cc @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "src/trace_processor/importers/perf/attrs_section_reader.h" + +#include +#include +#include + +#include "perfetto/base/logging.h" +#include "perfetto/base/status.h" +#include "perfetto/ext/base/status_or.h" +#include "perfetto/trace_processor/trace_blob_view.h" +#include "src/trace_processor/importers/perf/perf_file.h" + +namespace perfetto::trace_processor::perf_importer { + +// static +base::StatusOr AttrsSectionReader::Create( + const PerfFile::Header& header, + TraceBlobView section) { + PERFETTO_CHECK(section.size() == header.attrs.size); + + if (header.attr_size == 0) { + return base::ErrStatus("Invalid attr_size (0) in perf file header."); + } + + if (header.attrs.size % header.attr_size != 0) { + return base::ErrStatus("Invalid attrs section size %" PRIu64 + " for attr_size %" PRIu64 " in perf file header.", + header.attrs.size, header.attr_size); + } + + const size_t num_attr = header.attrs.size / header.attr_size; + + // Each entry is a perf_event_attr followed by a Section, but the size of + // the perf_event_attr struct written in the file might not be the same as + // sizeof(perf_event_attr) as this struct might grow over time (can be + // bigger or smaller). + static constexpr size_t kSectionSize = sizeof(PerfFile::Section); + if (header.attr_size < kSectionSize) { + return base::ErrStatus( + "Invalid attr_size in file header. Expected at least %zu, found " + "%" PRIu64, + kSectionSize, header.attr_size); + } + const size_t attr_size = header.attr_size - kSectionSize; + + return AttrsSectionReader(std::move(section), num_attr, attr_size); +} + +base::Status AttrsSectionReader::ReadNext(PerfFile::AttrsEntry& entry) { + PERFETTO_CHECK(reader_.ReadPerfEventAttr(entry.attr, attr_size_)); + + if (entry.attr.size != attr_size_) { + return base::ErrStatus( + "Invalid attr.size. Expected %zu, but found %" PRIu32, attr_size_, + entry.attr.size); + } + + PERFETTO_CHECK(reader_.Read(entry.ids)); + --num_attr_; + return base::OkStatus(); +} + +} // namespace perfetto::trace_processor::perf_importer diff --git a/src/trace_processor/importers/perf/attrs_section_reader.h b/src/trace_processor/importers/perf/attrs_section_reader.h new file mode 100644 index 0000000000..aecb634244 --- /dev/null +++ b/src/trace_processor/importers/perf/attrs_section_reader.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_PROCESSOR_IMPORTERS_PERF_ATTRS_SECTION_READER_H_ +#define SRC_TRACE_PROCESSOR_IMPORTERS_PERF_ATTRS_SECTION_READER_H_ + +#include +#include + +#include "perfetto/base/status.h" +#include "perfetto/ext/base/status_or.h" +#include "perfetto/trace_processor/trace_blob_view.h" +#include "src/trace_processor/importers/perf/perf_file.h" +#include "src/trace_processor/importers/perf/reader.h" + +namespace perfetto::trace_processor::perf_importer { + +// Helper to read the attrs section of a perf file. Provides an iterator like +// interface over the perf_event_attr entries. +class AttrsSectionReader { + public: + // Creates a new iterator. + // `attrs_section` data contained in the attrs section of the perf file. + static base::StatusOr Create( + const PerfFile::Header& header, + TraceBlobView attrs_section); + + // Returns true while there are available entries to read via `ReadNext`. + bool CanReadNext() const { return num_attr_ != 0; } + + // Reads the next entry. Can onlybe called if `HasMore` returns true. + base::Status ReadNext(PerfFile::AttrsEntry& entry); + + private: + AttrsSectionReader(TraceBlobView section, size_t num_attr, size_t attr_size) + : reader_(std::move(section)), + num_attr_(num_attr), + attr_size_(attr_size) {} + + Reader reader_; + size_t num_attr_; + const size_t attr_size_; +}; + +} // namespace perfetto::trace_processor::perf_importer + +#endif // SRC_TRACE_PROCESSOR_IMPORTERS_PERF_ATTRS_SECTION_READER_H_ diff --git a/src/trace_processor/importers/perf/dso_tracker.cc b/src/trace_processor/importers/perf/dso_tracker.cc new file mode 100644 index 0000000000..0a35d42265 --- /dev/null +++ b/src/trace_processor/importers/perf/dso_tracker.cc @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "src/trace_processor/importers/perf/dso_tracker.h" + +#include + +#include "perfetto/base/status.h" +#include "perfetto/ext/base/string_view.h" +#include "protos/third_party/simpleperf/record_file.pbzero.h" +#include "src/trace_processor/storage/trace_storage.h" +#include "src/trace_processor/tables/profiler_tables_py.h" +#include "src/trace_processor/types/trace_processor_context.h" + +namespace perfetto::trace_processor::perf_importer { +namespace { + +using third_party::simpleperf::proto::pbzero::FileFeature; +using DexFile = FileFeature::DexFile; +using ElfFile = FileFeature::ElfFile; +using KernelModule = FileFeature::KernelModule; +using DsoType = FileFeature::DsoType; +using Symbol = FileFeature::Symbol; + +void InsertSymbols(const FileFeature::Decoder& file, + AddressRangeMap& out) { + for (auto raw_symbol = file.symbol(); raw_symbol; ++raw_symbol) { + Symbol::Decoder symbol(*raw_symbol); + out.TrimOverlapsAndEmplace( + AddressRange::FromStartAndSize(symbol.vaddr(), symbol.len()), + symbol.name().ToStdString()); + } +} +} // namespace + +DsoTracker::DsoTracker(TraceProcessorContext* context) + : context_(context), + mapping_table_(context_->storage->stack_profile_mapping_table()) {} +DsoTracker::~DsoTracker() = default; + +void DsoTracker::AddSimpleperfFile2(const FileFeature::Decoder& file) { + Dso dso; + switch (file.type()) { + case DsoType::DSO_KERNEL: + InsertSymbols(file, kernel_symbols_); + return; + + case DsoType::DSO_ELF_FILE: { + ElfFile::Decoder elf(file.elf_file()); + dso.load_bias = file.min_vaddr() - elf.file_offset_of_min_vaddr(); + break; + } + + case DsoType::DSO_KERNEL_MODULE: { + KernelModule::Decoder module(file.kernel_module()); + dso.load_bias = file.min_vaddr() - module.memory_offset_of_min_vaddr(); + break; + } + + case DsoType::DSO_DEX_FILE: + case DsoType::DSO_SYMBOL_MAP_FILE: + case DsoType::DSO_UNKNOWN_FILE: + return; + } + + InsertSymbols(file, dso.symbols); + files_.Insert(context_->storage->InternString(file.path()), std::move(dso)); +} + +void DsoTracker::SymbolizeFrames() { + const StringId kEmptyString = context_->storage->InternString(""); + for (auto frame = context_->storage->mutable_stack_profile_frame_table() + ->IterateRows(); + frame; ++frame) { + if (frame.name() != kNullStringId && frame.name() != kEmptyString) { + continue; + } + + if (!TrySymbolizeFrame(frame.row_reference())) { + SymbolizeKernelFrame(frame.row_reference()); + } + } +} + +void DsoTracker::SymbolizeKernelFrame( + tables::StackProfileFrameTable::RowReference frame) { + const auto mapping = *mapping_table_.FindById(frame.mapping()); + uint64_t address = static_cast(frame.rel_pc()) + + static_cast(mapping.start()); + auto symbol = kernel_symbols_.Find(address); + if (symbol == kernel_symbols_.end()) { + return; + } + frame.set_name( + context_->storage->InternString(base::StringView(symbol->second))); +} + +bool DsoTracker::TrySymbolizeFrame( + tables::StackProfileFrameTable::RowReference frame) { + const auto mapping = *mapping_table_.FindById(frame.mapping()); + auto* file = files_.Find(mapping.name()); + if (!file) { + return false; + } + + // Load bias is something we can only determine by looking at the actual elf + // file. Thus PERF_RECORD_MMAP{2} events do not record it. So we need to + // potentially do an adjustment here if the load_bias tracked in the mapping + // table and the one reported by the file are mismatched. + uint64_t adj = file->load_bias - static_cast(mapping.load_bias()); + + auto symbol = file->symbols.Find(static_cast(frame.rel_pc()) + adj); + if (symbol == file->symbols.end()) { + return false; + } + frame.set_name( + context_->storage->InternString(base::StringView(symbol->second))); + return true; +} + +} // namespace perfetto::trace_processor::perf_importer diff --git a/src/trace_processor/importers/perf/dso_tracker.h b/src/trace_processor/importers/perf/dso_tracker.h new file mode 100644 index 0000000000..314d549359 --- /dev/null +++ b/src/trace_processor/importers/perf/dso_tracker.h @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PERF_DSO_TRACKER_H_ +#define SRC_TRACE_PROCESSOR_IMPORTERS_PERF_DSO_TRACKER_H_ + +#include +#include + +#include "perfetto/base/status.h" +#include "perfetto/ext/base/flat_hash_map.h" +#include "protos/third_party/simpleperf/record_file.pbzero.h" +#include "src/trace_processor/importers/common/address_range.h" +#include "src/trace_processor/storage/trace_storage.h" +#include "src/trace_processor/tables/profiler_tables_py.h" +#include "src/trace_processor/types/destructible.h" +#include "src/trace_processor/types/trace_processor_context.h" + +namespace perfetto::trace_processor::perf_importer { + +// Keeps track of DSO symbols to symbolize frames at the end of the trace +// parsing. +// TODO(b/334978369): We could potentially use this class (or a similar one) to +// process the ModuleSymbols proto packets and consolidate all symbolization in +// one place. +class DsoTracker : public Destructible { + public: + static DsoTracker& GetOrCreate(TraceProcessorContext* context) { + if (!context->perf_dso_tracker) { + context->perf_dso_tracker.reset(new DsoTracker(context)); + } + return static_cast(*context->perf_dso_tracker); + } + ~DsoTracker() override; + + // Add symbol data contained in a `FileFeature` proto. + void AddSimpleperfFile2( + const third_party::simpleperf::proto::pbzero::FileFeature::Decoder& file); + + // Tries to symbolize any `STACK_PROFILE_FRAME` frame missing the `name` + // attribute. This should be called at the end of parsing when all packets + // have been processed and all tables updated. + void SymbolizeFrames(); + + private: + struct Dso { + uint64_t load_bias; + AddressRangeMap symbols; + }; + + explicit DsoTracker(TraceProcessorContext* context); + + void SymbolizeKernelFrame(tables::StackProfileFrameTable::RowReference frame); + // Returns true it the frame was symbolized. + bool TrySymbolizeFrame(tables::StackProfileFrameTable::RowReference frame); + + TraceProcessorContext* const context_; + const tables::StackProfileMappingTable& mapping_table_; + base::FlatHashMap files_; + AddressRangeMap kernel_symbols_; +}; + +} // namespace perfetto::trace_processor::perf_importer + +#endif // SRC_TRACE_PROCESSOR_IMPORTERS_PERF_DSO_TRACKER_H_ diff --git a/src/trace_processor/importers/perf/features.cc b/src/trace_processor/importers/perf/features.cc new file mode 100644 index 0000000000..877f2fd09a --- /dev/null +++ b/src/trace_processor/importers/perf/features.cc @@ -0,0 +1,296 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "src/trace_processor/importers/perf/features.h" + +#include +#include + +#include "perfetto/base/logging.h" +#include "perfetto/base/status.h" +#include "perfetto/ext/base/string_utils.h" +#include "perfetto/ext/base/string_view.h" +#include "perfetto/trace_processor/status.h" +#include "perfetto/trace_processor/trace_blob_view.h" +#include "src/trace_processor/importers/perf/perf_event.h" +#include "src/trace_processor/importers/perf/reader.h" +#include "src/trace_processor/util/status_macros.h" + +namespace perfetto::trace_processor::perf_importer::feature { +namespace { + +struct BuildIdRecord { + static constexpr uint8_t kMaxSize = 20; + char data[kMaxSize]; + uint8_t size; + uint8_t reserved[3]; +}; + +uint8_t CountTrailingZeros(const BuildIdRecord& build_id) { + for (uint8_t i = 0; i < BuildIdRecord::kMaxSize; ++i) { + if (build_id.data[BuildIdRecord::kMaxSize - i - 1] != 0) { + return i; + } + } + return sizeof(build_id.data); +} + +// BuildIds are usually SHA-1 hashes (20 bytes), sometimes MD5 (16 bytes), +// sometimes 8 bytes long. Simpleperf adds trailing zeros up to 20. Do a best +// guess based on the number of trailing zeros. +uint8_t GuessBuildIdSize(const BuildIdRecord& build_id) { + static_assert(BuildIdRecord::kMaxSize == 20); + uint8_t len = BuildIdRecord::kMaxSize - CountTrailingZeros(build_id); + if (len > 16) { + return BuildIdRecord::kMaxSize; + } + if (len > 8) { + return 16; + } + return 8; +} + +bool ParseString(Reader& reader, std::string& out) { + uint32_t len; + base::StringView str; + if (!reader.Read(len) || len == 0 || !reader.ReadStringView(str, len)) { + return false; + } + + if (str.at(len - 1) != '\0') { + return false; + } + + // Strings are padded with null values, stop at first null + out = std::string(str.data()); + return true; +} + +bool ParseBuildId(const perf_event_header& header, + TraceBlobView blob, + BuildId& out) { + Reader reader(std::move(blob)); + + BuildIdRecord build_id; + + if (!reader.Read(out.pid) || !reader.Read(build_id) || + !reader.ReadStringUntilEndOrNull(out.filename)) { + return false; + } + + if (header.misc & PERF_RECORD_MISC_EXT_RESERVED) { + if (build_id.size > BuildIdRecord::kMaxSize) { + return false; + } + } else { + // Probably a simpleperf trace. Simpleperf fills build_ids with zeros up + // to a length of 20 and leaves the rest uninitialized :( so we can not read + // build_id.size or build_id.reserved to do any checks. + // TODO(b/334978369): We should be able to tell for sure whether this is + // simpleperf or not by checking the existence of SimpleperfMetaInfo. + build_id.size = GuessBuildIdSize(build_id); + } + out.build_id = std::string(build_id.data, build_id.size); + return true; +} + +util::Status ParseEventTypeInfo(std::string value, SimpleperfMetaInfo& out) { + for (const auto& line : base::SplitString(value, "\n")) { + auto tokens = base::SplitString(line, ","); + if (tokens.size() != 3) { + return util::ErrStatus("Invalid event_type_info: '%s'", line.c_str()); + } + + auto type = base::StringToUInt32(tokens[1]); + if (!type) { + return util::ErrStatus("Could not parse type in event_type_info: '%s'", + tokens[1].c_str()); + } + auto config = base::StringToUInt64(tokens[2]); + if (!config) { + return util::ErrStatus("Could not parse config in event_type_info: '%s'", + tokens[2].c_str()); + } + + out.event_type_info.Insert({*type, *config}, std::move(tokens[0])); + } + + return util::OkStatus(); +} + +util::Status ParseSimpleperfMetaInfoEntry( + std::pair entry, + SimpleperfMetaInfo& out) { + static constexpr char kEventTypeInfoKey[] = "event_type_info"; + if (entry.first == kEventTypeInfoKey) { + return ParseEventTypeInfo(std::move(entry.second), out); + } + + PERFETTO_CHECK( + out.entries.Insert(std::move(entry.first), std::move(entry.second)) + .second); + return util::OkStatus(); +} + +} // namespace + +// static +util::Status BuildId::Parse(TraceBlobView bytes, + std::function cb) { + Reader reader(std::move(bytes)); + while (reader.size_left() != 0) { + perf_event_header header; + TraceBlobView payload; + if (!reader.Read(header)) { + return base::ErrStatus( + "Failed to parse feature BuildId. Could not read header."); + } + if (header.size < sizeof(header)) { + return base::ErrStatus( + "Failed to parse feature BuildId. Invalid size in header."); + } + if (!reader.ReadBlob(payload, header.size - sizeof(header))) { + return base::ErrStatus( + "Failed to parse feature BuildId. Could not read payload."); + } + + BuildId build_id; + if (!ParseBuildId(header, std::move(payload), build_id)) { + return base::ErrStatus( + "Failed to parse feature BuildId. Could not read entry."); + } + + RETURN_IF_ERROR(cb(std::move(build_id))); + } + return util::OkStatus(); +} + +// static +util::Status SimpleperfMetaInfo::Parse(const TraceBlobView& bytes, + SimpleperfMetaInfo& out) { + auto* it_end = reinterpret_cast(bytes.data() + bytes.size()); + for (auto* it = reinterpret_cast(bytes.data()); it != it_end;) { + auto end = std::find(it, it_end, '\0'); + if (end == it_end) { + return util::ErrStatus("Failed to read key from Simpleperf MetaInfo"); + } + std::string key(it, end); + it = end; + ++it; + if (it == it_end) { + return util::ErrStatus("Missing value in Simpleperf MetaInfo"); + } + end = std::find(it, it_end, '\0'); + if (end == it_end) { + return util::ErrStatus("Failed to read value from Simpleperf MetaInfo"); + } + std::string value(it, end); + it = end; + ++it; + + RETURN_IF_ERROR(ParseSimpleperfMetaInfoEntry( + std::make_pair(std::move(key), std::move(value)), out)); + } + return util::OkStatus(); +} + +// static +util::Status EventDescription::Parse( + TraceBlobView bytes, + std::function cb) { + Reader reader(std::move(bytes)); + uint32_t nr; + uint32_t attr_size; + if (!reader.Read(nr) || !reader.Read(attr_size)) { + return util::ErrStatus("Failed to parse header for PERF_EVENT_DESC"); + } + + for (; nr != 0; --nr) { + EventDescription desc; + uint32_t nr_ids; + if (!reader.ReadPerfEventAttr(desc.attr, attr_size) || + !reader.Read(nr_ids) || !ParseString(reader, desc.event_string)) { + return util::ErrStatus("Failed to parse record for PERF_EVENT_DESC"); + } + + desc.ids.resize(nr_ids); + for (uint64_t& id : desc.ids) { + if (!reader.Read(id)) { + return util::ErrStatus("Failed to parse ids for PERF_EVENT_DESC"); + } + } + RETURN_IF_ERROR(cb(std::move(desc))); + } + return util::OkStatus(); +} + +util::Status ParseSimpleperfFile2(TraceBlobView bytes, + std::function cb) { + Reader reader(std::move(bytes)); + while (reader.size_left() != 0) { + uint32_t len; + if (!reader.Read(len)) { + return base::ErrStatus("Failed to parse len in FEATURE_SIMPLEPERF_FILE2"); + } + TraceBlobView payload; + if (!reader.ReadBlob(payload, len)) { + return base::ErrStatus( + "Failed to parse payload in FEATURE_SIMPLEPERF_FILE2"); + } + cb(std::move(payload)); + } + return util::OkStatus(); +} + +// static +util::Status HeaderGroupDesc::Parse(TraceBlobView bytes, HeaderGroupDesc& out) { + Reader reader(std::move(bytes)); + uint32_t nr; + if (!reader.Read(nr)) { + return util::ErrStatus("Failed to parse header for HEADER_GROUP_DESC"); + } + + HeaderGroupDesc group_desc; + group_desc.entries.resize(nr); + for (auto& e : group_desc.entries) { + if (!ParseString(reader, e.string) || !reader.Read(e.leader_idx) || + !reader.Read(e.nr_members)) { + return util::ErrStatus("Failed to parse HEADER_GROUP_DESC entry"); + } + } + out = std::move(group_desc); + return base::OkStatus(); +} + +base::StatusOr> ParseCmdline(TraceBlobView bytes) { + Reader reader(std::move(bytes)); + uint32_t nr; + if (!reader.Read(nr)) { + return util::ErrStatus("Failed to parse nr for CMDLINE"); + } + + std::vector args; + args.reserve(nr); + for (; nr != 0; --nr) { + args.emplace_back(); + if (!ParseString(reader, args.back())) { + return base::ErrStatus("Failed to parse string for CMDLINE"); + } + } + return std::move(args); +} + +} // namespace perfetto::trace_processor::perf_importer::feature diff --git a/src/trace_processor/importers/perf/features.h b/src/trace_processor/importers/perf/features.h new file mode 100644 index 0000000000..e028957274 --- /dev/null +++ b/src/trace_processor/importers/perf/features.h @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PERF_FEATURES_H_ +#define SRC_TRACE_PROCESSOR_IMPORTERS_PERF_FEATURES_H_ + +#include +#include +#include +#include +#include + +#include "perfetto/ext/base/flat_hash_map.h" +#include "perfetto/ext/base/hash.h" +#include "perfetto/ext/base/status_or.h" +#include "perfetto/trace_processor/status.h" +#include "src/trace_processor/importers/perf/perf_event.h" + +namespace perfetto ::trace_processor { +class TraceBlobView; + +namespace perf_importer::feature { + +enum Id : uint8_t { + ID_RESERVED = 0, + ID_TRACING_DATA = 1, + ID_BUILD_ID = 2, + ID_HOSTNAME = 3, + ID_OS_RELEASE = 4, + ID_VERSION = 5, + ID_ARCH = 6, + ID_NR_CPUS = 7, + ID_CPU_DESC = 8, + ID_CPU_ID = 9, + ID_TOTAL_MEM = 10, + ID_CMD_LINE = 11, + ID_EVENT_DESC = 12, + ID_CPU_TOPOLOGY = 13, + ID_NUMA_TOPOLOGY = 14, + ID_BRANCH_STACK = 15, + ID_PMU_MAPPINGS = 16, + ID_GROUP_DESC = 17, + ID_AUX_TRACE = 18, + ID_STAT = 19, + ID_CACHE = 20, + ID_SAMPLE_TIME = 21, + ID_SAMPLE_TOPOLOGY = 22, + ID_CLOCK_ID = 23, + ID_DIR_FORMAT = 24, + ID_BPF_PROG_INFO = 25, + ID_BPF_BTF = 26, + ID_COMPRESSED = 27, + ID_CPU_PUM_CAPS = 28, + ID_CLOCK_DATA = 29, + ID_HYBRID_TOPOLOGY = 30, + ID_PMU_CAPS = 31, + ID_SIMPLEPERF_FILE = 128, + ID_SIMPLEPERF_META_INFO = 129, + ID_SIMPLEPERF_FILE2 = 132, + ID_MAX = std::numeric_limits::max(), +}; + +struct BuildId { + static util::Status Parse(TraceBlobView, + std::function cb); + int32_t pid; + std::string build_id; + std::string filename; +}; + +struct HeaderGroupDesc { + static util::Status Parse(TraceBlobView, HeaderGroupDesc& out); + struct Entry { + std::string string; + uint32_t leader_idx; + uint32_t nr_members; + }; + std::vector entries; +}; + +struct EventDescription { + static util::Status Parse(TraceBlobView, + std::function cb); + perf_event_attr attr; + std::string event_string; + std::vector ids; +}; + +struct SimpleperfMetaInfo { + static util::Status Parse(const TraceBlobView&, SimpleperfMetaInfo& out); + base::FlatHashMap entries; + struct EventTypeAndConfig { + uint32_t type; + uint64_t config; + bool operator==(const EventTypeAndConfig& other) { + return type == other.type && config == other.config; + } + bool operator!=(const EventTypeAndConfig& other) { + return !(*this == other); + } + struct Hasher { + size_t operator()(const EventTypeAndConfig& o) const { + return static_cast(base::Hasher::Combine(o.config, o.type)); + } + }; + }; + using EventName = std::string; + base::FlatHashMap + event_type_info; +}; + +util::Status ParseSimpleperfFile2(TraceBlobView, + std::function cb); + +base::StatusOr> ParseCmdline(TraceBlobView blob); + +} // namespace perf_importer::feature + +} // namespace perfetto::trace_processor + +#endif // SRC_TRACE_PROCESSOR_IMPORTERS_PERF_FEATURES_H_ diff --git a/src/trace_processor/importers/perf/mmap_record.cc b/src/trace_processor/importers/perf/mmap_record.cc new file mode 100644 index 0000000000..d11fdd5414 --- /dev/null +++ b/src/trace_processor/importers/perf/mmap_record.cc @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "src/trace_processor/importers/perf/mmap_record.h" + +#include + +#include "perfetto/base/status.h" +#include "src/trace_processor/importers/perf/reader.h" +#include "src/trace_processor/importers/perf/record.h" + +namespace perfetto::trace_processor::perf_importer { + +base::Status MmapRecord::Parse(const Record& record) { + Reader reader(record.payload.copy()); + if (!reader.Read(*static_cast(this)) || + !reader.ReadCString(filename)) { + return base::ErrStatus("Failed to parse MMAP record"); + } + cpu_mode = record.GetCpuMode(); + return base::OkStatus(); +} + +base::Status Mmap2Record::Parse(const Record& record) { + Reader reader(record.payload.copy()); + if (!reader.Read(*static_cast(this)) || + !reader.ReadCString(filename)) { + return base::ErrStatus("Failed to parse MMAP record"); + } + + has_build_id = record.mmap_has_build_id(); + + if (has_build_id && build_id.build_id_size > + BaseMmap2Record::BuildIdFields::kMaxBuildIdSize) { + return base::ErrStatus( + "Invalid build_id_size in MMAP2 record. Expected <= %zu but found " + "%" PRIu8, + BaseMmap2Record::BuildIdFields::kMaxBuildIdSize, + build_id.build_id_size); + } + + cpu_mode = record.GetCpuMode(); + + return base::OkStatus(); +} + +std::optional Mmap2Record::GetBuildId() const { + return has_build_id ? std::make_optional(BuildId::FromRaw(std::string( + build_id.build_id_buf, build_id.build_id_size))) + : std::nullopt; +} + +} // namespace perfetto::trace_processor::perf_importer diff --git a/src/trace_processor/importers/perf/mmap_record.h b/src/trace_processor/importers/perf/mmap_record.h new file mode 100644 index 0000000000..37b3939425 --- /dev/null +++ b/src/trace_processor/importers/perf/mmap_record.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PERF_MMAP_RECORD_H_ +#define SRC_TRACE_PROCESSOR_IMPORTERS_PERF_MMAP_RECORD_H_ + +#include +#include +#include +#include "perfetto/base/status.h" +#include "protos/perfetto/trace/profiling/profile_packet.pbzero.h" +#include "src/trace_processor/util/build_id.h" + +namespace perfetto::trace_processor::perf_importer { + +struct Record; + +struct CommonMmapRecordFields { + uint32_t pid; + uint32_t tid; + uint64_t addr; + uint64_t len; + uint64_t pgoff; +}; + +struct MmapRecord : public CommonMmapRecordFields { + std::string filename; + protos::pbzero::Profiling::CpuMode cpu_mode; + + base::Status Parse(const Record& record); +}; + +struct BaseMmap2Record : public CommonMmapRecordFields { + struct BuildIdFields { + static constexpr size_t kMaxBuildIdSize = 20; + uint8_t build_id_size; + uint8_t reserved_1; + uint16_t reserved_2; + char build_id_buf[kMaxBuildIdSize]; + }; + struct InodeFields { + uint32_t maj; + uint32_t min; + int64_t ino; + uint64_t ino_generation; + }; + static_assert(sizeof(BuildIdFields) == sizeof(InodeFields)); + + union { + BuildIdFields build_id; + InodeFields inode; + }; + uint32_t prot; + uint32_t flags; +}; + +struct Mmap2Record : public BaseMmap2Record { + std::string filename; + protos::pbzero::Profiling::CpuMode cpu_mode; + bool has_build_id; + + base::Status Parse(const Record& record); + std::optional GetBuildId() const; +}; + +} // namespace perfetto::trace_processor::perf_importer + +#endif // SRC_TRACE_PROCESSOR_IMPORTERS_PERF_MMAP_RECORD_H_ diff --git a/src/trace_processor/importers/perf/perf_counter.cc b/src/trace_processor/importers/perf/perf_counter.cc new file mode 100644 index 0000000000..685e940404 --- /dev/null +++ b/src/trace_processor/importers/perf/perf_counter.cc @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "src/trace_processor/importers/perf/perf_counter.h" + +#include + +#include "perfetto/base/logging.h" +#include "src/trace_processor/tables/counter_tables_py.h" + +namespace perfetto::trace_processor::perf_importer { + +void PerfCounter::AddDelta(int64_t ts, double delta) { + last_count_ += delta; + counter_table_.Insert({ts, track_id_, last_count_}); +} + +void PerfCounter::AddCount(int64_t ts, double count) { + PERFETTO_CHECK(count >= last_count_); + last_count_ = count; + counter_table_.Insert({ts, track_id_, last_count_}); +} + +} // namespace perfetto::trace_processor::perf_importer diff --git a/src/trace_processor/importers/perf/perf_counter.h b/src/trace_processor/importers/perf/perf_counter.h new file mode 100644 index 0000000000..173620ef81 --- /dev/null +++ b/src/trace_processor/importers/perf/perf_counter.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PERF_PERF_COUNTER_H_ +#define SRC_TRACE_PROCESSOR_IMPORTERS_PERF_PERF_COUNTER_H_ + +#include + +#include "src/trace_processor/tables/counter_tables_py.h" +#include "src/trace_processor/tables/profiler_tables_py.h" + +namespace perfetto::trace_processor::perf_importer { + +// Helper class to keep track of perf counters and convert delta values found in +// perf files to absolute values needed for the perfetto counter table. +class PerfCounter { + public: + PerfCounter(tables::CounterTable* counter_table, + const tables::PerfCounterTrackTable::ConstRowReference& track) + : counter_table_(*counter_table), + track_id_(track.id()), + is_timebase_(track.is_timebase()) {} + + bool is_timebase() const { return is_timebase_; } + + void AddDelta(int64_t ts, double delta); + void AddCount(int64_t ts, double count); + + private: + tables::CounterTable& counter_table_; + tables::PerfCounterTrackTable::Id track_id_; + const bool is_timebase_; + double last_count_{0}; +}; + +} // namespace perfetto::trace_processor::perf_importer + +#endif // SRC_TRACE_PROCESSOR_IMPORTERS_PERF_PERF_COUNTER_H_ diff --git a/src/trace_processor/importers/perf/perf_data_parser.cc b/src/trace_processor/importers/perf/perf_data_parser.cc deleted file mode 100644 index e3dfef3082..0000000000 --- a/src/trace_processor/importers/perf/perf_data_parser.cc +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/trace_processor/importers/perf/perf_data_parser.h" - -#include -#include -#include -#include "perfetto/base/logging.h" -#include "perfetto/ext/base/string_utils.h" -#include "perfetto/trace_processor/trace_blob_view.h" -#include "src/trace_processor/importers/common/mapping_tracker.h" -#include "src/trace_processor/importers/common/process_tracker.h" -#include "src/trace_processor/importers/perf/perf_data_reader.h" -#include "src/trace_processor/importers/perf/perf_data_tracker.h" -#include "src/trace_processor/storage/trace_storage.h" -#include "src/trace_processor/tables/profiler_tables_py.h" - -namespace perfetto { -namespace trace_processor { -namespace perf_importer { - -using FramesTable = tables::StackProfileFrameTable; -using CallsitesTable = tables::StackProfileCallsiteTable; - -PerfDataParser::PerfDataParser(TraceProcessorContext* context) - : context_(context), tracker_(PerfDataTracker::GetOrCreate(context_)) {} - -PerfDataParser::~PerfDataParser() = default; - -base::StatusOr PerfDataParser::ParseSample( - TraceBlobView tbv) { - perf_importer::PerfDataReader reader(std::move(tbv)); - return tracker_->ParseSample(reader); -} - -void PerfDataParser::ParsePerfRecord(int64_t ts, TraceBlobView tbv) { - auto sample_status = ParseSample(std::move(tbv)); - if (!sample_status.ok()) { - return; - } - PerfDataTracker::PerfSample sample = *sample_status; - - // The sample has been validated in tokenizer so callchain shouldn't be empty. - PERFETTO_CHECK(!sample.callchain.empty()); - - // First instruction pointer in the callchain should be from kernel space, so - // it shouldn't be available in mappings. - UniquePid upid = context_->process_tracker->GetOrCreateProcess(*sample.pid); - if (context_->mapping_tracker->FindUserMappingForAddress( - upid, sample.callchain.front())) { - context_->storage->IncrementStats(stats::perf_samples_skipped); - return; - } - - if (sample.callchain.size() == 1) { - context_->storage->IncrementStats(stats::perf_samples_skipped); - return; - } - - std::vector frame_rows; - for (uint32_t i = 1; i < sample.callchain.size(); i++) { - UserMemoryMapping* mapping = - context_->mapping_tracker->FindUserMappingForAddress( - upid, sample.callchain[i]); - if (!mapping) { - context_->storage->IncrementStats(stats::perf_samples_skipped); - return; - } - FramesTable::Row new_row; - std::string mock_name = - base::StackString<1024>( - "%" PRIu64, sample.callchain[i] - mapping->memory_range().start()) - .ToStdString(); - new_row.name = context_->storage->InternString(mock_name.c_str()); - new_row.mapping = mapping->mapping_id(); - new_row.rel_pc = - static_cast(mapping->ToRelativePc(sample.callchain[i])); - frame_rows.push_back(new_row); - } - - // Insert frames. We couldn't do it before as no frames should be added if the - // mapping couldn't be found for any of them. - const auto& frames = context_->storage->mutable_stack_profile_frame_table(); - std::vector frame_ids; - for (const auto& row : frame_rows) { - frame_ids.push_back(frames->Insert(row).id); - } - - // Insert callsites. - const auto& callsites = - context_->storage->mutable_stack_profile_callsite_table(); - - std::optional parent_callsite_id; - for (uint32_t i = 0; i < frame_ids.size(); i++) { - CallsitesTable::Row callsite_row; - callsite_row.frame_id = frame_ids[i]; - callsite_row.depth = i; - callsite_row.parent_id = parent_callsite_id; - parent_callsite_id = callsites->Insert(callsite_row).id; - } - - // Insert stack sample. - tables::PerfSampleTable::Row perf_sample_row; - perf_sample_row.callsite_id = parent_callsite_id; - perf_sample_row.ts = ts; - if (sample.cpu) { - perf_sample_row.cpu = *sample.cpu; - } - if (sample.tid) { - auto utid = context_->process_tracker->GetOrCreateThread(*sample.tid); - perf_sample_row.utid = utid; - } - context_->storage->mutable_perf_sample_table()->Insert(perf_sample_row); -} - -} // namespace perf_importer -} // namespace trace_processor -} // namespace perfetto diff --git a/src/trace_processor/importers/perf/perf_data_parser.h b/src/trace_processor/importers/perf/perf_data_parser.h deleted file mode 100644 index f2ab0a3213..0000000000 --- a/src/trace_processor/importers/perf/perf_data_parser.h +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PERF_PERF_DATA_PARSER_H_ -#define SRC_TRACE_PROCESSOR_IMPORTERS_PERF_PERF_DATA_PARSER_H_ - -#include - -#include "perfetto/base/compiler.h" -#include "perfetto/trace_processor/trace_blob_view.h" -#include "src/trace_processor/importers/common/trace_parser.h" -#include "src/trace_processor/importers/perf/perf_data_tracker.h" - -namespace perfetto { -namespace trace_processor { -namespace perf_importer { - -// Parses samples from perf.data files. -class PerfDataParser : public PerfRecordParser { - public: - explicit PerfDataParser(TraceProcessorContext*); - ~PerfDataParser() override; - - // The data in TraceBlobView has to be a perf.data sample. - void ParsePerfRecord(int64_t timestamp, TraceBlobView) override; - - private: - base::StatusOr ParseSample(TraceBlobView); - - TraceProcessorContext* context_ = nullptr; - PerfDataTracker* tracker_ = nullptr; -}; - -} // namespace perf_importer -} // namespace trace_processor -} // namespace perfetto - -#endif // SRC_TRACE_PROCESSOR_IMPORTERS_PERF_PERF_DATA_PARSER_H_ diff --git a/src/trace_processor/importers/perf/perf_data_reader.cc b/src/trace_processor/importers/perf/perf_data_reader.cc deleted file mode 100644 index e0618266a8..0000000000 --- a/src/trace_processor/importers/perf/perf_data_reader.cc +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/trace_processor/importers/perf/perf_data_reader.h" - -#include -#include -#include -#include "perfetto/base/logging.h" -#include "perfetto/trace_processor/trace_blob_view.h" - -namespace perfetto { -namespace trace_processor { -namespace perf_importer { -void PerfDataReader::SkipSlow(size_t bytes_to_skip) { - size_t bytes_in_buffer = BytesInBuffer(); - - // Size fits in buffer. - if (bytes_in_buffer >= bytes_to_skip) { - buffer_offset_ += bytes_to_skip; - return; - } - - // Empty the buffer and increase the |blob_offset_|. - buffer_offset_ = 0; - buffer_.clear(); - blob_offset_ += bytes_to_skip - bytes_in_buffer; -} - -void PerfDataReader::PeekSlow(uint8_t* obj_data, size_t size) const { - size_t bytes_in_buffer = BytesInBuffer(); - - // Read from buffer. - if (bytes_in_buffer >= size) { - memcpy(obj_data, buffer_.data() + buffer_offset_, size); - return; - } - - // Read from blob and buffer. - memcpy(obj_data, buffer_.data() + buffer_offset_, bytes_in_buffer); - memcpy(obj_data + bytes_in_buffer, tbv_.data() + blob_offset_, - size - bytes_in_buffer); -} - -TraceBlobView PerfDataReader::PeekTraceBlobViewSlow(size_t size) const { - auto blob = TraceBlob::Allocate(size); - size_t bytes_in_buffer = BytesInBuffer(); - - // Data is in buffer, so we need to create a new TraceBlob from it. - if (bytes_in_buffer >= size) { - memcpy(blob.data(), buffer_.data() + buffer_offset_, size); - return TraceBlobView(std::move(blob)); - } - - // Data is in between blob and buffer and we need to dump data from buffer - // and blob to a new TraceBlob. - size_t bytes_from_blob = size - bytes_in_buffer; - memcpy(blob.data(), buffer_.data() + buffer_offset_, bytes_in_buffer); - memcpy(blob.data() + bytes_in_buffer, tbv_.data() + blob_offset_, - bytes_from_blob); - return TraceBlobView(std::move(blob)); -} - -} // namespace perf_importer -} // namespace trace_processor -} // namespace perfetto diff --git a/src/trace_processor/importers/perf/perf_data_reader.h b/src/trace_processor/importers/perf/perf_data_reader.h deleted file mode 100644 index acf07d1e66..0000000000 --- a/src/trace_processor/importers/perf/perf_data_reader.h +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PERF_PERF_DATA_READER_H_ -#define SRC_TRACE_PROCESSOR_IMPORTERS_PERF_PERF_DATA_READER_H_ - -#include -#include -#include -#include -#include - -#include "perfetto/base/logging.h" -#include "perfetto/trace_processor/trace_blob.h" -#include "perfetto/trace_processor/trace_blob_view.h" - -namespace perfetto { -namespace trace_processor { -namespace perf_importer { - -// Reader class for tokenizing and parsing. Currently used by perf importer, but -// it's design is not related to perf. Responsible for hiding away the -// complexity of reading values from TraceBlobView and glueing the tbvs together -// in case there is data between many of them. -class PerfDataReader { - public: - PerfDataReader() = default; - explicit PerfDataReader(TraceBlobView tbv) : tbv_(std::move(tbv)) {} - - // Updates old TraceBlobView with new one. If there is data left in the old - // one, it will be saved in the buffer. - void Append(TraceBlobView tbv) { - uint64_t size_before = BytesAvailable(); - buffer_.insert(buffer_.end(), tbv_.data() + blob_offset_, - tbv_.data() + tbv_.size()); - tbv_ = std::move(tbv); - blob_offset_ = 0; - - // Post condition. Checks whether no data has been lost in the Append. - PERFETTO_DCHECK(BytesAvailable() == size_before + tbv.size()); - } - - // Reads the |obj| and updates |file_offset_| of the reader. - // NOTE: Assumes count of bytes available is higher than sizeof(T). - template - void Read(T& obj) { - Peek(obj); - Skip(); - } - - // Reads the T value for std::optional. - // NOTE: Assumes count of bytes available is higher than sizeof(T). - template - void ReadOptional(std::optional& obj) { - T val; - Read(val); - obj = val; - } - - // Reads all of the data in the |vec| and updates |file_offset_| of the - // reader. - // NOTE: Assumes count of bytes available is higher than sizeof(T). - template - void ReadVector(std::vector& vec) { - PERFETTO_DCHECK(CanReadSize(sizeof(T) * vec.size())); - for (T& val : vec) { - Read(val); - } - } - - // Updates the |file_offset_| by the sizeof(T). - // NOTE: Assumes count of bytes available is higher than sizeof(T). - template - void Skip() { - Skip(sizeof(T)); - } - - // Updates the |file_offset_| by the |bytes_to_skip|. - // NOTE: Assumes count of bytes available is higher than sizeof(T). - void Skip(uint64_t bytes_to_skip) { - uint64_t bytes_available_before = BytesAvailable(); - PERFETTO_DCHECK(CanReadSize(bytes_to_skip)); - size_t skip = static_cast(bytes_to_skip); - - // Incrementing file offset is not related to the way data is split. - file_offset_ += skip; - size_t bytes_in_buffer = BytesInBuffer(); - - // Empty buffer. Increment |blob_offset_|. - if (PERFETTO_LIKELY(bytes_in_buffer == 0)) { - buffer_offset_ = 0; - buffer_.clear(); - blob_offset_ += skip; - } else { - SkipSlow(skip); - } - PERFETTO_DCHECK(BytesAvailable() == bytes_available_before - skip); - } - - // Peeks the |obj| without updating the |file_offset_| of the reader. - // NOTE: Assumes count of bytes available is higher than sizeof(T). - template - void Peek(T& obj) const { - PERFETTO_DCHECK(CanReadSize(sizeof(T))); - size_t bytes_available_before = BytesAvailable(); - - // Read from blob. - if (PERFETTO_LIKELY(BytesInBuffer() == 0)) { - memcpy(&obj, tbv_.data() + blob_offset_, sizeof(T)); - } else { - PeekSlow(reinterpret_cast(&obj), sizeof(T)); - } - - PERFETTO_DCHECK(BytesAvailable() == bytes_available_before); - } - - // Creates TraceBlobView with data of |data_size| bytes from current offset. - // NOTE: Assumes count of bytes available is higher than sizeof(T). - TraceBlobView PeekTraceBlobView(uint64_t data_size) const { - PERFETTO_DCHECK(CanReadSize(data_size)); - size_t size = static_cast(data_size); - size_t bytes_in_buffer = BytesInBuffer(); - - // Data is in blob, so it's enough to slice the existing |tbv_|. - if (PERFETTO_LIKELY(bytes_in_buffer == 0)) { - return tbv_.slice(tbv_.data() + blob_offset_, size); - } - return PeekTraceBlobViewSlow(size); - } - - // Returns if there is enough data to read offsets between |start| and |end|. - bool CanAccessFileRange(uint64_t start, uint64_t end) const { - return CanAccessFileOffset(static_cast(start)) && - CanAccessFileOffset(static_cast(end)); - } - - // Returns if there is enough data to read |size| bytes. - bool CanReadSize(uint64_t size) const { return size <= BytesAvailable(); } - - uint64_t current_file_offset() const { return file_offset_; } - - private: - void SkipSlow(size_t bytes_to_skip); - - void PeekSlow(uint8_t* obj_data, size_t) const; - - TraceBlobView PeekTraceBlobViewSlow(size_t) const; - - size_t BytesInBuffer() const { - PERFETTO_DCHECK(buffer_.size() >= buffer_offset_); - return buffer_.size() - buffer_offset_; - } - size_t BytesInBlob() const { return tbv_.size() - blob_offset_; } - size_t BytesAvailable() const { return BytesInBuffer() + BytesInBlob(); } - - bool CanAccessFileOffset(size_t off) const { - return off >= file_offset_ && off <= file_offset_ + BytesAvailable(); - } - - TraceBlobView tbv_; - std::vector buffer_; - - // Where we are in relation to the current blob. - size_t blob_offset_ = 0; - // Where we are in relation to the file. - size_t file_offset_ = 0; - // Where we are in relation to the buffer. - size_t buffer_offset_ = 0; -}; -} // namespace perf_importer -} // namespace trace_processor -} // namespace perfetto - -#endif // SRC_TRACE_PROCESSOR_IMPORTERS_PERF_PERF_DATA_READER_H_ diff --git a/src/trace_processor/importers/perf/perf_data_reader_unittest.cc b/src/trace_processor/importers/perf/perf_data_reader_unittest.cc deleted file mode 100644 index 5bf30810d6..0000000000 --- a/src/trace_processor/importers/perf/perf_data_reader_unittest.cc +++ /dev/null @@ -1,234 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/trace_processor/importers/perf/perf_data_reader.h" - -#include - -#include "perfetto/base/build_config.h" -#include "test/gtest_and_gmock.h" - -namespace perfetto { -namespace trace_processor { -namespace perf_importer { - -namespace { -template -TraceBlobView TraceBlobViewFromVector(std::vector nums) { - size_t data_size = sizeof(T) * nums.size(); - auto blob = TraceBlob::Allocate(data_size); - memcpy(blob.data(), nums.data(), data_size); - return TraceBlobView(std::move(blob)); -} -} // namespace - -TEST(PerfDataReaderUnittest, AppendToEmpty) { - TraceBlobView tbv = TraceBlobViewFromVector(std::vector{1, 2, 3}); - PerfDataReader reader; - EXPECT_FALSE(reader.CanReadSize(1)); - reader.Append(std::move(tbv)); - EXPECT_TRUE(reader.CanReadSize(sizeof(uint64_t) * 2)); -} - -TEST(PerfDataReaderUnittest, Append) { - TraceBlobView tbv = TraceBlobViewFromVector(std::vector{1, 2, 3}); - PerfDataReader reader(std::move(tbv)); - - EXPECT_TRUE(reader.CanReadSize(sizeof(uint64_t) * 3)); - EXPECT_FALSE(reader.CanReadSize(sizeof(uint64_t) * 3 + 1)); - - reader.Append(TraceBlobViewFromVector(std::vector{1, 2})); - EXPECT_TRUE(reader.CanReadSize(sizeof(uint64_t) * 5)); -} - -TEST(PerfDataReaderUnittest, Read) { - TraceBlobView tbv = TraceBlobViewFromVector(std::vector{2, 4, 8}); - PerfDataReader reader(std::move(tbv)); - uint64_t val; - reader.Read(val); - EXPECT_EQ(val, 2u); -} - -TEST(PerfDataReaderUnittest, ReadFromBuffer) { - TraceBlobView tbv = TraceBlobViewFromVector(std::vector{2, 4, 6}); - PerfDataReader reader(std::move(tbv)); - reader.Append(TraceBlobViewFromVector(std::vector{1, 3})); - - // Now the first vector should be in the buffer. - uint64_t val; - reader.Read(val); - EXPECT_EQ(val, 2u); -} - -TEST(PerfDataReaderUnittest, ReadBetweenBufferAndBlob) { - TraceBlobView tbv = TraceBlobViewFromVector(std::vector{2, 4}); - PerfDataReader reader(std::move(tbv)); - reader.Append(TraceBlobViewFromVector(std::vector{1, 3, 5})); - - struct Nums { - uint64_t x; - uint64_t y; - uint64_t z; - }; - - Nums nums; - reader.Read(nums); - - EXPECT_EQ(nums.x, 2u); - EXPECT_EQ(nums.y, 4u); - EXPECT_EQ(nums.z, 1u); -} - -TEST(PerfDataReaderUnittest, ReadOptional) { - TraceBlobView tbv = TraceBlobViewFromVector(std::vector{2, 4, 8}); - PerfDataReader reader(std::move(tbv)); - std::optional val; - reader.ReadOptional(val); - EXPECT_EQ(val, 2u); -} - -TEST(PerfDataReaderUnittest, ReadVector) { - TraceBlobView tbv = - TraceBlobViewFromVector(std::vector{2, 4, 8, 16, 32}); - PerfDataReader reader(std::move(tbv)); - - std::vector res(3); - reader.ReadVector(res); - - std::vector valid{2, 4, 8}; - EXPECT_EQ(res, valid); -} - -TEST(PerfDataReaderUnittest, Skip) { - TraceBlobView tbv = TraceBlobViewFromVector(std::vector{2, 4, 8}); - PerfDataReader reader(std::move(tbv)); - - reader.Skip(); - - uint64_t val; - reader.Read(val); - EXPECT_EQ(val, 4u); -} - -TEST(PerfDataReaderUnittest, SkipInBuffer) { - TraceBlobView tbv = TraceBlobViewFromVector(std::vector{2, 4}); - PerfDataReader reader(std::move(tbv)); - reader.Append(TraceBlobViewFromVector(std::vector{1, 3, 5})); - - reader.Skip(); - EXPECT_EQ(reader.current_file_offset(), sizeof(uint64_t)); -} - -TEST(PerfDataReaderUnittest, SkipBetweenBufferAndBlob) { - TraceBlobView tbv = TraceBlobViewFromVector(std::vector{2, 4}); - PerfDataReader reader(std::move(tbv)); - reader.Append(TraceBlobViewFromVector(std::vector{1, 3, 5})); - - struct Nums { - uint64_t x; - uint64_t y; - uint64_t z; - }; - - reader.Skip(); - EXPECT_EQ(reader.current_file_offset(), sizeof(Nums)); -} - -TEST(PerfDataReaderUnittest, Peek) { - TraceBlobView tbv = TraceBlobViewFromVector(std::vector{2, 4, 8}); - PerfDataReader reader(std::move(tbv)); - - uint64_t peek_val; - reader.Peek(peek_val); - - uint64_t val; - reader.Read(val); - EXPECT_EQ(val, 2u); -} - -TEST(PerfDataReaderUnittest, PeekFromBuffer) { - TraceBlobView tbv = TraceBlobViewFromVector(std::vector{2, 4, 6}); - PerfDataReader reader(std::move(tbv)); - reader.Append(TraceBlobViewFromVector(std::vector{1, 3})); - - uint64_t val; - reader.Peek(val); - EXPECT_EQ(val, 2u); -} - -TEST(PerfDataReaderUnittest, PeekBetweenBufferAndBlob) { - TraceBlobView tbv = TraceBlobViewFromVector(std::vector{2, 4}); - PerfDataReader reader(std::move(tbv)); - reader.Append(TraceBlobViewFromVector(std::vector{1, 3, 5})); - - struct Nums { - uint64_t x; - uint64_t y; - uint64_t z; - }; - - Nums nums; - reader.Peek(nums); - - EXPECT_EQ(nums.x, 2u); - EXPECT_EQ(nums.y, 4u); - EXPECT_EQ(nums.z, 1u); -} - -TEST(PerfDataReaderUnittest, GetTraceBlobView) { - TraceBlobView tbv = TraceBlobViewFromVector(std::vector{2, 4, 8}); - PerfDataReader reader(std::move(tbv)); - EXPECT_TRUE(reader.CanReadSize(sizeof(uint64_t) * 3)); - - TraceBlobView new_tbv = reader.PeekTraceBlobView(sizeof(uint64_t) * 2); - PerfDataReader new_reader(std::move(new_tbv)); - EXPECT_TRUE(new_reader.CanReadSize(sizeof(uint64_t) * 2)); - EXPECT_FALSE(new_reader.CanReadSize(sizeof(uint64_t) * 3)); -} - -TEST(PerfDataReaderUnittest, GetTraceBlobViewFromBuffer) { - TraceBlobView tbv = TraceBlobViewFromVector(std::vector{2, 4}); - PerfDataReader reader(std::move(tbv)); - reader.Append(TraceBlobViewFromVector(std::vector{1, 3, 5})); - - TraceBlobView new_tbv = reader.PeekTraceBlobView(sizeof(uint64_t) * 2); - PerfDataReader new_reader(std::move(new_tbv)); - EXPECT_TRUE(new_reader.CanReadSize(sizeof(uint64_t) * 2)); - EXPECT_FALSE(new_reader.CanReadSize(sizeof(uint64_t) * 3)); -} - -TEST(PerfDataReaderUnittest, GetTraceBlobViewFromBetweenBufferAndBlob) { - TraceBlobView tbv = TraceBlobViewFromVector(std::vector{2, 4}); - PerfDataReader reader(std::move(tbv)); - reader.Append(TraceBlobViewFromVector(std::vector{1, 3, 5})); - - TraceBlobView new_tbv = reader.PeekTraceBlobView(sizeof(uint64_t) * 3); - PerfDataReader new_reader(std::move(new_tbv)); - EXPECT_TRUE(new_reader.CanReadSize(sizeof(uint64_t) * 3)); - EXPECT_FALSE(new_reader.CanReadSize(sizeof(uint64_t) * 4)); -} - -TEST(PerfDataReaderUnittest, CanAccessFileRange) { - TraceBlobView tbv = TraceBlobViewFromVector(std::vector{2, 4, 8}); - PerfDataReader reader(std::move(tbv)); - EXPECT_TRUE(reader.CanAccessFileRange(2, sizeof(uint64_t) * 3)); - EXPECT_FALSE(reader.CanAccessFileRange(2, sizeof(uint64_t) * 3 + 10)); -} - -} // namespace perf_importer - -} // namespace trace_processor -} // namespace perfetto diff --git a/src/trace_processor/importers/perf/perf_data_tokenizer.cc b/src/trace_processor/importers/perf/perf_data_tokenizer.cc index 25b54b9834..a5062e928c 100644 --- a/src/trace_processor/importers/perf/perf_data_tokenizer.cc +++ b/src/trace_processor/importers/perf/perf_data_tokenizer.cc @@ -16,56 +16,104 @@ #include "src/trace_processor/importers/perf/perf_data_tokenizer.h" +#include +#include +#include #include #include +#include +#include +#include +#include #include +#include "perfetto/base/flat_set.h" #include "perfetto/base/logging.h" #include "perfetto/base/status.h" #include "perfetto/ext/base/status_or.h" +#include "perfetto/public/compiler.h" +#include "perfetto/trace_processor/ref_counted.h" #include "perfetto/trace_processor/trace_blob_view.h" +#include "protos/perfetto/trace/clock_snapshot.pbzero.h" +#include "protos/third_party/simpleperf/record_file.pbzero.h" +#include "src/trace_processor/importers/common/clock_tracker.h" #include "src/trace_processor/importers/common/slice_tracker.h" -#include "src/trace_processor/importers/perf/perf_data_reader.h" -#include "src/trace_processor/importers/perf/perf_data_tracker.h" +#include "src/trace_processor/importers/perf/attrs_section_reader.h" +#include "src/trace_processor/importers/perf/dso_tracker.h" +#include "src/trace_processor/importers/perf/features.h" #include "src/trace_processor/importers/perf/perf_event.h" +#include "src/trace_processor/importers/perf/perf_event_attr.h" +#include "src/trace_processor/importers/perf/perf_file.h" +#include "src/trace_processor/importers/perf/perf_session.h" +#include "src/trace_processor/importers/perf/reader.h" +#include "src/trace_processor/importers/perf/record.h" +#include "src/trace_processor/importers/proto/perf_sample_tracker.h" #include "src/trace_processor/sorter/trace_sorter.h" #include "src/trace_processor/storage/stats.h" +#include "src/trace_processor/util/build_id.h" #include "src/trace_processor/util/status_macros.h" -#include "protos/perfetto/trace/profiling/profile_packet.pbzero.h" - -namespace perfetto { -namespace trace_processor { -namespace perf_importer { +namespace perfetto::trace_processor::perf_importer { namespace { -protos::pbzero::Profiling::CpuMode GetCpuMode(const perf_event_header& header) { - switch (header.misc & kPerfRecordMiscCpumodeMask) { - case PERF_RECORD_MISC_KERNEL: - return protos::pbzero::Profiling::MODE_KERNEL; - case PERF_RECORD_MISC_USER: - return protos::pbzero::Profiling::MODE_USER; - case PERF_RECORD_MISC_HYPERVISOR: - return protos::pbzero::Profiling::MODE_HYPERVISOR; - case PERF_RECORD_MISC_GUEST_KERNEL: - return protos::pbzero::Profiling::MODE_GUEST_KERNEL; - case PERF_RECORD_MISC_GUEST_USER: - return protos::pbzero::Profiling::MODE_GUEST_USER; - default: - return protos::pbzero::Profiling::MODE_UNKNOWN; + +void AddIds(uint8_t id_offset, + uint64_t flags, + base::FlatSet& feature_ids) { + for (size_t i = 0; i < sizeof(flags) * 8; ++i) { + if (flags & 1) { + feature_ids.insert(id_offset); + } + flags >>= 1; + ++id_offset; + } +} + +base::FlatSet ExtractFeatureIds(const uint64_t& flags, + const uint64_t (&flags1)[3]) { + base::FlatSet feature_ids; + AddIds(0, flags, feature_ids); + AddIds(64, flags1[0], feature_ids); + AddIds(128, flags1[1], feature_ids); + AddIds(192, flags1[2], feature_ids); + return feature_ids; +} + +bool ReadTime(const Record& record, std::optional& time) { + if (!record.attr) { + time = std::nullopt; + return true; + } + Reader reader(record.payload.copy()); + if (record.header.type != PERF_RECORD_SAMPLE) { + std::optional offset = record.attr->time_offset_from_end(); + if (!offset.has_value()) { + time = std::nullopt; + return true; + } + if (*offset > reader.size_left()) { + return false; + } + return reader.Skip(reader.size_left() - *offset) && + reader.ReadOptional(time); } + + std::optional offset = record.attr->time_offset_from_start(); + if (!offset.has_value()) { + time = std::nullopt; + return true; + } + return reader.Skip(*offset) && reader.ReadOptional(time); } + } // namespace PerfDataTokenizer::PerfDataTokenizer(TraceProcessorContext* ctx) - : context_(ctx), - tracker_(PerfDataTracker::GetOrCreate(context_)), - reader_() {} + : context_(ctx) {} PerfDataTokenizer::~PerfDataTokenizer() = default; // A normal perf.data consts of: // [ header ] -// [ event ids (one array per attr) ] // [ attr section ] // [ data section ] // [ optional feature sections ] @@ -75,225 +123,325 @@ PerfDataTokenizer::~PerfDataTokenizer() = default; // Most file format documentation is outdated or misleading, instead see // perf_session__do_write_header() in linux/tools/perf/util/header.c. base::Status PerfDataTokenizer::Parse(TraceBlobView blob) { - reader_.Append(std::move(blob)); + buffer_.PushBack(std::move(blob)); - while (parsing_state_ != ParsingState::Records) { - base::StatusOr parsed = ParsingResult::Success; + base::StatusOr result = ParsingResult::kSuccess; + while (result.ok() && result.value() == ParsingResult::kSuccess && + !buffer_.empty()) { switch (parsing_state_) { - case ParsingState::Records: + case ParsingState::kParseHeader: + result = ParseHeader(); break; - case ParsingState::Header: - parsed = ParseHeader(); + + case ParsingState::kParseAttrs: + result = ParseAttrs(); break; - case ParsingState::AfterHeaderBuffer: - parsed = ParseAfterHeaderBuffer(); + + case ParsingState::kSeekRecords: + result = SeekRecords(); break; - case ParsingState::Attrs: - parsed = ParseAttrs(); + + case ParsingState::kParseRecords: + result = ParseRecords(); break; - case ParsingState::AttrIdsFromBuffer: - parsed = ParseAttrIdsFromBuffer(); + + case ParsingState::kParseFeatures: + result = ParseFeatures(); break; - case ParsingState::AttrIds: - parsed = ParseAttrIds(); + + case ParsingState::kParseFeatureSections: + result = ParseFeatureSections(); break; + + case ParsingState::kDone: + result = base::ErrStatus("Unexpected data"); } + } + return result.status(); +} - // There has been an error while parsing. - RETURN_IF_ERROR(parsed.status()); +base::StatusOr +PerfDataTokenizer::ParseHeader() { + auto tbv = buffer_.SliceOff(0, sizeof(header_)); + if (!tbv) { + return ParsingResult::kMoreDataNeeded; + } + PERFETTO_CHECK(Reader(std::move(*tbv)).Read(header_)); - // There is not enough data to parse so we need to load another blob. - if (*parsed == ParsingResult::NoSpace) - return base::OkStatus(); + // TODO: Check for endianess (big endian will have letters reversed); + if (memcmp(header_.magic, PerfFile::kPerfMagic, + sizeof(PerfFile::kPerfMagic)) != 0) { + return base::ErrStatus("Invalid magic string"); } - while (reader_.current_file_offset() < header_.data.end()) { - // Make sure |perf_event_header| of the sample is available. - if (!reader_.CanReadSize(sizeof(perf_event_header))) { - return base::OkStatus(); - } + if (header_.size != sizeof(PerfFile::Header)) { + return base::ErrStatus( + "Failed to perf file header size. Expected %zu" + ", found %" PRIu64, + sizeof(PerfFile::Header), header_.size); + } - perf_event_header ev_header; - reader_.Peek(ev_header); - PERFETTO_CHECK(ev_header.size >= sizeof(perf_event_header)); + feature_ids_ = ExtractFeatureIds(header_.flags, header_.flags1); + feature_headers_section_ = {header_.data.end(), + feature_ids_.size() * sizeof(PerfFile::Section)}; + context_->clock_tracker->SetTraceTimeClock( + protos::pbzero::ClockSnapshot::Clock::MONOTONIC); - if (!reader_.CanReadSize(ev_header.size)) { - return base::OkStatus(); + PERFETTO_CHECK(buffer_.PopFrontUntil(sizeof(PerfFile::Header))); + parsing_state_ = ParsingState::kParseAttrs; + return ParsingResult::kSuccess; +} + +base::StatusOr +PerfDataTokenizer::ParseAttrs() { + std::optional tbv = + buffer_.SliceOff(header_.attrs.offset, header_.attrs.size); + if (!tbv) { + return ParsingResult::kMoreDataNeeded; + } + + ASSIGN_OR_RETURN(AttrsSectionReader attr_reader, + AttrsSectionReader::Create(header_, std::move(*tbv))); + + PerfSession::Builder builder(context_); + while (attr_reader.CanReadNext()) { + PerfFile::AttrsEntry entry; + RETURN_IF_ERROR(attr_reader.ReadNext(entry)); + + if (entry.ids.size % sizeof(uint64_t) != 0) { + return base::ErrStatus("Invalid id section size: %" PRIu64, + entry.ids.size); } - reader_.Skip(); - uint64_t record_offset = reader_.current_file_offset(); - uint64_t record_size = ev_header.size - sizeof(perf_event_header); - - switch (ev_header.type) { - case PERF_RECORD_SAMPLE: { - TraceBlobView tbv = reader_.PeekTraceBlobView(record_size); - auto sample_status = tracker_->ParseSample(reader_); - if (!sample_status.ok()) { - continue; - } - PerfDataTracker::PerfSample sample = *sample_status; - if (!ValidateSample(*sample_status)) { - continue; - } - context_->sorter->PushPerfRecord( - static_cast(*sample_status->ts), std::move(tbv)); - break; - } - case PERF_RECORD_MMAP2: { - PERFETTO_CHECK(ev_header.size >= - sizeof(PerfDataTracker::Mmap2Record::Numeric)); - auto record = ParseMmap2Record(record_size); - RETURN_IF_ERROR(record.status()); - record->cpu_mode = GetCpuMode(ev_header); - tracker_->PushMmap2Record(*record); - break; - } - default: - break; + tbv = buffer_.SliceOff(entry.ids.offset, entry.ids.size); + if (!tbv) { + return ParsingResult::kMoreDataNeeded; } - reader_.Skip((record_offset + record_size) - reader_.current_file_offset()); + std::vector ids(entry.ids.size / sizeof(uint64_t)); + PERFETTO_CHECK(Reader(std::move(*tbv)).ReadVector(ids)); + builder.AddAttrAndIds(entry.attr, std::move(ids)); } - return base::OkStatus(); + ASSIGN_OR_RETURN(perf_session_, builder.Build()); + parsing_state_ = ParsingState::kSeekRecords; + return ParsingResult::kSuccess; } base::StatusOr -PerfDataTokenizer::ParseHeader() { - if (!reader_.CanReadSize(sizeof(PerfHeader))) { - return ParsingResult::NoSpace; +PerfDataTokenizer::SeekRecords() { + if (!buffer_.PopFrontUntil(header_.data.offset)) { + return ParsingResult::kMoreDataNeeded; } - reader_.Read(header_); - PERFETTO_CHECK(header_.size == sizeof(PerfHeader)); - if (header_.attr_size != - sizeof(perf_event_attr) + sizeof(PerfDataTracker::PerfFileSection)) { - return base::ErrStatus( - "Unsupported: perf.data collected with a different ABI version of " - "perf_event_attr."); + parsing_state_ = ParsingState::kParseRecords; + return ParsingResult::kSuccess; +} + +base::StatusOr +PerfDataTokenizer::ParseRecords() { + while (buffer_.start_offset() < header_.data.end()) { + Record record; + + if (auto res = ParseRecord(record); + !res.ok() || *res != ParsingResult::kSuccess) { + return res; + } + + if (!PushRecord(std::move(record))) { + context_->storage->IncrementStats(stats::perf_record_skipped); + } } - if (header_.attrs.offset > header_.data.offset) { - return base::ErrStatus( - "Can only import files where samples are located after the metadata."); + parsing_state_ = ParsingState::kParseFeatureSections; + return ParsingResult::kSuccess; +} + +base::StatusOr PerfDataTokenizer::ParseRecord( + Record& record) { + record.session = perf_session_; + std::optional tbv = + buffer_.SliceOff(buffer_.start_offset(), sizeof(record.header)); + if (!tbv) { + return ParsingResult::kMoreDataNeeded; } + PERFETTO_CHECK(Reader(std::move(*tbv)).Read(record.header)); - if (header_.size == header_.attrs.offset) { - parsing_state_ = ParsingState::Attrs; - } else { - parsing_state_ = ParsingState::AfterHeaderBuffer; + if (record.header.size < sizeof(record.header)) { + return base::ErrStatus("Invalid record size: %" PRIu16, record.header.size); } - return ParsingResult::Success; + + tbv = buffer_.SliceOff(buffer_.start_offset() + sizeof(record.header), + record.header.size - sizeof(record.header)); + if (!tbv) { + return ParsingResult::kMoreDataNeeded; + } + + record.payload = std::move(*tbv); + + base::StatusOr> attr = + perf_session_->FindAttrForRecord(record.header, record.payload); + if (!attr.ok()) { + return base::ErrStatus("Unable to determine perf_event_attr for record. %s", + attr.status().c_message()); + } + record.attr = *attr; + + buffer_.PopFrontBytes(record.header.size); + return ParsingResult::kSuccess; } -base::StatusOr -PerfDataTokenizer::ParseAfterHeaderBuffer() { - if (!reader_.CanAccessFileRange(header_.size, header_.attrs.offset)) { - return ParsingResult::NoSpace; +base::StatusOr PerfDataTokenizer::ToTraceTimestamp( + std::optional time) { + base::StatusOr trace_ts = + time.has_value() + ? context_->clock_tracker->ToTraceTime( + protos::pbzero::ClockSnapshot::Clock::MONOTONIC, + static_cast(*time)) + : std::max(latest_timestamp_, context_->sorter->max_timestamp()); + + if (PERFETTO_LIKELY(trace_ts.ok())) { + latest_timestamp_ = std::max(latest_timestamp_, *trace_ts); } - after_header_buffer_.resize( - static_cast(header_.attrs.offset - header_.size)); - reader_.ReadVector(after_header_buffer_); - parsing_state_ = ParsingState::Attrs; - return ParsingResult::Success; + + return trace_ts; } -base::StatusOr -PerfDataTokenizer::ParseAttrs() { - if (!reader_.CanAccessFileRange(header_.attrs.offset, header_.attrs.end())) { - return ParsingResult::NoSpace; +bool PerfDataTokenizer::PushRecord(Record record) { + std::optional time; + if (!ReadTime(record, time)) { + return false; } - reader_.Skip(header_.attrs.offset - reader_.current_file_offset()); - PerfDataTracker::PerfFileAttr attr; - for (uint64_t i = header_.attrs.offset; i < header_.attrs.end(); - i += header_.attr_size) { - reader_.Read(attr); - PERFETTO_CHECK(attr.ids.size % sizeof(uint64_t) == 0); - ids_start_ = std::min(ids_start_, attr.ids.offset); - ids_end_ = std::max(ids_end_, attr.ids.end()); - attrs_.push_back(attr); + + base::StatusOr trace_ts = ToTraceTimestamp(time); + if (!trace_ts.ok()) { + return false; } - if (ids_start_ == header_.size && ids_end_ <= header_.attrs.offset) { - parsing_state_ = ParsingState::AttrIdsFromBuffer; - } else { - parsing_state_ = ParsingState::AttrIds; + switch (record.header.type) { + case PERF_RECORD_AUXTRACE_INFO: + case PERF_RECORD_AUXTRACE: + case PERF_RECORD_AUX: + break; + default: + context_->sorter->PushPerfRecord(*trace_ts, std::move(record)); + break; } - return ParsingResult::Success; + + return true; } base::StatusOr -PerfDataTokenizer::ParseAttrIds() { - if (!reader_.CanAccessFileRange(ids_start_, ids_end_)) { - return ParsingResult::NoSpace; +PerfDataTokenizer::ParseFeatureSections() { + PERFETTO_CHECK(buffer_.start_offset() == header_.data.end()); + auto tbv = buffer_.SliceOff(feature_headers_section_.offset, + feature_headers_section_.size); + if (!tbv) { + return ParsingResult::kMoreDataNeeded; } - for (const auto& attr_file : attrs_) { - reader_.Skip(attr_file.ids.offset - reader_.current_file_offset()); - std::vector ids(static_cast(attr_file.ids.size) / - sizeof(uint64_t)); - reader_.ReadVector(ids); - tracker_->PushAttrAndIds({attr_file.attr, std::move(ids)}); + + Reader reader(std::move(*tbv)); + for (auto feature_id : feature_ids_) { + feature_sections_.emplace_back(std::piecewise_construct, + std::forward_as_tuple(feature_id), + std::forward_as_tuple()); + PERFETTO_CHECK(reader.Read(feature_sections_.back().second)); } - tracker_->ComputeCommonSampleType(); - reader_.Skip(header_.data.offset - reader_.current_file_offset()); - parsing_state_ = ParsingState::Records; - return ParsingResult::Success; + std::sort(feature_sections_.begin(), feature_sections_.end(), + [](const std::pair& lhs, + const std::pair& rhs) { + return lhs.second.offset > rhs.second.offset; + }); + + buffer_.PopFrontUntil(feature_headers_section_.end()); + parsing_state_ = feature_sections_.empty() ? ParsingState::kDone + : ParsingState::kParseFeatures; + return ParsingResult::kSuccess; } base::StatusOr -PerfDataTokenizer::ParseAttrIdsFromBuffer() { - // Each attribute points at an array of event ids. In this case, the ids are - // in |after_header_buffer_|, i.e. the file contents between the header and - // the start of the attr section. - for (const auto& attr_file : attrs_) { - size_t num_ids = static_cast(attr_file.ids.size / sizeof(uint64_t)); - std::vector ids(num_ids); - size_t rd_offset = static_cast(attr_file.ids.offset - ids_start_); - size_t rd_size = static_cast(attr_file.ids.size); - PERFETTO_CHECK(rd_offset + rd_size <= after_header_buffer_.size()); - memcpy(ids.data(), after_header_buffer_.data() + rd_offset, rd_size); - - tracker_->PushAttrAndIds({attr_file.attr, std::move(ids)}); +PerfDataTokenizer::ParseFeatures() { + while (!feature_sections_.empty()) { + const auto feature_id = feature_sections_.back().first; + const auto& section = feature_sections_.back().second; + auto tbv = buffer_.SliceOff(section.offset, section.size); + if (!tbv) { + return ParsingResult::kMoreDataNeeded; + } + + RETURN_IF_ERROR(ParseFeature(feature_id, std::move(*tbv))); + buffer_.PopFrontUntil(section.end()); + feature_sections_.pop_back(); } - after_header_buffer_.clear(); - tracker_->ComputeCommonSampleType(); - reader_.Skip(header_.data.offset - reader_.current_file_offset()); - parsing_state_ = ParsingState::Records; - return ParsingResult::Success; + parsing_state_ = ParsingState::kDone; + return ParsingResult::kSuccess; } -base::StatusOr -PerfDataTokenizer::ParseMmap2Record(uint64_t record_size) { - uint64_t start_offset = reader_.current_file_offset(); - PerfDataTracker::Mmap2Record record; - reader_.Read(record.num); - std::vector filename_buffer( - static_cast(record_size) - - sizeof(PerfDataTracker::Mmap2Record::Numeric)); - reader_.ReadVector(filename_buffer); - if (filename_buffer.back() != '\0') { - return base::ErrStatus( - "Invalid MMAP2 record: filename is not null terminated."); - } - record.filename = std::string(filename_buffer.begin(), filename_buffer.end()); - PERFETTO_CHECK(reader_.current_file_offset() == start_offset + record_size); - return record; -} +base::Status PerfDataTokenizer::ParseFeature(uint8_t feature_id, + TraceBlobView data) { + switch (feature_id) { + case feature::ID_CMD_LINE: { + ASSIGN_OR_RETURN(std::vector args, + feature::ParseCmdline(std::move(data))); + perf_session_->SetCmdline(args); + return base::OkStatus(); + } -bool PerfDataTokenizer::ValidateSample( - const PerfDataTracker::PerfSample& sample) { - if (!sample.cpu.has_value() || !sample.ts.has_value() || - sample.callchain.empty() || !sample.pid.has_value()) { - context_->storage->IncrementStats(stats::perf_samples_skipped); - return false; + case feature::ID_EVENT_DESC: + return feature::EventDescription::Parse( + std::move(data), [&](feature::EventDescription desc) { + for (auto id : desc.ids) { + perf_session_->SetEventName(id, std::move(desc.event_string)); + } + return base::OkStatus(); + }); + + case feature::ID_BUILD_ID: + return feature::BuildId::Parse( + std::move(data), [&](feature::BuildId build_id) { + perf_session_->AddBuildId( + build_id.pid, std::move(build_id.filename), + BuildId::FromRaw(std::move(build_id.build_id))); + return base::OkStatus(); + }); + + case feature::ID_GROUP_DESC: { + feature::HeaderGroupDesc group_desc; + RETURN_IF_ERROR( + feature::HeaderGroupDesc::Parse(std::move(data), group_desc)); + // TODO(carlscab): Do someting + break; + } + + case feature::ID_SIMPLEPERF_META_INFO: { + perf_session_->SetIsSimpleperf(); + feature::SimpleperfMetaInfo meta_info; + RETURN_IF_ERROR(feature::SimpleperfMetaInfo::Parse(data, meta_info)); + for (auto it = meta_info.event_type_info.GetIterator(); it; ++it) { + perf_session_->SetEventName(it.key().type, it.key().config, it.value()); + } + break; + } + case feature::ID_SIMPLEPERF_FILE2: { + perf_session_->SetIsSimpleperf(); + RETURN_IF_ERROR(feature::ParseSimpleperfFile2( + std::move(data), [&](TraceBlobView blob) { + third_party::simpleperf::proto::pbzero::FileFeature::Decoder file( + blob.data(), blob.length()); + DsoTracker::GetOrCreate(context_).AddSimpleperfFile2(file); + })); + + break; + } + default: + context_->storage->IncrementIndexedStats(stats::perf_features_skipped, + feature_id); } - return true; + + return base::OkStatus(); } void PerfDataTokenizer::NotifyEndOfFile() {} -} // namespace perf_importer -} // namespace trace_processor -} // namespace perfetto +} // namespace perfetto::trace_processor::perf_importer diff --git a/src/trace_processor/importers/perf/perf_data_tokenizer.h b/src/trace_processor/importers/perf/perf_data_tokenizer.h index 7a54088c86..f02efba7e8 100644 --- a/src/trace_processor/importers/perf/perf_data_tokenizer.h +++ b/src/trace_processor/importers/perf/perf_data_tokenizer.h @@ -18,45 +18,30 @@ #define SRC_TRACE_PROCESSOR_IMPORTERS_PERF_PERF_DATA_TOKENIZER_H_ #include +#include +#include +#include + +#include "perfetto/base/flat_set.h" #include "perfetto/base/status.h" #include "perfetto/ext/base/status_or.h" -#include "perfetto/ext/base/string_utils.h" +#include "perfetto/trace_processor/ref_counted.h" #include "perfetto/trace_processor/trace_blob_view.h" -#include "src/trace_processor/importers/perf/perf_data_reader.h" -#include "src/trace_processor/importers/perf/perf_data_tracker.h" -#include "src/trace_processor/importers/perf/perf_event.h" - -#include -#include -#include -#include - #include "src/trace_processor/importers/common/chunked_trace_reader.h" +#include "src/trace_processor/importers/perf/perf_file.h" +#include "src/trace_processor/importers/perf/perf_session.h" +#include "src/trace_processor/util/trace_blob_view_reader.h" namespace perfetto { namespace trace_processor { +class TraceProcessorContext; + namespace perf_importer { -using Section = PerfDataTracker::PerfFileSection; +struct Record; class PerfDataTokenizer : public ChunkedTraceReader { public: - struct PerfHeader { - static constexpr char PERF_MAGIC[] = "PERFILE2"; - - char magic[8]; - uint64_t size; - // Size of PerfFileAttr struct and section pointing to ids. - uint64_t attr_size; - Section attrs; - Section data; - Section event_types; - uint64_t flags; - uint64_t flags1[3]; - - uint64_t num_attrs() const { return attrs.size / attr_size; } - }; - explicit PerfDataTokenizer(TraceProcessorContext*); ~PerfDataTokenizer() override; PerfDataTokenizer(const PerfDataTokenizer&) = delete; @@ -68,39 +53,46 @@ class PerfDataTokenizer : public ChunkedTraceReader { private: enum class ParsingState { - Header = 0, - AfterHeaderBuffer = 1, - Attrs = 2, - AttrIds = 3, - AttrIdsFromBuffer = 4, - Records = 5 + kParseHeader, + kParseAttrs, + kSeekRecords, + kParseRecords, + kParseFeatureSections, + kParseFeatures, + kDone, }; - enum class ParsingResult { NoSpace = 0, Success = 1 }; + enum class ParsingResult { kMoreDataNeeded = 0, kSuccess = 1 }; base::StatusOr ParseHeader(); - base::StatusOr ParseAfterHeaderBuffer(); base::StatusOr ParseAttrs(); - base::StatusOr ParseAttrIds(); - base::StatusOr ParseAttrIdsFromBuffer(); + base::StatusOr SeekRecords(); + base::StatusOr ParseRecords(); + base::StatusOr ParseFeatureSections(); + base::StatusOr ParseFeatures(); - base::StatusOr ParseMmap2Record( - uint64_t record_size); + base::StatusOr ParseRecord(Record& record); + bool PushRecord(Record record); + base::Status ParseFeature(uint8_t feature_id, TraceBlobView payload); - bool ValidateSample(const PerfDataTracker::PerfSample&); + base::StatusOr ToTraceTimestamp(std::optional time); TraceProcessorContext* context_; - PerfDataTracker* tracker_; - ParsingState parsing_state_ = ParsingState::Header; + ParsingState parsing_state_ = ParsingState::kParseHeader; + + PerfFile::Header header_; + base::FlatSet feature_ids_; + PerfFile::Section feature_headers_section_; + // Sections for the features present in the perf file sorted by descending + // section offset. This is done so that we can pop from the back as we process + // the sections. + std::vector> feature_sections_; - PerfHeader header_; + RefPtr perf_session_; - std::vector attrs_; - uint64_t ids_start_ = std::numeric_limits::max(); - uint64_t ids_end_ = 0; - std::vector after_header_buffer_; + util::TraceBlobViewReader buffer_; - perf_importer::PerfDataReader reader_; + int64_t latest_timestamp_ = 0; }; } // namespace perf_importer diff --git a/src/trace_processor/importers/perf/perf_data_tracker.cc b/src/trace_processor/importers/perf/perf_data_tracker.cc deleted file mode 100644 index 8b3bc470ac..0000000000 --- a/src/trace_processor/importers/perf/perf_data_tracker.cc +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/trace_processor/importers/perf/perf_data_tracker.h" - -#include - -#include "perfetto/base/status.h" -#include "src/trace_processor/importers/common/address_range.h" -#include "src/trace_processor/importers/common/mapping_tracker.h" -#include "src/trace_processor/importers/common/process_tracker.h" -#include "src/trace_processor/storage/stats.h" -#include "src/trace_processor/storage/trace_storage.h" - -#include "protos/perfetto/trace/profiling/profile_packet.pbzero.h" - -namespace perfetto { -namespace trace_processor { -namespace perf_importer { -namespace { - -bool IsInKernel(protos::pbzero::Profiling::CpuMode cpu_mode) { - switch (cpu_mode) { - case protos::pbzero::Profiling::MODE_UNKNOWN: - PERFETTO_CHECK(false); - case protos::pbzero::Profiling::MODE_GUEST_KERNEL: - case protos::pbzero::Profiling::MODE_KERNEL: - return true; - case protos::pbzero::Profiling::MODE_USER: - case protos::pbzero::Profiling::MODE_HYPERVISOR: - case protos::pbzero::Profiling::MODE_GUEST_USER: - return false; - } - PERFETTO_CHECK(false); -} - -CreateMappingParams BuildCreateMappingParams( - PerfDataTracker::Mmap2Record record) { - return {AddressRange::FromStartAndSize(record.num.addr, record.num.len), - record.num.pgoff, - // start_offset: This is the offset into the file where the ELF header - // starts. We assume all file mappings are ELF files an thus this - // offset is 0. - 0, - // load_bias: This can only be read out of the actual ELF file, which - // we do not have here, so we set it to 0. When symbolizing we will - // hopefully have the real load bias and we can compensate there for a - // possible mismatch. - 0, record.filename, std::nullopt}; -} -} // namespace - -PerfDataTracker::~PerfDataTracker() = default; - -uint64_t PerfDataTracker::ComputeCommonSampleType() { - if (attrs_.empty()) { - return 0; - } - common_sample_type_ = std::numeric_limits::max(); - for (const auto& a : attrs_) { - common_sample_type_ &= a.attr.sample_type; - } - return common_sample_type_; -} - -const perf_event_attr* PerfDataTracker::FindAttrWithId(uint64_t id) const { - for (const auto& attr_and_ids : attrs_) { - if (auto x = - std::find(attr_and_ids.ids.begin(), attr_and_ids.ids.end(), id); - x == attr_and_ids.ids.end()) { - continue; - } - return &attr_and_ids.attr; - } - return nullptr; -} - -void PerfDataTracker::PushMmap2Record(Mmap2Record record) { - if (IsInKernel(record.cpu_mode)) { - context_->mapping_tracker->CreateKernelMemoryMapping( - BuildCreateMappingParams(std::move(record))); - } else { - UniquePid upid = - context_->process_tracker->GetOrCreateProcess(record.num.pid); - context_->mapping_tracker->CreateUserMemoryMapping( - upid, BuildCreateMappingParams(std::move(record))); - } -} - -base::StatusOr PerfDataTracker::ParseSample( - perfetto::trace_processor::perf_importer::PerfDataReader& reader) { - uint64_t sample_type = common_sample_type(); - PerfDataTracker::PerfSample sample; - - if (sample_type & PERF_SAMPLE_IDENTIFIER) { - reader.ReadOptional(sample.id); - if (auto attr = FindAttrWithId(*sample.id); attr) { - sample_type = attr->sample_type; - } else { - return base::ErrStatus("No attr for sample_id"); - } - } - - if (sample_type & PERF_SAMPLE_IP) { - reader.Skip(); - } - - if (sample_type & PERF_SAMPLE_TID) { - reader.ReadOptional(sample.pid); - reader.ReadOptional(sample.tid); - } - - if (sample_type & PERF_SAMPLE_TIME) { - reader.ReadOptional(sample.ts); - } - - // Ignored. Checked because we need to access later parts of sample. - if (sample_type & PERF_SAMPLE_ADDR) { - reader.Skip(); - } - - // The same value as PERF_SAMPLE_IDENTIFIER, so should be ignored. - if (sample_type & PERF_SAMPLE_ID) { - reader.Skip(); - } - - // Ignored. Checked because we need to access later parts of sample. - if (sample_type & PERF_SAMPLE_STREAM_ID) { - reader.Skip(); - } - - if (sample_type & PERF_SAMPLE_CPU) { - reader.ReadOptional(sample.cpu); - // Ignore next uint32_t res. - reader.Skip(); - } - - // Ignored. Checked because we need to access later parts of sample. - if (sample_type & PERF_SAMPLE_PERIOD) { - reader.Skip(); - } - - // Ignored. - // TODO(mayzner): Implement. - if (sample_type & PERF_SAMPLE_READ) { - context_->storage->IncrementStats(stats::perf_samples_skipped); - return base::ErrStatus("PERF_SAMPLE_READ is not supported"); - } - - if (sample_type & PERF_SAMPLE_CALLCHAIN) { - uint64_t vec_size; - reader.Read(vec_size); - - sample.callchain.resize(static_cast(vec_size)); - reader.ReadVector(sample.callchain); - } - - return sample; -} - -PerfDataTracker* PerfDataTracker::GetOrCreate(TraceProcessorContext* context) { - if (!context->perf_data_tracker) { - context->perf_data_tracker.reset(new PerfDataTracker(context)); - } - return static_cast(context->perf_data_tracker.get()); -} -} // namespace perf_importer -} // namespace trace_processor -} // namespace perfetto diff --git a/src/trace_processor/importers/perf/perf_data_tracker.h b/src/trace_processor/importers/perf/perf_data_tracker.h deleted file mode 100644 index c96b08d398..0000000000 --- a/src/trace_processor/importers/perf/perf_data_tracker.h +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PERF_PERF_DATA_TRACKER_H_ -#define SRC_TRACE_PROCESSOR_IMPORTERS_PERF_PERF_DATA_TRACKER_H_ - -#include -#include -#include -#include "perfetto/base/logging.h" -#include "perfetto/base/status.h" -#include "perfetto/ext/base/flat_hash_map.h" -#include "perfetto/ext/base/status_or.h" -#include "perfetto/ext/base/string_utils.h" -#include "protos/perfetto/trace/profiling/profile_packet.pbzero.h" -#include "src/trace_processor/importers/perf/perf_data_reader.h" -#include "src/trace_processor/importers/perf/perf_event.h" -#include "src/trace_processor/storage/trace_storage.h" -#include "src/trace_processor/tables/profiler_tables_py.h" -#include "src/trace_processor/types/destructible.h" -#include "src/trace_processor/types/trace_processor_context.h" - -namespace perfetto { -namespace trace_processor { -namespace perf_importer { - -using MappingTable = tables::StackProfileMappingTable; - -class PerfDataTracker : public Destructible { - public: - struct PerfFileSection { - uint64_t offset; - uint64_t size; - - uint64_t end() const { return offset + size; } - }; - struct PerfFileAttr { - perf_event_attr attr; - PerfFileSection ids; - }; - struct AttrAndIds { - perf_event_attr attr; - std::vector ids; - }; - struct PerfSample { - std::optional id = 0; - std::optional pid = 0; - std::optional tid = 0; - std::optional ts = 0; - std::optional cpu = 0; - std::vector callchain; - }; - struct Mmap2Record { - struct Numeric { - uint32_t pid; - uint32_t tid; - uint64_t addr; - uint64_t len; - uint64_t pgoff; - uint32_t maj; - uint32_t min; - uint64_t ino; - uint64_t ino_generation; - uint32_t prot; - uint32_t flags; - }; - protos::pbzero::Profiling::CpuMode cpu_mode; - Numeric num; - std::string filename; - }; - - PerfDataTracker(const PerfDataTracker&) = delete; - PerfDataTracker& operator=(const PerfDataTracker&) = delete; - explicit PerfDataTracker(TraceProcessorContext* context) - : context_(context) {} - ~PerfDataTracker() override; - static PerfDataTracker* GetOrCreate(TraceProcessorContext* context); - - uint64_t ComputeCommonSampleType(); - - void PushAttrAndIds(AttrAndIds data) { attrs_.push_back(std::move(data)); } - - void PushMmap2Record(Mmap2Record record); - - uint64_t common_sample_type() { return common_sample_type_; } - - base::StatusOr ParseSample( - perfetto::trace_processor::perf_importer::PerfDataReader&); - - private: - const perf_event_attr* FindAttrWithId(uint64_t id) const; - TraceProcessorContext* context_; - std::vector attrs_; - - uint64_t common_sample_type_; -}; -} // namespace perf_importer -} // namespace trace_processor -} // namespace perfetto - -#endif // SRC_TRACE_PROCESSOR_IMPORTERS_PERF_PERF_DATA_TRACKER_H_ diff --git a/src/trace_processor/importers/perf/perf_data_tracker_unittest.cc b/src/trace_processor/importers/perf/perf_data_tracker_unittest.cc deleted file mode 100644 index 923473f3a9..0000000000 --- a/src/trace_processor/importers/perf/perf_data_tracker_unittest.cc +++ /dev/null @@ -1,243 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/trace_processor/importers/perf/perf_data_tracker.h" - -#include -#include -#include -#include - -#include "perfetto/base/build_config.h" -#include "protos/perfetto/trace/profiling/profile_packet.pbzero.h" -#include "src/trace_processor/importers/common/address_range.h" -#include "src/trace_processor/importers/common/mapping_tracker.h" -#include "src/trace_processor/importers/common/process_tracker.h" -#include "src/trace_processor/importers/common/stack_profile_tracker.h" -#include "src/trace_processor/importers/perf/perf_event.h" -#include "test/gtest_and_gmock.h" - -namespace perfetto { -namespace trace_processor { -namespace perf_importer { -namespace { - -class PerfDataTrackerUnittest : public testing::Test { - public: - PerfDataTrackerUnittest() { - context_.storage = std::make_unique(); - context_.process_tracker = std::make_unique(&context_); - context_.stack_profile_tracker = - std::make_unique(&context_); - context_.mapping_tracker = std::make_unique(&context_); - } - - protected: - TraceProcessorContext context_; -}; - -TEST_F(PerfDataTrackerUnittest, ComputeCommonSampleType) { - PerfDataTracker* tracker = PerfDataTracker::GetOrCreate(&context_); - - PerfDataTracker::AttrAndIds attr_and_ids; - attr_and_ids.attr.sample_type = - PERF_SAMPLE_CALLCHAIN | PERF_SAMPLE_CPU | PERF_SAMPLE_TIME; - tracker->PushAttrAndIds(attr_and_ids); - - attr_and_ids.attr.sample_type = PERF_SAMPLE_ADDR | PERF_SAMPLE_CPU; - tracker->PushAttrAndIds(attr_and_ids); - - tracker->ComputeCommonSampleType(); - EXPECT_TRUE(tracker->common_sample_type() & PERF_SAMPLE_CPU); - EXPECT_FALSE(tracker->common_sample_type() & PERF_SAMPLE_CALLCHAIN); -} - -TEST_F(PerfDataTrackerUnittest, FindMapping) { - PerfDataTracker* tracker = PerfDataTracker::GetOrCreate(&context_); - - PerfDataTracker::Mmap2Record rec; - rec.cpu_mode = protos::pbzero::Profiling::MODE_USER; - rec.filename = "file1"; - rec.num.addr = 1000; - rec.num.len = 100; - rec.num.pid = 1; - rec.cpu_mode = protos::pbzero::Profiling::MODE_USER; - tracker->PushMmap2Record(rec); - - rec.num.addr = 2000; - tracker->PushMmap2Record(rec); - - rec.num.addr = 3000; - tracker->PushMmap2Record(rec); - - UserMemoryMapping* mapping = - context_.mapping_tracker->FindUserMappingForAddress( - context_.process_tracker->GetOrCreateProcess(1), 2050); - ASSERT_NE(mapping, nullptr); - EXPECT_EQ(mapping->memory_range().start(), 2000u); - EXPECT_EQ(mapping->memory_range().end(), 2100u); -} - -TEST_F(PerfDataTrackerUnittest, FindMappingFalse) { - PerfDataTracker* tracker = PerfDataTracker::GetOrCreate(&context_); - - PerfDataTracker::Mmap2Record rec; - rec.cpu_mode = protos::pbzero::Profiling::MODE_USER; - rec.filename = "file1"; - rec.num.addr = 1000; - rec.num.len = 100; - rec.num.pid = 1; - rec.cpu_mode = protos::pbzero::Profiling::MODE_USER; - tracker->PushMmap2Record(rec); - - UserMemoryMapping* mapping = - context_.mapping_tracker->FindUserMappingForAddress( - context_.process_tracker->GetOrCreateProcess(2), 2050); - EXPECT_EQ(mapping, nullptr); -} - -TEST_F(PerfDataTrackerUnittest, ParseSampleTrivial) { - PerfDataTracker* tracker = PerfDataTracker::GetOrCreate(&context_); - - PerfDataTracker::AttrAndIds attr_and_ids; - attr_and_ids.attr.sample_type = PERF_SAMPLE_TIME; - tracker->PushAttrAndIds(attr_and_ids); - tracker->ComputeCommonSampleType(); - - uint64_t ts = 100; - - TraceBlob blob = - TraceBlob::CopyFrom(static_cast(&ts), sizeof(uint64_t)); - PerfDataReader reader(TraceBlobView(std::move(blob))); - - auto parsed_sample = tracker->ParseSample(reader); - EXPECT_TRUE(parsed_sample.ok()); - EXPECT_EQ(parsed_sample->ts, 100u); -} - -TEST_F(PerfDataTrackerUnittest, ParseSampleCallchain) { - PerfDataTracker* tracker = PerfDataTracker::GetOrCreate(&context_); - - PerfDataTracker::AttrAndIds attr_and_ids; - attr_and_ids.attr.sample_type = PERF_SAMPLE_CALLCHAIN; - tracker->PushAttrAndIds(attr_and_ids); - tracker->ComputeCommonSampleType(); - - struct Sample { - uint64_t callchain_size; /* if PERF_SAMPLE_CALLCHAIN */ - std::vector callchain; /* if PERF_SAMPLE_CALLCHAIN */ - }; - - Sample sample; - sample.callchain_size = 3; - sample.callchain = std::vector{1, 2, 3}; - - TraceBlob blob = TraceBlob::Allocate(4 * sizeof(uint64_t)); - memcpy(blob.data(), &sample.callchain_size, sizeof(uint64_t)); - memcpy(blob.data() + sizeof(uint64_t), sample.callchain.data(), - sizeof(uint64_t) * 3); - PerfDataReader reader(TraceBlobView(std::move(blob))); - - auto parsed_sample = tracker->ParseSample(reader); - EXPECT_TRUE(parsed_sample.ok()); - EXPECT_EQ(parsed_sample->callchain.size(), 3u); -} - -TEST_F(PerfDataTrackerUnittest, ParseSampleWithoutId) { - PerfDataTracker* tracker = PerfDataTracker::GetOrCreate(&context_); - - PerfDataTracker::AttrAndIds attr_and_ids; - attr_and_ids.attr.sample_type = PERF_SAMPLE_TID | PERF_SAMPLE_TIME | - PERF_SAMPLE_CPU | PERF_SAMPLE_CALLCHAIN; - tracker->PushAttrAndIds(attr_and_ids); - tracker->ComputeCommonSampleType(); - - struct Sample { - uint32_t pid; /* if PERF_SAMPLE_TID */ - uint32_t tid; /* if PERF_SAMPLE_TID */ - uint64_t ts; /* if PERF_SAMPLE_TIME */ - uint32_t cpu; /* if PERF_SAMPLE_CPU */ - uint32_t res_ignore; /* if PERF_SAMPLE_CPU */ - uint64_t callchain_size; /* if PERF_SAMPLE_CALLCHAIN */ - }; - - Sample sample; - sample.pid = 2; - sample.ts = 100; - sample.cpu = 1; - sample.callchain_size = 3; - std::vector callchain{1, 2, 3}; - - TraceBlob blob = TraceBlob::Allocate(sizeof(Sample) + sizeof(uint64_t) * 3); - memcpy(blob.data(), &sample, sizeof(Sample)); - memcpy(blob.data() + sizeof(Sample), callchain.data(), sizeof(uint64_t) * 3); - - PerfDataReader reader(TraceBlobView(std::move(blob))); - EXPECT_TRUE(reader.CanReadSize(sizeof(Sample))); - - auto parsed_sample = tracker->ParseSample(reader); - EXPECT_TRUE(parsed_sample.ok()); - EXPECT_EQ(parsed_sample->callchain.size(), 3u); - EXPECT_EQ(sample.ts, parsed_sample->ts); -} - -TEST_F(PerfDataTrackerUnittest, ParseSampleWithId) { - PerfDataTracker* tracker = PerfDataTracker::GetOrCreate(&context_); - - PerfDataTracker::AttrAndIds attr_and_ids; - attr_and_ids.attr.sample_type = PERF_SAMPLE_CPU | PERF_SAMPLE_TID | - PERF_SAMPLE_IDENTIFIER | PERF_SAMPLE_ID | - PERF_SAMPLE_CALLCHAIN | PERF_SAMPLE_TIME; - attr_and_ids.ids.push_back(10); - tracker->PushAttrAndIds(attr_and_ids); - tracker->ComputeCommonSampleType(); - - struct Sample { - uint64_t identifier; /* if PERF_SAMPLE_IDENTIFIER */ - uint32_t pid; /* if PERF_SAMPLE_TID */ - uint32_t tid; /* if PERF_SAMPLE_TID */ - uint64_t ts; /* if PERF_SAMPLE_TIME */ - uint64_t id; /* if PERF_SAMPLE_ID */ - uint32_t cpu; /* if PERF_SAMPLE_CPU */ - uint32_t res_ignore; /* if PERF_SAMPLE_CPU */ - uint64_t callchain_size; /* if PERF_SAMPLE_CALLCHAIN */ - }; - - Sample sample; - sample.id = 10; - sample.identifier = 10; - sample.cpu = 1; - sample.pid = 2; - sample.ts = 100; - sample.callchain_size = 3; - std::vector callchain{1, 2, 3}; - - TraceBlob blob = TraceBlob::Allocate(sizeof(Sample) + sizeof(uint64_t) * 3); - memcpy(blob.data(), &sample, sizeof(Sample)); - memcpy(blob.data() + sizeof(Sample), callchain.data(), sizeof(uint64_t) * 3); - - PerfDataReader reader(TraceBlobView(std::move(blob))); - - auto parsed_sample = tracker->ParseSample(reader); - EXPECT_TRUE(parsed_sample.ok()); - EXPECT_EQ(parsed_sample->callchain.size(), 3u); - EXPECT_EQ(100u, parsed_sample->ts); -} - -} // namespace -} // namespace perf_importer -} // namespace trace_processor -} // namespace perfetto diff --git a/src/trace_processor/importers/perf/perf_event.h b/src/trace_processor/importers/perf/perf_event.h index 4763e23f71..58077be2e3 100644 --- a/src/trace_processor/importers/perf/perf_event.h +++ b/src/trace_processor/importers/perf/perf_event.h @@ -238,6 +238,7 @@ enum perf_record_misc { PERF_RECORD_MISC_GUEST_USER = 5, PERF_RECORD_MISC_MMAP_BUILD_ID = 1U << 14, + PERF_RECORD_MISC_EXT_RESERVED = 1U << 15, }; enum perf_event_read_format { @@ -250,7 +251,7 @@ enum perf_event_read_format { PERF_FORMAT_MAX = 1U << 5, /* non-ABI */ }; -enum perf_callchain_context { +enum perf_callchain_context : uint64_t { PERF_CONTEXT_HV = static_cast(-32), PERF_CONTEXT_KERNEL = static_cast(-128), PERF_CONTEXT_USER = static_cast(-512), diff --git a/src/trace_processor/importers/perf/perf_event_attr.cc b/src/trace_processor/importers/perf/perf_event_attr.cc index 1abf8d0aa3..86c8d23753 100644 --- a/src/trace_processor/importers/perf/perf_event_attr.cc +++ b/src/trace_processor/importers/perf/perf_event_attr.cc @@ -21,7 +21,12 @@ #include #include +#include "perfetto/ext/base/string_view.h" +#include "src/trace_processor/importers/perf/perf_counter.h" #include "src/trace_processor/importers/perf/perf_event.h" +#include "src/trace_processor/storage/trace_storage.h" +#include "src/trace_processor/tables/profiler_tables_py.h" +#include "src/trace_processor/types/trace_processor_context.h" namespace perfetto::trace_processor::perf_importer { @@ -90,11 +95,41 @@ std::optional IdOffsetFromEndOfNonSampleRecord( } } // namespace -PerfEventAttr::PerfEventAttr(perf_event_attr attr) - : attr_(std::move(attr)), +PerfEventAttr::PerfEventAttr(TraceProcessorContext* context, + tables::PerfSessionTable::Id perf_session_id, + perf_event_attr attr) + : context_(context), + perf_session_id_(perf_session_id), + attr_(attr), time_offset_from_start_(TimeOffsetFromStartOfSampleRecord(attr_)), time_offset_from_end_(TimeOffsetFromEndOfNonSampleRecord(attr_)), id_offset_from_start_(IdOffsetFromStartOfSampleRecord(attr_)), id_offset_from_end_(IdOffsetFromEndOfNonSampleRecord(attr_)) {} +PerfEventAttr::~PerfEventAttr() = default; + +PerfCounter& PerfEventAttr::GetOrCreateCounter(uint32_t cpu) { + auto it = counters_.find(cpu); + if (it == counters_.end()) { + it = counters_.emplace(cpu, CreateCounter(cpu)).first; + } + return it->second; +} + +PerfCounter PerfEventAttr::CreateCounter(uint32_t cpu) const { + tables::PerfCounterTrackTable::Row row; + row.name = context_->storage->InternString(base::StringView(event_name_)); + row.unit = context_->storage->InternString(base::StringView("")); + row.description = context_->storage->InternString(base::StringView("")); + row.perf_session_id = perf_session_id_; + row.cpu = cpu; + row.is_timebase = is_timebase(); + const auto counter_track_ref = + context_->storage->mutable_perf_counter_track_table() + ->Insert(row) + .row_reference; + return PerfCounter(context_->storage->mutable_counter_table(), + counter_track_ref); +} + } // namespace perfetto::trace_processor::perf_importer diff --git a/src/trace_processor/importers/perf/perf_event_attr.h b/src/trace_processor/importers/perf/perf_event_attr.h index 4f219aadf7..716f33a05b 100644 --- a/src/trace_processor/importers/perf/perf_event_attr.h +++ b/src/trace_processor/importers/perf/perf_event_attr.h @@ -21,17 +21,30 @@ #include #include #include +#include +#include +#include -#include "perfetto/ext/base/string_view.h" #include "perfetto/trace_processor/ref_counted.h" +#include "src/trace_processor/importers/perf/perf_counter.h" #include "src/trace_processor/importers/perf/perf_event.h" +#include "src/trace_processor/tables/profiler_tables_py.h" -namespace perfetto::trace_processor::perf_importer { +namespace perfetto::trace_processor { + +class TraceProcessorContext; + +namespace perf_importer { // Wrapper around a `perf_event_attr` object that add some helper methods. class PerfEventAttr : public RefCounted { public: - explicit PerfEventAttr(perf_event_attr attr); + PerfEventAttr(TraceProcessorContext* context, + tables::PerfSessionTable::Id perf_session_id_, + perf_event_attr attr); + ~PerfEventAttr(); + uint32_t type() const { return attr_.type; } + uint64_t config() const { return attr_.config; } uint64_t sample_type() const { return attr_.sample_type; } uint64_t read_format() const { return attr_.read_format; } bool sample_id_all() const { return !!attr_.sample_id_all; } @@ -48,12 +61,6 @@ class PerfEventAttr : public RefCounted { return attr_.freq ? std::make_optional(attr_.sample_freq) : std::nullopt; } - bool is_timebase() const { - // This is what simpleperf uses for events that are not supposed to sample - // TODO(b/334978369): Determine if there is a better way to figure this out. - return attr_.sample_period < (1ull << 62); - } - // Offset from the end of a record's payload to the time filed (if present). // To be used with non `PERF_RECORD_SAMPLE` records std::optional time_offset_from_end() const { @@ -81,14 +88,33 @@ class PerfEventAttr : public RefCounted { return id_offset_from_end_; } + void set_event_name(std::string event_name) { + event_name_ = std::move(event_name); + } + + PerfCounter& GetOrCreateCounter(uint32_t cpu); + private: + bool is_timebase() const { + // This is what simpleperf uses for events that are not supposed to sample + // TODO(b/334978369): Determine if there is a better way to figure this out. + return attr_.sample_period < (1ull << 62); + } + + PerfCounter CreateCounter(uint32_t cpu) const; + + TraceProcessorContext* const context_; + tables::PerfSessionTable::Id perf_session_id_; perf_event_attr attr_; std::optional time_offset_from_start_; std::optional time_offset_from_end_; std::optional id_offset_from_start_; std::optional id_offset_from_end_; + std::unordered_map counters_; + std::string event_name_; }; -} // namespace perfetto::trace_processor::perf_importer +} // namespace perf_importer +} // namespace perfetto::trace_processor #endif // SRC_TRACE_PROCESSOR_IMPORTERS_PERF_PERF_EVENT_ATTR_H_ diff --git a/src/trace_processor/importers/perf/perf_session.cc b/src/trace_processor/importers/perf/perf_session.cc index 86d983d0bd..6b3ed7f8f4 100644 --- a/src/trace_processor/importers/perf/perf_session.cc +++ b/src/trace_processor/importers/perf/perf_session.cc @@ -21,16 +21,24 @@ #include #include #include +#include +#include #include #include "perfetto/base/logging.h" #include "perfetto/base/status.h" #include "perfetto/ext/base/flat_hash_map.h" #include "perfetto/ext/base/status_or.h" +#include "perfetto/ext/base/string_utils.h" +#include "perfetto/ext/base/string_view.h" #include "perfetto/trace_processor/ref_counted.h" +#include "perfetto/trace_processor/trace_blob_view.h" #include "src/trace_processor/importers/perf/perf_event.h" #include "src/trace_processor/importers/perf/perf_event_attr.h" #include "src/trace_processor/importers/perf/reader.h" +#include "src/trace_processor/storage/trace_storage.h" // IWYU pragma: keep +#include "src/trace_processor/types/trace_processor_context.h" +#include "src/trace_processor/util/build_id.h" namespace perfetto::trace_processor::perf_importer { namespace { @@ -46,11 +54,14 @@ base::StatusOr> PerfSession::Builder::Build() { return base::ErrStatus("No perf_event_attr"); } - const PerfEventAttr base_attr(attr_with_ids_[0].attr); + auto perf_session_id = + context_->storage->mutable_perf_session_table()->Insert({}).id; + PerfEventAttr base_attr(context_, perf_session_id, attr_with_ids_[0].attr); base::FlatHashMap> attrs_by_id; for (const auto& entry : attr_with_ids_) { - RefPtr attr(new PerfEventAttr(entry.attr)); + RefPtr attr( + new PerfEventAttr(context_, perf_session_id, entry.attr)); if (base_attr.sample_id_all() != attr->sample_id_all()) { return base::ErrStatus( "perf_event_attr with different sample_id_all values"); @@ -67,29 +78,31 @@ base::StatusOr> PerfSession::Builder::Build() { } } } - if (attr_with_ids_.size() > 1 && (!base_attr.id_offset_from_start().has_value() || (base_attr.sample_id_all() && !base_attr.id_offset_from_end().has_value()))) { return base::ErrStatus("No id offsets for multiple perf_event_attr"); } - - return RefPtr(new PerfSession( - perf_session_id_, std::move(attrs_by_id), attr_with_ids_.size() == 1)); + return RefPtr(new PerfSession(context_, perf_session_id, + std::move(attrs_by_id), + attr_with_ids_.size() == 1)); } -base::StatusOr> PerfSession::FindAttrForRecord( +base::StatusOr> PerfSession::FindAttrForRecord( const perf_event_header& header, const TraceBlobView& payload) const { - RefPtr first(attrs_by_id_.GetIterator().value().get()); + if (header.type >= PERF_RECORD_USER_TYPE_START) { + return RefPtr(); + } + + RefPtr first(attrs_by_id_.GetIterator().value().get()); if (has_single_perf_event_attr_) { return first; } - if (header.type >= PERF_RECORD_USER_TYPE_START || - (header.type != PERF_RECORD_SAMPLE && !first->sample_id_all())) { - return RefPtr(); + if (header.type != PERF_RECORD_SAMPLE && !first->sample_id_all()) { + return first; } uint64_t id; @@ -97,11 +110,14 @@ base::StatusOr> PerfSession::FindAttrForRecord( return base::ErrStatus("Failed to read record id"); } + if (id == 0) { + return first; + } + auto it = FindAttrForEventId(id); if (!it) { return base::ErrStatus("No perf_event_attr for id %" PRIu64, id); } - return it; } @@ -119,18 +135,59 @@ bool PerfSession::ReadEventId(const perf_event_header& header, const size_t off = reader.size_left() - *first.id_offset_from_end(); return reader.Skip(off) && reader.Read(id); } - PERFETTO_CHECK(first.id_offset_from_start().has_value()); - return reader.Skip(*first.id_offset_from_start()) && reader.Read(id); } -RefPtr PerfSession::FindAttrForEventId(uint64_t id) const { - auto it = attrs_by_id_.Find(id); +RefPtr PerfSession::FindAttrForEventId(uint64_t id) const { + auto* it = attrs_by_id_.Find(id); if (!it) { - return RefPtr(); + return {}; } - return RefPtr(it->get()); + return RefPtr(it->get()); +} + +void PerfSession::SetEventName(uint64_t event_id, std::string name) { + auto* it = attrs_by_id_.Find(event_id); + if (!it) { + return; + } + (*it)->set_event_name(std::move(name)); +} + +void PerfSession::SetEventName(uint32_t type, + uint64_t config, + const std::string& name) { + for (auto it = attrs_by_id_.GetIterator(); it; ++it) { + if (it.value()->type() == type && it.value()->config() == config) { + it.value()->set_event_name(name); + } + } +} + +void PerfSession::AddBuildId(int32_t pid, + std::string filename, + BuildId build_id) { + build_ids_.Insert({pid, std::move(filename)}, std::move(build_id)); +} + +std::optional PerfSession::LookupBuildId( + uint32_t pid, + const std::string& filename) const { + // -1 is used in BUILD_ID feature to match any pid. + static constexpr int32_t kAnyPid = -1; + auto* it = build_ids_.Find({static_cast(pid), filename}); + if (!it) { + it = build_ids_.Find({kAnyPid, filename}); + } + return it ? std::make_optional(*it) : std::nullopt; +} + +void PerfSession::SetCmdline(const std::vector& args) { + context_->storage->mutable_perf_session_table() + ->FindById(perf_session_id_) + ->set_cmdline(context_->storage->InternString( + base::StringView(base::Join(args, " ")))); } } // namespace perfetto::trace_processor::perf_importer diff --git a/src/trace_processor/importers/perf/perf_session.h b/src/trace_processor/importers/perf/perf_session.h index abea41bec7..a442b5468d 100644 --- a/src/trace_processor/importers/perf/perf_session.h +++ b/src/trace_processor/importers/perf/perf_session.h @@ -21,26 +21,35 @@ #include #include #include +#include +#include +#include #include "perfetto/ext/base/flat_hash_map.h" +#include "perfetto/ext/base/hash.h" #include "perfetto/ext/base/status_or.h" #include "perfetto/trace_processor/ref_counted.h" #include "perfetto/trace_processor/trace_blob_view.h" #include "src/trace_processor/importers/perf/perf_event.h" #include "src/trace_processor/importers/perf/perf_event_attr.h" +#include "src/trace_processor/tables/profiler_tables_py.h" +#include "src/trace_processor/util/build_id.h" -namespace perfetto::trace_processor::perf_importer { +namespace perfetto::trace_processor { + +class TraceProcessorContext; + +namespace perf_importer { // Helper to deal with perf_event_attr instances in a perf file. class PerfSession : public RefCounted { public: class Builder { public: - explicit Builder(uint32_t perf_session_id) - : perf_session_id_(perf_session_id) {} + explicit Builder(TraceProcessorContext* context) : context_(context) {} base::StatusOr> Build(); Builder& AddAttrAndIds(perf_event_attr attr, std::vector ids) { - attr_with_ids_.push_back({std::move(attr), std::move(ids)}); + attr_with_ids_.push_back({attr, std::move(ids)}); return *this; } @@ -49,24 +58,59 @@ class PerfSession : public RefCounted { perf_event_attr attr; std::vector ids; }; - - uint32_t perf_session_id_; + TraceProcessorContext* const context_; std::vector attr_with_ids_; }; - uint32_t perf_session_id() const { return perf_session_id_; } + tables::PerfSessionTable::Id perf_session_id() const { + return perf_session_id_; + } - RefPtr FindAttrForEventId(uint64_t id) const; + RefPtr FindAttrForEventId(uint64_t id) const; - base::StatusOr> FindAttrForRecord( + base::StatusOr> FindAttrForRecord( const perf_event_header& header, const TraceBlobView& payload) const; + void SetCmdline(const std::vector& args); + void SetEventName(uint64_t event_id, std::string name); + void SetEventName(uint32_t type, uint64_t config, const std::string& name); + + void AddBuildId(int32_t pid, std::string filename, BuildId build_id); + std::optional LookupBuildId(uint32_t pid, + const std::string& filename) const; + + // The kernel stores the return address for non leaf frames in call chains. + // Simpleperf accounts for this when writing perf data files, linux perf does + // not. This method returns true if we need to convert return addresses to + // call sites when parsing call chains (i.e. if the trace comes from linux + // perf). + bool needs_pc_adjustment() const { return is_simpleperf_ == false; } + + void SetIsSimpleperf() { is_simpleperf_ = true; } + private: - PerfSession(uint32_t perf_session_id, + struct BuildIdMapKey { + int32_t pid; + std::string filename; + + struct Hasher { + size_t operator()(const BuildIdMapKey& k) const { + return static_cast(base::Hasher::Combine(k.pid, k.filename)); + } + }; + + bool operator==(const BuildIdMapKey& o) const { + return pid == o.pid && filename == o.filename; + } + }; + + PerfSession(TraceProcessorContext* context, + tables::PerfSessionTable::Id perf_session_id, base::FlatHashMap> attrs_by_id, bool has_single_perf_event_attr) - : perf_session_id_(perf_session_id), + : context_(context), + perf_session_id_(perf_session_id), attrs_by_id_(std::move(attrs_by_id)), has_single_perf_event_attr_(has_single_perf_event_attr) {} @@ -74,15 +118,21 @@ class PerfSession : public RefCounted { const TraceBlobView& payload, uint64_t& id) const; - uint32_t perf_session_id_; + TraceProcessorContext* const context_; + tables::PerfSessionTable::Id perf_session_id_; base::FlatHashMap> attrs_by_id_; // Multiple ids can map to the same perf_event_attr. This member tells us // whether there was only one perf_event_attr (with potentially different ids // associated). This makes the attr lookup given a record trivial and not // dependant no having any id field in the records. bool has_single_perf_event_attr_; + + bool is_simpleperf_ = false; + + base::FlatHashMap build_ids_; }; -} // namespace perfetto::trace_processor::perf_importer +} // namespace perf_importer +} // namespace perfetto::trace_processor #endif // SRC_TRACE_PROCESSOR_IMPORTERS_PERF_PERF_SESSION_H_ diff --git a/src/trace_processor/importers/perf/perf_session_unittest.cc b/src/trace_processor/importers/perf/perf_session_unittest.cc index 0ab85f23a5..2b15af862d 100644 --- a/src/trace_processor/importers/perf/perf_session_unittest.cc +++ b/src/trace_processor/importers/perf/perf_session_unittest.cc @@ -23,6 +23,8 @@ #include "perfetto/trace_processor/trace_blob.h" #include "perfetto/trace_processor/trace_blob_view.h" #include "src/trace_processor/importers/perf/perf_event.h" +#include "src/trace_processor/storage/trace_storage.h" +#include "src/trace_processor/types/trace_processor_context.h" #include "test/gtest_and_gmock.h" namespace perfetto::trace_processor::perf_importer { @@ -41,12 +43,16 @@ MATCHER_P(IsOkAndHolds, matcher, "") { } TEST(PerfSessionTest, NoAttrBuildFails) { - PerfSession::Builder builder(0); + TraceProcessorContext context; + context.storage.reset(new TraceStorage()); + PerfSession::Builder builder(&context); EXPECT_FALSE(builder.Build().ok()); } TEST(PerfSessionTest, OneAttrAndNoIdBuildSucceeds) { - PerfSession::Builder builder(0); + TraceProcessorContext context; + context.storage.reset(new TraceStorage()); + PerfSession::Builder builder(&context); perf_event_attr attr; attr.sample_id_all = false; attr.sample_type = PERF_SAMPLE_CALLCHAIN | PERF_SAMPLE_CPU | PERF_SAMPLE_TIME; @@ -61,7 +67,9 @@ TEST(PerfSessionTest, OneAttrAndNoIdBuildSucceeds) { } TEST(PerfSessionTest, MultipleAttrsAndNoIdBuildFails) { - PerfSession::Builder builder(0); + TraceProcessorContext context; + context.storage.reset(new TraceStorage()); + PerfSession::Builder builder(&context); perf_event_attr attr; attr.sample_id_all = true; attr.sample_type = PERF_SAMPLE_CALLCHAIN | PERF_SAMPLE_CPU | PERF_SAMPLE_TIME; @@ -71,7 +79,9 @@ TEST(PerfSessionTest, MultipleAttrsAndNoIdBuildFails) { } TEST(PerfSessionTest, MultipleIdsSameAttrAndNoIdCanExtractAttrFromRecord) { - PerfSession::Builder builder(0); + TraceProcessorContext context; + context.storage.reset(new TraceStorage()); + PerfSession::Builder builder(&context); perf_event_attr attr; attr.sample_id_all = true; attr.sample_type = PERF_SAMPLE_IP | PERF_SAMPLE_CPU | PERF_SAMPLE_TIME; @@ -95,7 +105,9 @@ TEST(PerfSessionTest, MultipleIdsSameAttrAndNoIdCanExtractAttrFromRecord) { } TEST(PerfSessionTest, NoCommonSampleIdAllBuildFails) { - PerfSession::Builder builder(0); + TraceProcessorContext context; + context.storage.reset(new TraceStorage()); + PerfSession::Builder builder(&context); perf_event_attr attr; attr.sample_id_all = true; attr.sample_type = PERF_SAMPLE_IDENTIFIER; @@ -111,7 +123,9 @@ TEST(PerfSessionTest, NoCommonSampleIdAllBuildFails) { } TEST(PerfSessionTest, NoCommonOffsetForSampleBuildFails) { - PerfSession::Builder builder(0); + TraceProcessorContext context; + context.storage.reset(new TraceStorage()); + PerfSession::Builder builder(&context); perf_event_attr attr; attr.sample_id_all = true; attr.sample_type = PERF_SAMPLE_IP | PERF_SAMPLE_ID; @@ -122,7 +136,9 @@ TEST(PerfSessionTest, NoCommonOffsetForSampleBuildFails) { } TEST(PerfSessionTest, NoCommonOffsetForNonSampleBuildFails) { - PerfSession::Builder builder(0); + TraceProcessorContext context; + context.storage.reset(new TraceStorage()); + PerfSession::Builder builder(&context); perf_event_attr attr; attr.sample_id_all = true; attr.sample_type = PERF_SAMPLE_ID | PERF_SAMPLE_TID; @@ -138,7 +154,9 @@ TEST(PerfSessionTest, NoCommonOffsetForNonSampleBuildFails) { } TEST(PerfSessionTest, NoCommonOffsetForNonSampleAndNoSampleIdAllBuildSucceeds) { - PerfSession::Builder builder(0); + TraceProcessorContext context; + context.storage.reset(new TraceStorage()); + PerfSession::Builder builder(&context); perf_event_attr attr; attr.sample_id_all = false; attr.sample_type = PERF_SAMPLE_IDENTIFIER | PERF_SAMPLE_TID; @@ -149,7 +167,9 @@ TEST(PerfSessionTest, NoCommonOffsetForNonSampleAndNoSampleIdAllBuildSucceeds) { } TEST(PerfSessionTest, MultiplesessionBuildSucceeds) { - PerfSession::Builder builder(0); + TraceProcessorContext context; + context.storage.reset(new TraceStorage()); + PerfSession::Builder builder(&context); perf_event_attr attr; attr.sample_id_all = true; attr.sample_type = PERF_SAMPLE_IP | PERF_SAMPLE_ID; @@ -159,7 +179,9 @@ TEST(PerfSessionTest, MultiplesessionBuildSucceeds) { } TEST(PerfSessionTest, FindAttrInRecordWithId) { - PerfSession::Builder builder(0); + TraceProcessorContext context; + context.storage.reset(new TraceStorage()); + PerfSession::Builder builder(&context); perf_event_attr attr; attr.sample_id_all = true; attr.sample_type = PERF_SAMPLE_IP | PERF_SAMPLE_ID; @@ -194,7 +216,9 @@ TEST(PerfSessionTest, FindAttrInRecordWithId) { } TEST(PerfSessionTest, FindAttrInRecordWithIdentifier) { - PerfSession::Builder builder(0); + TraceProcessorContext context; + context.storage.reset(new TraceStorage()); + PerfSession::Builder builder(&context); perf_event_attr attr; attr.sample_id_all = true; attr.sample_type = PERF_SAMPLE_IDENTIFIER | PERF_SAMPLE_IP; diff --git a/src/trace_processor/importers/perf/reader.h b/src/trace_processor/importers/perf/reader.h index faf31a215f..25d4f4c3eb 100644 --- a/src/trace_processor/importers/perf/reader.h +++ b/src/trace_processor/importers/perf/reader.h @@ -26,7 +26,9 @@ #include #include +#include "perfetto/ext/base/string_view.h" #include "perfetto/trace_processor/trace_blob_view.h" +#include "src/trace_processor/importers/perf/perf_event.h" namespace perfetto::trace_processor::perf_importer { @@ -45,6 +47,50 @@ class Reader { // methods are called. size_t size_left() const { return static_cast(end_ - current_); } + bool ReadStringView(base::StringView& str, size_t size) { + if (size_left() < size) { + return false; + } + str = base::StringView(reinterpret_cast(current_), size); + current_ += size; + return true; + } + + bool ReadPerfEventAttr(perf_event_attr& attr, size_t attr_size) { + const size_t bytes_to_read = std::min(attr_size, sizeof(attr)); + const size_t bytes_to_skip = attr_size - bytes_to_read; + static_assert(std::has_unique_object_representations_v); + + if (size_left() < bytes_to_read + bytes_to_skip) { + return false; + } + + memset(&attr, 0, sizeof(attr)); + + return Read(&attr, bytes_to_read) && Skip(bytes_to_skip); + } + + bool ReadBlob(TraceBlobView& blob, uint32_t size) { + if (size_left() < size) { + return false; + } + blob = TraceBlobView(buffer_, + static_cast(current_ - buffer_->data()), size); + current_ += size; + return true; + } + + bool ReadStringUntilEndOrNull(std::string& out) { + const uint8_t* ptr = current_; + while (ptr != end_ && *ptr != 0) { + ++ptr; + } + out = std::string(reinterpret_cast(current_), + static_cast(ptr - current_)); + current_ = ptr; + return true; + } + template bool Read(T& obj) { static_assert(std::has_unique_object_representations_v); diff --git a/src/trace_processor/importers/perf/record.h b/src/trace_processor/importers/perf/record.h index a50c5295b0..c4d2279ac9 100644 --- a/src/trace_processor/importers/perf/record.h +++ b/src/trace_processor/importers/perf/record.h @@ -63,7 +63,7 @@ struct Record { } RefPtr session; - RefPtr attr; + RefPtr attr; perf_event_header header; TraceBlobView payload; }; diff --git a/src/trace_processor/importers/perf/record_parser.cc b/src/trace_processor/importers/perf/record_parser.cc new file mode 100644 index 0000000000..a63a5bd110 --- /dev/null +++ b/src/trace_processor/importers/perf/record_parser.cc @@ -0,0 +1,349 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "src/trace_processor/importers/perf/record_parser.h" + +#include +#include +#include +#include +#include +#include + +#include "perfetto/base/logging.h" +#include "perfetto/base/status.h" +#include "perfetto/ext/base/string_view.h" +#include "perfetto/public/compiler.h" +#include "perfetto/trace_processor/ref_counted.h" +#include "src/trace_processor/importers/common/address_range.h" +#include "src/trace_processor/importers/common/create_mapping_params.h" +#include "src/trace_processor/importers/common/mapping_tracker.h" +#include "src/trace_processor/importers/common/process_tracker.h" +#include "src/trace_processor/importers/common/stack_profile_tracker.h" +#include "src/trace_processor/importers/common/virtual_memory_mapping.h" +#include "src/trace_processor/importers/perf/mmap_record.h" +#include "src/trace_processor/importers/perf/perf_counter.h" +#include "src/trace_processor/importers/perf/perf_event.h" +#include "src/trace_processor/importers/perf/perf_event_attr.h" +#include "src/trace_processor/importers/perf/reader.h" +#include "src/trace_processor/importers/perf/record.h" +#include "src/trace_processor/importers/perf/sample.h" +#include "src/trace_processor/importers/proto/perf_sample_tracker.h" +#include "src/trace_processor/importers/proto/profile_packet_utils.h" +#include "src/trace_processor/storage/stats.h" +#include "src/trace_processor/storage/trace_storage.h" +#include "src/trace_processor/tables/metadata_tables_py.h" +#include "src/trace_processor/tables/profiler_tables_py.h" +#include "src/trace_processor/util/build_id.h" +#include "src/trace_processor/util/status_macros.h" + +#include "protos/perfetto/trace/profiling/profile_packet.pbzero.h" + +namespace perfetto::trace_processor::perf_importer { +namespace { + +CreateMappingParams BuildCreateMappingParams( + const CommonMmapRecordFields& fields, + std::string filename, + std::optional build_id) { + return {AddressRange::FromStartAndSize(fields.addr, fields.len), fields.pgoff, + // start_offset: This is the offset into the file where the ELF header + // starts. We assume all file mappings are ELF files an thus this + // offset is 0. + 0, + // load_bias: This can only be read out of the actual ELF file, which + // we do not have here, so we set it to 0. When symbolizing we will + // hopefully have the real load bias and we can compensate there for a + // possible mismatch. + 0, std::move(filename), std::move(build_id)}; +} + +bool IsInKernel(protos::pbzero::Profiling::CpuMode cpu_mode) { + switch (cpu_mode) { + case protos::pbzero::Profiling::MODE_GUEST_KERNEL: + case protos::pbzero::Profiling::MODE_KERNEL: + return true; + case protos::pbzero::Profiling::MODE_USER: + case protos::pbzero::Profiling::MODE_HYPERVISOR: + case protos::pbzero::Profiling::MODE_GUEST_USER: + case protos::pbzero::Profiling::MODE_UNKNOWN: + return false; + } + PERFETTO_FATAL("For GCC."); +} + +} // namespace + +using FramesTable = tables::StackProfileFrameTable; +using CallsitesTable = tables::StackProfileCallsiteTable; + +RecordParser::RecordParser(TraceProcessorContext* context) + : context_(context), mapping_tracker_(context->mapping_tracker.get()) {} + +RecordParser::~RecordParser() = default; + +void RecordParser::ParsePerfRecord(int64_t ts, Record record) { + if (base::Status status = ParseRecord(ts, std::move(record)); !status.ok()) { + context_->storage->IncrementStats(record.header.type == PERF_RECORD_SAMPLE + ? stats::perf_samples_skipped + : stats::perf_record_skipped); + } +} + +base::Status RecordParser::ParseRecord(int64_t ts, Record record) { + switch (record.header.type) { + case PERF_RECORD_COMM: + return ParseComm(std::move(record)); + + case PERF_RECORD_SAMPLE: + return ParseSample(ts, std::move(record)); + + case PERF_RECORD_MMAP: + return ParseMmap(std::move(record)); + + case PERF_RECORD_MMAP2: + return ParseMmap2(std::move(record)); + + case PERF_RECORD_AUX: + case PERF_RECORD_AUXTRACE: + case PERF_RECORD_AUXTRACE_INFO: + // These should be dealt with at tokenization time + PERFETTO_FATAL("Unexpected record type at parsing time: %" PRIu32, + record.header.type); + + default: + context_->storage->IncrementIndexedStats( + stats::perf_unknown_record_type, + static_cast(record.header.type)); + return base::ErrStatus("Unknown PERF_RECORD with type %" PRIu32, + record.header.type); + } +} + +base::Status RecordParser::ParseSample(int64_t ts, Record record) { + Sample sample; + RETURN_IF_ERROR(sample.Parse(ts, record)); + + if (!sample.period.has_value() && record.attr != nullptr) { + sample.period = record.attr->sample_period(); + } + + return InternSample(std::move(sample)); +} + +base::Status RecordParser::InternSample(Sample sample) { + if (!sample.time.has_value()) { + // We do not really use this TS as this is using the perf clock, but we need + // it to be present so that we can compute the trace_ts done during + // tokenization. (Actually at tokenization time we do estimate a trace_ts if + // no perf ts is present, but for samples we want this to be as accurate as + // possible) + return base::ErrStatus( + "Can not parse samples with no PERF_SAMPLE_TIME field"); + } + + if (!sample.pid_tid.has_value()) { + return base::ErrStatus( + "Can not parse samples with no PERF_SAMPLE_TID field"); + } + + if (sample.cpu_mode == + protos::pbzero::perfetto_pbzero_enum_Profiling::MODE_UNKNOWN) { + context_->storage->IncrementStats(stats::perf_samples_cpu_mode_unknown); + } + + UniqueTid utid = context_->process_tracker->UpdateThread(sample.pid_tid->tid, + sample.pid_tid->pid); + const auto upid = *context_->storage->thread_table() + .FindById(tables::ThreadTable::Id(utid)) + ->upid(); + + if (sample.callchain.empty() && sample.ip.has_value()) { + sample.callchain.push_back(Sample::Frame{sample.cpu_mode, *sample.ip}); + } + std::optional callsite_id = InternCallchain( + upid, sample.callchain, sample.perf_session->needs_pc_adjustment()); + + context_->storage->mutable_perf_sample_table()->Insert( + {sample.trace_ts, utid, sample.cpu, + context_->storage->InternString( + ProfilePacketUtils::StringifyCpuMode(sample.cpu_mode)), + callsite_id, std::nullopt, sample.perf_session->perf_session_id()}); + + return UpdateCounters(sample); +} + +std::optional RecordParser::InternCallchain( + UniquePid upid, + const std::vector& callchain, + bool adjust_pc) { + if (callchain.empty()) { + return std::nullopt; + } + + auto& stack_profile_tracker = *context_->stack_profile_tracker; + + std::optional parent; + uint32_t depth = 0; + // Note callchain is not empty so this is always valid. + const auto leaf = --callchain.rend(); + for (auto it = callchain.rbegin(); it != callchain.rend(); ++it) { + uint64_t ip = it->ip; + + // For non leaf frames the ip stored in the chain is the return address, but + // what we really need is the address of the call instruction. For that we + // just need to move the ip one instruction back. Instructions can be of + // different sizes depending on the CPU arch (ARM, AARCH64, etc..). For + // symbolization to work we don't really need to point at the first byte of + // the instruction, any byte of the instruction seems to be enough, so use + // -1. + if (ip != 0 && it != leaf && adjust_pc) { + --ip; + } + + VirtualMemoryMapping* mapping; + if (IsInKernel(it->cpu_mode)) { + mapping = mapping_tracker_->FindKernelMappingForAddress(ip); + } else { + mapping = mapping_tracker_->FindUserMappingForAddress(upid, ip); + } + + if (!mapping) { + context_->storage->IncrementStats(stats::perf_dummy_mapping_used); + // Simpleperf will not create mappings for anonymous executable mappings + // which are used by JITted code (e.g. V8 JavaScript). + mapping = mapping_tracker_->GetDummyMapping(); + } + + const FrameId frame_id = + mapping->InternFrame(mapping->ToRelativePc(ip), ""); + + parent = stack_profile_tracker.InternCallsite(parent, frame_id, depth); + depth++; + } + return parent; +} + +base::Status RecordParser::ParseComm(Record record) { + Reader reader(record.payload.copy()); + uint32_t pid; + uint32_t tid; + std::string comm; + if (!reader.Read(pid) || !reader.Read(tid) || !reader.ReadCString(comm)) { + return base::ErrStatus("Failed to parse PERF_RECORD_COMM"); + } + + context_->process_tracker->UpdateThread(tid, pid); + context_->process_tracker->UpdateThreadName( + tid, context_->storage->InternString(base::StringView(comm)), + ThreadNamePriority::kFtrace); + + return base::OkStatus(); +} + +base::Status RecordParser::ParseMmap(Record record) { + MmapRecord mmap; + RETURN_IF_ERROR(mmap.Parse(record)); + std::optional build_id = + record.session->LookupBuildId(mmap.pid, mmap.filename); + if (IsInKernel(record.GetCpuMode())) { + context_->mapping_tracker->CreateKernelMemoryMapping( + BuildCreateMappingParams(mmap, std::move(mmap.filename), + std::move(build_id))); + return base::OkStatus(); + } + + context_->mapping_tracker->CreateUserMemoryMapping( + GetUpid(mmap), BuildCreateMappingParams(mmap, std::move(mmap.filename), + std::move(build_id))); + + return base::OkStatus(); +} + +base::Status RecordParser::ParseMmap2(Record record) { + Mmap2Record mmap2; + RETURN_IF_ERROR(mmap2.Parse(record)); + std::optional build_id = mmap2.GetBuildId(); + if (!build_id.has_value()) { + build_id = record.session->LookupBuildId(mmap2.pid, mmap2.filename); + } + if (IsInKernel(record.GetCpuMode())) { + context_->mapping_tracker->CreateKernelMemoryMapping( + BuildCreateMappingParams(mmap2, std::move(mmap2.filename), + std::move(build_id))); + return base::OkStatus(); + } + + context_->mapping_tracker->CreateUserMemoryMapping( + GetUpid(mmap2), BuildCreateMappingParams(mmap2, std::move(mmap2.filename), + std::move(build_id))); + + return base::OkStatus(); +} + +UniquePid RecordParser::GetUpid(const CommonMmapRecordFields& fields) const { + UniqueTid utid = + context_->process_tracker->UpdateThread(fields.tid, fields.pid); + auto upid = context_->storage->thread_table() + .FindById(tables::ThreadTable::Id(utid)) + ->upid(); + PERFETTO_CHECK(upid.has_value()); + return *upid; +} + +base::Status RecordParser::UpdateCounters(const Sample& sample) { + if (!sample.read_groups.empty()) { + return UpdateCountersInReadGroups(sample); + } + + if (!sample.cpu.has_value()) { + context_->storage->IncrementStats( + stats::perf_counter_skipped_because_no_cpu); + return base::OkStatus(); + } + + if (!sample.period.has_value() && !sample.attr->sample_period().has_value()) { + return base::ErrStatus("No period for sample"); + } + + uint64_t period = sample.period.has_value() ? *sample.period + : *sample.attr->sample_period(); + sample.attr->GetOrCreateCounter(*sample.cpu) + .AddDelta(sample.trace_ts, static_cast(period)); + return base::OkStatus(); +} + +base::Status RecordParser::UpdateCountersInReadGroups(const Sample& sample) { + if (!sample.cpu.has_value()) { + context_->storage->IncrementStats( + stats::perf_counter_skipped_because_no_cpu); + return base::OkStatus(); + } + + for (const auto& entry : sample.read_groups) { + RefPtr attr = + sample.perf_session->FindAttrForEventId(*entry.event_id); + if (PERFETTO_UNLIKELY(!attr)) { + return base::ErrStatus("No perf_event_attr for id %" PRIu64, + *entry.event_id); + } + attr->GetOrCreateCounter(*sample.cpu) + .AddCount(sample.trace_ts, static_cast(entry.value)); + } + return base::OkStatus(); +} + +} // namespace perfetto::trace_processor::perf_importer diff --git a/src/trace_processor/importers/perf/record_parser.h b/src/trace_processor/importers/perf/record_parser.h new file mode 100644 index 0000000000..76926d17c2 --- /dev/null +++ b/src/trace_processor/importers/perf/record_parser.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PERF_RECORD_PARSER_H_ +#define SRC_TRACE_PROCESSOR_IMPORTERS_PERF_RECORD_PARSER_H_ + +#include +#include +#include + +#include "perfetto/base/status.h" +#include "src/trace_processor/importers/common/trace_parser.h" +#include "src/trace_processor/importers/perf/mmap_record.h" +#include "src/trace_processor/importers/perf/record.h" +#include "src/trace_processor/importers/perf/sample.h" +#include "src/trace_processor/storage/trace_storage.h" + +namespace perfetto { +namespace trace_processor { + +class MappingTracker; +class TraceProcessorContext; + +namespace perf_importer { + +class PerfDataTracker; +class Reader; + +// Parses samples from perf.data files. +class RecordParser : public PerfRecordParser { + public: + explicit RecordParser(TraceProcessorContext*); + ~RecordParser() override; + + void ParsePerfRecord(int64_t timestamp, Record record) override; + + private: + base::Status ParseRecord(int64_t timestamp, Record record); + base::Status ParseSample(int64_t ts, Record record); + base::Status ParseComm(Record record); + base::Status ParseMmap(Record record); + base::Status ParseMmap2(Record record); + + base::Status InternSample(Sample sample); + + base::Status UpdateCounters(const Sample& sample); + base::Status UpdateCountersInReadGroups(const Sample& sample); + + std::optional InternCallchain( + UniquePid upid, + const std::vector& callchain, + bool adjust_pc); + + UniquePid GetUpid(const CommonMmapRecordFields& fields) const; + + TraceProcessorContext* const context_ = nullptr; + MappingTracker* const mapping_tracker_; +}; + +} // namespace perf_importer +} // namespace trace_processor +} // namespace perfetto + +#endif // SRC_TRACE_PROCESSOR_IMPORTERS_PERF_RECORD_PARSER_H_ diff --git a/src/trace_processor/importers/perf/sample.cc b/src/trace_processor/importers/perf/sample.cc new file mode 100644 index 0000000000..802cac736e --- /dev/null +++ b/src/trace_processor/importers/perf/sample.cc @@ -0,0 +1,258 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "src/trace_processor/importers/perf/sample.h" + +#include +#include +#include +#include + +#include "perfetto/base/logging.h" +#include "perfetto/base/status.h" +#include "perfetto/public/compiler.h" +#include "src/trace_processor/importers/perf/perf_event.h" +#include "src/trace_processor/importers/perf/reader.h" +#include "src/trace_processor/importers/perf/record.h" + +#include "protos/perfetto/trace/profiling/profile_packet.pbzero.h" + +namespace perfetto::trace_processor::perf_importer { +namespace { + +bool ParseSampleReadGroup(Reader& reader, + uint64_t read_format, + uint64_t num_records, + std::vector& out) { + out.resize(num_records); + for (auto& read : out) { + if (PERFETTO_UNLIKELY(!reader.Read(read.value))) { + return false; + } + + if (read_format & PERF_FORMAT_ID) { + if (PERFETTO_UNLIKELY(!reader.ReadOptional(read.event_id))) { + return false; + } + } + + if (read_format & PERF_FORMAT_LOST) { + uint64_t lost; + if (PERFETTO_UNLIKELY(!reader.Read(lost))) { + return false; + } + } + } + + return true; +} + +bool ParseSampleRead(Reader& reader, + uint64_t read_format, + std::vector& out) { + uint64_t value_or_nr; + + if (PERFETTO_UNLIKELY(!reader.Read(value_or_nr))) { + return false; + } + + if (read_format & PERF_FORMAT_TOTAL_TIME_ENABLED) { + uint64_t total_time_enabled; + if (PERFETTO_UNLIKELY(!reader.Read(total_time_enabled))) { + return false; + } + } + + if (read_format & PERF_FORMAT_TOTAL_TIME_RUNNING) { + uint64_t total_time_running; + if (PERFETTO_UNLIKELY(!reader.Read(total_time_running))) { + return false; + } + } + + if (read_format & PERF_FORMAT_GROUP) { + return ParseSampleReadGroup(reader, read_format, value_or_nr, out); + } + + std::optional event_id; + if (read_format & PERF_FORMAT_ID) { + event_id.emplace(0); + if (PERFETTO_UNLIKELY(!reader.ReadOptional(event_id))) { + return false; + } + } + + if (read_format & PERF_FORMAT_LOST) { + uint64_t lost; + if (PERFETTO_UNLIKELY(!reader.Read(lost))) { + return false; + } + } + + out.push_back({event_id, value_or_nr}); + + return true; +} + +protos::pbzero::Profiling::CpuMode PerfCallchainContextToCpuMode(uint64_t ip) { + switch (ip) { + case PERF_CONTEXT_HV: + return protos::pbzero::Profiling::MODE_HYPERVISOR; + case PERF_CONTEXT_KERNEL: + return protos::pbzero::Profiling::MODE_KERNEL; + case PERF_CONTEXT_USER: + return protos::pbzero::Profiling::MODE_USER; + case PERF_CONTEXT_GUEST_KERNEL: + return protos::pbzero::Profiling::MODE_GUEST_KERNEL; + case PERF_CONTEXT_GUEST_USER: + return protos::pbzero::Profiling::MODE_GUEST_USER; + case PERF_CONTEXT_GUEST: + default: + return protos::pbzero::Profiling::MODE_UNKNOWN; + } + PERFETTO_FATAL("For GCC"); +} + +bool IsPerfContextMark(uint64_t ip) { + return ip >= PERF_CONTEXT_MAX; +} + +bool ParseSampleCallchain(Reader& reader, + protos::pbzero::Profiling::CpuMode cpu_mode, + std::vector& out) { + uint64_t nr; + if (PERFETTO_UNLIKELY(!reader.Read(nr))) { + return false; + } + + std::vector frames; + frames.reserve(nr); + for (; nr != 0; --nr) { + uint64_t ip; + if (PERFETTO_UNLIKELY(!reader.Read(ip))) { + return false; + } + if (PERFETTO_UNLIKELY(IsPerfContextMark(ip))) { + cpu_mode = PerfCallchainContextToCpuMode(ip); + continue; + } + frames.push_back({cpu_mode, ip}); + } + + out = std::move(frames); + return true; +} +} // namespace + +base::Status Sample::Parse(int64_t in_trace_ts, const Record& record) { + PERFETTO_CHECK(record.attr); + const uint64_t sample_type = record.attr->sample_type(); + + trace_ts = in_trace_ts; + cpu_mode = record.GetCpuMode(); + perf_session = record.session; + attr = record.attr; + + Reader reader(record.payload.copy()); + + std::optional identifier; + if (sample_type & PERF_SAMPLE_IDENTIFIER) { + if (PERFETTO_UNLIKELY(!reader.ReadOptional(identifier))) { + return base ::ErrStatus("Not enough data to read PERF_SAMPLE_IDENTIFIER"); + } + } + + if (sample_type & PERF_SAMPLE_IP) { + if (PERFETTO_UNLIKELY(!reader.ReadOptional(ip))) { + return base ::ErrStatus("Not enough data to read PERF_SAMPLE_IP"); + } + } + + if (sample_type & PERF_SAMPLE_TID) { + if (PERFETTO_UNLIKELY(!reader.ReadOptional(pid_tid))) { + return base ::ErrStatus("Not enough data to read PERF_SAMPLE_TID"); + } + } + + if (sample_type & PERF_SAMPLE_TIME) { + if (PERFETTO_UNLIKELY(!reader.ReadOptional(time))) { + return base ::ErrStatus("Not enough data to read PERF_SAMPLE_TIME"); + } + } + + if (sample_type & PERF_SAMPLE_ADDR) { + if (PERFETTO_UNLIKELY(!reader.ReadOptional(addr))) { + return base ::ErrStatus("Not enough data to read PERF_SAMPLE_ADDR"); + } + } + + if (sample_type & PERF_SAMPLE_ID) { + if (PERFETTO_UNLIKELY(!reader.ReadOptional(id))) { + return base ::ErrStatus("Not enough data to read PERF_SAMPLE_ID"); + } + } + + if (identifier.has_value()) { + if (!id.has_value()) { + id = identifier; + } else if (PERFETTO_UNLIKELY(*identifier != *id)) { + return base::ErrStatus("ID and IDENTIFIER mismatch"); + } + } + + if (sample_type & PERF_SAMPLE_STREAM_ID) { + if (PERFETTO_UNLIKELY(!reader.ReadOptional(stream_id))) { + return base ::ErrStatus("Not enough data to read PERF_SAMPLE_STREAM_ID"); + } + } + + if (sample_type & PERF_SAMPLE_CPU) { + struct { + int32_t cpu; + int32_t unused; + } tmp; + if (PERFETTO_UNLIKELY(!reader.Read(tmp))) { + return base ::ErrStatus("Not enough data to read PERF_SAMPLE_CPU"); + } + cpu = tmp.cpu; + } + + if (sample_type & PERF_SAMPLE_PERIOD) { + if (PERFETTO_UNLIKELY(!reader.ReadOptional(period))) { + return base ::ErrStatus("Not enough data to read PERF_SAMPLE_PERIOD"); + } + } + + if (sample_type & PERF_SAMPLE_READ) { + if (PERFETTO_UNLIKELY( + !ParseSampleRead(reader, attr->read_format(), read_groups))) { + return base::ErrStatus("Failed to read PERF_SAMPLE_READ field"); + } + if (read_groups.empty()) { + return base::ErrStatus("No data in PERF_SAMPLE_READ field"); + } + } + + if (sample_type & PERF_SAMPLE_CALLCHAIN) { + if (PERFETTO_UNLIKELY(!ParseSampleCallchain(reader, cpu_mode, callchain))) { + return base::ErrStatus("Failed to read PERF_SAMPLE_CALLCHAIN field"); + } + } + + return base::OkStatus(); +} + +} // namespace perfetto::trace_processor::perf_importer diff --git a/src/trace_processor/importers/perf/sample.h b/src/trace_processor/importers/perf/sample.h new file mode 100644 index 0000000000..1aee3b9c2c --- /dev/null +++ b/src/trace_processor/importers/perf/sample.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PERF_SAMPLE_H_ +#define SRC_TRACE_PROCESSOR_IMPORTERS_PERF_SAMPLE_H_ + +#include +#include +#include + +#include "perfetto/base/status.h" +#include "perfetto/trace_processor/ref_counted.h" +#include "protos/perfetto/trace/profiling/profile_packet.pbzero.h" +#include "src/trace_processor/importers/perf/perf_event_attr.h" +#include "src/trace_processor/importers/perf/perf_session.h" + +namespace perfetto::trace_processor::perf_importer { + +struct Record; + +struct Sample { + struct Frame { + protos::pbzero::Profiling::CpuMode cpu_mode; + uint64_t ip; + }; + + struct PidTid { + uint32_t pid; + uint32_t tid; + }; + + struct ReadGroup { + std::optional event_id; + uint64_t value; + }; + + int64_t trace_ts; + protos::pbzero::Profiling::CpuMode cpu_mode; + RefPtr perf_session; + RefPtr attr; + + std::optional ip; + std::optional pid_tid; + std::optional time; + std::optional addr; + std::optional id; + std::optional stream_id; + std::optional cpu; + std::optional period; + std::vector read_groups; + std::vector callchain; + + base::Status Parse(int64_t trace_ts, const Record& record); +}; + +} // namespace perfetto::trace_processor::perf_importer + +#endif // SRC_TRACE_PROCESSOR_IMPORTERS_PERF_SAMPLE_H_ diff --git a/src/trace_processor/importers/proto/BUILD.gn b/src/trace_processor/importers/proto/BUILD.gn index dde995cd98..467e624dfa 100644 --- a/src/trace_processor/importers/proto/BUILD.gn +++ b/src/trace_processor/importers/proto/BUILD.gn @@ -18,6 +18,8 @@ source_set("minimal") { sources = [ "active_chrome_processes_tracker.cc", "active_chrome_processes_tracker.h", + "args_parser.cc", + "args_parser.h", "chrome_string_lookup.cc", "chrome_string_lookup.h", "chrome_system_probes_module.cc", @@ -98,6 +100,7 @@ source_set("minimal") { "../../util:build_id", "../../util:gzip", "../../util:profiler_util", + "../../util:trace_blob_view_reader", "../common", "../common:parser_types", "../etw:minimal", @@ -135,6 +138,12 @@ source_set("full") { "heap_graph_tracker.h", "metadata_module.cc", "metadata_module.h", + "pigweed_detokenizer.cc", + "pigweed_detokenizer.h", + "pixel_modem_module.cc", + "pixel_modem_module.h", + "pixel_modem_parser.cc", + "pixel_modem_parser.h", "statsd_module.cc", "statsd_module.h", "string_encoding_utils.cc", @@ -246,6 +255,12 @@ perfetto_cc_proto_descriptor("gen_cc_chrome_track_event_descriptor") { descriptor_target = "../../../../protos/third_party/chromium:descriptor" } +perfetto_cc_proto_descriptor("gen_cc_android_track_event_descriptor") { + descriptor_name = "android_track_event.descriptor" + descriptor_target = + "../../../../protos/perfetto/trace/android:android_track_event_descriptor" +} + perfetto_cc_proto_descriptor("gen_cc_config_descriptor") { descriptor_name = "config.descriptor" descriptor_target = "../../../../protos/perfetto/config:descriptor" @@ -261,6 +276,8 @@ source_set("unittests") { "perf_sample_tracker_unittest.cc", "profile_packet_sequence_state_unittest.cc", "proto_trace_parser_impl_unittest.cc", + "proto_trace_reader_unittest.cc", + "proto_trace_tokenizer_unittest.cc", "string_encoding_utils_unittests.cc", ] deps = [ diff --git a/src/trace_processor/importers/proto/additional_modules.cc b/src/trace_processor/importers/proto/additional_modules.cc index 91645c3936..0805476b77 100644 --- a/src/trace_processor/importers/proto/additional_modules.cc +++ b/src/trace_processor/importers/proto/additional_modules.cc @@ -24,6 +24,7 @@ #include "src/trace_processor/importers/proto/metadata_module.h" #include "src/trace_processor/importers/proto/multi_machine_trace_manager.h" #include "src/trace_processor/importers/proto/network_trace_module.h" +#include "src/trace_processor/importers/proto/pixel_modem_module.h" #include "src/trace_processor/importers/proto/statsd_module.h" #include "src/trace_processor/importers/proto/system_probes_module.h" #include "src/trace_processor/importers/proto/translation_table_module.h" @@ -45,6 +46,7 @@ void RegisterAdditionalModules(TraceProcessorContext* context) { context->modules.emplace_back(new MetadataModule(context)); context->modules.emplace_back(new V8Module(context)); context->modules.emplace_back(new WinscopeModule(context)); + context->modules.emplace_back(new PixelModemModule(context)); // Ftrace/Etw modules are special, because it has one extra method for parsing // ftrace/etw packets. So we need to store a pointer to it separately. diff --git a/src/trace_processor/importers/proto/args_parser.cc b/src/trace_processor/importers/proto/args_parser.cc new file mode 100644 index 0000000000..2aeb785c48 --- /dev/null +++ b/src/trace_processor/importers/proto/args_parser.cc @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "src/trace_processor/importers/proto/args_parser.h" + +#include "perfetto/ext/base/base64.h" +#include "src/trace_processor/importers/json/json_utils.h" + +namespace perfetto::trace_processor { + +using BoundInserter = ArgsTracker::BoundInserter; + +ArgsParser::ArgsParser(int64_t packet_timestamp, + BoundInserter& inserter, + TraceStorage& storage, + PacketSequenceStateGeneration* sequence_state, + bool support_json) + : support_json_(support_json), + packet_timestamp_(packet_timestamp), + sequence_state_(sequence_state), + inserter_(inserter), + storage_(storage) {} + +ArgsParser::~ArgsParser() = default; + +void ArgsParser::AddInteger(const Key& key, int64_t value) { + inserter_.AddArg(storage_.InternString(base::StringView(key.flat_key)), + storage_.InternString(base::StringView(key.key)), + Variadic::Integer(value)); +} + +void ArgsParser::AddUnsignedInteger(const Key& key, uint64_t value) { + inserter_.AddArg(storage_.InternString(base::StringView(key.flat_key)), + storage_.InternString(base::StringView(key.key)), + Variadic::UnsignedInteger(value)); +} + +void ArgsParser::AddString(const Key& key, const protozero::ConstChars& value) { + inserter_.AddArg(storage_.InternString(base::StringView(key.flat_key)), + storage_.InternString(base::StringView(key.key)), + Variadic::String(storage_.InternString(value))); +} + +void ArgsParser::AddString(const Key& key, const std::string& value) { + inserter_.AddArg( + storage_.InternString(base::StringView(key.flat_key)), + storage_.InternString(base::StringView(key.key)), + Variadic::String(storage_.InternString(base::StringView(value)))); +} + +void ArgsParser::AddDouble(const Key& key, double value) { + inserter_.AddArg(storage_.InternString(base::StringView(key.flat_key)), + storage_.InternString(base::StringView(key.key)), + Variadic::Real(value)); +} + +void ArgsParser::AddPointer(const Key& key, const void* value) { + inserter_.AddArg(storage_.InternString(base::StringView(key.flat_key)), + storage_.InternString(base::StringView(key.key)), + Variadic::Pointer(reinterpret_cast(value))); +} + +void ArgsParser::AddBoolean(const Key& key, bool value) { + inserter_.AddArg(storage_.InternString(base::StringView(key.flat_key)), + storage_.InternString(base::StringView(key.key)), + Variadic::Boolean(value)); +} + +void ArgsParser::AddBytes(const Key& key, const protozero::ConstBytes& value) { + std::string b64_data = base::Base64Encode(value.data, value.size); + AddString(key, b64_data); +} + +bool ArgsParser::AddJson(const Key& key, const protozero::ConstChars& value) { + if (!support_json_) + PERFETTO_FATAL("Unexpected JSON value when parsing data"); + + auto json_value = json::ParseJsonString(value); + if (!json_value) + return false; + return json::AddJsonValueToArgs(*json_value, base::StringView(key.flat_key), + base::StringView(key.key), &storage_, + &inserter_); +} + +void ArgsParser::AddNull(const Key& key) { + inserter_.AddArg(storage_.InternString(base::StringView(key.flat_key)), + storage_.InternString(base::StringView(key.key)), + Variadic::Null()); +} + +size_t ArgsParser::GetArrayEntryIndex(const std::string& array_key) { + return inserter_.GetNextArrayEntryIndex( + storage_.InternString(base::StringView(array_key))); +} + +size_t ArgsParser::IncrementArrayEntryIndex(const std::string& array_key) { + return inserter_.IncrementArrayEntryIndex( + storage_.InternString(base::StringView(array_key))); +} + +int64_t ArgsParser::packet_timestamp() { + return packet_timestamp_; +} + +PacketSequenceStateGeneration* ArgsParser::seq_state() { + return sequence_state_; +} + +InternedMessageView* ArgsParser::GetInternedMessageView(uint32_t field_id, + uint64_t iid) { + if (!sequence_state_) + return nullptr; + return sequence_state_->GetInternedMessageView(field_id, iid); +} + +} // namespace perfetto::trace_processor diff --git a/src/trace_processor/importers/proto/winscope/winscope_args_parser.h b/src/trace_processor/importers/proto/args_parser.h similarity index 61% rename from src/trace_processor/importers/proto/winscope/winscope_args_parser.h rename to src/trace_processor/importers/proto/args_parser.h index a0dfa4632b..7cd1578484 100644 --- a/src/trace_processor/importers/proto/winscope/winscope_args_parser.h +++ b/src/trace_processor/importers/proto/args_parser.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,22 +14,28 @@ * limitations under the License. */ -#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_WINSCOPE_WINSCOPE_ARGS_PARSER_H_ -#define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_WINSCOPE_WINSCOPE_ARGS_PARSER_H_ +#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_ARGS_PARSER_H_ +#define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_ARGS_PARSER_H_ #include "src/trace_processor/importers/common/args_tracker.h" +#include "src/trace_processor/importers/proto/packet_sequence_state_generation.h" #include "src/trace_processor/types/trace_processor_context.h" #include "src/trace_processor/util/proto_to_args_parser.h" -namespace perfetto { -namespace trace_processor { +namespace perfetto::trace_processor { -class WinscopeArgsParser : public util::ProtoToArgsParser::Delegate { +// A ProtoToArgsParser::Delegate that writes the parsed proto data into +// TraceStorage after interning key strings. +class ArgsParser : public util::ProtoToArgsParser::Delegate { public: using Key = util::ProtoToArgsParser::Key; - WinscopeArgsParser(ArgsTracker::BoundInserter& inserter, - TraceStorage& storage); + ArgsParser(int64_t packet_timestamp, + ArgsTracker::BoundInserter& inserter, + TraceStorage& storage, + PacketSequenceStateGeneration* sequence_state = nullptr, + bool support_json = false); + ~ArgsParser() override; void AddInteger(const Key&, int64_t) override; void AddUnsignedInteger(const Key&, uint64_t) override; void AddString(const Key&, const protozero::ConstChars&) override; @@ -37,10 +43,12 @@ class WinscopeArgsParser : public util::ProtoToArgsParser::Delegate { void AddDouble(const Key&, double) override; void AddPointer(const Key&, const void*) override; void AddBoolean(const Key&, bool) override; + void AddBytes(const Key&, const protozero::ConstBytes&) override; bool AddJson(const Key&, const protozero::ConstChars&) override; void AddNull(const Key&) override; size_t GetArrayEntryIndex(const std::string& array_key) override; size_t IncrementArrayEntryIndex(const std::string& array_key) override; + int64_t packet_timestamp() override; PacketSequenceStateGeneration* seq_state() override; protected: @@ -48,11 +56,13 @@ class WinscopeArgsParser : public util::ProtoToArgsParser::Delegate { uint64_t iid) override; private: + const bool support_json_; + const int64_t packet_timestamp_; + PacketSequenceStateGeneration* const sequence_state_; ArgsTracker::BoundInserter& inserter_; TraceStorage& storage_; }; -} // namespace trace_processor -} // namespace perfetto +} // namespace perfetto::trace_processor -#endif // SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_WINSCOPE_WINSCOPE_ARGS_PARSER_H_ +#endif // SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_ARGS_PARSER_H_ diff --git a/src/trace_processor/importers/proto/chrome_system_probes_parser.h b/src/trace_processor/importers/proto/chrome_system_probes_parser.h index ca688146bf..9c9397b7f0 100644 --- a/src/trace_processor/importers/proto/chrome_system_probes_parser.h +++ b/src/trace_processor/importers/proto/chrome_system_probes_parser.h @@ -43,7 +43,7 @@ class ChromeSystemProbesParser { // Maps a proto field number for memcounters in ProcessStats::Process to // their StringId. Keep kProcStatsProcessSize equal to 1 + max proto field // id of ProcessStats::Process. Also update SystemProbesParser. - static constexpr size_t kProcStatsProcessSize = 23; + static constexpr size_t kProcStatsProcessSize = 24; std::array proc_stats_process_names_{}; }; diff --git a/src/trace_processor/importers/proto/gpu_event_parser.cc b/src/trace_processor/importers/proto/gpu_event_parser.cc index 9022cbb02d..5664fba8c6 100644 --- a/src/trace_processor/importers/proto/gpu_event_parser.cc +++ b/src/trace_processor/importers/proto/gpu_event_parser.cc @@ -330,6 +330,7 @@ void GpuEventParser::ParseGpuRenderStageEvent( ConstBytes blob) { protos::pbzero::GpuRenderStageEvent::Decoder event(blob.data, blob.size); + int32_t pid = 0; if (event.has_specifications()) { protos::pbzero::GpuRenderStageEvent_Specifications::Decoder spec( event.specifications().data, event.specifications().size); @@ -349,6 +350,23 @@ void GpuEventParser::ParseGpuRenderStageEvent( context_->storage->InternString(stage.description()))); } } + if (spec.has_context_spec()) { + protos::pbzero::GpuRenderStageEvent_Specifications_ContextSpec::Decoder + context_spec(spec.context_spec()); + if (context_spec.has_pid()) { + pid = context_spec.pid(); + } + } + } + + if (event.has_context()) { + uint64_t context_id = event.context(); + auto* decoder = sequence_state->LookupInternedMessage< + protos::pbzero::InternedData::kGraphicsContextsFieldNumber, + protos::pbzero::InternedGraphicsContext>(context_id); + if (decoder) { + pid = decoder->pid(); + } } auto args_callback = [this, &event, @@ -468,7 +486,8 @@ void GpuEventParser::ParseGpuRenderStageEvent( row.command_buffer_name = command_buffer_name_id; row.submission_id = event.submission_id(); row.hw_queue_id = static_cast(hw_queue_id); - + row.upid = context_->process_tracker->GetOrCreateProcess( + static_cast(pid)); context_->slice_tracker->ScopedTyped( context_->storage->mutable_gpu_slice_table(), row, args_callback); } diff --git a/src/trace_processor/importers/proto/network_trace_module.cc b/src/trace_processor/importers/proto/network_trace_module.cc index dda8f7074e..d1516f937c 100644 --- a/src/trace_processor/importers/proto/network_trace_module.cc +++ b/src/trace_processor/importers/proto/network_trace_module.cc @@ -148,77 +148,98 @@ void NetworkTraceModule::ParseTracePacketData( void NetworkTraceModule::ParseGenericEvent( int64_t ts, int64_t dur, - protos::pbzero::NetworkPacketEvent::Decoder& evt, - std::function extra_args) { + int64_t length, + int64_t count, + protos::pbzero::NetworkPacketEvent::Decoder& evt) { // Tracks are per interface and per direction. const char* track_suffix = - evt.direction() == TrafficDirection::DIR_INGRESS ? " Received" - : evt.direction() == TrafficDirection::DIR_EGRESS ? " Transmitted" - : " DIR_UNKNOWN"; + evt.direction() == TrafficDirection::DIR_INGRESS ? "Received" + : evt.direction() == TrafficDirection::DIR_EGRESS ? "Transmitted" + : "DIR_UNKNOWN"; - base::StackString<64> name("%.*s%s", static_cast(evt.interface().size), + base::StackString<64> name("%.*s %s", static_cast(evt.interface().size), evt.interface().data, track_suffix); - StringId name_id = context_->storage->InternString(name.string_view()); + StringId track_name = context_->storage->InternString(name.string_view()); + StringId direction = context_->storage->InternString(track_suffix); + StringId iface = context_->storage->InternString(evt.interface()); + + if (!loaded_package_names_) { + loaded_package_names_ = true; + const auto& package_list = context_->storage->package_list_table(); + for (auto row = package_list.IterateRows(); row; ++row) { + package_names_.Insert(row.uid(), row.package_name()); + } + } // Android stores the app id in the lower part of the uid. The actual uid will // be `user_id * kPerUserRange + app_id`. For package lookup, we want app id. - int app_id = evt.uid() % kPerUserRange; + uint32_t app_id = evt.uid() % kPerUserRange; // Event titles are the package name, if available. - StringId title_id = kNullStringId; + StringId slice_name = kNullStringId; if (evt.uid() > 0) { - const auto& package_list = context_->storage->package_list_table(); - std::optional pkg_row = package_list.uid().IndexOf(app_id); - if (pkg_row) { - title_id = package_list.package_name()[*pkg_row]; + StringId* iter = package_names_.Find(app_id); + if (iter != nullptr) { + slice_name = *iter; } } // If the above fails, fall back to the uid. - if (title_id == kNullStringId) { + if (slice_name == kNullStringId) { base::StackString<32> title_str("uid=%" PRIu32, evt.uid()); - title_id = context_->storage->InternString(title_str.string_view()); + slice_name = context_->storage->InternString(title_str.string_view()); } TrackId track_id = context_->async_track_set_tracker->Scoped( - context_->async_track_set_tracker->InternGlobalTrackSet(name_id), ts, + context_->async_track_set_tracker->InternGlobalTrackSet(track_name), ts, dur); - context_->slice_tracker->Scoped( - ts, track_id, name_id, title_id, dur, [&](ArgsTracker::BoundInserter* i) { - StringId ip_proto; - switch (evt.ip_proto()) { - case kIpprotoTcp: - ip_proto = net_ipproto_tcp_; - break; - case kIpprotoUdp: - ip_proto = net_ipproto_udp_; - break; - case kIpprotoIcmp: - ip_proto = net_ipproto_icmp_; - break; - case kIpprotoIcmpv6: - ip_proto = net_ipproto_icmpv6_; - break; - default: { - base::StackString<32> proto("IPPROTO (%d)", evt.ip_proto()); - ip_proto = context_->storage->InternString(proto.string_view()); - } - } + tables::AndroidNetworkPacketsTable::Row actual_row; + actual_row.ts = ts; + actual_row.dur = dur; + actual_row.name = slice_name; + actual_row.track_id = track_id; + actual_row.category = track_name; + actual_row.iface = iface; + actual_row.direction = direction; + actual_row.packet_transport = GetIpProto(evt); + actual_row.packet_length = length; + actual_row.packet_count = count; + actual_row.socket_tag = evt.tag(); + actual_row.socket_uid = evt.uid(); + actual_row.socket_tag_str = context_->storage->InternString( + base::StackString<16>("0x%x", evt.tag()).string_view()); - i->AddArg(net_arg_ip_proto_, Variadic::String(ip_proto)); + if (evt.has_local_port()) { + actual_row.local_port = evt.local_port(); + } + if (evt.has_remote_port()) { + actual_row.remote_port = evt.remote_port(); + } + if (evt.has_icmp_type()) { + actual_row.packet_icmp_type = evt.icmp_type(); + } + if (evt.has_icmp_code()) { + actual_row.packet_icmp_code = evt.icmp_code(); + } + if (evt.has_tcp_flags()) { + actual_row.packet_tcp_flags = evt.tcp_flags(); + actual_row.packet_tcp_flags_str = context_->storage->InternString( + GetTcpFlagMask(evt.tcp_flags()).string_view()); + } + + context_->slice_tracker->ScopedTyped( + context_->storage->mutable_android_network_packets_table(), actual_row, + [&](ArgsTracker::BoundInserter* i) { + i->AddArg(net_arg_ip_proto_, + Variadic::String(actual_row.packet_transport)); i->AddArg(net_arg_uid_, Variadic::Integer(evt.uid())); - base::StackString<16> tag("0x%x", evt.tag()); - i->AddArg(net_arg_tag_, - Variadic::String( - context_->storage->InternString(tag.string_view()))); + i->AddArg(net_arg_tag_, Variadic::String(actual_row.socket_tag_str)); - if (evt.has_tcp_flags()) { - base::StackString<12> flags = GetTcpFlagMask(evt.tcp_flags()); + if (actual_row.packet_tcp_flags_str.has_value()) { i->AddArg(net_arg_tcp_flags_, - Variadic::String( - context_->storage->InternString(flags.string_view()))); + Variadic::String(*actual_row.packet_tcp_flags_str)); } if (evt.has_local_port()) { @@ -233,28 +254,41 @@ void NetworkTraceModule::ParseGenericEvent( if (evt.has_icmp_code()) { i->AddArg(net_arg_icmp_code_, Variadic::Integer(evt.icmp_code())); } - extra_args(i); + i->AddArg(net_arg_length_, Variadic::Integer(length)); + i->AddArg(packet_count_, Variadic::Integer(count)); }); } +StringId NetworkTraceModule::GetIpProto(NetworkPacketEvent::Decoder& evt) { + switch (evt.ip_proto()) { + case kIpprotoTcp: + return net_ipproto_tcp_; + case kIpprotoUdp: + return net_ipproto_udp_; + case kIpprotoIcmp: + return net_ipproto_icmp_; + case kIpprotoIcmpv6: + return net_ipproto_icmpv6_; + default: + return context_->storage->InternString( + base::StackString<32>("IPPROTO (%d)", evt.ip_proto()).string_view()); + } +} + void NetworkTraceModule::ParseNetworkPacketEvent(int64_t ts, ConstBytes blob) { NetworkPacketEvent::Decoder event(blob); - ParseGenericEvent(ts, /*dur=*/0, event, [&](ArgsTracker::BoundInserter* i) { - i->AddArg(net_arg_length_, Variadic::Integer(event.length())); - }); + ParseGenericEvent(ts, /*dur=*/0, event.length(), /*count=*/1, event); } void NetworkTraceModule::ParseNetworkPacketBundle(int64_t ts, ConstBytes blob) { NetworkPacketBundle::Decoder event(blob); NetworkPacketEvent::Decoder ctx(event.ctx()); int64_t dur = static_cast(event.total_duration()); + int64_t length = static_cast(event.total_length()); // Any bundle that makes it through tokenization must be aggregated bundles // with total packets/total length. - ParseGenericEvent(ts, dur, ctx, [&](ArgsTracker::BoundInserter* i) { - i->AddArg(net_arg_length_, Variadic::UnsignedInteger(event.total_length())); - i->AddArg(packet_count_, Variadic::UnsignedInteger(event.total_packets())); - }); + ParseGenericEvent(ts, dur, length, event.total_packets(), ctx); } void NetworkTraceModule::PushPacketBufferForSort( diff --git a/src/trace_processor/importers/proto/network_trace_module.h b/src/trace_processor/importers/proto/network_trace_module.h index 984ea822cb..579435676c 100644 --- a/src/trace_processor/importers/proto/network_trace_module.h +++ b/src/trace_processor/importers/proto/network_trace_module.h @@ -19,6 +19,7 @@ #include +#include "perfetto/ext/base/flat_hash_map.h" #include "perfetto/protozero/scattered_heap_buffer.h" #include "src/trace_processor/importers/common/args_tracker.h" #include "src/trace_processor/importers/common/parser_types.h" @@ -56,11 +57,11 @@ class NetworkTraceModule : public ProtoImporterModule { uint32_t field_id) override; private: - void ParseGenericEvent( - int64_t ts, - int64_t dur, - protos::pbzero::NetworkPacketEvent::Decoder& evt, - std::function extra_args); + void ParseGenericEvent(int64_t ts, + int64_t dur, + int64_t length, + int64_t count, + protos::pbzero::NetworkPacketEvent::Decoder& evt); void ParseNetworkPacketEvent(int64_t ts, protozero::ConstBytes blob); void ParseNetworkPacketBundle(int64_t ts, protozero::ConstBytes blob); @@ -70,9 +71,14 @@ class NetworkTraceModule : public ProtoImporterModule { void PushPacketBufferForSort(int64_t timestamp, RefPtr state); + StringId GetIpProto(protos::pbzero::NetworkPacketEvent::Decoder& evt); + TraceProcessorContext* context_; protozero::HeapBuffered packet_buffer_; + bool loaded_package_names_ = false; + base::FlatHashMap package_names_; + const StringId net_arg_length_; const StringId net_arg_ip_proto_; const StringId net_arg_tcp_flags_; 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 f174b352ac..317120eb21 100644 --- a/src/trace_processor/importers/proto/network_trace_module_unittest.cc +++ b/src/trace_processor/importers/proto/network_trace_module_unittest.cc @@ -20,6 +20,7 @@ #include "src/trace_processor/importers/common/args_translation_table.h" #include "src/trace_processor/importers/common/async_track_set_tracker.h" #include "src/trace_processor/importers/common/global_args_tracker.h" +#include "src/trace_processor/importers/common/process_track_translation_table.h" #include "src/trace_processor/importers/common/slice_tracker.h" #include "src/trace_processor/importers/common/slice_translation_table.h" #include "src/trace_processor/importers/common/track_tracker.h" @@ -45,6 +46,8 @@ class NetworkTraceModuleTest : public testing::Test { context_.args_tracker.reset(new ArgsTracker(&context_)); context_.global_args_tracker.reset(new GlobalArgsTracker(storage_)); context_.slice_translation_table.reset(new SliceTranslationTable(storage_)); + context_.process_track_translation_table.reset( + new ProcessTrackTranslationTable(storage_)); context_.args_translation_table.reset(new ArgsTranslationTable(storage_)); context_.async_track_set_tracker.reset(new AsyncTrackSetTracker(&context_)); context_.proto_trace_parser.reset(new ProtoTraceParserImpl(&context_)); @@ -53,13 +56,14 @@ class NetworkTraceModuleTest : public testing::Test { } util::Status TokenizeAndParse() { - context_.chunk_reader.reset(new ProtoTraceReader(&context_)); + context_.chunk_readers.push_back( + std::make_unique(&context_)); trace_->Finalize(); std::vector v = trace_.SerializeAsArray(); trace_.Reset(); - auto status = context_.chunk_reader->Parse( + auto status = context_.chunk_readers.back()->Parse( TraceBlobView(TraceBlob::CopyFrom(v.data(), v.size()))); context_.sorter->ExtractEventsForced(); context_.slice_tracker->FlushPendingSlices(); @@ -177,8 +181,8 @@ TEST_F(NetworkTraceModuleTest, TokenizeAndParseAggregateBundle) { EXPECT_EQ(slices.ts()[0], 123); EXPECT_EQ(slices.dur()[0], 10); - EXPECT_TRUE(HasArg(1u, "packet_length", Variadic::UnsignedInteger(172))); - EXPECT_TRUE(HasArg(1u, "packet_count", Variadic::UnsignedInteger(2))); + EXPECT_TRUE(HasArg(1u, "packet_length", Variadic::Integer(172))); + EXPECT_TRUE(HasArg(1u, "packet_count", Variadic::Integer(2))); } } // namespace diff --git a/src/trace_processor/importers/proto/perf_sample_tracker.cc b/src/trace_processor/importers/proto/perf_sample_tracker.cc index e47239e1c9..52ff1152b6 100644 --- a/src/trace_processor/importers/proto/perf_sample_tracker.cc +++ b/src/trace_processor/importers/proto/perf_sample_tracker.cc @@ -129,7 +129,7 @@ PerfSampleTracker::SamplingStreamInfo PerfSampleTracker::GetSamplingStreamInfo( seq_it = seq_state_.emplace(seq_id, CreatePerfSession()).first; } SequenceState* seq_state = &seq_it->second; - uint32_t session_id = seq_state->perf_session_id; + tables::PerfSessionTable::Id session_id = seq_state->perf_session_id; auto cpu_it = seq_state->per_cpu.find(cpu); if (cpu_it != seq_state->per_cpu.end()) @@ -162,15 +162,19 @@ PerfSampleTracker::SamplingStreamInfo PerfSampleTracker::GetSamplingStreamInfo( // tracing session). if (perf_defaults.has_value() && perf_defaults->process_shard_count() > 0) { context_->storage->SetIndexedStats( - stats::perf_process_shard_count, static_cast(session_id), + stats::perf_process_shard_count, static_cast(session_id.value), static_cast(perf_defaults->process_shard_count())); context_->storage->SetIndexedStats( - stats::perf_chosen_process_shard, static_cast(session_id), + stats::perf_chosen_process_shard, static_cast(session_id.value), static_cast(perf_defaults->chosen_process_shard())); } return {session_id, timebase_track_id}; } +tables::PerfSessionTable::Id PerfSampleTracker::CreatePerfSession() { + return context_->storage->mutable_perf_session_table()->Insert({}).id; +} + } // namespace trace_processor } // namespace perfetto diff --git a/src/trace_processor/importers/proto/perf_sample_tracker.h b/src/trace_processor/importers/proto/perf_sample_tracker.h index c1e62e25a3..178683db3f 100644 --- a/src/trace_processor/importers/proto/perf_sample_tracker.h +++ b/src/trace_processor/importers/proto/perf_sample_tracker.h @@ -23,6 +23,7 @@ #include #include "src/trace_processor/storage/trace_storage.h" +#include "src/trace_processor/tables/profiler_tables_py.h" namespace perfetto { namespace protos { @@ -36,10 +37,11 @@ class TraceProcessorContext; class PerfSampleTracker { public: struct SamplingStreamInfo { - uint32_t perf_session_id = 0; + tables::PerfSessionTable::Id perf_session_id; TrackId timebase_track_id = kInvalidTrackId; - SamplingStreamInfo(uint32_t _perf_session_id, TrackId _timebase_track_id) + SamplingStreamInfo(tables::PerfSessionTable::Id _perf_session_id, + TrackId _timebase_track_id) : perf_session_id(_perf_session_id), timebase_track_id(_timebase_track_id) {} }; @@ -52,8 +54,6 @@ class PerfSampleTracker { uint32_t cpu, protos::pbzero::TracePacketDefaults_Decoder* nullable_defaults); - uint32_t CreatePerfSession() { return next_perf_session_id_++; } - private: struct CpuSequenceState { TrackId timebase_track_id = kInvalidTrackId; @@ -63,15 +63,16 @@ class PerfSampleTracker { }; struct SequenceState { - uint32_t perf_session_id = 0; + tables::PerfSessionTable::Id perf_session_id; std::unordered_map per_cpu; - SequenceState(uint32_t _perf_session_id) + explicit SequenceState(tables::PerfSessionTable::Id _perf_session_id) : perf_session_id(_perf_session_id) {} }; + tables::PerfSessionTable::Id CreatePerfSession(); + std::unordered_map seq_state_; - uint32_t next_perf_session_id_ = 0; TraceProcessorContext* const context_; }; diff --git a/src/trace_processor/importers/proto/perf_sample_tracker_unittest.cc b/src/trace_processor/importers/proto/perf_sample_tracker_unittest.cc index 7515a04a50..df1fba0cc2 100644 --- a/src/trace_processor/importers/proto/perf_sample_tracker_unittest.cc +++ b/src/trace_processor/importers/proto/perf_sample_tracker_unittest.cc @@ -215,10 +215,10 @@ TEST_F(PerfSampleTrackerTest, ProcessShardingStatsEntries) { std::optional shard_count = context.storage->GetIndexedStats( stats::perf_process_shard_count, - static_cast(stream.perf_session_id)); + static_cast(stream.perf_session_id.value)); std::optional chosen_shard = context.storage->GetIndexedStats( stats::perf_chosen_process_shard, - static_cast(stream.perf_session_id)); + static_cast(stream.perf_session_id.value)); ASSERT_TRUE(shard_count.has_value()); EXPECT_EQ(shard_count.value(), 8); @@ -227,10 +227,10 @@ TEST_F(PerfSampleTrackerTest, ProcessShardingStatsEntries) { std::optional shard_count2 = context.storage->GetIndexedStats( stats::perf_process_shard_count, - static_cast(stream.perf_session_id)); + static_cast(stream.perf_session_id.value)); std::optional chosen_shard2 = context.storage->GetIndexedStats( stats::perf_chosen_process_shard, - static_cast(stream.perf_session_id)); + static_cast(stream.perf_session_id.value)); ASSERT_TRUE(shard_count2.has_value()); EXPECT_EQ(shard_count2.value(), 8); diff --git a/src/trace_processor/importers/proto/pigweed_detokenizer.cc b/src/trace_processor/importers/proto/pigweed_detokenizer.cc new file mode 100644 index 0000000000..b3a97b8798 --- /dev/null +++ b/src/trace_processor/importers/proto/pigweed_detokenizer.cc @@ -0,0 +1,313 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "src/trace_processor/importers/proto/pigweed_detokenizer.h" + +#include +#include +#include + +#include "perfetto/ext/base/flat_hash_map.h" +#include "perfetto/ext/base/status_or.h" +#include "perfetto/ext/base/string_utils.h" +#include "perfetto/protozero/field.h" + +// Removed date for an entry that is live. +static constexpr uint32_t kDateRemovedNever = 0xFFFFFFFF; + +static constexpr uint32_t kFormatBufferSize = 32; + +static constexpr std::array kHeaderPrefix = {'T', 'O', 'K', 'E', + 'N', 'S', '\0', '\0'}; + +struct Header { + std::array magic; + uint16_t version; + uint32_t entry_count; + uint32_t reserved; +}; + +struct Entry { + uint32_t token; + uint32_t date_removed; +}; + +static constexpr uint32_t ReadUint32(const uint8_t* bytes) { + return static_cast(bytes[0]) | + static_cast(bytes[1]) << 8 | + static_cast(bytes[2]) << 16 | + static_cast(bytes[3]) << 24; +} + +namespace perfetto::trace_processor::pigweed { + +PigweedDetokenizer CreateNullDetokenizer() { + return PigweedDetokenizer{base::FlatHashMap()}; +} + +base::StatusOr CreateDetokenizer( + const protozero::ConstBytes& bytes) { + base::FlatHashMap tokens; + // See Pigweed's token_database.h for a description of the format, + // but tl;dr we have: + // + // * Header. + // * Array of {token, date_removed} structs. + // * Matching table of null-terminated strings. + + if (bytes.size < sizeof(Header)) { + return base::ErrStatus("Truncated Pigweed database (no header)"); + } + + for (size_t i = 0; i < kHeaderPrefix.size(); ++i) { + if (bytes.data[i] != kHeaderPrefix[i]) { + return base::ErrStatus("Pigweed database has wrong magic"); + } + } + + size_t entry_count = ReadUint32(bytes.data + offsetof(Header, entry_count)); + + size_t entry_ix = sizeof(Header); + size_t string_ix = sizeof(Header) + entry_count * sizeof(Entry); + + if (string_ix > bytes.size) { + return base::ErrStatus("Truncated Pigweed database (no string table)"); + } + + for (size_t i = 0; i < entry_count; ++i) { + uint32_t token = ReadUint32(bytes.data + entry_ix); + uint32_t date_removed = + ReadUint32(bytes.data + entry_ix + offsetof(Entry, date_removed)); + + const uint8_t* next_null_char = static_cast( + memchr(bytes.data + string_ix, '\0', bytes.size - string_ix)); + const size_t next_string_ix = + static_cast(next_null_char - bytes.data) + 1; + if (next_string_ix > bytes.size) { + return base::ErrStatus( + "Truncated Pigweed database (string table not terminated)"); + } + + if (date_removed == kDateRemovedNever) { + std::string str(reinterpret_cast(bytes.data + string_ix)); + + tokens[token] = FormatString(str); + } + + entry_ix += sizeof(Entry); + string_ix = next_string_ix; + } + + return PigweedDetokenizer(std::move(tokens)); +} + +PigweedDetokenizer::PigweedDetokenizer( + base::FlatHashMap tokens) + : tokens_(std::move(tokens)) {} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-nonliteral" +#endif // defined(__GNUC__) || defined(__clang__) + +base::StatusOr PigweedDetokenizer::Detokenize( + const protozero::ConstBytes& bytes) const { + if (bytes.size < sizeof(uint32_t)) { + return base::ErrStatus("Truncated Pigweed payload"); + } + + const uint32_t token = ReadUint32(bytes.data); + + FormatString* format = tokens_.Find(token); + if (!format) { + return DetokenizedString(token, + FormatString(std::string("Token not found"))); + } + + const uint8_t* ptr = bytes.data + sizeof(uint32_t); + + std::vector> args; + std::vector args_formatted; + for (Arg arg : format->args()) { + char buffer[kFormatBufferSize]; + const char* fmt = arg.format.c_str(); + size_t formatted_size; + + if (arg.type == kFloat) { + if (ptr + sizeof(float) > bytes.data + bytes.size) { + return base::ErrStatus("Truncated Pigweed float"); + } + + float value_float; + memcpy(&value_float, ptr, sizeof(value_float)); + ptr += sizeof(value_float); + double value = static_cast(value_float); + args.push_back(value); + formatted_size = + perfetto::base::SprintfTrunc(buffer, kFormatBufferSize, fmt, value); + } else { + uint64_t value; + auto old_ptr = ptr; + ptr = protozero::proto_utils::ParseVarInt(ptr, bytes.data + bytes.size, + &value); + if (old_ptr == ptr) { + return base::ErrStatus("Truncated Pigweed varint"); + } + if (arg.type == kSignedInt) { + int64_t value_signed; + memcpy(&value_signed, &value, sizeof(value_signed)); + args.push_back(value_signed); + formatted_size = perfetto::base::SprintfTrunc(buffer, kFormatBufferSize, + fmt, value_signed); + } else { + if (arg.type == kUnsigned32) { + value &= 0xFFFFFFFFu; + } + args.push_back(value); + formatted_size = + perfetto::base::SprintfTrunc(buffer, kFormatBufferSize, fmt, value); + } + } + if (formatted_size == kFormatBufferSize - 1) { + return base::ErrStatus("Exceeded buffer size for number"); + } + args_formatted.push_back(std::string(buffer, formatted_size)); + if (ptr >= bytes.data + bytes.size) { + break; + } + } + + return DetokenizedString(token, *format, args, args_formatted); +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic pop +#endif // defined(__GNUC__) || defined(__clang__) + +DetokenizedString::DetokenizedString(const uint32_t token, + FormatString format_string) + : token_(token), format_string_(std::move(format_string)) {} + +DetokenizedString::DetokenizedString( + const uint32_t token, + FormatString format_string, + std::vector> args, + std::vector args_formatted) + : token_(token), + format_string_(format_string), + args_(args), + args_formatted_(args_formatted) {} + +std::string DetokenizedString::Format() const { + const auto args = format_string_.args(); + const auto fmt = format_string_.template_str(); + if (args.size() == 0) { + return fmt; + } + + std::string result; + + result.append(fmt.substr(0, args[0].begin)); + + for (size_t i = 0; i < args.size(); i++) { + result.append(args_formatted_[i]); + if (i < args.size() - 1) { + result.append(fmt.substr(args[i].end, args[i + 1].begin - args[i].end)); + } else { + result.append(fmt.substr(args[i].end, fmt.size() - args[i].end)); + } + } + + return result; +} + +static size_t SkipFlags(std::string fmt, size_t ix) { + while (fmt[ix] == '-' || fmt[ix] == '+' || fmt[ix] == '#' || fmt[ix] == ' ' || + fmt[ix] == '0') { + ix += 1; + } + return ix; +} + +static size_t SkipAsteriskOrInteger(std::string fmt, size_t ix) { + if (fmt[ix] == '*') { + return ix + 1; + } + + ix = (fmt[ix] == '-' || fmt[ix] == '+') ? ix + 1 : ix; + + while (std::isdigit(fmt[ix])) { + ix += 1; + } + return ix; +} + +static std::array ReadLengthModifier(std::string fmt, size_t ix) { + // Check for ll or hh. + if (fmt[ix] == fmt[ix + 1] && (fmt[ix] == 'l' || fmt[ix] == 'h')) { + return {fmt[ix], fmt[ix + 1]}; + } + if (std::strchr("hljztL", fmt[ix]) != nullptr) { + return {fmt[ix]}; + } + return {}; +} + +FormatString::FormatString(std::string format) : template_str_(format) { + size_t fmt_start = 0; + for (size_t i = 0; i < format.size(); i++) { + if (format[i] == '%') { + fmt_start = i; + i += 1; + + i = SkipFlags(format, i); + + // Field width. + i = SkipAsteriskOrInteger(format, i); + + // Precision. + if (format[i] == '.') { + i += 1; + i = SkipAsteriskOrInteger(format, i); + } + + // Length modifier + const std::array length = ReadLengthModifier(format, i); + i += (length[0] == '\0' ? 0 : 1) + (length[1] == '\0' ? 0 : 1); + + const char spec = format[i]; + const std::string arg_format = + format.substr(fmt_start, i - fmt_start + 1); + if (spec == 'c' || spec == 'd' || spec == 'i') { + args_.push_back(Arg{kSignedInt, arg_format, fmt_start, i + 1}); + } else if (strchr("oxXup", spec) != nullptr) { + // Size matters for unsigned integers. + if (length[0] == 'j' || length[1] == 'l') { + args_.push_back(Arg{kUnsigned64, arg_format, fmt_start, i + 1}); + } else { + args_.push_back(Arg{kUnsigned32, arg_format, fmt_start, i + 1}); + } + } else if (strchr("fFeEaAgG", spec) != nullptr) { + args_.push_back(Arg{kFloat, arg_format, fmt_start, i + 1}); + } else { + // Parsing failed. + // We ignore this silently for now. + } + } + } +} + +} // namespace perfetto::trace_processor::pigweed diff --git a/src/trace_processor/importers/proto/pigweed_detokenizer.h b/src/trace_processor/importers/proto/pigweed_detokenizer.h new file mode 100644 index 0000000000..6a0411fe62 --- /dev/null +++ b/src/trace_processor/importers/proto/pigweed_detokenizer.h @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_PIGWEED_DETOKENIZER_H_ +#define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_PIGWEED_DETOKENIZER_H_ + +#include +#include + +#include "perfetto/ext/base/flat_hash_map.h" +#include "perfetto/ext/base/status_or.h" +#include "perfetto/protozero/field.h" + +namespace perfetto::trace_processor::pigweed { + +// We only distinguish between the int types that we need to; we need +// to know different lengths for unsigned due to varint encoding. +// Strings are not supported. +enum ArgType { kSignedInt, kUnsigned32, kUnsigned64, kFloat }; + +// Representation of an arg in a formatting string: where it is, +// its contents, and its type. +struct Arg { + ArgType type; + std::string format; + size_t begin; + size_t end; +}; + +// A parsed format string from the database. +class FormatString { + public: + FormatString() = default; + FormatString(std::string template_str); + + const std::string& template_str() const { return template_str_; } + + const std::vector& args() const { return args_; } + + private: + std::string template_str_; + std::vector args_; +}; + +// A string that we have detokenized, along with any information we gathered +// along the way. +class DetokenizedString { + public: + explicit DetokenizedString(const uint32_t token, FormatString str_template); + + explicit DetokenizedString( + const uint32_t token, + FormatString str_template, + std::vector> args, + std::vector args_formatted); + + // The fully formatted string. + std::string Format() const; + + // The printf template used to format the string. + const std::string& template_str() const { + return format_string_.template_str(); + } + + // The ID of the template used to format the string. + uint32_t token() const { return token_; } + + // Numerical args in the string, in order. + const std::vector>& args() const { + return args_; + } + + private: + uint32_t token_; + FormatString format_string_; + // We don't bother holding 32 bit versions, just promote them. + std::vector> args_; + std::vector args_formatted_; +}; + +class PigweedDetokenizer { + public: + explicit PigweedDetokenizer(base::FlatHashMap tokens); + base::StatusOr Detokenize( + const protozero::ConstBytes& bytes) const; + + private: + base::FlatHashMap tokens_; +}; + +PigweedDetokenizer CreateNullDetokenizer(); + +base::StatusOr CreateDetokenizer( + const protozero::ConstBytes& blob); + +} // namespace perfetto::trace_processor::pigweed + +#endif // SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_PIGWEED_DETOKENIZER_H_ diff --git a/src/trace_processor/importers/proto/pixel_modem_module.cc b/src/trace_processor/importers/proto/pixel_modem_module.cc new file mode 100644 index 0000000000..bb4206a552 --- /dev/null +++ b/src/trace_processor/importers/proto/pixel_modem_module.cc @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "src/trace_processor/importers/proto/pixel_modem_module.h" + +#include "perfetto/base/build_config.h" +#include "perfetto/ext/base/string_writer.h" +#include "perfetto/protozero/scattered_heap_buffer.h" +#include "src/trace_processor/importers/common/machine_tracker.h" +#include "src/trace_processor/importers/common/track_tracker.h" + +#include "src/trace_processor/importers/proto/packet_sequence_state_generation.h" +#include "src/trace_processor/importers/proto/pixel_modem_parser.h" +#include "src/trace_processor/sorter/trace_sorter.h" + +#include "protos/perfetto/common/android_energy_consumer_descriptor.pbzero.h" +#include "protos/perfetto/trace/android/pixel_modem_events.pbzero.h" +#include "protos/perfetto/trace/trace_packet.pbzero.h" + +namespace perfetto { +namespace trace_processor { + +using perfetto::protos::pbzero::TracePacket; + +PixelModemModule::PixelModemModule(TraceProcessorContext* context) + : context_(context), parser_(context) { + RegisterForField(TracePacket::kPixelModemEventsFieldNumber, context); + RegisterForField(TracePacket::kPixelModemTokenDatabaseFieldNumber, context); +} + +ModuleResult PixelModemModule::TokenizePacket( + const protos::pbzero::TracePacket_Decoder& decoder, + TraceBlobView* /* packet */, + int64_t /* packet_timestamp */, + RefPtr state, + uint32_t field_id) { + // The database packet does not have a timestamp so needs to be handled at + // the tokenization phase. + if (field_id == TracePacket::kPixelModemTokenDatabaseFieldNumber) { + auto db = decoder.pixel_modem_token_database(); + protos::pbzero::PixelModemTokenDatabase::Decoder database(db); + + base::Status status = parser_.SetDatabase(database.database()); + if (status.ok()) { + return ModuleResult::Handled(); + } else { + return ModuleResult::Error(status.message()); + } + } + + if (field_id != TracePacket::kPixelModemEventsFieldNumber) { + return ModuleResult::Ignored(); + } + + // Pigweed events are similar to ftrace in that they have many events, each + // with their own timestamp, packed inside a single TracePacket. This means + // that, similar to ftrace, we need to unpack them and individually sort them. + + // However, as these events are not perf sensitive, it's not worth adding + // a lot of machinery to shepherd these events through the sorting queues + // in a special way. Therefore, we just forge new packets and sort them as if + // they came from the underlying trace. + auto events = decoder.pixel_modem_events(); + protos::pbzero::PixelModemEvents::Decoder evt(events.data, events.size); + + // To reduce overhead we store events and timestamps in parallel lists. + // We also store timestamps within a packet as deltas. + auto ts_it = evt.event_time_nanos(); + int64_t ts = 0; + for (auto it = evt.events(); it && ts_it; ++it, ++ts_it) { + protozero::ConstBytes event_bytes = *it; + ts += *ts_it; + + protozero::HeapBuffered data_packet; + data_packet->set_pixel_modem_events()->add_events(event_bytes); + std::vector vec = data_packet.SerializeAsArray(); + TraceBlob blob = TraceBlob::CopyFrom(vec.data(), vec.size()); + context_->sorter->PushTracePacket(ts, state, TraceBlobView(std::move(blob)), + context_->machine_id()); + } + + return ModuleResult::Handled(); +} + +void PixelModemModule::ParseTracePacketData(const TracePacket::Decoder& decoder, + int64_t ts, + const TracePacketData&, + uint32_t field_id) { + if (field_id != TracePacket::kPixelModemEventsFieldNumber) { + return; + } + + auto events = decoder.pixel_modem_events(); + protos::pbzero::PixelModemEvents::Decoder evt(events.data, events.size); + auto it = evt.events(); + + // We guarantee above there will be exactly one event. + parser_.ParseEvent(ts, *it); +} + +} // namespace trace_processor +} // namespace perfetto diff --git a/src/trace_processor/importers/proto/pixel_modem_module.h b/src/trace_processor/importers/proto/pixel_modem_module.h new file mode 100644 index 0000000000..15cea95b3b --- /dev/null +++ b/src/trace_processor/importers/proto/pixel_modem_module.h @@ -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. + */ + +#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_PIXEL_MODEM_MODULE_H_ +#define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_PIXEL_MODEM_MODULE_H_ + +#include "src/trace_processor/importers/proto/packet_sequence_state_generation.h" +#include "src/trace_processor/importers/proto/pixel_modem_parser.h" +#include "src/trace_processor/importers/proto/proto_importer_module.h" + +#include "protos/perfetto/config/trace_config.pbzero.h" +#include "protos/perfetto/trace/trace_packet.pbzero.h" + +namespace perfetto { +namespace trace_processor { + +class PixelModemModule : public ProtoImporterModule { + public: + explicit PixelModemModule(TraceProcessorContext* context); + + ModuleResult TokenizePacket(const protos::pbzero::TracePacket_Decoder&, + TraceBlobView* packet, + int64_t packet_timestamp, + RefPtr, + uint32_t field_id) override; + + void ParseTracePacketData(const protos::pbzero::TracePacket_Decoder& decoder, + int64_t ts, + const TracePacketData&, + uint32_t field_id) override; + + private: + TraceProcessorContext* context_ = nullptr; + PixelModemParser parser_; +}; + +} // namespace trace_processor +} // namespace perfetto + +#endif // SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_PIXEL_MODEM_MODULE_H_ diff --git a/src/trace_processor/importers/proto/pixel_modem_parser.cc b/src/trace_processor/importers/proto/pixel_modem_parser.cc new file mode 100644 index 0000000000..844c7ba512 --- /dev/null +++ b/src/trace_processor/importers/proto/pixel_modem_parser.cc @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "src/trace_processor/importers/proto/pixel_modem_parser.h" + +#include +#include + +#include "perfetto/ext/base/string_utils.h" +#include "perfetto/protozero/field.h" +#include "src/trace_processor/importers/common/async_track_set_tracker.h" +#include "src/trace_processor/importers/common/slice_tracker.h" +#include "src/trace_processor/importers/proto/pigweed_detokenizer.h" +#include "src/trace_processor/storage/trace_storage.h" +#include "src/trace_processor/util/status_macros.h" + +namespace perfetto::trace_processor { + +namespace { + +constexpr std::string_view kKeyDelimiterStart = "\u25A0"; +constexpr std::string_view kKeyDelimiterEnd = "\u2666"; +constexpr std::string_view kKeyDomain = "domain"; +constexpr std::string_view kKeyFormat = "format"; +constexpr std::string_view kModemNamePrefix = "Pixel Modem Events: "; +constexpr std::string_view kModemName = "Pixel Modem Events"; + +// Modem inputs in particular have this key-value encoding. It's not a Pigweed +// thing. +std::map SplitUpModemString(std::string input) { + auto delimStart = std::string(kKeyDelimiterStart); + auto delimEnd = std::string(kKeyDelimiterEnd); + + std::map result; + + std::vector pairs = base::SplitString(input, delimStart); + for (auto it = pairs.begin(); it != pairs.end(); it++) { + std::vector pair = base::SplitString(*it, delimEnd); + if (pair.size() >= 2) { + result.insert({pair[0], pair[1]}); + } + } + + return result; +} + +} // namespace + +PixelModemParser::PixelModemParser(TraceProcessorContext* context) + : context_(context), + detokenizer_(pigweed::CreateNullDetokenizer()), + template_id_(context->storage->InternString("raw_template")), + token_id_(context->storage->InternString("token_id")) {} + +PixelModemParser::~PixelModemParser() = default; + +base::Status PixelModemParser::SetDatabase(protozero::ConstBytes blob) { + ASSIGN_OR_RETURN(detokenizer_, pigweed::CreateDetokenizer(blob)); + return base::OkStatus(); +} + +base::Status PixelModemParser::ParseEvent(int64_t ts, + protozero::ConstBytes blob) { + ASSIGN_OR_RETURN(pigweed::DetokenizedString detokenized_str, + detokenizer_.Detokenize(blob)); + + std::string event = detokenized_str.Format(); + + auto map = SplitUpModemString(event); + auto domain = map.find(std::string(kKeyDomain)); + auto format = map.find(std::string(kKeyFormat)); + + std::string track_name = domain == map.end() + ? std::string(kModemName) + : std::string(kModemNamePrefix) + domain->second; + std::string slice_name = format == map.end() ? event : format->second; + + StringId track_name_id = context_->storage->InternString(track_name.c_str()); + StringId slice_name_id = context_->storage->InternString(slice_name.c_str()); + auto set_id = + context_->async_track_set_tracker->InternGlobalTrackSet(track_name_id); + TrackId id = context_->async_track_set_tracker->Scoped(set_id, ts, 0); + + context_->slice_tracker->Scoped( + ts, id, kNullStringId, slice_name_id, 0, + [this, &detokenized_str](ArgsTracker::BoundInserter* inserter) { + inserter->AddArg(template_id_, + Variadic::String(context_->storage->InternString( + detokenized_str.template_str().c_str()))); + inserter->AddArg(token_id_, Variadic::Integer(detokenized_str.token())); + auto pw_args = detokenized_str.args(); + for (size_t i = 0; i < pw_args.size(); i++) { + StringId arg_name = context_->storage->InternString( + ("pw_token_" + std::to_string(detokenized_str.token()) + ".arg_" + + std::to_string(i)) + .c_str()); + auto arg = pw_args[i]; + if (int64_t* int_arg = std::get_if(&arg)) { + inserter->AddArg(arg_name, Variadic::Integer(*int_arg)); + } else if (uint64_t* uint_arg = std::get_if(&arg)) { + inserter->AddArg(arg_name, Variadic::UnsignedInteger(*uint_arg)); + } else { + inserter->AddArg(arg_name, Variadic::Real(std::get(arg))); + } + } + }); + + return base::OkStatus(); +} + +} // namespace perfetto::trace_processor diff --git a/src/trace_processor/importers/proto/pixel_modem_parser.h b/src/trace_processor/importers/proto/pixel_modem_parser.h new file mode 100644 index 0000000000..65c8e678be --- /dev/null +++ b/src/trace_processor/importers/proto/pixel_modem_parser.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_PIXEL_MODEM_PARSER_H_ +#define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_PIXEL_MODEM_PARSER_H_ + +#include "perfetto/protozero/field.h" +#include "src/trace_processor/importers/proto/pigweed_detokenizer.h" +#include "src/trace_processor/storage/trace_storage.h" +#include "src/trace_processor/types/trace_processor_context.h" + +namespace perfetto { +namespace trace_processor { + +class TraceProcessorContext; + +class PixelModemParser { + public: + explicit PixelModemParser(TraceProcessorContext* context); + ~PixelModemParser(); + + base::Status SetDatabase(protozero::ConstBytes); + base::Status ParseEvent(int64_t, protozero::ConstBytes); + + private: + TraceProcessorContext* context_ = nullptr; + pigweed::PigweedDetokenizer detokenizer_; + + const StringId template_id_; + const StringId token_id_; +}; + +} // namespace trace_processor +} // namespace perfetto + +#endif // SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_PIXEL_MODEM_PARSER_H_ diff --git a/src/trace_processor/importers/proto/profile_module.cc b/src/trace_processor/importers/proto/profile_module.cc index 55e3547bd5..a5e3672d92 100644 --- a/src/trace_processor/importers/proto/profile_module.cc +++ b/src/trace_processor/importers/proto/profile_module.cc @@ -15,6 +15,7 @@ */ #include "src/trace_processor/importers/proto/profile_module.h" +#include #include #include "perfetto/base/logging.h" @@ -238,7 +239,7 @@ void ProfileModule::ParsePerfSample( PerfSample::ProducerEvent::PROFILER_STOP_GUARDRAIL) { context_->storage->SetIndexedStats( stats::perf_guardrail_stop_ts, - static_cast(sampling_stream.perf_session_id), ts); + static_cast(sampling_stream.perf_session_id.value), ts); } return; } @@ -454,13 +455,17 @@ void ProfileModule::ParseModuleSymbols(ConstBytes blob) { ArgsTranslationTable::SourceLocation last_location; for (auto line_it = address_symbols.lines(); line_it; ++line_it) { protos::pbzero::Line::Decoder line(*line_it); + auto file_name = line.source_file_name(); context_->storage->mutable_symbol_table()->Insert( {symbol_set_id, context_->storage->InternString(line.function_name()), - context_->storage->InternString(line.source_file_name()), - line.line_number()}); + file_name.size == 0 ? kNullStringId + : context_->storage->InternString(file_name), + line.has_line_number() && file_name.size != 0 + ? std::make_optional(line.line_number()) + : std::nullopt}); last_location = ArgsTranslationTable::SourceLocation{ - line.source_file_name().ToStdString(), - line.function_name().ToStdString(), line.line_number()}; + file_name.ToStdString(), line.function_name().ToStdString(), + line.line_number()}; has_lines = true; } if (!has_lines) { diff --git a/src/trace_processor/importers/proto/proto_trace_parser_impl.cc b/src/trace_processor/importers/proto/proto_trace_parser_impl.cc index 9d318a1c82..8a11d5d8af 100644 --- a/src/trace_processor/importers/proto/proto_trace_parser_impl.cc +++ b/src/trace_processor/importers/proto/proto_trace_parser_impl.cc @@ -29,8 +29,8 @@ #include "perfetto/ext/base/uuid.h" #include "src/trace_processor/importers/common/args_tracker.h" +#include "src/trace_processor/importers/common/cpu_tracker.h" #include "src/trace_processor/importers/common/event_tracker.h" -#include "src/trace_processor/importers/common/machine_tracker.h" #include "src/trace_processor/importers/common/metadata_tracker.h" #include "src/trace_processor/importers/common/parser_types.h" #include "src/trace_processor/importers/common/process_tracker.h" @@ -44,7 +44,6 @@ #include "src/trace_processor/types/trace_processor_context.h" #include "src/trace_processor/types/variadic.h" -#include "protos/perfetto/common/trace_stats.pbzero.h" #include "protos/perfetto/config/trace_config.pbzero.h" #include "protos/perfetto/trace/chrome/chrome_trace_event.pbzero.h" #include "protos/perfetto/trace/perfetto/perfetto_metatrace.pbzero.h" @@ -85,9 +84,6 @@ void ProtoTraceParserImpl::ParseTracePacket(int64_t ts, TracePacketData data) { } } - if (packet.has_trace_stats()) - ParseTraceStats(packet.trace_stats()); - if (packet.has_chrome_events()) { ParseChromeEvents(ts, packet.chrome_events()); } @@ -160,116 +156,14 @@ void ProtoTraceParserImpl::ParseInlineSchedWaking(uint32_t cpu, context_->args_tracker->Flush(); } -void ProtoTraceParserImpl::ParseTraceStats(ConstBytes blob) { - protos::pbzero::TraceStats::Decoder evt(blob.data, blob.size); - auto* storage = context_->storage.get(); - storage->SetStats(stats::traced_producers_connected, - static_cast(evt.producers_connected())); - storage->SetStats(stats::traced_producers_seen, - static_cast(evt.producers_seen())); - storage->SetStats(stats::traced_data_sources_registered, - static_cast(evt.data_sources_registered())); - storage->SetStats(stats::traced_data_sources_seen, - static_cast(evt.data_sources_seen())); - storage->SetStats(stats::traced_tracing_sessions, - static_cast(evt.tracing_sessions())); - storage->SetStats(stats::traced_total_buffers, - static_cast(evt.total_buffers())); - storage->SetStats(stats::traced_chunks_discarded, - static_cast(evt.chunks_discarded())); - storage->SetStats(stats::traced_patches_discarded, - static_cast(evt.patches_discarded())); - storage->SetStats(stats::traced_flushes_requested, - static_cast(evt.flushes_requested())); - storage->SetStats(stats::traced_flushes_succeeded, - static_cast(evt.flushes_succeeded())); - storage->SetStats(stats::traced_flushes_failed, - static_cast(evt.flushes_failed())); - - if (evt.has_filter_stats()) { - protos::pbzero::TraceStats::FilterStats::Decoder fstat(evt.filter_stats()); - storage->SetStats(stats::filter_errors, - static_cast(fstat.errors())); - storage->SetStats(stats::filter_input_bytes, - static_cast(fstat.input_bytes())); - storage->SetStats(stats::filter_input_packets, - static_cast(fstat.input_packets())); - storage->SetStats(stats::filter_output_bytes, - static_cast(fstat.output_bytes())); - storage->SetStats(stats::filter_time_taken_ns, - static_cast(fstat.time_taken_ns())); - for (auto [i, it] = std::tuple{0, fstat.bytes_discarded_per_buffer()}; it; - ++it, ++i) { - storage->SetIndexedStats(stats::traced_buf_bytes_filtered_out, i, - static_cast(*it)); - } - } - - switch (evt.final_flush_outcome()) { - case protos::pbzero::TraceStats::FINAL_FLUSH_SUCCEEDED: - storage->IncrementStats(stats::traced_final_flush_succeeded, 1); - break; - case protos::pbzero::TraceStats::FINAL_FLUSH_FAILED: - storage->IncrementStats(stats::traced_final_flush_failed, 1); - break; - case protos::pbzero::TraceStats::FINAL_FLUSH_UNSPECIFIED: - break; - } - - int buf_num = 0; - for (auto it = evt.buffer_stats(); it; ++it, ++buf_num) { - protos::pbzero::TraceStats::BufferStats::Decoder buf(*it); - storage->SetIndexedStats(stats::traced_buf_buffer_size, buf_num, - static_cast(buf.buffer_size())); - storage->SetIndexedStats(stats::traced_buf_bytes_written, buf_num, - static_cast(buf.bytes_written())); - storage->SetIndexedStats(stats::traced_buf_bytes_overwritten, buf_num, - static_cast(buf.bytes_overwritten())); - storage->SetIndexedStats(stats::traced_buf_bytes_read, buf_num, - static_cast(buf.bytes_read())); - storage->SetIndexedStats(stats::traced_buf_padding_bytes_written, buf_num, - static_cast(buf.padding_bytes_written())); - storage->SetIndexedStats(stats::traced_buf_padding_bytes_cleared, buf_num, - static_cast(buf.padding_bytes_cleared())); - storage->SetIndexedStats(stats::traced_buf_chunks_written, buf_num, - static_cast(buf.chunks_written())); - storage->SetIndexedStats(stats::traced_buf_chunks_rewritten, buf_num, - static_cast(buf.chunks_rewritten())); - storage->SetIndexedStats(stats::traced_buf_chunks_overwritten, buf_num, - static_cast(buf.chunks_overwritten())); - storage->SetIndexedStats(stats::traced_buf_chunks_discarded, buf_num, - static_cast(buf.chunks_discarded())); - storage->SetIndexedStats(stats::traced_buf_chunks_read, buf_num, - static_cast(buf.chunks_read())); - storage->SetIndexedStats( - stats::traced_buf_chunks_committed_out_of_order, buf_num, - static_cast(buf.chunks_committed_out_of_order())); - storage->SetIndexedStats(stats::traced_buf_write_wrap_count, buf_num, - static_cast(buf.write_wrap_count())); - storage->SetIndexedStats(stats::traced_buf_patches_succeeded, buf_num, - static_cast(buf.patches_succeeded())); - storage->SetIndexedStats(stats::traced_buf_patches_failed, buf_num, - static_cast(buf.patches_failed())); - storage->SetIndexedStats(stats::traced_buf_readaheads_succeeded, buf_num, - static_cast(buf.readaheads_succeeded())); - storage->SetIndexedStats(stats::traced_buf_readaheads_failed, buf_num, - static_cast(buf.readaheads_failed())); - storage->SetIndexedStats(stats::traced_buf_abi_violations, buf_num, - static_cast(buf.abi_violations())); - storage->SetIndexedStats( - stats::traced_buf_trace_writer_packet_loss, buf_num, - static_cast(buf.trace_writer_packet_loss())); - } -} - void ProtoTraceParserImpl::ParseChromeEvents(int64_t ts, ConstBytes blob) { TraceStorage* storage = context_->storage.get(); protos::pbzero::ChromeEventBundle::Decoder bundle(blob.data, blob.size); ArgsTracker args(context_); if (bundle.has_metadata()) { + auto ucpu = context_->cpu_tracker->GetOrCreateCpu(0); RawId id = storage->mutable_raw_table() - ->Insert({ts, raw_chrome_metadata_event_id_, 0, 0, 0, 0, - context_->machine_id()}) + ->Insert({ts, raw_chrome_metadata_event_id_, 0, 0, 0, ucpu}) .id; auto inserter = args.AddArgsTo(id); @@ -315,9 +209,10 @@ void ProtoTraceParserImpl::ParseChromeEvents(int64_t ts, ConstBytes blob) { } if (bundle.has_legacy_ftrace_output()) { + auto ucpu = context_->cpu_tracker->GetOrCreateCpu(0); RawId id = storage->mutable_raw_table() ->Insert({ts, raw_chrome_legacy_system_trace_event_id_, 0, 0, - 0, 0, context_->machine_id()}) + 0, ucpu}) .id; std::string data; @@ -336,9 +231,10 @@ void ProtoTraceParserImpl::ParseChromeEvents(int64_t ts, ConstBytes blob) { protos::pbzero::ChromeLegacyJsonTrace::USER_TRACE) { continue; } + auto ucpu = context_->cpu_tracker->GetOrCreateCpu(0); RawId id = storage->mutable_raw_table() ->Insert({ts, raw_chrome_legacy_user_trace_event_id_, 0, 0, - 0, 0, context_->machine_id()}) + 0, ucpu}) .id; Variadic value = Variadic::String(storage->InternString(legacy_trace.data())); diff --git a/src/trace_processor/importers/proto/proto_trace_parser_impl.h b/src/trace_processor/importers/proto/proto_trace_parser_impl.h index e9bce35b31..2c4dc077f7 100644 --- a/src/trace_processor/importers/proto/proto_trace_parser_impl.h +++ b/src/trace_processor/importers/proto/proto_trace_parser_impl.h @@ -65,7 +65,6 @@ class ProtoTraceParserImpl : public ProtoTraceParser { int64_t /*ts*/, InlineSchedWaking data) override; - void ParseTraceStats(ConstBytes); void ParseChromeEvents(int64_t ts, ConstBytes); void ParseMetatraceEvent(int64_t ts, ConstBytes); diff --git a/src/trace_processor/importers/proto/proto_trace_parser_impl_unittest.cc b/src/trace_processor/importers/proto/proto_trace_parser_impl_unittest.cc index 95b16d1309..e549fa5654 100644 --- a/src/trace_processor/importers/proto/proto_trace_parser_impl_unittest.cc +++ b/src/trace_processor/importers/proto/proto_trace_parser_impl_unittest.cc @@ -23,10 +23,13 @@ #include "src/trace_processor/importers/common/args_tracker.h" #include "src/trace_processor/importers/common/args_translation_table.h" #include "src/trace_processor/importers/common/clock_tracker.h" +#include "src/trace_processor/importers/common/cpu_tracker.h" #include "src/trace_processor/importers/common/event_tracker.h" #include "src/trace_processor/importers/common/flow_tracker.h" +#include "src/trace_processor/importers/common/machine_tracker.h" #include "src/trace_processor/importers/common/mapping_tracker.h" #include "src/trace_processor/importers/common/metadata_tracker.h" +#include "src/trace_processor/importers/common/process_track_translation_table.h" #include "src/trace_processor/importers/common/process_tracker.h" #include "src/trace_processor/importers/common/slice_tracker.h" #include "src/trace_processor/importers/common/stack_profile_tracker.h" @@ -259,12 +262,16 @@ class ProtoTraceParserTest : public ::testing::Test { context_.args_translation_table.reset(new ArgsTranslationTable(storage_)); context_.metadata_tracker.reset( new MetadataTracker(context_.storage.get())); + context_.machine_tracker.reset(new MachineTracker(&context_, 0)); + context_.cpu_tracker.reset(new CpuTracker(&context_)); event_ = new MockEventTracker(&context_); context_.event_tracker.reset(event_); sched_ = new MockSchedEventTracker(&context_); context_.ftrace_sched_tracker.reset(sched_); process_ = new NiceMock(&context_); context_.process_tracker.reset(process_); + context_.process_track_translation_table.reset( + new ProcessTrackTranslationTable(storage_)); slice_ = new NiceMock(&context_); context_.slice_tracker.reset(slice_); context_.slice_translation_table.reset(new SliceTranslationTable(storage_)); @@ -272,8 +279,8 @@ class ProtoTraceParserTest : public ::testing::Test { context_.clock_tracker.reset(clock_); context_.flow_tracker.reset(new FlowTracker(&context_)); context_.proto_trace_parser.reset(new ProtoTraceParserImpl(&context_)); - context_.sorter.reset(new TraceSorter(&context_, - TraceSorter::SortingMode::kFullSort)); + context_.sorter.reset( + new TraceSorter(&context_, TraceSorter::SortingMode::kFullSort)); context_.descriptor_pool_.reset(new DescriptorPool()); RegisterDefaultModules(&context_); @@ -289,8 +296,9 @@ class ProtoTraceParserTest : public ::testing::Test { std::vector trace_bytes = trace_.SerializeAsArray(); std::unique_ptr raw_trace(new uint8_t[trace_bytes.size()]); memcpy(raw_trace.get(), trace_bytes.data(), trace_bytes.size()); - context_.chunk_reader.reset(new ProtoTraceReader(&context_)); - auto status = context_.chunk_reader->Parse(TraceBlobView( + context_.chunk_readers.push_back( + std::make_unique(&context_)); + auto status = context_.chunk_readers.back()->Parse(TraceBlobView( TraceBlob::TakeOwnership(std::move(raw_trace), trace_bytes.size()))); ResetTraceBuffers(); @@ -389,17 +397,19 @@ TEST_F(ProtoTraceParserTest, LoadEventsIntoRaw) { ASSERT_EQ(raw.row_count(), 2u); const auto& args = context_.storage->arg_table(); ASSERT_EQ(args.row_count(), 6u); - // Order is by row and then by StringIds. - ASSERT_EQ(args.key()[0], context_.storage->InternString("comm")); - ASSERT_EQ(args.key()[1], context_.storage->InternString("pid")); - ASSERT_EQ(args.key()[2], context_.storage->InternString("oom_score_adj")); - ASSERT_EQ(args.key()[3], context_.storage->InternString("clone_flags")); - ASSERT_EQ(args.key()[4], context_.storage->InternString("ip")); - ASSERT_EQ(args.key()[5], context_.storage->InternString("buf")); - ASSERT_STREQ(args.string_value().GetString(0).c_str(), task_newtask); - ASSERT_EQ(args.int_value()[1], 123); - ASSERT_EQ(args.int_value()[2], 15); - ASSERT_EQ(args.int_value()[3], 12); + // Order is by row and then in the same order as encountered in the trace. + std::vector expected_keys; + for (uint32_t i = 0; i < args.row_count(); i++) { + expected_keys.push_back( + context_.storage->GetString(args.key()[i]).ToStdString()); + } + ASSERT_THAT(expected_keys, + testing::ElementsAre("pid", "comm", "clone_flags", + "oom_score_adj", "ip", "buf")); + ASSERT_EQ(args.int_value()[0], 123); + ASSERT_STREQ(args.string_value().GetString(1).c_str(), task_newtask); + ASSERT_EQ(args.int_value()[2], 12); + ASSERT_EQ(args.int_value()[3], 15); ASSERT_EQ(args.int_value()[4], 20); ASSERT_STREQ(args.string_value().GetString(5).c_str(), buf_value); @@ -610,7 +620,7 @@ TEST_F(ProtoTraceParserTest, LoadCpuFreq) { Tokenize(); context_.sorter->ExtractEventsForced(); - EXPECT_EQ(context_.storage->cpu_counter_track_table().cpu()[0], 10u); + EXPECT_EQ(context_.storage->cpu_counter_track_table().ucpu()[0].value, 10u); } TEST_F(ProtoTraceParserTest, LoadCpuFreqKHz) { @@ -633,10 +643,10 @@ TEST_F(ProtoTraceParserTest, LoadCpuFreqKHz) { auto row = context_.storage->cpu_counter_track_table().FindById(TrackId(0)); EXPECT_EQ(context_.storage->GetString(row->name()), "cpufreq"); - EXPECT_EQ(row->cpu(), 0u); + EXPECT_EQ(row->ucpu().value, 0u); row = context_.storage->cpu_counter_track_table().FindById(TrackId(1)); - EXPECT_EQ(row->cpu(), 1u); + EXPECT_EQ(row->ucpu().value, 1u); } TEST_F(ProtoTraceParserTest, LoadMemInfo) { @@ -675,6 +685,25 @@ TEST_F(ProtoTraceParserTest, LoadVmStats) { EXPECT_EQ(context_.storage->track_table().row_count(), 2u); } +TEST_F(ProtoTraceParserTest, LoadThermal) { + auto* packet = trace_->add_packet(); + uint64_t ts = 1000; + packet->set_timestamp(ts); + auto* bundle = packet->set_sys_stats(); + auto* thermal_zone = bundle->add_thermal_zone(); + thermal_zone->set_type("MOCKTYPE"); + uint64_t temp = 10000; + thermal_zone->set_temp(temp); + + EXPECT_CALL(*event_, + PushCounter(static_cast(ts), + DoubleEq(static_cast(temp)), TrackId{1u})); + Tokenize(); + context_.sorter->ExtractEventsForced(); + + EXPECT_EQ(context_.storage->track_table().row_count(), 2u); +} + TEST_F(ProtoTraceParserTest, LoadProcessPacket) { auto* tree = trace_->add_packet()->set_process_tree(); auto* process = tree->add_processes(); @@ -2368,7 +2397,9 @@ TEST_F(ProtoTraceParserTest, TrackEventParseLegacyEventIntoRawTable) { EXPECT_EQ(raw_table.ts()[0], 1010000); EXPECT_EQ(raw_table.name()[0], storage_->InternString("track_event.legacy_event")); - EXPECT_EQ(raw_table.cpu()[0], 0u); + auto ucpu = raw_table.ucpu()[0]; + const auto& cpu_table = storage_->cpu_table(); + EXPECT_EQ(cpu_table.cpu()[ucpu.value], 0u); EXPECT_EQ(raw_table.utid()[0], 1u); EXPECT_EQ(raw_table.arg_set_id()[0], 1u); diff --git a/src/trace_processor/importers/proto/proto_trace_reader.cc b/src/trace_processor/importers/proto/proto_trace_reader.cc index 2e50f0cc33..1c06b17f68 100644 --- a/src/trace_processor/importers/proto/proto_trace_reader.cc +++ b/src/trace_processor/importers/proto/proto_trace_reader.cc @@ -16,11 +16,14 @@ #include "src/trace_processor/importers/proto/proto_trace_reader.h" +#include #include #include +#include #include "perfetto/base/build_config.h" #include "perfetto/base/logging.h" +#include "perfetto/ext/base/flat_hash_map.h" #include "perfetto/ext/base/string_view.h" #include "perfetto/ext/base/utils.h" #include "perfetto/protozero/proto_decoder.h" @@ -41,11 +44,13 @@ #include "src/trace_processor/util/gzip_utils.h" #include "protos/perfetto/common/builtin_clock.pbzero.h" +#include "protos/perfetto/common/trace_stats.pbzero.h" #include "protos/perfetto/config/trace_config.pbzero.h" #include "protos/perfetto/trace/clock_snapshot.pbzero.h" #include "protos/perfetto/trace/extension_descriptor.pbzero.h" #include "protos/perfetto/trace/perfetto/tracing_service_event.pbzero.h" #include "protos/perfetto/trace/profiling/profile_common.pbzero.h" +#include "protos/perfetto/trace/remote_clock_sync.pbzero.h" #include "protos/perfetto/trace/trace.pbzero.h" #include "protos/perfetto/trace/trace_packet.pbzero.h" @@ -118,6 +123,16 @@ util::Status ProtoTraceReader::ParsePacket(TraceBlobView packet) { HandlePreviousPacketDropped(decoder); } + uint32_t sequence_id = decoder.trusted_packet_sequence_id(); + if (sequence_id) { + auto [data_loss, inserted] = + packet_sequence_data_loss_.Insert(sequence_id, 0); + + if (!inserted && decoder.previous_packet_dropped()) { + *data_loss += 1; + } + } + // It is important that we parse defaults before parsing other fields such as // the timestamp, since the defaults could affect them. if (decoder.has_trace_packet_defaults()) { @@ -131,8 +146,16 @@ util::Status ProtoTraceReader::ParsePacket(TraceBlobView packet) { } if (decoder.has_clock_snapshot()) { - return ParseClockSnapshot(decoder.clock_snapshot(), - decoder.trusted_packet_sequence_id()); + return ParseClockSnapshot(decoder.clock_snapshot(), sequence_id); + } + + if (decoder.has_trace_stats()) { + ParseTraceStats(decoder.trace_stats()); + } + + if (decoder.has_remote_clock_sync()) { + PERFETTO_DCHECK(context_->machine_id()); + return ParseRemoteClockSync(decoder.remote_clock_sync()); } if (decoder.has_service_event()) { @@ -429,6 +452,126 @@ util::Status ProtoTraceReader::ParseClockSnapshot(ConstBytes blob, return util::OkStatus(); } +util::Status ProtoTraceReader::ParseRemoteClockSync(ConstBytes blob) { + protos::pbzero::RemoteClockSync::Decoder evt(blob.data, blob.size); + + std::vector sync_clock_snapshots; + // Decode the RemoteClockSync message into a struct for calculating offsets. + for (auto it = evt.synced_clocks(); it; ++it) { + sync_clock_snapshots.emplace_back(); + auto& sync_clocks = sync_clock_snapshots.back(); + + protos::pbzero::RemoteClockSync::SyncedClocks::Decoder synced_clocks(*it); + protos::pbzero::ClockSnapshot::ClockSnapshot::Decoder host_clocks( + synced_clocks.host_clocks()); + for (auto clock_it = host_clocks.clocks(); clock_it; clock_it++) { + protos::pbzero::ClockSnapshot::ClockSnapshot::Clock::Decoder clock( + *clock_it); + sync_clocks[clock.clock_id()].first = clock.timestamp(); + } + + std::vector clock_timestamps; + protos::pbzero::ClockSnapshot::ClockSnapshot::Decoder client_clocks( + synced_clocks.client_clocks()); + for (auto clock_it = client_clocks.clocks(); clock_it; clock_it++) { + protos::pbzero::ClockSnapshot::ClockSnapshot::Clock::Decoder clock( + *clock_it); + sync_clocks[clock.clock_id()].second = clock.timestamp(); + clock_timestamps.emplace_back(clock.clock_id(), clock.timestamp(), 1, + false); + } + + // In addition for calculating clock offsets, client clock snapshots are + // also added to clock tracker to emulate tracing service taking periodical + // clock snapshots. This builds a clock conversion path from a local trace + // time (e.g. Chrome trace time) to client builtin clock (CLOCK_MONOTONIC) + // which can be converted to host trace time (CLOCK_BOOTTIME). + context_->clock_tracker->AddSnapshot(clock_timestamps); + } + + // Calculate clock offsets and report to the ClockTracker. + auto clock_offsets = CalculateClockOffsets(sync_clock_snapshots); + for (auto it = clock_offsets.GetIterator(); it; ++it) { + context_->clock_tracker->SetClockOffset(it.key(), it.value()); + } + + return util::OkStatus(); +} + +base::FlatHashMap +ProtoTraceReader::CalculateClockOffsets( + std::vector& sync_clock_snapshots) { + base::FlatHashMap clock_offsets; + + // The RemoteClockSync message contains a sequence of |synced_clocks| + // messages. Each |synced_clocks| message contains pairs of ClockSnapshots + // taken on both the client and host sides. + // + // The "synced_clocks" messages are emitted periodically. A single round of + // data collection involves four snapshots: + // 1. Client snapshot + // 2. Host snapshot (triggered by client's IPC message) + // 3. Client snapshot (triggered by host's IPC message) + // 4. Host snapshot + // + // These four snapshots are used to estimate the clock offset between the + // client and host for each default clock domain present in the ClockSnapshot. + std::map> raw_clock_offsets; + // Remote clock syncs happen in an interval of 30 sec. 2 adjacent clock + // snapshots belong to the same round if they happen within 30 secs. + constexpr uint64_t clock_sync_interval_ns = 30lu * 1000000000; + for (size_t i = 1; i < sync_clock_snapshots.size(); i++) { + // Synced clocks are taken by client snapshot -> host snapshot. + auto& ping_clocks = sync_clock_snapshots[i - 1]; + auto& update_clocks = sync_clock_snapshots[i]; + + auto ping_client = + ping_clocks[protos::pbzero::BuiltinClock::BUILTIN_CLOCK_BOOTTIME] + .second; + auto update_client = + update_clocks[protos::pbzero::BuiltinClock::BUILTIN_CLOCK_BOOTTIME] + .second; + // |ping_clocks| and |update_clocks| belong to 2 different rounds of remote + // clock sync rounds. + if (update_client - ping_client >= clock_sync_interval_ns) + continue; + + for (auto it = ping_clocks.GetIterator(); it; ++it) { + const auto clock_id = it.key(); + const auto [t1h, t1c] = it.value(); + const auto [t2h, t2c] = update_clocks[clock_id]; + + if (!t1h || !t1c || !t2h || !t2c) + continue; + + int64_t offset1 = + static_cast(t1c + t2c) / 2 - static_cast(t1h); + int64_t offset2 = + static_cast(t2c) - static_cast(t1h + t2h) / 2; + + // Clock values are taken in the order of t1c, t1h, t2c, t2h. Offset + // calculation requires at least 3 timestamps as a round trip. We have 4, + // which can be treated as 2 round trips: + // 1. t1c, t1h, t2c as the round trip initiated by the client. Offset 1 + // = (t1c + t2c) / 2 - t1h + // 2. t1h, t2c, t2h as the round trip initiated by the host. Offset 2 = + // t2c - (t1h + t2h) / 2 + raw_clock_offsets[clock_id].push_back(offset1); + raw_clock_offsets[clock_id].push_back(offset2); + } + + // Use the average of estimated clock offsets in the clock tracker. + for (const auto& [clock_id, offsets] : raw_clock_offsets) { + int64_t avg_offset = + std::accumulate(offsets.begin(), offsets.end(), 0LL) / + static_cast(offsets.size()); + clock_offsets[clock_id] = avg_offset; + } + } + + return clock_offsets; +} + std::optional ProtoTraceReader::GetBuiltinClockNameOrNull( int64_t clock_id) { switch (clock_id) { @@ -474,6 +617,125 @@ util::Status ProtoTraceReader::ParseServiceEvent(int64_t ts, ConstBytes blob) { return util::OkStatus(); } +void ProtoTraceReader::ParseTraceStats(ConstBytes blob) { + protos::pbzero::TraceStats::Decoder evt(blob.data, blob.size); + auto* storage = context_->storage.get(); + storage->SetStats(stats::traced_producers_connected, + static_cast(evt.producers_connected())); + storage->SetStats(stats::traced_producers_seen, + static_cast(evt.producers_seen())); + storage->SetStats(stats::traced_data_sources_registered, + static_cast(evt.data_sources_registered())); + storage->SetStats(stats::traced_data_sources_seen, + static_cast(evt.data_sources_seen())); + storage->SetStats(stats::traced_tracing_sessions, + static_cast(evt.tracing_sessions())); + storage->SetStats(stats::traced_total_buffers, + static_cast(evt.total_buffers())); + storage->SetStats(stats::traced_chunks_discarded, + static_cast(evt.chunks_discarded())); + storage->SetStats(stats::traced_patches_discarded, + static_cast(evt.patches_discarded())); + storage->SetStats(stats::traced_flushes_requested, + static_cast(evt.flushes_requested())); + storage->SetStats(stats::traced_flushes_succeeded, + static_cast(evt.flushes_succeeded())); + storage->SetStats(stats::traced_flushes_failed, + static_cast(evt.flushes_failed())); + + if (evt.has_filter_stats()) { + protos::pbzero::TraceStats::FilterStats::Decoder fstat(evt.filter_stats()); + storage->SetStats(stats::filter_errors, + static_cast(fstat.errors())); + storage->SetStats(stats::filter_input_bytes, + static_cast(fstat.input_bytes())); + storage->SetStats(stats::filter_input_packets, + static_cast(fstat.input_packets())); + storage->SetStats(stats::filter_output_bytes, + static_cast(fstat.output_bytes())); + storage->SetStats(stats::filter_time_taken_ns, + static_cast(fstat.time_taken_ns())); + for (auto [i, it] = std::tuple{0, fstat.bytes_discarded_per_buffer()}; it; + ++it, ++i) { + storage->SetIndexedStats(stats::traced_buf_bytes_filtered_out, i, + static_cast(*it)); + } + } + + switch (evt.final_flush_outcome()) { + case protos::pbzero::TraceStats::FINAL_FLUSH_SUCCEEDED: + storage->IncrementStats(stats::traced_final_flush_succeeded, 1); + break; + case protos::pbzero::TraceStats::FINAL_FLUSH_FAILED: + storage->IncrementStats(stats::traced_final_flush_failed, 1); + break; + case protos::pbzero::TraceStats::FINAL_FLUSH_UNSPECIFIED: + break; + } + + int buf_num = 0; + for (auto it = evt.buffer_stats(); it; ++it, ++buf_num) { + protos::pbzero::TraceStats::BufferStats::Decoder buf(*it); + storage->SetIndexedStats(stats::traced_buf_buffer_size, buf_num, + static_cast(buf.buffer_size())); + storage->SetIndexedStats(stats::traced_buf_bytes_written, buf_num, + static_cast(buf.bytes_written())); + storage->SetIndexedStats(stats::traced_buf_bytes_overwritten, buf_num, + static_cast(buf.bytes_overwritten())); + storage->SetIndexedStats(stats::traced_buf_bytes_read, buf_num, + static_cast(buf.bytes_read())); + storage->SetIndexedStats(stats::traced_buf_padding_bytes_written, buf_num, + static_cast(buf.padding_bytes_written())); + storage->SetIndexedStats(stats::traced_buf_padding_bytes_cleared, buf_num, + static_cast(buf.padding_bytes_cleared())); + storage->SetIndexedStats(stats::traced_buf_chunks_written, buf_num, + static_cast(buf.chunks_written())); + storage->SetIndexedStats(stats::traced_buf_chunks_rewritten, buf_num, + static_cast(buf.chunks_rewritten())); + storage->SetIndexedStats(stats::traced_buf_chunks_overwritten, buf_num, + static_cast(buf.chunks_overwritten())); + storage->SetIndexedStats(stats::traced_buf_chunks_discarded, buf_num, + static_cast(buf.chunks_discarded())); + storage->SetIndexedStats(stats::traced_buf_chunks_read, buf_num, + static_cast(buf.chunks_read())); + storage->SetIndexedStats( + stats::traced_buf_chunks_committed_out_of_order, buf_num, + static_cast(buf.chunks_committed_out_of_order())); + storage->SetIndexedStats(stats::traced_buf_write_wrap_count, buf_num, + static_cast(buf.write_wrap_count())); + storage->SetIndexedStats(stats::traced_buf_patches_succeeded, buf_num, + static_cast(buf.patches_succeeded())); + storage->SetIndexedStats(stats::traced_buf_patches_failed, buf_num, + static_cast(buf.patches_failed())); + storage->SetIndexedStats(stats::traced_buf_readaheads_succeeded, buf_num, + static_cast(buf.readaheads_succeeded())); + storage->SetIndexedStats(stats::traced_buf_readaheads_failed, buf_num, + static_cast(buf.readaheads_failed())); + storage->SetIndexedStats(stats::traced_buf_abi_violations, buf_num, + static_cast(buf.abi_violations())); + storage->SetIndexedStats( + stats::traced_buf_trace_writer_packet_loss, buf_num, + static_cast(buf.trace_writer_packet_loss())); + } + + base::FlatHashMap data_loss_per_buffer; + + for (auto it = evt.writer_stats(); it; ++it) { + protos::pbzero::TraceStats::WriterStats::Decoder writer(*it); + auto* data_loss = packet_sequence_data_loss_.Find( + static_cast(writer.sequence_id())); + if (data_loss) { + data_loss_per_buffer[static_cast(writer.buffer())] += + static_cast(*data_loss); + } + } + + for (auto it = data_loss_per_buffer.GetIterator(); it; ++it) { + storage->SetIndexedStats(stats::traced_buf_sequence_packet_loss, it.key(), + it.value()); + } +} + void ProtoTraceReader::NotifyEndOfFile() {} } // namespace trace_processor diff --git a/src/trace_processor/importers/proto/proto_trace_reader.h b/src/trace_processor/importers/proto/proto_trace_reader.h index a93ad98811..0356f98eae 100644 --- a/src/trace_processor/importers/proto/proto_trace_reader.h +++ b/src/trace_processor/importers/proto/proto_trace_reader.h @@ -64,11 +64,23 @@ class ProtoTraceReader : public ChunkedTraceReader { util::Status Parse(TraceBlobView) override; void NotifyEndOfFile() override; + using SyncClockSnapshots = base::FlatHashMap< + int64_t, + std::pair>; + base::FlatHashMap + CalculateClockOffsetsForTesting( + std::vector& sync_clock_snapshots) { + return CalculateClockOffsets(sync_clock_snapshots); + } + + std::optional GetBuiltinClockNameOrNull(int64_t clock_id); + private: using ConstBytes = protozero::ConstBytes; util::Status ParsePacket(TraceBlobView); util::Status ParseServiceEvent(int64_t ts, ConstBytes); util::Status ParseClockSnapshot(ConstBytes blob, uint32_t seq_id); + util::Status ParseRemoteClockSync(ConstBytes blob); void HandleIncrementalStateCleared( const protos::pbzero::TracePacket_Decoder&); void HandleFirstPacketOnSequence(uint32_t packet_sequence_id); @@ -78,8 +90,10 @@ class ProtoTraceReader : public ChunkedTraceReader { void ParseInternedData(const protos::pbzero::TracePacket_Decoder&, TraceBlobView interned_data); void ParseTraceConfig(ConstBytes); + void ParseTraceStats(ConstBytes); - std::optional GetBuiltinClockNameOrNull(int64_t clock_id); + base::FlatHashMap + CalculateClockOffsets(std::vector&); PacketSequenceStateBuilder* GetIncrementalStateForPacketSequence( uint32_t sequence_id) { @@ -104,6 +118,8 @@ class ProtoTraceReader : public ChunkedTraceReader { base::FlatHashMap packet_sequence_state_builders_; + base::FlatHashMap packet_sequence_data_loss_; + StringId skipped_packet_key_id_; StringId invalid_incremental_state_key_id_; }; diff --git a/src/trace_processor/importers/proto/proto_trace_reader_unittest.cc b/src/trace_processor/importers/proto/proto_trace_reader_unittest.cc new file mode 100644 index 0000000000..166640cf26 --- /dev/null +++ b/src/trace_processor/importers/proto/proto_trace_reader_unittest.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_processor/importers/proto/proto_trace_reader.h" +#include + +#include "perfetto/protozero/scattered_heap_buffer.h" +#include "protos/perfetto/common/builtin_clock.pbzero.h" +#include "src/trace_processor/importers/common/clock_tracker.h" +#include "src/trace_processor/importers/common/machine_tracker.h" +#include "src/trace_processor/storage/trace_storage.h" + +#include "test/gtest_and_gmock.h" + +#include "protos/perfetto/trace/clock_snapshot.pbzero.h" +#include "protos/perfetto/trace/remote_clock_sync.pbzero.h" + +namespace perfetto::trace_processor { +namespace { + +constexpr auto REALTIME = protos::pbzero::BUILTIN_CLOCK_REALTIME; +constexpr auto BOOTTIME = protos::pbzero::BUILTIN_CLOCK_BOOTTIME; + +class ProtoTraceReaderTest : public ::testing::Test { + public: + ProtoTraceReaderTest() { + context_.storage = std::make_unique(); + context_.machine_tracker = + std::make_unique(&context_, 0x1001); + context_.clock_tracker = std::make_unique(&context_); + proto_trace_reader_ = std::make_unique(&context_); + } + + util::Status Tokenize() { + trace_->Finalize(); + std::vector trace_bytes = trace_.SerializeAsArray(); + std::unique_ptr raw_trace(new uint8_t[trace_bytes.size()]); + memcpy(raw_trace.get(), trace_bytes.data(), trace_bytes.size()); + auto status = proto_trace_reader_->Parse(TraceBlobView( + TraceBlob::TakeOwnership(std::move(raw_trace), trace_bytes.size()))); + + trace_.Reset(); + return status; + } + + protected: + protozero::HeapBuffered trace_; + TraceProcessorContext context_; + std::unique_ptr proto_trace_reader_; +}; + +TEST_F(ProtoTraceReaderTest, RemoteClockSync_Valid) { + context_.machine_tracker = + std::make_unique(&context_, 0x1001); + + auto* packet = trace_->add_packet(); + packet->set_machine_id(0x1001); + auto* remote_clock_sync = packet->set_remote_clock_sync(); + auto* synced_clocks = remote_clock_sync->add_synced_clocks(); + auto* client_clocks = synced_clocks->set_client_clocks(); + + // First synced clock snapshots on both sides. + auto* clock = client_clocks->add_clocks(); + clock->set_clock_id(BOOTTIME); + clock->set_timestamp(10000); + + auto* host_clocks = synced_clocks->set_host_clocks(); + clock = host_clocks->add_clocks(); + clock->set_clock_id(BOOTTIME); + clock->set_timestamp(120000); + + // Second synced clock snapshots on both sides. + synced_clocks = remote_clock_sync->add_synced_clocks(); + + client_clocks = synced_clocks->set_client_clocks(); + clock = client_clocks->add_clocks(); + clock->set_clock_id(BOOTTIME); + clock->set_timestamp(25000); + + host_clocks = synced_clocks->set_host_clocks(); + clock = host_clocks->add_clocks(); + clock->set_clock_id(BOOTTIME); + clock->set_timestamp(135000); + + ASSERT_TRUE(Tokenize().ok()); + ASSERT_EQ(1u, context_.clock_tracker->clock_offsets_for_testing().size()); +} + +TEST_F(ProtoTraceReaderTest, RemoteClockSync_Incomplete) { + context_.machine_tracker = + std::make_unique(&context_, 0x1001); + + auto* packet = trace_->add_packet(); + packet->set_machine_id(0x1001); + auto* remote_clock_sync = packet->set_remote_clock_sync(); + auto* synced_clocks = remote_clock_sync->add_synced_clocks(); + auto* client_clocks = synced_clocks->set_client_clocks(); + + // First synced clock snapshots on both sides. + auto* clock = client_clocks->add_clocks(); + clock->set_clock_id(BOOTTIME); + clock->set_timestamp(10000); + + auto* host_clocks = synced_clocks->set_host_clocks(); + clock = host_clocks->add_clocks(); + clock->set_clock_id(BOOTTIME); + clock->set_timestamp(120000); + + // Second synced clock snapshots on both sides. + synced_clocks = remote_clock_sync->add_synced_clocks(); + + client_clocks = synced_clocks->set_client_clocks(); + clock = client_clocks->add_clocks(); + clock->set_clock_id(BOOTTIME); + clock->set_timestamp(25000); + + // Missing the second host CLOCK_BOOTTIME making it below the minimum + // requirement for using the remote_clock_sync for calculating clock offset. + + ASSERT_TRUE(Tokenize().ok()); + // No valid clock offset. + ASSERT_EQ(0u, context_.clock_tracker->clock_offsets_for_testing().size()); +} + +TEST_F(ProtoTraceReaderTest, CalculateClockOffset) { + std::vector sync_clock_snapshots; + ProtoTraceReader::SyncClockSnapshots snapshots; + snapshots[BOOTTIME] = {120000, 10000}; + snapshots[REALTIME] = {135000, 25000}; + sync_clock_snapshots.push_back(std::move(snapshots)); + + snapshots[BOOTTIME] = {140000, 20000}; + snapshots[REALTIME] = {150000, 35000}; + sync_clock_snapshots.push_back(std::move(snapshots)); + + auto clock_offsets = proto_trace_reader_->CalculateClockOffsetsForTesting( + sync_clock_snapshots); + ASSERT_EQ(2u, clock_offsets.size()); + // Client 10000 20000 + // Host 120000 140000 + // Estimated offsets: (10000 + 20000)/2 - 120000 = -105000, + // 20000 - (120000 + 140000) / 2 = -110000. + // Average = -107500. + ASSERT_EQ(-107500, clock_offsets[BOOTTIME]); + // Client 25000 35000 + // Host 135000 150000 + // Estimated offsets: (25000 + 35000)/2 - 135000 = -105000, + // 35000 - (135000 + 150000) / 2 = -107500. + // Average = -106250. + ASSERT_EQ(-106250, clock_offsets[REALTIME]); +} + +TEST_F(ProtoTraceReaderTest, CalculateClockOffset_AboveThreshold) { + std::vector sync_clock_snapshots; + ProtoTraceReader::SyncClockSnapshots snapshots; + snapshots[BOOTTIME] = {120000, 10000}; + snapshots[REALTIME] = {135000, 25000}; + sync_clock_snapshots.push_back(std::move(snapshots)); + + // 30 sec interval: the 2 clock snapshots will be considered 2 different + // rounds of clock synchronization IPC exchange and won't be used. + auto interval = 30ull * 1000 * 1000 * 1000; + snapshots[BOOTTIME] = {120000 + interval, 10000 + interval}; + snapshots[REALTIME] = {135000 + interval, 25000 + interval}; + sync_clock_snapshots.push_back(std::move(snapshots)); + + auto clock_offsets = proto_trace_reader_->CalculateClockOffsetsForTesting( + sync_clock_snapshots); + ASSERT_EQ(0u, clock_offsets.size()); +} + +TEST_F(ProtoTraceReaderTest, CalculateClockOffset_MultiRounds) { + std::vector sync_clock_snapshots; + ProtoTraceReader::SyncClockSnapshots snapshots; + // This emits clock offsets -105000, -110000. + snapshots[BOOTTIME] = {120000, 10000}; + sync_clock_snapshots.push_back(std::move(snapshots)); + snapshots[BOOTTIME] = {140000, 20000}; + sync_clock_snapshots.push_back(std::move(snapshots)); + + // The interval works as a delimeter of IPC exchange. + auto interval = 30ull * 1000 * 1000 * 1000; + + // This emits clock offsets: (30000 + 45000) / 2 - 160000 = -122500, + // 45000 - (160000 + 170000) / 2 = -120000. + snapshots[BOOTTIME] = {160000 + interval, 30000 + interval}; + sync_clock_snapshots.push_back(std::move(snapshots)); + snapshots[BOOTTIME] = {170000 + interval, 45000 + interval}; + sync_clock_snapshots.push_back(std::move(snapshots)); + + auto clock_offsets = proto_trace_reader_->CalculateClockOffsetsForTesting( + sync_clock_snapshots); + ASSERT_EQ(1u, clock_offsets.size()); + // Average(-105000, -110000, -122500, -120000) = -114375. + ASSERT_EQ(-114375, clock_offsets[BOOTTIME]); +} + +} // namespace +} // namespace perfetto::trace_processor diff --git a/src/trace_processor/importers/proto/proto_trace_tokenizer.h b/src/trace_processor/importers/proto/proto_trace_tokenizer.h index 2526900644..ad580760f8 100644 --- a/src/trace_processor/importers/proto/proto_trace_tokenizer.h +++ b/src/trace_processor/importers/proto/proto_trace_tokenizer.h @@ -17,140 +17,176 @@ #ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_PROTO_TRACE_TOKENIZER_H_ #define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_PROTO_TRACE_TOKENIZER_H_ +#include +#include #include -#include +#include +#include +#include "perfetto/base/logging.h" #include "perfetto/base/status.h" +#include "perfetto/protozero/field.h" #include "perfetto/protozero/proto_utils.h" #include "perfetto/public/compiler.h" -#include "perfetto/trace_processor/status.h" -#include "perfetto/trace_processor/trace_blob.h" #include "perfetto/trace_processor/trace_blob_view.h" +#include "protos/perfetto/trace/trace_packet.pbzero.h" #include "src/trace_processor/util/gzip_utils.h" -#include "src/trace_processor/util/status_macros.h" #include "protos/perfetto/trace/trace.pbzero.h" -#include "protos/perfetto/trace/trace_packet.pbzero.h" +#include "src/trace_processor/util/status_macros.h" +#include "src/trace_processor/util/trace_blob_view_reader.h" -namespace perfetto { -namespace trace_processor { +namespace perfetto::trace_processor { // Reads a protobuf trace in chunks and extracts boundaries of trace packets -// (or subfields, for the case of ftrace) with their timestamps. +// with their timestamps. class ProtoTraceTokenizer { public: ProtoTraceTokenizer(); - template - util::Status Tokenize(TraceBlobView blob, Callback callback) { - const uint8_t* data = blob.data(); - size_t size = blob.size(); - if (!partial_buf_.empty()) { - // It takes ~5 bytes for a proto preamble + the varint size. - const size_t kHeaderBytes = 5; - if (PERFETTO_UNLIKELY(partial_buf_.size() < kHeaderBytes)) { - size_t missing_len = std::min(kHeaderBytes - partial_buf_.size(), size); - partial_buf_.insert(partial_buf_.end(), &data[0], &data[missing_len]); - if (partial_buf_.size() < kHeaderBytes) - return util::OkStatus(); - data += missing_len; - size -= missing_len; + template + base::Status Tokenize(TraceBlobView tbv, Callback callback) { + reader_.PushBack(std::move(tbv)); + + for (;;) { + size_t start_offset = reader_.start_offset(); + size_t avail = reader_.avail(); + + // The header must be at least 2 bytes (1 byte for tag, 1 byte for + // size/varint) and can be at most 20 bytes (10 bytes for tag + 10 bytes + // for size/varint). + const size_t kMinHeaderBytes = 2; + const size_t kMaxHeaderBytes = 20; + std::optional header = reader_.SliceOff( + start_offset, + std::min(std::max(avail, kMinHeaderBytes), kMaxHeaderBytes)); + + // This means that kMinHeaderBytes was not available. Just wait for the + // next round. + if (PERFETTO_UNLIKELY(!header)) { + return base::OkStatus(); } - // At this point we have enough data in |partial_buf_| to read at least - // the field header and know the size of the next TracePacket. - const uint8_t* pos = &partial_buf_[0]; - uint8_t proto_field_tag = *pos; - uint64_t field_size = 0; - // We cannot do &partial_buf_[partial_buf_.size()] because that crashes - // on MSVC STL debug builds, so does &*partial_buf_.end(). - const uint8_t* next = protozero::proto_utils::ParseVarInt( - ++pos, &partial_buf_.front() + partial_buf_.size(), &field_size); - bool parse_failed = next == pos; - pos = next; - if (proto_field_tag != kTracePacketTag || field_size == 0 || - parse_failed) { - return util::ErrStatus( - "Failed parsing a TracePacket from the partial buffer"); + uint64_t tag; + const uint8_t* tag_start = header->data(); + const uint8_t* tag_end = protozero::proto_utils::ParseVarInt( + tag_start, header->data() + header->size(), &tag); + + if (PERFETTO_UNLIKELY(tag_end == tag_start)) { + return header->size() < kMaxHeaderBytes + ? base::OkStatus() + : base::ErrStatus("Failed to parse tag"); } - // At this point we know how big the TracePacket is. - size_t hdr_size = static_cast(pos - &partial_buf_[0]); - size_t size_incl_header = static_cast(field_size + hdr_size); - PERFETTO_DCHECK(size_incl_header > partial_buf_.size()); - - // There is a good chance that between the |partial_buf_| and the new - // |data| of the current call we have enough bytes to parse a TracePacket. - if (partial_buf_.size() + size >= size_incl_header) { - // Create a new buffer for the whole TracePacket and copy into that: - // 1) The beginning of the TracePacket (including the proto header) from - // the partial buffer. - // 2) The rest of the TracePacket from the current |data| buffer (note - // that we might have consumed already a few bytes form |data| - // earlier in this function, hence we need to keep |off| into - // account). - TraceBlob glued = TraceBlob::Allocate(size_incl_header); - memcpy(glued.data(), partial_buf_.data(), partial_buf_.size()); - // |size_missing| is the number of bytes for the rest of the TracePacket - // in |data|. - size_t size_missing = size_incl_header - partial_buf_.size(); - memcpy(glued.data() + partial_buf_.size(), &data[0], size_missing); - data += size_missing; - size -= size_missing; - partial_buf_.clear(); - RETURN_IF_ERROR( - ParseInternal(TraceBlobView(std::move(glued)), callback)); - } else { - partial_buf_.insert(partial_buf_.end(), data, &data[size]); - return util::OkStatus(); + if (PERFETTO_UNLIKELY(tag != kTracePacketTag)) { + // Other field. Skip. + auto field_type = static_cast(tag & 0b111); + switch (field_type) { + case static_cast( + protozero::proto_utils::ProtoWireType::kVarInt): { + uint64_t varint; + const uint8_t* varint_start = tag_end; + const uint8_t* varint_end = protozero::proto_utils::ParseVarInt( + tag_end, header->data() + header->size(), &varint); + if (PERFETTO_UNLIKELY(varint_end == varint_start)) { + return header->size() < kMaxHeaderBytes + ? base::OkStatus() + : base::ErrStatus("Failed to skip varint"); + } + PERFETTO_CHECK(reader_.PopFrontBytes( + static_cast(varint_end - tag_start))); + continue; + } + case static_cast( + protozero::proto_utils::ProtoWireType::kLengthDelimited): { + uint64_t varint; + const uint8_t* varint_start = tag_end; + const uint8_t* varint_end = protozero::proto_utils::ParseVarInt( + tag_end, header->data() + header->size(), &varint); + if (PERFETTO_UNLIKELY(varint_end == varint_start)) { + return header->size() < kMaxHeaderBytes + ? base::OkStatus() + : base::ErrStatus("Failed to skip delimited"); + } + + size_t size_incl_header = + static_cast(varint_end - tag_start) + varint; + if (size_incl_header > avail) { + return base::OkStatus(); + } + PERFETTO_CHECK(reader_.PopFrontBytes(size_incl_header)); + continue; + } + case static_cast( + protozero::proto_utils::ProtoWireType::kFixed64): { + size_t size_incl_header = + static_cast(tag_end - tag_start) + 8; + if (size_incl_header > avail) { + return base::OkStatus(); + } + PERFETTO_CHECK(reader_.PopFrontBytes(size_incl_header)); + continue; + } + case static_cast( + protozero::proto_utils::ProtoWireType::kFixed32): { + size_t size_incl_header = + static_cast(tag_end - tag_start) + 4; + if (size_incl_header > avail) { + return base::OkStatus(); + } + PERFETTO_CHECK(reader_.PopFrontBytes(size_incl_header)); + continue; + } + default: + return base::ErrStatus("Unknown field type"); + } } - } - return ParseInternal(blob.slice(data, size), callback); - } - private: - static constexpr uint8_t kTracePacketTag = - protozero::proto_utils::MakeTagLengthDelimited( - protos::pbzero::Trace::kPacketFieldNumber); + uint64_t field_size; + const uint8_t* size_start = tag_end; + const uint8_t* size_end = protozero::proto_utils::ParseVarInt( + size_start, header->data() + header->size(), &field_size); + + // If we had less than the maximum number of header bytes, it's possible + // that we just need more to actually parse. Otherwise, this is an error. + if (PERFETTO_UNLIKELY(size_start == size_end)) { + return header->size() < kMaxHeaderBytes + ? base::OkStatus() + : base::ErrStatus("Failed to parse TracePacket size"); + } - template - util::Status ParseInternal(TraceBlobView whole_buf, Callback callback) { - static constexpr auto kLengthDelimited = - protozero::proto_utils::ProtoWireType::kLengthDelimited; - const uint8_t* const start = whole_buf.data(); - protos::pbzero::Trace::Decoder decoder(whole_buf.data(), whole_buf.size()); - for (auto it = decoder.packet(); it; ++it) { - if (PERFETTO_UNLIKELY(it->type() != kLengthDelimited)) { - return base::ErrStatus("Failed to parse TracePacket bounds"); + // Empty packets can legitimately happen if the producer ends up emitting + // no data: just ignore them. + auto hdr_size = static_cast(size_end - header->data()); + if (PERFETTO_UNLIKELY(field_size == 0)) { + PERFETTO_CHECK(reader_.PopFrontBytes(hdr_size)); + continue; } - protozero::ConstBytes packet = *it; - TraceBlobView sliced = whole_buf.slice(packet.data, packet.size); - RETURN_IF_ERROR(ParsePacket(std::move(sliced), callback)); - } - const size_t bytes_left = decoder.bytes_left(); - if (bytes_left > 0) { - PERFETTO_DCHECK(partial_buf_.empty()); - partial_buf_.insert(partial_buf_.end(), &start[decoder.read_offset()], - &start[decoder.read_offset() + bytes_left]); - } - return util::OkStatus(); - } + // If there's not enough bytes in the reader, then we cannot do anymore. + size_t size_incl_header = hdr_size + field_size; + if (size_incl_header > avail) { + return base::OkStatus(); + } + + auto packet = reader_.SliceOff(start_offset + hdr_size, field_size); + PERFETTO_CHECK(packet); + PERFETTO_CHECK(reader_.PopFrontBytes(hdr_size + field_size)); + protos::pbzero::TracePacket::Decoder decoder(packet->data(), + packet->length()); + if (!decoder.has_compressed_packets()) { + RETURN_IF_ERROR(callback(std::move(*packet))); + continue; + } - template - util::Status ParsePacket(TraceBlobView packet, Callback callback) { - protos::pbzero::TracePacket::Decoder decoder(packet.data(), - packet.length()); - if (decoder.has_compressed_packets()) { if (!util::IsGzipSupported()) { - return util::Status( + return base::ErrStatus( "Cannot decode compressed packets. Zlib not enabled"); } protozero::ConstBytes field = decoder.compressed_packets(); - TraceBlobView compressed_packets = packet.slice(field.data, field.size); + TraceBlobView compressed_packets = packet->slice(field.data, field.size); TraceBlobView packets; - RETURN_IF_ERROR(Decompress(std::move(compressed_packets), &packets)); const uint8_t* start = packets.data(); @@ -158,35 +194,38 @@ class ProtoTraceTokenizer { const uint8_t* ptr = start; while ((end - ptr) > 2) { const uint8_t* packet_outer = ptr; - if (PERFETTO_UNLIKELY(*ptr != kTracePacketTag)) - return util::ErrStatus("Expected TracePacket tag"); + if (PERFETTO_UNLIKELY(*ptr != kTracePacketTag)) { + return base::ErrStatus("Expected TracePacket tag"); + } uint64_t packet_size = 0; ptr = protozero::proto_utils::ParseVarInt(++ptr, end, &packet_size); const uint8_t* packet_start = ptr; ptr += packet_size; - if (PERFETTO_UNLIKELY((ptr - packet_outer) < 2 || ptr > end)) - return util::ErrStatus("Invalid packet size"); - + if (PERFETTO_UNLIKELY((ptr - packet_outer) < 2 || ptr > end)) { + return base::ErrStatus("Invalid packet size"); + } TraceBlobView sliced = packets.slice(packet_start, static_cast(packet_size)); - RETURN_IF_ERROR(ParsePacket(std::move(sliced), callback)); + RETURN_IF_ERROR(callback(std::move(sliced))); } - return util::OkStatus(); } - return callback(std::move(packet)); } - util::Status Decompress(TraceBlobView input, TraceBlobView* output); + private: + static constexpr uint8_t kTracePacketTag = + protozero::proto_utils::MakeTagLengthDelimited( + protos::pbzero::Trace::kPacketFieldNumber); + + base::Status Decompress(TraceBlobView input, TraceBlobView* output); // Used to glue together trace packets that span across two (or more) // Parse() boundaries. - std::vector partial_buf_; + util::TraceBlobViewReader reader_; // Allows support for compressed trace packets. util::GzipDecompressor decompressor_; }; -} // namespace trace_processor -} // namespace perfetto +} // namespace perfetto::trace_processor #endif // SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_PROTO_TRACE_TOKENIZER_H_ diff --git a/src/trace_processor/importers/proto/proto_trace_tokenizer_unittest.cc b/src/trace_processor/importers/proto/proto_trace_tokenizer_unittest.cc new file mode 100644 index 0000000000..982246e1ce --- /dev/null +++ b/src/trace_processor/importers/proto/proto_trace_tokenizer_unittest.cc @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "src/trace_processor/importers/proto/proto_trace_tokenizer.h" + +#include "perfetto/protozero/scattered_heap_buffer.h" +#include "test/gtest_and_gmock.h" + +namespace perfetto::trace_processor { +namespace { + +using testing::Invoke; +using testing::MockFunction; + +std::string_view ToStringView(const TraceBlobView& tbv) { + return std::string_view(reinterpret_cast(tbv.data()), + tbv.size()); +} + +TEST(ProtoTraceTokenizerTest, TwoPacketsSingleBlob) { + protozero::HeapBuffered message; + message->AppendString(/*field_id=*/1, "payload1"); + message->AppendString(/*field_id=*/1, "payload2"); + std::vector data = message.SerializeAsArray(); + + MockFunction cb; + + ProtoTraceTokenizer tokenizer; + + EXPECT_CALL(cb, Call) + .WillOnce(Invoke([](TraceBlobView out) { + EXPECT_EQ(ToStringView(out), "payload1"); + return base::OkStatus(); + })) + .WillOnce(Invoke([](TraceBlobView out) { + EXPECT_EQ(ToStringView(out), "payload2"); + return base::OkStatus(); + })); + + auto bv = TraceBlobView(TraceBlob::CopyFrom(data.data(), data.size())); + EXPECT_TRUE(tokenizer.Tokenize(std::move(bv), cb.AsStdFunction()).ok()); +} + +TEST(ProtoTraceTokenizerTest, TwoPacketsByteByByte) { + protozero::HeapBuffered message; + message->AppendString(/*field_id=*/1, "payload1"); + message->AppendString(/*field_id=*/1, "payload2"); + std::vector data = message.SerializeAsArray(); + + ProtoTraceTokenizer tokenizer; + + MockFunction cb; + EXPECT_CALL(cb, Call) + .WillOnce(Invoke([](TraceBlobView out) { + EXPECT_EQ(ToStringView(out), "payload1"); + return base::OkStatus(); + })) + .WillOnce(Invoke([](TraceBlobView out) { + EXPECT_EQ(ToStringView(out), "payload2"); + return base::OkStatus(); + })); + + for (uint8_t c : data) { + auto bv = TraceBlobView(TraceBlob::CopyFrom(&c, sizeof(c))); + EXPECT_TRUE(tokenizer.Tokenize(std::move(bv), cb.AsStdFunction()).ok()); + } +} + +TEST(ProtoTraceTokenizerTest, SkipFieldsSingleBlob) { + protozero::HeapBuffered message; + message->AppendVarInt(/*field_id=*/2, 42); + message->AppendString(/*field_id=*/1, "payload1"); + message->AppendString(/*field_id=*/3, "ignored"); + message->AppendFixed(/*field_id=*/3, 42); + message->AppendString(/*field_id=*/1, "payload2"); + message->AppendFixed(/*field_id=*/3, 42); + message->AppendString(/*field_id=*/1, "payload3"); + std::vector data = message.SerializeAsArray(); + + ProtoTraceTokenizer tokenizer; + + MockFunction cb; + EXPECT_CALL(cb, Call) + .WillOnce(Invoke([](TraceBlobView out) { + EXPECT_EQ(ToStringView(out), "payload1"); + return base::OkStatus(); + })) + .WillOnce(Invoke([](TraceBlobView out) { + EXPECT_EQ(ToStringView(out), "payload2"); + return base::OkStatus(); + })) + .WillOnce(Invoke([](TraceBlobView out) { + EXPECT_EQ(ToStringView(out), "payload3"); + return base::OkStatus(); + })); + + auto bv = TraceBlobView(TraceBlob::CopyFrom(data.data(), data.size())); + EXPECT_TRUE(tokenizer.Tokenize(std::move(bv), cb.AsStdFunction()).ok()); +} + +TEST(ProtoTraceTokenizerTest, SkipFieldsSingleByteByByte) { + protozero::HeapBuffered message; + message->AppendVarInt(/*field_id=*/2, 42); + message->AppendString(/*field_id=*/1, "payload1"); + message->AppendString(/*field_id=*/3, "ignored"); + message->AppendFixed(/*field_id=*/3, 42); + message->AppendString(/*field_id=*/1, "payload2"); + message->AppendFixed(/*field_id=*/3, 42); + message->AppendString(/*field_id=*/1, "payload3"); + std::vector data = message.SerializeAsArray(); + + ProtoTraceTokenizer tokenizer; + + MockFunction cb; + EXPECT_CALL(cb, Call) + .WillOnce(Invoke([](TraceBlobView out) { + EXPECT_EQ(ToStringView(out), "payload1"); + return base::OkStatus(); + })) + .WillOnce(Invoke([](TraceBlobView out) { + EXPECT_EQ(ToStringView(out), "payload2"); + return base::OkStatus(); + })) + .WillOnce(Invoke([](TraceBlobView out) { + EXPECT_EQ(ToStringView(out), "payload3"); + return base::OkStatus(); + })); + + for (uint8_t c : data) { + auto bv = TraceBlobView(TraceBlob::CopyFrom(&c, sizeof(c))); + EXPECT_TRUE(tokenizer.Tokenize(std::move(bv), cb.AsStdFunction()).ok()); + } +} + +} // namespace +} // namespace perfetto::trace_processor diff --git a/src/trace_processor/importers/proto/statsd_module.cc b/src/trace_processor/importers/proto/statsd_module.cc index e925b5a633..62faf01025 100644 --- a/src/trace_processor/importers/proto/statsd_module.cc +++ b/src/trace_processor/importers/proto/statsd_module.cc @@ -23,6 +23,7 @@ #include "src/trace_processor/importers/common/machine_tracker.h" #include "src/trace_processor/importers/common/slice_tracker.h" #include "src/trace_processor/importers/common/track_tracker.h" +#include "src/trace_processor/importers/proto/args_parser.h" #include "src/trace_processor/importers/proto/packet_sequence_state_generation.h" #include "src/trace_processor/sorter/trace_sorter.h" #include "src/trace_processor/storage/stats.h" @@ -37,111 +38,6 @@ namespace { constexpr const char* kAtomProtoName = ".android.os.statsd.Atom"; -using BoundInserter = ArgsTracker::BoundInserter; - -class InserterDelegate : public util::ProtoToArgsParser::Delegate { - public: - InserterDelegate(BoundInserter& inserter, TraceStorage& storage) - : inserter_(inserter), storage_(storage) {} - ~InserterDelegate() override = default; - - using Key = util::ProtoToArgsParser::Key; - - void AddInteger(const Key& key, int64_t value) override { - StringId flat_key_id = - storage_.InternString(base::StringView(key.flat_key)); - StringId key_id = storage_.InternString(base::StringView(key.key)); - Variadic variadic_val = Variadic::Integer(value); - inserter_.AddArg(flat_key_id, key_id, variadic_val); - } - - void AddUnsignedInteger(const Key& key, uint64_t value) override { - StringId flat_key_id = - storage_.InternString(base::StringView(key.flat_key)); - StringId key_id = storage_.InternString(base::StringView(key.key)); - Variadic variadic_val = Variadic::UnsignedInteger(value); - inserter_.AddArg(flat_key_id, key_id, variadic_val); - } - - void AddString(const Key& key, const protozero::ConstChars& value) override { - StringId flat_key_id = - storage_.InternString(base::StringView(key.flat_key)); - StringId key_id = storage_.InternString(base::StringView(key.key)); - Variadic variadic_val = Variadic::String(storage_.InternString(value)); - inserter_.AddArg(flat_key_id, key_id, variadic_val); - } - - void AddString(const Key& key, const std::string& value) override { - StringId flat_key_id = - storage_.InternString(base::StringView(key.flat_key)); - StringId key_id = storage_.InternString(base::StringView(key.key)); - Variadic variadic_val = - Variadic::String(storage_.InternString(base::StringView(value))); - inserter_.AddArg(flat_key_id, key_id, variadic_val); - } - - void AddDouble(const Key& key, double value) override { - StringId flat_key_id = - storage_.InternString(base::StringView(key.flat_key)); - StringId key_id = storage_.InternString(base::StringView(key.key)); - Variadic variadic_val = Variadic::Real(value); - inserter_.AddArg(flat_key_id, key_id, variadic_val); - } - - void AddPointer(const Key& key, const void* value) override { - StringId flat_key_id = - storage_.InternString(base::StringView(key.flat_key)); - StringId key_id = storage_.InternString(base::StringView(key.key)); - Variadic variadic_val = - Variadic::Pointer(reinterpret_cast(value)); - inserter_.AddArg(flat_key_id, key_id, variadic_val); - } - - void AddBoolean(const Key& key, bool value) override { - StringId flat_key_id = - storage_.InternString(base::StringView(key.flat_key)); - StringId key_id = storage_.InternString(base::StringView(key.key)); - Variadic variadic_val = Variadic::Boolean(value); - inserter_.AddArg(flat_key_id, key_id, variadic_val); - } - - bool AddJson(const Key&, const protozero::ConstChars&) override { - PERFETTO_FATAL("Unexpected JSON value when parsing statsd data"); - } - - void AddNull(const Key& key) override { - StringId flat_key_id = - storage_.InternString(base::StringView(key.flat_key)); - StringId key_id = storage_.InternString(base::StringView(key.key)); - Variadic variadic_val = Variadic::Null(); - inserter_.AddArg(flat_key_id, key_id, variadic_val); - } - - size_t GetArrayEntryIndex(const std::string& array_key) override { - base::ignore_result(array_key); - return 0; - } - - size_t IncrementArrayEntryIndex(const std::string& array_key) override { - base::ignore_result(array_key); - return 0; - } - - PacketSequenceStateGeneration* seq_state() override { return nullptr; } - - protected: - InternedMessageView* GetInternedMessageView(uint32_t field_id, - uint64_t iid) override { - base::ignore_result(field_id); - base::ignore_result(iid); - return nullptr; - } - - private: - BoundInserter& inserter_; - TraceStorage& storage_; -}; - // If we don't know about the atom format put whatever details we // can. This has the following restrictions: // - We can't tell the difference between double, fixed64, sfixed64 @@ -296,7 +192,7 @@ void StatsdModule::ParseAtom(int64_t ts, protozero::ConstBytes nested_bytes) { } SliceId slice = opt_slice.value(); auto inserter = context_->args_tracker->AddArgsTo(slice); - InserterDelegate delegate(inserter, *context_->storage.get()); + ArgsParser delegate(ts, inserter, *context_->storage.get()); const auto& fields = pool_.descriptor()->fields(); const auto& field_it = fields.find(nested_field_id); diff --git a/src/trace_processor/importers/proto/system_probes_parser.cc b/src/trace_processor/importers/proto/system_probes_parser.cc index 5b878eddd3..5323e78872 100644 --- a/src/trace_processor/importers/proto/system_probes_parser.cc +++ b/src/trace_processor/importers/proto/system_probes_parser.cc @@ -15,6 +15,7 @@ */ #include "src/trace_processor/importers/proto/system_probes_parser.h" +#include #include "perfetto/base/logging.h" #include "perfetto/ext/base/string_utils.h" @@ -22,6 +23,7 @@ #include "perfetto/ext/traced/sys_stats_counters.h" #include "perfetto/protozero/proto_decoder.h" #include "src/trace_processor/importers/common/clock_tracker.h" +#include "src/trace_processor/importers/common/cpu_tracker.h" #include "src/trace_processor/importers/common/event_tracker.h" #include "src/trace_processor/importers/common/metadata_tracker.h" #include "src/trace_processor/importers/common/process_tracker.h" @@ -102,6 +104,19 @@ std::optional FingerprintToSdkVersion(const std::string& fingerprint) { std::string version = fingerprint.substr(colon + 1, slash - (colon + 1)); return VersionStringToSdkVersion(version); } + +struct CpuInfo { + uint32_t cpu = 0; + std::optional capacity; + std::vector frequencies; + protozero::ConstChars processor; +}; + +struct CpuMaxFrequency { + uint32_t cpu = 0; + uint32_t max_frequency = 0; +}; + } // namespace SystemProbesParser::SystemProbesParser(TraceProcessorContext* context) @@ -131,7 +146,8 @@ SystemProbesParser::SystemProbesParser(TraceProcessorContext* context) cpu_times_softirq_ns_id_( context->storage->InternString("cpu.times.softirq_ns")), oom_score_adj_id_(context->storage->InternString("oom_score_adj")), - cpu_freq_id_(context_->storage->InternString("cpufreq")) { + cpu_freq_id_(context_->storage->InternString("cpufreq")), + thermal_unit_id_(context->storage->InternString("C")) { for (const auto& name : BuildMeminfoCounterNames()) { meminfo_strs_id_.emplace_back(context->storage->InternString(name)); } @@ -168,6 +184,8 @@ SystemProbesParser::SystemProbesParser(TraceProcessorContext* context) context->storage->InternString("mem.smaps.pss.file"); proc_stats_process_names_[ProcessStats::Process::kSmrPssShmemKbFieldNumber] = context->storage->InternString("mem.smaps.pss.shmem"); + proc_stats_process_names_[ProcessStats::Process::kSmrSwapPssKbFieldNumber] = + context->storage->InternString("mem.smaps.swap.pss"); proc_stats_process_names_ [ProcessStats::Process::kRuntimeUserModeFieldNumber] = context->storage->InternString("runtime.user_ns"); @@ -457,6 +475,15 @@ void SystemProbesParser::ParseSysStats(int64_t ts, ConstBytes blob) { context_->event_tracker->PushCounter( ts, static_cast(psi.total_ns()), track); } + + for (auto it = sys_stats.thermal_zone(); it; ++it) { + protos::pbzero::SysStats::ThermalZone::Decoder thermal(*it); + StringId track_name = context_->storage->InternString(thermal.type()); + TrackId track = context_->track_tracker->InternGlobalCounterTrack( + TrackTracker::Group::kThermals, track_name, {}, thermal_unit_id_); + context_->event_tracker->PushCounter( + ts, static_cast(thermal.temp()), track); + } } void SystemProbesParser::ParseProcessTree(ConstBytes blob) { @@ -731,6 +758,21 @@ void SystemProbesParser::ParseSystemInfo(ConstBytes blob) { metadata::android_sdk_version, Variadic::Integer(*opt_sdk_version)); } + if (packet.has_android_soc_model()) { + context_->metadata_tracker->SetMetadata( + metadata::android_soc_model, + Variadic::String( + context_->storage->InternString(packet.android_soc_model()))); + } + + if (packet.has_android_hardware_revision()) { + context_->metadata_tracker->SetMetadata( + metadata::android_hardware_revision, + Variadic::String( + context_->storage->InternString( + packet.android_hardware_revision()))); + } + page_size_ = packet.page_size(); if (!page_size_) { page_size_ = 4096; @@ -743,36 +785,85 @@ void SystemProbesParser::ParseSystemInfo(ConstBytes blob) { void SystemProbesParser::ParseCpuInfo(ConstBytes blob) { protos::pbzero::CpuInfo::Decoder packet(blob.data, blob.size); - uint32_t cluster_id = 0; - std::vector last_cpu_freqs; - for (auto it = packet.cpus(); it; it++) { + std::vector cpu_infos; + + // Decode CpuInfo packet + uint32_t cpu_id = 0; + for (auto it = packet.cpus(); it; it++, cpu_id++) { protos::pbzero::CpuInfo::Cpu::Decoder cpu(*it); - tables::CpuTable::Row cpu_row; - if (cpu.has_processor()) { - cpu_row.processor = context_->storage->InternString(cpu.processor()); - } - std::vector freqs; + CpuInfo current_cpu_info; + current_cpu_info.cpu = cpu_id; + current_cpu_info.processor = cpu.processor(); for (auto freq_it = cpu.frequencies(); freq_it; freq_it++) { - freqs.push_back(*freq_it); + uint32_t current_cpu_frequency = *freq_it; + current_cpu_info.frequencies.push_back(current_cpu_frequency); } - - // Here we assume that cluster of CPUs are 'next' to each other. - if (freqs != last_cpu_freqs && !last_cpu_freqs.empty()) { - cluster_id++; + if (cpu.has_capacity()) { + current_cpu_info.capacity = cpu.capacity(); } - cpu_row.cluster_id = cluster_id; - cpu_row.machine_id = context_->machine_id(); + cpu_infos.push_back(current_cpu_info); + } - last_cpu_freqs = freqs; - tables::CpuTable::Id cpu_row_id = - context_->storage->mutable_cpu_table()->Insert(cpu_row).id; + // Calculate cluster ids + // We look to use capacities as it is an ARM provided metric which is designed + // to measure the heterogeneity of CPU clusters however we fallback on the + // maximum frequency as an estimate - for (auto freq_it = cpu.frequencies(); freq_it; freq_it++) { - uint32_t freq = *freq_it; + // Capacities are defined as existing on all CPUs if present and so we set + // them as invalid if any is missing + bool valid_capacities = + std::all_of(cpu_infos.begin(), cpu_infos.end(), + [](CpuInfo info) { return info.capacity.has_value(); }); + + std::vector cluster_ids(cpu_infos.size()); + uint32_t cluster_id = 0; + + if (valid_capacities) { + std::sort(cpu_infos.begin(), cpu_infos.end(), + [](auto a, auto b) { return a.capacity < b.capacity; }); + uint32_t previous_capacity = *cpu_infos[0].capacity; + for (CpuInfo& cpu_info : cpu_infos) { + uint32_t capacity = *cpu_info.capacity; + // If cpus have the same capacity, they should have the same cluster id + if (previous_capacity < capacity) { + previous_capacity = capacity; + cluster_id++; + } + cluster_ids[cpu_info.cpu] = cluster_id; + } + } else { + // Use max frequency if capacities are invalid + std::vector cpu_max_freqs; + for (CpuInfo& info : cpu_infos) { + cpu_max_freqs.push_back( + {info.cpu, *std::max_element(info.frequencies.begin(), + info.frequencies.end())}); + } + std::sort(cpu_max_freqs.begin(), cpu_max_freqs.end(), + [](auto a, auto b) { return a.max_frequency < b.max_frequency; }); + + uint32_t previous_max_freq = cpu_max_freqs[0].max_frequency; + for (CpuMaxFrequency& cpu_max_freq : cpu_max_freqs) { + uint32_t max_freq = cpu_max_freq.max_frequency; + // If cpus have the same max frequency, they should have the same + // cluster_id + if (previous_max_freq < max_freq) { + previous_max_freq = max_freq; + cluster_id++; + } + cluster_ids[cpu_max_freq.cpu] = cluster_id; + } + } + + // Add values to tables + for (CpuInfo& cpu_info : cpu_infos) { + tables::CpuTable::Id ucpu = context_->cpu_tracker->SetCpuInfo( + cpu_info.cpu, cpu_info.processor, cluster_ids[cpu_info.cpu], + cpu_info.capacity); + for (uint32_t frequency : cpu_info.frequencies) { tables::CpuFreqTable::Row cpu_freq_row; - cpu_freq_row.cpu_id = cpu_row_id; - cpu_freq_row.freq = freq; - cpu_freq_row.machine_id = context_->machine_id(); + cpu_freq_row.ucpu = ucpu; + cpu_freq_row.freq = frequency; context_->storage->mutable_cpu_freq_table()->Insert(cpu_freq_row); } } diff --git a/src/trace_processor/importers/proto/system_probes_parser.h b/src/trace_processor/importers/proto/system_probes_parser.h index a54beb7b3f..124c003969 100644 --- a/src/trace_processor/importers/proto/system_probes_parser.h +++ b/src/trace_processor/importers/proto/system_probes_parser.h @@ -68,6 +68,7 @@ class SystemProbesParser { const StringId cpu_times_softirq_ns_id_; const StringId oom_score_adj_id_; const StringId cpu_freq_id_; + const StringId thermal_unit_id_; std::vector meminfo_strs_id_; std::vector vmstat_strs_id_; @@ -75,7 +76,7 @@ class SystemProbesParser { // their StringId. Keep kProcStatsProcessSize equal to 1 + max proto field // id of ProcessStats::Process. Also update the value in // ChromeSystemProbesParser. - static constexpr size_t kProcStatsProcessSize = 23; + static constexpr size_t kProcStatsProcessSize = 24; std::array proc_stats_process_names_{}; // Maps a SysStats::PsiSample::PsiResource type to its StringId. diff --git a/src/trace_processor/importers/proto/track_event_parser.cc b/src/trace_processor/importers/proto/track_event_parser.cc index 5854630495..e252be5e1e 100644 --- a/src/trace_processor/importers/proto/track_event_parser.cc +++ b/src/trace_processor/importers/proto/track_event_parser.cc @@ -26,13 +26,15 @@ #include "perfetto/trace_processor/status.h" #include "src/trace_processor/importers/common/args_tracker.h" #include "src/trace_processor/importers/common/args_translation_table.h" +#include "src/trace_processor/importers/common/cpu_tracker.h" #include "src/trace_processor/importers/common/event_tracker.h" #include "src/trace_processor/importers/common/flow_tracker.h" -#include "src/trace_processor/importers/common/machine_tracker.h" +#include "src/trace_processor/importers/common/process_track_translation_table.h" #include "src/trace_processor/importers/common/process_tracker.h" #include "src/trace_processor/importers/common/track_tracker.h" #include "src/trace_processor/importers/common/virtual_memory_mapping.h" #include "src/trace_processor/importers/json/json_utils.h" +#include "src/trace_processor/importers/proto/args_parser.h" #include "src/trace_processor/importers/proto/packet_analyzer.h" #include "src/trace_processor/importers/proto/profile_packet_utils.h" #include "src/trace_processor/importers/proto/stack_profile_sequence_state.h" @@ -74,103 +76,6 @@ using protozero::ConstBytes; constexpr int64_t kPendingThreadDuration = -1; constexpr int64_t kPendingThreadInstructionDelta = -1; -class TrackEventArgsParser : public util::ProtoToArgsParser::Delegate { - public: - TrackEventArgsParser(int64_t packet_timestamp, - BoundInserter& inserter, - TraceStorage& storage, - PacketSequenceStateGeneration& sequence_state) - : packet_timestamp_(packet_timestamp), - inserter_(inserter), - storage_(storage), - sequence_state_(sequence_state) {} - - ~TrackEventArgsParser() override; - - using Key = util::ProtoToArgsParser::Key; - - void AddInteger(const Key& key, int64_t value) final { - inserter_.AddArg(storage_.InternString(base::StringView(key.flat_key)), - storage_.InternString(base::StringView(key.key)), - Variadic::Integer(value)); - } - void AddUnsignedInteger(const Key& key, uint64_t value) final { - inserter_.AddArg(storage_.InternString(base::StringView(key.flat_key)), - storage_.InternString(base::StringView(key.key)), - Variadic::UnsignedInteger(value)); - } - void AddString(const Key& key, const protozero::ConstChars& value) final { - inserter_.AddArg(storage_.InternString(base::StringView(key.flat_key)), - storage_.InternString(base::StringView(key.key)), - Variadic::String(storage_.InternString(value))); - } - void AddString(const Key& key, const std::string& value) final { - inserter_.AddArg( - storage_.InternString(base::StringView(key.flat_key)), - storage_.InternString(base::StringView(key.key)), - Variadic::String(storage_.InternString(base::StringView(value)))); - } - void AddDouble(const Key& key, double value) final { - inserter_.AddArg(storage_.InternString(base::StringView(key.flat_key)), - storage_.InternString(base::StringView(key.key)), - Variadic::Real(value)); - } - void AddPointer(const Key& key, const void* value) final { - inserter_.AddArg(storage_.InternString(base::StringView(key.flat_key)), - storage_.InternString(base::StringView(key.key)), - Variadic::Pointer(reinterpret_cast(value))); - } - void AddBoolean(const Key& key, bool value) final { - inserter_.AddArg(storage_.InternString(base::StringView(key.flat_key)), - storage_.InternString(base::StringView(key.key)), - Variadic::Boolean(value)); - } - void AddBytes(const Key& key, const protozero::ConstBytes& value) final { - std::string b64_data = base::Base64Encode(value.data, value.size); - AddString(key, b64_data); - } - bool AddJson(const Key& key, const protozero::ConstChars& value) final { - auto json_value = json::ParseJsonString(value); - if (!json_value) - return false; - return json::AddJsonValueToArgs(*json_value, base::StringView(key.flat_key), - base::StringView(key.key), &storage_, - &inserter_); - } - void AddNull(const Key& key) final { - inserter_.AddArg(storage_.InternString(base::StringView(key.flat_key)), - storage_.InternString(base::StringView(key.key)), - Variadic::Null()); - } - - size_t GetArrayEntryIndex(const std::string& array_key) final { - return inserter_.GetNextArrayEntryIndex( - storage_.InternString(base::StringView(array_key))); - } - - size_t IncrementArrayEntryIndex(const std::string& array_key) final { - return inserter_.IncrementArrayEntryIndex( - storage_.InternString(base::StringView(array_key))); - } - - InternedMessageView* GetInternedMessageView(uint32_t field_id, - uint64_t iid) final { - return sequence_state_.GetInternedMessageView(field_id, iid); - } - - int64_t packet_timestamp() final { return packet_timestamp_; } - - PacketSequenceStateGeneration* seq_state() final { return &sequence_state_; } - - private: - int64_t packet_timestamp_; - BoundInserter& inserter_; - TraceStorage& storage_; - PacketSequenceStateGeneration& sequence_state_; -}; - -TrackEventArgsParser::~TrackEventArgsParser() = default; - // Paths on Windows use backslash rather than slash as a separator. // Normalise the paths by replacing backslashes with slashes to make it // easier to write cross-platform scripts. @@ -1118,10 +1023,11 @@ class TrackEventParser::EventImporter { if (!utid_) return util::ErrStatus("raw legacy event without thread association"); - RawId id = storage_->mutable_raw_table() - ->Insert({ts_, parser_->raw_legacy_event_id_, 0, *utid_, 0, - 0, context_->machine_id()}) - .id; + auto ucpu = context_->cpu_tracker->GetOrCreateCpu(0); + RawId id = + storage_->mutable_raw_table() + ->Insert({ts_, parser_->raw_legacy_event_id_, *utid_, 0, 0, ucpu}) + .id; auto inserter = context_->args_tracker->AddArgsTo(id); inserter @@ -1226,8 +1132,8 @@ class TrackEventParser::EventImporter { } } - TrackEventArgsParser args_writer(ts_, *inserter, *storage_, - *sequence_state_); + ArgsParser args_writer(ts_, *inserter, *storage_, sequence_state_, + /*support_json=*/true); int unknown_extensions = 0; log_errors(parser_->args_parser_.ParseMessage( blob_, ".perfetto.protos.TrackEvent", &parser_->reflect_fields_, @@ -1624,8 +1530,10 @@ void TrackEventParser::ParseTrackDescriptor( // Override the name with the most recent name seen (after sorting by ts). if (decoder.has_name() || decoder.has_static_name()) { auto* tracks = context_->storage->mutable_track_table(); - StringId name_id = context_->storage->InternString( + const StringId raw_name_id = context_->storage->InternString( decoder.has_name() ? decoder.name() : decoder.static_name()); + const StringId name_id = + context_->process_track_translation_table->TranslateName(raw_name_id); tracks->mutable_name()->Set(*tracks->id().IndexOf(track_id), name_id); } } diff --git a/src/trace_processor/importers/proto/track_event_tracker.cc b/src/trace_processor/importers/proto/track_event_tracker.cc index 56e081dd66..e758b9d051 100644 --- a/src/trace_processor/importers/proto/track_event_tracker.cc +++ b/src/trace_processor/importers/proto/track_event_tracker.cc @@ -18,6 +18,7 @@ #include "src/trace_processor/importers/common/args_tracker.h" #include "src/trace_processor/importers/common/args_translation_table.h" +#include "src/trace_processor/importers/common/process_track_translation_table.h" #include "src/trace_processor/importers/common/process_tracker.h" #include "src/trace_processor/importers/common/track_tracker.h" #include "src/trace_processor/tables/track_tables_py.h" @@ -201,7 +202,9 @@ std::optional TrackEventTracker::GetDescriptorTrack( reservation_it->second.is_counter) { return track_id; } - tracks->mutable_name()->Set(row, event_name); + const StringId track_name = + context_->process_track_translation_table->TranslateName(event_name); + tracks->mutable_name()->Set(row, track_name); return track_id; } diff --git a/src/trace_processor/importers/proto/translation_table_module.cc b/src/trace_processor/importers/proto/translation_table_module.cc index 8684df031e..f45e353268 100644 --- a/src/trace_processor/importers/proto/translation_table_module.cc +++ b/src/trace_processor/importers/proto/translation_table_module.cc @@ -16,6 +16,7 @@ #include "src/trace_processor/importers/proto/translation_table_module.h" #include "src/trace_processor/importers/common/args_translation_table.h" +#include "src/trace_processor/importers/common/process_track_translation_table.h" #include "src/trace_processor/importers/common/slice_translation_table.h" #include "src/trace_processor/importers/proto/packet_sequence_state_generation.h" @@ -54,6 +55,8 @@ ModuleResult TranslationTableModule::TokenizePacket( translation_table.chrome_performance_mark()); } else if (translation_table.has_slice_name()) { ParseSliceNameRules(translation_table.slice_name()); + } else if (translation_table.has_process_track_name()) { + ParseProcessTrackNameRules(translation_table.process_track_name()); } return ModuleResult::Handled(); } @@ -113,5 +116,17 @@ void TranslationTableModule::ParseSliceNameRules(protozero::ConstBytes bytes) { } } +void TranslationTableModule::ParseProcessTrackNameRules( + protozero::ConstBytes bytes) { + const auto process_track_name = + protos::pbzero::ProcessTrackNameTranslationTable::Decoder(bytes); + for (auto it = process_track_name.raw_to_deobfuscated_name(); it; ++it) { + protos::pbzero::ProcessTrackNameTranslationTable:: + RawToDeobfuscatedNameEntry::Decoder entry(*it); + context_->process_track_translation_table->AddNameTranslationRule( + entry.key(), entry.value()); + } +} + } // namespace trace_processor } // namespace perfetto diff --git a/src/trace_processor/importers/proto/translation_table_module.h b/src/trace_processor/importers/proto/translation_table_module.h index 2060f41392..62d4c725ba 100644 --- a/src/trace_processor/importers/proto/translation_table_module.h +++ b/src/trace_processor/importers/proto/translation_table_module.h @@ -46,6 +46,7 @@ class TranslationTableModule : public ProtoImporterModule { void ParseChromeUserEventRules(protozero::ConstBytes bytes); void ParseChromePerformanceMarkRules(protozero::ConstBytes bytes); void ParseSliceNameRules(protozero::ConstBytes bytes); + void ParseProcessTrackNameRules(protozero::ConstBytes bytes); TraceProcessorContext* context_; }; diff --git a/src/trace_processor/importers/proto/v8_sequence_state.h b/src/trace_processor/importers/proto/v8_sequence_state.h index 1b60ca0a28..14da638a26 100644 --- a/src/trace_processor/importers/proto/v8_sequence_state.h +++ b/src/trace_processor/importers/proto/v8_sequence_state.h @@ -57,7 +57,8 @@ class V8SequenceState final V8Tracker* const v8_tracker_; using InterningId = uint64_t; - base::FlatHashMap isolates_; + base::FlatHashMap> + isolates_; base::FlatHashMap js_scripts_; base::FlatHashMap wasm_scripts_; base::FlatHashMap js_functions_; diff --git a/src/trace_processor/importers/proto/v8_tracker.cc b/src/trace_processor/importers/proto/v8_tracker.cc index 8bacba0c85..d60bef1df8 100644 --- a/src/trace_processor/importers/proto/v8_tracker.cc +++ b/src/trace_processor/importers/proto/v8_tracker.cc @@ -23,6 +23,7 @@ #include #include "perfetto/base/logging.h" +#include "perfetto/base/status.h" #include "perfetto/ext/base/base64.h" #include "perfetto/ext/base/flat_hash_map.h" #include "perfetto/ext/base/string_view.h" @@ -144,7 +145,7 @@ V8Tracker::V8Tracker(TraceProcessorContext* context) : context_(context) {} V8Tracker::~V8Tracker() = default; -IsolateId V8Tracker::InternIsolate(protozero::ConstBytes bytes) { +std::optional V8Tracker::InternIsolate(protozero::ConstBytes bytes) { InternedV8Isolate::Decoder isolate(bytes); const IsolateKey isolate_key{ @@ -155,6 +156,13 @@ IsolateId V8Tracker::InternIsolate(protozero::ConstBytes bytes) { return *id; } + // TODO(b/347250452): Implement support for no code range + if (!isolate.has_code_range()) { + context_->storage->IncrementStats(stats::v8_isolate_has_no_code_range); + isolate_index_.Insert(isolate_key, std::nullopt); + return std::nullopt; + } + return *isolate_index_.Insert(isolate_key, CreateIsolate(isolate)).first; } @@ -178,7 +186,6 @@ UserMemoryMapping* V8Tracker::FindEmbeddedBlobMapping( std::pair V8Tracker::GetIsolateCodeRanges( UniquePid upid, const protos::pbzero::InternedV8Isolate::Decoder& isolate) { - // TODO(carlscab): Implement support for no code range PERFETTO_CHECK(isolate.has_code_range()); IsolateCodeRanges res; diff --git a/src/trace_processor/importers/proto/v8_tracker.h b/src/trace_processor/importers/proto/v8_tracker.h index b17affbd34..0abcc30ce9 100644 --- a/src/trace_processor/importers/proto/v8_tracker.h +++ b/src/trace_processor/importers/proto/v8_tracker.h @@ -20,6 +20,7 @@ #include #include #include +#include #include "perfetto/ext/base/flat_hash_map.h" #include "perfetto/ext/base/hash.h" @@ -52,7 +53,9 @@ class V8Tracker : public Destructible { ~V8Tracker() override; - IsolateId InternIsolate(protozero::ConstBytes bytes); + // Might return `std::nullopt` if we can not create an isolate because it has + // no code range (we do not support this yet). + std::optional InternIsolate(protozero::ConstBytes bytes); tables::V8JsScriptTable::Id InternJsScript(protozero::ConstBytes bytes, IsolateId isolate_id); tables::V8WasmScriptTable::Id InternWasmScript(protozero::ConstBytes bytes, @@ -171,7 +174,8 @@ class V8Tracker : public Destructible { // those here. base::FlatHashMap shared_code_ranges_; - base::FlatHashMap isolate_index_; + base::FlatHashMap, IsolateKey::Hasher> + isolate_index_; base::FlatHashMap, tables::V8JsScriptTable::Id, ScriptIndexHash> diff --git a/src/trace_processor/importers/proto/winscope/BUILD.gn b/src/trace_processor/importers/proto/winscope/BUILD.gn index 09b7b77b38..ab09809d56 100644 --- a/src/trace_processor/importers/proto/winscope/BUILD.gn +++ b/src/trace_processor/importers/proto/winscope/BUILD.gn @@ -16,6 +16,10 @@ import("../../../../../gn/perfetto_cc_proto_descriptor.gni") source_set("full") { sources = [ + "android_input_event_parser.cc", + "android_input_event_parser.h", + "protolog_message_decoder.cc", + "protolog_message_decoder.h", "protolog_messages_tracker.cc", "protolog_messages_tracker.h", "protolog_parser.cc", @@ -28,8 +32,8 @@ source_set("full") { "surfaceflinger_layers_parser.h", "surfaceflinger_transactions_parser.cc", "surfaceflinger_transactions_parser.h", - "winscope_args_parser.cc", - "winscope_args_parser.h", + "viewcapture_args_parser.cc", + "viewcapture_args_parser.h", "winscope_module.cc", "winscope_module.h", ] @@ -38,10 +42,10 @@ source_set("full") { "../:proto_importer_module", "../../../../../gn:default_deps", "../../../../../protos/perfetto/trace:zero", - "../../../../../protos/perfetto/trace/android:zero", "../../../../../protos/perfetto/trace/android:winscope_common_zero", "../../../../../protos/perfetto/trace/android:winscope_extensions_zero", "../../../../../protos/perfetto/trace/android:winscope_regular_zero", + "../../../../../protos/perfetto/trace/android:zero", "../../../../../protos/perfetto/trace/interned_data:zero", "../../../../../protos/perfetto/trace/profiling:zero", "../../../../protozero", diff --git a/src/trace_processor/importers/proto/winscope/android_input_event_parser.cc b/src/trace_processor/importers/proto/winscope/android_input_event_parser.cc new file mode 100644 index 0000000000..03bf5adb0e --- /dev/null +++ b/src/trace_processor/importers/proto/winscope/android_input_event_parser.cc @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "src/trace_processor/importers/proto/winscope/android_input_event_parser.h" + +#include "protos/perfetto/trace/android/android_input_event.pbzero.h" +#include "src/trace_processor/importers/common/args_tracker.h" +#include "src/trace_processor/importers/proto/args_parser.h" +#include "src/trace_processor/importers/proto/winscope/winscope.descriptor.h" +#include "src/trace_processor/storage/trace_storage.h" +#include "src/trace_processor/tables/android_tables_py.h" +#include "src/trace_processor/types/trace_processor_context.h" + +namespace perfetto::trace_processor { + +using perfetto::protos::pbzero::AndroidInputEvent; +using perfetto::protos::pbzero::AndroidKeyEvent; +using perfetto::protos::pbzero::AndroidMotionEvent; +using perfetto::protos::pbzero::AndroidWindowInputDispatchEvent; +using perfetto::protos::pbzero::TracePacket; + +AndroidInputEventParser::AndroidInputEventParser(TraceProcessorContext* context) + : context_(*context), args_parser_{pool_} { + pool_.AddFromFileDescriptorSet(kWinscopeDescriptor.data(), + kWinscopeDescriptor.size()); +} + +void AndroidInputEventParser::ParseAndroidInputEvent( + int64_t packet_ts, + const protozero::ConstBytes& bytes) { + auto input_event = AndroidInputEvent::Decoder(bytes); + + constexpr static auto supported_fields = std::array{ + AndroidInputEvent::kDispatcherMotionEventFieldNumber, + AndroidInputEvent::kDispatcherMotionEventRedactedFieldNumber, + AndroidInputEvent::kDispatcherKeyEventFieldNumber, + AndroidInputEvent::kDispatcherKeyEventRedactedFieldNumber, + AndroidInputEvent::kDispatcherWindowDispatchEventFieldNumber, + AndroidInputEvent::kDispatcherWindowDispatchEventRedactedFieldNumber}; + + for (auto sub_field_id : supported_fields) { + auto sub_field = input_event.Get(static_cast(sub_field_id)); + if (!sub_field.valid()) + continue; + + switch (sub_field_id) { + case AndroidInputEvent::kDispatcherMotionEventFieldNumber: + case AndroidInputEvent::kDispatcherMotionEventRedactedFieldNumber: + ParseMotionEvent(packet_ts, sub_field.as_bytes()); + return; + case AndroidInputEvent::kDispatcherKeyEventFieldNumber: + case AndroidInputEvent::kDispatcherKeyEventRedactedFieldNumber: + ParseKeyEvent(packet_ts, sub_field.as_bytes()); + return; + case AndroidInputEvent::kDispatcherWindowDispatchEventFieldNumber: + case AndroidInputEvent::kDispatcherWindowDispatchEventRedactedFieldNumber: + ParseWindowDispatchEvent(packet_ts, sub_field.as_bytes()); + return; + } + } +} + +void AndroidInputEventParser::ParseMotionEvent( + int64_t packet_ts, + const protozero::ConstBytes& bytes) { + AndroidMotionEvent::Decoder event_proto(bytes); + tables::AndroidMotionEventsTable::Row event_row; + event_row.event_id = event_proto.event_id(); + event_row.ts = packet_ts; + + auto event_row_id = context_.storage->mutable_android_motion_events_table() + ->Insert(event_row) + .id; + auto inserter = context_.args_tracker->AddArgsTo(event_row_id); + ArgsParser writer{packet_ts, inserter, *context_.storage}; + + base::Status status = + args_parser_.ParseMessage(bytes, ".perfetto.protos.AndroidMotionEvent", + nullptr /*parse all fields*/, writer); + if (!status.ok()) + context_.storage->IncrementStats(stats::android_input_event_parse_errors); +} + +void AndroidInputEventParser::ParseKeyEvent( + int64_t packet_ts, + const protozero::ConstBytes& bytes) { + AndroidKeyEvent::Decoder event_proto(bytes); + tables::AndroidKeyEventsTable::Row event_row; + event_row.event_id = event_proto.event_id(); + event_row.ts = packet_ts; + + auto event_row_id = context_.storage->mutable_android_key_events_table() + ->Insert(event_row) + .id; + auto inserter = context_.args_tracker->AddArgsTo(event_row_id); + ArgsParser writer{packet_ts, inserter, *context_.storage}; + + base::Status status = + args_parser_.ParseMessage(bytes, ".perfetto.protos.AndroidKeyEvent", + nullptr /*parse all fields*/, writer); + if (!status.ok()) + context_.storage->IncrementStats(stats::android_input_event_parse_errors); +} + +void AndroidInputEventParser::ParseWindowDispatchEvent( + int64_t packet_ts, + const protozero::ConstBytes& bytes) { + AndroidWindowInputDispatchEvent::Decoder event_proto(bytes); + tables::AndroidInputEventDispatchTable::Row event_row; + event_row.event_id = event_proto.event_id(); + event_row.vsync_id = event_proto.vsync_id(); + event_row.window_id = event_proto.window_id(); + + auto event_row_id = + context_.storage->mutable_android_input_event_dispatch_table() + ->Insert(event_row) + .id; + auto inserter = context_.args_tracker->AddArgsTo(event_row_id); + ArgsParser writer{packet_ts, inserter, *context_.storage}; + + base::Status status = args_parser_.ParseMessage( + bytes, ".perfetto.protos.AndroidWindowInputDispatchEvent", + nullptr /*parse all fields*/, writer); + if (!status.ok()) + context_.storage->IncrementStats(stats::android_input_event_parse_errors); +} + +} // namespace perfetto::trace_processor diff --git a/src/trace_processor/importers/proto/winscope/android_input_event_parser.h b/src/trace_processor/importers/proto/winscope/android_input_event_parser.h new file mode 100644 index 0000000000..96f523b2e6 --- /dev/null +++ b/src/trace_processor/importers/proto/winscope/android_input_event_parser.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_WINSCOPE_ANDROID_INPUT_EVENT_PARSER_H_ +#define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_WINSCOPE_ANDROID_INPUT_EVENT_PARSER_H_ + +#include +#include "perfetto/base/build_config.h" +#include "protos/perfetto/trace/trace_packet.pbzero.h" +#include "src/trace_processor/importers/common/parser_types.h" +#include "src/trace_processor/importers/proto/proto_importer_module.h" +#include "src/trace_processor/util/descriptors.h" +#include "src/trace_processor/util/proto_to_args_parser.h" + +namespace perfetto::trace_processor { + +class AndroidInputEventParser { + public: + explicit AndroidInputEventParser(TraceProcessorContext* context); + + void ParseAndroidInputEvent(int64_t packet_ts, + const protozero::ConstBytes& bytes); + + private: + TraceProcessorContext& context_; + DescriptorPool pool_; + util::ProtoToArgsParser args_parser_; + + void ParseMotionEvent(int64_t packet_ts, const protozero::ConstBytes& bytes); + void ParseKeyEvent(int64_t packet_ts, const protozero::ConstBytes& bytes); + void ParseWindowDispatchEvent(int64_t packet_ts, const protozero::ConstBytes& bytes); +}; + +} // namespace perfetto::trace_processor + +#endif // SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_WINSCOPE_ANDROID_INPUT_EVENT_PARSER_H_ diff --git a/src/trace_processor/importers/proto/winscope/protolog_message_decoder.cc b/src/trace_processor/importers/proto/winscope/protolog_message_decoder.cc new file mode 100644 index 0000000000..739c5401bb --- /dev/null +++ b/src/trace_processor/importers/proto/winscope/protolog_message_decoder.cc @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "src/trace_processor/importers/proto/winscope/protolog_message_decoder.h" + +#include +#include +#include + +#include "perfetto/ext/base/flat_hash_map.h" +#include "perfetto/ext/base/string_utils.h" +#include "perfetto/ext/base/string_view.h" + +namespace perfetto::trace_processor { + +ProtoLogMessageDecoder::ProtoLogMessageDecoder() = default; +ProtoLogMessageDecoder::~ProtoLogMessageDecoder() = default; + +std::optional ProtoLogMessageDecoder::Decode( + uint64_t message_id, + const std::vector& sint64_params, + const std::vector& double_params, + const std::vector& boolean_params, + const std::vector& string_params) { + auto tracked_message = tracked_messages_.Find(message_id); + if (tracked_message == nullptr) { + return std::nullopt; + } + + auto message = tracked_message->message; + + auto group = tracked_groups_.Find(tracked_message->group_id); + if (group == nullptr) { + return std::nullopt; + } + + std::string formatted_message; + formatted_message.reserve(message.size()); + + auto sint64_params_itr = sint64_params.begin(); + auto double_params_itr = double_params.begin(); + auto boolean_params_itr = boolean_params.begin(); + auto str_params_itr = string_params.begin(); + + for (size_t i = 0; i < message.length();) { + if (message.at(i) == '%' && i + 1 < message.length()) { + switch (message.at(i + 1)) { + case '%': + break; + case 'd': { + base::StackString<32> param("%" PRId64, *sint64_params_itr); + formatted_message.append(param.c_str()); + ++sint64_params_itr; + break; + } + case 'o': { + base::StackString<32> param("%" PRIo64, *sint64_params_itr); + formatted_message.append(param.c_str()); + ++sint64_params_itr; + break; + } + case 'x': { + base::StackString<32> param("%" PRIx64, *sint64_params_itr); + formatted_message.append(param.c_str()); + ++sint64_params_itr; + break; + } + case 'f': { + base::StackString<32> param("%f", *double_params_itr); + formatted_message.append(param.c_str()); + ++double_params_itr; + break; + } + case 'e': { + base::StackString<32> param("%e", *double_params_itr); + formatted_message.append(param.c_str()); + ++double_params_itr; + break; + } + case 'g': { + base::StackString<32> param("%g", *double_params_itr); + formatted_message.append(param.c_str()); + ++double_params_itr; + break; + } + case 's': { + formatted_message.append(*str_params_itr); + ++str_params_itr; + break; + } + case 'b': { + formatted_message.append(*boolean_params_itr ? "true" : "false"); + ++boolean_params_itr; + break; + } + default: + formatted_message.push_back(message[i]); + formatted_message.push_back(message[i + 1]); + } + + i += 2; + } else { + formatted_message.push_back(message[i]); + i += 1; + } + } + + return DecodedMessage{tracked_message->level, group->tag, formatted_message}; +} + +void ProtoLogMessageDecoder::TrackGroup(uint32_t id, const std::string& tag) { + tracked_groups_.Insert(id, TrackedGroup{tag}); +} + +void ProtoLogMessageDecoder::TrackMessage(uint64_t message_id, + ProtoLogLevel level, + uint32_t group_id, + const std::string& message) { + tracked_messages_.Insert(message_id, + TrackedMessage{level, group_id, message}); +} + +} // namespace perfetto::trace_processor diff --git a/src/trace_processor/importers/proto/winscope/protolog_message_decoder.h b/src/trace_processor/importers/proto/winscope/protolog_message_decoder.h new file mode 100644 index 0000000000..55388b4ea4 --- /dev/null +++ b/src/trace_processor/importers/proto/winscope/protolog_message_decoder.h @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_WINSCOPE_PROTOLOG_MESSAGE_DECODER_H_ +#define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_WINSCOPE_PROTOLOG_MESSAGE_DECODER_H_ + +#include +#include +#include +#include + +#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::trace_processor { + +enum ProtoLogLevel : int32_t { + DEBUG = 1, + VERBOSE = 2, + INFO = 3, + WARN = 4, + ERROR = 5, + WTF = 6, +}; + +struct DecodedMessage { + ProtoLogLevel log_level; + std::string group_tag; + std::string message; +}; + +struct TrackedGroup { + std::string tag; +}; + +struct TrackedMessage { + ProtoLogLevel level; + uint32_t group_id; + std::string message; +}; + +class ProtoLogMessageDecoder : public Destructible { + public: + explicit ProtoLogMessageDecoder(); + virtual ~ProtoLogMessageDecoder() override; + + static ProtoLogMessageDecoder* GetOrCreate(TraceProcessorContext* context) { + if (!context->protolog_message_decoder) { + context->protolog_message_decoder.reset(new ProtoLogMessageDecoder()); + } + return static_cast( + context->protolog_message_decoder.get()); + } + + std::optional Decode( + uint64_t message_id, + const std::vector& sint64_params, + const std::vector& double_params, + const std::vector& boolean_params, + const std::vector& string_params); + + void TrackGroup(uint32_t id, const std::string& tag); + + void TrackMessage(uint64_t message_id, + ProtoLogLevel level, + uint32_t group_id, + const std::string& message); + + private: + base::FlatHashMap tracked_groups_; + base::FlatHashMap tracked_messages_; +}; + +} // namespace perfetto::trace_processor + +#endif // SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_WINSCOPE_PROTOLOG_MESSAGE_DECODER_H_ 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 1cfd530cd3..2cf095a66a 100644 --- a/src/trace_processor/importers/proto/winscope/protolog_messages_tracker.cc +++ b/src/trace_processor/importers/proto/winscope/protolog_messages_tracker.cc @@ -43,4 +43,9 @@ ProtoLogMessagesTracker::GetTrackedMessagesByMessageId(uint64_t message_id) { return tracked_messages; } +void ProtoLogMessagesTracker::ClearTrackedMessagesForMessageId( + uint64_t message_id) { + tracked_protolog_messages.Erase(message_id); +} + } // 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 de0285f046..3a34ffb034 100644 --- a/src/trace_processor/importers/proto/winscope/protolog_messages_tracker.h +++ b/src/trace_processor/importers/proto/winscope/protolog_messages_tracker.h @@ -57,6 +57,7 @@ class ProtoLogMessagesTracker : public Destructible { void TrackMessage(TrackedProtoLogMessage tracked_protolog_message); std::optional*> GetTrackedMessagesByMessageId(uint64_t message_id); + void ClearTrackedMessagesForMessageId(uint64_t message_id); private: base::FlatHashMap> diff --git a/src/trace_processor/importers/proto/winscope/protolog_parser.cc b/src/trace_processor/importers/proto/winscope/protolog_parser.cc index a95777da93..e760fcabf5 100644 --- a/src/trace_processor/importers/proto/winscope/protolog_parser.cc +++ b/src/trace_processor/importers/proto/winscope/protolog_parser.cc @@ -34,6 +34,7 @@ #include "protos/perfetto/trace/profiling/profile_packet.pbzero.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_message_decoder.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/storage/stats.h" @@ -43,15 +44,6 @@ namespace perfetto::trace_processor { -enum ProtoLogLevel : int32_t { - DEBUG = 1, - VERBOSE = 2, - INFO = 3, - WARN = 4, - ERROR = 5, - WTF = 6, -}; - ProtoLogParser::ProtoLogParser(TraceProcessorContext* context) : context_(context), args_parser_{pool_}, @@ -126,179 +118,140 @@ void ProtoLogParser::ParseProtoLogMessage( auto* protolog_table = context_->storage->mutable_protolog_table(); tables::ProtoLogTable::Row row; + row.ts = timestamp; auto row_id = protolog_table->Insert(row).id; - auto* protolog_message_tracker = - ProtoLogMessagesTracker::GetOrCreate(context_); - struct ProtoLogMessagesTracker::TrackedProtoLogMessage tracked_message = { - protolog_message.message_id(), - std::move(sint64_params), - std::move(double_params), - std::move(boolean_params), - std::move(string_params), - stacktrace, - row_id, - timestamp}; - protolog_message_tracker->TrackMessage(std::move(tracked_message)); + auto* protolog_message_decoder = + ProtoLogMessageDecoder::GetOrCreate(context_); + + auto decoded_message_opt = protolog_message_decoder->Decode( + protolog_message.message_id(), sint64_params, double_params, + boolean_params, string_params); + if (decoded_message_opt.has_value()) { + auto decoded_message = decoded_message_opt.value(); + PopulateReservedRowWithMessage(row_id, decoded_message.log_level, + decoded_message.group_tag, + decoded_message.message, stacktrace); + } else { + // Viewer config used to decode messages not yet processed for this message. + // Delaying decoding... + auto* protolog_message_tracker = + ProtoLogMessagesTracker::GetOrCreate(context_); + + protolog_message_tracker->TrackMessage( + ProtoLogMessagesTracker::TrackedProtoLogMessage{ + protolog_message.message_id(), std::move(sint64_params), + std::move(double_params), std::move(boolean_params), + std::move(string_params), stacktrace, row_id, timestamp}); + } } void ProtoLogParser::ParseProtoLogViewerConfig(protozero::ConstBytes blob) { - auto* protolog_table = context_->storage->mutable_protolog_table(); - protos::pbzero::ProtoLogViewerConfig::Decoder protolog_viewer_config(blob); - base::FlatHashMap group_tags; + AddViewerConfigToMessageDecoder(protolog_viewer_config); + + for (auto it = protolog_viewer_config.messages(); it; ++it) { + protos::pbzero::ProtoLogViewerConfig::MessageData::Decoder message_data( + *it); + ProcessPendingMessagesWithId(message_data.message_id()); + } +} + +void ProtoLogParser::AddViewerConfigToMessageDecoder( + protos::pbzero::ProtoLogViewerConfig::Decoder& protolog_viewer_config) { + auto* protolog_message_decoder = + ProtoLogMessageDecoder::GetOrCreate(context_); + for (auto it = protolog_viewer_config.groups(); it; ++it) { protos::pbzero::ProtoLogViewerConfig::Group::Decoder group(*it); - group_tags.Insert(group.id(), group.tag().ToStdString()); + protolog_message_decoder->TrackGroup(group.id(), group.tag().ToStdString()); } - auto* protolog_message_tracker = - ProtoLogMessagesTracker::GetOrCreate(context_); - for (auto it = protolog_viewer_config.messages(); it; ++it) { protos::pbzero::ProtoLogViewerConfig::MessageData::Decoder message_data( *it); - auto tracked_messages_opt = - protolog_message_tracker->GetTrackedMessagesByMessageId( - message_data.message_id()); - - if (tracked_messages_opt.has_value()) { - auto* group_tag = group_tags.Find(message_data.group_id()); - - for (const auto& tracked_message : *tracked_messages_opt.value()) { - auto formatted_message = FormatMessage( - message_data.message().ToStdString(), tracked_message.sint64_params, - tracked_message.double_params, tracked_message.boolean_params, - tracked_message.string_params); - - auto row = - protolog_table->FindById(tracked_message.table_row_id).value(); - - row.set_ts(tracked_message.timestamp); - - StringPool::Id level; - switch (message_data.level()) { - case ProtoLogLevel::DEBUG: - level = log_level_debug_string_id_; - break; - case ProtoLogLevel::VERBOSE: - level = log_level_verbose_string_id_; - break; - case ProtoLogLevel::INFO: - level = log_level_info_string_id_; - break; - case ProtoLogLevel::WARN: - level = log_level_warn_string_id_; - break; - case ProtoLogLevel::ERROR: - level = log_level_error_string_id_; - break; - case ProtoLogLevel::WTF: - level = log_level_wtf_string_id_; - break; - default: - level = log_level_unknown_string_id_; - break; - } - row.set_level(level); - - auto tag = - context_->storage->InternString(base::StringView(*group_tag)); - row.set_tag(tag); - - auto message = context_->storage->InternString( - base::StringView(formatted_message)); - row.set_message(message); - - if (tracked_message.stacktrace.has_value()) { - row.set_stacktrace(tracked_message.stacktrace.value()); - } - } - } + protolog_message_decoder->TrackMessage( + message_data.message_id(), + static_cast(message_data.level()), + message_data.group_id(), message_data.message().ToStdString()); } } -std::string ProtoLogParser::FormatMessage( - const std::string& message, - const std::vector& sint64_params, - const std::vector& double_params, - const std::vector& boolean_params, - const std::vector& string_params) { - std::string formatted_message; - formatted_message.reserve(message.size()); - - auto sint64_params_itr = sint64_params.begin(); - auto double_params_itr = double_params.begin(); - auto boolean_params_itr = boolean_params.begin(); - auto str_params_itr = string_params.begin(); - - for (size_t i = 0; i < message.length();) { - if (message.at(i) == '%') { - switch (message.at(i + 1)) { - case '%': - break; - case 'd': { - base::StackString<32> param("%" PRId64, *sint64_params_itr); - formatted_message.append(param.c_str()); - ++sint64_params_itr; - break; - } - case 'o': { - base::StackString<32> param("%" PRIo64, *sint64_params_itr); - formatted_message.append(param.c_str()); - ++sint64_params_itr; - break; - } - case 'x': { - base::StackString<32> param("%" PRIx64, *sint64_params_itr); - formatted_message.append(param.c_str()); - ++sint64_params_itr; - break; - } - case 'f': { - base::StackString<32> param("%f", *double_params_itr); - formatted_message.append(param.c_str()); - ++double_params_itr; - break; - } - case 'e': { - base::StackString<32> param("%e", *double_params_itr); - formatted_message.append(param.c_str()); - ++double_params_itr; - break; - } - case 'g': { - base::StackString<32> param("%g", *double_params_itr); - formatted_message.append(param.c_str()); - ++double_params_itr; - break; - } - case 's': { - formatted_message.append(*str_params_itr); - ++str_params_itr; - break; - } - case 'b': { - formatted_message.append(*boolean_params_itr ? "true" : "false"); - ++boolean_params_itr; - break; - } - default: - // Should never happen - context_->storage->IncrementStats( - stats::winscope_protolog_invalid_interpolation_parse_errors); - } +void ProtoLogParser::ProcessPendingMessagesWithId(uint64_t message_id) { + auto* protolog_message_decoder = + ProtoLogMessageDecoder::GetOrCreate(context_); + auto* protolog_message_tracker = + ProtoLogMessagesTracker::GetOrCreate(context_); - i += 2; - } else { - formatted_message.push_back(message[i]); - i += 1; + auto tracked_messages_opt = + protolog_message_tracker->GetTrackedMessagesByMessageId(message_id); + + if (tracked_messages_opt.has_value()) { + // There are undecoded messages that can now be docoded to populate the + // table. + for (const auto& tracked_message : *tracked_messages_opt.value()) { + auto message = protolog_message_decoder + ->Decode(tracked_message.message_id, + tracked_message.sint64_params, + tracked_message.double_params, + tracked_message.boolean_params, + tracked_message.string_params) + .value(); + + PopulateReservedRowWithMessage( + tracked_message.table_row_id, message.log_level, message.group_tag, + message.message, tracked_message.stacktrace); } + + // Clear to avoid decoding again + protolog_message_tracker->ClearTrackedMessagesForMessageId(message_id); } +} + +void ProtoLogParser::PopulateReservedRowWithMessage( + tables::ProtoLogTable::Id table_row_id, + ProtoLogLevel log_level, + std::string& group_tag, + std::string& message, + std::optional stacktrace) { + auto* protolog_table = context_->storage->mutable_protolog_table(); + auto row = protolog_table->FindById(table_row_id).value(); + + StringPool::Id level; + switch (log_level) { + case ProtoLogLevel::DEBUG: + level = log_level_debug_string_id_; + break; + case ProtoLogLevel::VERBOSE: + level = log_level_verbose_string_id_; + break; + case ProtoLogLevel::INFO: + level = log_level_info_string_id_; + break; + case ProtoLogLevel::WARN: + level = log_level_warn_string_id_; + break; + case ProtoLogLevel::ERROR: + level = log_level_error_string_id_; + break; + case ProtoLogLevel::WTF: + level = log_level_wtf_string_id_; + break; + } + row.set_level(level); - return formatted_message; + auto tag = context_->storage->InternString(base::StringView(group_tag)); + row.set_tag(tag); + + auto message_string_id = + context_->storage->InternString(base::StringView(message)); + row.set_message(message_string_id); + + if (stacktrace.has_value()) { + row.set_stacktrace(stacktrace.value()); + } } } // 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 4c0db6febf..9e8549242a 100644 --- a/src/trace_processor/importers/proto/winscope/protolog_parser.h +++ b/src/trace_processor/importers/proto/winscope/protolog_parser.h @@ -21,6 +21,9 @@ #include #include +#include "protos/perfetto/trace/android/protolog.pbzero.h" +#include "src/trace_processor/importers/proto/winscope/protolog_message_decoder.h" +#include "src/trace_processor/importers/proto/winscope/protolog_messages_tracker.h" #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" @@ -38,11 +41,14 @@ class ProtoLogParser { void ParseProtoLogViewerConfig(protozero::ConstBytes); private: - std::string FormatMessage(const std::string& message, - const std::vector& sint64_params, - const std::vector& double_params, - const std::vector& boolean_params, - const std::vector& string_params); + void AddViewerConfigToMessageDecoder( + protos::pbzero::ProtoLogViewerConfig::Decoder& protolog_viewer_config); + void ProcessPendingMessagesWithId(uint64_t message_id); + void PopulateReservedRowWithMessage(tables::ProtoLogTable::Id table_row_id, + ProtoLogLevel level, + std::string& group_tag, + std::string& formatted_message, + std::optional stacktrace); TraceProcessorContext* const context_; DescriptorPool pool_; diff --git a/src/trace_processor/importers/proto/winscope/shell_transitions_parser.cc b/src/trace_processor/importers/proto/winscope/shell_transitions_parser.cc index 68d2a82c64..12618acdcc 100644 --- a/src/trace_processor/importers/proto/winscope/shell_transitions_parser.cc +++ b/src/trace_processor/importers/proto/winscope/shell_transitions_parser.cc @@ -19,8 +19,8 @@ #include "protos/perfetto/trace/android/shell_transition.pbzero.h" #include "src/trace_processor/importers/common/args_tracker.h" +#include "src/trace_processor/importers/proto/args_parser.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/trace_storage.h" #include "src/trace_processor/types/trace_processor_context.h" @@ -49,7 +49,7 @@ void ShellTransitionsParser::ParseTransition(protozero::ConstBytes blob) { } auto inserter = context_->args_tracker->AddArgsTo(row_id); - WinscopeArgsParser writer(inserter, *context_->storage.get()); + ArgsParser writer(/*timestamp=*/0, inserter, *context_->storage.get()); base::Status status = args_parser_.ParseMessage( blob, kShellTransitionsProtoName, nullptr /* parse all fields */, writer); diff --git a/src/trace_processor/importers/proto/winscope/surfaceflinger_layers_parser.cc b/src/trace_processor/importers/proto/winscope/surfaceflinger_layers_parser.cc index ebe370c6dc..20687e4e16 100644 --- a/src/trace_processor/importers/proto/winscope/surfaceflinger_layers_parser.cc +++ b/src/trace_processor/importers/proto/winscope/surfaceflinger_layers_parser.cc @@ -18,8 +18,8 @@ #include "protos/perfetto/trace/android/surfaceflinger_layers.pbzero.h" #include "src/trace_processor/importers/common/args_tracker.h" +#include "src/trace_processor/importers/proto/args_parser.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/types/trace_processor_context.h" namespace perfetto { @@ -44,7 +44,7 @@ void SurfaceFlingerLayersParser::Parse(int64_t timestamp, .id; auto inserter = context_->args_tracker->AddArgsTo(snapshot_id); - WinscopeArgsParser writer(inserter, *context_->storage.get()); + ArgsParser writer(timestamp, inserter, *context_->storage); base::Status status = args_parser_.ParseMessage(blob, kLayersSnapshotProtoName, &kLayersSnapshotFieldsToArgsParse, writer); @@ -55,11 +55,12 @@ void SurfaceFlingerLayersParser::Parse(int64_t timestamp, protos::pbzero::LayersProto::Decoder layers_decoder( snapshot_decoder.layers().data, snapshot_decoder.layers().size); for (auto it = layers_decoder.layers(); it; ++it) { - ParseLayer(*it, snapshot_id); + ParseLayer(timestamp, *it, snapshot_id); } } void SurfaceFlingerLayersParser::ParseLayer( + int64_t timestamp, protozero::ConstBytes blob, tables::SurfaceFlingerLayersSnapshotTable::Id snapshot_id) { tables::SurfaceFlingerLayerTable::Row layer; @@ -69,7 +70,7 @@ void SurfaceFlingerLayersParser::ParseLayer( ArgsTracker tracker(context_); auto inserter = tracker.AddArgsTo(layerId); - WinscopeArgsParser writer(inserter, *context_->storage.get()); + ArgsParser writer(timestamp, inserter, *context_->storage); base::Status status = args_parser_.ParseMessage( blob, kLayerProtoName, nullptr /* parse all fields */, writer); if (!status.ok()) { diff --git a/src/trace_processor/importers/proto/winscope/surfaceflinger_layers_parser.h b/src/trace_processor/importers/proto/winscope/surfaceflinger_layers_parser.h index 866cbae683..f615a8e426 100644 --- a/src/trace_processor/importers/proto/winscope/surfaceflinger_layers_parser.h +++ b/src/trace_processor/importers/proto/winscope/surfaceflinger_layers_parser.h @@ -39,7 +39,8 @@ class SurfaceFlingerLayersParser { ".perfetto.protos.LayersSnapshotProto"; static constexpr auto* kLayerProtoName = ".perfetto.protos.LayerProto"; - void ParseLayer(protozero::ConstBytes blob, + void ParseLayer(int64_t timestamp, + protozero::ConstBytes blob, tables::SurfaceFlingerLayersSnapshotTable::Id); TraceProcessorContext* const context_; diff --git a/src/trace_processor/importers/proto/winscope/surfaceflinger_transactions_parser.cc b/src/trace_processor/importers/proto/winscope/surfaceflinger_transactions_parser.cc index ae3a3e9121..c2c563813a 100644 --- a/src/trace_processor/importers/proto/winscope/surfaceflinger_transactions_parser.cc +++ b/src/trace_processor/importers/proto/winscope/surfaceflinger_transactions_parser.cc @@ -18,8 +18,8 @@ #include "protos/perfetto/trace/android/surfaceflinger_transactions.pbzero.h" #include "src/trace_processor/importers/common/args_tracker.h" +#include "src/trace_processor/importers/proto/args_parser.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/trace_storage.h" #include "src/trace_processor/types/trace_processor_context.h" @@ -43,7 +43,7 @@ void SurfaceFlingerTransactionsParser::Parse(int64_t timestamp, ArgsTracker tracker(context_); auto inserter = tracker.AddArgsTo(rowId); - WinscopeArgsParser writer(inserter, *context_->storage.get()); + ArgsParser writer(timestamp, inserter, *context_->storage.get()); base::Status status = args_parser_.ParseMessage(blob, kTransactionTraceEntryProtoName, nullptr /* parse all fields */, writer); diff --git a/src/trace_processor/importers/proto/winscope/viewcapture_args_parser.cc b/src/trace_processor/importers/proto/winscope/viewcapture_args_parser.cc new file mode 100644 index 0000000000..ed881632e4 --- /dev/null +++ b/src/trace_processor/importers/proto/winscope/viewcapture_args_parser.cc @@ -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. + */ + +#include "src/trace_processor/importers/proto/winscope/viewcapture_args_parser.h" +#include "perfetto/ext/base/string_utils.h" +#include "perfetto/ext/base/string_view.h" +#include "protos/perfetto/trace/interned_data/interned_data.pbzero.h" +#include "protos/perfetto/trace/profiling/profile_common.pbzero.h" +#include "src/trace_processor/importers/proto/packet_sequence_state_generation.h" + +namespace perfetto { +namespace trace_processor { + +ViewCaptureArgsParser::ViewCaptureArgsParser( + int64_t packet_timestamp, + ArgsTracker::BoundInserter& inserter, + TraceStorage& storage, + PacketSequenceStateGeneration* sequence_state) + : ArgsParser(packet_timestamp, inserter, storage, sequence_state), + storage_{storage} {} + +void ViewCaptureArgsParser::AddInteger(const Key& key, int64_t value) { + if (TryAddDeinternedString(key, static_cast(value))) { + return; + } + ArgsParser::AddInteger(key, value); +} + +void ViewCaptureArgsParser::AddUnsignedInteger(const Key& key, uint64_t value) { + if (TryAddDeinternedString(key, value)) { + return; + } + ArgsParser::AddUnsignedInteger(key, value); +} + +bool ViewCaptureArgsParser::TryAddDeinternedString(const Key& key, + uint64_t iid) { + bool is_interned_field = base::EndsWith(key.key, "_iid"); + if (!is_interned_field) { + return false; + } + + const auto deintern_key = key.key.substr(0, key.key.size() - 4); + const auto deintern_flat_key = + key.flat_key.substr(0, key.flat_key.size() - 4); + const auto deintern_key_combined = Key{deintern_flat_key, deintern_key}; + const auto deintern_val = TryDeinternString(key, iid); + + if (!deintern_val) { + ArgsParser::AddString( + deintern_key_combined, + protozero::ConstChars{ERROR_MSG.data(), ERROR_MSG.size()}); + storage_.IncrementStats( + stats::winscope_viewcapture_missing_interned_string_parse_errors); + return false; + } + + ArgsParser::AddString(deintern_key_combined, *deintern_val); + return true; +} + +std::optional ViewCaptureArgsParser::TryDeinternString( + const Key& key, + uint64_t iid) { + if (base::EndsWith(key.key, "class_name_iid")) { + auto* decoder = + seq_state() + ->LookupInternedMessage< + protos::pbzero::InternedData::kViewcaptureClassNameFieldNumber, + protos::pbzero::InternedString>(iid); + if (decoder) { + return protozero::ConstChars{ + reinterpret_cast(decoder->str().data), + decoder->str().size}; + } + } else if (base::EndsWith(key.key, "package_name_iid")) { + auto* decoder = + seq_state() + ->LookupInternedMessage(iid); + if (decoder) { + return protozero::ConstChars{ + reinterpret_cast(decoder->str().data), + decoder->str().size}; + } + } else if (base::EndsWith(key.key, "view_id_iid")) { + auto* decoder = + seq_state() + ->LookupInternedMessage< + protos::pbzero::InternedData::kViewcaptureViewIdFieldNumber, + protos::pbzero::InternedString>(iid); + if (decoder) { + return protozero::ConstChars{ + reinterpret_cast(decoder->str().data), + decoder->str().size}; + } + } else if (base::EndsWith(key.key, "window_name_iid")) { + auto* decoder = + seq_state() + ->LookupInternedMessage< + protos::pbzero::InternedData::kViewcaptureWindowNameFieldNumber, + protos::pbzero::InternedString>(iid); + if (decoder) { + return protozero::ConstChars{ + reinterpret_cast(decoder->str().data), + decoder->str().size}; + } + } + + return std::nullopt; +} + +} // namespace trace_processor +} // namespace perfetto diff --git a/src/trace_processor/importers/proto/winscope/viewcapture_args_parser.h b/src/trace_processor/importers/proto/winscope/viewcapture_args_parser.h new file mode 100644 index 0000000000..32b76b6e62 --- /dev/null +++ b/src/trace_processor/importers/proto/winscope/viewcapture_args_parser.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_WINSCOPE_VIEWCAPTURE_ARGS_PARSER_H_ +#define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_WINSCOPE_VIEWCAPTURE_ARGS_PARSER_H_ + +#include + +#include "src/trace_processor/importers/proto/args_parser.h" + +namespace perfetto { +namespace trace_processor { + +// Specialized args parser to de-intern ViewCapture strings +class ViewCaptureArgsParser : public ArgsParser { + public: + using Key = ArgsParser::Key; + + ViewCaptureArgsParser(int64_t packet_timestamp, + ArgsTracker::BoundInserter& inserter, + TraceStorage& storage, + PacketSequenceStateGeneration* sequence_state); + void AddInteger(const Key&, int64_t) override; + void AddUnsignedInteger(const Key&, uint64_t) override; + + private: + bool TryAddDeinternedString(const Key&, uint64_t); + std::optional TryDeinternString(const Key&, uint64_t); + + const base::StringView ERROR_MSG{"STRING DE-INTERNING ERROR"}; + TraceStorage& storage_; +}; + +} // namespace trace_processor +} // namespace perfetto + +#endif // SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_WINSCOPE_VIEWCAPTURE_ARGS_PARSER_H_ diff --git a/src/trace_processor/importers/proto/winscope/winscope_args_parser.cc b/src/trace_processor/importers/proto/winscope/winscope_args_parser.cc deleted file mode 100644 index aa4ff3e692..0000000000 --- a/src/trace_processor/importers/proto/winscope/winscope_args_parser.cc +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/trace_processor/importers/proto/winscope/winscope_args_parser.h" - -namespace perfetto { -namespace trace_processor { - -WinscopeArgsParser::WinscopeArgsParser(ArgsTracker::BoundInserter& inserter, - TraceStorage& storage) - : inserter_{inserter}, storage_{storage} {} - -void WinscopeArgsParser::AddInteger(const Key& key, int64_t value) { - const auto flat_key_id = - storage_.InternString(base::StringView(key.flat_key)); - const auto key_id = storage_.InternString(base::StringView(key.key)); - const auto variadic_val = Variadic::Integer(value); - inserter_.AddArg(flat_key_id, key_id, variadic_val); -} - -void WinscopeArgsParser::AddUnsignedInteger(const Key& key, uint64_t value) { - const auto flat_key_id = - storage_.InternString(base::StringView(key.flat_key)); - const auto key_id = storage_.InternString(base::StringView(key.key)); - const auto variadic_val = Variadic::UnsignedInteger(value); - inserter_.AddArg(flat_key_id, key_id, variadic_val); -} - -void WinscopeArgsParser::AddString(const Key& key, - const protozero::ConstChars& value) { - const auto flat_key_id = - storage_.InternString(base::StringView(key.flat_key)); - const auto key_id = storage_.InternString(base::StringView(key.key)); - const auto variadic_val = Variadic::String(storage_.InternString(value)); - inserter_.AddArg(flat_key_id, key_id, variadic_val); -} - -void WinscopeArgsParser::AddString(const Key& key, const std::string& value) { - const auto flat_key_id = - storage_.InternString(base::StringView(key.flat_key)); - const auto key_id = storage_.InternString(base::StringView(key.key)); - const auto variadic_val = - Variadic::String(storage_.InternString(base::StringView(value))); - inserter_.AddArg(flat_key_id, key_id, variadic_val); -} - -void WinscopeArgsParser::AddDouble(const Key& key, double value) { - const auto flat_key_id = - storage_.InternString(base::StringView(key.flat_key)); - const auto key_id = storage_.InternString(base::StringView(key.key)); - const auto variadic_val = Variadic::Real(value); - inserter_.AddArg(flat_key_id, key_id, variadic_val); -} - -void WinscopeArgsParser::AddPointer(const Key& key, const void* value) { - const auto flat_key_id = - storage_.InternString(base::StringView(key.flat_key)); - const auto key_id = storage_.InternString(base::StringView(key.key)); - const auto variadic_val = - Variadic::Pointer(reinterpret_cast(value)); - inserter_.AddArg(flat_key_id, key_id, variadic_val); -} - -void WinscopeArgsParser::AddBoolean(const Key& key, bool value) { - const auto flat_key_id = - storage_.InternString(base::StringView(key.flat_key)); - const auto key_id = storage_.InternString(base::StringView(key.key)); - const auto variadic_val = Variadic::Boolean(value); - inserter_.AddArg(flat_key_id, key_id, variadic_val); -} - -bool WinscopeArgsParser::AddJson(const Key&, const protozero::ConstChars&) { - PERFETTO_FATAL("Unexpected JSON value when parsing SurfaceFlinger data"); -} - -void WinscopeArgsParser::AddNull(const Key& key) { - const auto flat_key_id = - storage_.InternString(base::StringView(key.flat_key)); - const auto key_id = storage_.InternString(base::StringView(key.key)); - const auto variadic_val = Variadic::Null(); - inserter_.AddArg(flat_key_id, key_id, variadic_val); -} - -size_t WinscopeArgsParser::GetArrayEntryIndex(const std::string& array_key) { - return inserter_.GetNextArrayEntryIndex( - storage_.InternString(base::StringView(array_key))); -} - -size_t WinscopeArgsParser::IncrementArrayEntryIndex( - const std::string& array_key) { - return inserter_.IncrementArrayEntryIndex( - storage_.InternString(base::StringView(array_key))); -} - -PacketSequenceStateGeneration* WinscopeArgsParser::seq_state() { - return nullptr; -} - -InternedMessageView* WinscopeArgsParser::GetInternedMessageView( - uint32_t field_id, - uint64_t iid) { - base::ignore_result(field_id); - base::ignore_result(iid); - return nullptr; -} - -} // namespace trace_processor -} // namespace perfetto diff --git a/src/trace_processor/importers/proto/winscope/winscope_module.cc b/src/trace_processor/importers/proto/winscope/winscope_module.cc index 8d7e03753d..9946a2343c 100644 --- a/src/trace_processor/importers/proto/winscope/winscope_module.cc +++ b/src/trace_processor/importers/proto/winscope/winscope_module.cc @@ -17,8 +17,9 @@ #include "src/trace_processor/importers/proto/winscope/winscope_module.h" #include "protos/perfetto/trace/android/winscope_extensions.pbzero.h" #include "protos/perfetto/trace/android/winscope_extensions_impl.pbzero.h" +#include "src/trace_processor/importers/proto/args_parser.h" +#include "src/trace_processor/importers/proto/winscope/viewcapture_args_parser.h" #include "src/trace_processor/importers/proto/winscope/winscope.descriptor.h" -#include "src/trace_processor/importers/proto/winscope/winscope_args_parser.h" namespace perfetto { namespace trace_processor { @@ -32,7 +33,8 @@ WinscopeModule::WinscopeModule(TraceProcessorContext* context) surfaceflinger_layers_parser_(context), surfaceflinger_transactions_parser_(context), shell_transitions_parser_(context), - protolog_parser_(context) { + protolog_parser_(context), + android_input_event_parser_(context) { RegisterForField(TracePacket::kSurfaceflingerLayersSnapshotFieldNumber, context); RegisterForField(TracePacket::kSurfaceflingerTransactionsFieldNumber, @@ -76,13 +78,15 @@ void WinscopeModule::ParseTracePacketData(const TracePacket::Decoder& decoder, decoder.protolog_viewer_config()); return; case TracePacket::kWinscopeExtensionsFieldNumber: - ParseWinscopeExtensionsData(decoder.winscope_extensions(), timestamp); + ParseWinscopeExtensionsData(decoder.winscope_extensions(), timestamp, + data); return; } } void WinscopeModule::ParseWinscopeExtensionsData(protozero::ConstBytes blob, - int64_t timestamp) { + int64_t timestamp, + const TracePacketData& data) { WinscopeExtensionsImpl::Decoder decoder(blob.data, blob.size); if (auto field = @@ -97,6 +101,20 @@ void WinscopeModule::ParseWinscopeExtensionsData(protozero::ConstBytes blob, WinscopeExtensionsImpl::kInputmethodServiceFieldNumber); field.valid()) { ParseInputMethodServiceData(timestamp, field.as_bytes()); + } else if (field = + decoder.Get(WinscopeExtensionsImpl::kViewcaptureFieldNumber); + field.valid()) { + ParseViewCaptureData(timestamp, field.as_bytes(), + data.sequence_state.get()); + } else if (field = decoder.Get( + WinscopeExtensionsImpl::kAndroidInputEventFieldNumber); + field.valid()) { + android_input_event_parser_.ParseAndroidInputEvent(timestamp, + field.as_bytes()); + } else if (field = + decoder.Get(WinscopeExtensionsImpl::kWindowmanagerFieldNumber); + field.valid()) { + ParseWindowManagerData(timestamp, field.as_bytes()); } } @@ -109,7 +127,7 @@ void WinscopeModule::ParseInputMethodClientsData(int64_t timestamp, ArgsTracker tracker(context_); auto inserter = tracker.AddArgsTo(rowId); - WinscopeArgsParser writer(inserter, *context_->storage.get()); + ArgsParser writer(timestamp, inserter, *context_->storage.get()); base::Status status = args_parser_.ParseMessage(blob, kInputMethodClientsProtoName, nullptr /* parse all fields */, writer); @@ -130,7 +148,7 @@ void WinscopeModule::ParseInputMethodManagerServiceData( ArgsTracker tracker(context_); auto inserter = tracker.AddArgsTo(rowId); - WinscopeArgsParser writer(inserter, *context_->storage.get()); + ArgsParser writer(timestamp, inserter, *context_->storage.get()); base::Status status = args_parser_.ParseMessage(blob, kInputMethodManagerServiceProtoName, nullptr /* parse all fields */, writer); @@ -149,7 +167,7 @@ void WinscopeModule::ParseInputMethodServiceData(int64_t timestamp, ArgsTracker tracker(context_); auto inserter = tracker.AddArgsTo(rowId); - WinscopeArgsParser writer(inserter, *context_->storage.get()); + ArgsParser writer(timestamp, inserter, *context_->storage.get()); base::Status status = args_parser_.ParseMessage(blob, kInputMethodServiceProtoName, nullptr /* parse all fields */, writer); @@ -159,5 +177,41 @@ void WinscopeModule::ParseInputMethodServiceData(int64_t timestamp, } } +void WinscopeModule::ParseViewCaptureData( + int64_t timestamp, + protozero::ConstBytes blob, + PacketSequenceStateGeneration* sequence_state) { + tables::ViewCaptureTable::Row row; + row.ts = timestamp; + auto rowId = context_->storage->mutable_viewcapture_table()->Insert(row).id; + + ArgsTracker tracker(context_); + auto inserter = tracker.AddArgsTo(rowId); + ViewCaptureArgsParser writer(timestamp, inserter, *context_->storage.get(), + sequence_state); + base::Status status = args_parser_.ParseMessage( + blob, kViewCaptureProtoName, nullptr /* parse all fields */, writer); + if (!status.ok()) { + context_->storage->IncrementStats(stats::winscope_viewcapture_parse_errors); + } +} + +void WinscopeModule::ParseWindowManagerData(int64_t timestamp, + protozero::ConstBytes blob) { + tables::WindowManagerTable::Row row; + row.ts = timestamp; + auto rowId = context_->storage->mutable_windowmanager_table()->Insert(row).id; + + ArgsTracker tracker(context_); + auto inserter = tracker.AddArgsTo(rowId); + ArgsParser writer(timestamp, inserter, *context_->storage.get()); + base::Status status = args_parser_.ParseMessage( + blob, kWindowManagerProtoName, nullptr /* parse all fields */, writer); + if (!status.ok()) { + context_->storage->IncrementStats( + stats::winscope_windowmanager_parse_errors); + } +} + } // namespace trace_processor } // namespace perfetto diff --git a/src/trace_processor/importers/proto/winscope/winscope_module.h b/src/trace_processor/importers/proto/winscope/winscope_module.h index c77fcb49c7..649e329077 100644 --- a/src/trace_processor/importers/proto/winscope/winscope_module.h +++ b/src/trace_processor/importers/proto/winscope/winscope_module.h @@ -21,6 +21,7 @@ #include "perfetto/base/build_config.h" #include "src/trace_processor/importers/common/parser_types.h" #include "src/trace_processor/importers/proto/proto_importer_module.h" +#include "src/trace_processor/importers/proto/winscope/android_input_event_parser.h" #include "src/trace_processor/importers/proto/winscope/protolog_parser.h" #include "src/trace_processor/importers/proto/winscope/shell_transitions_parser.h" #include "src/trace_processor/importers/proto/winscope/surfaceflinger_layers_parser.h" @@ -42,21 +43,28 @@ class WinscopeModule : public ProtoImporterModule { private: void ParseWinscopeExtensionsData(protozero::ConstBytes blob, - int64_t timestamp); + int64_t timestamp, + const TracePacketData&); void ParseInputMethodClientsData(int64_t timestamp, protozero::ConstBytes blob); void ParseInputMethodManagerServiceData(int64_t timestamp, protozero::ConstBytes blob); void ParseInputMethodServiceData(int64_t timestamp, protozero::ConstBytes blob); + void ParseViewCaptureData(int64_t timestamp, + protozero::ConstBytes blob, + PacketSequenceStateGeneration* sequence_state); + void ParseWindowManagerData(int64_t timestamp, protozero::ConstBytes blob); static constexpr auto* kInputMethodClientsProtoName = ".perfetto.protos.InputMethodClientsTraceProto"; static constexpr auto* kInputMethodManagerServiceProtoName = ".perfetto.protos.InputMethodManagerServiceTraceProto"; - static constexpr auto* kInputMethodServiceProtoName = ".perfetto.protos.InputMethodServiceTraceProto"; + static constexpr auto* kViewCaptureProtoName = ".perfetto.protos.ViewCapture"; + static constexpr auto* kWindowManagerProtoName = + ".perfetto.protos.WindowManagerTraceEntry"; TraceProcessorContext* const context_; DescriptorPool pool_; @@ -66,6 +74,7 @@ class WinscopeModule : public ProtoImporterModule { SurfaceFlingerTransactionsParser surfaceflinger_transactions_parser_; ShellTransitionsParser shell_transitions_parser_; ProtoLogParser protolog_parser_; + AndroidInputEventParser android_input_event_parser_; }; } // namespace trace_processor diff --git a/src/trace_processor/importers/zip/BUILD.gn b/src/trace_processor/importers/zip/BUILD.gn new file mode 100644 index 0000000000..0262e02cd6 --- /dev/null +++ b/src/trace_processor/importers/zip/BUILD.gn @@ -0,0 +1,32 @@ +# Copyright (C) 2022 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +source_set("full") { + sources = [ + "zip_trace_reader.cc", + "zip_trace_reader.h", + ] + deps = [ + "../../../../gn:default_deps", + "../../../../include/perfetto/ext/base:base", + "../../../trace_processor:storage_minimal", + "../../types", + "../../util:trace_type", + "../../util:util", + "../../util:zip_reader", + "../android_bugreport", + "../common", + "../proto:minimal", + ] +} diff --git a/src/trace_processor/importers/zip/zip_trace_reader.cc b/src/trace_processor/importers/zip/zip_trace_reader.cc new file mode 100644 index 0000000000..11cc0655a2 --- /dev/null +++ b/src/trace_processor/importers/zip/zip_trace_reader.cc @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "src/trace_processor/importers/zip/zip_trace_reader.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "perfetto/base/logging.h" +#include "perfetto/base/status.h" +#include "perfetto/ext/base/status_or.h" +#include "perfetto/trace_processor/trace_blob.h" +#include "perfetto/trace_processor/trace_blob_view.h" +#include "src/trace_processor/forwarding_trace_parser.h" +#include "src/trace_processor/importers/android_bugreport/android_bugreport_reader.h" +#include "src/trace_processor/importers/common/trace_file_tracker.h" +#include "src/trace_processor/types/trace_processor_context.h" +#include "src/trace_processor/util/status_macros.h" +#include "src/trace_processor/util/trace_type.h" +#include "src/trace_processor/util/zip_reader.h" + +namespace perfetto::trace_processor { + +ZipTraceReader::ZipTraceReader(TraceProcessorContext* context) + : context_(context) {} +ZipTraceReader::~ZipTraceReader() = default; + +bool ZipTraceReader::Entry::operator<(const Entry& rhs) const { + // Traces with symbols should be the last ones to be read. + // TODO(carlscab): Proto traces with just ModuleSymbols packets should be an + // exception. We actually need those are the very end (once whe have all the + // Frames). Alternatively we could build a map address -> symbol during + // tokenization and use this during parsing to resolve symbols. + if (trace_type == kSymbolsTraceType) { + return false; + } + if (rhs.trace_type == kSymbolsTraceType) { + return true; + } + + // Proto traces should always parsed first as they might contains clock sync + // data needed to correctly parse other traces. + if (rhs.trace_type == TraceType::kProtoTraceType) { + return false; + } + if (trace_type == TraceType::kProtoTraceType) { + return true; + } + + return std::tie(name, index) < std::tie(rhs.name, rhs.index); +} + +base::Status ZipTraceReader::Parse(TraceBlobView blob) { + return zip_reader_.Parse(std::move(blob)); +} + +void ZipTraceReader::NotifyEndOfFile() { + base::Status status = NotifyEndOfFileImpl(); + if (!status.ok()) { + PERFETTO_ELOG("ZipTraceReader failed: %s", status.c_message()); + } +} + +base::Status ZipTraceReader::NotifyEndOfFileImpl() { + std::vector files = zip_reader_.TakeFiles(); + + // Android bug reports are ZIP files and its files do not get handled + // separately. + if (AndroidBugreportReader::IsAndroidBugReport(files)) { + return AndroidBugreportReader::Parse(context_, std::move(files)); + } + + base::StatusOr> entries = ExtractEntries(std::move(files)); + if (!entries.ok()) { + return entries.status(); + } + std::sort(entries->begin(), entries->end()); + + for (Entry& e : *entries) { + ScopedActiveTraceFile trace_file = + context_->trace_file_tracker->StartNewFile(e.name, e.trace_type, + e.uncompressed_data.size()); + + auto chunk_reader = std::make_unique(context_); + auto& parser = *chunk_reader; + context_->chunk_readers.push_back(std::move(chunk_reader)); + + RETURN_IF_ERROR(parser.Parse(std::move(e.uncompressed_data))); + parser.NotifyEndOfFile(); + + // Make sure the ForwardingTraceParser determined the same trace type as we + // did. + PERFETTO_CHECK(parser.trace_type() == e.trace_type); + } + return base::OkStatus(); +} + +base::StatusOr> +ZipTraceReader::ExtractEntries(std::vector files) { + // TODO(carlsacab): There is a lot of unnecessary copying going on here. + // ZipTraceReader can directly parse the ZIP file and given that we know the + // decompressed size we could directly decompress into TraceBlob chunks and + // send them to the tokenizer. + std::vector entries; + std::vector buffer; + for (size_t i = 0; i < files.size(); ++i) { + const util::ZipFile& zip_file = files[i]; + Entry entry; + entry.name = zip_file.name(); + entry.index = i; + RETURN_IF_ERROR(files[i].Decompress(&buffer)); + entry.uncompressed_data = + TraceBlobView(TraceBlob::CopyFrom(buffer.data(), buffer.size())); + entry.trace_type = GuessTraceType(entry.uncompressed_data.data(), + entry.uncompressed_data.size()); + entries.push_back(std::move(entry)); + } + return std::move(entries); +} + +} // namespace perfetto::trace_processor diff --git a/src/trace_processor/importers/zip/zip_trace_reader.h b/src/trace_processor/importers/zip/zip_trace_reader.h new file mode 100644 index 0000000000..880561892a --- /dev/null +++ b/src/trace_processor/importers/zip/zip_trace_reader.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_ZIP_ZIP_TRACE_READER_H_ +#define SRC_TRACE_PROCESSOR_IMPORTERS_ZIP_ZIP_TRACE_READER_H_ + +#include +#include +#include +#include + +#include "perfetto/base/status.h" +#include "perfetto/ext/base/status_or.h" +#include "perfetto/trace_processor/trace_blob_view.h" +#include "src/trace_processor/importers/common/chunked_trace_reader.h" +#include "src/trace_processor/util/trace_type.h" +#include "src/trace_processor/util/zip_reader.h" + +namespace perfetto::trace_processor { + +class ForwardingTraceParser; +class TraceProcessorContext; + +// Forwards files contained in a ZIP to the appropiate ChunkedTraceReader. It is +// guaranteed that proto traces will be parsed first. +class ZipTraceReader : public ChunkedTraceReader { + public: + explicit ZipTraceReader(TraceProcessorContext* context); + ~ZipTraceReader() override; + + // ChunkedTraceReader implementation + base::Status Parse(TraceBlobView) override; + void NotifyEndOfFile() override; + + private: + // Represents a file in the ZIP file. Used to sort them before sending the + // files one by one to a `ForwardingTraceParser` instance. + struct Entry { + // File name. Used to break ties. + std::string name; + // Position in the zip file. Used to break ties. + size_t index; + // Trace type. This is the main attribute traces are ordered by. Proto + // traces are always parsed first as they might contains clock sync + // data needed to correctly parse other traces. + TraceType trace_type; + TraceBlobView uncompressed_data; + // Comparator used to determine the order in which files in the ZIP will be + // read. + bool operator<(const Entry& rhs) const; + }; + + base::Status NotifyEndOfFileImpl(); + static base::StatusOr> ExtractEntries( + std::vector files); + base::Status ParseEntry(Entry entry); + + TraceProcessorContext* const context_; + util::ZipReader zip_reader_; +}; + +} // namespace perfetto::trace_processor + +#endif // SRC_TRACE_PROCESSOR_IMPORTERS_ZIP_ZIP_TRACE_READER_H_ diff --git a/src/trace_processor/metrics/metrics.h b/src/trace_processor/metrics/metrics.h index 1f4363ee4c..fdae244956 100644 --- a/src/trace_processor/metrics/metrics.h +++ b/src/trace_processor/metrics/metrics.h @@ -196,6 +196,9 @@ struct UnwrapMetricProto : public SqlFunction { // These functions implement the RepeatedField SQL aggregate functions. struct RepeatedField : public SqliteAggregateFunction { + static constexpr char kName[] = "RepeatedField"; + static constexpr int kArgCount = 1; + static void Step(sqlite3_context* ctx, int argc, sqlite3_value** argv); static void Final(sqlite3_context* ctx); }; diff --git a/src/trace_processor/metrics/sql/android/BUILD.gn b/src/trace_processor/metrics/sql/android/BUILD.gn index 9eb8b04e42..69b5915680 100644 --- a/src/trace_processor/metrics/sql/android/BUILD.gn +++ b/src/trace_processor/metrics/sql/android/BUILD.gn @@ -28,6 +28,7 @@ perfetto_sql_source_set("android") { "android_blocking_calls_unagg.sql", "android_boot.sql", "android_boot_unagg.sql", + "android_broadcasts.sql", "android_camera.sql", "android_camera_unagg.sql", "android_cpu.sql", @@ -141,5 +142,6 @@ perfetto_sql_source_set("android") { "sysui_notif_shade_list_builder_slices.sql", "sysui_update_notif_on_ui_mode_changed_metric.sql", "unsymbolized_frames.sql", + "wattson_app_startup.sql", ] } diff --git a/src/trace_processor/metrics/sql/android/android_blocking_calls_cuj_metric.sql b/src/trace_processor/metrics/sql/android/android_blocking_calls_cuj_metric.sql index 5838125096..6b3d457cdc 100644 --- a/src/trace_processor/metrics/sql/android/android_blocking_calls_cuj_metric.sql +++ b/src/trace_processor/metrics/sql/android/android_blocking_calls_cuj_metric.sql @@ -85,10 +85,8 @@ FROM all_cujs; -- slice, there needs to be 2 entries for that slice, one for each cuj id. -- (2) each slice needs to be trimmed to be fully inside the cuj associated -- (as we don't care about what's outside cujs) -DROP TABLE IF EXISTS android_blocking_calls_cuj_calls; -CREATE TABLE android_blocking_calls_cuj_calls AS -WITH -main_thread_slices_scoped_to_cujs AS ( +DROP TABLE IF EXISTS main_thread_slices_scoped_to_cujs; +CREATE PERFETTO TABLE main_thread_slices_scoped_to_cujs AS SELECT s.id, s.id AS slice_id, @@ -106,8 +104,12 @@ FROM _android_critical_blocking_calls s -- 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 -) + AND s.upid = cuj.upid; + + + +DROP TABLE IF EXISTS android_blocking_calls_cuj_calls; +CREATE TABLE android_blocking_calls_cuj_calls AS SELECT name, COUNT(*) AS occurrences, diff --git a/src/trace_processor/metrics/sql/android/android_blocking_calls_unagg.sql b/src/trace_processor/metrics/sql/android/android_blocking_calls_unagg.sql index 2008c0b2cb..d0fe3efb1b 100644 --- a/src/trace_processor/metrics/sql/android/android_blocking_calls_unagg.sql +++ b/src/trace_processor/metrics/sql/android/android_blocking_calls_unagg.sql @@ -36,6 +36,7 @@ SELECT MAX(dur) AS max_dur_ns, MIN(dur) AS min_dur_ns, SUM(dur) AS total_dur_ns, + AVG(dur) AS avg_dur_ns, upid, process_name FROM @@ -71,9 +72,11 @@ SELECT AndroidBlockingCallsUnagg( AndroidBlockingCall( 'name', d.name, 'cnt', d.occurrences, + 'avg_dur_ms', CAST(avg_dur_ns / 1e6 AS INT), 'total_dur_ms', CAST(total_dur_ns / 1e6 AS INT), 'max_dur_ms', CAST(max_dur_ns / 1e6 AS INT), 'min_dur_ms', CAST(min_dur_ns / 1e6 AS INT), + 'avg_dur_ns', CAST(d.avg_dur_ns AS INT), 'total_dur_ns', d.total_dur_ns, 'max_dur_ns', d.max_dur_ns, 'min_dur_ns', d.min_dur_ns @@ -81,6 +84,7 @@ SELECT AndroidBlockingCallsUnagg( ) FROM ( SELECT b.name, b.occurrences, + b.avg_dur_ns, b.total_dur_ns, b.max_dur_ns, b.min_dur_ns diff --git a/src/trace_processor/metrics/sql/android/android_boot.sql b/src/trace_processor/metrics/sql/android/android_boot.sql index 91403878f2..222c68196c 100644 --- a/src/trace_processor/metrics/sql/android/android_boot.sql +++ b/src/trace_processor/metrics/sql/android/android_boot.sql @@ -14,10 +14,29 @@ -- limitations under the License. -- -INCLUDE PERFETTO MODULE android.process_metadata; INCLUDE PERFETTO MODULE android.app_process_starts; +INCLUDE PERFETTO MODULE android.broadcasts; INCLUDE PERFETTO MODULE android.garbage_collection; INCLUDE PERFETTO MODULE android.oom_adjuster; +INCLUDE PERFETTO MODULE android.process_metadata; + +DROP VIEW IF EXISTS android_oom_adj_intervals_with_detailed_bucket_name; +CREATE PERFETTO VIEW android_oom_adj_intervals_with_detailed_bucket_name AS +SELECT + ts, + dur, + score, + android_oom_adj_score_to_detailed_bucket_name(score, android_appid) AS bucket, + upid, + process_name, + oom_adj_id, + oom_adj_ts, + oom_adj_dur, + oom_adj_track_id, + oom_adj_thread_name, + oom_adj_reason, + oom_adj_trigger +FROM _oom_adjuster_intervals; CREATE OR REPLACE PERFETTO FUNCTION get_durations(process_name STRING) RETURNS TABLE(uint_sleep_dur LONG, total_dur LONG) AS @@ -41,7 +60,7 @@ SELECT bucket, process_name, oom_adj_reason -FROM android_oom_adj_intervals; +FROM android_oom_adj_intervals_with_detailed_bucket_name; DROP VIEW IF EXISTS oom_adj_events_by_process_name; CREATE PERFETTO VIEW oom_adj_events_by_process_name AS @@ -280,7 +299,7 @@ SELECT AndroidBootMetric( NULL as name, bucket, SUM(dur) as total_dur - FROM android_oom_adj_intervals + FROM android_oom_adj_intervals_with_detailed_bucket_name WHERE ts > first_user_unlocked() GROUP BY bucket) ), @@ -296,7 +315,7 @@ SELECT AndroidBootMetric( process_name as name, bucket, SUM(dur) as total_dur - FROM android_oom_adj_intervals + FROM android_oom_adj_intervals_with_detailed_bucket_name WHERE ts > first_user_unlocked() AND process_name IS NOT NULL GROUP BY process_name, bucket) @@ -318,9 +337,80 @@ SELECT AndroidBootMetric( 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 + FROM android_oom_adj_intervals_with_detailed_bucket_name WHERE ts > first_user_unlocked() GROUP BY oom_adj_reason + ) + ), + 'post_boot_broadcast_process_count_by_intent', ( + SELECT RepeatedField( + AndroidBootMetric_BroadcastCountAggregation( + 'name', intent_action, + 'count', process_name_counts + ) + ) + FROM ( + SELECT + intent_action, + COUNT(process_name) as process_name_counts + FROM _android_broadcasts_minsdk_u + WHERE ts > first_user_unlocked() + GROUP BY intent_action + ) + ), + 'post_boot_broadcast_count_by_process', ( + SELECT RepeatedField( + AndroidBootMetric_BroadcastCountAggregation( + 'name', process_name, + 'count', broadcast_counts + ) + ) + FROM ( + SELECT + process_name, + COUNT(id) as broadcast_counts + FROM _android_broadcasts_minsdk_u + WHERE ts > first_user_unlocked() + GROUP BY process_name + ) + ), + 'post_boot_brodcast_duration_agg_by_intent', ( + SELECT RepeatedField( + AndroidBootMetric_BroadcastDurationAggregation( + 'name', intent_action, + 'avg_duration', avg_duration, + 'max_duration', max_duration, + 'sum_duration', sum_duration + ) + ) + FROM ( + SELECT + intent_action, + AVG(dur) as avg_duration, + SUM(dur) as sum_duration, + MAX(dur) as max_duration + FROM _android_broadcasts_minsdk_u + WHERE ts > first_user_unlocked() + GROUP BY intent_action + ) + ), 'post_boot_brodcast_duration_agg_by_process', ( + SELECT RepeatedField( + AndroidBootMetric_BroadcastDurationAggregation( + 'name', process_name, + 'avg_duration', avg_duration, + 'max_duration', max_duration, + 'sum_duration', sum_duration + ) + ) + FROM ( + SELECT + process_name, + AVG(dur) as avg_duration, + SUM(dur) as sum_duration, + MAX(dur) as max_duration + FROM _android_broadcasts_minsdk_u + WHERE ts > first_user_unlocked() + GROUP BY process_name ) ) ); diff --git a/src/trace_processor/metrics/sql/android/android_broadcasts.sql b/src/trace_processor/metrics/sql/android/android_broadcasts.sql new file mode 100644 index 0000000000..bba437cfb2 --- /dev/null +++ b/src/trace_processor/metrics/sql/android/android_broadcasts.sql @@ -0,0 +1,88 @@ +-- +-- 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.broadcasts; + +DROP VIEW IF EXISTS android_broadcasts_output; +CREATE PERFETTO VIEW android_broadcasts_output AS +SELECT AndroidBroadcastsMetric( + 'process_count_by_intent', ( + SELECT RepeatedField( + AndroidBroadcastsMetric_BroadcastCountAggregation( + 'name', intent_action, + 'count', process_name_counts + ) + ) + FROM ( + SELECT + intent_action, + COUNT(process_name) as process_name_counts + FROM _android_broadcasts_minsdk_u + GROUP BY intent_action + ) + ), + 'broadcast_count_by_process', ( + SELECT RepeatedField( + AndroidBroadcastsMetric_BroadcastCountAggregation( + 'name', process_name, + 'count', broadcast_counts + ) + ) + FROM ( + SELECT + process_name, + COUNT(id) as broadcast_counts + FROM _android_broadcasts_minsdk_u + GROUP BY process_name + ) + ), + 'brodcast_duration_agg_by_intent', ( + SELECT RepeatedField( + AndroidBroadcastsMetric_BroadcastDurationAggregation( + 'name', intent_action, + 'avg_duration', avg_duration, + 'max_duration', max_duration, + 'sum_duration', sum_duration + ) + ) + FROM ( + SELECT + intent_action, + AVG(dur) as avg_duration, + SUM(dur) as sum_duration, + MAX(dur) as max_duration + FROM _android_broadcasts_minsdk_u + GROUP BY intent_action + ) + ), 'brodcast_duration_agg_by_process', ( + SELECT RepeatedField( + AndroidBroadcastsMetric_BroadcastDurationAggregation( + 'name', process_name, + 'avg_duration', avg_duration, + 'max_duration', max_duration, + 'sum_duration', sum_duration + ) + ) + FROM ( + SELECT + process_name, + AVG(dur) as avg_duration, + SUM(dur) as sum_duration, + MAX(dur) as max_duration + FROM _android_broadcasts_minsdk_u + GROUP BY process_name + ) + ) +) \ No newline at end of file diff --git a/src/trace_processor/metrics/sql/android/android_camera.sql b/src/trace_processor/metrics/sql/android/android_camera.sql index 3e2cb4aa8c..a893cf6b46 100644 --- a/src/trace_processor/metrics/sql/android/android_camera.sql +++ b/src/trace_processor/metrics/sql/android/android_camera.sql @@ -30,7 +30,9 @@ FROM rss_and_swap_span JOIN ( SELECT max(start_ts), upid FROM process - WHERE name GLOB '*GoogleCamera*' + WHERE name GLOB '*GoogleCamera' + OR name GLOB '*googlecamera.fishfood' + OR name GLOB '*GoogleCameraEng' LIMIT 1 ) AS gca USING (upid); diff --git a/src/trace_processor/metrics/sql/android/android_dma_heap.sql b/src/trace_processor/metrics/sql/android/android_dma_heap.sql index 03813f7d5f..0870fb37d9 100644 --- a/src/trace_processor/metrics/sql/android/android_dma_heap.sql +++ b/src/trace_processor/metrics/sql/android/android_dma_heap.sql @@ -14,6 +14,8 @@ -- limitations under the License. -- +INCLUDE PERFETTO MODULE android.memory.dmabuf; + DROP VIEW IF EXISTS dma_heap_timeline; CREATE PERFETTO VIEW dma_heap_timeline AS SELECT @@ -34,45 +36,23 @@ SELECT MAX(value) AS max_size FROM dma_heap_timeline; -DROP VIEW IF EXISTS dma_heap_raw_allocs; -CREATE PERFETTO VIEW dma_heap_raw_allocs AS -SELECT - ts, - value AS instant_value, - SUM(value) OVER win AS value -FROM counter c JOIN thread_counter_track t ON c.track_id = t.id -WHERE (name = 'mem.dma_heap_change') AND value > 0 -WINDOW win AS ( - PARTITION BY name ORDER BY ts - ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW -); - -DROP VIEW IF EXISTS dma_heap_total_stats; -CREATE PERFETTO VIEW dma_heap_total_stats AS -SELECT - SUM(instant_value) AS total_alloc_size_bytes -FROM dma_heap_raw_allocs; - --- We need to group by ts here as we can have two ion events from --- different processes occurring at the same timestamp. We take the --- max as this will take both allocations into account at that --- timestamp. -DROP VIEW IF EXISTS android_dma_heap_event; -CREATE PERFETTO VIEW android_dma_heap_event AS -SELECT - 'counter' AS track_type, - printf('Buffers created from DMA-BUF heaps: ') AS track_name, - ts, - MAX(value) AS value -FROM dma_heap_raw_allocs -GROUP BY 1, 2, 3; - DROP VIEW IF EXISTS android_dma_heap_output; CREATE PERFETTO VIEW android_dma_heap_output AS +WITH _process_stats AS ( + SELECT process_name, SUM(buf_size) delta + FROM android_dmabuf_allocs + GROUP BY 1 +) SELECT AndroidDmaHeapMetric( - 'avg_size_bytes', avg_size, - 'min_size_bytes', min_size, - 'max_size_bytes', max_size, - 'total_alloc_size_bytes', total_alloc_size_bytes + 'avg_size_bytes', (SELECT avg_size FROM dma_heap_stats), + 'min_size_bytes', (SELECT min_size FROM dma_heap_stats), + 'max_size_bytes', (SELECT max_size FROM dma_heap_stats), + 'total_alloc_size_bytes', ( + SELECT CAST(SUM(buf_size) AS DOUBLE) FROM android_dmabuf_allocs WHERE buf_size > 0 + ), + 'total_delta_bytes', (SELECT SUM(buf_size) FROM android_dmabuf_allocs), + 'process_stats', ( + SELECT RepeatedField(AndroidDmaHeapMetric_ProcessStats('process_name', process_name, 'delta_bytes', delta)) + FROM _process_stats ) -FROM dma_heap_stats JOIN dma_heap_total_stats; +); diff --git a/src/trace_processor/metrics/sql/android/android_lmk_reason.sql b/src/trace_processor/metrics/sql/android/android_lmk_reason.sql index 0ca22583a8..b7c7ca524f 100644 --- a/src/trace_processor/metrics/sql/android/android_lmk_reason.sql +++ b/src/trace_processor/metrics/sql/android/android_lmk_reason.sql @@ -64,8 +64,8 @@ lmk_process_sizes AS ( FROM lmk_events JOIN rss_and_swap_span WHERE lmk_events.ts - BETWEEN rss_and_swap_span.ts - AND rss_and_swap_span.ts + MAX(rss_and_swap_span.dur - 1, 0) + BETWEEN rss_and_swap_span.ts + AND rss_and_swap_span.ts + MAX(rss_and_swap_span.dur - 1, 0) ), lmk_process_sizes_output AS ( SELECT ts, RepeatedField(AndroidLmkReasonMetric_Process( diff --git a/src/trace_processor/metrics/sql/android/android_oom_adjuster.sql b/src/trace_processor/metrics/sql/android/android_oom_adjuster.sql index 7b752a89d1..156458608b 100644 --- a/src/trace_processor/metrics/sql/android/android_oom_adjuster.sql +++ b/src/trace_processor/metrics/sql/android/android_oom_adjuster.sql @@ -15,6 +15,51 @@ -- INCLUDE PERFETTO MODULE android.oom_adjuster; +DROP VIEW IF EXISTS android_oom_adj_intervals_with_detailed_bucket_name; +CREATE PERFETTO VIEW android_oom_adj_intervals_with_detailed_bucket_name ( + -- Timestamp the oom_adj score of the process changed + ts INT, + -- Duration until the next oom_adj score change of the process. + dur INT, + -- oom_adj score of the process. + score INT, + -- oom_adj bucket of the process. + bucket STRING, + -- Upid of the process having an oom_adj update. + upid INT, + -- Name of the process having an oom_adj update. + process_name STRING, + -- Slice id of the latest oom_adj update in the system_server. + oom_adj_id INT, + -- Timestamp of the latest oom_adj update in the system_server. + oom_adj_ts INT, + -- Duration of the latest oom_adj update in the system_server. + oom_adj_dur INT, + -- Track id of the latest oom_adj update in the system_server + oom_adj_track_id INT, + -- Thread name of the latest oom_adj update in the system_server. + oom_adj_thread_name STRING, + -- Reason for the latest oom_adj update in the system_server. + oom_adj_reason STRING, + -- Trigger for the latest oom_adj update in the system_server. + oom_adj_trigger STRING + ) AS +SELECT + ts, + dur, + score, + android_oom_adj_score_to_detailed_bucket_name(score, android_appid) AS bucket, + upid, + process_name, + oom_adj_id, + oom_adj_ts, + oom_adj_dur, + oom_adj_track_id, + oom_adj_thread_name, + oom_adj_reason, + oom_adj_trigger +FROM _oom_adjuster_intervals; + DROP TABLE IF EXISTS _oom_adj_events_with_src_bucket; CREATE PERFETTO TABLE _oom_adj_events_with_src_bucket AS @@ -24,7 +69,7 @@ SELECT bucket, process_name, oom_adj_reason -FROM android_oom_adj_intervals; +FROM android_oom_adj_intervals_with_detailed_bucket_name; DROP VIEW IF EXISTS oom_adj_events_by_process_name; CREATE PERFETTO VIEW oom_adj_events_by_process_name AS @@ -100,7 +145,7 @@ SELECT AndroidOomAdjusterMetric( ) FROM ( SELECT NULL as name, bucket, SUM(dur) as total_dur - FROM android_oom_adj_intervals GROUP BY bucket + FROM android_oom_adj_intervals_with_detailed_bucket_name GROUP BY bucket ) ), 'oom_adj_bucket_duration_agg_by_process',(SELECT RepeatedField( @@ -112,7 +157,7 @@ SELECT AndroidOomAdjusterMetric( ) FROM ( SELECT process_name as name, bucket, SUM(dur) as total_dur - FROM android_oom_adj_intervals + FROM android_oom_adj_intervals_with_detailed_bucket_name WHERE process_name IS NOT NULL GROUP BY process_name, bucket ) @@ -133,7 +178,7 @@ SELECT AndroidOomAdjusterMetric( 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 + FROM android_oom_adj_intervals_with_detailed_bucket_name GROUP BY oom_adj_reason ) ) ); diff --git a/src/trace_processor/metrics/sql/android/android_startup.sql b/src/trace_processor/metrics/sql/android/android_startup.sql index 95d050bbe7..2e2e48e71e 100644 --- a/src/trace_processor/metrics/sql/android/android_startup.sql +++ b/src/trace_processor/metrics/sql/android/android_startup.sql @@ -514,7 +514,7 @@ SELECT ) ), - 'slow_start_reason_detailed', get_slow_start_reason_detailed(launches.startup_id) + 'slow_start_reason_with_details', get_slow_start_reason_with_details(launches.startup_id) ) AS startup FROM android_startups launches; diff --git a/src/trace_processor/metrics/sql/android/jank/frames.sql b/src/trace_processor/metrics/sql/android/jank/frames.sql index ef47b1c27f..8f72e96805 100644 --- a/src/trace_processor/metrics/sql/android/jank/frames.sql +++ b/src/trace_processor/metrics/sql/android/jank/frames.sql @@ -13,6 +13,8 @@ -- See the License for the specific language governing permissions and -- limitations under the License. +INCLUDE PERFETTO MODULE android.frames.jank_type; + DROP TABLE IF EXISTS vsync_missed_callback; CREATE PERFETTO TABLE vsync_missed_callback AS SELECT CAST(STR_SPLIT(name, 'Callback#', 1) AS INTEGER) AS vsync, @@ -35,14 +37,9 @@ SELECT cuj_id, vsync, -- We use MAX to check if at least one of the layers jank_type matches the pattern - MAX(jank_type GLOB '*App Deadline Missed*') AS app_missed, + MAX(android_is_app_jank_type(jank_type)) AS app_missed, -- We use MAX to check if at least one of the layers jank_type matches the pattern - MAX( - jank_type GLOB '*SurfaceFlinger CPU Deadline Missed*' - OR jank_type GLOB '*SurfaceFlinger GPU Deadline Missed*' - OR jank_type GLOB '*SurfaceFlinger Scheduling*' - OR jank_type GLOB '*Prediction Error*' - OR jank_type GLOB '*Display HAL*') AS sf_missed, + MAX(android_is_sf_jank_type(jank_type)) AS sf_missed, IFNULL(MAX(sf_callback_missed), 0) AS sf_callback_missed, IFNULL(MAX(hwui_callback_missed), 0) AS hwui_callback_missed, -- We use MIN to check if ALL layers finished on time diff --git a/src/trace_processor/metrics/sql/android/jank/params.sql b/src/trace_processor/metrics/sql/android/jank/params.sql index a8d555a741..3a5d0b05c7 100644 --- a/src/trace_processor/metrics/sql/android/jank/params.sql +++ b/src/trace_processor/metrics/sql/android/jank/params.sql @@ -7,7 +7,8 @@ VALUES ('SPLASHSCREEN_AVD', 'll.splashscreen'), ('ONE_HANDED_ENTER_TRANSITION::*', 'wmshell.main'), ('ONE_HANDED_EXIT_TRANSITION::*', 'wmshell.main'), -('PIP_TRANSITION::*', 'wmshell.main'); +('PIP_TRANSITION::*', 'wmshell.main'), +('BACK_PANEL_ARROW', 'BackPanelUiThre'); -- Matches each CUJ with the right set of parameters. diff --git a/src/trace_processor/metrics/sql/android/java_heap_class_stats.sql b/src/trace_processor/metrics/sql/android/java_heap_class_stats.sql index 531b440d41..47299ae8c5 100644 --- a/src/trace_processor/metrics/sql/android/java_heap_class_stats.sql +++ b/src/trace_processor/metrics/sql/android/java_heap_class_stats.sql @@ -16,54 +16,7 @@ SELECT RUN_METRIC('android/process_metadata.sql'); -INCLUDE PERFETTO MODULE memory.heap_graph_dominator_tree; -INCLUDE PERFETTO MODULE graphs.partition; - -DROP TABLE IF EXISTS _heap_graph_dominator_tree_for_partition; -CREATE PERFETTO TABLE _heap_graph_dominator_tree_for_partition AS -SELECT - tree.id, - tree.idom_id as parent_id, - obj.type_id as group_key -FROM memory_heap_graph_dominator_tree tree -JOIN heap_graph_object obj USING(id) -UNION ALL --- provide a single root required by tree partition if heap graph exists. -SELECT - memory_heap_graph_super_root_fn() AS id, - NULL AS parent_id, - (SELECT MAX(id) + 1 FROM heap_graph_class) AS group_key -WHERE memory_heap_graph_super_root_fn() IS NOT NULL; - -DROP TABLE IF EXISTS _heap_object_marked_for_dominated_stats; -CREATE PERFETTO TABLE _heap_object_marked_for_dominated_stats AS -SELECT - id, - IIF(parent_id IS NULL, 1, 0) as marked -FROM tree_structural_partition_by_group!(_heap_graph_dominator_tree_for_partition) -ORDER BY id; - -DROP TABLE IF EXISTS _heap_class_stats; -CREATE PERFETTO TABLE _heap_class_stats AS -SELECT - obj.upid, - obj.graph_sample_ts, - obj.type_id, - COUNT(1) AS obj_count, - SUM(self_size) AS size_bytes, - SUM(native_size) AS native_size_bytes, - SUM(IIF(obj.reachable, 1, 0)) AS reachable_obj_count, - SUM(IIF(obj.reachable, self_size, 0)) AS reachable_size_bytes, - SUM(IIF(obj.reachable, native_size, 0)) AS reachable_native_size_bytes, - SUM(IIF(marked, dominated_obj_count, 0)) AS dominated_obj_count, - SUM(IIF(marked, dominated_size_bytes, 0)) AS dominated_size_bytes, - SUM(IIF(marked, dominated_native_size_bytes, 0)) AS dominated_native_size_bytes -FROM heap_graph_object obj --- Left joins to preserve unreachable objects. -LEFT JOIN _heap_object_marked_for_dominated_stats USING(id) -LEFT JOIN memory_heap_graph_dominator_tree USING(id) -GROUP BY 1, 2, 3 -ORDER BY 1, 2, 3; +INCLUDE PERFETTO MODULE android.memory.heap_graph.heap_graph_class_aggregation; DROP VIEW IF EXISTS java_heap_class_stats_output; CREATE PERFETTO VIEW java_heap_class_stats_output AS @@ -74,7 +27,8 @@ heap_class_stats_count_protos AS ( upid, graph_sample_ts, RepeatedField(JavaHeapClassStats_TypeCount( - 'type_name', IFNULL(c.deobfuscated_name, c.name), + 'type_name', type_name, + 'is_libcore_or_array', is_libcore_or_array, 'obj_count', obj_count, 'size_bytes', size_bytes, 'native_size_bytes', native_size_bytes, @@ -85,8 +39,7 @@ heap_class_stats_count_protos AS ( 'dominated_size_bytes', dominated_size_bytes, 'dominated_native_size_bytes', dominated_native_size_bytes )) AS count_protos - FROM _heap_class_stats s - JOIN heap_graph_class c ON s.type_id = c.id + FROM android_heap_graph_class_aggregation s GROUP BY 1, 2 ), -- Group by to build the repeated field by upid diff --git a/src/trace_processor/metrics/sql/android/network_activity_template.sql b/src/trace_processor/metrics/sql/android/network_activity_template.sql index adba404276..b82058dc67 100644 --- a/src/trace_processor/metrics/sql/android/network_activity_template.sql +++ b/src/trace_processor/metrics/sql/android/network_activity_template.sql @@ -50,16 +50,17 @@ WITH quantized AS ( with_last AS ( SELECT *, - LAG(ts+dur) OVER ( + MAX(ts+dur) OVER ( PARTITION BY {{group_by}} ORDER BY ts - ) AS last_ts + ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING + ) AS max_end_so_far FROM quantized ), with_group AS ( SELECT *, - COUNT(IIF(ts-last_ts>{{idle_ns}}, 1, null)) OVER ( + COUNT(IIF(ts-max_end_so_far>{{idle_ns}}, 1, null)) OVER ( PARTITION BY {{group_by}} ORDER BY ts ) AS group_id diff --git a/src/trace_processor/metrics/sql/android/process_mem.sql b/src/trace_processor/metrics/sql/android/process_mem.sql index f52f63e1f2..4622462bca 100644 --- a/src/trace_processor/metrics/sql/android/process_mem.sql +++ b/src/trace_processor/metrics/sql/android/process_mem.sql @@ -14,75 +14,52 @@ -- limitations under the License. -- --- Create all the views used to generate the Android Memory metrics proto. --- Anon RSS -SELECT RUN_METRIC('android/process_counter_span_view.sql', - 'table_name', 'anon_rss', - 'counter_name', 'mem.rss.anon'); +INCLUDE PERFETTO MODULE android.memory.process; +INCLUDE PERFETTO MODULE linux.memory.process; --- File RSS -SELECT RUN_METRIC('android/process_counter_span_view.sql', - 'table_name', 'file_rss', - 'counter_name', 'mem.rss.file'); +SELECT RUN_METRIC('android/process_oom_score.sql'); -SELECT RUN_METRIC('android/process_counter_span_view.sql', - 'table_name', 'shmem_rss', - 'counter_name', 'mem.rss.shmem'); +DROP VIEW IF EXISTS anon_rss_span; +CREATE PERFETTO VIEW anon_rss_span AS +SELECT * FROM _anon_rss; --- Swap -SELECT RUN_METRIC('android/process_counter_span_view.sql', - 'table_name', 'swap', - 'counter_name', 'mem.swap'); +DROP VIEW IF EXISTS file_rss_span; +CREATE PERFETTO VIEW file_rss_span AS +SELECT * FROM _file_rss; --- OOM score -SELECT RUN_METRIC('android/process_oom_score.sql'); +DROP VIEW IF EXISTS shmem_rss_span; +CREATE PERFETTO VIEW shmem_rss_span AS +SELECT * FROM _shmem_rss; --- Anon RSS + Swap -DROP TABLE IF EXISTS anon_and_swap_join; -CREATE VIRTUAL TABLE anon_and_swap_join -USING SPAN_OUTER_JOIN(anon_rss_span PARTITIONED upid, swap_span PARTITIONED upid); +DROP VIEW IF EXISTS swap_span; +CREATE PERFETTO VIEW swap_span AS +SELECT * FROM _swap; DROP VIEW IF EXISTS anon_and_swap_span; CREATE PERFETTO VIEW anon_and_swap_span AS SELECT ts, dur, upid, IFNULL(anon_rss_val, 0) + IFNULL(swap_val, 0) AS anon_and_swap_val -FROM anon_and_swap_join; - --- Anon RSS + file RSS + Swap -DROP TABLE IF EXISTS anon_and_file_and_swap_join; -CREATE VIRTUAL TABLE anon_and_file_and_swap_join -USING SPAN_OUTER_JOIN( - anon_and_swap_join PARTITIONED upid, - file_rss_span PARTITIONED upid -); - --- RSS + Swap -DROP TABLE IF EXISTS rss_and_swap_join; -CREATE VIRTUAL TABLE rss_and_swap_join -USING SPAN_OUTER_JOIN( - anon_and_file_and_swap_join PARTITIONED upid, - shmem_rss_span PARTITIONED upid -); +FROM _anon_swap_sj; DROP VIEW IF EXISTS rss_and_swap_span; CREATE PERFETTO VIEW rss_and_swap_span AS SELECT - ts, dur, upid, - CAST(IFNULL(file_rss_val, 0) AS INT) AS file_rss_val, - CAST(IFNULL(anon_rss_val, 0) AS INT) AS anon_rss_val, - CAST(IFNULL(shmem_rss_val, 0) AS INT) AS shmem_rss_val, - CAST(IFNULL(swap_val, 0) AS INT) AS swap_val, - CAST( - IFNULL(anon_rss_val, 0) - + IFNULL(file_rss_val, 0) - + IFNULL(shmem_rss_val, 0) AS int) AS rss_val, - CAST( - IFNULL(anon_rss_val, 0) - + IFNULL(swap_val, 0) - + IFNULL(file_rss_val, 0) - + IFNULL(shmem_rss_val, 0) AS int) AS rss_and_swap_val -FROM rss_and_swap_join; + ts, + dur, + upid, + file_rss AS file_rss_val, + anon_rss AS anon_rss_val, + shmem_rss AS shmem_rss_val, + swap AS swap_val, + COALESCE(file_rss, 0) + + COALESCE(anon_rss, 0) + + COALESCE(shmem_rss, 0) AS rss_val, + COALESCE(file_rss, 0) + + COALESCE(anon_rss, 0) + + COALESCE(shmem_rss, 0) + + COALESCE(swap, 0) AS rss_and_swap_val +FROM _memory_rss_and_swap_per_process_table; -- If we have dalvik events enabled (for ART trace points) we can construct the java heap timeline. SELECT RUN_METRIC('android/process_counter_span_view.sql', @@ -94,22 +71,22 @@ CREATE PERFETTO VIEW java_heap_span AS SELECT ts, dur, upid, java_heap_kb_val * 1024 AS java_heap_val FROM java_heap_kb_span; +DROP TABLE IF EXISTS java_heap_by_oom_span; +CREATE VIRTUAL TABLE java_heap_by_oom_span +USING SPAN_JOIN(java_heap_span PARTITIONED upid, oom_score_span PARTITIONED upid); + DROP TABLE IF EXISTS anon_rss_by_oom_span; CREATE VIRTUAL TABLE anon_rss_by_oom_span -USING SPAN_JOIN(anon_rss_span PARTITIONED upid, oom_score_span PARTITIONED upid); +USING SPAN_JOIN(_anon_rss PARTITIONED upid, oom_score_span PARTITIONED upid); DROP TABLE IF EXISTS file_rss_by_oom_span; CREATE VIRTUAL TABLE file_rss_by_oom_span -USING SPAN_JOIN(file_rss_span PARTITIONED upid, oom_score_span PARTITIONED upid); +USING SPAN_JOIN(_file_rss PARTITIONED upid, oom_score_span PARTITIONED upid); DROP TABLE IF EXISTS swap_by_oom_span; CREATE VIRTUAL TABLE swap_by_oom_span -USING SPAN_JOIN(swap_span PARTITIONED upid, oom_score_span PARTITIONED upid); +USING SPAN_JOIN(_swap PARTITIONED upid, oom_score_span PARTITIONED upid); DROP TABLE IF EXISTS anon_and_swap_by_oom_span; CREATE VIRTUAL TABLE anon_and_swap_by_oom_span USING SPAN_JOIN(anon_and_swap_span PARTITIONED upid, oom_score_span PARTITIONED upid); - -DROP TABLE IF EXISTS java_heap_by_oom_span; -CREATE VIRTUAL TABLE java_heap_by_oom_span -USING SPAN_JOIN(java_heap_span PARTITIONED upid, oom_score_span PARTITIONED upid); diff --git a/src/trace_processor/metrics/sql/android/process_metadata.sql b/src/trace_processor/metrics/sql/android/process_metadata.sql index 855d27f95a..d7f68a8c79 100644 --- a/src/trace_processor/metrics/sql/android/process_metadata.sql +++ b/src/trace_processor/metrics/sql/android/process_metadata.sql @@ -16,32 +16,16 @@ INCLUDE PERFETTO MODULE android.process_metadata; +-- Alias the process_metadata_table since unfortunately there are places +-- depending on it (which we will clean up) DROP VIEW IF EXISTS process_metadata_table; CREATE PERFETTO VIEW process_metadata_table AS -SELECT android_process_metadata.*, pid FROM android_process_metadata -JOIN process USING(upid); +SELECT * FROM android_process_metadata; -DROP VIEW IF EXISTS uid_package_count; -CREATE PERFETTO VIEW uid_package_count AS -SELECT * FROM _uid_package_count; - -DROP VIEW IF EXISTS process_metadata; -CREATE PERFETTO VIEW process_metadata AS -WITH upid_packages AS ( - SELECT - upid, - RepeatedField(AndroidProcessMetadata_Package( - 'package_name', package_list.package_name, - 'apk_version_code', package_list.version_code, - 'debuggable', package_list.debuggable - )) AS packages_for_uid - FROM process - JOIN package_list ON process.android_appid = package_list.uid - GROUP BY upid -) -SELECT - upid, - NULL_IF_EMPTY(AndroidProcessMetadata( +CREATE OR REPLACE PERFETTO FUNCTION process_metadata_proto(upid INT) +RETURNS PROTO +AS +SELECT NULL_IF_EMPTY(AndroidProcessMetadata( 'name', process_name, 'uid', uid, 'pid', pid, @@ -49,16 +33,22 @@ SELECT 'package_name', package_name, 'apk_version_code', version_code, 'debuggable', debuggable - )), - 'packages_for_uid', packages_for_uid - )) AS metadata -FROM process_metadata_table -LEFT JOIN upid_packages USING (upid); + )) + )) +FROM android_process_metadata +WHERE upid = $upid; + +DROP VIEW IF EXISTS process_metadata; +CREATE PERFETTO VIEW process_metadata AS +SELECT + upid, + process_metadata_proto(upid) AS metadata +FROM android_process_metadata; -- Given a process name, return if it is debuggable. CREATE OR REPLACE PERFETTO FUNCTION is_process_debuggable(process_name STRING) RETURNS BOOL AS SELECT p.debuggable -FROM process_metadata_table p +FROM android_process_metadata p WHERE p.process_name = $process_name LIMIT 1; diff --git a/src/trace_processor/metrics/sql/android/startup/slow_start_reasons.sql b/src/trace_processor/metrics/sql/android/startup/slow_start_reasons.sql index 2a673c0039..21e17b19d7 100644 --- a/src/trace_processor/metrics/sql/android/startup/slow_start_reasons.sql +++ b/src/trace_processor/metrics/sql/android/startup/slow_start_reasons.sql @@ -41,270 +41,530 @@ CREATE OR REPLACE PERFETTO FUNCTION get_ns_to_ms(ns LONG) RETURNS STRING AS SELECT SUBSTRING(CAST(($ns + 0.0) / 1e6 AS STRING), 1, 6); -CREATE OR REPLACE PERFETTO FUNCTION get_longest_chunk(start_ns LONG, dur_ns LONG, tid LONG, name STRING) -RETURNS STRING AS - SELECT " [ longest_chunk:" - || " start_s " || get_ns_to_s($start_ns - TRACE_START()) - || " dur_ms " || get_ns_to_ms($dur_ns) - || " thread_id " || $tid - || " thread_name " || $name - || " ]"; - CREATE OR REPLACE PERFETTO FUNCTION get_main_thread_time_for_launch_in_runnable_state( - startup_id LONG, launches_dur LONG) -RETURNS STRING AS - SELECT - " target" || " 15%" - || " actual " - || get_percent(main_thread_time_for_launch_in_runnable_state($startup_id), $launches_dur) - || "%" - || get_longest_chunk(ts, dur, tid, name) - || " [ extra_info: " - || " launches_dur_ms " || get_ns_to_ms($launches_dur) - || " runnable_dur_ms " - || get_ns_to_ms(main_thread_time_for_launch_in_runnable_state($startup_id)) - || " R_sum_dur_ms " - || get_ns_to_ms(IFNULL(main_thread_time_for_launch_and_state($startup_id, "R"), 0)) - || " R+(Preempted)_sum_dur_ms " - || get_ns_to_ms(IFNULL(main_thread_time_for_launch_and_state($startup_id, "R+"), 0)) - || " ]" - FROM launch_threads_by_thread_state l - JOIN thread USING (utid) - WHERE l.startup_id = $startup_id AND (state GLOB "R" OR state GLOB "R+") AND l.is_main_thread - ORDER BY dur DESC - LIMIT 1; - -CREATE OR REPLACE PERFETTO FUNCTION get_android_sum_dur_on_main_thread_for_startup_and_slice( - startup_id LONG, slice_name STRING, launches_dur LONG) -RETURNS STRING AS - SELECT - " target" || " 20%" - || " actual " - || get_percent(android_sum_dur_on_main_thread_for_startup_and_slice( - $startup_id, $slice_name), $launches_dur) || "%" - || get_longest_chunk(slice_ts, slice_dur, tid, name) - || " [ extra_info: " - || " launches_dur_ms " || get_ns_to_ms($launches_dur) - || " sum_dur_ms " - || get_ns_to_ms(android_sum_dur_on_main_thread_for_startup_and_slice($startup_id, $slice_name)) - || " ]" - FROM android_thread_slices_for_all_startups slices + startup_id LONG, num_threads INT) +RETURNS PROTO AS + SELECT RepeatedField(AndroidStartupMetric_TraceThreadSection( + 'start_timestamp', ts, 'end_timestamp', ts + dur, + 'thread_utid', utid, 'thread_name', thread_name)) + FROM ( + SELECT ts, dur, utid, thread_name + FROM launch_threads_by_thread_state l JOIN thread USING (utid) - WHERE startup_id = $startup_id AND slice_name GLOB $slice_name AND slices.is_main_thread - ORDER BY slice_dur DESC - LIMIT 1; + WHERE l.startup_id = $startup_id AND (state GLOB "R" OR state GLOB "R+") AND l.is_main_thread + ORDER BY dur DESC + LIMIT $num_threads); -CREATE OR REPLACE PERFETTO FUNCTION get_android_sum_dur_for_startup_and_slice( - startup_id LONG, slice_name STRING, target_ms LONG) -RETURNS STRING AS - SELECT - " target " || $target_ms || "ms" - || " actual " - || get_ns_to_ms(android_sum_dur_for_startup_and_slice($startup_id, $slice_name)) || "ms" - || get_longest_chunk(slice_ts, slice_dur, tid, name) - FROM android_thread_slices_for_all_startups +CREATE OR REPLACE PERFETTO FUNCTION get_main_thread_time_for_launch_and_state( + startup_id LONG, state STRING, num_threads INT) +RETURNS PROTO AS + SELECT RepeatedField(AndroidStartupMetric_TraceThreadSection( + 'start_timestamp', ts, 'end_timestamp', ts + dur, + 'thread_utid', utid, 'thread_name', thread_name)) + FROM ( + SELECT ts, dur, utid, thread_name + FROM launch_threads_by_thread_state l JOIN thread USING (utid) - WHERE startup_id = $startup_id AND slice_name GLOB $slice_name - ORDER BY slice_dur DESC - LIMIT 1; + WHERE l.startup_id = $startup_id AND state GLOB $state AND l.is_main_thread + ORDER BY dur DESC + LIMIT $num_threads); -CREATE OR REPLACE PERFETTO FUNCTION get_potential_cpu_contention_with_another_process(startup_id LONG) -RETURNS STRING AS - SELECT - " target" || " 100ms" - || " actual " - || get_ns_to_ms(main_thread_time_for_launch_in_runnable_state($startup_id)) || "ms" - || " most_active_process_for_launch " || most_active_process_for_launch($startup_id) - || get_longest_chunk(ts, dur, tid, name) - || " [ extra_info: " - || " runnable_dur_ms " - || get_ns_to_ms(main_thread_time_for_launch_in_runnable_state($startup_id)) - || " R_sum_dur_ms " - || get_ns_to_ms(IFNULL(main_thread_time_for_launch_and_state($startup_id, "R"), 0)) - || " R+(Preempted)_sum_dur " - || IFNULL(main_thread_time_for_launch_and_state($startup_id, "R+"), 0) - || " ]" - FROM launch_threads_by_thread_state l - JOIN thread USING (utid) - WHERE l.startup_id = $startup_id AND (state GLOB "R" OR state GLOB "R+") AND l.is_main_thread - ORDER BY dur DESC - LIMIT 1; - -CREATE OR REPLACE PERFETTO FUNCTION get_jit_activity(startup_id LONG) -RETURNS STRING AS - SELECT - " target" || " 100ms" - || " actual " - || get_ns_to_ms(thread_time_for_launch_state_and_thread( - $startup_id, 'Running', 'Jit thread pool')) - || "ms" - || get_longest_chunk(ts, dur, tid, name) - FROM launch_threads_by_thread_state l - JOIN thread USING (utid) - WHERE l.startup_id = $startup_id AND state GLOB 'Running' AND thread_name = 'Jit thread pool' - ORDER BY dur DESC - LIMIT 1; +CREATE OR REPLACE PERFETTO FUNCTION get_main_thread_time_for_launch_state_and_io_wait( + startup_id INT, state STRING, io_wait BOOL, num_threads INT) +RETURNS PROTO AS + SELECT RepeatedField(AndroidStartupMetric_TraceThreadSection( + 'start_timestamp', ts, 'end_timestamp', ts + dur, + 'thread_utid', utid, 'thread_name', thread_name)) + FROM ( + SELECT ts, dur, utid, thread_name + FROM launch_threads_by_thread_state l + JOIN thread USING (utid) + WHERE l.startup_id = $startup_id AND state GLOB $state + AND l.is_main_thread AND l.io_wait = $io_wait + ORDER BY dur DESC + LIMIT $num_threads); -CREATE OR REPLACE PERFETTO FUNCTION get_main_thread_binder_transactions_blocked( - startup_id LONG, threshold DOUBLE) -RETURNS STRING AS - SELECT - " per_instance_target" || " 20ms" - || " per_instance_actual " || get_ns_to_ms(request.slice_dur) || "ms" - || get_longest_chunk(request.slice_ts, request.slice_dur, tid, request.thread_name) - || " [ extra_info: " - || " reply.dur_ms " || get_ns_to_ms(reply.dur) - || " ]" +CREATE OR REPLACE PERFETTO FUNCTION get_thread_time_for_launch_state_and_thread( + startup_id INT, state STRING, thread_name STRING, num_threads INT) +RETURNS PROTO AS + SELECT RepeatedField(AndroidStartupMetric_TraceThreadSection( + 'start_timestamp', ts, 'end_timestamp', ts + dur, + 'thread_utid', utid, 'thread_name', thread_name)) FROM ( - SELECT slice_id as id, slice_dur, thread_name, process.name as process, - s.arg_set_id, is_main_thread, - slice_ts, s.utid - FROM android_thread_slices_for_all_startups s - JOIN process ON ( - EXTRACT_ARG(s.arg_set_id, "destination process") = process.pid - ) - WHERE startup_id = $startup_id AND slice_name GLOB "binder transaction" - AND slice_dur > $threshold - ) request - JOIN following_flow(request.id) arrow - JOIN slice reply ON reply.id = arrow.slice_in - JOIN thread USING (utid) - WHERE reply.dur > $threshold AND request.is_main_thread - ORDER BY request.slice_dur DESC - LIMIT 1; + SELECT ts, dur, utid, thread_name + FROM launch_threads_by_thread_state l + JOIN thread USING (utid) + WHERE l.startup_id = $startup_id AND state GLOB $state AND thread_name = $thread_name + ORDER BY dur DESC + LIMIT $num_threads); CREATE OR REPLACE PERFETTO FUNCTION get_missing_baseline_profile_for_launch( startup_id LONG, pkg_name STRING) -RETURNS STRING AS - SELECT - " target " || "FALSE" - || " actual " || "TRUE" - || get_longest_chunk(slice_ts, slice_dur, -1, thread_name) - || " [ extra_info: " - || " slice_name " || slice_name - || " ]" - FROM ( - SELECT * - FROM ANDROID_SLICES_FOR_STARTUP_AND_SLICE_NAME( - $startup_id, - "location=* status=* filter=* reason=*" - ) - ORDER BY slice_name - ) +RETURNS PROTO AS + SELECT RepeatedField(AndroidStartupMetric_TraceSliceSection( + 'start_timestamp', slice_ts, + 'end_timestamp', slice_ts + slice_dur, + 'slice_id', slice_id, + 'slice_name', slice_name)) + FROM ( + SELECT slice_ts, slice_dur, slice_id, slice_name + FROM ANDROID_SLICES_FOR_STARTUP_AND_SLICE_NAME($startup_id, + "location=* status=* filter=* reason=*") WHERE -- when location is the package odex file and the reason is "install" or "install-dm", -- if the compilation filter is not "speed-profile", baseline/cloud profile is missing. SUBSTR(STR_SPLIT(slice_name, " status=", 0), LENGTH("location=") + 1) GLOB ("*" || $pkg_name || "*odex") AND (STR_SPLIT(slice_name, " reason=", 1) = "install" - OR STR_SPLIT(slice_name, " reason=", 1) = "install-dm") + OR STR_SPLIT(slice_name, " reason=", 1) = "install-dm") + ORDER BY slice_dur DESC + LIMIT 1); + +CREATE OR REPLACE PERFETTO FUNCTION get_run_from_apk(startup_id LONG) +RETURNS PROTO AS + SELECT RepeatedField(AndroidStartupMetric_TraceSliceSection( + 'start_timestamp', slice_ts, + 'end_timestamp', slice_ts + slice_dur, + 'slice_id', slice_id, + 'slice_name', slice_name)) + FROM ( + SELECT slice_ts, slice_dur, slice_id, slice_name + FROM android_thread_slices_for_all_startups + WHERE + startup_id = $startup_id AND is_main_thread AND + slice_name GLOB "location=* status=* filter=* reason=*" AND + STR_SPLIT(STR_SPLIT(slice_name, " filter=", 1), " reason=", 0) + GLOB ("*" || "run-from-apk" || "*") + ORDER BY slice_dur DESC + LIMIT 1); + +CREATE OR REPLACE PERFETTO FUNCTION get_unlock_running_during_launch_slice(startup_id LONG) +RETURNS PROTO AS + SELECT RepeatedField(AndroidStartupMetric_TraceSliceSection( + 'start_timestamp', slice_ts, + 'end_timestamp', slice_ts + slice_dur, + 'slice_id', slice_id, + 'slice_name', slice_name)) + FROM ( + SELECT slice.ts as slice_ts, slice.dur as slice_dur, + slice.id as slice_id, slice.name as slice_name + FROM slice, android_startups launches + JOIN thread_track ON slice.track_id = thread_track.id + JOIN thread USING(utid) + JOIN process USING(upid) + WHERE launches.startup_id = $startup_id + AND slice.name = "KeyguardUpdateMonitor#onAuthenticationSucceeded" + AND process.name = "com.android.systemui" + AND slice.ts >= launches.ts + AND (slice.ts + slice.dur) <= launches.ts_end + LIMIT 1); + +CREATE OR REPLACE PERFETTO FUNCTION get_gc_activity(startup_id LONG, num_slices INT) +RETURNS PROTO AS + SELECT RepeatedField(AndroidStartupMetric_TraceSliceSection( + 'start_timestamp', slice_ts, + 'end_timestamp', slice_ts + slice_dur, + 'slice_id', slice_id, + 'slice_name', slice_name)) + FROM ( + SELECT slice_ts, slice_dur, slice_id, slice_name + FROM android_thread_slices_for_all_startups slice + WHERE + slice.startup_id = $startup_id AND + ( + slice_name GLOB "*semispace GC" OR + slice_name GLOB "*mark sweep GC" OR + slice_name GLOB "*concurrent copying GC" + ) ORDER BY slice_dur DESC - LIMIT 1; + LIMIT $num_slices); + +CREATE OR REPLACE PERFETTO FUNCTION get_dur_on_main_thread_for_startup_and_slice( + startup_id LONG, slice_name STRING, num_slices INT) +RETURNS PROTO AS + SELECT RepeatedField(AndroidStartupMetric_TraceSliceSection( + 'start_timestamp', slice_ts, + 'end_timestamp', slice_ts + slice_dur, + 'slice_id', slice_id, + 'slice_name', slice_name)) + FROM ( + SELECT slice_ts, slice_dur, slice_id, slice_name + FROM android_thread_slices_for_all_startups l + WHERE startup_id = $startup_id + AND slice_name GLOB $slice_name + ORDER BY slice_dur DESC + LIMIT $num_slices); + +CREATE OR REPLACE PERFETTO FUNCTION get_main_thread_binder_transactions_blocked( + startup_id LONG, threshold DOUBLE, num_slices INT) +RETURNS PROTO AS + SELECT RepeatedField(AndroidStartupMetric_TraceSliceSection( + 'start_timestamp', slice_ts, 'end_timestamp', slice_ts + slice_dur, + 'slice_id', slice_id, 'slice_name', slice_name)) + FROM ( + SELECT request.slice_ts as slice_ts, request.slice_dur as slice_dur, + request.id as slice_id, request.slice_name as slice_name + FROM ( + SELECT slice_id as id, slice_dur, thread_name, process.name as process, + s.arg_set_id, is_main_thread, + slice_ts, s.utid, slice_name + FROM android_thread_slices_for_all_startups s + JOIN process ON ( + EXTRACT_ARG(s.arg_set_id, "destination process") = process.pid + ) + WHERE startup_id = $startup_id AND slice_name GLOB "binder transaction" + AND slice_dur > $threshold + ) request + JOIN following_flow(request.id) arrow + JOIN slice reply ON reply.id = arrow.slice_in + JOIN thread USING (utid) + WHERE reply.dur > $threshold AND request.is_main_thread + ORDER BY request.slice_dur DESC + LIMIT $num_slices); + +CREATE OR REPLACE PERFETTO FUNCTION get_slices_concurrent_to_launch( + startup_id INT, slice_glob STRING, num_slices INT) +RETURNS PROTO AS + SELECT RepeatedField(AndroidStartupMetric_TraceSliceSection( + 'start_timestamp', ts, 'end_timestamp', ts + dur, + 'slice_id', id, 'slice_name', name)) + FROM ( + SELECT s.ts as ts, dur, id, name FROM slice s + JOIN ( + SELECT ts, ts_end + FROM android_startups + WHERE startup_id = $startup_id + ) launch + WHERE + s.name GLOB $slice_glob AND + s.ts BETWEEN launch.ts AND launch.ts_end + ORDER BY dur DESC LIMIT $num_slices); +CREATE OR REPLACE PERFETTO FUNCTION get_slices_for_startup_and_slice_name( + startup_id INT, slice_name STRING, num_slices INT) +RETURNS PROTO AS + SELECT RepeatedField(AndroidStartupMetric_TraceSliceSection( + 'start_timestamp', slice_ts, 'end_timestamp', slice_ts + slice_dur, + 'slice_id', slice_id, 'slice_name', slice_name)) + FROM ( + SELECT slice_ts, slice_dur, slice_id, slice_name + FROM android_thread_slices_for_all_startups + WHERE startup_id = $startup_id AND slice_name GLOB $slice_name + ORDER BY slice_dur DESC + LIMIT $num_slices); -CREATE OR REPLACE PERFETTO FUNCTION get_slow_start_reason_detailed(startup_id LONG) +CREATE OR REPLACE PERFETTO FUNCTION get_process_running_concurrent_to_launch( + startup_id INT, process_glob STRING) +RETURNS STRING AS + SELECT process.name + FROM sched + JOIN thread USING (utid) + JOIN process USING (upid) + JOIN ( + SELECT ts, ts_end + FROM android_startups + WHERE startup_id = $startup_id + ) launch + WHERE + process.name GLOB $process_glob AND + sched.ts BETWEEN launch.ts AND launch.ts_end + ORDER BY (launch.ts_end - sched.ts) DESC + LIMIT 1; + +CREATE OR REPLACE PERFETTO FUNCTION get_slow_start_reason_with_details(startup_id LONG) RETURNS PROTO AS - SELECT RepeatedField(AndroidStartupMetric_SlowStartReasonDetailed( + SELECT RepeatedField(AndroidStartupMetric_SlowStartReason( + 'reason_id', reason_id, 'reason', slow_cause, - 'details', details)) + 'severity', severity, + 'launch_dur', launch_dur, + 'expected_value', expected_val, + 'actual_value', actual_val, + 'trace_slice_sections', trace_slices, + 'trace_thread_sections', trace_threads, + 'additional_info', extra)) FROM ( - SELECT 'No baseline or cloud profiles' AS slow_cause, - get_missing_baseline_profile_for_launch(launch.startup_id, launch.package) as details + SELECT 'No baseline or cloud profiles' as slow_cause, + launch.dur as launch_dur, + 'NO_BASELINE_OR_CLOUD_PROFILES' as reason_id, + 'ERROR' as severity, + AndroidStartupMetric_ThresholdValue( + 'value', FALSE, + 'unit', 'TRUE_OR_FALSE') as expected_val, + AndroidStartupMetric_ActualValue( + 'value', TRUE) as actual_val, + get_missing_baseline_profile_for_launch(launch.startup_id, launch.package) + as trace_slices, + NULL as trace_threads, + NULL as extra FROM android_startups launch WHERE launch.startup_id = $startup_id AND missing_baseline_profile_for_launch(launch.startup_id, launch.package) UNION ALL - SELECT 'Optimized artifacts missing, run from apk' as slow_cause, NULL as details + SELECT 'Optimized artifacts missing, run from apk' as slow_cause, + launch.dur as launch_dur, + 'RUN_FROM_APK' as reason_id, + 'ERROR' as severity, + AndroidStartupMetric_ThresholdValue( + 'value', FALSE, + 'unit', 'TRUE_OR_FALSE') as expected_val, + AndroidStartupMetric_ActualValue( + 'value', TRUE) as actual_val, + get_run_from_apk(launch.startup_id) as trace_slices, + NULL as trace_threads, + NULL as extra FROM android_startups launch WHERE launch.startup_id = $startup_id - AND run_from_apk_for_launch(launch.startup_id) + AND run_from_apk_for_launch(launch.startup_id) UNION ALL - SELECT 'Unlock running during launch' as slow_cause, NULL as details + SELECT 'Unlock running during launch' as slow_cause, + launch.dur as launch_dur, + 'UNLOCK_RUNNING' as reason_id, + 'ERROR' as severity, + AndroidStartupMetric_ThresholdValue( + 'value', FALSE, + 'unit', 'TRUE_OR_FALSE') as expected_val, + AndroidStartupMetric_ActualValue( + 'value', TRUE) as actual_val, + get_unlock_running_during_launch_slice(launch.startup_id) as trace_slices, + NULL as trace_threads, + NULL as extra FROM android_startups launch WHERE launch.startup_id = $startup_id AND is_unlock_running_during_launch(launch.startup_id) UNION ALL - SELECT 'App in debuggable mode' as slow_cause, NULL as details - FROM android_startups launch + SELECT 'App in debuggable mode' as slow_cause, + launch.dur as launch_dur, + 'APP_IN_DEBUGGABLE_MODE' as reason_id, + 'ERROR' as severity, + AndroidStartupMetric_ThresholdValue( + 'value', FALSE, + 'unit', 'TRUE_OR_FALSE') as expected_val, + AndroidStartupMetric_ActualValue( + 'value', TRUE) as actual_val, + NULL as trace_slices, + NULL as trace_threads, + NULL as extra + FROM android_startups launch WHERE launch.startup_id = $startup_id AND is_process_debuggable(launch.package) UNION ALL - SELECT 'GC Activity' as slow_cause, NULL as details + SELECT 'GC Activity' as slow_cause, + launch.dur as launch_dur, + 'GC_ACTIVITY' as reason_id, + 'ERROR' as severity, + AndroidStartupMetric_ThresholdValue( + 'value', FALSE, + 'unit', 'TRUE_OR_FALSE') as expected_val, + AndroidStartupMetric_ActualValue( + 'value', TRUE) as actual_val, + get_gc_activity(launch.startup_id, 1) as trace_slices, + NULL as trace_threads, + NULL as extra FROM android_startups launch WHERE launch.startup_id = $startup_id AND total_gc_time_by_launch(launch.startup_id) > 0 UNION ALL - SELECT 'dex2oat running during launch' AS slow_cause, NULL as details + SELECT 'dex2oat running during launch' AS slow_cause, + launch.dur as launch_dur, + 'DEX2OAT_RUNNING' as reason_id, + 'WARNING' as severity, + AndroidStartupMetric_ThresholdValue( + 'value', FALSE, + 'unit', 'TRUE_OR_FALSE') as expected_val, + AndroidStartupMetric_ActualValue( + 'value', TRUE) as actual_val, + NULL as trace_slices, + NULL as trace_threads, + 'Process: ' || get_process_running_concurrent_to_launch(launch.startup_id, '*dex2oat64') + as extra FROM android_startups launch WHERE launch.startup_id = $startup_id AND dur_of_process_running_concurrent_to_launch(launch.startup_id, '*dex2oat64') > 0 UNION ALL - SELECT 'installd running during launch' AS slow_cause, NULL as details + SELECT 'installd running during launch' AS slow_cause, + launch.dur as launch_dur, + 'INSTALLD_RUNNING' as reason_id, + 'ERROR' as severity, + AndroidStartupMetric_ThresholdValue( + 'value', FALSE, + 'unit', 'TRUE_OR_FALSE') as expected_val, + AndroidStartupMetric_ActualValue( + 'value', TRUE) as actual_val, + NULL as trace_slices, + NULL as trace_threads, + 'Process: ' || get_process_running_concurrent_to_launch(launch.startup_id, '*installd') + as extra FROM android_startups launch WHERE launch.startup_id = $startup_id AND dur_of_process_running_concurrent_to_launch(launch.startup_id, '*installd') > 0 UNION ALL SELECT 'Main Thread - Time spent in Runnable state' as slow_cause, - get_main_thread_time_for_launch_in_runnable_state( - launch.startup_id, launch.dur) as details + launch.dur as launch_dur, + 'MAIN_THREAD_TIME_SPENT_IN_RUNNABLE' as reason_id, + 'WARNING' as severity, + AndroidStartupMetric_ThresholdValue( + 'value', 15, + 'unit', 'PERCENTAGE', + 'higher_expected', FALSE) as expected_val, + AndroidStartupMetric_ActualValue( + 'value', + main_thread_time_for_launch_in_runnable_state(launch.startup_id) * 100 / launch.dur, + 'dur', main_thread_time_for_launch_in_runnable_state(launch.startup_id)) as actual_val, + NULL as trace_slices, + get_main_thread_time_for_launch_in_runnable_state(launch.startup_id, 3) as trace_threads, + NULL as extra FROM android_startups launch WHERE launch.startup_id = $startup_id AND main_thread_time_for_launch_in_runnable_state(launch.startup_id) > launch.dur * 0.15 UNION ALL - SELECT 'Main Thread - Time spent in interruptible sleep state' - AS slow_cause, NULL as details + SELECT 'Main Thread - Time spent in interruptible sleep state' as slow_cause, + launch.dur as launch_dur, + 'MAIN_THREAD_TIME_SPENT_IN_INTERRUPTIBLE_SLEEP' as reason_id, + 'WARNING' as severity, + AndroidStartupMetric_ThresholdValue( + 'value', 2900000000, + 'unit', 'NS', + 'higher_expected', FALSE) as expected_val, + AndroidStartupMetric_ActualValue( + 'value', main_thread_time_for_launch_and_state(launch.startup_id, 'S')) as actual_val, + NULL as trace_slices, + get_main_thread_time_for_launch_and_state(launch.startup_id, 'S', 3) as trace_threads, + NULL as extra FROM android_startups launch WHERE launch.startup_id = $startup_id AND main_thread_time_for_launch_and_state(launch.startup_id, 'S') > 2900e6 UNION ALL - SELECT 'Main Thread - Time spent in Blocking I/O' as slow_cause, NULL as details + SELECT 'Main Thread - Time spent in Blocking I/O' as slow_cause, + launch.dur as launch_dur, + 'MAIN_THREAD_TIME_SPENT_IN_BLOCKING_IO' as reason_id, + 'WARNING' as severity, + AndroidStartupMetric_ThresholdValue( + 'value', 450000000, + 'unit', 'NS', + 'higher_expected', FALSE) as expected_val, + AndroidStartupMetric_ActualValue( + 'value', main_thread_time_for_launch_state_and_io_wait( + launch.startup_id, 'D*', TRUE)) as actual_val, + NULL as trace_slices, + get_main_thread_time_for_launch_state_and_io_wait( + launch.startup_id, 'D*', TRUE, 3) as trace_threads, + NULL as extra FROM android_startups launch WHERE launch.startup_id = $startup_id AND main_thread_time_for_launch_state_and_io_wait(launch.startup_id, 'D*', TRUE) > 450e6 UNION ALL SELECT 'Main Thread - Time spent in OpenDexFilesFromOat*' as slow_cause, - get_android_sum_dur_on_main_thread_for_startup_and_slice( - launch.startup_id, 'OpenDexFilesFromOat*', launch.dur) as details + launch.dur as launch_dur, + 'MAIN_THREAD_TIME_SPENT_IN_OPEN_DEX_FILES_FROM_OAT' as reason_id, + 'WARNING' as severity, + AndroidStartupMetric_ThresholdValue( + 'value', 20, + 'unit', 'PERCENTAGE', + 'higher_expected', FALSE) as expected_val, + AndroidStartupMetric_ActualValue( + 'value', android_sum_dur_on_main_thread_for_startup_and_slice( + launch.startup_id, 'OpenDexFilesFromOat*') * 100 / launch.dur, + 'dur', android_sum_dur_on_main_thread_for_startup_and_slice( + launch.startup_id, 'OpenDexFilesFromOat*')) as actual_val, + get_dur_on_main_thread_for_startup_and_slice(launch.startup_id, 'OpenDexFilesFromOat*', 3) + as trace_slices, + NULL as trace_threads, + NULL as extra FROM android_startups launch WHERE launch.startup_id = $startup_id AND android_sum_dur_on_main_thread_for_startup_and_slice( launch.startup_id, 'OpenDexFilesFromOat*') > launch.dur * 0.2 UNION ALL - SELECT 'Time spent in bindApplication' - AS slow_cause, NULL as details + SELECT 'Time spent in bindApplication' as slow_cause, + launch.dur as launch_dur, + 'TIME_SPENT_IN_BIND_APPLICATION' as reason_id, + 'WARNING' as severity, + AndroidStartupMetric_ThresholdValue( + 'value', 1250000000, + 'unit', 'NS', + 'higher_expected', FALSE) as expected_val, + AndroidStartupMetric_ActualValue( + 'value', android_sum_dur_for_startup_and_slice( + launch.startup_id, 'bindApplication')) as actual_val, + get_dur_on_main_thread_for_startup_and_slice(launch.startup_id, 'bindApplication', 3) + as trace_slices, + NULL as trace_threads, + NULL as extra FROM android_startups launch WHERE launch.startup_id = $startup_id AND android_sum_dur_for_startup_and_slice(launch.startup_id, 'bindApplication') > 1250e6 UNION ALL - SELECT 'Time spent in view inflation' as slow_cause, NULL as details + SELECT 'Time spent in view inflation' as slow_cause, + launch.dur as launch_dur, + 'TIME_SPENT_IN_VIEW_INFLATION' as reason_id, + 'WARNING' as severity, + AndroidStartupMetric_ThresholdValue( + 'value', 450000000, + 'unit', 'NS', + 'higher_expected', FALSE) as expected_val, + AndroidStartupMetric_ActualValue( + 'value', android_sum_dur_for_startup_and_slice( + launch.startup_id, 'inflate')) as actual_val, + get_dur_on_main_thread_for_startup_and_slice(launch.startup_id, 'inflate', 3) + as trace_slices, + NULL as trace_threads, + NULL as extra FROM android_startups launch WHERE launch.startup_id = $startup_id AND android_sum_dur_for_startup_and_slice(launch.startup_id, 'inflate') > 450e6 UNION ALL SELECT 'Time spent in ResourcesManager#getResources' as slow_cause, - get_android_sum_dur_for_startup_and_slice( - launch.startup_id, 'ResourcesManager#getResources', 130) as details + launch.dur as launch_dur, + 'TIME_SPENT_IN_RESOURCES_MANAGER_GET_RESOURCES' as reason_id, + 'WARNING' as severity, + AndroidStartupMetric_ThresholdValue( + 'value', 130000000, + 'unit', 'NS', + 'higher_expected', FALSE) as expected_val, + AndroidStartupMetric_ActualValue( + 'value', android_sum_dur_for_startup_and_slice( + launch.startup_id, 'ResourcesManager#getResources')) as actual_val, + get_dur_on_main_thread_for_startup_and_slice( + launch.startup_id, 'ResourcesManager#getResources', 3) as trace_slices, + NULL as trace_threads, + NULL as extra FROM android_startups launch WHERE launch.startup_id = $startup_id AND android_sum_dur_for_startup_and_slice( launch.startup_id, 'ResourcesManager#getResources') > 130e6 UNION ALL - SELECT 'Time spent verifying classes' - AS slow_cause, NULL as details + SELECT 'Time spent verifying classes' as slow_cause, + launch.dur as launch_dur, + 'TIME_SPENT_VERIFYING_CLASSES' as reason_id, + 'WARNING' as severity, + AndroidStartupMetric_ThresholdValue( + 'value', 15, + 'unit', 'PERCENTAGE', + 'higher_expected', FALSE) as expected_val, + AndroidStartupMetric_ActualValue( + 'value', android_sum_dur_for_startup_and_slice( + launch.startup_id, 'VerifyClass*') * 100 / launch.dur, + 'dur', android_sum_dur_for_startup_and_slice( + launch.startup_id, 'VerifyClass*')) as actual_val, + get_dur_on_main_thread_for_startup_and_slice(launch.startup_id, 'VerifyClass*', 3) + as trace_slices, + NULL as trace_threads, + NULL as extra FROM android_startups launch WHERE launch.startup_id = $startup_id AND android_sum_dur_for_startup_and_slice(launch.startup_id, 'VerifyClass*') @@ -312,7 +572,19 @@ RETURNS PROTO AS UNION ALL SELECT 'Potential CPU contention with another process' AS slow_cause, - get_potential_cpu_contention_with_another_process(launch.startup_id) as details + launch.dur as launch_dur, + 'POTENTIAL_CPU_CONTENTION_WITH_ANOTHER_PROCESS' as reason_id, + 'WARNING' as severity, + AndroidStartupMetric_ThresholdValue( + 'value', 100000000, + 'unit', 'NS', + 'higher_expected', FALSE) as expected_val, + AndroidStartupMetric_ActualValue( + 'value', + main_thread_time_for_launch_in_runnable_state(launch.startup_id)) as actual_val, + NULL as trace_slices, + get_main_thread_time_for_launch_in_runnable_state(launch.startup_id, 3) as trace_threads, + NULL as extra FROM android_startups launch WHERE launch.startup_id = $startup_id AND main_thread_time_for_launch_in_runnable_state(launch.startup_id) > 100e6 AND @@ -320,7 +592,20 @@ RETURNS PROTO AS UNION ALL SELECT 'JIT Activity' as slow_cause, - get_jit_activity(launch.startup_id) as details + launch.dur as launch_dur, + 'JIT_ACTIVITY' as reason_id, + 'WARNING' as severity, + AndroidStartupMetric_ThresholdValue( + 'value', 100000000, + 'unit', 'NS', + 'higher_expected', FALSE) as expected_val, + AndroidStartupMetric_ActualValue( + 'value', thread_time_for_launch_state_and_thread( + launch.startup_id, 'Running', 'Jit thread pool')) as actual_val, + NULL as trace_slices, + get_thread_time_for_launch_state_and_thread( + launch.startup_id, 'Running', 'Jit thread pool', 3) as trace_threads, + NULL as extra FROM android_startups launch WHERE launch.startup_id = $startup_id AND thread_time_for_launch_state_and_thread( @@ -330,8 +615,23 @@ RETURNS PROTO AS ) > 100e6 UNION ALL - SELECT 'Main Thread - Lock contention' - AS slow_cause, NULL as details + SELECT 'Main Thread - Lock contention' as slow_cause, + launch.dur as launch_dur, + 'MAIN_THREAD_LOCK_CONTENTION' as reason_id, + 'WARNING' as severity, + AndroidStartupMetric_ThresholdValue( + 'value', 20, + 'unit', 'PERCENTAGE', + 'higher_expected', FALSE) as expected_val, + AndroidStartupMetric_ActualValue( + 'value', android_sum_dur_on_main_thread_for_startup_and_slice( + launch.startup_id, 'Lock contention on*') * 100 / launch.dur, + 'dur', android_sum_dur_on_main_thread_for_startup_and_slice( + launch.startup_id, 'Lock contention on*')) as actual_val, + get_dur_on_main_thread_for_startup_and_slice(launch.startup_id, 'lock contention on*', 3) + as trace_slices, + NULL as trace_threads, + NULL as extra FROM android_startups launch WHERE launch.startup_id = $startup_id AND android_sum_dur_on_main_thread_for_startup_and_slice( @@ -340,61 +640,140 @@ RETURNS PROTO AS ) > launch.dur * 0.2 UNION ALL - SELECT 'Main Thread - Monitor contention' - AS slow_cause, NULL as details + SELECT 'Main Thread - Monitor contention' as slow_cause, + launch.dur as launch_dur, + 'MAIN_THREAD_MONITOR_CONTENTION' as reason_id, + 'WARNING' as severity, + AndroidStartupMetric_ThresholdValue( + 'value', 15, + 'unit', 'PERCENTAGE', + 'higher_expected', FALSE) as expected_val, + AndroidStartupMetric_ActualValue( + 'value', android_sum_dur_on_main_thread_for_startup_and_slice( + launch.startup_id, 'Lock contention on a monitor*') * 100 / launch.dur, + 'dur', android_sum_dur_on_main_thread_for_startup_and_slice( + launch.startup_id, 'Lock contention on a monitor*')) as actual_val, + get_dur_on_main_thread_for_startup_and_slice( + launch.startup_id, 'lock contention on a monitor*', 3) as trace_slices, + NULL as trace_threads, + NULL as extra FROM android_startups launch WHERE launch.startup_id = $startup_id AND android_sum_dur_on_main_thread_for_startup_and_slice( - launch.startup_id, - 'Lock contention on a monitor*' - ) > launch.dur * 0.15 + launch.startup_id, + 'Lock contention on a monitor*' + ) > launch.dur * 0.15 UNION ALL - SELECT 'JIT compiled methods' as slow_cause, NULL as details + SELECT 'JIT compiled methods' as slow_cause, + launch.dur as launch_dur, + 'JIT_COMPILED_METHODS' as reason_id, + 'WARNING' as severity, + AndroidStartupMetric_ThresholdValue( + 'value', 65, + 'unit', 'COUNT', + 'higher_expected', FALSE) as expected_val, + AndroidStartupMetric_ActualValue( + 'value', (SELECT COUNT(1) + FROM ANDROID_SLICES_FOR_STARTUP_AND_SLICE_NAME(launch.startup_id, 'JIT compiling*') + WHERE thread_name = 'Jit thread pool')) as actual_val, + get_slices_for_startup_and_slice_name(launch.startup_id, 'JIT compiling*', 3) + as trace_slices, + NULL as traced_threads, + NULL as extra FROM android_startups launch WHERE launch.startup_id = $startup_id AND ( SELECT COUNT(1) FROM ANDROID_SLICES_FOR_STARTUP_AND_SLICE_NAME(launch.startup_id, 'JIT compiling*') - WHERE thread_name = 'Jit thread pool' - ) > 65 + WHERE thread_name = 'Jit thread pool') > 65 UNION ALL - SELECT 'Broadcast dispatched count' as slow_cause, NULL as details + SELECT 'Broadcast dispatched count' as slow_cause, + launch.dur as launch_dur, + 'BROADCAST_DISPATCHED_COUNT' as reason_id, + 'WARNING' as severity, + AndroidStartupMetric_ThresholdValue( + 'value', 15, + 'unit', 'COUNT', + 'higher_expected', FALSE) as expected_val, + AndroidStartupMetric_ActualValue( + 'value', count_slices_concurrent_to_launch(launch.startup_id, + 'Broadcast dispatched*')) as actual_val, + get_slices_concurrent_to_launch(launch.startup_id, 'Broadcast dispatched*', 3) + as trace_slices, + NULL as trace_threads, + NULL as extra FROM android_startups launch WHERE launch.startup_id = $startup_id AND count_slices_concurrent_to_launch( launch.startup_id, - 'Broadcast dispatched*' - ) > 15 + 'Broadcast dispatched*') > 15 UNION ALL - SELECT 'Broadcast received count' as slow_cause, NULL as details + SELECT 'Broadcast received count' as slow_cause, + launch.dur as launch_dur, + 'BROADCAST_RECEIVED_COUNT' as reason_id, + 'WARNING' as severity, + AndroidStartupMetric_ThresholdValue( + 'value', 50, + 'unit', 'COUNT', + 'higher_expected', FALSE) as expected_val, + AndroidStartupMetric_ActualValue( + 'value', count_slices_concurrent_to_launch(launch.startup_id, + 'broadcastReceiveReg*')) as actual_val, + get_slices_concurrent_to_launch(launch.startup_id, 'broadcastReceiveReg*', 3) + as trace_slices, + NULL as trace_threads, + NULL as extra FROM android_startups launch WHERE launch.startup_id = $startup_id AND count_slices_concurrent_to_launch( - launch.startup_id, - 'broadcastReceiveReg*' - ) > 50 + launch.startup_id, + 'broadcastReceiveReg*') > 50 UNION ALL - SELECT 'Startup running concurrent to launch' as slow_cause, NULL as details + SELECT 'Startup running concurrent to launch' as slow_cause, + launch.dur as launch_dur, + 'STARTUP_RUNNING_CONCURRENT' as reason_id, + 'ERROR' as severity, + AndroidStartupMetric_ThresholdValue( + 'value', FALSE, + 'unit', 'TRUE_OR_FALSE') as expected_val, + AndroidStartupMetric_ActualValue( + 'value', TRUE) as actual_val, + NULL as trace_slices, + NULL as trace_threads, + 'Package: ' || ( + SELECT package + FROM android_startups l + WHERE l.startup_id != launch.startup_id + AND _is_spans_overlapping(l.ts, l.ts_end, launch.ts, launch.ts_end) + LIMIT 1) as extra FROM android_startups launch WHERE launch.startup_id = $startup_id AND EXISTS( SELECT package FROM android_startups l WHERE l.startup_id != launch.startup_id - AND _is_spans_overlapping(l.ts, l.ts_end, launch.ts, launch.ts_end) - ) + AND _is_spans_overlapping(l.ts, l.ts_end, launch.ts, launch.ts_end)) UNION ALL SELECT 'Main Thread - Binder transactions blocked' as slow_cause, - get_main_thread_binder_transactions_blocked(launch.startup_id, 2e7) as details + launch.dur as launch_dur, + 'MAIN_THREAD_BINDER_TRANSCATIONS_BLOCKED' as reason_id, + 'WARNING' as severity, + AndroidStartupMetric_ThresholdValue( + 'value', FALSE, + 'unit', 'TRUE_OR_FALSE') as expected_val, + AndroidStartupMetric_ActualValue( + 'value', TRUE) as actual_val, + get_main_thread_binder_transactions_blocked(launch.startup_id, 2e7, 3) as trace_slices, + NULL as trace_threads, + NULL as extra FROM android_startups launch WHERE launch.startup_id = $startup_id AND ( SELECT COUNT(1) - FROM BINDER_TRANSACTION_REPLY_SLICES_FOR_LAUNCH(launch.startup_id, 2e7) - ) > 0 - ); + FROM BINDER_TRANSACTION_REPLY_SLICES_FOR_LAUNCH(launch.startup_id, 2e7)) > 0 + ); diff --git a/src/trace_processor/metrics/sql/android/wattson_app_startup.sql b/src/trace_processor/metrics/sql/android/wattson_app_startup.sql new file mode 100644 index 0000000000..7a8df8be84 --- /dev/null +++ b/src/trace_processor/metrics/sql/android/wattson_app_startup.sql @@ -0,0 +1,221 @@ + +-- 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 wattson.device_infos; +INCLUDE PERFETTO MODULE wattson.curves.ungrouped; + +DROP VIEW IF EXISTS _app_startup_window; +CREATE PERFETTO VIEW _app_startup_window AS +SELECT + ts, + dur, + startup_id +FROM android_startups; + +DROP TABLE IF EXISTS _windowed_wattson; +CREATE VIRTUAL TABLE _windowed_wattson +USING + SPAN_JOIN(_app_startup_window, _system_state_curves); + +DROP VIEW IF EXISTS _wattson_base_components_pws; +CREATE PERFETTO VIEW _wattson_base_components_pws AS +SELECT + -- *_curve is in units of mW, so multiplying by ns gives pWs + SUM(static_curve * dur) as static_pws, + SUM(cpu0_curve * dur) as cpu0_pws, + SUM(cpu1_curve * dur) as cpu1_pws, + SUM(cpu2_curve * dur) as cpu2_pws, + SUM(cpu3_curve * dur) as cpu3_pws, + SUM(cpu4_curve * dur) as cpu4_pws, + SUM(cpu5_curve * dur) as cpu5_pws, + SUM(cpu6_curve * dur) as cpu6_pws, + SUM(cpu7_curve * dur) as cpu7_pws, + -- L3/SCU interconnect is already scaled by 10^6 in the L3 LUTs, so when + -- converting to pWs need to scale by 10^3 + SUM(l3_hit_value + l3_miss_value) * 1000 as l3_pws, + SUM(dur) as total_dur, + startup_id +FROM _windowed_wattson +GROUP BY startup_id; + +DROP VIEW IF EXISTS _wattson_cpu_rail_total; +CREATE PERFETTO VIEW _wattson_cpu_rail_total AS +SELECT + startup_id, + total_dur, + 'cpu_subsystem' as name, + ( + cpu0_pws + cpu1_pws + cpu2_pws + cpu3_pws + + cpu4_pws + cpu5_pws + cpu6_pws + cpu7_pws + + static_pws + l3_pws + ) / total_dur as estimate_mw +FROM _wattson_base_components_pws +GROUP BY startup_id; + +DROP VIEW IF EXISTS _wattson_cpu_subsubrail_grouped; +CREATE PERFETTO VIEW _wattson_cpu_subsubrail_grouped AS +SELECT + startup_id, + 'cpu0' as name, + map.policy, + cpu0_pws / total_dur as estimate_mw +FROM _wattson_base_components_pws +CROSS JOIN _dev_cpu_policy_map as map WHERE map.cpu = 0 +GROUP BY startup_id +UNION ALL +SELECT + startup_id, + 'cpu1' as name, + map.policy, + cpu1_pws / total_dur as estimate_mw +FROM _wattson_base_components_pws +CROSS JOIN _dev_cpu_policy_map as map WHERE map.cpu = 1 +GROUP BY startup_id +UNION ALL +SELECT + startup_id, + 'cpu2' as name, + map.policy, + cpu2_pws / total_dur as estimate_mw +FROM _wattson_base_components_pws +CROSS JOIN _dev_cpu_policy_map as map WHERE map.cpu = 2 +GROUP BY startup_id +UNION ALL +SELECT + startup_id, + 'cpu3' as name, + map.policy, + cpu3_pws / total_dur as estimate_mw +FROM _wattson_base_components_pws +CROSS JOIN _dev_cpu_policy_map as map WHERE map.cpu = 3 +GROUP BY startup_id +UNION ALL +SELECT + startup_id, + 'cpu4' as name, + map.policy, + cpu4_pws / total_dur as estimate_mw +FROM _wattson_base_components_pws +CROSS JOIN _dev_cpu_policy_map as map WHERE map.cpu = 4 +GROUP BY startup_id +UNION ALL +SELECT + startup_id, + 'cpu5' as name, + map.policy, + cpu5_pws / total_dur as estimate_mw +FROM _wattson_base_components_pws +CROSS JOIN _dev_cpu_policy_map as map WHERE map.cpu = 5 +GROUP BY startup_id +UNION ALL +SELECT + startup_id, + 'cpu6' as name, + map.policy, + cpu6_pws / total_dur as estimate_mw +FROM _wattson_base_components_pws +CROSS JOIN _dev_cpu_policy_map as map WHERE map.cpu = 6 +GROUP BY startup_id +UNION ALL +SELECT + startup_id, + 'cpu7' as name, + map.policy, + cpu7_pws / total_dur as estimate_mw +FROM _wattson_base_components_pws +CROSS JOIN _dev_cpu_policy_map as map WHERE map.cpu = 7 +GROUP BY startup_id; + +DROP VIEW IF EXISTS _wattson_cpu_subrail_grouped; +CREATE PERFETTO VIEW _wattson_cpu_subrail_grouped AS +SELECT + startup_id, + NULL as policy, + 'DSU_SCU' as name, + (static_pws + l3_pws) / total_dur as estimate_mw +FROM _wattson_base_components_pws GROUP BY startup_id +UNION ALL +SELECT + startup_id, + policy, + CONCAT('policy', policy) as name, + SUM(estimate_mw) as estimate_mw +FROM _wattson_cpu_subsubrail_grouped GROUP BY startup_id, policy; + +-- Grouped by CPUs, the smallest building block available +DROP VIEW IF EXISTS _cpu_subsubrail_estimate_per_startup_proto; +CREATE PERFETTO VIEW _cpu_subsubrail_estimate_per_startup_proto AS +SELECT + startup_id, + policy, + RepeatedField( + AndroidWattsonRailEstimate( + 'name', name, + 'estimate_mw', estimate_mw + ) + ) AS proto +FROM _wattson_cpu_subsubrail_grouped +GROUP BY startup_id, policy; + +-- Grouped by CPU policy +DROP VIEW IF EXISTS _cpu_subrail_estimate_per_startup_proto; +CREATE PERFETTO VIEW _cpu_subrail_estimate_per_startup_proto AS +SELECT + startup_id, + RepeatedField( + AndroidWattsonRailEstimate( + 'name', name, + 'estimate_mw', estimate_mw, + 'rail', _cpu_subsubrail_estimate_per_startup_proto.proto + ) + ) AS proto +FROM _wattson_cpu_subrail_grouped +-- Some subrails will not have any subsubrails, so LEFT JOIN +LEFT JOIN _cpu_subsubrail_estimate_per_startup_proto USING (startup_id, policy) +GROUP BY startup_id; + +-- Grouped into single entry for entirety of CPU system +DROP VIEW IF EXISTS _cpu_rail_estimate_per_startup_proto; +CREATE PERFETTO VIEW _cpu_rail_estimate_per_startup_proto AS +SELECT + startup_id, + RepeatedField( + AndroidWattsonRailEstimate( + 'name', name, + 'estimate_mw', estimate_mw, + 'rail', _cpu_subrail_estimate_per_startup_proto.proto + ) + ) AS proto +FROM _wattson_cpu_rail_total +JOIN _cpu_subrail_estimate_per_startup_proto USING (startup_id) +GROUP BY startup_id; + +DROP VIEW IF EXISTS wattson_app_startup_output; +CREATE PERFETTO VIEW wattson_app_startup_output AS +SELECT AndroidWattsonTimePeriodMetric( + 'metric_version', 1, + 'period_info', ( + SELECT RepeatedField( + AndroidWattsonEstimateInfo( + 'period_id', startup_id, + 'period_dur', dur, + 'rail', _cpu_rail_estimate_per_startup_proto.proto + ) + ) + FROM _app_startup_window + JOIN _cpu_rail_estimate_per_startup_proto USING (startup_id) + ) +); diff --git a/src/trace_processor/metrics/sql/experimental/chrome_dropped_frames.sql b/src/trace_processor/metrics/sql/experimental/chrome_dropped_frames.sql index 7ca915874e..2c33bc03cb 100644 --- a/src/trace_processor/metrics/sql/experimental/chrome_dropped_frames.sql +++ b/src/trace_processor/metrics/sql/experimental/chrome_dropped_frames.sql @@ -15,8 +15,8 @@ -- Find all dropped frames, i.e. all PipelineReporters slices whose -- state is 'STATE_DROPPED'. -DROP VIEW IF EXISTS dropped_pipeline_reporter_slice; -CREATE PERFETTO VIEW dropped_pipeline_reporter_slice AS +DROP TABLE IF EXISTS dropped_pipeline_reporter_slice; +CREATE PERFETTO TABLE dropped_pipeline_reporter_slice AS SELECT slice.* FROM slice JOIN args ON slice.arg_set_id = args.arg_set_id diff --git a/src/trace_processor/metrics/sql/trace_metadata.sql b/src/trace_processor/metrics/sql/trace_metadata.sql index b963ff2b6d..cbad75d38e 100644 --- a/src/trace_processor/metrics/sql/trace_metadata.sql +++ b/src/trace_processor/metrics/sql/trace_metadata.sql @@ -14,18 +14,6 @@ -- limitations under the License. -- --- Expose all clock snapshots as instant events. -DROP VIEW IF EXISTS trace_metadata_event; -CREATE PERFETTO VIEW trace_metadata_event AS -SELECT - 'slice' AS track_type, - 'Clock Snapshots' AS track_name, - ts, - 0 AS dur, - 'Snapshot' AS slice_name -FROM clock_snapshot -GROUP BY ts; - DROP VIEW IF EXISTS trace_metadata_output; CREATE PERFETTO VIEW trace_metadata_output AS SELECT TraceMetadata( diff --git a/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.cc b/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.cc index 8f163a0069..e990364746 100644 --- a/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.cc +++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.cc @@ -30,8 +30,10 @@ #include #include +#include "perfetto/base/compiler.h" #include "perfetto/base/logging.h" #include "perfetto/base/status.h" +#include "perfetto/ext/base/flat_hash_map.h" #include "perfetto/ext/base/status_or.h" #include "perfetto/ext/base/string_utils.h" #include "perfetto/ext/base/string_view.h" @@ -48,6 +50,7 @@ #include "src/trace_processor/sqlite/scoped_db.h" #include "src/trace_processor/sqlite/sql_source.h" #include "src/trace_processor/sqlite/sqlite_engine.h" +#include "src/trace_processor/sqlite/sqlite_utils.h" #include "src/trace_processor/tp_metatrace.h" #include "src/trace_processor/util/sql_argument.h" #include "src/trace_processor/util/sql_modules.h" @@ -141,7 +144,12 @@ SqlSource RewriteToDummySql(const SqlSource& source) { SqlSource::FromTraceProcessorImplementation("SELECT 0 WHERE 0")); } -constexpr std::array kTokensAllowedInMacro({ +constexpr std::array kTokensAllowedInMacro({ + "_ArgumentList", + "_ColumnNameList", + "_Macro", + "_SqlFragment", + "_TableNameList", "ColumnName", "Expr", "TableOrSubquery", @@ -205,14 +213,32 @@ PerfettoSqlEngine::PerfettoSqlEngine(StringPool* pool) } } -void PerfettoSqlEngine::RegisterStaticTable(const Table& table, +base::StatusOr +PerfettoSqlEngine::PrepareSqliteStatement(SqlSource sql_source) { + PerfettoSqlParser parser(std::move(sql_source), macros_); + if (!parser.Next()) { + return base::ErrStatus("No statement found to prepare"); + } + auto* sqlite = std::get_if(&parser.statement()); + if (!sqlite) { + return base::ErrStatus("Statement was not a valid SQLite statement"); + } + SqliteEngine::PreparedStatement stmt = + engine_->PrepareStatement(parser.statement_sql()); + if (parser.Next()) { + return base::ErrStatus("Too many statements found to prepare"); + } + return std::move(stmt); +} + +void PerfettoSqlEngine::RegisterStaticTable(Table* table, const std::string& table_name, Table::Schema schema) { // Make sure we didn't accidentally leak a state from a previous table // creation. PERFETTO_CHECK(!static_table_context_->temporary_create_state); static_table_context_->temporary_create_state = - std::make_unique(&table, std::move(schema)); + std::make_unique(table, std::move(schema)); base::StackString<1024> sql( R"( @@ -312,6 +338,14 @@ PerfettoSqlEngine::ExecuteUntilLastStatement(SqlSource sql_source) { auto sql = macro->sql; RETURN_IF_ERROR(ExecuteCreateMacro(*macro)); source = RewriteToDummySql(sql); + } else if (auto* create_index = std::get_if( + &parser.statement())) { + RETURN_IF_ERROR(ExecuteCreateIndex(*create_index)); + source = RewriteToDummySql(parser.statement_sql()); + } else if (auto* drop_index = std::get_if( + &parser.statement())) { + RETURN_IF_ERROR(ExecuteDropIndex(*drop_index)); + source = RewriteToDummySql(parser.statement_sql()); } else { // If none of the above matched, this must just be an SQL statement // directly executable by SQLite. @@ -361,9 +395,6 @@ PerfettoSqlEngine::ExecuteUntilLastStatement(SqlSource sql_source) { record->AddArg("Original SQL", res->original_sql()); record->AddArg("Executed SQL", res->sql()); }); - PERFETTO_DLOG("Executing statement"); - PERFETTO_DLOG("Original SQL: %s", res->original_sql()); - PERFETTO_DLOG("Executed SQL: %s", res->sql()); res->Step(); RETURN_IF_ERROR(res->status()); } @@ -442,12 +473,15 @@ base::Status PerfettoSqlEngine::ExecuteCreateTable( RETURN_IF_ERROR(maybe_column_names.status()); std::vector column_names = *maybe_column_names; - RETURN_IF_ERROR(ValidateColumnNames(column_names, create_table.schema, - "CREATE PERFETTO TABLE")); + base::StatusOr> + effective_schema = ValidateAndGetEffectiveSchema( + column_names, create_table.schema, "CREATE PERFETTO TABLE"); + RETURN_IF_ERROR(effective_schema.status()); size_t column_count = column_names.size(); - RuntimeTable::Builder builder(pool_, std::move(column_names)); + RuntimeTable::Builder builder(pool_, column_names); uint32_t rows = 0; + int res; for (res = sqlite3_step(stmt.sqlite_stmt()); res == SQLITE_ROW; ++rows, res = sqlite3_step(stmt.sqlite_stmt())) { @@ -485,8 +519,29 @@ base::Status PerfettoSqlEngine::ExecuteCreateTable( create_table.name.c_str(), sqlite3_errmsg(engine_->db())); } + ASSIGN_OR_RETURN(auto table, std::move(builder).Build(rows)); + // Validate the column types. + if (!effective_schema->empty()) { + auto actual_schema = table->schema(); + for (size_t i = 0; i < column_count; ++i) { + SqlValue::Type type = actual_schema.columns[i].type; + sql_argument::Type declared_type = (*effective_schema)[i].type(); + SqlValue::Type effective_declared_type = + sql_argument::TypeToSqlValueType(declared_type); + if (type != SqlValue::kNull && type != effective_declared_type) { + return base::ErrStatus( + "CREATE PERFETTO TABLE: column '%s' declared as %s (%s) in the " + "schema, but %s found", + column_names[i].c_str(), + sql_argument::TypeToHumanFriendlyString(declared_type), + sqlite::utils::SqlValueTypeToString(effective_declared_type), + sqlite::utils::SqlValueTypeToString(type)); + } + } + } + // TODO(lalitm): unfortunately, in the (very unlikely) event that there is a // sqlite3_interrupt call between the DROP and CREATE, we can end up with the // non-atomic query execution. Fixing this is extremely difficult as it @@ -549,8 +604,10 @@ base::Status PerfettoSqlEngine::ExecuteCreateView( RETURN_IF_ERROR(maybe_column_names.status()); std::vector column_names = *maybe_column_names; - RETURN_IF_ERROR(ValidateColumnNames(column_names, create_view.schema, - "CREATE PERFETTO VIEW")); + RETURN_IF_ERROR(ValidateAndGetEffectiveSchema(column_names, + create_view.schema, + "CREATE PERFETTO VIEW") + .status()); } RETURN_IF_ERROR(Execute(create_view.create_view_sql).status()); @@ -592,6 +649,51 @@ base::Status PerfettoSqlEngine::ExecuteInclude( return IncludeModuleImpl(*module, key, parser); } +base::Status PerfettoSqlEngine::ExecuteCreateIndex( + const PerfettoSqlParser::CreateIndex& index) { + Table* t = GetMutableTableOrNull(index.table_name); + if (!t) { + return base::ErrStatus("CREATE PERFETTO INDEX: Table '%s' not found", + index.table_name.c_str()); + } + + std::vector obs; + std::vector col_idxs; + for (const std::string& col_name : index.col_names) { + const std::optional opt_col = t->ColumnIdxFromName(col_name); + if (!opt_col) { + return base::ErrStatus( + "CREATE PERFETTO INDEX: Column '%s' not found in table '%s'", + index.col_names.front().c_str(), index.table_name.c_str()); + } + Order o; + o.col_idx = *opt_col; + obs.push_back(o); + col_idxs.push_back(*opt_col); + } + + Query q; + q.orders = obs; + RowMap sorted_rm = t->QueryToRowMap(q); + + RETURN_IF_ERROR(t->SetIndex(index.name, std::move(col_idxs), + std::move(sorted_rm).TakeAsIndexVector(), + index.replace)); + return base::OkStatus(); +} + +base::Status PerfettoSqlEngine::ExecuteDropIndex( + const PerfettoSqlParser::DropIndex& index) { + Table* t = GetMutableTableOrNull(index.table_name); + if (!t) { + return base::ErrStatus("DROP PERFETTO INDEX: Table '%s' not found", + index.table_name.c_str()); + } + + RETURN_IF_ERROR(t->DropIndex(index.name)); + return base::OkStatus(); +} + base::Status PerfettoSqlEngine::IncludeModuleImpl( sql_modules::RegisteredModule& module, const std::string& key, @@ -818,7 +920,7 @@ base::Status PerfettoSqlEngine::ExecuteCreateMacro( base::StatusOr> PerfettoSqlEngine::GetColumnNamesFromSelectStatement( const SqliteEngine::PreparedStatement& stmt, - const char* tag) { + const char* tag) const { auto columns = static_cast(sqlite3_column_count(stmt.sqlite_stmt())); std::vector column_names; @@ -844,24 +946,41 @@ PerfettoSqlEngine::GetColumnNamesFromSelectStatement( return column_names; } -base::Status PerfettoSqlEngine::ValidateColumnNames( +base::StatusOr> +PerfettoSqlEngine::ValidateAndGetEffectiveSchema( const std::vector& column_names, const std::vector& schema, - const char* tag) { - // If the user has not provided a schema, we have nothing to validate. + const char* tag) const { + std::vector duplicate_columns; + for (auto it = column_names.begin(); it != column_names.end(); ++it) { + if (std::count(it + 1, column_names.end(), *it) > 0) { + duplicate_columns.push_back(*it); + } + } + if (!duplicate_columns.empty()) { + return base::ErrStatus("%s: multiple columns are named: %s", tag, + base::Join(duplicate_columns, ", ").c_str()); + } + + // If the user has not provided a schema, we have nothing further to validate. if (schema.empty()) { - return base::OkStatus(); + return schema; } std::vector columns_missing_from_query; std::vector columns_missing_from_schema; + std::vector effective_schema; + for (const std::string& name : column_names) { - bool present = + auto it = std::find_if(schema.begin(), schema.end(), [&name](const auto& arg) { return arg.name() == base::StringView(name); - }) != schema.end(); - if (!present) { + }); + bool present = it != schema.end(); + if (present) { + effective_schema.push_back(*it); + } else { columns_missing_from_schema.push_back(name); } } @@ -876,29 +995,30 @@ base::Status PerfettoSqlEngine::ValidateColumnNames( } } - if (columns_missing_from_query.empty() && - columns_missing_from_schema.empty()) { - return base::OkStatus(); + if (!columns_missing_from_query.empty() && + !columns_missing_from_schema.empty()) { + return base::ErrStatus( + "%s: the following columns are declared in the schema, but do not " + "exist: " + "%s; and the folowing columns exist, but are not declared: %s", + tag, base::Join(columns_missing_from_query, ", ").c_str(), + base::Join(columns_missing_from_schema, ", ").c_str()); } - if (columns_missing_from_query.empty()) { + if (!columns_missing_from_schema.empty()) { return base::ErrStatus( "%s: the following columns are missing from the schema: %s", tag, base::Join(columns_missing_from_schema, ", ").c_str()); } - if (columns_missing_from_schema.empty()) { + if (!columns_missing_from_query.empty()) { return base::ErrStatus( "%s: the following columns are declared in the schema, but do not " "exist: %s", tag, base::Join(columns_missing_from_query, ", ").c_str()); } - return base::ErrStatus( - "%s: the following columns are declared in the schema, but do not exist: " - "%s; and the folowing columns exist, but are not declared: %s", - tag, base::Join(columns_missing_from_query, ", ").c_str(), - base::Join(columns_missing_from_schema, ", ").c_str()); + return effective_schema; } const RuntimeTable* PerfettoSqlEngine::GetRuntimeTableOrNull( @@ -907,10 +1027,21 @@ const RuntimeTable* PerfettoSqlEngine::GetRuntimeTableOrNull( return state ? state->runtime_table.get() : nullptr; } +RuntimeTable* PerfettoSqlEngine::GetMutableRuntimeTableOrNull( + std::string_view name) { + auto* state = runtime_table_context_->manager.FindStateByName(name); + return state ? state->runtime_table.get() : nullptr; +} + const Table* PerfettoSqlEngine::GetStaticTableOrNull( std::string_view name) const { auto* state = static_table_context_->manager.FindStateByName(name); return state ? state->static_table : nullptr; } +Table* PerfettoSqlEngine::GetMutableStaticTableOrNull(std::string_view name) { + auto* state = static_table_context_->manager.FindStateByName(name); + return state ? state->static_table : nullptr; +} + } // namespace perfetto::trace_processor diff --git a/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h b/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h index 1d7642f48f..5f009da59a 100644 --- a/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h +++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h @@ -17,6 +17,7 @@ #ifndef SRC_TRACE_PROCESSOR_PERFETTO_SQL_ENGINE_PERFETTO_SQL_ENGINE_H_ #define SRC_TRACE_PROCESSOR_PERFETTO_SQL_ENGINE_PERFETTO_SQL_ENGINE_H_ +#include #include #include #include @@ -83,6 +84,14 @@ class PerfettoSqlEngine { // no valid SQL to run. base::StatusOr ExecuteUntilLastStatement(SqlSource sql); + // Prepares a single SQLite statement in |sql| and returns a + // |PreparedStatement| object. + // + // Returns an error if the preparation of the statement failed or if there was + // no valid SQL to run. + base::StatusOr PrepareSqliteStatement( + SqlSource sql); + // Registers a trace processor C++ function to be runnable from SQL. // // The format of the function is given by the |SqlFunction|. @@ -115,13 +124,27 @@ class PerfettoSqlEngine { std::unique_ptr ctx, bool deterministic = true); + // Registers a trace processor C++ function to be runnable from SQL. + // + // The format of the function is given by the |SqliteFunction|. + // + // |ctx|: context object for the function; this object *must* + // outlive the function so should likely be either static or + // scoped to the lifetime of TraceProcessor. + // |deterministic|: whether this function has deterministic output given the + // same set of arguments. + template + base::Status RegisterSqliteFunction(typename Function::UserDataContext* ctx, + bool deterministic = true); + template + base::Status RegisterSqliteFunction( + std::unique_ptr ctx, + bool deterministic = true); + // Registers a trace processor C++ aggregate function to be runnable from SQL. // // The format of the function is given by the |SqliteAggregateFunction|. // - // |name|: name of the function in SQL - // |argc|: number of arguments for this function. This can be -1 if - // the number of arguments is variable. // |ctx|: context object for the function; this object *must* // outlive the function so should likely be either static or // scoped to the lifetime of TraceProcessor. @@ -129,8 +152,6 @@ class PerfettoSqlEngine { // same set of arguments. template base::Status RegisterSqliteAggregateFunction( - const char* name, - int argc, typename Function::UserDataContext* ctx, bool deterministic = true); @@ -164,7 +185,7 @@ class PerfettoSqlEngine { // Registers a trace processor C++ table with SQLite with an SQL name of // |name|. - void RegisterStaticTable(const Table&, + void RegisterStaticTable(Table*, const std::string& name, Table::Schema schema); @@ -208,12 +229,35 @@ class PerfettoSqlEngine { runtime_function_count_ + macros_.size(); } + // Find table (Static or Runtime) registered with engine with provided name. + const Table* GetTableOrNull(std::string_view name) const { + if (auto maybe_runtime = GetRuntimeTableOrNull(name); maybe_runtime) { + return maybe_runtime; + } + return GetStaticTableOrNull(name); + } + // Find RuntimeTable registered with engine with provided name. const RuntimeTable* GetRuntimeTableOrNull(std::string_view) const; // Find static table registered with engine with provided name. const Table* GetStaticTableOrNull(std::string_view) const; + // Find table (Static or Runtime) registered with engine with provided name. + Table* GetMutableTableOrNull(std::string_view name) { + if (auto maybe_runtime = GetMutableRuntimeTableOrNull(name); + maybe_runtime) { + return maybe_runtime; + } + return GetMutableStaticTableOrNull(name); + } + + // Find RuntimeTable registered with engine with provided name. + RuntimeTable* GetMutableRuntimeTableOrNull(std::string_view); + + // Find static table registered with engine with provided name. + Table* GetMutableStaticTableOrNull(std::string_view); + private: base::Status ExecuteCreateFunction(const PerfettoSqlParser::CreateFunction&); @@ -228,6 +272,10 @@ class PerfettoSqlEngine { base::Status ExecuteCreateMacro(const PerfettoSqlParser::CreateMacro&); + base::Status ExecuteCreateIndex(const PerfettoSqlParser::CreateIndex&); + + base::Status ExecuteDropIndex(const PerfettoSqlParser::DropIndex&); + template base::Status RegisterFunctionWithSqlite( const char* name, @@ -236,17 +284,21 @@ class PerfettoSqlEngine { bool deterministic = true); // Get the column names from a statement. - // |operator_name| is used in the error message if the statement is invalid. - static base::StatusOr> - GetColumnNamesFromSelectStatement(const SqliteEngine::PreparedStatement& stmt, - const char* tag); + // |tag| is used in the error message if the statement is invalid. + base::StatusOr> GetColumnNamesFromSelectStatement( + const SqliteEngine::PreparedStatement& stmt, + const char* tag) const; // Validates that the column names in |column_names| match the |schema|. - // |operator_name| is used in the error message if the statement is invalid. - static base::Status ValidateColumnNames( + // Given that PerfettoSQL supports an arbitrary order of columns in the + // schema, this function also normalises the schema by reordering the schema + // columns to match the order of columns in the query. |tag| is used in the + // error message if the statement is invalid. + base::StatusOr> + ValidateAndGetEffectiveSchema( const std::vector& column_names, const std::vector& schema, - const char* operator_name); + const char* tag) const; // Given a module and a key, include the correct file(s) from the module. // The key can contain a wildcard to include all files in the module with the @@ -348,15 +400,37 @@ base::Status PerfettoSqlEngine::RegisterStaticFunction( nullptr, deterministic); } +template +base::Status PerfettoSqlEngine::RegisterSqliteFunction( + typename Function::UserDataContext* ctx, + bool deterministic) { + static_function_count_++; + return engine_->RegisterFunction(Function::kName, Function::kArgCount, + Function::Step, ctx, nullptr, deterministic); +} + +template +base::Status PerfettoSqlEngine::RegisterSqliteFunction( + std::unique_ptr ctx, + bool deterministic) { + static_function_count_++; + return engine_->RegisterFunction( + Function::kName, Function::kArgCount, Function::Step, ctx.release(), + [](void* ptr) { + std::unique_ptr( + static_cast(ptr)); + }, + deterministic); +} + template base::Status PerfettoSqlEngine::RegisterSqliteAggregateFunction( - const char* name, - int argc, typename Function::UserDataContext* ctx, bool deterministic) { static_aggregate_function_count_++; return engine_->RegisterAggregateFunction( - name, argc, Function::Step, Function::Final, ctx, nullptr, deterministic); + Function::kName, Function::kArgCount, Function::Step, Function::Final, + ctx, nullptr, deterministic); } template diff --git a/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine_unittest.cc b/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine_unittest.cc index 58a690cfbf..cabf9e4177 100644 --- a/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine_unittest.cc +++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine_unittest.cc @@ -137,7 +137,24 @@ TEST_F(PerfettoSqlEngineTest, Table_Schema) { ASSERT_TRUE(res.ok()) << res.status().c_message(); } -TEST_F(PerfettoSqlEngineTest, Table_IncorrectSchema) { +TEST_F(PerfettoSqlEngineTest, Table_Schema_EmptyTable) { + // This test checks that the type checks correctly work on empty tables (and + // that columns with no data do not default to "int"). + auto res = engine_.Execute( + SqlSource::FromExecuteQuery("CREATE PERFETTO TABLE foo(bar STRING) AS " + "SELECT 'bar' as bar WHERE bar = 'foo'")); + ASSERT_TRUE(res.ok()) << res.status().c_message(); +} + +TEST_F(PerfettoSqlEngineTest, Table_Schema_NullColumn) { + // This test checks that the type checks correctly work on columns without + // data (and that columns with no non-NULL data do not default to "int"). + auto res = engine_.Execute(SqlSource::FromExecuteQuery( + "CREATE PERFETTO TABLE foo(bar STRING) AS SELECT NULL as bar")); + ASSERT_TRUE(res.ok()) << res.status().c_message(); +} + +TEST_F(PerfettoSqlEngineTest, Table_IncorrectSchema_MissingColumn) { auto res = engine_.Execute(SqlSource::FromExecuteQuery( "CREATE PERFETTO TABLE foo(x INT) AS SELECT 1 as y")); ASSERT_FALSE(res.ok()); @@ -148,6 +165,15 @@ TEST_F(PerfettoSqlEngineTest, Table_IncorrectSchema) { "folowing columns exist, but are not declared: y")); } +TEST_F(PerfettoSqlEngineTest, Table_IncorrectSchema_IncorrectType) { + auto res = engine_.Execute(SqlSource::FromExecuteQuery( + "CREATE PERFETTO TABLE foo(x INT) AS SELECT '1' as x")); + ASSERT_FALSE(res.ok()); + EXPECT_THAT(res.status().c_message(), + testing::EndsWith("CREATE PERFETTO TABLE: column 'x' declared as " + "INT (LONG) in the schema, but STRING found")); +} + TEST_F(PerfettoSqlEngineTest, Table_Drop) { auto res_create = engine_.Execute(SqlSource::FromExecuteQuery( "CREATE PERFETTO TABLE foo AS SELECT 'foo' AS bar")); @@ -299,10 +325,10 @@ TEST_F(PerfettoSqlEngineTest, MismatchedRange) { tables::SliceTable parent(&pool_); tables::ExpectedFrameTimelineSliceTable child(&pool_, &parent); - engine_.RegisterStaticTable(parent, "parent", + engine_.RegisterStaticTable(&parent, "parent", tables::SliceTable::ComputeStaticSchema()); engine_.RegisterStaticTable( - child, "child", + &child, "child", tables::ExpectedFrameTimelineSliceTable::ComputeStaticSchema()); for (uint32_t i = 0; i < 5; i++) { diff --git a/src/trace_processor/perfetto_sql/engine/perfetto_sql_parser.cc b/src/trace_processor/perfetto_sql/engine/perfetto_sql_parser.cc index a246c6a813..78eaf414e9 100644 --- a/src/trace_processor/perfetto_sql/engine/perfetto_sql_parser.cc +++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_parser.cc @@ -24,6 +24,7 @@ #include #include +#include "perfetto/base/compiler.h" #include "perfetto/base/logging.h" #include "perfetto/base/status.h" #include "perfetto/ext/base/flat_hash_map.h" @@ -40,15 +41,17 @@ using Token = SqliteTokenizer::Token; using Statement = PerfettoSqlParser::Statement; enum class State { - kStmtStart, + kDrop, + kDropPerfetto, kCreate, - kInclude, - kIncludePerfetto, kCreateOr, kCreateOrReplace, kCreateOrReplacePerfetto, kCreatePerfetto, + kInclude, + kIncludePerfetto, kPassthrough, + kStmtStart, }; bool KeywordEqual(std::string_view expected, std::string_view actual) { @@ -153,6 +156,8 @@ bool PerfettoSqlParser::Next() { state = State::kCreate; } else if (TokenIsCustomKeyword("include", token)) { state = State::kInclude; + } else if (TokenIsSqliteKeyword("drop", token)) { + state = State::kDrop; } else { state = State::kPassthrough; } @@ -172,6 +177,19 @@ bool PerfettoSqlParser::Next() { return ErrorAtToken(token, "Use 'INCLUDE PERFETTO MODULE {include_key}'."); } + case State::kDrop: + if (TokenIsCustomKeyword("perfetto", token)) { + state = State::kDropPerfetto; + } else { + state = State::kPassthrough; + } + break; + case State::kDropPerfetto: + if (TokenIsSqliteKeyword("index", token)) { + return ParseDropPerfettoIndex(*first_non_space_token); + } else { + return ErrorAtToken(token, "Only Perfetto index can be dropped"); + } case State::kCreate: if (TokenIsSqliteKeyword("trigger", token)) { // TODO(lalitm): add this to the "errors" documentation page @@ -213,9 +231,12 @@ bool PerfettoSqlParser::Next() { if (TokenIsCustomKeyword("macro", token)) { return ParseCreatePerfettoMacro(replace); } + if (TokenIsSqliteKeyword("index", token)) { + return ParseCreatePerfettoIndex(replace, *first_non_space_token); + } base::StackString<1024> err( - "Expected 'FUNCTION', 'TABLE' or 'MACRO' after 'CREATE PERFETTO', " - "received '%*s'.", + "Expected 'FUNCTION', 'TABLE', 'MACRO' OR 'INDEX' after 'CREATE " + "PERFETTO', received '%*s'.", static_cast(token.str.size()), token.str.data()); return ErrorAtToken(token, err.c_str()); } @@ -298,6 +319,101 @@ bool PerfettoSqlParser::ParseCreatePerfettoTableOrView( return true; } +bool PerfettoSqlParser::ParseCreatePerfettoIndex(bool replace, + Token first_non_space_token) { + Token index_name_tok = tokenizer_.NextNonWhitespace(); + if (index_name_tok.token_type != SqliteTokenType::TK_ID) { + base::StackString<1024> err("Invalid index name %.*s", + static_cast(index_name_tok.str.size()), + index_name_tok.str.data()); + return ErrorAtToken(index_name_tok, err.c_str()); + } + std::string index_name(index_name_tok.str); + + auto token = tokenizer_.NextNonWhitespace(); + if (!TokenIsSqliteKeyword("on", token)) { + base::StackString<1024> err("Expected 'ON' after index name, received %*s.", + static_cast(token.str.size()), + token.str.data()); + return ErrorAtToken(token, err.c_str()); + } + + Token table_name_tok = tokenizer_.NextNonWhitespace(); + if (table_name_tok.token_type != SqliteTokenType::TK_ID) { + base::StackString<1024> err("Invalid table name %.*s", + static_cast(table_name_tok.str.size()), + table_name_tok.str.data()); + return ErrorAtToken(table_name_tok, err.c_str()); + } + std::string table_name(table_name_tok.str); + + token = tokenizer_.NextNonWhitespace(); + if (token.token_type != SqliteTokenType::TK_LP) { + base::StackString<1024> err( + "Expected parenthesis after table name, received '%*s'.", + static_cast(token.str.size()), token.str.data()); + return ErrorAtToken(token, err.c_str()); + } + + std::vector cols; + + do { + Token col_name_tok = tokenizer_.NextNonWhitespace(); + cols.push_back(std::string(col_name_tok.str)); + token = tokenizer_.NextNonWhitespace(); + } while (token.token_type == SqliteTokenType::TK_COMMA); + + if (token.token_type != SqliteTokenType::TK_RP) { + base::StackString<1024> err("Expected closed parenthesis, received '%*s'.", + static_cast(token.str.size()), + token.str.data()); + return ErrorAtToken(token, err.c_str()); + } + + Token terminal = tokenizer_.NextTerminal(); + statement_sql_ = tokenizer_.Substr(first_non_space_token, terminal); + statement_ = CreateIndex{replace, index_name, table_name, cols}; + return true; +} + +bool PerfettoSqlParser::ParseDropPerfettoIndex( + SqliteTokenizer::Token first_non_space_token) { + Token index_name_tok = tokenizer_.NextNonWhitespace(); + if (index_name_tok.token_type != SqliteTokenType::TK_ID) { + base::StackString<1024> err("Invalid index name %.*s", + static_cast(index_name_tok.str.size()), + index_name_tok.str.data()); + return ErrorAtToken(index_name_tok, err.c_str()); + } + std::string index_name(index_name_tok.str); + + auto token = tokenizer_.NextNonWhitespace(); + if (!TokenIsSqliteKeyword("on", token)) { + base::StackString<1024> err("Expected 'ON' after index name, received %*s.", + static_cast(token.str.size()), + token.str.data()); + return ErrorAtToken(token, err.c_str()); + } + + Token table_name_tok = tokenizer_.NextNonWhitespace(); + if (table_name_tok.token_type != SqliteTokenType::TK_ID) { + base::StackString<1024> err("Invalid table name %.*s", + static_cast(table_name_tok.str.size()), + table_name_tok.str.data()); + return ErrorAtToken(table_name_tok, err.c_str()); + } + std::string table_name(table_name_tok.str); + + token = tokenizer_.NextNonWhitespace(); + if (!token.IsTerminal()) { + return ErrorAtToken( + token, "Nothing is allowed after table name in DROP PERFETTO INDEX"); + } + statement_sql_ = tokenizer_.Substr(first_non_space_token, token); + statement_ = DropIndex{index_name, table_name}; + return true; +} + bool PerfettoSqlParser::ParseCreatePerfettoFunction( bool replace, Token first_non_space_token) { diff --git a/src/trace_processor/perfetto_sql/engine/perfetto_sql_parser.h b/src/trace_processor/perfetto_sql/engine/perfetto_sql_parser.h index a9864e7563..bafa3e75e1 100644 --- a/src/trace_processor/perfetto_sql/engine/perfetto_sql_parser.h +++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_parser.h @@ -77,6 +77,20 @@ class PerfettoSqlParser { SqlSource create_view_sql; std::vector schema; }; + // Indicates that the specified SQL was a CREATE PERFETTO INDEX statement + // with the following parameters. + struct CreateIndex { + bool replace = false; + std::string name; + std::string table_name; + std::vector col_names; + }; + // Indicates that the specified SQL was a DROP PERFETTO INDEX statement + // with the following parameters. + struct DropIndex { + std::string name; + std::string table_name; + }; // Indicates that the specified SQL was a INCLUDE PERFETTO MODULE statement // with the following parameter. struct Include { @@ -91,12 +105,14 @@ class PerfettoSqlParser { SqlSource returns; SqlSource sql; }; - using Statement = std::variant; + SqliteSql>; // Creates a new SQL parser with the a block of PerfettoSQL statements. // Concretely, the passed string can contain >1 statement. @@ -162,6 +178,11 @@ class PerfettoSqlParser { bool ParseCreatePerfettoMacro(bool replace); + bool ParseCreatePerfettoIndex(bool replace, + SqliteTokenizer::Token first_non_space_token); + + bool ParseDropPerfettoIndex(SqliteTokenizer::Token first_non_space_token); + // Convert a "raw" argument (i.e. one that points to specific tokens) to the // argument definition consumed by the rest of the SQL code. // Guarantees to call ErrorAtToken if std::nullopt is returned. diff --git a/src/trace_processor/perfetto_sql/engine/perfetto_sql_parser_unittest.cc b/src/trace_processor/perfetto_sql/engine/perfetto_sql_parser_unittest.cc index e3653ca379..bc26039727 100644 --- a/src/trace_processor/perfetto_sql/engine/perfetto_sql_parser_unittest.cc +++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_parser_unittest.cc @@ -37,6 +37,7 @@ using CreateTable = PerfettoSqlParser::CreateTable; using CreateView = PerfettoSqlParser::CreateView; using Include = PerfettoSqlParser::Include; using CreateMacro = PerfettoSqlParser::CreateMacro; +using CreateIndex = PerfettoSqlParser::CreateIndex; namespace { diff --git a/src/trace_processor/perfetto_sql/engine/perfetto_sql_preprocessor.cc b/src/trace_processor/perfetto_sql/engine/perfetto_sql_preprocessor.cc index feaa228790..a381468c49 100644 --- a/src/trace_processor/perfetto_sql/engine/perfetto_sql_preprocessor.cc +++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_preprocessor.cc @@ -16,18 +16,177 @@ #include "src/trace_processor/perfetto_sql/engine/perfetto_sql_preprocessor.h" +#include +#include +#include #include +#include +#include #include #include +#include +#include "perfetto/base/logging.h" #include "perfetto/base/status.h" +#include "perfetto/ext/base/flat_hash_map.h" +#include "perfetto/ext/base/status_or.h" #include "perfetto/ext/base/string_utils.h" #include "src/trace_processor/sqlite/sql_source.h" #include "src/trace_processor/sqlite/sqlite_tokenizer.h" #include "src/trace_processor/util/status_macros.h" -namespace perfetto { -namespace trace_processor { +namespace perfetto::trace_processor { +namespace { + +enum IntrinsicMacro { + kStringify, + kTokenZipJoin, + kPrefixedTokenZipJoin, + kTokenApply, + kTokenMapJoin, + kTokenMapJoinWithCapture, + kComma, + kOther +}; + +IntrinsicMacro MacroNameToEnum(const std::string& macro_name) { + if (macro_name == "__intrinsic_stringify") + return kStringify; + if (macro_name == "__intrinsic_token_zip_join") + return kTokenZipJoin; + if (macro_name == "__intrinsic_prefixed_token_zip_join") + return kPrefixedTokenZipJoin; + if (macro_name == "__intrinsic_token_apply") + return kTokenApply; + if (macro_name == "__intrinsic_token_map_join") + return kTokenMapJoin; + if (macro_name == "__intrinsic_token_map_join_with_capture") + return kTokenMapJoinWithCapture; + if (macro_name == "__intrinsic_token_comma") + return kComma; + + return kOther; +} + +base::Status ErrorAtToken(const SqliteTokenizer& tokenizer, + const SqliteTokenizer::Token& token, + const char* error) { + std::string traceback = tokenizer.AsTraceback(token); + return base::ErrStatus("%s%s", traceback.c_str(), error); +} + +struct InvocationArg { + std::optional arg; + bool has_more; +}; + +base::StatusOr ParseMacroInvocationArg( + SqliteTokenizer& tokenizer, + SqliteTokenizer::Token& tok, + bool has_prev_args) { + uint32_t nested_parens = 0; + bool seen_token_in_arg = false; + auto start = tokenizer.NextNonWhitespace(); + for (tok = start;; tok = tokenizer.NextNonWhitespace()) { + if (tok.IsTerminal()) { + if (tok.token_type == SqliteTokenType::TK_SEMI) { + // TODO(b/290185551): add a link to macro documentation. + return ErrorAtToken(tokenizer, tok, + "Semi-colon is not allowed in macro invocation"); + } + // TODO(b/290185551): add a link to macro documentation. + return ErrorAtToken(tokenizer, tok, "Macro invocation not complete"); + } + + bool is_arg_terminator = tok.token_type == SqliteTokenType::TK_RP || + tok.token_type == SqliteTokenType::TK_COMMA; + if (nested_parens == 0 && is_arg_terminator) { + bool token_required = + has_prev_args || tok.token_type != SqliteTokenType::TK_RP; + if (!seen_token_in_arg && token_required) { + // TODO(b/290185551): add a link to macro documentation. + return ErrorAtToken(tokenizer, tok, "Macro arg is empty"); + } + return InvocationArg{ + seen_token_in_arg ? std::make_optional(tokenizer.Substr(start, tok)) + : std::optional(std::nullopt), + tok.token_type == SqliteTokenType::TK_COMMA, + }; + } + seen_token_in_arg = true; + + if (tok.token_type == SqliteTokenType::TK_LP) { + nested_parens++; + continue; + } + if (tok.token_type == SqliteTokenType::TK_RP) { + nested_parens--; + continue; + } + } +} + +base::StatusOr> ExecuteStringify( + const SqliteTokenizer& tokenizer, + const SqliteTokenizer::Token& name_token, + const std::vector& args) { + if (args.empty()) { + return ErrorAtToken(tokenizer, name_token, + "stringify: stringify must not be empty"); + } + + // Track the set of variables that, even if we see during stringify, we ignore + // and stringify them anyway. + std::unordered_set ignored_variables; + for (uint32_t i = 1; i < args.size(); ++i) { + ignored_variables.emplace(args[i].sql()); + } + + // Ensure that we don't stringifiy any SQL variables present (unless they were + // explcitily marked as ignored). + SqliteTokenizer t(args[0]); + for (auto tok = t.NextNonWhitespace(); !tok.IsTerminal(); + tok = t.NextNonWhitespace()) { + if (tok.token_type == SqliteTokenType::TK_VARIABLE && + !ignored_variables.count(std::string(tok.str.substr(1)))) { + return {std::nullopt}; + } + } + std::string res = "'" + args[0].sql() + "'"; + return {SqlSource::FromTraceProcessorImplementation(std::move(res))}; +} + +void RewriteIntrinsicMacro(const std::string& macro_name, + std::optional& res, + std::vector& token_list, + SqliteTokenizer& tokenizer, + SqlSource::Rewriter& rewriter, + SqliteTokenizer::Token prev, + SqliteTokenizer::Token tok) { + if (res) { + tokenizer.Rewrite(rewriter, prev, tok, *std::move(res), + SqliteTokenizer::EndToken::kInclusive); + return; + } + + // We failed to rewrite because a variable was still present in SQL. + // Just readd the stringify SQL with newly expanded token list. + std::vector pieces; + pieces.reserve(token_list.size()); + for (const auto& list : token_list) { + if (base::TrimWhitespace(list.sql()) == ",") { + pieces.emplace_back("__intrinsic_token_comma!()"); + } else { + pieces.emplace_back(list.sql()); + } + } + tokenizer.Rewrite(rewriter, prev, tok, + SqlSource::FromTraceProcessorImplementation( + macro_name + "!(" + base::Join(pieces, ", ") + ")"), + SqliteTokenizer::EndToken::kInclusive); +} + +} // namespace PerfettoSqlPreprocessor::PerfettoSqlPreprocessor( SqlSource source, @@ -78,7 +237,10 @@ base::StatusOr PerfettoSqlPreprocessor::RewriteInternal( } auto binding_it = arg_bindings.find(std::string(tok.str.substr(1))); if (binding_it == arg_bindings.end()) { - return ErrorAtToken(tokenizer, tok, "Variable not found"); + // TODO(lalitm): reenable making this an error once we actually pass + // macros around in graph_scan instead of bare-SQL. + // return ErrorAtToken(tokenizer, tok, "Variable not found"); + continue; } tokenizer.RewriteToken(rewriter, tok, binding_it->second); continue; @@ -87,140 +249,359 @@ base::StatusOr PerfettoSqlPreprocessor::RewriteInternal( continue; } - base::StatusOr invocation_or = - ParseMacroInvocation(tokenizer, tok, prev, arg_bindings); - RETURN_IF_ERROR(invocation_or.status()); + const auto& name_token = prev; + if (name_token.token_type == SqliteTokenType::TK_VARIABLE) { + // TODO(b/290185551): add a link to macro documentation. + return ErrorAtToken(tokenizer, name_token, + "Macro name cannot be a variable"); + } + if (name_token.token_type != SqliteTokenType::TK_ID) { + // TODO(b/290185551): add a link to macro documentation. + return ErrorAtToken(tokenizer, name_token, "Macro invocation is invalid"); + } + + // Go to the opening parenthesis of the macro invocation. + tok = tokenizer.NextNonWhitespace(); - seen_macros_.emplace(invocation_or->macro->name); - auto source_or = - RewriteInternal(invocation_or->macro->sql, invocation_or->arg_bindings); - RETURN_IF_ERROR(source_or.status()); - seen_macros_.erase(invocation_or->macro->name); + std::string macro_name(name_token.str); + IntrinsicMacro macro_enum = MacroNameToEnum(macro_name); + ASSIGN_OR_RETURN(std::vector token_list, + ParseTokenList(tokenizer, tok, arg_bindings)); - tokenizer.Rewrite(rewriter, prev, tok, std::move(*source_or), - SqliteTokenizer::EndToken::kInclusive); + // Non intrinsic macro. + if (macro_enum == kOther) { + ASSIGN_OR_RETURN(SqlSource invocation, + ExecuteMacroInvocation(tokenizer, prev, macro_name, + std::move(token_list))); + tokenizer.Rewrite(rewriter, prev, tok, std::move(invocation), + SqliteTokenizer::EndToken::kInclusive); + continue; + } + + // Token comma instrinsic macro requires special handling. + if (macro_enum == kComma) { + if (!token_list.empty()) { + return ErrorAtToken(tokenizer, name_token, + "token_comma: no arguments allowd"); + } + tokenizer.Rewrite(rewriter, prev, tok, + SqlSource::FromTraceProcessorImplementation(","), + SqliteTokenizer::EndToken::kInclusive); + continue; + } + + // Intrinsic macros. + std::optional res; + switch (macro_enum) { + case kStringify: { + ASSIGN_OR_RETURN(res, + ExecuteStringify(tokenizer, name_token, token_list)); + break; + } + case kTokenZipJoin: { + ASSIGN_OR_RETURN( + res, ExecuteTokenZipJoin(tokenizer, name_token, token_list, false)); + break; + } + case kPrefixedTokenZipJoin: { + ASSIGN_OR_RETURN( + res, ExecuteTokenZipJoin(tokenizer, name_token, token_list, true)); + break; + } + case kTokenMapJoin: { + ASSIGN_OR_RETURN( + res, ExecuteTokenMapJoin(tokenizer, name_token, token_list)); + break; + } + case kTokenMapJoinWithCapture: { + ASSIGN_OR_RETURN(res, ExecuteTokenMapJoinWithCapture( + tokenizer, name_token, token_list)); + + break; + } + + case kTokenApply: { + ASSIGN_OR_RETURN(res, + ExecuteTokenApply(tokenizer, name_token, token_list)); + break; + } + case kComma: + case kOther: + PERFETTO_FATAL("Shouldn't be reached"); + } + RewriteIntrinsicMacro(macro_name, res, token_list, tokenizer, rewriter, + prev, tok); } return std::move(rewriter).Build(); } -base::StatusOr -PerfettoSqlPreprocessor::ParseMacroInvocation( +base::StatusOr> PerfettoSqlPreprocessor::ParseTokenList( SqliteTokenizer& tokenizer, SqliteTokenizer::Token& tok, - const SqliteTokenizer::Token& name_token, - const std::unordered_map& arg_bindings) { - if (name_token.token_type == SqliteTokenType::TK_VARIABLE) { - // TODO(b/290185551): add a link to macro documentation. - return ErrorAtToken(tokenizer, name_token, - "Macro name cannot contain a variable"); - } - if (name_token.token_type != SqliteTokenType::TK_ID) { - // TODO(b/290185551): add a link to macro documentation. - return ErrorAtToken(tokenizer, name_token, "Macro invocation is invalid"); - } - - // Get the opening left parenthesis. - tok = tokenizer.NextNonWhitespace(); + const std::unordered_map& bindings) { if (tok.token_type != SqliteTokenType::TK_LP) { - // TODO(b/290185551): add a link to macro documentation. - return ErrorAtToken(tokenizer, tok, "( expected to open macro invocation"); + return ErrorAtToken(tokenizer, tok, "( expected to open token list"); } + std::vector tokens; + bool has_more = true; + while (has_more) { + ASSIGN_OR_RETURN(InvocationArg invocation_arg, + ParseMacroInvocationArg(tokenizer, tok, !tokens.empty())); + if (invocation_arg.arg) { + ASSIGN_OR_RETURN(SqlSource res, + RewriteInternal(invocation_arg.arg.value(), bindings)); + tokens.emplace_back(std::move(res)); + } + has_more = invocation_arg.has_more; + } + return tokens; +} - std::string macro_name(name_token.str); +base::StatusOr PerfettoSqlPreprocessor::ExecuteMacroInvocation( + const SqliteTokenizer& tokenizer, + const SqliteTokenizer::Token& name_token, + const std::string& macro_name, + std::vector token_list) { Macro* macro = macros_->Find(macro_name); if (!macro) { // TODO(b/290185551): add a link to macro documentation. base::StackString<1024> err("Macro %s does not exist", macro_name.c_str()); return ErrorAtToken(tokenizer, name_token, err.c_str()); } - if (seen_macros_.count(macro_name)) { // TODO(b/290185551): add a link to macro documentation. return ErrorAtToken(tokenizer, name_token, "Macros cannot be recursive or mutually recursive"); } - - std::unordered_map inner_bindings; - for (bool has_more = true; has_more;) { - base::StatusOr source_or = - ParseMacroInvocationArg(tokenizer, tok, !inner_bindings.empty()); - RETURN_IF_ERROR(source_or.status()); - if (source_or->arg) { - base::StatusOr res = - RewriteInternal(source_or->arg.value(), arg_bindings); - RETURN_IF_ERROR(res.status()); - if (macro->args.size() <= inner_bindings.size()) { - // TODO(lalitm): add a link to macro documentation. - return ErrorAtToken(tokenizer, name_token, - "Macro invoked with too many args"); - } - inner_bindings.emplace(macro->args[inner_bindings.size()], *res); - } - has_more = source_or->has_more; - } - - if (inner_bindings.size() < macro->args.size()) { + if (token_list.size() < macro->args.size()) { // TODO(lalitm): add a link to macro documentation. return ErrorAtToken(tokenizer, name_token, "Macro invoked with too few args"); } + if (token_list.size() > macro->args.size()) { + // TODO(lalitm): add a link to macro documentation. + return ErrorAtToken(tokenizer, name_token, + "Macro invoked with too many args"); + } + std::unordered_map inner_bindings; + for (auto& t : token_list) { + inner_bindings.emplace(macro->args[inner_bindings.size()], std::move(t)); + } PERFETTO_CHECK(inner_bindings.size() == macro->args.size()); - return MacroInvocation{macro, inner_bindings}; + + seen_macros_.emplace(macro->name); + ASSIGN_OR_RETURN(SqlSource res, RewriteInternal(macro->sql, inner_bindings)); + seen_macros_.erase(macro->name); + return res; } -base::StatusOr -PerfettoSqlPreprocessor::ParseMacroInvocationArg(SqliteTokenizer& tokenizer, - SqliteTokenizer::Token& tok, - bool has_prev_args) { - uint32_t nested_parens = 0; - bool seen_token_in_arg = false; - auto start = tokenizer.NextNonWhitespace(); - for (tok = start;; tok = tokenizer.NextNonWhitespace()) { - if (tok.IsTerminal()) { - if (tok.token_type == SqliteTokenType::TK_SEMI) { - // TODO(b/290185551): add a link to macro documentation. - return ErrorAtToken(tokenizer, tok, - "Semi-colon is not allowed in macro invocation"); - } - // TODO(b/290185551): add a link to macro documentation. - return ErrorAtToken(tokenizer, tok, "Macro invocation not complete"); - } +base::StatusOr> +PerfettoSqlPreprocessor::ExecuteTokenZipJoin( + const SqliteTokenizer& tokenizer, + const SqliteTokenizer::Token& name_token, + std::vector token_list, + bool prefixed) { + if (token_list.size() != 4) { + return ErrorAtToken(tokenizer, name_token, + "token_zip_join: must have exactly four args"); + } - bool is_arg_terminator = tok.token_type == SqliteTokenType::TK_RP || - tok.token_type == SqliteTokenType::TK_COMMA; - if (nested_parens == 0 && is_arg_terminator) { - bool token_required = - has_prev_args || tok.token_type != SqliteTokenType::TK_RP; - if (!seen_token_in_arg && token_required) { - // TODO(b/290185551): add a link to macro documentation. - return ErrorAtToken(tokenizer, tok, "Macro arg is empty"); - } - return InvocationArg{ - seen_token_in_arg ? std::make_optional(tokenizer.Substr(start, tok)) - : std::optional(std::nullopt), - tok.token_type == SqliteTokenType::TK_COMMA, - }; - } - seen_token_in_arg = true; + SqliteTokenizer first_tokenizer(std::move(token_list[0])); + SqliteTokenizer::Token inner_tok = first_tokenizer.NextNonWhitespace(); + if (inner_tok.token_type == SqliteTokenType::TK_VARIABLE) { + return {std::nullopt}; + } + ASSIGN_OR_RETURN(std::vector first_sources, + ParseTokenList(first_tokenizer, inner_tok, {})); - if (tok.token_type == SqliteTokenType::TK_LP) { - nested_parens++; - continue; + SqliteTokenizer second_tokenizer(std::move(token_list[1])); + inner_tok = second_tokenizer.NextNonWhitespace(); + if (inner_tok.token_type == SqliteTokenType::TK_VARIABLE) { + return {std::nullopt}; + } + ASSIGN_OR_RETURN(std::vector second_sources, + ParseTokenList(second_tokenizer, inner_tok, {})); + + SqliteTokenizer name_tokenizer(token_list[2]); + inner_tok = name_tokenizer.NextNonWhitespace(); + if (inner_tok.token_type == SqliteTokenType::TK_VARIABLE) { + return {std::nullopt}; + } + + size_t zip_count = std::min(first_sources.size(), second_sources.size()); + std::vector res; + for (uint32_t i = 0; i < zip_count; ++i) { + ASSIGN_OR_RETURN( + SqlSource invocation_res, + ExecuteMacroInvocation(tokenizer, name_token, token_list[2].sql(), + {first_sources[i], second_sources[i]})); + res.push_back(invocation_res.sql()); + } + + if (res.empty()) { + return {SqlSource::FromTraceProcessorImplementation("")}; + } + + std::string zipped = base::Join(res, " " + token_list[3].sql() + " "); + if (prefixed) { + zipped = " " + token_list[3].sql() + " " + zipped; + } + return {SqlSource::FromTraceProcessorImplementation(zipped)}; +} + +base::StatusOr> +PerfettoSqlPreprocessor::ExecuteTokenApply( + const SqliteTokenizer& tokenizer, + const SqliteTokenizer::Token& name_token, + std::vector token_list) { + if (token_list.size() != 3) { + return ErrorAtToken(tokenizer, name_token, + "token_apply: must have exactly three args"); + } + + SqliteTokenizer arg_list_tokenizer(token_list[0]); + SqliteTokenizer::Token inner_tok = arg_list_tokenizer.NextNonWhitespace(); + if (inner_tok.token_type == SqliteTokenType::TK_VARIABLE) { + return {std::nullopt}; + } + ASSIGN_OR_RETURN(std::vector arg_list_sources, + ParseTokenList(arg_list_tokenizer, inner_tok, {})); + + SqliteTokenizer name_tokenizer(token_list[1]); + inner_tok = name_tokenizer.NextNonWhitespace(); + if (inner_tok.token_type == SqliteTokenType::TK_VARIABLE) { + return {std::nullopt}; + } + + std::vector res; + for (const auto& arg_list_source : arg_list_sources) { + SqliteTokenizer args_tokenizer(arg_list_source); + inner_tok = args_tokenizer.NextNonWhitespace(); + if (inner_tok.token_type == SqliteTokenType::TK_VARIABLE) { + return {std::nullopt}; } - if (tok.token_type == SqliteTokenType::TK_RP) { - nested_parens--; - continue; + + ASSIGN_OR_RETURN(std::vector args_sources, + ParseTokenList(args_tokenizer, inner_tok, {})); + + ASSIGN_OR_RETURN(SqlSource invocation_res, + ExecuteMacroInvocation(tokenizer, name_token, + token_list[1].sql(), args_sources)); + res.push_back(invocation_res.sql()); + } + + if (res.empty()) { + return {SqlSource::FromTraceProcessorImplementation("")}; + } + + std::string zipped = base::Join(res, " " + token_list[2].sql() + " "); + return {SqlSource::FromTraceProcessorImplementation(zipped)}; +} + +base::StatusOr> +PerfettoSqlPreprocessor::ExecuteTokenMapJoin( + const SqliteTokenizer& tokenizer, + const SqliteTokenizer::Token& name_token, + std::vector token_list) { + if (token_list.size() != 3) { + return ErrorAtToken(tokenizer, name_token, + "token_map_join: must have exactly three args"); + } + + SqliteTokenizer arg_list_tokenizer(token_list[0]); + SqliteTokenizer::Token inner_tok = arg_list_tokenizer.NextNonWhitespace(); + if (inner_tok.token_type == SqliteTokenType::TK_VARIABLE) { + return {std::nullopt}; + } + ASSIGN_OR_RETURN(std::vector arg_list_sources, + ParseTokenList(arg_list_tokenizer, inner_tok, {})); + + SqliteTokenizer name_tokenizer(token_list[1]); + inner_tok = name_tokenizer.NextNonWhitespace(); + if (inner_tok.token_type == SqliteTokenType::TK_VARIABLE) { + return {std::nullopt}; + } + + std::vector res; + for (const auto& arg_list_source : arg_list_sources) { + SqliteTokenizer args_tokenizer(arg_list_source); + inner_tok = args_tokenizer.NextNonWhitespace(); + if (inner_tok.token_type == SqliteTokenType::TK_VARIABLE) { + return {std::nullopt}; } + + ASSIGN_OR_RETURN( + SqlSource invocation_res, + ExecuteMacroInvocation(tokenizer, name_token, token_list[1].sql(), + {arg_list_source})); + res.push_back(invocation_res.sql()); } + + if (res.empty()) { + return {SqlSource::FromTraceProcessorImplementation("")}; + } + + std::string zipped = base::Join(res, " " + token_list[2].sql() + " "); + return {SqlSource::FromTraceProcessorImplementation(zipped)}; } -base::Status PerfettoSqlPreprocessor::ErrorAtToken( +base::StatusOr> +PerfettoSqlPreprocessor::ExecuteTokenMapJoinWithCapture( const SqliteTokenizer& tokenizer, - const SqliteTokenizer::Token& token, - const char* error) { - std::string traceback = tokenizer.AsTraceback(token); - return base::ErrStatus("%s%s", traceback.c_str(), error); + const SqliteTokenizer::Token& name_token, + std::vector token_list) { + if (token_list.size() != 4) { + return ErrorAtToken( + tokenizer, name_token, + "token_map_join_with_capture: must have exactly four args"); + } + + SqliteTokenizer arg_list_tokenizer(token_list[0]); + SqliteTokenizer::Token inner_tok = arg_list_tokenizer.NextNonWhitespace(); + if (inner_tok.token_type == SqliteTokenType::TK_VARIABLE) { + return {std::nullopt}; + } + ASSIGN_OR_RETURN(std::vector arg_list_sources, + ParseTokenList(arg_list_tokenizer, inner_tok, {})); + + SqliteTokenizer name_tokenizer(token_list[1]); + inner_tok = name_tokenizer.NextNonWhitespace(); + if (inner_tok.token_type == SqliteTokenType::TK_VARIABLE) { + return {std::nullopt}; + } + + SqliteTokenizer capture_tokenizer(token_list[2]); + inner_tok = capture_tokenizer.NextNonWhitespace(); + if (inner_tok.token_type == SqliteTokenType::TK_VARIABLE) { + return {std::nullopt}; + } + ASSIGN_OR_RETURN(std::vector captured_args, + ParseTokenList(capture_tokenizer, inner_tok, {})); + + std::vector res; + for (const auto& arg_list_source : arg_list_sources) { + SqliteTokenizer args_tokenizer(arg_list_source); + inner_tok = args_tokenizer.NextNonWhitespace(); + if (inner_tok.token_type == SqliteTokenType::TK_VARIABLE) { + return {std::nullopt}; + } + + std::vector macro_args{arg_list_source}; + macro_args.insert(macro_args.end(), captured_args.begin(), + captured_args.end()); + ASSIGN_OR_RETURN( + SqlSource invocation_res, + ExecuteMacroInvocation(tokenizer, name_token, token_list[1].sql(), + std::move(macro_args))); + res.push_back(invocation_res.sql()); + } + + if (res.empty()) { + return {SqlSource::FromTraceProcessorImplementation("")}; + } + + std::string zipped = base::Join(res, " " + token_list[3].sql() + " "); + return {SqlSource::FromTraceProcessorImplementation(zipped)}; } -} // namespace trace_processor -} // namespace perfetto +} // namespace perfetto::trace_processor diff --git a/src/trace_processor/perfetto_sql/engine/perfetto_sql_preprocessor.h b/src/trace_processor/perfetto_sql/engine/perfetto_sql_preprocessor.h index a1d15e340e..1ccd0a32d7 100644 --- a/src/trace_processor/perfetto_sql/engine/perfetto_sql_preprocessor.h +++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_preprocessor.h @@ -23,13 +23,13 @@ #include #include +#include "perfetto/base/status.h" #include "perfetto/ext/base/flat_hash_map.h" #include "perfetto/ext/base/status_or.h" #include "src/trace_processor/sqlite/sql_source.h" #include "src/trace_processor/sqlite/sqlite_tokenizer.h" -namespace perfetto { -namespace trace_processor { +namespace perfetto::trace_processor { // Preprocessor for PerfettoSQL statements. The main responsiblity of this // class is to perform similar functions to the C/C++ preprocessor (e.g. @@ -68,31 +68,41 @@ class PerfettoSqlPreprocessor { SqlSource& statement() { return *statement_; } private: - struct MacroInvocation { - const Macro* macro; - std::unordered_map arg_bindings; - }; - struct InvocationArg { - std::optional arg; - bool has_more; - }; - - base::Status ErrorAtToken(const SqliteTokenizer& tokenizer, - const SqliteTokenizer::Token& token, - const char* error); base::StatusOr RewriteInternal( const SqlSource&, const std::unordered_map& arg_bindings); - base::StatusOr ParseMacroInvocation( + base::StatusOr> ParseTokenList( SqliteTokenizer& tokenizer, SqliteTokenizer::Token& token, - const SqliteTokenizer::Token& name_token, const std::unordered_map& arg_bindings); - base::StatusOr ParseMacroInvocationArg( - SqliteTokenizer& tokenizer, - SqliteTokenizer::Token& token, - bool has_prev_args); + + base::StatusOr ExecuteMacroInvocation( + const SqliteTokenizer& tokenizer, + const SqliteTokenizer::Token& name_token, + const std::string& macro_name, + std::vector token_list); + + base::StatusOr> ExecuteTokenZipJoin( + const SqliteTokenizer& tokenizer, + const SqliteTokenizer::Token& name_token, + std::vector token_list, + bool prefixed); + + base::StatusOr> ExecuteTokenApply( + const SqliteTokenizer& tokenizer, + const SqliteTokenizer::Token& name_token, + std::vector token_list); + + base::StatusOr> ExecuteTokenMapJoin( + const SqliteTokenizer& tokenizer, + const SqliteTokenizer::Token& name_token, + std::vector token_list); + + base::StatusOr> ExecuteTokenMapJoinWithCapture( + const SqliteTokenizer& tokenizer, + const SqliteTokenizer::Token& name_token, + std::vector token_list); SqliteTokenizer global_tokenizer_; const base::FlatHashMap* macros_ = nullptr; @@ -101,7 +111,6 @@ class PerfettoSqlPreprocessor { base::Status status_; }; -} // namespace trace_processor -} // namespace perfetto +} // namespace perfetto::trace_processor #endif // SRC_TRACE_PROCESSOR_PERFETTO_SQL_ENGINE_PERFETTO_SQL_PREPROCESSOR_H_ diff --git a/src/trace_processor/perfetto_sql/engine/perfetto_sql_preprocessor_unittest.cc b/src/trace_processor/perfetto_sql/engine/perfetto_sql_preprocessor_unittest.cc index 104ee3d92a..57b6a8ce4f 100644 --- a/src/trace_processor/perfetto_sql/engine/perfetto_sql_preprocessor_unittest.cc +++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_preprocessor_unittest.cc @@ -16,17 +16,18 @@ #include "src/trace_processor/perfetto_sql/engine/perfetto_sql_preprocessor.h" -#include #include #include "perfetto/ext/base/flat_hash_map.h" #include "src/trace_processor/perfetto_sql/engine/perfetto_sql_test_utils.h" +#include "src/trace_processor/sqlite/sql_source.h" #include "test/gtest_and_gmock.h" -namespace perfetto { -namespace trace_processor { +namespace perfetto::trace_processor { namespace { +using ::testing::HasSubstr; + using Macro = PerfettoSqlPreprocessor::Macro; class PerfettoSqlPreprocessorUnittest : public ::testing::Test { @@ -161,6 +162,265 @@ TEST_F(PerfettoSqlPreprocessorUnittest, NestedMacro) { ASSERT_EQ(preprocessor.statement().sql(), "SELECT 1"); } +TEST_F(PerfettoSqlPreprocessorUnittest, Stringify) { + auto sf = SqlSource::FromExecuteQuery( + "CREATE PERFETTO MACRO sf(a Expr, b Expr) Returns Expr AS " + "__intrinsic_stringify!($a + $b)"); + macros_.Insert("sf", Macro{ + false, + "sf", + {"a", "b"}, + FindSubstr(sf, "__intrinsic_stringify!($a + $b)"), + }); + auto bar = SqlSource::FromExecuteQuery( + "CREATE PERFETTO MACRO bar(a Expr, b Expr) Returns Expr AS " + "sf!((SELECT $a), (SELECT $b))"); + macros_.Insert("bar", Macro{ + false, + "bar", + {"a", "b"}, + FindSubstr(bar, "sf!((SELECT $a), (SELECT $b))"), + }); + auto baz = SqlSource::FromExecuteQuery( + "CREATE PERFETTO MACRO baz(a Expr, b Expr) Returns Expr AS " + "SELECT bar!((SELECT $a), (SELECT $b))"); + macros_.Insert("baz", Macro{ + false, + "baz", + {"a", "b"}, + FindSubstr(baz, "bar!((SELECT $a), (SELECT $b))"), + }); + + { + auto source = + SqlSource::FromExecuteQuery("__intrinsic_stringify!(foo bar baz)"); + PerfettoSqlPreprocessor preprocessor(source, macros_); + ASSERT_TRUE(preprocessor.NextStatement()) + << preprocessor.status().message(); + ASSERT_EQ(preprocessor.statement().sql(), "'foo bar baz'"); + ASSERT_FALSE(preprocessor.NextStatement()); + } + + { + auto source = SqlSource::FromExecuteQuery("sf!(1, 2)"); + PerfettoSqlPreprocessor preprocessor(source, macros_); + ASSERT_TRUE(preprocessor.NextStatement()) + << preprocessor.status().message(); + ASSERT_EQ(preprocessor.statement().sql(), "'1 + 2'"); + ASSERT_FALSE(preprocessor.NextStatement()); + } + + { + auto source = SqlSource::FromExecuteQuery("baz!(1, 2)"); + PerfettoSqlPreprocessor preprocessor(source, macros_); + ASSERT_TRUE(preprocessor.NextStatement()) + << preprocessor.status().message(); + ASSERT_EQ(preprocessor.statement().sql(), + "'(SELECT (SELECT 1)) + (SELECT (SELECT 2))'"); + ASSERT_FALSE(preprocessor.NextStatement()); + } + + { + auto source = SqlSource::FromExecuteQuery("__intrinsic_stringify!()"); + PerfettoSqlPreprocessor preprocessor(source, macros_); + ASSERT_FALSE(preprocessor.NextStatement()); + ASSERT_EQ(preprocessor.status().message(), + "Traceback (most recent call last):\n" + " File \"stdin\" line 1 col 1\n" + " __intrinsic_stringify!()\n" + " ^\n" + "stringify: stringify must not be empty"); + } +} + +TEST_F(PerfettoSqlPreprocessorUnittest, ZipJoin) { + auto foo = SqlSource::FromExecuteQuery( + "CREATE PERFETTO MACRO G(a Expr, b Expr) Returns Expr AS $a AS $b"); + macros_.Insert("G", Macro{ + false, + "G", + {"a", "b"}, + FindSubstr(foo, "$a AS $b"), + }); + + auto zj = SqlSource::FromExecuteQuery( + "CREATE PERFETTO MACRO ZJ(a Expr, b Expr, c Expr, d Expr) Returns Expr " + "AS __intrinsic_token_zip_join!($a, $b, $c, $d)"); + macros_.Insert( + "ZJ", Macro{ + false, + "ZJ", + {"a", "b", "c", "d"}, + FindSubstr(zj, "__intrinsic_token_zip_join!($a, $b, $c, $d)"), + }); + { + auto source = SqlSource::FromExecuteQuery( + "__intrinsic_token_zip_join!((foo, bar), (baz, bat), G, AND)"); + PerfettoSqlPreprocessor preprocessor(source, macros_); + ASSERT_TRUE(preprocessor.NextStatement()) + << preprocessor.status().message(); + ASSERT_EQ(preprocessor.statement().sql(), "foo AS baz AND bar AS bat"); + ASSERT_FALSE(preprocessor.NextStatement()); + } + { + auto source = SqlSource::FromExecuteQuery( + "__intrinsic_token_zip_join!((foo, bar), (baz, bat, bada), G, AND)"); + PerfettoSqlPreprocessor preprocessor(source, macros_); + ASSERT_TRUE(preprocessor.NextStatement()) + << preprocessor.status().message(); + ASSERT_EQ(preprocessor.statement().sql(), "foo AS baz AND bar AS bat"); + ASSERT_FALSE(preprocessor.NextStatement()); + } + { + auto source = SqlSource::FromExecuteQuery( + "__intrinsic_token_zip_join!((foo, bar), (baz, bat, bada), G, " + "__intrinsic_token_comma!())"); + PerfettoSqlPreprocessor preprocessor(source, macros_); + ASSERT_TRUE(preprocessor.NextStatement()) + << preprocessor.status().message(); + ASSERT_EQ(preprocessor.statement().sql(), "foo AS baz , bar AS bat"); + ASSERT_FALSE(preprocessor.NextStatement()); + } + { + auto source = SqlSource::FromExecuteQuery( + "ZJ!((foo, bar), (baz, bat, bada), G, __intrinsic_token_comma!())"); + PerfettoSqlPreprocessor preprocessor(source, macros_); + ASSERT_TRUE(preprocessor.NextStatement()) + << preprocessor.status().message(); + ASSERT_EQ(preprocessor.statement().sql(), "foo AS baz , bar AS bat"); + ASSERT_FALSE(preprocessor.NextStatement()); + } +} + +TEST_F(PerfettoSqlPreprocessorUnittest, TokenApply) { + auto foo = SqlSource::FromExecuteQuery( + "CREATE PERFETTO MACRO G(a Expr, b Expr) Returns Expr AS $a AS $b"); + macros_.Insert("G", Macro{ + false, + "G", + {"a", "b"}, + FindSubstr(foo, "$a AS $b"), + }); + + auto tp = SqlSource::FromExecuteQuery( + "CREATE PERFETTO MACRO TokApply(a Expr, b Expr, c Expr) Returns Expr " + "AS __intrinsic_token_apply!($a, $b, $c)"); + macros_.Insert("TokApply", + Macro{ + false, + "TokApply", + {"a", "b", "c"}, + FindSubstr(tp, "__intrinsic_token_apply!($a, $b, $c)"), + }); + { + auto source = + SqlSource::FromExecuteQuery("__intrinsic_token_apply!((), G, AND)"); + PerfettoSqlPreprocessor preprocessor(source, macros_); + ASSERT_TRUE(preprocessor.NextStatement()) + << preprocessor.status().message(); + ASSERT_EQ(preprocessor.statement().sql(), ""); + ASSERT_FALSE(preprocessor.NextStatement()); + } + { + auto source = SqlSource::FromExecuteQuery( + "__intrinsic_token_apply!(((foo, bar)), G, AND)"); + PerfettoSqlPreprocessor preprocessor(source, macros_); + ASSERT_TRUE(preprocessor.NextStatement()) + << preprocessor.status().message(); + ASSERT_EQ(preprocessor.statement().sql(), "foo AS bar"); + ASSERT_FALSE(preprocessor.NextStatement()); + } + { + auto source = SqlSource::FromExecuteQuery( + "__intrinsic_token_apply!(((foo, bar), (baz, bat)), G, AND)"); + PerfettoSqlPreprocessor preprocessor(source, macros_); + ASSERT_TRUE(preprocessor.NextStatement()) + << preprocessor.status().message(); + ASSERT_EQ(preprocessor.statement().sql(), "foo AS bar AND baz AS bat"); + ASSERT_FALSE(preprocessor.NextStatement()); + } + { + auto source = SqlSource::FromExecuteQuery( + "__intrinsic_token_apply!(((foo, bar), (baz, bat, bada)), G, AND)"); + PerfettoSqlPreprocessor preprocessor(source, macros_); + ASSERT_FALSE(preprocessor.NextStatement()); + ASSERT_THAT(preprocessor.status().message(), HasSubstr("too many args")); + } + { + auto source = SqlSource::FromExecuteQuery( + "__intrinsic_token_apply!(((foo, bar), (baz)), G, AND)"); + PerfettoSqlPreprocessor preprocessor(source, macros_); + ASSERT_FALSE(preprocessor.NextStatement()); + ASSERT_THAT(preprocessor.status().message(), HasSubstr("too few args")); + } + { + auto source = SqlSource::FromExecuteQuery( + "TokApply!(((foo, bar), (baz, bat)), G, AND)"); + PerfettoSqlPreprocessor preprocessor(source, macros_); + ASSERT_TRUE(preprocessor.NextStatement()) + << preprocessor.status().message(); + ASSERT_EQ(preprocessor.statement().sql(), "foo AS bar AND baz AS bat"); + ASSERT_FALSE(preprocessor.NextStatement()); + } +} + +TEST_F(PerfettoSqlPreprocessorUnittest, TokenMapJoin) { + auto foo = SqlSource::FromExecuteQuery( + "CREATE PERFETTO MACRO G(a Expr) Returns Expr AS baza.$a"); + macros_.Insert("G", Macro{ + false, + "G", + {"a"}, + FindSubstr(foo, "baza.$a"), + }); + + auto tp = SqlSource::FromExecuteQuery( + "CREATE PERFETTO MACRO TokMapJoin(a Expr, b Expr, c Expr) Returns Expr " + "AS __intrinsic_token_map_join!($a, $b, $c)"); + macros_.Insert("TokMapJoin", + Macro{ + false, + "TokMapJoin", + {"a", "b", "c"}, + FindSubstr(tp, "__intrinsic_token_map_join!($a, $b, $c)"), + }); + { + auto source = + SqlSource::FromExecuteQuery("__intrinsic_token_map_join!((), G, AND)"); + PerfettoSqlPreprocessor preprocessor(source, macros_); + ASSERT_TRUE(preprocessor.NextStatement()) + << preprocessor.status().message(); + ASSERT_EQ(preprocessor.statement().sql(), ""); + ASSERT_FALSE(preprocessor.NextStatement()); + } + { + auto source = SqlSource::FromExecuteQuery( + "__intrinsic_token_map_join!((foo), G, AND)"); + PerfettoSqlPreprocessor preprocessor(source, macros_); + ASSERT_TRUE(preprocessor.NextStatement()) + << preprocessor.status().message(); + ASSERT_EQ(preprocessor.statement().sql(), "baza.foo"); + ASSERT_FALSE(preprocessor.NextStatement()); + } + { + auto source = SqlSource::FromExecuteQuery( + "__intrinsic_token_map_join!((foo, baz), G, AND)"); + PerfettoSqlPreprocessor preprocessor(source, macros_); + ASSERT_TRUE(preprocessor.NextStatement()) + << preprocessor.status().message(); + ASSERT_EQ(preprocessor.statement().sql(), "baza.foo AND baza.baz"); + ASSERT_FALSE(preprocessor.NextStatement()); + } + { + auto source = + SqlSource::FromExecuteQuery("TokMapJoin!((foo, bar), G, AND)"); + PerfettoSqlPreprocessor preprocessor(source, macros_); + ASSERT_TRUE(preprocessor.NextStatement()) + << preprocessor.status().message(); + ASSERT_EQ(preprocessor.statement().sql(), "baza.foo AND baza.bar"); + ASSERT_FALSE(preprocessor.NextStatement()); + } +} + } // namespace -} // namespace trace_processor -} // namespace perfetto +} // namespace perfetto::trace_processor diff --git a/src/trace_processor/perfetto_sql/engine/perfetto_sql_test_utils.h b/src/trace_processor/perfetto_sql/engine/perfetto_sql_test_utils.h index 5a46950900..78cc4dc053 100644 --- a/src/trace_processor/perfetto_sql/engine/perfetto_sql_test_utils.h +++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_test_utils.h @@ -61,6 +61,17 @@ constexpr bool operator==(const PerfettoSqlParser::CreateMacro& a, std::tie(b.replace, b.name, b.sql, b.args); } +constexpr bool operator==(const PerfettoSqlParser::CreateIndex& a, + const PerfettoSqlParser::CreateIndex& b) { + return std::tie(a.replace, a.name, a.table_name, a.col_names) == + std::tie(b.replace, b.name, b.table_name, b.col_names); +} + +constexpr bool operator==(const PerfettoSqlParser::DropIndex& a, + const PerfettoSqlParser::DropIndex& b) { + return std::tie(a.name, a.table_name) == std::tie(b.name, b.table_name); +} + inline std::ostream& operator<<(std::ostream& stream, const SqlSource& sql) { return stream << "SqlSource(sql=" << testing::PrintToString(sql.sql()) << ")"; } diff --git a/src/trace_processor/perfetto_sql/engine/runtime_table_function.cc b/src/trace_processor/perfetto_sql/engine/runtime_table_function.cc index 12faa70e12..a2de04aa50 100644 --- a/src/trace_processor/perfetto_sql/engine/runtime_table_function.cc +++ b/src/trace_processor/perfetto_sql/engine/runtime_table_function.cc @@ -51,14 +51,14 @@ auto CreateTableStrFromState(RuntimeTableFunctionModule::State* state) { columns.reserve(state->return_values.size()); for (const auto& ret : state->return_values) { columns.emplace_back(ret.name().ToStdString() + " " + - sqlite::utils::SqlValueTypeToString( + sqlite::utils::SqlValueTypeToSqliteTypeName( sql_argument::TypeToSqlValueType(ret.type()))); } for (const auto& arg : state->prototype.arguments) { // Add the "in_" prefix to every argument param to avoid clashes between the // output and input parameters. columns.emplace_back("in_" + arg.name().ToStdString() + " " + - sqlite::utils::SqlValueTypeToString( + sqlite::utils::SqlValueTypeToSqliteTypeName( sql_argument::TypeToSqlValueType(arg.type())) + " HIDDEN"); } diff --git a/src/trace_processor/perfetto_sql/engine/table_pointer_module.cc b/src/trace_processor/perfetto_sql/engine/table_pointer_module.cc index 9ef53b1243..f8ee59f1e3 100644 --- a/src/trace_processor/perfetto_sql/engine/table_pointer_module.cc +++ b/src/trace_processor/perfetto_sql/engine/table_pointer_module.cc @@ -178,8 +178,9 @@ int TablePointerModule::Filter(sqlite3_vtab_cursor* cur, c->table->columns().begin(), c->table->columns().end(), [&tok](const ColumnLegacy& col) { return col.name() == tok; }); if (it == c->table->columns().end()) { - return sqlite::utils::SetError(c->pVtab, - "column does not exist in table"); + base::StackString<128> err("column '%s' does not exist in table", + sqlite3_value_text(argv[i])); + return sqlite::utils::SetError(c->pVtab, err.c_str()); } c->bound_col_to_table_index[c->col_count++] = static_cast(std::distance(c->table->columns().begin(), it)); diff --git a/src/trace_processor/perfetto_sql/intrinsics/functions/BUILD.gn b/src/trace_processor/perfetto_sql/intrinsics/functions/BUILD.gn index b749026ccd..53df4f7b69 100644 --- a/src/trace_processor/perfetto_sql/intrinsics/functions/BUILD.gn +++ b/src/trace_processor/perfetto_sql/intrinsics/functions/BUILD.gn @@ -26,12 +26,16 @@ source_set("functions") { "create_function.h", "create_view_function.cc", "create_view_function.h", - "dfs.cc", - "dfs.h", "dominator_tree.cc", "dominator_tree.h", + "graph_scan.cc", + "graph_scan.h", + "graph_traversal.cc", + "graph_traversal.h", "import.cc", "import.h", + "interval_intersect.cc", + "interval_intersect.h", "layout_functions.cc", "layout_functions.h", "math.cc", @@ -46,6 +50,8 @@ source_set("functions") { "structural_tree_partition.h", "to_ftrace.cc", "to_ftrace.h", + "type_builders.cc", + "type_builders.h", "utils.h", "window_functions.h", ] @@ -77,6 +83,7 @@ source_set("functions") { "../../../util:sql_argument", "../../../util:stdlib", "../../engine", + "../types", ] public_deps = [ ":interface" ] } diff --git a/src/trace_processor/perfetto_sql/intrinsics/functions/clock_functions.h b/src/trace_processor/perfetto_sql/intrinsics/functions/clock_functions.h index a30993d8eb..93df66d8e9 100644 --- a/src/trace_processor/perfetto_sql/intrinsics/functions/clock_functions.h +++ b/src/trace_processor/perfetto_sql/intrinsics/functions/clock_functions.h @@ -188,7 +188,7 @@ base::Status ToTimecode::Run(void*, int64_t mm = ss / 60; ss = ss % 60; - int64_t hh = mm % 60; + int64_t hh = mm / 60; mm = mm % 60; base::StackString<64> buf("%02" PRId64 ":%02" PRId64 ":%02" PRId64 diff --git a/src/trace_processor/perfetto_sql/intrinsics/functions/dfs.cc b/src/trace_processor/perfetto_sql/intrinsics/functions/dfs.cc deleted file mode 100644 index 526455e25b..0000000000 --- a/src/trace_processor/perfetto_sql/intrinsics/functions/dfs.cc +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/trace_processor/perfetto_sql/intrinsics/functions/dfs.h" - -#include -#include -#include -#include -#include -#include - -#include "perfetto/base/logging.h" -#include "perfetto/public/compiler.h" -#include "src/trace_processor/perfetto_sql/intrinsics/functions/tables_py.h" -#include "src/trace_processor/sqlite/bindings/sqlite_aggregate_function.h" -#include "src/trace_processor/sqlite/bindings/sqlite_result.h" - -namespace perfetto::trace_processor { -namespace tables { -DfsTable::~DfsTable() = default; -} // namespace tables - -namespace { - -using Destinations = std::vector; - -struct AggCtx : SqliteAggregateContext { - std::vector source_to_destinations_map; - std::optional start_id; -}; - -} // namespace - -void Dfs::Step(sqlite3_context* ctx, int argc, sqlite3_value** argv) { - if (argc != kArgCount) { - return sqlite::result::Error(ctx, "dfs: incorrect number of arguments"); - } - - auto& agg_ctx = AggCtx::GetOrCreateContextForStep(ctx); - auto source = static_cast(sqlite3_value_int64(argv[0])); - auto dest = static_cast(sqlite3_value_int64(argv[1])); - - // For every source node, create a mapping to the destination nodes. - agg_ctx.source_to_destinations_map.resize( - std::max(agg_ctx.source_to_destinations_map.size(), - std::max(source + 1, dest + 1))); - agg_ctx.source_to_destinations_map[source].push_back(dest); - if (PERFETTO_UNLIKELY(!agg_ctx.start_id)) { - agg_ctx.start_id = static_cast(sqlite3_value_int64(argv[2])); - } -} - -void Dfs::Final(sqlite3_context* ctx) { - auto raw_agg_ctx = AggCtx::GetContextOrNullForFinal(ctx); - auto table = std::make_unique(GetUserData(ctx)); - if (auto* agg_ctx = raw_agg_ctx.get(); agg_ctx) { - std::vector seen(agg_ctx->source_to_destinations_map.size()); - struct StackState { - uint32_t id; - std::optional parent_id; - }; - - std::vector stack{{*agg_ctx->start_id, std::nullopt}}; - while (!stack.empty()) { - StackState state = stack.back(); - stack.pop_back(); - - if (seen[state.id]) { - continue; - } - seen[state.id] = true; - - tables::DfsTable::Row row; - row.node_id = state.id; - row.parent_node_id = state.parent_id; - table->Insert(row); - - PERFETTO_DCHECK(state.id < agg_ctx->source_to_destinations_map.size()); - const auto& children = agg_ctx->source_to_destinations_map[state.id]; - for (auto it = children.rbegin(); it != children.rend(); ++it) { - stack.emplace_back(StackState{*it, state.id}); - } - } - } - return sqlite::result::RawPointer( - ctx, table.release(), "TABLE", - [](void* ptr) { delete static_cast(ptr); }); -} - -} // namespace perfetto::trace_processor diff --git a/src/trace_processor/perfetto_sql/intrinsics/functions/dfs.h b/src/trace_processor/perfetto_sql/intrinsics/functions/dfs.h deleted file mode 100644 index 66c66c6884..0000000000 --- a/src/trace_processor/perfetto_sql/intrinsics/functions/dfs.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_FUNCTIONS_DFS_H_ -#define SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_FUNCTIONS_DFS_H_ - -#include "src/trace_processor/containers/string_pool.h" -#include "src/trace_processor/sqlite/bindings/sqlite_aggregate_function.h" - -namespace perfetto::trace_processor { - -// An SQL aggregate-function which performs a DFS from a given start node in a -// graph and returns all the nodes which are reachable from the start node. -// -// Arguments: -// 1) |source_node_id|: a non-null uint32 corresponding to the source of edge. -// 2) |dest_node_id|: a non-null uint32 corresponding to the destination of -// the edge. -// 3) |start_node_id|: a non-null uint32 corresponding to of the start node in -// the graph from which reachability should be computed. -// -// Returns: -// A value table with the nodes reachable from the start node and their -// "parent" in the tree generated by the DFS. The schema of the table is -// (node_id int64_t, parent_node_id optional). -// -// Note: this function is not intended to be used directly from SQL: instead -// macros exist in the standard library, wrapping it and making it -// user-friendly. -struct Dfs : public SqliteAggregateFunction { - static constexpr char kName[] = "__intrinsic_dfs"; - static constexpr int kArgCount = 3; - using UserDataContext = StringPool; - - static void Step(sqlite3_context*, int argc, sqlite3_value** argv); - static void Final(sqlite3_context* ctx); -}; - -} // namespace perfetto::trace_processor - -#endif // SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_FUNCTIONS_DFS_H_ diff --git a/src/trace_processor/perfetto_sql/intrinsics/functions/graph_scan.cc b/src/trace_processor/perfetto_sql/intrinsics/functions/graph_scan.cc new file mode 100644 index 0000000000..1df93ad97e --- /dev/null +++ b/src/trace_processor/perfetto_sql/intrinsics/functions/graph_scan.cc @@ -0,0 +1,661 @@ +/* + * 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/functions/graph_scan.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "perfetto/base/logging.h" +#include "perfetto/base/status.h" +#include "perfetto/ext/base/status_or.h" +#include "perfetto/ext/base/string_utils.h" +#include "src/trace_processor/containers/string_pool.h" +#include "src/trace_processor/db/runtime_table.h" +#include "src/trace_processor/perfetto_sql/engine/function_util.h" +#include "src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h" +#include "src/trace_processor/perfetto_sql/intrinsics/types/array.h" +#include "src/trace_processor/perfetto_sql/intrinsics/types/node.h" +#include "src/trace_processor/perfetto_sql/intrinsics/types/row_dataframe.h" +#include "src/trace_processor/perfetto_sql/intrinsics/types/value.h" +#include "src/trace_processor/sqlite/bindings/sqlite_bind.h" +#include "src/trace_processor/sqlite/bindings/sqlite_column.h" +#include "src/trace_processor/sqlite/bindings/sqlite_function.h" +#include "src/trace_processor/sqlite/bindings/sqlite_result.h" +#include "src/trace_processor/sqlite/bindings/sqlite_stmt.h" +#include "src/trace_processor/sqlite/bindings/sqlite_type.h" +#include "src/trace_processor/sqlite/bindings/sqlite_value.h" +#include "src/trace_processor/sqlite/sql_source.h" +#include "src/trace_processor/sqlite/sqlite_engine.h" +#include "src/trace_processor/sqlite/sqlite_utils.h" +#include "src/trace_processor/util/status_macros.h" + +namespace perfetto::trace_processor { +namespace { + +base::Status InitToOutputAndStepTable(const perfetto_sql::RowDataframe& inits, + const perfetto_sql::Graph& graph, + RuntimeTable::Builder& step, + uint32_t& step_row_count, + RuntimeTable::Builder& out, + uint32_t& out_row_count) { + std::vector empty_edges; + auto get_edges = [&](uint32_t id) { + return id < graph.size() ? graph[id].outgoing_edges : empty_edges; + }; + + for (uint32_t i = 0; i < inits.size(); ++i) { + const auto* cell = inits.cells.data() + i * inits.column_names.size(); + auto id = static_cast(std::get(*cell)); + RETURN_IF_ERROR(out.AddInteger(0, id)); + out_row_count++; + for (uint32_t outgoing : get_edges(id)) { + step_row_count++; + RETURN_IF_ERROR(step.AddInteger(0, outgoing)); + } + for (uint32_t j = 1; j < inits.column_names.size(); ++j) { + switch (cell[j].index()) { + case perfetto_sql::ValueIndex(): + RETURN_IF_ERROR(out.AddNull(j)); + for ([[maybe_unused]] uint32_t _ : get_edges(id)) { + RETURN_IF_ERROR(step.AddNull(j)); + } + break; + case perfetto_sql::ValueIndex(): { + int64_t r = std::get(cell[j]); + RETURN_IF_ERROR(out.AddInteger(j, r)); + for ([[maybe_unused]] uint32_t _ : get_edges(id)) { + RETURN_IF_ERROR(step.AddInteger(j, r)); + } + break; + } + case perfetto_sql::ValueIndex(): { + double r = std::get(cell[j]); + RETURN_IF_ERROR(out.AddFloat(j, r)); + for ([[maybe_unused]] uint32_t _ : get_edges(id)) { + RETURN_IF_ERROR(step.AddFloat(j, r)); + } + break; + } + case perfetto_sql::ValueIndex(): { + const char* r = std::get(cell[j]).c_str(); + RETURN_IF_ERROR(out.AddText(j, r)); + for ([[maybe_unused]] uint32_t _ : get_edges(id)) { + RETURN_IF_ERROR(step.AddText(j, r)); + } + break; + } + default: + PERFETTO_FATAL("Invalid index"); + } + } + } + return base::OkStatus(); +} + +base::Status SqliteToOutputAndStepTable(SqliteEngine::PreparedStatement& stmt, + const perfetto_sql::Graph& graph, + RuntimeTable::Builder& step, + uint32_t& step_row_count, + RuntimeTable::Builder& out, + uint32_t& out_row_count) { + std::vector empty_edges; + auto get_edges = [&](uint32_t id) { + return id < graph.size() ? graph[id].outgoing_edges : empty_edges; + }; + + uint32_t col_count = sqlite::column::Count(stmt.sqlite_stmt()); + while (stmt.Step()) { + auto id = + static_cast(sqlite::column::Int64(stmt.sqlite_stmt(), 0)); + out_row_count++; + RETURN_IF_ERROR(out.AddInteger(0, id)); + for (uint32_t outgoing : get_edges(id)) { + step_row_count++; + RETURN_IF_ERROR(step.AddInteger(0, outgoing)); + } + for (uint32_t i = 1; i < col_count; ++i) { + switch (sqlite::column::Type(stmt.sqlite_stmt(), i)) { + case sqlite::Type::kNull: + RETURN_IF_ERROR(out.AddNull(i)); + for ([[maybe_unused]] uint32_t _ : get_edges(id)) { + RETURN_IF_ERROR(step.AddNull(i)); + } + break; + case sqlite::Type::kInteger: { + int64_t a = sqlite::column::Int64(stmt.sqlite_stmt(), i); + RETURN_IF_ERROR(out.AddInteger(i, a)); + for ([[maybe_unused]] uint32_t _ : get_edges(id)) { + RETURN_IF_ERROR(step.AddInteger(i, a)); + } + break; + } + case sqlite::Type::kText: { + const char* a = sqlite::column::Text(stmt.sqlite_stmt(), i); + RETURN_IF_ERROR(out.AddText(i, a)); + for ([[maybe_unused]] uint32_t _ : get_edges(id)) { + RETURN_IF_ERROR(step.AddText(i, a)); + } + break; + } + case sqlite::Type::kFloat: { + double a = sqlite::column::Double(stmt.sqlite_stmt(), i); + RETURN_IF_ERROR(out.AddFloat(i, a)); + for ([[maybe_unused]] uint32_t _ : get_edges(id)) { + RETURN_IF_ERROR(step.AddFloat(i, a)); + } + break; + } + case sqlite::Type::kBlob: + return base::ErrStatus("Unsupported blob type"); + } + } + } + return stmt.status(); +} + +base::StatusOr PrepareStatement( + PerfettoSqlEngine& engine, + const std::vector& cols, + const std::string& sql) { + std::vector select_cols; + std::vector bind_cols; + for (uint32_t i = 0; i < cols.size(); ++i) { + select_cols.emplace_back( + base::StackString<1024>("c%" PRIu32 " as %s", i, cols[i].c_str()) + .ToStdString()); + bind_cols.emplace_back(base::StackString<1024>( + "__intrinsic_table_ptr_bind(c%" PRIu32 ", '%s')", + i, cols[i].c_str()) + .ToStdString()); + } + + // TODO(lalitm): verify that the init aggregates line up correctly with the + // aggregation macro. + std::string raw_sql = + "(SELECT $cols FROM __intrinsic_table_ptr($var) WHERE $where)"; + raw_sql = base::ReplaceAll(raw_sql, "$cols", base::Join(select_cols, ",")); + raw_sql = base::ReplaceAll(raw_sql, "$where", base::Join(bind_cols, " AND ")); + std::string res = base::ReplaceAll(sql, "$table", raw_sql); + return engine.PrepareSqliteStatement( + SqlSource::FromTraceProcessorImplementation("SELECT * FROM " + res)); +} + +struct NodeState { + uint32_t depth = 0; + enum : uint8_t { + kUnvisited, + kWaitingForDescendants, + kDone, + } visit_state = kUnvisited; +}; + +struct DepthTable { + RuntimeTable::Builder builder; + uint32_t row_count = 0; +}; + +struct GraphAggregatingScanner { + base::StatusOr> Run(); + std::vector InitializeStateFromMaxNode(); + uint32_t DfsAndComputeMaxDepth(std::vector stack); + base::Status PushDownStartingAggregates(RuntimeTable::Builder& res, + uint32_t& res_row_count); + base::Status PushDownAggregates(SqliteEngine::PreparedStatement& agg_stmt, + uint32_t agg_col_count, + RuntimeTable::Builder& res, + uint32_t& res_row_count); + + const std::vector& GetEdges(uint32_t id) { + return id < graph.size() ? graph[id].outgoing_edges : empty_edges; + } + + PerfettoSqlEngine* engine; + StringPool* pool; + const perfetto_sql::Graph& graph; + const perfetto_sql::RowDataframe& inits; + std::string_view reduce; + std::vector empty_edges; + + std::vector state; + std::vector tables_per_depth; +}; + +std::vector GraphAggregatingScanner::InitializeStateFromMaxNode() { + std::vector stack; + auto nodes_size = static_cast(graph.size()); + for (uint32_t i = 0; i < inits.size(); ++i) { + auto start_id = static_cast( + std::get(inits.cells[i * inits.column_names.size()])); + nodes_size = std::max(nodes_size, static_cast(start_id) + 1); + for (uint32_t dest : GetEdges(start_id)) { + stack.emplace_back(static_cast(dest)); + } + } + state = std::vector(nodes_size); + return stack; +} + +uint32_t GraphAggregatingScanner::DfsAndComputeMaxDepth( + std::vector stack) { + uint32_t max_depth = 0; + while (!stack.empty()) { + uint32_t source_id = stack.back(); + NodeState& source = state[source_id]; + switch (source.visit_state) { + case NodeState::kUnvisited: + source.visit_state = NodeState::kWaitingForDescendants; + for (uint32_t dest_id : GetEdges(source_id)) { + stack.push_back(dest_id); + } + break; + case NodeState::kWaitingForDescendants: + stack.pop_back(); + source.visit_state = NodeState::kDone; + for (uint32_t dest_id : GetEdges(source_id)) { + PERFETTO_DCHECK(state[dest_id].visit_state == NodeState::kDone); + source.depth = std::max(state[dest_id].depth + 1, source.depth); + } + max_depth = std::max(max_depth, source.depth); + break; + case NodeState::kDone: + stack.pop_back(); + break; + } + } + return max_depth; +} + +base::Status GraphAggregatingScanner::PushDownAggregates( + SqliteEngine::PreparedStatement& agg_stmt, + uint32_t agg_col_count, + RuntimeTable::Builder& res, + uint32_t& res_row_count) { + while (agg_stmt.Step()) { + auto id = + static_cast(sqlite::column::Int64(agg_stmt.sqlite_stmt(), 0)); + res_row_count++; + RETURN_IF_ERROR(res.AddInteger(0, id)); + for (uint32_t outgoing : GetEdges(id)) { + auto& dt = tables_per_depth[state[outgoing].depth]; + dt.row_count++; + RETURN_IF_ERROR(dt.builder.AddInteger(0, outgoing)); + } + for (uint32_t i = 1; i < agg_col_count; ++i) { + switch (sqlite::column::Type(agg_stmt.sqlite_stmt(), i)) { + case sqlite::Type::kNull: + RETURN_IF_ERROR(res.AddNull(i)); + for (uint32_t outgoing : GetEdges(id)) { + auto& dt = tables_per_depth[state[outgoing].depth]; + RETURN_IF_ERROR(dt.builder.AddNull(i)); + } + break; + case sqlite::Type::kInteger: { + int64_t a = sqlite::column::Int64(agg_stmt.sqlite_stmt(), i); + RETURN_IF_ERROR(res.AddInteger(i, a)); + for (uint32_t outgoing : GetEdges(id)) { + auto& dt = tables_per_depth[state[outgoing].depth]; + RETURN_IF_ERROR(dt.builder.AddInteger(i, a)); + } + break; + } + case sqlite::Type::kText: { + const char* a = sqlite::column::Text(agg_stmt.sqlite_stmt(), i); + RETURN_IF_ERROR(res.AddText(i, a)); + for (uint32_t outgoing : GetEdges(id)) { + auto& dt = tables_per_depth[state[outgoing].depth]; + RETURN_IF_ERROR(dt.builder.AddText(i, a)); + } + break; + } + case sqlite::Type::kFloat: { + double a = sqlite::column::Double(agg_stmt.sqlite_stmt(), i); + RETURN_IF_ERROR(res.AddFloat(i, a)); + for (uint32_t outgoing : GetEdges(id)) { + auto& dt = tables_per_depth[state[outgoing].depth]; + RETURN_IF_ERROR(dt.builder.AddFloat(i, a)); + } + break; + } + case sqlite::Type::kBlob: + return base::ErrStatus("Unsupported blob type"); + } + } + } + return agg_stmt.status(); +} + +base::Status GraphAggregatingScanner::PushDownStartingAggregates( + RuntimeTable::Builder& res, + uint32_t& res_row_count) { + for (uint32_t i = 0; i < inits.size(); ++i) { + const auto* cell = inits.cells.data() + i * inits.column_names.size(); + auto id = static_cast(std::get(*cell)); + RETURN_IF_ERROR(res.AddInteger(0, id)); + res_row_count++; + for (uint32_t outgoing : GetEdges(id)) { + auto& dt = tables_per_depth[state[outgoing].depth]; + dt.row_count++; + RETURN_IF_ERROR(dt.builder.AddInteger(0, outgoing)); + } + for (uint32_t j = 1; j < inits.column_names.size(); ++j) { + switch (cell[j].index()) { + case perfetto_sql::ValueIndex(): + RETURN_IF_ERROR(res.AddNull(j)); + for (uint32_t outgoing : GetEdges(id)) { + auto& dt = tables_per_depth[state[outgoing].depth]; + RETURN_IF_ERROR(dt.builder.AddNull(j)); + } + break; + case perfetto_sql::ValueIndex(): { + int64_t r = std::get(cell[j]); + RETURN_IF_ERROR(res.AddInteger(j, r)); + for (uint32_t outgoing : GetEdges(id)) { + auto& dt = tables_per_depth[state[outgoing].depth]; + RETURN_IF_ERROR(dt.builder.AddInteger(j, r)); + } + break; + } + case perfetto_sql::ValueIndex(): { + double r = std::get(cell[j]); + RETURN_IF_ERROR(res.AddFloat(j, r)); + for (uint32_t outgoing : GetEdges(id)) { + auto& dt = tables_per_depth[state[outgoing].depth]; + RETURN_IF_ERROR(dt.builder.AddFloat(j, r)); + } + break; + } + case perfetto_sql::ValueIndex(): { + const char* r = std::get(cell[j]).c_str(); + RETURN_IF_ERROR(res.AddText(j, r)); + for (uint32_t outgoing : GetEdges(id)) { + auto& dt = tables_per_depth[state[outgoing].depth]; + RETURN_IF_ERROR(dt.builder.AddText(j, r)); + } + break; + } + default: + PERFETTO_FATAL("Invalid index"); + } + } + } + return base::OkStatus(); +} + +base::StatusOr> GraphAggregatingScanner::Run() { + if (!inits.id_column_index) { + return base::ErrStatus( + "graph_aggregating_scan: 'id' column is not present in initial nodes " + "table"); + } + if (inits.id_column_index != 0) { + return base::ErrStatus( + "graph_aggregating_scan: 'id' column must be the first column in the " + "initial nodes table"); + } + + // The basic idea of this algorithm is as follows: + // 1) Setup the state vector by figuring out the maximum id in the initial and + // graph tables. + // 2) Do a DFS to compute the depth of each node and figure out the max depth. + // 3) Setup all the table builders for each depth. + // 4) For all the starting nodes, push down their values to their dependents + // and also store the aggregates in the final result table. + // 5) Going from highest depth downward, run the aggregation SQL the user + // specified, push down those values to their dependents and also store the + // aggregates in the final result table. + // 6) Return the final result table. + // + // The complexity of this algorithm is O(n) in both memory and CPU. + // + // TODO(lalitm): there is a significant optimization we can do here: instead + // of pulling the data from SQL to C++ and then feeding that to the runtime + // table builder, we could just have an aggregate function which directly + // writes into the table itself. This would be better because: + // 1) It would be faster + // 2) It would remove the need for first creating a row dataframe and then a + // table builder for the initial nodes + // 3) It would allow code deduplication between the initial query, the step + // query and also CREATE PERFETTO TABLE: the code here is very similar to + // the code in PerfettoSqlEngine. + + RuntimeTable::Builder res(pool, inits.column_names); + uint32_t res_row_count = 0; + uint32_t max_depth = DfsAndComputeMaxDepth(InitializeStateFromMaxNode()); + + for (uint32_t i = 0; i < max_depth + 1; ++i) { + tables_per_depth.emplace_back( + DepthTable{RuntimeTable::Builder(pool, inits.column_names), 0}); + } + + RETURN_IF_ERROR(PushDownStartingAggregates(res, res_row_count)); + ASSIGN_OR_RETURN(auto agg_stmt, PrepareStatement(*engine, inits.column_names, + std::string(reduce))); + RETURN_IF_ERROR(agg_stmt.status()); + + uint32_t agg_col_count = sqlite::column::Count(agg_stmt.sqlite_stmt()); + std::vector aggregate_cols; + aggregate_cols.reserve(agg_col_count); + for (uint32_t i = 0; i < agg_col_count; ++i) { + aggregate_cols.emplace_back( + sqlite::column::Name(agg_stmt.sqlite_stmt(), i)); + } + + if (aggregate_cols != inits.column_names) { + return base::ErrStatus( + "graph_scan: aggregate SQL columns do not match init columns"); + } + + for (auto i = static_cast(tables_per_depth.size() - 1); i >= 0; + --i) { + int err = sqlite::stmt::Reset(agg_stmt.sqlite_stmt()); + if (err != SQLITE_OK) { + return base::ErrStatus("Failed to reset statement"); + } + auto idx = static_cast(i); + ASSIGN_OR_RETURN(auto depth_tab, + std::move(tables_per_depth[idx].builder) + .Build(tables_per_depth[idx].row_count)); + err = sqlite::bind::Pointer( + agg_stmt.sqlite_stmt(), 1, depth_tab.release(), "TABLE", [](void* tab) { + std::unique_ptr(static_cast(tab)); + }); + if (err != SQLITE_OK) { + return base::ErrStatus("Failed to bind pointer %d", err); + } + RETURN_IF_ERROR( + PushDownAggregates(agg_stmt, agg_col_count, res, res_row_count)); + } + return std::move(res).Build(res_row_count); +} + +struct GraphAggregatingScan : public SqliteFunction { + static constexpr char kName[] = "__intrinsic_graph_aggregating_scan"; + static constexpr int kArgCount = 4; + struct UserDataContext { + PerfettoSqlEngine* engine; + StringPool* pool; + }; + + static void Step(sqlite3_context* ctx, int argc, sqlite3_value** argv) { + PERFETTO_DCHECK(argc == kArgCount); + + auto* user_data = GetUserData(ctx); + const char* reduce = sqlite::value::Text(argv[2]); + if (!reduce) { + return sqlite::result::Error( + ctx, "graph_aggregating_scan: aggegate SQL cannot be null"); + } + const char* column_list = sqlite::value::Text(argv[3]); + if (!column_list) { + return sqlite::result::Error( + ctx, "graph_aggregating_scan: column list cannot be null"); + } + + std::vector col_names{"id"}; + for (const auto& c : + base::SplitString(base::StripChars(column_list, "()", ' '), ",")) { + col_names.push_back(base::TrimWhitespace(c)); + } + + const auto* init = sqlite::value::Pointer( + argv[1], "ROW_DATAFRAME"); + if (!init) { + SQLITE_ASSIGN_OR_RETURN( + ctx, auto table, + RuntimeTable::Builder(user_data->pool, col_names).Build(0)); + return sqlite::result::UniquePointer(ctx, std::move(table), "TABLE"); + } + if (col_names != init->column_names) { + return sqlite::result::Error( + ctx, base::StackString<1024>( + "graph_aggregating_scan: column list '%s' does not match " + "initial table list '%s'", + base::Join(col_names, ",").c_str(), + base::Join(init->column_names, ",").c_str()) + .c_str()); + } + + const auto* nodes = + sqlite::value::Pointer(argv[0], "GRAPH"); + GraphAggregatingScanner scanner{ + user_data->engine, + user_data->pool, + nodes ? *nodes : perfetto_sql::Graph(), + *init, + reduce, + {}, + {}, + {}, + }; + auto result = scanner.Run(); + if (!result.ok()) { + return sqlite::utils::SetError(ctx, result.status()); + } + return sqlite::result::UniquePointer(ctx, std::move(*result), "TABLE"); + } +}; + +struct GraphScan : public SqliteFunction { + static constexpr char kName[] = "__intrinsic_graph_scan"; + static constexpr int kArgCount = 4; + struct UserDataContext { + PerfettoSqlEngine* engine; + StringPool* pool; + }; + + static void Step(sqlite3_context* ctx, int argc, sqlite3_value** argv) { + PERFETTO_DCHECK(argc == kArgCount); + + auto* user_data = GetUserData(ctx); + const char* step_sql = sqlite::value::Text(argv[2]); + if (!step_sql) { + return sqlite::result::Error(ctx, "graph_scan: step SQL cannot be null"); + } + const char* column_list = sqlite::value::Text(argv[3]); + if (!column_list) { + return sqlite::result::Error(ctx, + "graph_scan: column list cannot be null"); + } + + std::vector col_names{"id"}; + for (const auto& c : + base::SplitString(base::StripChars(column_list, "()", ' '), ",")) { + col_names.push_back(base::TrimWhitespace(c)); + } + + const auto* init = sqlite::value::Pointer( + argv[1], "ROW_DATAFRAME"); + if (!init) { + SQLITE_ASSIGN_OR_RETURN( + ctx, auto table, + RuntimeTable::Builder(user_data->pool, col_names).Build(0)); + return sqlite::result::UniquePointer(ctx, std::move(table), "TABLE"); + } + if (col_names != init->column_names) { + base::StackString<1024> errmsg( + "graph_scan: column list '%s' does not match initial table list '%s'", + base::Join(col_names, ",").c_str(), + base::Join(init->column_names, ",").c_str()); + return sqlite::result::Error(ctx, errmsg.c_str()); + } + + const auto* raw_graph = + sqlite::value::Pointer(argv[0], "GRAPH"); + const auto& graph = raw_graph ? *raw_graph : perfetto_sql::Graph(); + + RuntimeTable::Builder out(user_data->pool, init->column_names); + uint32_t out_count = 0; + + std::unique_ptr step_table; + { + RuntimeTable::Builder step(user_data->pool, init->column_names); + uint32_t step_count = 0; + SQLITE_RETURN_IF_ERROR( + ctx, InitToOutputAndStepTable(*init, graph, step, step_count, out, + out_count)); + SQLITE_ASSIGN_OR_RETURN(ctx, step_table, + std::move(step).Build(step_count)); + } + SQLITE_ASSIGN_OR_RETURN( + ctx, auto agg_stmt, + PrepareStatement(*user_data->engine, init->column_names, step_sql)); + while (step_table->row_count() > 0) { + int err = sqlite::stmt::Reset(agg_stmt.sqlite_stmt()); + if (err != SQLITE_OK) { + return sqlite::utils::SetError(ctx, "Failed to reset statement"); + } + err = sqlite::bind::UniquePointer(agg_stmt.sqlite_stmt(), 1, + std::move(step_table), "TABLE"); + if (err != SQLITE_OK) { + return sqlite::utils::SetError( + ctx, + base::StackString<1024>("Failed to bind pointer %d", err).c_str()); + } + + RuntimeTable::Builder step(user_data->pool, init->column_names); + uint32_t step_count = 0; + SQLITE_RETURN_IF_ERROR( + ctx, SqliteToOutputAndStepTable(agg_stmt, graph, step, step_count, + out, out_count)); + SQLITE_ASSIGN_OR_RETURN(ctx, step_table, + std::move(step).Build(step_count)); + } + SQLITE_ASSIGN_OR_RETURN(ctx, auto res, std::move(out).Build(out_count)); + return sqlite::result::UniquePointer(ctx, std::move(res), "TABLE"); + } +}; + +} // namespace + +base::Status RegisterGraphScanFunctions(PerfettoSqlEngine& engine, + StringPool* pool) { + RETURN_IF_ERROR(engine.RegisterSqliteFunction( + std::make_unique( + GraphScan::UserDataContext{&engine, pool}))); + return engine.RegisterSqliteFunction( + std::make_unique( + GraphAggregatingScan::UserDataContext{&engine, pool})); +} + +} // namespace perfetto::trace_processor diff --git a/src/trace_processor/perfetto_sql/intrinsics/functions/graph_scan.h b/src/trace_processor/perfetto_sql/intrinsics/functions/graph_scan.h new file mode 100644 index 0000000000..10efcd9749 --- /dev/null +++ b/src/trace_processor/perfetto_sql/intrinsics/functions/graph_scan.h @@ -0,0 +1,32 @@ +/* + * 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_FUNCTIONS_GRAPH_SCAN_H_ +#define SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_FUNCTIONS_GRAPH_SCAN_H_ + +#include "perfetto/base/status.h" +#include "src/trace_processor/containers/string_pool.h" +#include "src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h" + +namespace perfetto::trace_processor { + +// Registers all graph scan related functions with |engine|. +base::Status RegisterGraphScanFunctions(PerfettoSqlEngine& engine, + StringPool* pool); + +} // namespace perfetto::trace_processor + +#endif // SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_FUNCTIONS_GRAPH_SCAN_H_ diff --git a/src/trace_processor/perfetto_sql/intrinsics/functions/graph_traversal.cc b/src/trace_processor/perfetto_sql/intrinsics/functions/graph_traversal.cc new file mode 100644 index 0000000000..8f0b46e6c1 --- /dev/null +++ b/src/trace_processor/perfetto_sql/intrinsics/functions/graph_traversal.cc @@ -0,0 +1,176 @@ +/* + * 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/functions/graph_traversal.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "perfetto/base/logging.h" +#include "perfetto/base/status.h" +#include "perfetto/ext/base/circular_queue.h" +#include "perfetto/public/compiler.h" +#include "src/trace_processor/containers/string_pool.h" +#include "src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h" +#include "src/trace_processor/perfetto_sql/intrinsics/functions/tables_py.h" +#include "src/trace_processor/perfetto_sql/intrinsics/types/array.h" +#include "src/trace_processor/perfetto_sql/intrinsics/types/node.h" +#include "src/trace_processor/sqlite/bindings/sqlite_aggregate_function.h" +#include "src/trace_processor/sqlite/bindings/sqlite_result.h" +#include "src/trace_processor/sqlite/bindings/sqlite_value.h" +#include "src/trace_processor/util/status_macros.h" + +namespace perfetto::trace_processor { +namespace tables { +TreeTable::~TreeTable() = default; +} // namespace tables + +namespace { + +struct State { + uint32_t id; + std::optional parent_id; +}; + +// An SQL aggregate-function which performs a DFS from a given start node in a +// graph and returns all the nodes which are reachable from the start node. +// +// Note: this function is not intended to be used directly from SQL: instead +// macros exist in the standard library, wrapping it and making it +// user-friendly. +struct Dfs : public SqliteAggregateFunction { + static constexpr char kName[] = "__intrinsic_dfs"; + static constexpr int kArgCount = 2; + using UserDataContext = StringPool; + + static void Step(sqlite3_context* ctx, int argc, sqlite3_value** argv) { + PERFETTO_DCHECK(argc == kArgCount); + + auto* graph = sqlite::value::Pointer(argv[0], "GRAPH"); + auto table = std::make_unique(GetUserData(ctx)); + if (!graph) { + return sqlite::result::UniquePointer(ctx, std::move(table), "TABLE"); + } + PERFETTO_DCHECK(!graph->empty()); + + // If the array is empty, be forgiving and return an empty array. We could + // return an error here but in 99% of cases, the caller will simply want + // an empty table instead. + auto* start_ids = + sqlite::value::Pointer(argv[1], "ARRAY"); + if (!start_ids) { + return sqlite::result::UniquePointer(ctx, std::move(table), "TABLE"); + } + PERFETTO_DCHECK(!start_ids->empty()); + + std::vector visited(graph->size()); + std::vector stack; + for (int64_t x : *start_ids) { + stack.emplace_back(State{static_cast(x), std::nullopt}); + } + while (!stack.empty()) { + State state = stack.back(); + stack.pop_back(); + + auto& node = (*graph)[state.id]; + if (visited[state.id]) { + continue; + } + table->Insert({state.id, state.parent_id}); + visited[state.id] = true; + + const auto& children = node.outgoing_edges; + for (auto it = children.rbegin(); it != children.rend(); ++it) { + stack.emplace_back(State{*it, state.id}); + } + } + return sqlite::result::UniquePointer(ctx, std::move(table), "TABLE"); + } +}; + +// An SQL aggregate-function which performs a BFS from a given start node in a +// graph and returns all the nodes which are reachable from the start node. +// +// Note: this function is not intended to be used directly from SQL: instead +// macros exist in the standard library, wrapping it and making it +// user-friendly. +struct Bfs : public SqliteAggregateFunction { + static constexpr char kName[] = "__intrinsic_bfs"; + static constexpr int kArgCount = 2; + using UserDataContext = StringPool; + + static void Step(sqlite3_context* ctx, int argc, sqlite3_value** argv) { + PERFETTO_DCHECK(argc == kArgCount); + + auto* graph = sqlite::value::Pointer(argv[0], "GRAPH"); + auto table = std::make_unique(GetUserData(ctx)); + if (!graph) { + return sqlite::result::UniquePointer(ctx, std::move(table), "TABLE"); + } + PERFETTO_DCHECK(!graph->empty()); + + // If the array is empty, be forgiving and return an empty array. We could + // return an error here but in 99% of cases, the caller will simply want + // an empty table instead. + auto* start_ids = + sqlite::value::Pointer(argv[1], "ARRAY"); + if (!start_ids) { + return sqlite::result::UniquePointer(ctx, std::move(table), "TABLE"); + } + PERFETTO_DCHECK(!start_ids->empty()); + + std::vector visited(graph->size()); + base::CircularQueue queue; + for (int64_t raw_id : *start_ids) { + auto id = static_cast(raw_id); + if (id >= graph->size() || visited[id]) { + continue; + } + visited[id] = true; + queue.emplace_back(State{id, std::nullopt}); + } + while (!queue.empty()) { + State state = queue.front(); + queue.pop_front(); + table->Insert({state.id, state.parent_id}); + + auto& node = (*graph)[state.id]; + for (uint32_t n : node.outgoing_edges) { + if (visited[n]) { + continue; + } + visited[n] = true; + queue.emplace_back(State{n, state.id}); + } + } + return sqlite::result::UniquePointer(ctx, std::move(table), "TABLE"); + } +}; + +} // namespace + +base::Status RegisterGraphTraversalFunctions(PerfettoSqlEngine& engine, + StringPool& pool) { + RETURN_IF_ERROR(engine.RegisterSqliteFunction(&pool)); + return engine.RegisterSqliteFunction(&pool); +} + +} // namespace perfetto::trace_processor diff --git a/src/trace_processor/perfetto_sql/intrinsics/functions/graph_traversal.h b/src/trace_processor/perfetto_sql/intrinsics/functions/graph_traversal.h new file mode 100644 index 0000000000..a10a3a78da --- /dev/null +++ b/src/trace_processor/perfetto_sql/intrinsics/functions/graph_traversal.h @@ -0,0 +1,39 @@ +/* + * 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_FUNCTIONS_GRAPH_TRAVERSAL_H_ +#define SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_FUNCTIONS_GRAPH_TRAVERSAL_H_ + +#include "perfetto/base/status.h" + +namespace perfetto::trace_processor { + +class PerfettoSqlEngine; +class StringPool; + +// Registers the following array related functions with SQLite: +// * __intrinsic_dfs: a scalar function which performs a DFS traversal +// of the graph. +// * __intrinsic_bfs: a scalar function which performs a BFS traversal +// of the graph. +// TODO(lalitm): once we have some stability here, expand the comments +// here. +base::Status RegisterGraphTraversalFunctions(PerfettoSqlEngine& engine, + StringPool& string_pool); + +} // namespace perfetto::trace_processor + +#endif // SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_FUNCTIONS_GRAPH_TRAVERSAL_H_ diff --git a/src/trace_processor/perfetto_sql/intrinsics/functions/interval_intersect.cc b/src/trace_processor/perfetto_sql/intrinsics/functions/interval_intersect.cc new file mode 100644 index 0000000000..ce47fb9ca8 --- /dev/null +++ b/src/trace_processor/perfetto_sql/intrinsics/functions/interval_intersect.cc @@ -0,0 +1,355 @@ +/* + * 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/functions/interval_intersect.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "perfetto/base/compiler.h" +#include "perfetto/base/logging.h" +#include "perfetto/base/status.h" +#include "perfetto/ext/base/flat_hash_map.h" +#include "perfetto/ext/base/status_or.h" +#include "perfetto/ext/base/string_utils.h" +#include "perfetto/trace_processor/basic_types.h" +#include "src/trace_processor/containers/interval_tree.h" +#include "src/trace_processor/containers/string_pool.h" +#include "src/trace_processor/db/runtime_table.h" +#include "src/trace_processor/perfetto_sql/engine/function_util.h" +#include "src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h" +#include "src/trace_processor/perfetto_sql/intrinsics/types/partitioned_intervals.h" +#include "src/trace_processor/sqlite/bindings/sqlite_bind.h" +#include "src/trace_processor/sqlite/bindings/sqlite_column.h" +#include "src/trace_processor/sqlite/bindings/sqlite_function.h" +#include "src/trace_processor/sqlite/bindings/sqlite_result.h" +#include "src/trace_processor/sqlite/bindings/sqlite_stmt.h" +#include "src/trace_processor/sqlite/bindings/sqlite_type.h" +#include "src/trace_processor/sqlite/bindings/sqlite_value.h" +#include "src/trace_processor/sqlite/sqlite_utils.h" +#include "src/trace_processor/util/status_macros.h" + +namespace perfetto::trace_processor::perfetto_sql { +namespace { + +static const uint32_t kArgCols = 2; +static const uint32_t kIdCols = 5; +static const uint32_t kPartitionColsOffset = kArgCols + kIdCols; + +using Intervals = std::vector; +using BuilderColType = RuntimeTable::BuilderColumnType; + +struct MultiIndexInterval { + uint64_t start; + uint64_t end; + std::vector idx_in_table; +}; + +BuilderColType FromSqlValueTypeToBuilderType(SqlValue::Type type) { + switch (type) { + case SqlValue::kLong: + return RuntimeTable::kNullInt; + case SqlValue::kDouble: + return RuntimeTable::kNullDouble; + case SqlValue::kString: + return RuntimeTable::kString; + case SqlValue::kNull: + case SqlValue::kBytes: + PERFETTO_FATAL("Wrong type"); + } + PERFETTO_FATAL("For gcc"); +} + +base::StatusOr> GetPartitionsSqlType( + const PartitionToValuesMap& map) { + auto it = map.GetIterator(); + if (!it) { + return std::vector(); + } + uint32_t part_count = static_cast(it.value().size()); + std::vector types(part_count, BuilderColType::kNull); + bool any_part_not_found = true; + for (; it; ++it) { + any_part_not_found = false; + for (uint32_t i = 0; i < part_count && any_part_not_found; i++) { + auto type = types[i]; + if (type != BuilderColType::kNull) { + continue; + } + if (it.value()[i].is_null()) { + any_part_not_found = true; + continue; + } + types[i] = FromSqlValueTypeToBuilderType(it.value()[i].type); + } + } + if (any_part_not_found) { + return base::ErrStatus( + "INTERVAL_INTERSECT: Can't partition on column that only has NULLs"); + } + return types; +} + +static base::StatusOr PushPartition( + RuntimeTable::Builder& builder, + const std::vector& table_intervals, + const std::vector& partition_values) { + size_t tables_count = table_intervals.size(); + std::vector tables_order(tables_count); + std::iota(tables_order.begin(), tables_order.end(), 0); + + // Sort `tables_order` from the smallest to the biggest + std::sort(tables_order.begin(), tables_order.end(), + [table_intervals](const uint32_t idx_a, const uint32_t idx_b) { + return table_intervals[idx_a]->size() < + table_intervals[idx_b]->size(); + }); + uint32_t smallest_table_idx = tables_order.front(); + PERFETTO_DCHECK(!table_intervals[smallest_table_idx]->empty()); + + // Trivially translate intervals from smallest table to `MultiIndexIntervals`. + std::vector res; + res.reserve(table_intervals.back()->size()); + for (const auto& interval : *table_intervals[smallest_table_idx]) { + MultiIndexInterval m_int; + m_int.start = interval.start; + m_int.end = interval.end; + m_int.idx_in_table.resize(tables_count); + m_int.idx_in_table[smallest_table_idx] = interval.id; + res.push_back(m_int); + } + + // Create an interval tree on all tables except the smallest - the first one. + std::vector overlaps_with_this_table; + overlaps_with_this_table.reserve(table_intervals.back()->size()); + for (uint32_t i = 1; i < tables_count && !res.empty(); i++) { + overlaps_with_this_table.clear(); + uint32_t table_idx = tables_order[i]; + IntervalTree cur_tree(*table_intervals[table_idx]); + for (const auto& r : res) { + Intervals new_intervals; + cur_tree.FindOverlaps(r.start, r.end, new_intervals); + for (const auto& overlap : new_intervals) { + MultiIndexInterval m_int; + m_int.idx_in_table = std::move(r.idx_in_table); + m_int.idx_in_table[table_idx] = overlap.id; + m_int.start = overlap.start; + m_int.end = overlap.end; + overlaps_with_this_table.push_back(std::move(m_int)); + } + } + + res = std::move(overlaps_with_this_table); + } + + uint32_t rows_count = static_cast(res.size()); + std::vector timestamps(rows_count); + std::vector durations(rows_count); + std::vector> ids(tables_count); + for (auto& t_ids_vec : ids) { + t_ids_vec.resize(rows_count); + } + + for (uint32_t i = 0; i < rows_count; i++) { + const MultiIndexInterval& interval = res[i]; + timestamps[i] = static_cast(interval.start); + durations[i] = static_cast(interval.end) - + static_cast(interval.start); + for (uint32_t j = 0; j < tables_count; j++) { + ids[j][i] = interval.idx_in_table[j]; + } + } + + builder.AddNonNullIntegersUnchecked(0, std::move(timestamps)); + builder.AddNonNullIntegersUnchecked(1, std::move(durations)); + for (uint32_t i = 0; i < tables_count; i++) { + builder.AddNonNullIntegersUnchecked(i + kArgCols, ids[i]); + } + + for (uint32_t i = 0; i < partition_values.size(); i++) { + const SqlValue& part_val = partition_values[i]; + switch (part_val.type) { + case SqlValue::kLong: + RETURN_IF_ERROR(builder.AddIntegers(i + kPartitionColsOffset, + part_val.AsLong(), rows_count)); + continue; + case SqlValue::kDouble: + RETURN_IF_ERROR(builder.AddFloats(i + kPartitionColsOffset, + part_val.AsDouble(), rows_count)); + continue; + case SqlValue::kString: + RETURN_IF_ERROR(builder.AddTexts(i + kPartitionColsOffset, + part_val.AsString(), rows_count)); + continue; + case SqlValue::kNull: + RETURN_IF_ERROR(builder.AddNulls(i + kPartitionColsOffset, rows_count)); + continue; + case SqlValue::kBytes: + PERFETTO_FATAL("Invalid partition type"); + } + } + + return static_cast(res.size()); +} + +struct IntervalIntersect : public SqliteFunction { + static constexpr char kName[] = "__intrinsic_interval_intersect"; + // Two tables that are being intersected. + // TODO(mayzner): Support more tables. + static constexpr int kArgCount = -1; + + struct UserDataContext { + PerfettoSqlEngine* engine; + StringPool* pool; + }; + + static void Step(sqlite3_context* ctx, int argc, sqlite3_value** argv) { + PERFETTO_DCHECK(argc >= 2); + size_t tabc = static_cast(argc - 1); + if (tabc > kIdCols) { + return sqlite::result::Error( + ctx, "interval intersect: Can intersect at most 5 tables"); + } + const char* partition_list = sqlite::value::Text(argv[argc - 1]); + if (!partition_list) { + return sqlite::result::Error( + ctx, "interval intersect: column list cannot be null"); + } + + // Get column names of return columns. + std::vector ret_col_names{"ts", "dur"}; + for (uint32_t i = 0; i < kIdCols; i++) { + ret_col_names.push_back(base::StackString<32>("id_%u", i).ToStdString()); + } + std::vector partition_columns = + base::SplitString(base::StripChars(partition_list, "()", ' '), ","); + if (partition_columns.size() > 4) { + return sqlite::result::Error( + ctx, "interval intersect: Can take at most 4 partitions."); + } + for (const auto& c : partition_columns) { + std::string p_col_name = base::TrimWhitespace(c).c_str(); + if (!p_col_name.empty()) { + ret_col_names.push_back(p_col_name); + } + } + + // Get data from of each table. + std::vector tables(tabc); + std::vector t_intervals(tabc); + + for (uint32_t i = 0; i < tabc; i++) { + tables[i] = sqlite::value::Pointer( + argv[i], PartitionedTable::kName); + + // If any of the tables is empty the intersection with it also has to be + // empty. + if (!tables[i] || tables[i]->intervals.size() == 0) { + SQLITE_ASSIGN_OR_RETURN( + ctx, std::unique_ptr ret_table, + RuntimeTable::Builder(GetUserData(ctx)->pool, ret_col_names) + .Build(0)); + return sqlite::result::UniquePointer(ctx, std::move(ret_table), + "TABLE"); + } + t_intervals[i] = &tables[i]->intervals; + } + + std::vector col_types(kArgCols + tabc, + BuilderColType::kInt); + // Add dummy id cols. + col_types.resize(kArgCols + kIdCols, BuilderColType::kNullInt); + + PartitionToValuesMap* p_values = &tables[0]->partition_values; + SQLITE_ASSIGN_OR_RETURN(ctx, std::vector p_types, + GetPartitionsSqlType(*p_values)); + col_types.insert(col_types.end(), p_types.begin(), p_types.end()); + + RuntimeTable::Builder builder(GetUserData(ctx)->pool, ret_col_names, + col_types); + + // Partitions will be taken from the table which has the least number of + // them. + auto min_el = std::min_element(t_intervals.begin(), t_intervals.end(), + [](const auto& t_a, const auto& t_b) { + return t_a->size() < t_b->size(); + }); + + auto t_least_partitions = + static_cast(std::distance(t_intervals.begin(), min_el)); + + // The only partitions we should look at are partitions from the table + // with the least partitions. + const PartitionToIntervalsMap* p_intervals = + t_intervals[t_least_partitions]; + + // For each partition insert into table. + uint32_t rows = 0; + for (auto p_it = p_intervals->GetIterator(); p_it; ++p_it) { + std::vector unpartitioned_intervals; + bool all_have_p = true; + + // From each table get all vectors of intervals. + for (uint32_t i = 0; i < tabc; i++) { + PartitionToIntervalsMap* t = t_intervals[i]; + if (auto found = t->Find(p_it.key())) { + unpartitioned_intervals.push_back(found); + } else { + all_have_p = false; + break; + } + } + + // Only push into the table if all tables have this partition present. + if (all_have_p) { + SQLITE_ASSIGN_OR_RETURN(ctx, uint32_t pushed_rows, + PushPartition(builder, unpartitioned_intervals, + (*p_values)[p_it.key()])); + rows += pushed_rows; + } + } + + // Fill the dummy id columns with nulls. + for (uint32_t i = static_cast(tabc); i < kIdCols; i++) { + SQLITE_RETURN_IF_ERROR(ctx, builder.AddNulls(i + kArgCols, rows)); + } + + SQLITE_ASSIGN_OR_RETURN(ctx, std::unique_ptr ret_tab, + std::move(builder).Build(rows)); + + return sqlite::result::UniquePointer(ctx, std::move(ret_tab), "TABLE"); + } +}; + +} // namespace + +base::Status RegisterIntervalIntersectFunctions(PerfettoSqlEngine& engine, + StringPool* pool) { + return engine.RegisterSqliteFunction( + std::make_unique( + IntervalIntersect::UserDataContext{&engine, pool})); +} + +} // namespace perfetto::trace_processor::perfetto_sql diff --git a/src/trace_processor/perfetto_sql/intrinsics/functions/interval_intersect.h b/src/trace_processor/perfetto_sql/intrinsics/functions/interval_intersect.h new file mode 100644 index 0000000000..f1063bac34 --- /dev/null +++ b/src/trace_processor/perfetto_sql/intrinsics/functions/interval_intersect.h @@ -0,0 +1,32 @@ +/* + * 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_FUNCTIONS_INTERVAL_INTERSECT_H_ +#define SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_FUNCTIONS_INTERVAL_INTERSECT_H_ + +#include "perfetto/base/status.h" +#include "src/trace_processor/containers/string_pool.h" +#include "src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h" + +namespace perfetto::trace_processor::perfetto_sql { + +// Registers all interval intersect related functions with |engine|. +base::Status RegisterIntervalIntersectFunctions(PerfettoSqlEngine& engine, + StringPool* pool); + +} // namespace perfetto::trace_processor::perfetto_sql + +#endif // SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_FUNCTIONS_INTERVAL_INTERSECT_H_ diff --git a/src/trace_processor/perfetto_sql/intrinsics/functions/pprof_functions.cc b/src/trace_processor/perfetto_sql/intrinsics/functions/pprof_functions.cc index ae1c4d3342..89f7e58d1f 100644 --- a/src/trace_processor/perfetto_sql/intrinsics/functions/pprof_functions.cc +++ b/src/trace_processor/perfetto_sql/intrinsics/functions/pprof_functions.cc @@ -49,8 +49,6 @@ namespace { using protos::pbzero::Stack; -constexpr char kFunctionName[] = "EXPERIMENTAL_PROFILE"; - template std::unique_ptr WrapUnique(T* ptr) { return std::unique_ptr(ptr); @@ -182,15 +180,16 @@ base::Status StepStatus(sqlite3_context* ctx, } struct ProfileBuilder { + static constexpr char kName[] = "EXPERIMENTAL_PROFILE"; + static constexpr int kArgCount = -1; using UserDataContext = TraceProcessorContext; static void Step(sqlite3_context* ctx, int argc, sqlite3_value** argv) { PERFETTO_CHECK(argc >= 0); base::Status status = StepStatus(ctx, static_cast(argc), argv); - if (!status.ok()) { - sqlite::utils::SetError(ctx, kFunctionName, status); + sqlite::utils::SetError(ctx, kName, status); } } @@ -212,8 +211,7 @@ struct ProfileBuilder { base::Status PprofFunctions::Register(PerfettoSqlEngine& engine, TraceProcessorContext* context) { - return engine.RegisterSqliteAggregateFunction(kFunctionName, - -1, context); + return engine.RegisterSqliteAggregateFunction(context); } } // namespace perfetto::trace_processor 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 2d6854859a..05c44c24aa 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 @@ -29,7 +29,7 @@ #include "src/trace_processor/perfetto_sql/intrinsics/functions/tables_py.h" #include "src/trace_processor/sqlite/bindings/sqlite_aggregate_function.h" #include "src/trace_processor/sqlite/bindings/sqlite_result.h" -#include "src/trace_processor/sqlite/sqlite_value.h" +#include "src/trace_processor/sqlite/bindings/sqlite_value.h" namespace perfetto::trace_processor { namespace tables { @@ -81,8 +81,8 @@ void StructuralTreePartition::StructuralTreePartition::Step( // For performance reasons, we don't typecheck the arguments and assume they // are longs. - auto id = static_cast(sqlite::value::Long(argv[0])); - auto group = static_cast(sqlite::value::Long(argv[2])); + auto id = static_cast(sqlite::value::Int64(argv[0])); + auto group = static_cast(sqlite::value::Int64(argv[2])); // Keep track of the maximum group seen. agg_ctx.max_group = std::max(agg_ctx.max_group, group); @@ -101,7 +101,7 @@ void StructuralTreePartition::StructuralTreePartition::Step( } // Otherwise, this is a non-root. Increment the child count of its parent. - auto parent_id = static_cast(sqlite::value::Long(parent_id_value)); + auto parent_id = static_cast(sqlite::value::Int64(parent_id_value)); uint32_t max_id = std::max(id, parent_id); if (max_id >= agg_ctx.child_count_by_id.size()) { agg_ctx.child_count_by_id.resize(max_id + 1); diff --git a/src/trace_processor/perfetto_sql/intrinsics/functions/tables.py b/src/trace_processor/perfetto_sql/intrinsics/functions/tables.py index 1cc8602a94..3fa5d65324 100644 --- a/src/trace_processor/perfetto_sql/intrinsics/functions/tables.py +++ b/src/trace_processor/perfetto_sql/intrinsics/functions/tables.py @@ -20,10 +20,11 @@ # This class should contain all table schemas for functions (scalar or # aggregate) which return tables. -DFS_TABLE = Table( +# Helper table to return any sort of "tree-like" table from functions. +TREE_TABLE = Table( python_module=__file__, - class_name="DfsTable", - sql_name="__intrinsic_dfs", + class_name="TreeTable", + sql_name="__unused", columns=[ C("node_id", CppUint32()), C("parent_node_id", CppOptional(CppUint32())), @@ -50,7 +51,7 @@ # Keep this list sorted. ALL_TABLES = [ - DFS_TABLE, DOMINATOR_TREE_TABLE, STRUCTURAL_TREE_PARTITION_TABLE, + TREE_TABLE, ] 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 387664e6a1..efa7659d6c 100644 --- a/src/trace_processor/perfetto_sql/intrinsics/functions/to_ftrace.cc +++ b/src/trace_processor/perfetto_sql/intrinsics/functions/to_ftrace.cc @@ -630,9 +630,11 @@ SystraceSerializer::ScopedCString SystraceSerializer::SerializeToString( void SystraceSerializer::SerializePrefix(uint32_t raw_row, base::StringWriter* writer) { const auto& raw = storage_->raw_table(); + const auto& cpu_table = storage_->cpu_table(); int64_t ts = raw.ts()[raw_row]; - uint32_t cpu = raw.cpu()[raw_row]; + auto ucpu = raw.ucpu()[raw_row]; + auto cpu = cpu_table.cpu()[ucpu.value]; UniqueTid utid = raw.utid()[raw_row]; uint32_t tid = storage_->thread_table().tid()[utid]; @@ -675,7 +677,7 @@ void SystraceSerializer::SerializePrefix(uint32_t raw_row, writer->AppendPaddedInt<' ', 5>(tgid); } writer->AppendLiteral(") ["); - writer->AppendPaddedInt<'0', 3>(cpu); + writer->AppendPaddedInt<'0', 3>(cpu ? *cpu : 0); writer->AppendLiteral("] .... "); writer->AppendInt(ftrace_time.secs); diff --git a/src/trace_processor/perfetto_sql/intrinsics/functions/type_builders.cc b/src/trace_processor/perfetto_sql/intrinsics/functions/type_builders.cc new file mode 100644 index 0000000000..d9428638bd --- /dev/null +++ b/src/trace_processor/perfetto_sql/intrinsics/functions/type_builders.cc @@ -0,0 +1,404 @@ +/* + * 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/functions/type_builders.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "perfetto/base/logging.h" +#include "perfetto/base/status.h" +#include "perfetto/ext/base/flat_hash_map.h" +#include "perfetto/ext/base/hash.h" +#include "perfetto/public/compiler.h" +#include "perfetto/trace_processor/basic_types.h" +#include "src/trace_processor/containers/interval_tree.h" +#include "src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h" +#include "src/trace_processor/perfetto_sql/intrinsics/types/array.h" +#include "src/trace_processor/perfetto_sql/intrinsics/types/node.h" +#include "src/trace_processor/perfetto_sql/intrinsics/types/partitioned_intervals.h" +#include "src/trace_processor/perfetto_sql/intrinsics/types/row_dataframe.h" +#include "src/trace_processor/perfetto_sql/intrinsics/types/struct.h" +#include "src/trace_processor/sqlite/bindings/sqlite_aggregate_function.h" +#include "src/trace_processor/sqlite/bindings/sqlite_function.h" +#include "src/trace_processor/sqlite/bindings/sqlite_result.h" +#include "src/trace_processor/sqlite/bindings/sqlite_type.h" +#include "src/trace_processor/sqlite/bindings/sqlite_value.h" +#include "src/trace_processor/sqlite/sqlite_utils.h" +#include "src/trace_processor/util/status_macros.h" + +namespace perfetto::trace_processor { +namespace { + +inline void HashSqlValue(base::Hasher& h, const SqlValue& v) { + switch (v.type) { + case SqlValue::Type::kString: + h.Update(v.AsString()); + break; + case SqlValue::Type::kDouble: + h.Update(v.AsDouble()); + break; + case SqlValue::Type::kLong: + h.Update(v.AsLong()); + break; + case SqlValue::Type::kBytes: + PERFETTO_FATAL("Wrong type"); + break; + case SqlValue::Type::kNull: + h.Update(nullptr); + break; + } + return; +} + +using Array = std::variant; + +// An SQL aggregate-function which creates an array. +struct ArrayAgg : public SqliteAggregateFunction { + static constexpr char kName[] = "__intrinsic_array_agg"; + static constexpr int kArgCount = 1; + struct AggCtx : SqliteAggregateContext { + template + void Push(sqlite3_context* ctx, T value) { + if (PERFETTO_UNLIKELY(!array)) { + array = std::vector{std::move(value)}; + return; + } + auto* a = std::get_if>(&*array); + if (!a) { + return sqlite::result::Error( + ctx, "ARRAY_AGG: all values must have the same type"); + } + a->emplace_back(std::move(value)); + } + template + void Result(sqlite3_context* ctx, const char* type) { + auto res = std::make_unique>( + std::get>(std::move(*array))); + return sqlite::result::UniquePointer(ctx, std::move(res), type); + } + + std::optional array; + }; + + static void Step(sqlite3_context* ctx, int argc, sqlite3_value** argv) { + PERFETTO_DCHECK(argc == kArgCount); + + auto& agg_ctx = AggCtx::GetOrCreateContextForStep(ctx); + switch (sqlite::value::Type(argv[0])) { + case sqlite::Type::kInteger: + return agg_ctx.Push(ctx, sqlite::value::Int64(argv[0])); + case sqlite::Type::kText: + return agg_ctx.Push(ctx, sqlite::value::Text(argv[0])); + case sqlite::Type::kFloat: + return agg_ctx.Push(ctx, sqlite::value::Double(argv[0])); + case sqlite::Type::kNull: + return sqlite::result::Error( + ctx, + "ARRAY_AGG: nulls are not supported. They should be filtered out " + "before calling ARRAY_AGG."); + case sqlite::Type::kBlob: + return sqlite::result::Error(ctx, + "ARRAY_AGG: blobs are not supported."); + } + } + static void Final(sqlite3_context* ctx) { + auto raw_agg_ctx = AggCtx::GetContextOrNullForFinal(ctx); + if (!raw_agg_ctx) { + return sqlite::result::Null(ctx); + } + + auto& array = *raw_agg_ctx.get()->array; + switch (array.index()) { + case 0 /* int64_t */: + return raw_agg_ctx.get()->Result(ctx, "ARRAY"); + case 1 /* double */: + return raw_agg_ctx.get()->Result(ctx, "ARRAY"); + case 2 /* std::string */: + return raw_agg_ctx.get()->Result(ctx, "ARRAY"); + default: + PERFETTO_FATAL("%zu is not a valid index", array.index()); + } + } +}; + +// An SQL aggregate function which creates a graph. +struct NodeAgg : public SqliteAggregateFunction { + static constexpr char kName[] = "__intrinsic_graph_agg"; + static constexpr int kArgCount = 2; + struct AggCtx : SqliteAggregateContext { + perfetto_sql::Graph graph; + }; + + static void Step(sqlite3_context* ctx, int argc, sqlite3_value** argv) { + PERFETTO_DCHECK(argc == kArgCount); + + auto source_id = static_cast(sqlite::value::Int64(argv[0])); + auto target_id = static_cast(sqlite::value::Int64(argv[1])); + uint32_t max_id = std::max(source_id, target_id); + auto& agg_ctx = AggCtx::GetOrCreateContextForStep(ctx); + if (max_id >= agg_ctx.graph.size()) { + agg_ctx.graph.resize(max_id + 1); + } + agg_ctx.graph[source_id].outgoing_edges.push_back(target_id); + } + static void Final(sqlite3_context* ctx) { + auto raw_agg_ctx = AggCtx::GetContextOrNullForFinal(ctx); + if (!raw_agg_ctx.get()) { + return; + } + auto nodes = std::make_unique( + std::move(raw_agg_ctx.get()->graph)); + return sqlite::result::UniquePointer(ctx, std::move(nodes), "GRAPH"); + } +}; + +// An SQL scalar function which creates an struct. +struct Struct : public SqliteFunction { + static constexpr char kName[] = "__intrinsic_struct"; + static constexpr int kArgCount = -1; + + static void Step(sqlite3_context* ctx, int rargc, sqlite3_value** argv) { + auto argc = static_cast(rargc); + if (argc % 2 != 0) { + return sqlite::result::Error( + ctx, "STRUCT: must have an even number of arguments"); + } + if (argc / 2 > perfetto_sql::Struct::kMaxFields) { + return sqlite::utils::SetError( + ctx, base::ErrStatus("STRUCT: only at most %d fields are supported", + perfetto_sql::Struct::kMaxFields)); + } + + auto s = std::make_unique(); + s->field_count = argc / 2; + for (uint32_t i = 0; i < s->field_count; ++i) { + if (sqlite::value::Type(argv[i]) != sqlite::Type::kText) { + return sqlite::result::Error(ctx, + "STRUCT: field names must be strings"); + } + auto& field = s->fields[i]; + field.first = sqlite::value::Text(argv[i]); + switch (sqlite::value::Type(argv[s->field_count + i])) { + case sqlite::Type::kText: + field.second = sqlite::value::Text(argv[s->field_count + i]); + break; + case sqlite::Type::kInteger: + field.second = sqlite::value::Int64(argv[s->field_count + i]); + break; + case sqlite::Type::kFloat: + field.second = sqlite::value::Double(argv[s->field_count + i]); + break; + case sqlite::Type::kNull: + field.second = std::monostate(); + break; + case sqlite::Type::kBlob: + return sqlite::result::Error(ctx, + "STRUCT: blob fields not supported"); + } + } + return sqlite::result::UniquePointer(ctx, std::move(s), "STRUCT"); + } +}; + +// An SQL aggregate function which creates a RowDataframe. +struct RowDataframeAgg : public SqliteAggregateFunction { + static constexpr char kName[] = "__intrinsic_row_dataframe_agg"; + static constexpr int kArgCount = -1; + struct AggCtx : SqliteAggregateContext { + perfetto_sql::RowDataframe dataframe; + std::optional argc_index; + }; + + static void Step(sqlite3_context* ctx, int rargc, sqlite3_value** argv) { + auto argc = static_cast(rargc); + if (argc % 2 != 0) { + return sqlite::result::Error( + ctx, "ROW_DATAFRAME_AGG: must have an even number of arguments"); + } + + auto& agg_ctx = AggCtx::GetOrCreateContextForStep(ctx); + auto& df = agg_ctx.dataframe; + if (df.column_names.empty()) { + for (uint32_t i = 0; i < argc; i += 2) { + df.column_names.emplace_back(sqlite::value::Text(argv[i])); + if (df.column_names.back() == "id") { + df.id_column_index = i / 2; + agg_ctx.argc_index = i + 1; + } + } + } + + if (agg_ctx.argc_index) { + auto id = static_cast( + sqlite::value::Int64(argv[*agg_ctx.argc_index])); + if (id >= df.id_to_cell_index.size()) { + df.id_to_cell_index.resize(id + 1, + std::numeric_limits::max()); + } + df.id_to_cell_index[id] = static_cast(df.cells.size()); + } + + for (uint32_t i = 1; i < argc; i += 2) { + switch (sqlite::value::Type(argv[i])) { + case sqlite::Type::kText: + df.cells.emplace_back(sqlite::value::Text(argv[i])); + break; + case sqlite::Type::kInteger: + df.cells.emplace_back(sqlite::value::Int64(argv[i])); + break; + case sqlite::Type::kFloat: + df.cells.emplace_back(sqlite::value::Double(argv[i])); + break; + case sqlite::Type::kNull: + df.cells.emplace_back(std::monostate()); + break; + case sqlite::Type::kBlob: + return sqlite::result::Error( + ctx, "ROW_DATAFRAME_AGG: blob fields not supported"); + } + } + } + + static void Final(sqlite3_context* ctx) { + auto raw_agg_ctx = AggCtx::GetContextOrNullForFinal(ctx); + if (!raw_agg_ctx) { + return sqlite::result::Null(ctx); + } + return sqlite::result::UniquePointer( + ctx, + std::make_unique( + std::move(raw_agg_ctx.get()->dataframe)), + "ROW_DATAFRAME"); + } +}; + +struct IntervalTreeIntervalsAgg + : public SqliteAggregateFunction { + static constexpr char kName[] = "__intrinsic_interval_tree_intervals_agg"; + static constexpr int kArgCount = -1; + static constexpr int kMinArgCount = 3; + struct AggCtx : SqliteAggregateContext { + perfetto_sql::PartitionedTable partitions; + std::vector tmp_vals; + uint64_t max_ts = std::numeric_limits::min(); + }; + + static void Step(sqlite3_context* ctx, int rargc, sqlite3_value** argv) { + auto argc = static_cast(rargc); + PERFETTO_DCHECK(argc >= kMinArgCount); + auto& agg_ctx = AggCtx::GetOrCreateContextForStep(ctx); + auto& parts = AggCtx::GetOrCreateContextForStep(ctx).partitions; + + // Fetch and validate the interval. + IntervalTree::Interval interval; + interval.id = static_cast(sqlite::value::Int64(argv[0])); + interval.start = static_cast(sqlite::value::Int64(argv[1])); + if (interval.start < agg_ctx.max_ts) { + sqlite::result::Error( + ctx, "Interval intersect requires intervals to be sorted by ts."); + return; + } + agg_ctx.max_ts = interval.start; + int64_t dur = sqlite::value::Int64(argv[2]); + if (dur < 1) { + sqlite::result::Error( + ctx, "Interval intersect only works on intervals with dur > 0"); + return; + } + interval.end = interval.start + static_cast(dur); + + // Fast path for no partitions. + if (argc == kMinArgCount) { + parts.intervals[0].push_back(std::move(interval)); + return; + } + + // On the first |Step()| we need to fetch the names of the partitioned + // columns. + if (parts.partition_column_names.empty()) { + for (uint32_t i = 3; i < argc; i += 2) { + parts.partition_column_names.push_back( + sqlite::utils::SqliteValueToSqlValue(argv[i]).AsString()); + } + agg_ctx.tmp_vals.resize(parts.partition_column_names.size()); + } + + // Create a partition key. + base::Hasher h; + uint32_t j = 0; + for (uint32_t i = kMinArgCount + 1; i < argc; i += 2) { + SqlValue new_val = sqlite::utils::SqliteValueToSqlValue(argv[i]); + agg_ctx.tmp_vals[j] = new_val; + HashSqlValue(h, new_val); + j++; + } + + uint64_t key = h.digest(); + auto* part = parts.intervals.Find(key); + + // If we encountered this partition before we only have to push the interval + // into it. + if (part) { + part->push_back(interval); + return; + } + + std::vector part_values; + for (uint32_t i = kMinArgCount + 1; i < argc; i += 2) { + part_values.push_back(sqlite::utils::SqliteValueToSqlValue(argv[i])); + } + parts.partition_values[key] = agg_ctx.tmp_vals; + parts.intervals[key].push_back(interval); + } + + static void Final(sqlite3_context* ctx) { + auto raw_agg_ctx = AggCtx::GetContextOrNullForFinal(ctx); + if (!raw_agg_ctx) { + return sqlite::result::Null(ctx); + } + return sqlite::result::UniquePointer( + ctx, + std::make_unique( + std::move(raw_agg_ctx.get()->partitions)), + perfetto_sql::PartitionedTable::kName); + } +}; + +} // namespace + +base::Status RegisterTypeBuilderFunctions(PerfettoSqlEngine& engine) { + RETURN_IF_ERROR(engine.RegisterSqliteAggregateFunction(nullptr)); + RETURN_IF_ERROR(engine.RegisterSqliteFunction(nullptr)); + RETURN_IF_ERROR( + engine.RegisterSqliteAggregateFunction(nullptr)); + RETURN_IF_ERROR( + engine.RegisterSqliteAggregateFunction( + nullptr)); + return engine.RegisterSqliteAggregateFunction(nullptr); +} + +} // namespace perfetto::trace_processor diff --git a/src/trace_processor/perfetto_sql/intrinsics/functions/type_builders.h b/src/trace_processor/perfetto_sql/intrinsics/functions/type_builders.h new file mode 100644 index 0000000000..a357bc42cd --- /dev/null +++ b/src/trace_processor/perfetto_sql/intrinsics/functions/type_builders.h @@ -0,0 +1,41 @@ +/* + * 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_FUNCTIONS_TYPE_BUILDERS_H_ +#define SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_FUNCTIONS_TYPE_BUILDERS_H_ + +#include "perfetto/base/status.h" + +namespace perfetto::trace_processor { + +class PerfettoSqlEngine; +class StringPool; + +// Registers the following PerfettoSQL type related functions with SQLite: +// * __intrinsic_graph_agg: an aggregate function which builds a graph. +// * __intrinsic_array_agg: an aggregate function which allows building +// arrays from tables. +// * __intrinsic_struct: a scalar function which allows creating a +// struct from its component fields. +// * __intrinsic_row_dataframe_agg: an aggregate function which +// creates a data structure allowing efficient lookups of rows by id. +// TODO(lalitm): once we have some stability here, expand the comments +// here. +base::Status RegisterTypeBuilderFunctions(PerfettoSqlEngine& engine); + +} // namespace perfetto::trace_processor + +#endif // SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_FUNCTIONS_TYPE_BUILDERS_H_ diff --git a/src/trace_processor/perfetto_sql/intrinsics/operators/BUILD.gn b/src/trace_processor/perfetto_sql/intrinsics/operators/BUILD.gn index 49a4a81ad8..57ab6117ae 100644 --- a/src/trace_processor/perfetto_sql/intrinsics/operators/BUILD.gn +++ b/src/trace_processor/perfetto_sql/intrinsics/operators/BUILD.gn @@ -20,6 +20,8 @@ source_set("operators") { sources = [ "counter_mipmap_operator.cc", "counter_mipmap_operator.h", + "interval_intersect_operator.cc", + "interval_intersect_operator.h", "slice_mipmap_operator.cc", "slice_mipmap_operator.h", "span_join_operator.cc", diff --git a/src/trace_processor/perfetto_sql/intrinsics/operators/interval_intersect_operator.cc b/src/trace_processor/perfetto_sql/intrinsics/operators/interval_intersect_operator.cc new file mode 100644 index 0000000000..7067cb4198 --- /dev/null +++ b/src/trace_processor/perfetto_sql/intrinsics/operators/interval_intersect_operator.cc @@ -0,0 +1,568 @@ +/* + * 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/interval_intersect_operator.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "perfetto/base/logging.h" +#include "perfetto/base/status.h" +#include "perfetto/ext/base/flat_hash_map.h" +#include "perfetto/ext/base/hash.h" +#include "perfetto/ext/base/status_or.h" +#include "perfetto/ext/base/string_utils.h" +#include "perfetto/trace_processor/basic_types.h" +#include "perfetto/trace_processor/status.h" +#include "src/trace_processor/containers/interval_tree.h" +#include "src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h" +#include "src/trace_processor/sqlite/bindings/sqlite_result.h" +#include "src/trace_processor/sqlite/module_lifecycle_manager.h" +#include "src/trace_processor/sqlite/sqlite_utils.h" +#include "src/trace_processor/util/status_macros.h" + +namespace perfetto::trace_processor { +namespace { + +using Op = IntervalIntersectOperator; +using Cursor = Op::Cursor; +using Manager = sqlite::ModuleStateManager; +using ColumnsMap = Op::SchemaToTableColumnMap; + +constexpr char kSliceSchema[] = R"( + CREATE TABLE x( + tab TEXT HIDDEN, + exposed_cols_str TEXT HIDDEN, + ts BIGINT, + ts_end BIGINT, + id BIGINT, + c0 ANY, + c1 ANY, + c2 ANY, + c3 ANY, + c4 ANY, + c5 ANY, + c6 ANY, + c7 ANY, + c8 ANY, + PRIMARY KEY(id) + ) WITHOUT ROWID +)"; + +enum SchemaColumnIds { + kTableName = 0, + kExposedCols = 1, + kTs = 2, + kTsEnd = 3, + kId = 4, + kAdditional = 5, + kMaxCol = 13 +}; + +constexpr uint32_t kArgsCount = 2; + +inline void HashSqlValue(base::Hasher& h, const SqlValue& v) { + switch (v.type) { + case SqlValue::Type::kString: + h.Update(v.AsString()); + break; + case SqlValue::Type::kDouble: + h.Update(v.AsDouble()); + break; + case SqlValue::Type::kLong: + h.Update(v.AsLong()); + break; + case SqlValue::Type::kBytes: + PERFETTO_FATAL("Wrong type"); + break; + case SqlValue::Type::kNull: + h.Update(nullptr); + break; + } + return; +} + +base::StatusOr ColIdForName(const Table* t, + const std::string& col_name, + const std::string& table_name) { + auto x = t->ColumnIdxFromName(col_name); + if (!x.has_value()) { + return base::ErrStatus("interval_intersect: No column '%s' in table '%s'", + col_name.c_str(), table_name.c_str()); + } + return *x; +} + +base::StatusOr CreateIntervalTrees( + const Table* t, + const std::string& table_name, + const ColumnsMap& cols) { + uint32_t ts_col_idx = 0; + ASSIGN_OR_RETURN(ts_col_idx, ColIdForName(t, "ts", table_name)); + uint32_t ts_end_col_idx = 0; + ASSIGN_OR_RETURN(ts_end_col_idx, ColIdForName(t, "ts_end", table_name)); + uint32_t id_col_idx = 0; + ASSIGN_OR_RETURN(id_col_idx, ColIdForName(t, "id", table_name)); + + std::vector cols_for_tree; + for (const auto& c : cols) { + if (c) { + cols_for_tree.push_back(*c); + } + } + + base::FlatHashMap> + sorted_intervals; + for (Table::Iterator it = t->IterateRows(); it; ++it) { + IntervalTree::Interval i; + i.start = static_cast(it.Get(ts_col_idx).AsLong()); + i.end = static_cast(it.Get(ts_end_col_idx).AsLong()); + i.id = static_cast(it.Get(id_col_idx).AsLong()); + + base::Hasher h; + for (const auto& c : cols_for_tree) { + SqlValue v = it.Get(c); + HashSqlValue(h, v); + } + sorted_intervals[h.digest()].push_back(i); + } + + Cursor::TreesMap ret; + for (auto it = sorted_intervals.GetIterator(); it; ++it) { + IntervalTree x(it.value()); + ret[it.key()] = std::make_unique(std::move(x)); + } + return std::move(ret); +} + +base::StatusOr GetRhsValue(sqlite3_index_info* info, + SchemaColumnIds col) { + sqlite3_value* val = nullptr; + + int ret = -1; + for (int i = 0; i < info->nConstraint; ++i) { + auto c = info->aConstraint[i]; + if (sqlite::utils::IsOpEq(c.op) && c.iColumn == col) + ret = sqlite3_vtab_rhs_value(info, i, &val); + } + if (ret != SQLITE_OK) { + return base::ErrStatus("Invalid RHS value."); + } + + return sqlite::utils::SqliteValueToSqlValue(val); +} + +base::StatusOr GetTableFromRhsValue(PerfettoSqlEngine* engine, + sqlite3_index_info* info) { + ASSIGN_OR_RETURN(SqlValue table_name_val, GetRhsValue(info, kTableName)); + if (table_name_val.type != SqlValue::kString) { + return base::ErrStatus("Table name is not a string"); + } + + const std::string table_name = table_name_val.AsString(); + const Table* t = engine->GetTableOrNull(table_name); + if (!t) { + return base::ErrStatus("Table not registered"); + } + return t; +} + +base::StatusOr GetExposedColumns( + const std::string& exposed_cols_str, + const Table* tab) { + ColumnsMap ret; + for (const std::string& col : base::SplitString(exposed_cols_str, ",")) { + std::string col_name = base::TrimWhitespace(col); + auto table_i = tab->ColumnIdxFromName(col_name); + if (!table_i) { + return base::ErrStatus("Didn't find column '%s'", col_name.c_str()); + } + uint32_t schema_idx = + *base::CStringToUInt32( + std::string(col_name.begin() + 1, col_name.end()).c_str()) + + kAdditional; + ret[schema_idx] = static_cast(*table_i); + } + return ret; +} + +base::Status CreateCursorInnerData(Cursor::InnerData* inner, + PerfettoSqlEngine* engine, + const std::string& table_name, + const ColumnsMap& cols) { + // Build the tree for the runtime table if possible + const Table* t = engine->GetTableOrNull(table_name); + if (!t) { + return base::ErrStatus("interval_intersect operator: table not found"); + } + ASSIGN_OR_RETURN(inner->trees, CreateIntervalTrees(t, table_name, cols)); + return base::OkStatus(); +} + +base::Status CreateCursorOuterData(const Table* t, + Cursor::OuterData* outer, + const std::string& table_name) { + outer->it = std::make_unique(t->IterateRows()); + + ASSIGN_OR_RETURN(outer->additional_cols[kId], + ColIdForName(t, "id", table_name)); + ASSIGN_OR_RETURN(outer->additional_cols[kTs], + ColIdForName(t, "ts", table_name)); + ASSIGN_OR_RETURN(outer->additional_cols[kTsEnd], + ColIdForName(t, "ts_end", table_name)); + + return base::OkStatus(); +} + +} // namespace + +int IntervalIntersectOperator::Connect(sqlite3* db, + void* raw_ctx, + int, + const char* const* argv, + sqlite3_vtab** vtab, + char**) { + // No args because we are not creating vtab, not like mipmap op. + if (int ret = sqlite3_declare_vtab(db, kSliceSchema); ret != SQLITE_OK) { + return ret; + } + + // Create the state to access the engine in Filter. + auto ctx = GetContext(raw_ctx); + auto state = std::make_unique(); + state->engine = ctx->engine; + + std::unique_ptr res = std::make_unique(); + res->state = ctx->manager.OnCreate(argv, std::move(state)); + *vtab = res.release(); + return SQLITE_OK; +} + +int IntervalIntersectOperator::Disconnect(sqlite3_vtab* vtab) { + std::unique_ptr tab(GetVtab(vtab)); + sqlite::ModuleStateManager::OnDestroy(tab->state); + return SQLITE_OK; +} + +int IntervalIntersectOperator::BestIndex(sqlite3_vtab* t, + sqlite3_index_info* info) { + int n = info->nConstraint; + + // Validate `table_name` constraint. We expect it to be a constraint on + // equality and on the kTableName column. + base::Status args_status = + sqlite::utils::ValidateFunctionArguments(info, kArgsCount, [](int c) { + return c == SchemaColumnIds::kTableName || + c == SchemaColumnIds::kExposedCols; + }); + + PERFETTO_CHECK(args_status.ok()); + if (!args_status.ok()) { + return SQLITE_CONSTRAINT; + } + + // Find real rows count + PerfettoSqlEngine* engine = Manager::GetState(GetVtab(t)->state)->engine; + SQLITE_ASSIGN_OR_RETURN(t, const Table* tab, + GetTableFromRhsValue(engine, info)); + if (!t) { + return sqlite::utils::SetError(t, "Table not registered"); + } + uint32_t rows_count = tab->row_count(); + info->estimatedRows = rows_count; + + // Count usable constraints among args and required schema. + uint32_t count_usable = 0; + for (int i = 0; i < n; ++i) { + auto c = info->aConstraint[i]; + if (c.iColumn < kAdditional) { + count_usable += c.usable; + } + } + + // There is nothing more to do for only args constraints, which happens for + // the kOuter operator. + if (count_usable == kArgsCount) { + info->idxNum = kOuter; + info->estimatedCost = rows_count; + return SQLITE_OK; + } + + // For inner we expect all constraints to be usable. + PERFETTO_CHECK(count_usable == 4); + if (count_usable != 4) { + return SQLITE_CONSTRAINT; + } + + info->idxNum = kInner; + + // Cost of querying centered interval tree. + info->estimatedCost = log2(rows_count); + + // We are now doing BestIndex of kInner. + + auto ts_found = false; + auto ts_end_found = false; + int argv_index = kAdditional; + auto* s = Manager::GetState(GetVtab(t)->state); + + for (int i = 0; i < n; ++i) { + const auto& c = info->aConstraint[i]; + + // Ignore table_name constraints as we validated it before. + if (c.iColumn == kTableName || c.iColumn == kExposedCols) { + continue; + } + + // We should omit all constraints. + // TODO(mayzner): Remove after we support handling other columns. + auto& usage = info->aConstraintUsage[i]; + usage.omit = true; + + // The constraints we are looking for is `A.ts < B.ts_end AND A.ts_end > + // B.ts`. That is why for `ts` column we can only have `kLt` operator and + // for `ts_end` only `kGt`. + + // Add `ts` constraint. + if (c.iColumn == kTs && !ts_found) { + ts_found = true; + if (!sqlite::utils::IsOpLt(c.op)) { + return sqlite::utils::SetError( + t, "interval_intersect operator: `ts` columns has wrong operation"); + } + // The index is moved by one. + usage.argvIndex = kTs + 1; + continue; + } + + // Add `ts_end` constraint. + if (c.iColumn == kTsEnd && !ts_end_found) { + ts_end_found = true; + if (!sqlite::utils::IsOpGt(c.op)) { + return sqlite::utils::SetError(t, + "interval_intersect operator: `ts_end` " + "columns has wrong operation"); + } + usage.argvIndex = kTsEnd + 1; + continue; + } + + if (c.iColumn >= kAdditional) { + if (!sqlite::utils::IsOpEq(c.op)) { + return sqlite::utils::SetError(t, + "interval_intersect operator: `ts_end` " + "columns has wrong operation"); + } + usage.argvIndex = argv_index++; + s->argv_to_col_map[static_cast(c.iColumn)] = usage.argvIndex; + continue; + } + + return sqlite::utils::SetError( + t, "interval_intersect operator: wrong constraint"); + } + + return SQLITE_OK; +} + +int IntervalIntersectOperator::Open(sqlite3_vtab*, + sqlite3_vtab_cursor** cursor) { + std::unique_ptr c = std::make_unique(); + *cursor = c.release(); + return SQLITE_OK; +} + +int IntervalIntersectOperator::Close(sqlite3_vtab_cursor* cursor) { + std::unique_ptr c(GetCursor(cursor)); + return SQLITE_OK; +} + +int IntervalIntersectOperator::Filter(sqlite3_vtab_cursor* cursor, + int idxNum, + const char*, + int, + sqlite3_value** argv) { + auto* c = GetCursor(cursor); + c->type = static_cast(idxNum); + + auto* t = GetVtab(c->pVtab); + PerfettoSqlEngine* engine = Manager::GetState(t->state)->engine; + + // Table name constraint. + auto table_name_sql_val = sqlite::utils::SqliteValueToSqlValue(argv[0]); + if (table_name_sql_val.type != SqlValue::kString) { + return sqlite::utils::SetError( + t, "interval_intersect operator: table name is not a string"); + } + std::string table_name = table_name_sql_val.AsString(); + + // Exposed columns constraint. + auto exposed_cols_sql_val = sqlite::utils::SqliteValueToSqlValue(argv[1]); + if (exposed_cols_sql_val.type != SqlValue::kString) { + return sqlite::utils::SetError( + t, "interval_intersect operator: exposed columns is not a string"); + } + std::string exposed_cols_str = exposed_cols_sql_val.AsString(); + + // If the cursor has different table cached or differenct cols reset the + // cursor. + if (c->table_name != table_name || exposed_cols_str != c->exposed_cols_str) { + c->inner.trees.Clear(); + c->outer.it.reset(); + } + c->exposed_cols_str = exposed_cols_str; + + if (c->type == kOuter) { + // We expect this function to be called only once per table, so recreate + // this if needed. + c->table = engine->GetTableOrNull(table_name); + c->table_name = table_name; + SQLITE_ASSIGN_OR_RETURN(t, c->outer.additional_cols, + GetExposedColumns(c->exposed_cols_str, c->table)); + SQLITE_RETURN_IF_ERROR( + t, CreateCursorOuterData(c->table, &c->outer, table_name)); + return SQLITE_OK; + } + + PERFETTO_DCHECK(c->type == kInner); + const auto argv_map = Manager::GetState(GetVtab(t)->state)->argv_to_col_map; + + // Create inner cursor if tree doesn't exist. + if (c->inner.trees.size() == 0) { + c->table = engine->GetTableOrNull(table_name); + c->table_name = table_name; + Op::SchemaToTableColumnMap exposed_cols_map; + SQLITE_ASSIGN_OR_RETURN(t, exposed_cols_map, + GetExposedColumns(c->exposed_cols_str, c->table)); + SchemaToTableColumnMap new_map; + for (uint32_t i = 0; i < Op::kSchemaColumnsCount; i++) { + if (argv_map[i]) { + new_map[i] = exposed_cols_map[i]; + } + } + + SQLITE_RETURN_IF_ERROR( + c->pVtab, + CreateCursorInnerData(&c->inner, engine, table_name, new_map)); + } + + // Query |c.tree| on the interval and materialize the results. + auto ts_constraint = sqlite::utils::SqliteValueToSqlValue(argv[kTs]); + if (ts_constraint.type != SqlValue::kLong) { + return sqlite::utils::SetError( + t, "interval_intersect operator: `ts` constraint has to be a number"); + } + + auto ts_end_constraint = sqlite::utils::SqliteValueToSqlValue(argv[kTsEnd]); + if (ts_end_constraint.type != SqlValue::kLong) { + return sqlite::utils::SetError( + t, + "interval_intersect operator: `ts_end` constraint has to be a number"); + } + + uint64_t end = static_cast(ts_constraint.AsLong()); + uint64_t start = static_cast(ts_end_constraint.AsLong()); + + base::Hasher h; + for (uint32_t i = 0; i < argv_map.size(); i++) { + if (argv_map[i]) { + uint32_t x = *argv_map[i]; + HashSqlValue(h, sqlite::utils::SqliteValueToSqlValue(argv[x - 1])); + } + } + + c->inner.Query(start, end, h.digest()); + + return SQLITE_OK; +} + +int IntervalIntersectOperator::Next(sqlite3_vtab_cursor* cursor) { + auto* c = GetCursor(cursor); + + switch (c->type) { + case kInner: + c->inner.index++; + break; + case kOuter: + ++(*c->outer.it); + break; + } + + return SQLITE_OK; +} + +int IntervalIntersectOperator::Eof(sqlite3_vtab_cursor* cursor) { + auto* c = GetCursor(cursor); + + switch (c->type) { + case kInner: + return c->inner.index >= c->inner.query_results.size(); + case kOuter: + return !(*c->outer.it); + } + PERFETTO_FATAL("For GCC"); +} + +int IntervalIntersectOperator::Column(sqlite3_vtab_cursor* cursor, + sqlite3_context* ctx, + int N) { + auto* c = GetCursor(cursor); + + if (c->type == kInner) { + PERFETTO_DCHECK(N == kId); + sqlite::result::Long(ctx, c->inner.GetResultId()); + return SQLITE_OK; + } + + PERFETTO_CHECK(c->type == kOuter); + + switch (N) { + case kTs: + sqlite::result::Long(ctx, c->outer.Get(kTs).AsLong()); + break; + case kTsEnd: + sqlite::result::Long(ctx, c->outer.Get(kTsEnd).AsLong()); + break; + case kId: + sqlite::result::Long(ctx, c->outer.Get(kId).AsLong()); + break; + case kExposedCols: + case kTableName: + return sqlite::utils::SetError( + GetVtab(cursor->pVtab), + "interval_intersect operator: invalid column"); + default: + PERFETTO_DCHECK(N >= kAdditional && N <= kMaxCol); + sqlite::utils::ReportSqlValue(ctx, c->outer.Get(N)); + break; + } + + return SQLITE_OK; +} + +int IntervalIntersectOperator::Rowid(sqlite3_vtab_cursor*, sqlite_int64*) { + return SQLITE_ERROR; +} + +} // namespace perfetto::trace_processor diff --git a/src/trace_processor/perfetto_sql/intrinsics/operators/interval_intersect_operator.h b/src/trace_processor/perfetto_sql/intrinsics/operators/interval_intersect_operator.h new file mode 100644 index 0000000000..896d1a7149 --- /dev/null +++ b/src/trace_processor/perfetto_sql/intrinsics/operators/interval_intersect_operator.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_PERFETTO_SQL_INTRINSICS_OPERATORS_INTERVAL_INTERSECT_OPERATOR_H_ +#define SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_OPERATORS_INTERVAL_INTERSECT_OPERATOR_H_ + +#include +#include +#include +#include +#include + +#include "perfetto/ext/base/hash.h" +#include "perfetto/trace_processor/basic_types.h" +#include "src/trace_processor/containers/bit_vector.h" +#include "src/trace_processor/containers/interval_tree.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 { + +struct IntervalIntersectOperator : sqlite::Module { + static constexpr uint16_t kSchemaColumnsCount = 16; + using SchemaCol = uint16_t; + using SchemaToTableColumnMap = + std::array, kSchemaColumnsCount>; + + enum OperatorType { kInner = 0, kOuter = 1 }; + struct State { + PerfettoSqlEngine* engine; + std::array, kSchemaColumnsCount> argv_to_col_map{}; + }; + + struct Context { + explicit Context(PerfettoSqlEngine* _engine) : engine(_engine) {} + PerfettoSqlEngine* engine; + sqlite::ModuleStateManager manager; + }; + + struct Vtab : sqlite::Module::Vtab { + sqlite::ModuleStateManager::PerVtabState* state; + }; + + struct Cursor : sqlite::Module::Cursor { + using TreesKey = uint64_t; + using TreesMap = base::FlatHashMap, + base::AlreadyHashed>; + + struct InnerData { + TreesMap trees; + SchemaToTableColumnMap additional_cols; + + std::vector query_results; + uint32_t index = 0; + + inline uint32_t GetResultId() const { return query_results[index]; } + inline void Query(uint64_t start, + uint64_t end, + const TreesKey& tree_key) { + query_results.clear(); + index = 0; + auto* tree_ptr = trees.Find(tree_key); + if (!tree_ptr) { + return; + } + (*tree_ptr)->FindOverlaps(start, end, query_results); + } + }; + + struct OuterData { + std::unique_ptr it; + SchemaToTableColumnMap additional_cols; + + inline SqlValue Get(int col) { + return it->Get(*additional_cols[static_cast(col)]); + } + }; + + OperatorType type; + std::string table_name; + std::string exposed_cols_str; + const Table* table = nullptr; + + // Only one of those can be non null. + InnerData inner; + OuterData outer; + }; + + static constexpr auto kType = kEponymousOnly; + static constexpr bool kSupportsWrites = false; + static constexpr bool kDoesOverloadFunctions = false; + + 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*); + + // This needs to happen at the end as it depends on the functions + // defined above. + static constexpr sqlite3_module kModule = CreateModule(); +}; + +} // namespace perfetto::trace_processor + +#endif // SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_OPERATORS_INTERVAL_INTERSECT_OPERATOR_H_ diff --git a/src/trace_processor/perfetto_sql/intrinsics/operators/span_join_operator.cc b/src/trace_processor/perfetto_sql/intrinsics/operators/span_join_operator.cc index 49ad2891f7..2a6063ea0b 100644 --- a/src/trace_processor/perfetto_sql/intrinsics/operators/span_join_operator.cc +++ b/src/trace_processor/perfetto_sql/intrinsics/operators/span_join_operator.cc @@ -229,8 +229,10 @@ base::Status SpanJoinOperatorModule::TableDefinition::Create( if (col.first != SqlValue::Type::kLong && col.first != SqlValue::Type::kNull) { return base::ErrStatus( - "SPAN_JOIN: Invalid type for column '%s' in table %s", - col.second.c_str(), desc.name.c_str()); + "SPAN_JOIN: Invalid type for column '%s' in table %s: expect LONG " + "or NULL, but %s found", + col.second.c_str(), desc.name.c_str(), + sqlite::utils::SqlValueTypeToString(col.first)); } } if (base::Contains(col.second, ",")) { @@ -280,7 +282,7 @@ SpanJoinOperatorModule::TableDefinition::CreateVtabCreateTableSection() const { cols += col.second + ","; } else { cols += col.second + " " + - sqlite::utils::SqlValueTypeToString(col.first) + ","; + sqlite::utils::SqlValueTypeToSqliteTypeName(col.first) + ","; } } return cols; @@ -594,7 +596,6 @@ std::string SpanJoinOperatorModule::TableDefinition::CreateSqlQuery( sql += IsPartitioned() ? base::Join({"`" + partition_col() + "`", "ts"}, ", ") : "ts"; sql += ";"; - PERFETTO_DLOG("%s", sql.c_str()); return sql; } @@ -726,8 +727,6 @@ int SpanJoinOperatorModule::Create(sqlite3* db, base::StackString<1024> create_table_str( kStmt, partition.c_str(), t1_section.c_str(), t2_section.c_str(), primary_key.c_str()); - PERFETTO_DLOG("SPAN_JOIN: create table statement: %s", - create_table_str.c_str()); state->create_table_stmt = create_table_str.ToStdString(); if (int ret = sqlite3_declare_vtab(db, create_table_str.c_str()); ret != SQLITE_OK) { @@ -837,10 +836,9 @@ int SpanJoinOperatorModule::Close(sqlite3_vtab_cursor* cursor) { int SpanJoinOperatorModule::Filter(sqlite3_vtab_cursor* cursor, int, const char* idxStr, - int argc, + int, sqlite3_value** argv) { PERFETTO_TP_TRACE(metatrace::Category::QUERY_DETAILED, "SPAN_JOIN_XFILTER"); - PERFETTO_DLOG("SpanJoin::Filter: argc=%d, idxStr=%s", argc, idxStr); Cursor* c = GetCursor(cursor); Vtab* table = GetVtab(cursor->pVtab); diff --git a/src/trace_processor/perfetto_sql/intrinsics/table_functions/BUILD.gn b/src/trace_processor/perfetto_sql/intrinsics/table_functions/BUILD.gn index 8847ec2847..7be5b26054 100644 --- a/src/trace_processor/perfetto_sql/intrinsics/table_functions/BUILD.gn +++ b/src/trace_processor/perfetto_sql/intrinsics/table_functions/BUILD.gn @@ -41,8 +41,6 @@ source_set("table_functions") { "experimental_slice_layout.h", "flamegraph_construction_algorithms.cc", "flamegraph_construction_algorithms.h", - "interval_intersect.cc", - "interval_intersect.h", "table_info.cc", "table_info.h", ] diff --git a/src/trace_processor/perfetto_sql/intrinsics/table_functions/interval_intersect.cc b/src/trace_processor/perfetto_sql/intrinsics/table_functions/interval_intersect.cc deleted file mode 100644 index 9ce74239e9..0000000000 --- a/src/trace_processor/perfetto_sql/intrinsics/table_functions/interval_intersect.cc +++ /dev/null @@ -1,233 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/interval_intersect.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "perfetto/base/compiler.h" -#include "perfetto/base/logging.h" -#include "perfetto/base/status.h" -#include "perfetto/ext/base/status_or.h" -#include "perfetto/protozero/proto_decoder.h" -#include "perfetto/protozero/proto_utils.h" -#include "perfetto/trace_processor/basic_types.h" -#include "perfetto/trace_processor/status.h" -#include "protos/perfetto/trace_processor/metrics_impl.pbzero.h" -#include "src/trace_processor/containers/string_pool.h" -#include "src/trace_processor/db/column.h" -#include "src/trace_processor/db/table.h" -#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/tables_py.h" -#include "src/trace_processor/util/status_macros.h" - -namespace perfetto::trace_processor { -namespace tables { -IntervalIntersectTable::~IntervalIntersectTable() = default; -} // namespace tables - -namespace { - -using RepeatedDecoder = protos::pbzero::RepeatedBuilderResult::Decoder; -using RepeatedIter = ::protozero::PackedRepeatedFieldIterator< - ::protozero::proto_utils::ProtoWireType::kFixed64, - int64_t>; - -base::StatusOr DecodeArgument(const SqlValue& raw_arg, - const char* debug_name, - bool& parse_error) { - if (raw_arg.type != SqlValue::kBytes) { - return base::ErrStatus( - "interval_intersect: '%s' should be a repeated field", debug_name); - } - protos::pbzero::ProtoBuilderResult::Decoder proto_arg( - static_cast(raw_arg.AsBytes()), raw_arg.bytes_count); - if (!proto_arg.is_repeated()) { - return base::ErrStatus( - "interval_intersect: '%s' is not generated by RepeatedField " - "function", - debug_name); - } - - auto iter = - protos::pbzero::RepeatedBuilderResult::Decoder(proto_arg.repeated()) - .int_values(&parse_error); - if (parse_error) { - return base::ErrStatus( - "interval_intersect: error when parsing '%s' values.", debug_name); - } - - return iter; -} - -struct Interval { - int64_t id; - int64_t ts; - int64_t dur; - - int64_t end() { return ts + dur; } -}; - -struct IntervalsIterator { - RepeatedIter ids; - RepeatedIter tses; - RepeatedIter durs; - - static base::StatusOr Create( - const SqlValue& raw_ids, - const SqlValue& raw_timestamps, - const SqlValue& raw_durs, - bool& parse_error) { - ASSIGN_OR_RETURN(RepeatedIter ids, - DecodeArgument(raw_ids, "ids", parse_error)); - ASSIGN_OR_RETURN(RepeatedIter tses, - DecodeArgument(raw_timestamps, "timestamps", parse_error)); - ASSIGN_OR_RETURN(RepeatedIter durs, - DecodeArgument(raw_durs, "durations", parse_error)); - - return IntervalsIterator{ids, tses, durs}; - } - - void operator++() { - PERFETTO_DCHECK(ids && tses && durs); - ids++; - tses++; - durs++; - } - - Interval operator*() const { return Interval{*ids, *tses, *durs}; } - - explicit operator bool() const { return bool(ids); } -}; - -} // namespace -IntervalIntersect::IntervalIntersect(StringPool* pool) : pool_(pool) {} -IntervalIntersect::~IntervalIntersect() = default; - -Table::Schema IntervalIntersect::CreateSchema() { - return tables::IntervalIntersectTable::ComputeStaticSchema(); -} - -std::string IntervalIntersect::TableName() { - return tables::IntervalIntersectTable::Name(); -} - -uint32_t IntervalIntersect::EstimateRowCount() { - // TODO(mayzner): Give proper estimate. - return 1024; -} - -base::StatusOr> IntervalIntersect::ComputeTable( - const std::vector& args) { - PERFETTO_DCHECK(args.size() == 6); - - // If either of the provided sets of columns is empty return. - auto pred = [](const SqlValue& val) { return val.is_null(); }; - if (std::any_of(args.begin(), args.end(), pred)) { - // We expect that either all left table values are empty or all right table - // values are empty. - if (std::all_of(args.begin(), args.begin() + 3, pred) || - std::all_of(args.begin() + 3, args.begin() + 6, pred)) { - return std::unique_ptr( - std::make_unique(pool_)); - } - return base::ErrStatus( - "interval_intersect: not all of the arguments of one of the tables are " - "null"); - } - - bool parse_error = false; - ASSIGN_OR_RETURN( - IntervalsIterator l_it, - IntervalsIterator::Create(args[0], args[1], args[2], parse_error)); - ASSIGN_OR_RETURN( - IntervalsIterator r_it, - IntervalsIterator::Create(args[3], args[4], args[5], parse_error)); - - // If there are no intervals in one of the tables then there are no intervals - // returned. - if (!l_it || !r_it) { - return std::unique_ptr
    ( - std::make_unique(pool_)); - } - - // We copy |l_it| and |r_it| for the second for loop. - IntervalsIterator l_it_2 = l_it; - IntervalsIterator r_it_2 = r_it; - - auto table = std::make_unique(pool_); - - // Find all intersections where interval from right table started duringan - // interval from left table. - for (Interval l_i = *l_it; l_it && r_it && !parse_error; - ++l_it, l_i = *l_it) { - // If the next |r_i| starts after |l_i| ends, that means that we need to - // go the the next |l_i|, so we need to exit the loop. - for (Interval r_i = *r_it; r_it && r_i.ts < l_i.end() && !parse_error; - ++r_it, r_i = *r_it) { - // We already know (because we are in the loop) that |r_i| started before - // |l_i| ended, we should not intersect only if |r_i| started before - // |l_i|. - if (r_i.ts < l_i.ts) { - continue; - } - - tables::IntervalIntersectTable::Row row; - row.ts = std::max(r_i.ts, l_i.ts); - row.dur = std::min(r_i.end(), l_i.end()) - row.ts; - row.left_id = static_cast(l_i.id); - row.right_id = static_cast(r_i.id); - table->Insert(row); - } - } - - // Find all intersections where interval from the left table started during an - // interval from right table. - for (Interval r_i = *r_it_2; r_it_2 && l_it_2 && !parse_error; - ++r_it_2, r_i = *r_it_2) { - for (Interval l_i = *l_it_2; l_it_2 && l_i.ts < r_i.end() && !parse_error; - ++l_it_2, l_i = *l_it_2) { - // The only difference between this and above algorithm is not - // intersecting if the intervals started at the same time. We do this to - // prevent double counting intervals. - if (l_i.ts <= r_i.ts) { - continue; - } - - tables::IntervalIntersectTable::Row row; - row.ts = std::max(r_i.ts, l_i.ts); - row.dur = std::min(r_i.end(), l_i.end()) - row.ts; - row.left_id = static_cast(l_i.id); - row.right_id = static_cast(r_i.id); - table->Insert(row); - } - } - - if (parse_error) { - return base::ErrStatus( - "interval_intersect: Error in parsing of one of the arguments."); - } - - return std::unique_ptr
    (std::move(table)); -} - -} // namespace perfetto::trace_processor diff --git a/src/trace_processor/perfetto_sql/intrinsics/table_functions/interval_intersect.h b/src/trace_processor/perfetto_sql/intrinsics/table_functions/interval_intersect.h deleted file mode 100644 index ec8ccdf41a..0000000000 --- a/src/trace_processor/perfetto_sql/intrinsics/table_functions/interval_intersect.h +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_TABLE_FUNCTIONS_INTERVAL_INTERSECT_H_ -#define SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_TABLE_FUNCTIONS_INTERVAL_INTERSECT_H_ - -#include -#include -#include -#include - -#include "perfetto/ext/base/status_or.h" -#include "perfetto/trace_processor/basic_types.h" -#include "src/trace_processor/containers/string_pool.h" -#include "src/trace_processor/db/table.h" -#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/static_table_function.h" - -namespace perfetto::trace_processor { - -// An SQL table-function which computes the intersection of intervals from two -// tables. -// -// Given two sets of sorted non-overlapping intervals (with id, timestamp and -// duration) returns intervals that are intersections between those two sets, -// with ids to state what intervals are intersected. -// -// LEFT . - - - - - - . -// RIGHT - - . - - . - - -// Intersection . - . - - . - . -// -// Arguments are RepeatedBuilderResult protos containing a column of -// numerics values: -// 1) |in_left_ids|(uint32_t): Ids from the left table. -// 2) |in_left_tses|(uint64_t): Timestamps (starts) of intervals from -// the left table. -// 3) |in_left_durs|(uint64_t): Durations of intervals -// from the left table. -// 4) |in_right_ids|(uint32_t): Ids from the right table. -// 5) |in_right_tses|(uint64_t): Timestamps (starts) of intervals -// from the right table. -// 6) |in_right_durs|(uint64_t): Durations of intervals from the right table. -// -// NOTES: -// - The first 3 arguments have to have the same number of values. -// - Timestamps in left and right columns have to be sorted. -// -// Returns: -// 1) |ts|: Start of the intersection. -// 2) |dur|: Duration of the intersection. -// 3) |left_id|: Id of the slice that was intersected in the first table. -// 4) |right_id|: Id of the slice that was intersected in the second table. -class IntervalIntersect : public StaticTableFunction { - public: - explicit IntervalIntersect(StringPool*); - virtual ~IntervalIntersect() override; - - // StaticTableFunction implementation. - Table::Schema CreateSchema() override; - std::string TableName() override; - uint32_t EstimateRowCount() override; - base::StatusOr> ComputeTable( - const std::vector& arguments) override; - - private: - StringPool* pool_; -}; - -} // namespace perfetto::trace_processor - -#endif // SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_TABLE_FUNCTIONS_INTERVAL_INTERSECT_H_ diff --git a/src/trace_processor/perfetto_sql/intrinsics/table_functions/tables.py b/src/trace_processor/perfetto_sql/intrinsics/table_functions/tables.py index bfc121763d..aa882dddc9 100644 --- a/src/trace_processor/perfetto_sql/intrinsics/table_functions/tables.py +++ b/src/trace_processor/perfetto_sql/intrinsics/table_functions/tables.py @@ -123,7 +123,7 @@ EXPERIMENTAL_SCHED_UPID_TABLE = Table( python_module=__file__, class_name="ExperimentalSchedUpidTable", - sql_name="experimental_sched_upid", + sql_name="__intrinsic_sched_upid", columns=[ C("upid", CppOptional(CppTableId(PROCESS_TABLE))), ], @@ -139,23 +139,6 @@ ], parent=SLICE_TABLE) -INTERVAL_INTERSECT_TABLE = Table( - python_module=__file__, - class_name="IntervalIntersectTable", - sql_name="__intrinsic_interval_intersect", - columns=[ - C("ts", CppInt64()), - C("dur", CppInt64()), - C("left_id", CppUint32()), - C("right_id", CppUint32()), - C("in_left_ids", CppOptional(CppString()), flags=ColumnFlag.HIDDEN), - C("in_left_tses", CppOptional(CppString()), flags=ColumnFlag.HIDDEN), - C("in_left_durs", CppOptional(CppString()), flags=ColumnFlag.HIDDEN), - C("in_right_ids", CppOptional(CppString()), flags=ColumnFlag.HIDDEN), - C("in_right_tses", CppOptional(CppString()), flags=ColumnFlag.HIDDEN), - C("in_right_durs", CppOptional(CppString()), flags=ColumnFlag.HIDDEN), - ]) - DFS_WEIGHT_BOUNDED_TABLE = Table( python_module=__file__, class_name="DfsWeightBoundedTable", @@ -193,6 +176,5 @@ EXPERIMENTAL_COUNTER_DUR_TABLE, EXPERIMENTAL_SCHED_UPID_TABLE, EXPERIMENTAL_SLICE_LAYOUT_TABLE, - INTERVAL_INTERSECT_TABLE, TABLE_INFO_TABLE, ] diff --git a/src/trace_processor/perfetto_sql/intrinsics/types/BUILD.gn b/src/trace_processor/perfetto_sql/intrinsics/types/BUILD.gn new file mode 100644 index 0000000000..628f05f5a5 --- /dev/null +++ b/src/trace_processor/perfetto_sql/intrinsics/types/BUILD.gn @@ -0,0 +1,35 @@ +# Copyright (C) 2022 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("../../../../../gn/perfetto_tp_tables.gni") +import("../../../../../gn/test.gni") + +assert(enable_perfetto_trace_processor_sqlite) + +source_set("types") { + sources = [ + "array.h", + "node.h", + "partitioned_intervals.h", + "row_dataframe.h", + "struct.h", + "value.h", + ] + deps = [ + "../../../../../gn:default_deps", + "../../../../../include/perfetto/ext/base:base", + "../../../../../include/perfetto/trace_processor:basic_types", + "../../../containers", + ] +} diff --git a/src/trace_processor/perfetto_sql/intrinsics/types/array.h b/src/trace_processor/perfetto_sql/intrinsics/types/array.h new file mode 100644 index 0000000000..14829d5569 --- /dev/null +++ b/src/trace_processor/perfetto_sql/intrinsics/types/array.h @@ -0,0 +1,32 @@ +/* + * 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_TYPES_ARRAY_H_ +#define SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_TYPES_ARRAY_H_ + +#include +#include +#include + +namespace perfetto::trace_processor::perfetto_sql { + +using IntArray = std::vector; +using DoubleArray = std::vector; +using StringArray = std::vector; + +} // namespace perfetto::trace_processor::perfetto_sql + +#endif // SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_TYPES_ARRAY_H_ diff --git a/src/trace_processor/perfetto_sql/intrinsics/types/node.h b/src/trace_processor/perfetto_sql/intrinsics/types/node.h new file mode 100644 index 0000000000..c0e3f798c0 --- /dev/null +++ b/src/trace_processor/perfetto_sql/intrinsics/types/node.h @@ -0,0 +1,32 @@ +/* + * 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_TYPES_NODE_H_ +#define SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_TYPES_NODE_H_ + +#include +#include + +namespace perfetto::trace_processor::perfetto_sql { + +struct Node { + std::vector outgoing_edges; +}; +using Graph = std::vector; + +} // namespace perfetto::trace_processor::perfetto_sql + +#endif // SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_TYPES_NODE_H_ diff --git a/src/trace_processor/perfetto_sql/intrinsics/types/partitioned_intervals.h b/src/trace_processor/perfetto_sql/intrinsics/types/partitioned_intervals.h new file mode 100644 index 0000000000..f42e5eb256 --- /dev/null +++ b/src/trace_processor/perfetto_sql/intrinsics/types/partitioned_intervals.h @@ -0,0 +1,47 @@ +/* + * 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_TYPES_PARTITIONED_INTERVALS_H_ +#define SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_TYPES_PARTITIONED_INTERVALS_H_ + +#include +#include +#include +#include "perfetto/ext/base/flat_hash_map.h" +#include "perfetto/trace_processor/basic_types.h" +#include "src/trace_processor/containers/interval_tree.h" + +namespace perfetto::trace_processor::perfetto_sql { + +using PartitionToIntervalsMap = + base::FlatHashMap, + base::AlreadyHashed>; + +using PartitionToValuesMap = base:: + FlatHashMap, base::AlreadyHashed>; + +struct PartitionedTable { + static constexpr char kName[] = "INTERVAL_TREE_PARTITIONS"; + PartitionToIntervalsMap intervals; + PartitionToValuesMap partition_values; + + std::vector partition_column_names; +}; + +} // namespace perfetto::trace_processor::perfetto_sql + +#endif // SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_TYPES_PARTITIONED_INTERVALS_H_ diff --git a/src/trace_processor/perfetto_sql/intrinsics/types/row_dataframe.h b/src/trace_processor/perfetto_sql/intrinsics/types/row_dataframe.h new file mode 100644 index 0000000000..43f546c806 --- /dev/null +++ b/src/trace_processor/perfetto_sql/intrinsics/types/row_dataframe.h @@ -0,0 +1,58 @@ +/* + * 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_TYPES_ROW_DATAFRAME_H_ +#define SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_TYPES_ROW_DATAFRAME_H_ + +#include +#include +#include +#include +#include + +#include "src/trace_processor/perfetto_sql/intrinsics/types/array.h" +#include "src/trace_processor/perfetto_sql/intrinsics/types/value.h" + +namespace perfetto::trace_processor::perfetto_sql { + +// Data structure to allow easy exchange of "table-like" data between SQL and +// C++ code. Allows fast lookup of rows by id (if an id column exists). +struct RowDataframe { + perfetto_sql::StringArray column_names; + std::vector id_to_cell_index; + // Cell = a value at a row + column index. + std::vector cells; + std::optional id_column_index; + + const perfetto_sql::Value* RowForId(uint32_t id) const { + return cells.data() + id_to_cell_index[id]; + } + + std::optional FindColumnWithName(const std::string& name) { + auto it = std::find(column_names.begin(), column_names.end(), name); + return it == column_names.end() ? std::nullopt + : std::make_optional(static_cast( + it - column_names.begin())); + } + + uint32_t size() const { + return static_cast(cells.size() / column_names.size()); + } +}; + +} // namespace perfetto::trace_processor::perfetto_sql + +#endif // SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_TYPES_ROW_DATAFRAME_H_ diff --git a/src/trace_processor/perfetto_sql/intrinsics/types/struct.h b/src/trace_processor/perfetto_sql/intrinsics/types/struct.h new file mode 100644 index 0000000000..00011eddf6 --- /dev/null +++ b/src/trace_processor/perfetto_sql/intrinsics/types/struct.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_TYPES_STRUCT_H_ +#define SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_TYPES_STRUCT_H_ + +#include +#include +#include +#include + +#include "src/trace_processor/perfetto_sql/intrinsics/types/value.h" + +namespace perfetto::trace_processor::perfetto_sql { + +struct Struct { + static constexpr uint32_t kMaxFields = 8; + std::array, kMaxFields> fields; + uint32_t field_count = 0; +}; + +} // namespace perfetto::trace_processor::perfetto_sql + +#endif // SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_TYPES_STRUCT_H_ diff --git a/src/trace_processor/perfetto_sql/intrinsics/types/value.h b/src/trace_processor/perfetto_sql/intrinsics/types/value.h new file mode 100644 index 0000000000..026ee23085 --- /dev/null +++ b/src/trace_processor/perfetto_sql/intrinsics/types/value.h @@ -0,0 +1,45 @@ +/* + * 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_TYPES_VALUE_H_ +#define SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_TYPES_VALUE_H_ + +#include +#include +#include + +namespace perfetto::trace_processor::perfetto_sql { + +using Value = std::variant; + +template +inline constexpr uint32_t ValueIndex() { + if constexpr (std::is_same_v) { + return 0; + } else if constexpr (std::is_same_v) { + return 1; + } else if constexpr (std::is_same_v) { + return 2; + } else if constexpr (std::is_same_v) { + return 3; + } else { + static_assert(!sizeof(T*), "T is not supported"); + } +} + +} // namespace perfetto::trace_processor::perfetto_sql + +#endif // SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_TYPES_VALUE_H_ diff --git a/src/trace_processor/perfetto_sql/prelude/views.sql b/src/trace_processor/perfetto_sql/prelude/views.sql index edcefe8fec..6dcbae25c3 100644 --- a/src/trace_processor/perfetto_sql/prelude/views.sql +++ b/src/trace_processor/perfetto_sql/prelude/views.sql @@ -53,3 +53,8 @@ SELECT WHEN 'json' THEN string_value ELSE NULL END AS display_value FROM internal_args; + +CREATE VIEW perf_session +AS +SELECT *, id AS perf_session_id +FROM __intrinsic_perf_session; \ No newline at end of file diff --git a/src/trace_processor/perfetto_sql/stdlib/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/BUILD.gn index 349e2f6954..d351a31515 100644 --- a/src/trace_processor/perfetto_sql/stdlib/BUILD.gn +++ b/src/trace_processor/perfetto_sql/stdlib/BUILD.gn @@ -20,15 +20,16 @@ assert(enable_perfetto_trace_processor_sqlite) perfetto_amalgamated_sql_header("stdlib") { deps = [ "android", + "callstacks", "chrome:chrome_sql", "common", "counters", - "cpu", "deprecated/v42/common", + "export", "graphs", "intervals", "linux", - "memory", + "metasql", "pkvm", "prelude", "sched", @@ -36,7 +37,7 @@ perfetto_amalgamated_sql_header("stdlib") { "stack_trace", "time", "v8", - "viz/summary", + "viz", "wattson", ] generated_header = "stdlib.h" diff --git a/src/trace_processor/perfetto_sql/stdlib/android/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/android/BUILD.gn index feba87c6de..f51ef7577d 100644 --- a/src/trace_processor/perfetto_sql/stdlib/android/BUILD.gn +++ b/src/trace_processor/perfetto_sql/stdlib/android/BUILD.gn @@ -17,7 +17,10 @@ import("../../../../../gn/perfetto_sql.gni") perfetto_sql_source_set("android") { deps = [ "auto", + "cpu", "frames", + "gpu", + "memory", "startup", "winscope", ] @@ -47,5 +50,6 @@ perfetto_sql_source_set("android") { "statsd.sql", "suspend.sql", "thread.sql", + "version.sql", ] } diff --git a/src/trace_processor/perfetto_sql/stdlib/android/auto/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/android/auto/BUILD.gn index bf52d69d51..7c89f2575f 100644 --- a/src/trace_processor/perfetto_sql/stdlib/android/auto/BUILD.gn +++ b/src/trace_processor/perfetto_sql/stdlib/android/auto/BUILD.gn @@ -15,7 +15,5 @@ import("../../../../../../gn/perfetto_sql.gni") perfetto_sql_source_set("auto") { - sources = [ - "multiuser.sql", - ] + sources = [ "multiuser.sql" ] } diff --git a/src/trace_processor/perfetto_sql/stdlib/android/auto/multiuser.sql b/src/trace_processor/perfetto_sql/stdlib/android/auto/multiuser.sql index ea95dcc4e2..2b408ef9c9 100644 --- a/src/trace_processor/perfetto_sql/stdlib/android/auto/multiuser.sql +++ b/src/trace_processor/perfetto_sql/stdlib/android/auto/multiuser.sql @@ -64,7 +64,7 @@ FROM ( FROM android_startups UNION SELECT - slice.ts as event_end_time, + slice.ts + slice.dur as event_end_time, slice.name as event_end_name FROM slice WHERE slice.name GLOB "finishUserStopped-10*" diff --git a/src/trace_processor/perfetto_sql/stdlib/android/binder.sql b/src/trace_processor/perfetto_sql/stdlib/android/binder.sql index e168970871..2991dee4f4 100644 --- a/src/trace_processor/perfetto_sql/stdlib/android/binder.sql +++ b/src/trace_processor/perfetto_sql/stdlib/android/binder.sql @@ -16,6 +16,7 @@ INCLUDE PERFETTO MODULE android.process_metadata; INCLUDE PERFETTO MODULE android.suspend; +INCLUDE PERFETTO MODULE slices.flow; -- Count Binder transactions per process. CREATE PERFETTO VIEW android_binder_metrics_by_process( @@ -55,11 +56,9 @@ WITH maybe_broken_binder_txn AS ( ON ancestor.id = slice.parent_id WHERE ancestor.name = 'binder transaction' GROUP BY ancestor.id -), nested_binder_txn AS ( - -- Detect the non-broken cases which are just nested binder txns - SELECT slice_out AS id - FROM maybe_broken_binder_txn - JOIN following_flow(maybe_broken_binder_txn.id) + ), nested_binder_txn AS ( + -- Detect the non-broken cases which are just nested binder txns + SELECT DISTINCT root_node_id AS id FROM _slice_following_flow!(maybe_broken_binder_txn) ), broken_binder_txn AS ( -- Exclude the nested txns from the 'maybe broken' set SELECT * FROM maybe_broken_binder_txn @@ -158,7 +157,7 @@ GROUP BY binder_txn_id, binder_reply_id; -CREATE TABLE _oom_score AS +CREATE PERFETTO TABLE _oom_score AS SELECT process.upid, CAST(c.value AS INT) AS value, @@ -169,7 +168,7 @@ CREATE TABLE _oom_score AS JOIN process USING (upid) WHERE t.name = 'oom_score_adj'; -CREATE INDEX _oom_score_idx ON _oom_score(upid, ts); +CREATE PERFETTO INDEX _oom_score_idx ON _oom_score(upid, ts); -- Breakdown synchronous binder transactions per txn. -- It returns data about the client and server ends of every binder transaction. @@ -477,7 +476,7 @@ FROM all_binder LEFT JOIN android_process_metadata client_process_metadata ON all_binder.client_upid = client_process_metadata.upid LEFT JOIN android_process_metadata server_process_metadata - ON all_binder.server_upid = client_process_metadata.upid; + ON all_binder.server_upid = server_process_metadata.upid; -- Returns a DAG of all outgoing binder txns from a process. -- The roots of the graph are the threads making the txns and the graph flows from: diff --git a/src/trace_processor/perfetto_sql/stdlib/android/broadcasts.sql b/src/trace_processor/perfetto_sql/stdlib/android/broadcasts.sql index 6467ea7aeb..b3d2e70718 100644 --- a/src/trace_processor/perfetto_sql/stdlib/android/broadcasts.sql +++ b/src/trace_processor/perfetto_sql/stdlib/android/broadcasts.sql @@ -36,9 +36,9 @@ CREATE PERFETTO TABLE _android_broadcasts_minsdk_u( -- Name of the process the broadcast was sent to. process_name STRING, -- Pid of the process the broadcast was sent to. - pid STRING, + pid INT, -- Upid of the process the broadcast was sent to. - upid STRING, + upid INT, -- Id of the broacast queue the broadcast was dispatched from. queue_id INT, -- Slice id of the broadcast dispatch. @@ -64,8 +64,10 @@ WITH ), broadcast_process_running AS ( SELECT + slice.id AS id, slice.ts, slice.dur, + broadcast_queues.queue_id, _extract_broadcast_process_name(slice.name) AS process_name, CAST(str_split(str_split(str_split(slice.name, '/', 0), ' ', 1), ':', 0) AS INT) AS pid, queue_id @@ -73,23 +75,28 @@ WITH JOIN broadcast_queues ON broadcast_queues.id = slice.track_id WHERE slice.name GLOB '* running' + ), + broadcast_intent_action AS ( + SELECT + str_split(str_split(slice.name, '/', 0), ' ', 1) AS intent_action, + slice.parent_id, + slice.id AS intent_id, + slice.ts AS intent_ts, + slice.track_id AS track_id, + slice.dur AS intent_dur + FROM slice + WHERE slice.name GLOB '* scheduled' ) -SELECT - str_split(str_split(slice.name, '/', 0), ' ', 1) AS intent_action, - process_name, - pid, - _pid_to_upid(pid, slice.ts) AS upid, - queue_id, - slice.id, - slice.ts, - slice.dur, - slice.track_id -FROM broadcast_process_running -JOIN broadcast_queues - USING (queue_id) -JOIN slice - ON ( - broadcast_process_running.ts < slice.ts - AND slice.ts < broadcast_process_running.ts + broadcast_process_running.dur - AND slice.track_id = broadcast_queues.id) -WHERE slice.name GLOB '* scheduled'; + SELECT + broadcast_intent_action.intent_action, + broadcast_process_running.process_name, + broadcast_process_running.pid, + _pid_to_upid(broadcast_process_running.pid, broadcast_intent_action.intent_ts) AS upid, + broadcast_process_running.queue_id, + broadcast_intent_action.intent_id AS id, + broadcast_intent_action.intent_ts AS ts, + broadcast_intent_action.intent_dur AS dur, + broadcast_intent_action.track_id + FROM broadcast_intent_action + JOIN broadcast_process_running + ON parent_id = id; diff --git a/src/trace_processor/perfetto_sql/stdlib/android/cpu/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/android/cpu/BUILD.gn new file mode 100644 index 0000000000..3afe0dfe28 --- /dev/null +++ b/src/trace_processor/perfetto_sql/stdlib/android/cpu/BUILD.gn @@ -0,0 +1,19 @@ +# 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("../../../../../../gn/perfetto_sql.gni") + +perfetto_sql_source_set("cpu") { + sources = [ "cluster_type.sql" ] +} diff --git a/src/trace_processor/perfetto_sql/stdlib/android/cpu/cluster_type.sql b/src/trace_processor/perfetto_sql/stdlib/android/cpu/cluster_type.sql new file mode 100644 index 0000000000..b5bc55e07e --- /dev/null +++ b/src/trace_processor/perfetto_sql/stdlib/android/cpu/cluster_type.sql @@ -0,0 +1,83 @@ +-- +-- 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. + +-- Uses cluster_id which has been calculated using the cpu_capacity in order +-- to determine the cluster type for cpus with 2, 3 or 4 clusters +-- indicating whether they are "little", "medium" or "big". + +INCLUDE PERFETTO MODULE intervals.overlap; + +CREATE PERFETTO TABLE _cores AS +WITH data(cluster_id, cluster_type, cluster_count) AS ( + VALUES + (0, 'little', 2), (1, 'big', 2), + (0, 'little', 3), (1, 'medium', 3), (2, 'big', 3), + (0, 'little', 4), (1, 'medium', 4), (2, 'medium', 4), (3, 'big', 4) +) +SELECT * FROM data; + +-- Stores the mapping of a cpu to its cluster type - e.g. little, medium, big. +-- This cluster type is determined by initially using cpu_capacity from sysfs +-- and grouping clusters with identical capacities, ordered by size. +-- In the case that capacities are not present, max frequency is used instead. +-- If nothing is avaiable, NULL is returned. +CREATE PERFETTO TABLE android_cpu_cluster_mapping ( + -- Alias of `cpu.ucpu`. + ucpu INT, + -- Alias of `cpu.cpu`. + cpu INT, + -- The cluster type of the CPU. + cluster_type STRING +) AS +SELECT + ucpu, + cpu, + _cores.cluster_type AS cluster_type +FROM + cpu +LEFT JOIN _cores ON _cores.cluster_id = cpu.cluster_id +AND _cores.cluster_count = (SELECT COUNT(DISTINCT cluster_id) FROM cpu); + +-- The count of active CPUs with a given cluster type over time. +CREATE PERFETTO FUNCTION _active_cpu_count_for_cluster_type( + -- Type of the CPU cluster as reported by android_cpu_cluster_mapping. Usually 'little', 'medium' or 'big'. + cluster_type STRING +) RETURNS TABLE( + -- Timestamp when the number of active CPU changed. + ts LONG, + -- Number of active CPUs, covering the range from this timestamp to the next + -- row's timestamp. + active_cpu_count LONG +) AS +WITH +-- Materialise the relevant clusters to avoid calling a function for each row of the sched table. +cluster AS ( + SELECT ucpu + FROM android_cpu_cluster_mapping + WHERE cluster_type = $cluster_type +), +-- Filter sched events corresponding to running tasks. +-- utid=0 is the swapper thread / idle task. +tasks AS ( + SELECT ts, dur + FROM sched + WHERE + ucpu IN (SELECT ucpu FROM cluster) + AND utid != 0 +) +SELECT + ts, value as active_cpu_count +FROM intervals_overlap_count!(tasks, ts, dur) +ORDER BY ts; diff --git a/src/trace_processor/perfetto_sql/stdlib/android/dvfs.sql b/src/trace_processor/perfetto_sql/stdlib/android/dvfs.sql index d6a43d6a56..91686b3b18 100644 --- a/src/trace_processor/perfetto_sql/stdlib/android/dvfs.sql +++ b/src/trace_processor/perfetto_sql/stdlib/android/dvfs.sql @@ -68,9 +68,9 @@ CREATE PERFETTO TABLE android_dvfs_counter_stats( -- Counter name on which all the other values are aggregated on. name STRING, -- Max of all counter values for the counter name. - max INT, + max DOUBLE, -- Min of all counter values for the counter name. - min INT, + min DOUBLE, -- Duration between the first and last counter value for the counter name. dur INT, -- Weighted avergate of all the counter values for the counter name. diff --git a/src/trace_processor/perfetto_sql/stdlib/android/frames/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/android/frames/BUILD.gn index 306758a058..4c1708bdd2 100644 --- a/src/trace_processor/perfetto_sql/stdlib/android/frames/BUILD.gn +++ b/src/trace_processor/perfetto_sql/stdlib/android/frames/BUILD.gn @@ -16,6 +16,7 @@ import("../../../../../../gn/perfetto_sql.gni") perfetto_sql_source_set("frames") { sources = [ + "jank_type.sql", "per_frame_metrics.sql", "timeline.sql", "timeline_maxsdk28.sql", diff --git a/src/trace_processor/perfetto_sql/stdlib/android/frames/jank_type.sql b/src/trace_processor/perfetto_sql/stdlib/android/frames/jank_type.sql new file mode 100644 index 0000000000..156c5b9da9 --- /dev/null +++ b/src/trace_processor/perfetto_sql/stdlib/android/frames/jank_type.sql @@ -0,0 +1,38 @@ +-- +-- 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. + +-- Categorizes whether the jank was caused by Surface Flinger +CREATE PERFETTO FUNCTION android_is_sf_jank_type( + -- the jank type + -- from args.display_value with key = "Jank type" + jank_type STRING) +-- True when the jank type represents sf jank +RETURNS BOOL AS +SELECT + $jank_type GLOB '*SurfaceFlinger CPU Deadline Missed*' + OR $jank_type GLOB '*SurfaceFlinger GPU Deadline Missed*' + OR $jank_type GLOB '*SurfaceFlinger Scheduling*' + OR $jank_type GLOB '*Prediction Error*' + OR $jank_type GLOB '*Display HAL*'; + + +-- Categorizes whether the jank was caused by the app +CREATE PERFETTO FUNCTION android_is_app_jank_type( + -- the jank type + -- from args.display_value with key = "Jank type" + jank_type STRING) +-- True when the jank type represents app jank +RETURNS BOOL AS +SELECT $jank_type GLOB '*App Deadline Missed*'; \ No newline at end of file diff --git a/src/trace_processor/perfetto_sql/stdlib/android/frames/timeline_maxsdk28.sql b/src/trace_processor/perfetto_sql/stdlib/android/frames/timeline_maxsdk28.sql index b8fad07af3..0c5ba2656e 100644 --- a/src/trace_processor/perfetto_sql/stdlib/android/frames/timeline_maxsdk28.sql +++ b/src/trace_processor/perfetto_sql/stdlib/android/frames/timeline_maxsdk28.sql @@ -40,15 +40,21 @@ CREATE PERFETTO TABLE _frames_maxsdk_28( -- "maxsdk28" sdk STRING ) AS -WITH do_frames AS ( +WITH choreographer AS ( + SELECT id + FROM slice + WHERE name = 'Choreographer#doFrame' +), +do_frames AS ( SELECT id, ts, LEAD(ts, 1, TRACE_END()) OVER (PARTITION BY upid ORDER BY ts) AS next_do_frame, utid, upid - FROM thread_slice - WHERE name = 'Choreographer#doFrame' AND is_main_thread = 1 + FROM choreographer + JOIN thread_slice USING (id) + WHERE is_main_thread = 1 ORDER BY ts ), draw_frames AS ( diff --git a/src/trace_processor/perfetto_sql/stdlib/android/freezer.sql b/src/trace_processor/perfetto_sql/stdlib/android/freezer.sql index 9ccdca9a81..77af316771 100644 --- a/src/trace_processor/perfetto_sql/stdlib/android/freezer.sql +++ b/src/trace_processor/perfetto_sql/stdlib/android/freezer.sql @@ -100,7 +100,7 @@ CREATE PERFETTO TABLE android_freezer_events ( -- Unfreeze reason Integer. unfreeze_reason_int INT, -- Unfreeze reason String. - unfreeze_reason_str INT + unfreeze_reason_str STRING ) AS WITH diff --git a/src/trace_processor/perfetto_sql/stdlib/android/garbage_collection.sql b/src/trace_processor/perfetto_sql/stdlib/android/garbage_collection.sql index 84816570a4..06e4ae97aa 100644 --- a/src/trace_processor/perfetto_sql/stdlib/android/garbage_collection.sql +++ b/src/trace_processor/perfetto_sql/stdlib/android/garbage_collection.sql @@ -137,19 +137,19 @@ CREATE PERFETTO TABLE android_garbage_collection_events ( -- Upid of process running garbage collection. upid INT, -- Name of thread running garbage collection. - thread_name INT, + thread_name STRING, -- Name of process running garbage collection. - process_name INT, + process_name STRING, -- Type of garbage collection. gc_type STRING, -- Whether gargage collection is mark compact or copying. is_mark_compact INT, -- MB reclaimed after garbage collection. - reclaimed_mb INT, + reclaimed_mb DOUBLE, -- Minimum heap size in MB during garbage collection. - min_heap_mb INT, + min_heap_mb DOUBLE, -- Maximum heap size in MB during garbage collection. - max_heap_mb INT, + max_heap_mb DOUBLE, -- Garbage collection id. gc_id INT, -- Garbage collection timestamp. diff --git a/src/trace_processor/perfetto_sql/stdlib/android/gpu/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/android/gpu/BUILD.gn new file mode 100644 index 0000000000..09a5e85917 --- /dev/null +++ b/src/trace_processor/perfetto_sql/stdlib/android/gpu/BUILD.gn @@ -0,0 +1,22 @@ +# 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("../../../../../../gn/perfetto_sql.gni") + +perfetto_sql_source_set("gpu") { + sources = [ + "frequency.sql", + "memory.sql", + ] +} diff --git a/src/trace_processor/perfetto_sql/stdlib/android/gpu/frequency.sql b/src/trace_processor/perfetto_sql/stdlib/android/gpu/frequency.sql new file mode 100644 index 0000000000..e2511eba82 --- /dev/null +++ b/src/trace_processor/perfetto_sql/stdlib/android/gpu/frequency.sql @@ -0,0 +1,42 @@ +-- +-- 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 counters.intervals; + +-- GPU frequency counter per GPU. +CREATE PERFETTO TABLE android_gpu_frequency( + -- Timestamp + ts INT, + -- Duration + dur INT, + -- GPU id. Joinable with `gpu_counter_track.gpu_id`. + gpu_id INT, + -- GPU frequency + gpu_freq INT +) AS +SELECT + ts, + dur, + gpu_id, + cast_int!(value) AS gpu_freq +FROM counter_leading_intervals!(( + SELECT c.* + FROM counter c + JOIN gpu_counter_track t + ON t.id = c.track_id AND t.name = 'gpufreq' + WHERE gpu_id IS NOT NULL +)) +JOIN gpu_counter_track t ON t.id = track_id; diff --git a/src/trace_processor/perfetto_sql/stdlib/cpu/cpus.sql b/src/trace_processor/perfetto_sql/stdlib/android/gpu/memory.sql similarity index 61% rename from src/trace_processor/perfetto_sql/stdlib/cpu/cpus.sql rename to src/trace_processor/perfetto_sql/stdlib/android/gpu/memory.sql index 0d13879a21..3eb8e11e80 100644 --- a/src/trace_processor/perfetto_sql/stdlib/cpu/cpus.sql +++ b/src/trace_processor/perfetto_sql/stdlib/android/gpu/memory.sql @@ -13,16 +13,23 @@ -- See the License for the specific language governing permissions and -- limitations under the License. -INCLUDE PERFETTO MODULE cpu.size; +INCLUDE PERFETTO MODULE linux.memory.general; --- All of the CPUs with their core type as a descriptive size ('little', 'mid', 'big', etc). -CREATE PERFETTO TABLE cpu_core_types( - -- Index of the CPU. - cpu_index INT, - -- A descriptive size ('little', 'mid', 'big', etc) or NULL if we have insufficient information. - size STRING +-- Counter for GPU memory per process with duration. +CREATE PERFETTO TABLE android_gpu_memory_per_process( + -- Timestamp + ts INT, + -- Duration + dur INT, + -- Upid of the process + upid INT, + -- GPU memory + gpu_memory INT ) AS SELECT - cpu as cpu_index, - cpu_guess_core_type(cpu) AS size -FROM _ranked_cpus; \ No newline at end of file + ts, + dur, + upid, + cast_int!(value) AS gpu_memory +FROM _all_counters_per_process +WHERE name = 'GPU Memory'; diff --git a/src/trace_processor/perfetto_sql/stdlib/android/input.sql b/src/trace_processor/perfetto_sql/stdlib/android/input.sql index a1d7b114d7..379c56ac4c 100644 --- a/src/trace_processor/perfetto_sql/stdlib/android/input.sql +++ b/src/trace_processor/perfetto_sql/stdlib/android/input.sql @@ -76,17 +76,17 @@ CREATE PERFETTO TABLE android_input_events ( -- Tid of thread receiving the input event. tid INT, -- Name of thread receiving the input event. - thread_name INT, + thread_name STRING, -- Pid of process receiving the input event. pid INT, -- Name of process receiving the input event. - process_name INT, + process_name STRING, -- Input event type. See InputTransport.h: InputMessage#Type - event_type INT, + event_type STRING, -- Input event sequence number, monotonically increasing for an event channel and pid. - event_seq INT, + event_seq STRING, -- Input event channel name. - event_channel INT, + event_channel STRING, -- Thread track id of input event dispatching thread. dispatch_track_id INT, -- Timestamp input event was dispatched. @@ -120,7 +120,7 @@ SELECT receive.dur AS receive_dur, receive.track_id AS receive_track_id FROM (SELECT * FROM _input_message_sent WHERE thread_name = 'InputDispatcher') dispatch -JOIN (SELECT * FROM _input_message_received WHERE event_type != '0x2') receive +JOIN (SELECT * FROM _input_message_received WHERE event_type NOT IN ('0x2', 'FINISHED')) receive ON REPLACE(receive.event_channel, '(client)', '(server)') = dispatch.event_channel AND dispatch.event_seq = receive.event_seq @@ -128,5 +128,64 @@ JOIN (SELECT * FROM _input_message_sent WHERE thread_name != 'InputDispatcher') ON REPLACE(finish.event_channel, '(client)', '(server)') = dispatch.event_channel AND dispatch.event_seq = finish.event_seq -JOIN (SELECT * FROM _input_message_received WHERE event_type = '0x2') finish_ack +JOIN (SELECT * FROM _input_message_received WHERE event_type IN ('0x2', 'FINISHED')) finish_ack ON finish_ack.event_channel = dispatch.event_channel AND dispatch.event_seq = finish_ack.event_seq; + +-- Key events processed by the Android framework (from android.input.inputevent data source). +CREATE PERFETTO VIEW android_key_events( + -- ID of the trace entry + id INT, + -- The randomly-generated ID associated with each input event processed + -- by Android Framework, used to track the event through the input pipeline + event_id INT, + -- The timestamp of when the input event was processed by the system + ts INT, + -- Details of the input event parsed from the proto message + arg_set_id INT +) AS +SELECT + id, + event_id, + ts, + arg_set_id +FROM __intrinsic_android_key_events; + +-- Motion events processed by the Android framework (from android.input.inputevent data source). +CREATE PERFETTO VIEW android_motion_events( + -- ID of the trace entry + id INT, + -- The randomly-generated ID associated with each input event processed + -- by Android Framework, used to track the event through the input pipeline + event_id INT, + -- The timestamp of when the input event was processed by the system + ts INT, + -- Details of the input event parsed from the proto message + arg_set_id INT +) AS +SELECT + id, + event_id, + ts, + arg_set_id +FROM __intrinsic_android_motion_events; + +-- Input event dispatching information in Android (from android.input.inputevent data source). +CREATE PERFETTO VIEW android_input_event_dispatch( + -- ID of the trace entry + id INT, + -- Event ID of the input event that was dispatched + event_id INT, + -- Extra args parsed from the proto message + arg_set_id INT, + -- Vsync ID that identifies the state of the windows during which the dispatch decision was made + vsync_id INT, + -- Window ID of the window receiving the event + window_id INT +) AS +SELECT + id, + event_id, + arg_set_id, + vsync_id, + window_id +FROM __intrinsic_android_input_event_dispatch; diff --git a/src/trace_processor/perfetto_sql/stdlib/android/memory/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/android/memory/BUILD.gn new file mode 100644 index 0000000000..43382e5880 --- /dev/null +++ b/src/trace_processor/perfetto_sql/stdlib/android/memory/BUILD.gn @@ -0,0 +1,26 @@ +# 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("../../../../../../gn/perfetto_sql.gni") + +perfetto_sql_source_set("memory") { + deps = [ + "heap_graph", + "heap_profile", + ] + sources = [ + "dmabuf.sql", + "process.sql", + ] +} diff --git a/src/trace_processor/perfetto_sql/stdlib/android/memory/dmabuf.sql b/src/trace_processor/perfetto_sql/stdlib/android/memory/dmabuf.sql new file mode 100644 index 0000000000..1576e9c689 --- /dev/null +++ b/src/trace_processor/perfetto_sql/stdlib/android/memory/dmabuf.sql @@ -0,0 +1,100 @@ +-- +-- 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.binder; +INCLUDE PERFETTO MODULE slices.with_context; + +-- Raw ftrace events +CREATE PERFETTO TABLE _raw_dmabuf_events AS +SELECT + (SELECT int_value FROM args WHERE arg_set_id = c.arg_set_id AND key = 'inode') AS inode, + tt.utid, + c.ts, + CAST(c.value AS INT) AS buf_size +FROM thread_counter_track tt + JOIN counter c ON c.track_id = tt.id +WHERE tt.name = 'mem.dma_heap_change'; + +-- gralloc binder reply slices +CREATE PERFETTO TABLE _gralloc_binders AS +SELECT + id AS gralloc_binder_reply_id, + utid, + ts, + dur +FROM thread_slice +WHERE process_name GLOB '/vendor/bin/hw/android.hardware.graphics.allocator*' +AND name = 'binder reply'; + +-- Match gralloc thread allocations to inbound binders +CREATE PERFETTO TABLE _attributed_dmabufs AS +SELECT + r.inode, + r.ts, + r.buf_size, + IFNULL(b.client_utid, r.utid) AS attr_utid +FROM _raw_dmabuf_events r +LEFT JOIN _gralloc_binders gb ON r.utid = gb.utid AND r.ts BETWEEN gb.ts AND gb.ts + gb.dur +LEFT JOIN android_binder_txns b ON gb.gralloc_binder_reply_id = b.binder_reply_id +ORDER BY r.inode, r.ts; + +CREATE PERFETTO FUNCTION _alloc_source(is_alloc BOOL, inode INT, ts INT) +RETURNS INT AS +SELECT attr_utid +FROM _attributed_dmabufs +WHERE + inode = $inode + AND ( + ($is_alloc AND ts = $ts) OR + (NOT $is_alloc AND ts < $ts) + ) +ORDER BY ts DESC +LIMIT 1; + +-- Track dmabuf allocations, re-attributing gralloc allocations to their source +-- (if binder transactions to gralloc are recorded). +CREATE PERFETTO TABLE android_dmabuf_allocs ( + -- timestamp of the allocation + ts INT, + -- allocation size (will be negative for release) + buf_size INT, + -- dmabuf inode + inode INT, + -- utid of thread responsible for the allocation + -- if a dmabuf is allocated by gralloc we follow the binder transaction + -- to the requesting thread (requires binder tracing) + utid INT, + -- tid of thread responsible for the allocation + tid INT, + -- thread name + thread_name STRING, + -- upid of process responsible for the allocation (matches utid) + upid INT, + -- pid of process responsible for the allocation + pid INT, + -- process name + process_name STRING +) AS +WITH _thread_allocs AS ( + SELECT inode, ts, buf_size, _alloc_source(buf_size > 0, inode, ts) AS utid + FROM _attributed_dmabufs +) +SELECT ts, buf_size, inode, + utid, tid, thread.name AS thread_name, + upid, pid, process.name AS process_name +FROM _thread_allocs allocs +JOIN thread USING (utid) +LEFT JOIN process USING (upid) +ORDER BY ts; diff --git a/src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/BUILD.gn new file mode 100644 index 0000000000..2c86bfe8f7 --- /dev/null +++ b/src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/BUILD.gn @@ -0,0 +1,27 @@ +# 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("../../../../../../../gn/perfetto_sql.gni") + +perfetto_sql_source_set("heap_graph") { + sources = [ + "class_tree.sql", + "dominator_class_tree.sql", + "dominator_tree.sql", + "excluded_refs.sql", + "heap_graph_class_aggregation.sql", + "helpers.sql", + "raw_dominator_tree.sql", + ] +} diff --git a/src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/class_tree.sql b/src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/class_tree.sql new file mode 100644 index 0000000000..6616af5fb6 --- /dev/null +++ b/src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/class_tree.sql @@ -0,0 +1,50 @@ +-- +-- 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.memory.heap_graph.excluded_refs; +INCLUDE PERFETTO MODULE android.memory.heap_graph.helpers; +INCLUDE PERFETTO MODULE graphs.search; + +-- Converts the heap graph into a tree by performing a BFS on the graph from +-- the roots. This basically ends up with all paths being the shortest path +-- from the root to the node (with lower ids being picked in the case of ties). +CREATE PERFETTO TABLE _heap_graph_object_min_depth_tree AS +SELECT node_id AS id, parent_node_id AS parent_id +FROM graph_reachable_bfs!( + ( + SELECT owner_id AS source_node_id, owned_id AS dest_node_id + FROM heap_graph_reference ref + WHERE ref.id NOT IN _excluded_refs AND ref.owned_id IS NOT NULL + ORDER BY ref.owned_id + ), + ( + SELECT id AS node_id + FROM heap_graph_object + WHERE root_type IS NOT NULL + ) +) +ORDER BY id; + +CREATE PERFETTO TABLE _heap_graph_path_hashes as +SELECT * +FROM _heap_graph_type_path_hash!(_heap_graph_object_min_depth_tree); + +CREATE PERFETTO TABLE _heap_graph_path_hashes_aggregated as +SELECT * +FROM _heap_graph_path_hash_aggregate!(_heap_graph_path_hashes); + +CREATE PERFETTO TABLE _heap_graph_class_tree AS +SELECT * +FROM _heap_graph_path_hashes_to_class_tree!(_heap_graph_path_hashes_aggregated); diff --git a/src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/dominator_class_tree.sql b/src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/dominator_class_tree.sql new file mode 100644 index 0000000000..f0d9eea126 --- /dev/null +++ b/src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/dominator_class_tree.sql @@ -0,0 +1,34 @@ +-- +-- 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.memory.heap_graph.helpers; +INCLUDE PERFETTO MODULE android.memory.heap_graph.raw_dominator_tree; + +CREATE PERFETTO TABLE _heap_graph_dominator_path_hashes as +SELECT * +FROM _heap_graph_type_path_hash!(( + SELECT id, idom_id AS parent_id + FROM _raw_heap_graph_dominator_tree +)); + +CREATE PERFETTO TABLE _heap_graph_dominator_path_hashes_aggregated as +SELECT * +FROM _heap_graph_path_hash_aggregate!(_heap_graph_dominator_path_hashes); + +CREATE PERFETTO TABLE _heap_graph_dominator_class_tree AS +SELECT * +FROM _heap_graph_path_hashes_to_class_tree!( + _heap_graph_dominator_path_hashes_aggregated +); diff --git a/src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/dominator_tree.sql b/src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/dominator_tree.sql new file mode 100644 index 0000000000..900bee9ff2 --- /dev/null +++ b/src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/dominator_tree.sql @@ -0,0 +1,112 @@ +-- +-- 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 graphs.scan; +INCLUDE PERFETTO MODULE android.memory.heap_graph.raw_dominator_tree; + +CREATE PERFETTO TABLE _heap_graph_dominator_tree_bottom_up_scan AS +SELECT * +FROM _graph_aggregating_scan!( + ( + SELECT id AS source_node_id, idom_id AS dest_node_id + FROM _raw_heap_graph_dominator_tree + WHERE idom_id IS NOT NULL + ), + ( + SELECT + p.id, + 1 AS subtree_count, + o.self_size AS subtree_size_bytes, + o.native_size AS subtree_native_size_bytes + FROM _raw_heap_graph_dominator_tree p + JOIN heap_graph_object o USING (id) + LEFT JOIN _raw_heap_graph_dominator_tree c ON p.id = c.idom_id + WHERE c.id IS NULL + ), + (subtree_count, subtree_size_bytes, subtree_native_size_bytes), + ( + WITH children_agg AS ( + SELECT + t.id, + SUM(t.subtree_count) AS subtree_count, + SUM(t.subtree_size_bytes) AS subtree_size_bytes, + SUM(t.subtree_native_size_bytes) AS subtree_native_size_bytes + FROM $table t + GROUP BY t.id + ) + SELECT + c.id, + c.subtree_count + 1 AS subtree_count, + c.subtree_size_bytes + self_size AS subtree_size_bytes, + c.subtree_native_size_bytes + native_size AS subtree_native_size_bytes + FROM children_agg c + JOIN heap_graph_object o USING (id) + ) +) +ORDER BY id; + +CREATE PERFETTO TABLE _heap_graph_dominator_tree_top_down_scan AS +SELECT * +FROM _graph_scan!( + ( + SELECT idom_id AS source_node_id, id AS dest_node_id + FROM _raw_heap_graph_dominator_tree + WHERE idom_id IS NOT NULL + ), + ( + SELECT id, 1 AS depth + FROM _raw_heap_graph_dominator_tree + WHERE idom_id IS NULL + ), + (depth), + (SELECT t.id, t.depth + 1 AS depth FROM $table t) +) +ORDER BY id; + +-- All reachable heap graph objects, their immediate dominators and summary of +-- their dominated sets. +-- The heap graph dominator tree is calculated by stdlib graphs.dominator_tree. +-- Each reachable object is a node in the dominator tree, their immediate +-- dominator is their parent node in the tree, and their dominated set is all +-- their descendants in the tree. All size information come from the +-- heap_graph_object prelude table. +CREATE PERFETTO TABLE heap_graph_dominator_tree( + -- Heap graph object id. + id INT, + -- Immediate dominator object id of the object. If the immediate dominator + -- is the "super-root" (i.e. the object is a root or is dominated by multiple + -- roots) then `idom_id` will be NULL. + idom_id INT, + -- Count of all objects dominated by this object, self inclusive. + dominated_obj_count INT, + -- Total self_size of all objects dominated by this object, self inclusive. + dominated_size_bytes INT, + -- Total native_size of all objects dominated by this object, self inclusive. + dominated_native_size_bytes INT, + -- Depth of the object in the dominator tree. Depth of root objects are 1. + depth INT +) AS +SELECT + r.id, + r.idom_id AS idom_id, + d.subtree_count AS dominated_obj_count, + d.subtree_size_bytes AS dominated_size_bytes, + d.subtree_native_size_bytes AS dominated_native_size_bytes, + t.depth +FROM _raw_heap_graph_dominator_tree r +JOIN _heap_graph_dominator_tree_bottom_up_scan d USING(id) +JOIN _heap_graph_dominator_tree_top_down_scan t USING (id) +WHERE r.id IS NOT NULL +ORDER BY id; diff --git a/src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/excluded_refs.sql b/src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/excluded_refs.sql new file mode 100644 index 0000000000..a98c38076d --- /dev/null +++ b/src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/excluded_refs.sql @@ -0,0 +1,33 @@ +-- +-- 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 _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' +) +ORDER BY type_id; + +CREATE PERFETTO TABLE _excluded_refs AS +SELECT ref.id +FROM heap_graph_reference ref +CROSS JOIN heap_graph_object robj USING (reference_set_id) +CROSS JOIN _ref_type_ids USING (type_id) +WHERE ref.field_name = 'java.lang.ref.Reference.referent' +ORDER BY ref.id; diff --git a/src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/heap_graph_class_aggregation.sql b/src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/heap_graph_class_aggregation.sql new file mode 100644 index 0000000000..fd0494e26f --- /dev/null +++ b/src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/heap_graph_class_aggregation.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.memory.heap_graph.dominator_tree; +INCLUDE PERFETTO MODULE graphs.partition; + +CREATE PERFETTO FUNCTION _partition_tree_super_root_fn() +-- The assigned id of the "super root". +RETURNS INT AS +SELECT id + 1 +FROM heap_graph_object +ORDER BY id DESC +LIMIT 1; + +CREATE PERFETTO FUNCTION _is_libcore_or_array(obj_name STRING) +RETURNS BOOL AS +SELECT ($obj_name GLOB 'java.*' AND NOT $obj_name GLOB 'java.lang.Class<*>') + OR $obj_name GLOB 'j$.*' + OR $obj_name GLOB 'int[*' + OR $obj_name GLOB 'long[*' + OR $obj_name GLOB 'byte[*' + OR $obj_name GLOB 'char[*' + OR $obj_name GLOB 'short[*' + OR $obj_name GLOB 'float[*' + OR $obj_name GLOB 'double[*' + OR $obj_name GLOB 'boolean[*' + OR $obj_name GLOB 'android.util.*Array*'; + +CREATE PERFETTO TABLE _heap_graph_dominator_tree_for_partition AS +SELECT + tree.id, + IFNULL(tree.idom_id, _partition_tree_super_root_fn()) as parent_id, + obj.type_id as group_key +FROM heap_graph_dominator_tree tree +JOIN heap_graph_object obj USING(id) +UNION ALL +-- provide a single root required by tree partition if heap graph exists. +SELECT + _partition_tree_super_root_fn() AS id, + NULL AS parent_id, + (SELECT id + 1 FROM heap_graph_class ORDER BY id desc LIMIT 1) AS group_key +WHERE _partition_tree_super_root_fn() IS NOT NULL; + +CREATE PERFETTO TABLE _heap_object_marked_for_dominated_stats AS +SELECT + id, + IIF(parent_id IS NULL, 1, 0) as marked +FROM tree_structural_partition_by_group!(_heap_graph_dominator_tree_for_partition) +ORDER BY id; + +-- Class-level breakdown of the java heap. +-- Per type name aggregates the object stats and the dominator tree stats. +CREATE PERFETTO TABLE android_heap_graph_class_aggregation ( + -- Process upid + upid INT, + -- Heap dump timestamp + graph_sample_ts INT, + -- Class type id + type_id INT, + -- Class name (deobfuscated if available) + type_name STRING, + -- Is type an instance of a libcore object (java.*) or array + is_libcore_or_array BOOL, + -- Count of class instances + obj_count INT, + -- Size of class instances + size_bytes INT, + -- Native size of class instances + native_size_bytes INT, + -- Count of reachable class instances + reachable_obj_count INT, + -- Size of reachable class instances + reachable_size_bytes INT, + -- Native size of reachable class instances + reachable_native_size_bytes INT, + -- Count of all objects dominated by instances of this class + -- Only applies to reachable objects + dominated_obj_count INT, + -- Size of all objects dominated by instances of this class + -- Only applies to reachable objects + dominated_size_bytes INT, + -- Native size of all objects dominated by instances of this class + -- Only applies to reachable objects + dominated_native_size_bytes INT +) AS +WITH base AS ( + -- First level aggregation to avoid joining with class for every object + SELECT + obj.upid, + obj.graph_sample_ts, + obj.type_id, + COUNT(1) AS obj_count, + SUM(self_size) AS size_bytes, + SUM(native_size) AS native_size_bytes, + SUM(IIF(obj.reachable, 1, 0)) AS reachable_obj_count, + SUM(IIF(obj.reachable, self_size, 0)) AS reachable_size_bytes, + SUM(IIF(obj.reachable, native_size, 0)) AS reachable_native_size_bytes, + SUM(IIF(marked, dominated_obj_count, 0)) AS dominated_obj_count, + SUM(IIF(marked, dominated_size_bytes, 0)) AS dominated_size_bytes, + SUM(IIF(marked, dominated_native_size_bytes, 0)) AS dominated_native_size_bytes + FROM heap_graph_object obj + -- Left joins to preserve unreachable objects. + LEFT JOIN _heap_object_marked_for_dominated_stats USING(id) + LEFT JOIN heap_graph_dominator_tree USING(id) + GROUP BY 1, 2, 3 + ORDER BY 1, 2, 3 +) +SELECT + upid, + graph_sample_ts, + type_id, + IFNULL(cls.deobfuscated_name, cls.name) AS type_name, + _is_libcore_or_array(IFNULL(cls.deobfuscated_name, cls.name)) + AS is_libcore_or_array, + SUM(obj_count) AS obj_count, + SUM(size_bytes) AS size_bytes, + SUM(native_size_bytes) AS native_size_bytes, + SUM(reachable_obj_count) AS reachable_obj_count, + SUM(reachable_size_bytes) AS reachable_size_bytes, + SUM(reachable_native_size_bytes) AS reachable_native_size_bytes, + SUM(dominated_obj_count) AS dominated_obj_count, + SUM(dominated_size_bytes) AS dominated_size_bytes, + SUM(dominated_native_size_bytes) AS dominated_native_size_bytes +FROM base +JOIN heap_graph_class cls ON base.type_id = cls.id +GROUP BY 1, 2, 3, 4, 5 +ORDER BY 1, 2, 3, 4, 5; diff --git a/src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/helpers.sql b/src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/helpers.sql new file mode 100644 index 0000000000..63dc3cc817 --- /dev/null +++ b/src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/helpers.sql @@ -0,0 +1,145 @@ +-- +-- 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 graphs.scan; + +-- Given a table containing a "tree-ified" heap graph object table (i.e. +-- by using a dominator tree or shortest path algorithm), computes a hash of +-- the path from the root to each node in the graph based on class names. +-- +-- This allows an SQL aggregation of all nodes which have the same hash to +-- build a "class-tree" instead of the object tree. +CREATE PERFETTO MACRO _heap_graph_type_path_hash(tab TableOrSubquery) +RETURNS TableOrSubquery +AS ( + SELECT id, path_hash, parent_path_hash + FROM _graph_scan!( + ( + SELECT parent_id AS source_node_id, id AS dest_node_id + FROM $tab + WHERE parent_id IS NOT NULL + ), + ( + SELECT + t.id, + o.type_id as parent_type_id, + HASH( + o.upid, + o.graph_sample_ts, + o.type_id, + IFNULL(o.root_type, '') + ) AS path_hash, + 0 AS parent_path_hash + FROM $tab t + JOIN heap_graph_object o USING (id) + WHERE t.parent_id IS NULL + ), + (parent_type_id, path_hash, parent_path_hash), + ( + SELECT + t.id, + o.type_id as parent_type_id, + IIF( + o.type_id = t.parent_type_id, + t.path_hash, + HASH(t.path_hash, o.type_id) + ) AS path_hash, + IIF( + o.type_id = t.parent_type_id, + t.parent_path_hash, + t.path_hash + ) AS parent_path_hash + FROM $table t + JOIN heap_graph_object o USING (id) + ) + ) + ORDER BY id +); + +-- Given a table containing heap graph tree-table with path hashes computed +-- (see _heap_graph_type_path_hash macro), aggregates together all nodes +-- with the same hash and also splits out "native size" as a separate node under +-- the nodes which contain the native size. +CREATE PERFETTO MACRO _heap_graph_path_hash_aggregate(tab TableOrSubquery) +RETURNS TableOrSubquery +AS ( + with x AS ( + SELECT + o.graph_sample_ts, + o.upid, + path_hash, + parent_path_hash, + COALESCE(c.deobfuscated_name, c.name) AS name, + o.root_type, + COUNT() AS self_count, + SUM(o.self_size) AS self_size, + SUM(o.native_size > 0) AS self_native_count, + SUM(o.native_size) AS self_native_size + FROM $tab + JOIN heap_graph_object o USING (id) + JOIN heap_graph_class c ON o.type_id = c.id + GROUP BY path_hash + ) + SELECT + graph_sample_ts, + upid, + HASH(path_hash, 'native', '') AS path_hash, + path_hash AS parent_path_hash, + '[native] ' || x.name AS name, + root_type, + SUM(x.self_native_count) AS self_count, + SUM(x.self_native_size) AS self_size + FROM x + WHERE x.self_native_size > 0 + GROUP BY path_hash + UNION ALL + SELECT + graph_sample_ts, + upid, + path_hash, + parent_path_hash, + name, + root_type, + self_count, + self_size + FROM x + ORDER BY path_hash +); + +-- Given a table containing heap graph tree-table aggregated by path hashes +-- (see _heap_graph_path_hash_aggregate) computes the "class tree" by converting +-- the path hashes to ids. +-- +-- Note that |tab| *must* be a Perfetto (e.g. not a subquery) for this macro +-- to work. +CREATE PERFETTO MACRO _heap_graph_path_hashes_to_class_tree(tab TableOrSubquery) +RETURNS TableOrSubquery +AS ( + SELECT + graph_sample_ts, + upid, + _auto_id AS id, + ( + SELECT p._auto_id + FROM $tab p + WHERE c.parent_path_hash = p.path_hash + ) AS parent_id, + name, + root_type, + self_count, + self_size + FROM $tab c + ORDER BY id +); diff --git a/src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/raw_dominator_tree.sql b/src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/raw_dominator_tree.sql new file mode 100644 index 0000000000..90cd71b52c --- /dev/null +++ b/src/trace_processor/perfetto_sql/stdlib/android/memory/heap_graph/raw_dominator_tree.sql @@ -0,0 +1,60 @@ +-- +-- 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.memory.heap_graph.excluded_refs; +INCLUDE PERFETTO MODULE graphs.dominator_tree; + +-- The assigned id of the "super root". +-- Since a Java heap graph is a "forest" structure, we need to add a imaginary +-- "super root" node which connects all the roots of the forest into a single +-- connected component, so that the dominator tree algorithm can be performed. +CREATE PERFETTO FUNCTION _heap_graph_super_root_fn() +-- The assigned id of the "super root". +RETURNS INT AS +SELECT id + 1 +FROM heap_graph_object +ORDER BY id DESC +LIMIT 1; + +CREATE PERFETTO TABLE _raw_heap_graph_dominator_tree AS +SELECT + node_id AS id, + iif( + dominator_node_id = _heap_graph_super_root_fn(), + null, + dominator_node_id + ) as idom_id +FROM graph_dominator_tree!( + ( + SELECT + ref.owner_id AS source_node_id, + 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 ref.id NOT IN _excluded_refs + AND ref.owned_id IS NOT NULL + UNION ALL + SELECT + (SELECT _heap_graph_super_root_fn()) as source_node_id, + id AS dest_node_id + FROM heap_graph_object + WHERE root_type IS NOT NULL + ), + (SELECT _heap_graph_super_root_fn()) +) +-- Excluding the imaginary root. +WHERE dominator_node_id IS NOT NULL +ORDER BY id; diff --git a/src/trace_processor/perfetto_sql/stdlib/android/memory/heap_profile/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/android/memory/heap_profile/BUILD.gn new file mode 100644 index 0000000000..daaf9e0b7d --- /dev/null +++ b/src/trace_processor/perfetto_sql/stdlib/android/memory/heap_profile/BUILD.gn @@ -0,0 +1,19 @@ +# 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("../../../../../../../gn/perfetto_sql.gni") + +perfetto_sql_source_set("heap_profile") { + sources = [ "callstacks.sql" ] +} diff --git a/src/trace_processor/perfetto_sql/stdlib/android/memory/heap_profile/callstacks.sql b/src/trace_processor/perfetto_sql/stdlib/android/memory/heap_profile/callstacks.sql new file mode 100644 index 0000000000..2867ad7c87 --- /dev/null +++ b/src/trace_processor/perfetto_sql/stdlib/android/memory/heap_profile/callstacks.sql @@ -0,0 +1,47 @@ +-- +-- 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 callstacks.stack_profile; + +CREATE PERFETTO MACRO _android_heap_profile_callstacks_for_allocations( + allocations TableOrSubquery +) +RETURNS TableOrSubquery +AS +( + WITH metrics AS MATERIALIZED ( + SELECT + callsite_id, + SUM(size) AS self_size, + SUM(count) AS self_count, + SUM(alloc_size) AS self_alloc_size, + SUM(alloc_count) AS self_alloc_count + FROM $allocations + GROUP BY callsite_id + ) + SELECT + c.id, + c.parent_id, + c.name, + c.mapping_name, + c.source_file, + c.line_number, + IFNULL(m.self_size, 0) AS self_size, + IFNULL(m.self_count, 0) AS self_count, + IFNULL(m.self_alloc_size, 0) AS self_alloc_size, + IFNULL(m.self_alloc_count, 0) AS self_alloc_count + FROM _callstacks_for_stack_profile_samples!(metrics) c + LEFT JOIN metrics m USING (callsite_id) +); diff --git a/src/trace_processor/perfetto_sql/stdlib/android/memory/process.sql b/src/trace_processor/perfetto_sql/stdlib/android/memory/process.sql new file mode 100644 index 0000000000..efa56e1f1e --- /dev/null +++ b/src/trace_processor/perfetto_sql/stdlib/android/memory/process.sql @@ -0,0 +1,99 @@ +-- +-- 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; +INCLUDE PERFETTO MODULE linux.memory.process; + +-- OOM score tables + +CREATE VIRTUAL TABLE _mem_ooms_sj +USING SPAN_OUTER_JOIN( + android_oom_adj_intervals PARTITIONED upid, + _memory_rss_and_swap_per_process_table PARTITIONED upid); + +-- Process memory and it's OOM adjuster scores. Detects transitions, each new +-- interval means that either the memory or OOM adjuster score of the process changed. +CREATE PERFETTO TABLE memory_oom_score_with_rss_and_swap_per_process( + -- Timestamp the oom_adj score or memory of the process changed + ts INT, + -- Duration until the next oom_adj score or memory change of the process. + dur INT, + -- oom adjuster score of the process. + score INT, + -- oom adjuster bucket of the process. + bucket STRING, + -- Upid of the process having an oom_adj update. + upid INT, + -- Name of the process having an oom_adj update. + process_name STRING, + -- Pid of the process having an oom_adj update. + pid INT, + -- Slice of the latest oom_adj update in the system_server. Alias of + -- `slice.id`. + oom_adj_id INT, + -- Timestamp of the latest oom_adj update in the system_server. + oom_adj_ts INT, + -- Duration of the latest oom_adj update in the system_server. + oom_adj_dur INT, + -- Track of the latest oom_adj update in the system_server. Alias of + -- `track.id`. + oom_adj_track_id INT, + -- Thread name of the latest oom_adj update in the system_server. + oom_adj_thread_name STRING, + -- Reason for the latest oom_adj update in the system_server. + oom_adj_reason STRING, + -- Trigger for the latest oom_adj update in the system_server. + oom_adj_trigger STRING, + -- Anon RSS counter value + anon_rss INT, + -- File RSS counter value + file_rss INT, + -- Shared memory RSS counter value + shmem_rss INT, + -- Total RSS value. Sum of `anon_rss`, `file_rss` and `shmem_rss`. Returns + -- value even if one of the values is NULL. + rss INT, + -- Swap counter value + swap INT, + -- Sum or `anon_rss` and `swap`. Returns value even if one of the values is + -- NULL. + anon_rss_and_swap INT, + -- Sum or `rss` and `swap`. Returns value even if one of the values is NULL. + rss_and_swap INT +) AS +SELECT + ts, + dur, + score, + bucket, + upid, + process_name, + pid, + oom_adj_id, + oom_adj_ts, + oom_adj_dur, + oom_adj_track_id, + oom_adj_thread_name, + oom_adj_reason, + oom_adj_trigger, + anon_rss, + file_rss, + shmem_rss, + file_rss + anon_rss + COALESCE(shmem_rss, 0) AS rss, + swap, + anon_rss + COALESCE(swap, 0) AS anon_rss_and_swap, + anon_rss + file_rss + COALESCE(shmem_rss, 0) + COALESCE(swap, 0) AS rss_and_swap +FROM _mem_ooms_sj +JOIN process USING (upid); diff --git a/src/trace_processor/perfetto_sql/stdlib/android/monitor_contention.sql b/src/trace_processor/perfetto_sql/stdlib/android/monitor_contention.sql index 005f86745e..c7ad3bff5c 100644 --- a/src/trace_processor/perfetto_sql/stdlib/android/monitor_contention.sql +++ b/src/trace_processor/perfetto_sql/stdlib/android/monitor_contention.sql @@ -121,29 +121,62 @@ WHERE GROUP BY slice.id; -- Contains parsed monitor contention slices. --- --- @column blocking_method Name of the method holding the lock. --- @column blocked_methhod Name of the method trying to acquire the lock. --- @column short_blocking_method Blocking_method without arguments and return types. --- @column short_blocked_method Blocked_method without arguments and return types. --- @column blocking_src File location of blocking_method in form . --- @column blocked_src File location of blocked_method in form . --- @column waiter_count Zero indexed number of threads trying to acquire the lock. --- @column blocking_utid Utid of thread holding the lock. --- @column blocking_thread_name Thread name of thread holding the lock. --- @column upid Upid of process experiencing lock contention. --- @column process_name Process name of process experiencing lock contention. --- @column id Slice id of lock contention. --- @column ts Timestamp of lock contention start. --- @column dur Wall clock duration of lock contention. --- @column monotonic_dur Monotonic clock duration of lock contention. --- @column track_id Thread track id of blocked thread. --- @column is_blocked_main_thread Whether the blocked thread is the main thread. --- @column is_blocking_main_thread Whether the blocking thread is the main thread. --- @column binder_reply_id Slice id of binder reply slice if lock contention was part of a binder txn. --- @column binder_reply_ts Timestamp of binder reply slice if lock contention was part of a binder txn. --- @column binder_reply_tid Tid of binder reply slice if lock contention was part of a binder txn. -CREATE TABLE android_monitor_contention AS +CREATE PERFETTO TABLE android_monitor_contention( +-- Name of the method holding the lock. +blocking_method STRING, +-- Blocked_method without arguments and return types. +blocked_method STRING, +-- Blocking_method without arguments and return types. +short_blocking_method STRING, +-- Blocked_method without arguments and return types. +short_blocked_method STRING, +-- File location of blocking_method in form . +blocking_src STRING, +-- File location of blocked_method in form . +blocked_src STRING, +-- Zero indexed number of threads trying to acquire the lock. +waiter_count INT, +-- Utid of thread holding the lock. +blocked_utid INT, +-- Thread name of thread holding the lock. +blocked_thread_name STRING, +-- Utid of thread holding the lock. +blocking_utid INT, +-- Thread name of thread holding the lock. +blocking_thread_name STRING, +-- Tid of thread holding the lock. +blocking_tid INT, +-- Upid of process experiencing lock contention. +upid INT, +-- Process name of process experiencing lock contention. +process_name STRING, +-- Slice id of lock contention. +id INT, +-- Timestamp of lock contention start. +ts INT, +-- Wall clock duration of lock contention. +dur INT, +-- Monotonic clock duration of lock contention. +monotonic_dur INT, +-- Thread track id of blocked thread. +track_id INT, +-- Whether the blocked thread is the main thread. +is_blocked_thread_main INT, +-- Tid of the blocked thread +blocked_thread_tid INT, +-- Whether the blocking thread is the main thread. +is_blocking_thread_main INT, +-- Tid of thread holding the lock. +blocking_thread_tid INT, +-- Slice id of binder reply slice if lock contention was part of a binder txn. +binder_reply_id INT, +-- Timestamp of binder reply slice if lock contention was part of a binder txn. +binder_reply_ts INT, +-- Tid of binder reply slice if lock contention was part of a binder txn. +binder_reply_tid INT, +-- Pid of process experiencing lock contention. +pid INT +) AS SELECT android_extract_android_monitor_contention_blocking_method(slice.name) AS blocking_method, android_extract_android_monitor_contention_blocked_method(slice.name) AS blocked_method, @@ -192,10 +225,10 @@ WHERE slice.name GLOB 'monitor contention*' AND short_blocked_method IS NOT NULL GROUP BY slice.id; -CREATE INDEX _android_monitor_contention_blocking_utid_idx +CREATE PERFETTO INDEX _android_monitor_contention_blocking_utid_idx ON android_monitor_contention (blocking_utid, ts); -CREATE INDEX _android_monitor_contention_id_idx +CREATE PERFETTO INDEX _android_monitor_contention_id_idx ON android_monitor_contention (id); -- Monitor contention slices that are blocked by another monitor contention slice. @@ -214,7 +247,7 @@ AND child.ts BETWEEN parent.ts AND parent.ts + parent.dur; -- Monitor contention slices that are neither blocking nor blocked by another monitor contention -- slice. They neither have |parent_id| nor |child_id| fields. -CREATE TABLE _isolated AS +CREATE PERFETTO TABLE _isolated AS WITH parents_and_children AS ( SELECT id FROM _children UNION ALL @@ -227,31 +260,66 @@ WITH parents_and_children AS ( SELECT * FROM android_monitor_contention JOIN isolated USING (id); -- Contains parsed monitor contention slices with the parent-child relationships. --- --- @column parent_id Id of monitor contention slice blocking this contention. --- @column blocking_method Name of the method holding the lock. --- @column blocked_methhod Name of the method trying to acquire the lock. --- @column short_blocking_method Blocking_method without arguments and return types. --- @column short_blocked_method Blocked_method without arguments and return types. --- @column blocking_src File location of blocking_method in form . --- @column blocked_src File location of blocked_method in form . --- @column waiter_count Zero indexed number of threads trying to acquire the lock. --- @column blocking_utid Utid of thread holding the lock. --- @column blocking_thread_name Thread name of thread holding the lock. --- @column upid Upid of process experiencing lock contention. --- @column process_name Process name of process experiencing lock contention. --- @column id Slice id of lock contention. --- @column ts Timestamp of lock contention start. --- @column dur Wall clock duration of lock contention. --- @column monotonic_dur Monotonic clock duration of lock contention. --- @column track_id Thread track id of blocked thread. --- @column is_blocked_main_thread Whether the blocked thread is the main thread. --- @column is_blocking_main_thread Whether the blocking thread is the main thread. --- @column binder_reply_id Slice id of binder reply slice if lock contention was part of a binder txn. --- @column binder_reply_ts Timestamp of binder reply slice if lock contention was part of a binder txn. --- @column binder_reply_tid Tid of binder reply slice if lock contention was part of a binder txn. --- @column child_id Id of monitor contention slice blocked by this contention. -CREATE TABLE android_monitor_contention_chain AS +CREATE PERFETTO TABLE android_monitor_contention_chain( +-- Id of monitor contention slice blocking this contention. +parent_id INT, +-- Name of the method holding the lock. +blocking_method STRING, +-- Blocked_method without arguments and return types. +blocked_method STRING, +-- Blocking_method without arguments and return types. +short_blocking_method STRING, +-- Blocked_method without arguments and return types. +short_blocked_method STRING, +-- File location of blocking_method in form . +blocking_src STRING, +-- File location of blocked_method in form . +blocked_src STRING, +-- Zero indexed number of threads trying to acquire the lock. +waiter_count INT, +-- Utid of thread holding the lock. +blocked_utid INT, +-- Thread name of thread holding the lock. +blocked_thread_name STRING, +-- Utid of thread holding the lock. +blocking_utid INT, +-- Thread name of thread holding the lock. +blocking_thread_name STRING, +-- Tid of thread holding the lock. +blocking_tid INT, +-- Upid of process experiencing lock contention. +upid INT, +-- Process name of process experiencing lock contention. +process_name STRING, +-- Slice id of lock contention. +id INT, +-- Timestamp of lock contention start. +ts INT, +-- Wall clock duration of lock contention. +dur INT, +-- Monotonic clock duration of lock contention. +monotonic_dur INT, +-- Thread track id of blocked thread. +track_id INT, +-- Whether the blocked thread is the main thread. +is_blocked_thread_main INT, +-- Tid of the blocked thread +blocked_thread_tid INT, +-- Whether the blocking thread is the main thread. +is_blocking_thread_main INT, +-- Tid of thread holding the lock. +blocking_thread_tid INT, +-- Slice id of binder reply slice if lock contention was part of a binder txn. +binder_reply_id INT, +-- Timestamp of binder reply slice if lock contention was part of a binder txn. +binder_reply_ts INT, +-- Tid of binder reply slice if lock contention was part of a binder txn. +binder_reply_tid INT, +-- Pid of process experiencing lock contention. +pid INT, +-- Id of monitor contention slice blocked by this contention. +child_id INT +) AS SELECT NULL AS parent_id, *, NULL AS child_id FROM _isolated UNION ALL SELECT c.*, p.child_id FROM _children c @@ -260,7 +328,7 @@ UNION SELECT c.parent_id, p.* FROM _parents p LEFT JOIN _children c USING(id); -CREATE INDEX _android_monitor_contention_chain_idx +CREATE PERFETTO INDEX _android_monitor_contention_chain_idx ON android_monitor_contention_chain (blocking_method, blocking_utid, ts); -- First blocked node on a lock, i.e nodes with |waiter_count| = 0. The |dur| here is adjusted diff --git a/src/trace_processor/perfetto_sql/stdlib/android/network_packets.sql b/src/trace_processor/perfetto_sql/stdlib/android/network_packets.sql index d1a5537fe9..4ce48107cf 100644 --- a/src/trace_processor/perfetto_sql/stdlib/android/network_packets.sql +++ b/src/trace_processor/perfetto_sql/stdlib/android/network_packets.sql @@ -46,27 +46,31 @@ CREATE PERFETTO VIEW android_network_packets( -- 1-byte ICMP type identifier. packet_icmp_type INT, -- 1-byte ICMP code identifier. - packet_icmp_code INT + packet_icmp_code INT, + -- Packet's tcp flags bitmask (e.g. FIN=0x1, SYN=0x2). + packet_tcp_flags_int INT, + -- Packet's socket tag as an integer. + socket_tag_int INT ) AS SELECT ts, dur, - track.name AS track_name, - slice.name AS package_name, - str_split(track.name, ' ', 0) AS iface, - str_split(track.name, ' ', 1) AS direction, - ifnull(extract_arg(arg_set_id, 'packet_count'), 1) AS packet_count, - extract_arg(arg_set_id, 'packet_length') AS packet_length, - extract_arg(arg_set_id, 'packet_transport') AS packet_transport, - extract_arg(arg_set_id, 'packet_tcp_flags') AS packet_tcp_flags, - extract_arg(arg_set_id, 'socket_tag') AS socket_tag, - extract_arg(arg_set_id, 'socket_uid') AS socket_uid, - extract_arg(arg_set_id, 'local_port') AS local_port, - extract_arg(arg_set_id, 'remote_port') AS remote_port, - extract_arg(arg_set_id, 'packet_icmp_type') AS packet_icmp_type, - extract_arg(arg_set_id, 'packet_icmp_code') AS packet_icmp_code -FROM slice -JOIN track - ON slice.track_id = track.id -WHERE (track.name GLOB '* Transmitted' OR - track.name GLOB '* Received'); + category AS track_name, + name AS package_name, + iface, + direction, + packet_count, + packet_length, + packet_transport, + -- For backwards compatibility, the _str suffixed flags (which the ui shows) + -- are exposed without suffix, and the integer fields get suffix instead. + packet_tcp_flags_str AS packet_tcp_flags, + packet_tcp_flags AS packet_tcp_flags_int, + socket_tag_str AS socket_tag, + socket_tag AS socket_tag_int, + socket_uid, + local_port, + remote_port, + packet_icmp_type, + packet_icmp_code +FROM __intrinsic_android_network_packets; diff --git a/src/trace_processor/perfetto_sql/stdlib/android/oom_adjuster.sql b/src/trace_processor/perfetto_sql/stdlib/android/oom_adjuster.sql index 3ce017e42e..c09414e235 100644 --- a/src/trace_processor/perfetto_sql/stdlib/android/oom_adjuster.sql +++ b/src/trace_processor/perfetto_sql/stdlib/android/oom_adjuster.sql @@ -17,8 +17,30 @@ INCLUDE PERFETTO MODULE slices.with_context; INCLUDE PERFETTO MODULE counters.intervals; --- Converts an oom_adj score Integer to String bucket name. +-- Converts an oom_adj score Integer to String sample name. +-- One of: cached, background, job, foreground_service, bfgs, foreground and +-- system. CREATE PERFETTO FUNCTION android_oom_adj_score_to_bucket_name( + -- `oom_score` value + oom_score INT +) +-- Returns the sample bucket based on the oom score. +RETURNS STRING +AS +SELECT + CASE + WHEN $oom_score >= 900 THEN 'cached' + WHEN $oom_score BETWEEN 250 AND 900 THEN 'background' + WHEN $oom_score BETWEEN 201 AND 250 THEN 'job' + WHEN $oom_score = 200 THEN 'foreground_service' + WHEN $oom_score BETWEEN 100 AND 200 THEN 'bfgs' + WHEN $oom_score BETWEEN 0 AND 100 THEN 'foreground' + WHEN $oom_score < 0 THEN 'system' +END; + +-- Converts an oom_adj score Integer to String bucket name. +-- Deprecated: use `android_oom_adj_score_to_bucket_name` instead. +CREATE PERFETTO FUNCTION android_oom_adj_score_to_detailed_bucket_name( -- oom_adj score. value INT, -- android_app id of the process. @@ -52,8 +74,58 @@ SELECT ELSE 'unknown_app' END; +CREATE PERFETTO TABLE _oom_adjuster_intervals AS +WITH reason AS ( + SELECT + thread_slice.id AS oom_adj_id, + thread_slice.ts AS oom_adj_ts, + thread_slice.dur AS oom_adj_dur, + thread_slice.track_id AS oom_adj_track_id, + utid AS oom_adj_utid, + thread_name AS oom_adj_thread_name, + str_split(thread_slice.name, '_', 1) AS oom_adj_reason, + slice.name AS oom_adj_trigger, + LEAD(thread_slice.ts) OVER (ORDER BY thread_slice.ts) AS oom_adj_next_ts + FROM thread_slice + LEFT JOIN slice ON slice.id = thread_slice.parent_id AND slice.dur != -1 + WHERE thread_slice.name GLOB 'updateOomAdj_*' AND process_name = 'system_server' +) +SELECT + ts, + dur, + cast_int!(value) AS score, + process.upid, + process.name AS process_name, + reason.oom_adj_id, + reason.oom_adj_ts, + reason.oom_adj_dur, + reason.oom_adj_track_id, + reason.oom_adj_thread_name, + reason.oom_adj_utid, + reason.oom_adj_reason, + reason.oom_adj_trigger, + android_appid +FROM + counter_leading_intervals + !( + ( + SELECT counter.* + FROM counter + JOIN counter_track track + ON track.id = counter.track_id AND track.name = 'oom_score_adj' + )) + counter +JOIN process_counter_track track + ON counter.track_id = track.id +JOIN process + USING (upid) +LEFT JOIN reason + ON counter.ts BETWEEN oom_adj_ts AND COALESCE(oom_adj_next_ts, trace_end()) +WHERE track.name = 'oom_score_adj'; + + -- All oom adj state intervals across all processes along with the reason for the state update. -CREATE PERFETTO TABLE android_oom_adj_intervals ( +CREATE PERFETTO VIEW android_oom_adj_intervals ( -- Timestamp the oom_adj score of the process changed ts INT, -- Duration until the next oom_adj score change of the process. @@ -81,49 +153,18 @@ CREATE PERFETTO TABLE android_oom_adj_intervals ( -- Trigger for the latest oom_adj update in the system_server. oom_adj_trigger STRING ) AS -WITH - reason AS ( - SELECT - thread_slice.id AS oom_adj_id, - thread_slice.ts AS oom_adj_ts, - thread_slice.dur AS oom_adj_dur, - thread_slice.track_id AS oom_adj_track_id, - thread_name AS oom_adj_thread_name, - str_split(thread_slice.name, '_', 1) AS oom_adj_reason, - slice.name AS oom_adj_trigger, - LEAD(thread_slice.ts) OVER (ORDER BY thread_slice.ts) AS oom_adj_next_ts - FROM thread_slice - LEFT JOIN slice ON slice.id = thread_slice.parent_id AND slice.dur != -1 - WHERE thread_slice.name GLOB 'updateOomAdj_*' AND process_name = 'system_server' - ) SELECT ts, dur, - value AS score, - android_oom_adj_score_to_bucket_name(value, android_appid) AS bucket, - process.upid, - process.name AS process_name, - reason.oom_adj_id, - reason.oom_adj_ts, - reason.oom_adj_dur, - reason.oom_adj_track_id, - reason.oom_adj_thread_name, - reason.oom_adj_reason, - reason.oom_adj_trigger -FROM - counter_leading_intervals - !( - ( - SELECT counter.* - FROM counter - JOIN counter_track track - ON track.id = counter.track_id AND track.name = 'oom_score_adj' - )) - counter -JOIN process_counter_track track - ON counter.track_id = track.id -JOIN process - USING (upid) -LEFT JOIN reason - ON counter.ts BETWEEN oom_adj_ts AND COALESCE(oom_adj_next_ts, trace_end()) -WHERE track.name = 'oom_score_adj'; + score, + android_oom_adj_score_to_bucket_name(score) AS bucket, + upid, + process_name, + oom_adj_id, + oom_adj_ts, + oom_adj_dur, + oom_adj_track_id, + oom_adj_thread_name, + oom_adj_reason, + oom_adj_trigger +FROM _oom_adjuster_intervals; diff --git a/src/trace_processor/perfetto_sql/stdlib/android/power_rails.sql b/src/trace_processor/perfetto_sql/stdlib/android/power_rails.sql index 6630c5370d..3f9d2c409b 100644 --- a/src/trace_processor/perfetto_sql/stdlib/android/power_rails.sql +++ b/src/trace_processor/perfetto_sql/stdlib/android/power_rails.sql @@ -14,23 +14,59 @@ -- limitations under the License. -- --- Android power rails counters. +INCLUDE PERFETTO MODULE counters.intervals; +INCLUDE PERFETTO MODULE time.conversion; + +-- Android power rails counters data. +-- For details see: https://perfetto.dev/docs/data-sources/battery-counters#odpm +-- NOTE: Requires dedicated hardware - table is only populated on Pixels. CREATE PERFETTO TABLE android_power_rails_counters ( -- `counter.id` id INT, - -- Counter timestamp. + -- Timestamp of the energy measurement. ts INT, - -- Power rail name. From `counter_track.name`. - power_rail_name INT, - -- Power rails counter value in micro watts. + -- Time until the next energy measurement. + dur INT, + -- Power rail name. Alias of `counter_track.name`. + power_rail_name STRING, + -- Raw power rail name. + raw_power_rail_name STRING, + -- Energy accumulated by this rail since boot in microwatt-seconds + -- (uWs) (AKA micro-joules). Alias of `counter.value`. + energy_since_boot DOUBLE, + -- Energy accumulated by this rail at next energy measurement in + -- microwatt-seconds (uWs) (AKA micro-joules). Alias of `counter.value` of + -- the next meaningful (with value change) counter value. + energy_since_boot_at_end DOUBLE, + -- Average power in mW (milliwatts) over between ts and the next energy + -- measurement. + average_power DOUBLE, + -- The change of energy accumulated by this rails since the last + -- measurement in microwatt-seconds (uWs) (AKA micro-joules). + energy_delta DOUBLE, + -- Power rail track id. Alias of `counter_track.id`. + track_id INT, + -- DEPRECATED. Use `energy_since_boot` instead. value DOUBLE +) AS +WITH counter_table AS ( +SELECT + c.* +FROM counter c +JOIN counter_track t ON c.track_id = t.id +WHERE name GLOB 'power.*' ) -AS SELECT c.id, c.ts, + c.dur, t.name AS power_rail_name, + EXTRACT_ARG(source_arg_set_id, 'raw_name') AS raw_power_rail_name, + c.value AS energy_since_boot, + c.next_value AS energy_since_boot_at_end, + 1e3*(c.delta_value/(time_to_s(c.dur))) AS average_power, + c.delta_value AS energy_delta, + c.track_id, c.value -FROM counter c -JOIN counter_track t ON c.track_id = t.id -WHERE name GLOB 'power.*'; \ No newline at end of file +FROM counter_leading_intervals!(counter_table) c +JOIN counter_track t ON c.track_id = t.id; 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 a3d47f2764..46a3a95fe7 100644 --- a/src/trace_processor/perfetto_sql/stdlib/android/process_metadata.sql +++ b/src/trace_processor/perfetto_sql/stdlib/android/process_metadata.sql @@ -65,6 +65,8 @@ FROM min_distance; CREATE PERFETTO TABLE android_process_metadata( -- Process upid. upid INT, + -- Process pid. + pid INT, -- Process name. process_name STRING, -- Android app UID. @@ -80,6 +82,7 @@ CREATE PERFETTO TABLE android_process_metadata( ) AS SELECT process.upid, + process.pid, -- workaround for b/169226092: the bug has been fixed it Android T, but -- we support ingesting traces from older Android versions. CASE @@ -100,4 +103,5 @@ FROM process LEFT JOIN _uid_package_count ON process.android_appid = _uid_package_count.uid LEFT JOIN _android_package_for_process( process.android_appid, _uid_package_count.cnt, process.name -) AS plist; +) AS plist +ORDER BY upid; diff --git a/src/trace_processor/perfetto_sql/stdlib/android/startup/startup_events.sql b/src/trace_processor/perfetto_sql/stdlib/android/startup/startup_events.sql index 25d8519ac2..b6455f4345 100644 --- a/src/trace_processor/perfetto_sql/stdlib/android/startup/startup_events.sql +++ b/src/trace_processor/perfetto_sql/stdlib/android/startup/startup_events.sql @@ -13,6 +13,8 @@ -- See the License for the specific language governing permissions and -- limitations under the License. +INCLUDE PERFETTO MODULE slices.with_context; + -- All activity startup events. CREATE PERFETTO TABLE _startup_events AS SELECT @@ -20,9 +22,7 @@ SELECT dur, ts + dur AS ts_end, STR_SPLIT(s.name, ": ", 1) AS package_name -FROM slice s -JOIN process_track t ON s.track_id = t.id -JOIN process USING(upid) +FROM process_slice s WHERE s.name GLOB 'launching: *' - AND (process.name IS NULL OR process.name = 'system_server'); + AND (process_name IS NULL OR process_name = 'system_server'); diff --git a/src/trace_processor/perfetto_sql/stdlib/android/startup/startups.sql b/src/trace_processor/perfetto_sql/stdlib/android/startup/startups.sql index e496e7c3c5..a715f520e3 100644 --- a/src/trace_processor/perfetto_sql/stdlib/android/startup/startups.sql +++ b/src/trace_processor/perfetto_sql/stdlib/android/startup/startups.sql @@ -17,21 +17,7 @@ INCLUDE PERFETTO MODULE android.process_metadata; INCLUDE PERFETTO MODULE android.startup.startups_maxsdk28; INCLUDE PERFETTO MODULE android.startup.startups_minsdk29; INCLUDE PERFETTO MODULE android.startup.startups_minsdk33; - -CREATE PERFETTO FUNCTION _slice_count( - -- Name of the slices to counted. - slice_glob STRING) --- Number of slices with the name. -RETURNS INT AS -SELECT COUNT(1) FROM slice WHERE name GLOB $slice_glob; - --- Gather all startup data. Populate by different sdks. -CREATE PERFETTO TABLE _all_startups AS -SELECT sdk, startup_id, ts, ts_end, dur, package, startup_type FROM _startups_maxsdk28 -UNION ALL -SELECT sdk, startup_id, ts, ts_end, dur, package, startup_type FROM _startups_minsdk29 -UNION ALL -SELECT sdk, startup_id, ts, ts_end, dur, package, startup_type FROM _startups_minsdk33; +INCLUDE PERFETTO MODULE android.version; -- All activity startups in the trace by startup id. -- Populated by different scripts depending on the platform version/contents. @@ -48,15 +34,37 @@ CREATE PERFETTO TABLE android_startups( package STRING, -- Startup type. startup_type STRING -) AS -SELECT startup_id, ts, ts_end, dur, package, startup_type FROM -_all_startups WHERE ( CASE - WHEN _slice_count('launchingActivity#*:*') > 0 - THEN sdk = "minsdk33" - WHEN _slice_count('MetricsLogger:*') > 0 - THEN sdk = "minsdk29" - ELSE sdk = "maxsdk28" - END); +) +AS +WITH version AS ( + SELECT CASE + WHEN _android_sdk_version() >= 33 THEN 33 + WHEN _android_sdk_version() >= 29 THEN 29 + WHEN _android_sdk_version() IS NOT NULL THEN 28 + WHEN ( + SELECT COUNT() + FROM slice + WHERE name GLOB 'launchingActivity#*:*' + ) > 0 THEN 33 + WHEN ( + SELECT COUNT() + FROM slice + WHERE name GLOB 'MetricsLogger:*' + ) > 0 THEN 29 + ELSE 28 + END AS v +) +SELECT _auto_id as startup_id, ts, ts_end, dur, package, startup_type +FROM _startups_maxsdk28 +WHERE (SELECT v from version) = 28 +UNION ALL +SELECT _auto_id as startup_id, ts, ts_end, dur, package, startup_type +FROM _startups_minsdk29 +WHERE (SELECT v from version) = 29 +UNION ALL +SELECT startup_id, ts, ts_end, dur, package, startup_type +FROM _startups_minsdk33 +WHERE (SELECT v from version) = 33; -- Create a table containing only the slices which are necessary for determining -- whether a startup happened. @@ -89,7 +97,7 @@ CREATE PERFETTO TABLE android_startup_processes( -- Upid of process on which activity started. upid INT, -- Type of the startup. - startup_type INT + startup_type STRING ) AS -- This is intentionally a materialized query. For some reason, if we don't -- materialize, we end up with a query which is an order of magnitude slower :( @@ -220,7 +228,9 @@ CREATE PERFETTO FUNCTION android_slices_for_startup_and_slice_name( -- Glob of the slice. slice_name STRING) RETURNS TABLE( --- Name of the slice. + -- Id of the slice. + slice_id INT, + -- Name of the slice. slice_name STRING, -- Timestamp of start of the slice. slice_ts INT, @@ -231,7 +241,7 @@ RETURNS TABLE( -- Arg set id. arg_set_id INT ) AS -SELECT slice_name, slice_ts, slice_dur, thread_name, arg_set_id +SELECT slice_id, slice_name, slice_ts, slice_dur, thread_name, arg_set_id FROM android_thread_slices_for_all_startups WHERE startup_id = $startup_id AND slice_name GLOB $slice_name; 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 071968a9bc..c1cc506462 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 @@ -13,9 +13,9 @@ -- See the License for the specific language governing permissions and -- limitations under the License. -INCLUDE PERFETTO MODULE slices.with_context; -INCLUDE PERFETTO MODULE android.startup.startup_events; INCLUDE PERFETTO MODULE android.frames.timeline; +INCLUDE PERFETTO MODULE android.startup.startup_events; +INCLUDE PERFETTO MODULE slices.with_context; CREATE PERFETTO TABLE _startups_maxsdk28 AS -- Warm and cold starts only are based on the launching slice @@ -42,29 +42,26 @@ maybe_hot AS ( FROM thread_slice sl JOIN android_first_frame_after(sl.ts) rs WHERE name = 'activityResume' - AND sl.is_main_thread - -- Remove any launches here where the activityResume slices happens during - -- a warm/cold startup. - AND NOT EXISTS ( - SELECT 1 - FROM warm_and_cold wac - WHERE sl.ts BETWEEN wac.ts AND wac.ts_end - LIMIT 1) + AND sl.is_main_thread + -- Remove any launches here where the activityResume slices happens during + -- a warm/cold startup. + 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 UNION ALL SELECT * FROM maybe_hot - ) SELECT - "maxsdk28" AS sdk, - ROW_NUMBER() OVER(ORDER BY ts) AS startup_id, ts, ts_end, ts_end - ts AS dur, package, startup_type -FROM cold_warm_hot; - - +FROM cold_warm_hot +ORDER BY ts; diff --git a/src/trace_processor/perfetto_sql/stdlib/android/startup/startups_minsdk29.sql b/src/trace_processor/perfetto_sql/stdlib/android/startup/startups_minsdk29.sql index fcc1fa9d8d..7041bdddda 100644 --- a/src/trace_processor/perfetto_sql/stdlib/android/startup/startups_minsdk29.sql +++ b/src/trace_processor/perfetto_sql/stdlib/android/startup/startups_minsdk29.sql @@ -26,8 +26,6 @@ WHERE name = 'MetricsLogger:launchObserverNotifyIntentStarted'; -- activity starts. CREATE PERFETTO TABLE _activity_intent_recv_spans AS SELECT - ROW_NUMBER() - OVER(ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS startup_id, ts, LEAD(ts, 1, trace_end()) OVER(ORDER BY ts) - ts AS dur FROM _activity_intent_received @@ -52,8 +50,6 @@ WHERE name = 'MetricsLogger:launchObserverNotifyActivityLaunchFinished'; -- is not reliable in the case of failed startups. CREATE PERFETTO TABLE _startups_minsdk29 AS SELECT - "minsdk29" as sdk, - lpart.startup_id, lpart.ts, le.ts_end, le.ts_end - lpart.ts AS dur, @@ -67,4 +63,5 @@ WHERE ( SELECT COUNT(1) FROM _activity_intent_startup_successful AS successful WHERE successful.ts BETWEEN lpart.ts AND lpart.ts + lpart.dur -) > 0; +) > 0 +ORDER BY lpart.ts; diff --git a/src/trace_processor/perfetto_sql/stdlib/android/startup/startups_minsdk33.sql b/src/trace_processor/perfetto_sql/stdlib/android/startup/startups_minsdk33.sql index b6438089e1..2378473d69 100644 --- a/src/trace_processor/perfetto_sql/stdlib/android/startup/startups_minsdk33.sql +++ b/src/trace_processor/perfetto_sql/stdlib/android/startup/startups_minsdk33.sql @@ -50,7 +50,6 @@ GROUP BY 1, 2, 3; CREATE PERFETTO TABLE _startups_minsdk33 AS SELECT - "minsdk33" as sdk, startup_id, ts, ts + dur AS ts_end, @@ -59,6 +58,3 @@ SELECT startup_type FROM _startup_async_events JOIN _startup_complete_events USING (startup_id); - - - diff --git a/src/trace_processor/perfetto_sql/stdlib/android/suspend.sql b/src/trace_processor/perfetto_sql/stdlib/android/suspend.sql index 0d31b0dc7b..1551ea6795 100644 --- a/src/trace_processor/perfetto_sql/stdlib/android/suspend.sql +++ b/src/trace_processor/perfetto_sql/stdlib/android/suspend.sql @@ -73,7 +73,9 @@ SELECT ts, dur, 'awake' AS power_state FROM awake_slice UNION ALL SELECT ts, dur, 'suspended' AS power_state -FROM suspend_slice; +FROM suspend_slice +ORDER BY ts; -- Order by will cause Perfetto table to index by ts. + -- Extracts the duration without counting CPU suspended time from an event. -- This is the same as converting an event duration from wall clock to monotonic clock. diff --git a/src/trace_processor/perfetto_sql/stdlib/common/cpus.sql b/src/trace_processor/perfetto_sql/stdlib/android/version.sql similarity index 66% rename from src/trace_processor/perfetto_sql/stdlib/common/cpus.sql rename to src/trace_processor/perfetto_sql/stdlib/android/version.sql index 7923e86ca1..e82777867c 100644 --- a/src/trace_processor/perfetto_sql/stdlib/common/cpus.sql +++ b/src/trace_processor/perfetto_sql/stdlib/android/version.sql @@ -13,9 +13,8 @@ -- See the License for the specific language governing permissions and -- limitations under the License. --- No new changes allowed. Will be removed after v45 of Perfetto. --- --- We decided to move away from the generalised `common` module and migrate the --- most useful functionality into specialised modules. -INCLUDE PERFETTO MODULE deprecated.v42.common.args; -INCLUDE PERFETTO MODULE deprecated.v42.common.cpus; \ No newline at end of file +CREATE PERFETTO FUNCTION _android_sdk_version() +RETURNS INT AS +SELECT int_value AS sdk_version +FROM metadata +WHERE name = 'android_sdk_version'; diff --git a/src/trace_processor/perfetto_sql/stdlib/android/winscope/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/android/winscope/BUILD.gn index 93abe356ef..8a07032be1 100644 --- a/src/trace_processor/perfetto_sql/stdlib/android/winscope/BUILD.gn +++ b/src/trace_processor/perfetto_sql/stdlib/android/winscope/BUILD.gn @@ -17,5 +17,7 @@ import("../../../../../../gn/perfetto_sql.gni") perfetto_sql_source_set("winscope") { sources = [ "inputmethod.sql", + "viewcapture.sql", + "windowmanager.sql", ] } diff --git a/src/trace_processor/perfetto_sql/stdlib/common/thread_states.sql b/src/trace_processor/perfetto_sql/stdlib/android/winscope/viewcapture.sql similarity index 60% rename from src/trace_processor/perfetto_sql/stdlib/common/thread_states.sql rename to src/trace_processor/perfetto_sql/stdlib/android/winscope/viewcapture.sql index a21e9da657..77e68b5f12 100644 --- a/src/trace_processor/perfetto_sql/stdlib/common/thread_states.sql +++ b/src/trace_processor/perfetto_sql/stdlib/android/winscope/viewcapture.sql @@ -1,5 +1,5 @@ -- --- Copyright 2022 The Android Open Source Project +-- 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. @@ -13,9 +13,17 @@ -- See the License for the specific language governing permissions and -- limitations under the License. --- No new changes allowed. Will be removed after v45 of Perfetto. --- --- We decided to move away from the generalised `common` module and migrate the --- most useful functionality into specialised modules. -INCLUDE PERFETTO MODULE deprecated.v42.common.args; -INCLUDE PERFETTO MODULE deprecated.v42.common.thread_states; \ No newline at end of file +-- Android viewcapture (from android.viewcapture data source). +CREATE PERFETTO VIEW android_viewcapture( + -- Snapshot id + id INT, + -- Timestamp when the snapshot was triggered + ts INT, + -- Extra args parsed from the proto message + arg_set_id INT +) AS +SELECT + id, + ts, + arg_set_id +FROM __intrinsic_viewcapture; diff --git a/src/trace_processor/perfetto_sql/stdlib/android/winscope/windowmanager.sql b/src/trace_processor/perfetto_sql/stdlib/android/winscope/windowmanager.sql new file mode 100644 index 0000000000..e60212598c --- /dev/null +++ b/src/trace_processor/perfetto_sql/stdlib/android/winscope/windowmanager.sql @@ -0,0 +1,29 @@ +-- +-- 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. + +-- Android WindowManager (from android.windowmanager data source). +CREATE PERFETTO VIEW android_windowmanager( + -- Snapshot id + id INT, + -- Timestamp when the snapshot was triggered + ts INT, + -- Extra args parsed from the proto message + arg_set_id INT +) AS +SELECT + id, + ts, + arg_set_id +FROM __intrinsic_windowmanager; diff --git a/src/trace_processor/perfetto_sql/stdlib/memory/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/callstacks/BUILD.gn similarity index 88% rename from src/trace_processor/perfetto_sql/stdlib/memory/BUILD.gn rename to src/trace_processor/perfetto_sql/stdlib/callstacks/BUILD.gn index ae3ad179ca..02b23eebd4 100644 --- a/src/trace_processor/perfetto_sql/stdlib/memory/BUILD.gn +++ b/src/trace_processor/perfetto_sql/stdlib/callstacks/BUILD.gn @@ -14,6 +14,6 @@ import("../../../../../gn/perfetto_sql.gni") -perfetto_sql_source_set("memory") { - sources = [ "heap_graph_dominator_tree.sql" ] +perfetto_sql_source_set("callstacks") { + sources = [ "stack_profile.sql" ] } diff --git a/src/trace_processor/perfetto_sql/stdlib/callstacks/stack_profile.sql b/src/trace_processor/perfetto_sql/stdlib/callstacks/stack_profile.sql new file mode 100644 index 0000000000..702151bb91 --- /dev/null +++ b/src/trace_processor/perfetto_sql/stdlib/callstacks/stack_profile.sql @@ -0,0 +1,114 @@ +-- +-- 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 graphs.hierarchy; + +CREATE PERFETTO TABLE _callstack_spf_summary AS +SELECT + id, + symbol_set_id, + ( + SELECT id + FROM stack_profile_symbol s + WHERE s.symbol_set_id = f.symbol_set_id + ORDER BY id + LIMIT 1 + ) AS min_symbol_id, + ( + SELECT id + FROM stack_profile_symbol s + WHERE s.symbol_set_id = f.symbol_set_id + ORDER BY id DESC + LIMIT 1 + ) AS max_symbol_id +FROM stack_profile_frame f +ORDER BY id; + +CREATE PERFETTO TABLE _callstack_spc_raw_forest AS +SELECT + c.id AS callsite_id, + s.id AS symbol_id, + IIF( + s.id IS f.min_symbol_id, + c.parent_id, + c.id + ) AS parent_callsite_id, + IIF( + s.id IS f.min_symbol_id, + pf.max_symbol_id, + s.id - 1 + ) AS parent_symbol_id, + f.id AS frame_id, + s.id IS f.max_symbol_id AS is_leaf +FROM stack_profile_callsite c +JOIN _callstack_spf_summary f ON c.frame_id = f.id +LEFT JOIN stack_profile_symbol s USING (symbol_set_id) +LEFT JOIN stack_profile_callsite p ON c.parent_id = p.id +LEFT JOIN _callstack_spf_summary pf ON p.frame_id = pf.id +ORDER BY c.id; + +CREATE PERFETTO TABLE _callstack_spc_forest AS +SELECT + c._auto_id AS id, + p._auto_id AS parent_id, + -- TODO(lalitm): consider demangling in a separate table as + -- demangling is suprisingly inefficient and is taking a + -- significant fraction of the runtime on big traces. + IFNULL( + DEMANGLE(COALESCE(s.name, f.deobfuscated_name, f.name)), + COALESCE(s.name, f.deobfuscated_name, f.name) + ) AS name, + f.mapping AS mapping_id, + s.source_file, + s.line_number, + c.callsite_id, + c.is_leaf AS is_leaf_function_in_callsite_frame +FROM _callstack_spc_raw_forest c +JOIN stack_profile_frame f ON c.frame_id = f.id +LEFT JOIN stack_profile_symbol s ON c.symbol_id = s.id +LEFT JOIN _callstack_spc_raw_forest p ON + p.callsite_id = c.parent_callsite_id + AND p.symbol_id IS c.parent_symbol_id +ORDER BY c._auto_id; + +CREATE PERFETTO INDEX _callstack_spc_index +ON _callstack_spc_forest(callsite_id); + +CREATE PERFETTO MACRO _callstacks_for_stack_profile_samples( + spc_samples TableOrSubquery +) +RETURNS TableOrSubquery +AS +( + SELECT + f.id, + f.parent_id, + f.callsite_id, + f.name, + m.name AS mapping_name, + f.source_file, + f.line_number + FROM _tree_reachable_ancestors_or_self!( + _callstack_spc_forest, + ( + SELECT f.id + FROM $spc_samples s + JOIN _callstack_spc_forest f USING (callsite_id) + WHERE f.is_leaf_function_in_callsite_frame + ) + ) g + JOIN _callstack_spc_forest f USING (id) + JOIN stack_profile_mapping m ON f.mapping_id = m.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 fcc893306f..cf8ec56981 100644 --- a/src/trace_processor/perfetto_sql/stdlib/chrome/chrome_scrolls.sql +++ b/src/trace_processor/perfetto_sql/stdlib/chrome/chrome_scrolls.sql @@ -31,20 +31,18 @@ CREATE PERFETTO TABLE chrome_scrolls( ) AS WITH all_scrolls AS ( SELECT - 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*" + event_type AS name, + ts, + dur, + scroll_id + FROM chrome_gesture_scroll_events ), scroll_starts AS ( SELECT scroll_id, MIN(ts) AS gesture_scroll_begin_ts FROM all_scrolls - WHERE name = "GESTURE_SCROLL_BEGIN" + WHERE name = 'GESTURE_SCROLL_BEGIN' GROUP BY scroll_id ), scroll_ends AS ( @@ -52,8 +50,12 @@ scroll_ends AS ( scroll_id, MAX(ts) AS gesture_scroll_end_ts FROM all_scrolls - WHERE name GLOB "*GESTURE_SCROLL_UPDATE" - OR name = "GESTURE_SCROLL_END" + WHERE name IN ( + 'GESTURE_SCROLL_UPDATE', + 'FIRST_GESTURE_SCROLL_UPDATE', + 'INERTIAL_GESTURE_SCROLL_UPDATE', + 'GESTURE_SCROLL_END' + ) GROUP BY scroll_id ) SELECT @@ -68,49 +70,3 @@ FROM all_scrolls sa LEFT JOIN scroll_ends se ON sa.scroll_id = se.scroll_id GROUP BY sa.scroll_id; - --- Defines slices for all of scrolls intervals in a trace based on the scroll --- definition in chrome_scrolls. Note that scrolls may overlap (particularly in --- cases of jank/broken traces, etc); so scrolling intervals are not exactly the --- same as individual scrolls. -CREATE PERFETTO VIEW chrome_scrolling_intervals( - -- The unique identifier of the scroll interval. This may span multiple scrolls if they overlap. - id INT, - -- Comma-separated list of scroll ids that are included in this interval. - scroll_ids STRING, - -- The start timestamp of the scroll interval. - ts INT, - -- The duration of the scroll interval. - dur INT -) AS -WITH all_scrolls AS ( - SELECT - id AS scroll_id, - s.ts AS start_ts, - s.ts + s.dur AS end_ts - FROM chrome_scrolls s), -ordered_end_ts AS ( - SELECT - *, - MAX(end_ts) OVER (ORDER BY start_ts) AS max_end_ts_so_far - FROM all_scrolls), -range_starts AS ( - SELECT - *, - CASE - WHEN start_ts <= 1 + LAG(max_end_ts_so_far) OVER (ORDER BY start_ts) THEN 0 - ELSE 1 - END AS range_start - FROM ordered_end_ts), -range_groups AS ( - SELECT - *, - SUM(range_start) OVER (ORDER BY start_ts) AS range_group - FROM range_starts) -SELECT - range_group AS id, - GROUP_CONCAT(scroll_id) AS scroll_ids, - MIN(start_ts) AS ts, - MAX(end_ts) - MIN(start_ts) AS dur -FROM range_groups -GROUP BY range_group; diff --git a/src/trace_processor/perfetto_sql/stdlib/chrome/event_latency.sql b/src/trace_processor/perfetto_sql/stdlib/chrome/event_latency.sql new file mode 100644 index 0000000000..8e58912205 --- /dev/null +++ b/src/trace_processor/perfetto_sql/stdlib/chrome/event_latency.sql @@ -0,0 +1,159 @@ +-- Copyright 2024 The Chromium Authors +-- Use of this source code is governed by a BSD-style license that can be +-- found in the LICENSE file. + +INCLUDE PERFETTO MODULE deprecated.v42.common.slices; + +-- 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 first one, so it's most useful when working with a timeline broken down +-- into phases, where each subphase can happen only once. +CREATE PERFETTO FUNCTION _descendant_slice_end( + -- Id of the parent slice. + parent_id INT, + -- Name of the child with the desired end TS. + child_name STRING +) +-- End timestamp of the child or NULL if it doesn't exist. +RETURNS INT AS +SELECT + CASE WHEN s.dur + IS NOT -1 THEN s.ts + s.dur + ELSE NULL + END +FROM descendant_slice($parent_id) s +WHERE s.name GLOB $child_name +LIMIT 1; + +-- Returns the presentation timestamp for a given EventLatency slice. +-- This is either the end of +-- SwapEndToPresentationCompositorFrame (if it exists), +-- the end of LatchToPresentation (if it exists), +-- the end of SwapStartToPresentation (if it exists), +-- or the end of LatchToSwapEnd (workaround in older Chrome versions). +CREATE PERFETTO FUNCTION _get_presentation_timestamp( + -- The slice id which we need the presentation timestamp for. + id LONG +) +RETURNS INT AS +SELECT + COALESCE(_descendant_slice_end(id, 'SwapEndToPresentationCompositorFrame'), + _descendant_slice_end(id, '*ToPresentation'), + _descendant_slice_end(id, 'LatchToSwapEnd')) +FROM slice WHERE $id = id; + +-- All EventLatency slices. +CREATE PERFETTO TABLE chrome_event_latencies( + -- Slice Id for the EventLatency scroll event. + id INT, + -- Slice name. + name STRING, + -- The start timestamp of the scroll. + ts INT, + -- The duration of the scroll. + dur INT, + -- The id of the scroll update event. + scroll_update_id INT, + -- Whether this input event was presented. + is_presented BOOL, + -- EventLatency event type. + event_type STRING, + -- Perfetto track this slice is found on. + track_id INT +) AS +SELECT + slice.id, + slice.name, + slice.ts, + slice.dur, + EXTRACT_arg(arg_set_id, 'event_latency.event_latency_id') AS scroll_update_id, + has_descendant_slice_with_name( + slice.id, + 'SubmitCompositorFrameToPresentationCompositorFrame') + AS is_presented, + EXTRACT_ARG(arg_set_id, 'event_latency.event_type') AS event_type, + slice.track_id +FROM slice +WHERE name = 'EventLatency'; + +-- All EventLatency slices that are relevant to scrolling, including presented +-- pinches. Materialized to reduce how many times we query slice. +CREATE PERFETTO TABLE _gesture_scroll_events_no_scroll_id +AS +SELECT + name, + ts, + dur, + id, + scroll_update_id, + is_presented, + _get_presentation_timestamp(chrome_event_latencies.id) + AS presentation_timestamp, + event_type, + track_id +FROM chrome_event_latencies +WHERE ( + event_type GLOB '*GESTURE_SCROLL*' + -- Pinches are only relevant if the frame was presented. + OR (event_type GLOB '*GESTURE_PINCH_UPDATE' + AND has_descendant_slice_with_name( + id, + 'SubmitCompositorFrameToPresentationCompositorFrame') + ) +); + +-- 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 scroll_update_id +FROM _gesture_scroll_events_no_scroll_id +WHERE event_type = 'GESTURE_SCROLL_BEGIN' +AND ts<=$ts +ORDER BY ts DESC +LIMIT 1; + +-- All scroll-related events (frames) including gesture scroll updates, begins +-- and ends with respective scroll ids and start/end timestamps, regardless of +-- being presented. This includes pinches that were presented. See b/315761896 +-- for context on pinches. +CREATE PERFETTO TABLE chrome_gesture_scroll_events( + -- Slice Id for the EventLatency scroll event. + id INT, + -- Slice name. + name STRING, + -- The start timestamp of the scroll. + ts INT, + -- The duration of the scroll. + dur INT, + -- The id of the scroll update event. + scroll_update_id INT, + -- The id of the scroll. + scroll_id INT, + -- Whether this input event was presented. + is_presented BOOL, + -- Frame presentation timestamp aka the timestamp of the + -- SwapEndToPresentationCompositorFrame substage. + -- TODO(b/341047059): temporarily use LatchToSwapEnd as a workaround if + -- SwapEndToPresentationCompositorFrame is missing due to b/247542163. + presentation_timestamp INT, + -- EventLatency event type. + event_type STRING, + -- Perfetto track this slice is found on. + track_id INT +) AS +SELECT + id, + name, + ts, + dur, + scroll_update_id, + chrome_get_most_recent_scroll_begin_id(ts) AS scroll_id, + is_presented, + presentation_timestamp, + event_type, + track_id +FROM _gesture_scroll_events_no_scroll_id; diff --git a/src/trace_processor/perfetto_sql/stdlib/chrome/interactions.sql b/src/trace_processor/perfetto_sql/stdlib/chrome/interactions.sql index 78600dc1cd..a3211cabaf 100644 --- a/src/trace_processor/perfetto_sql/stdlib/chrome/interactions.sql +++ b/src/trace_processor/perfetto_sql/stdlib/chrome/interactions.sql @@ -7,6 +7,7 @@ -- Currently we only track Chrome page loads and their associated metrics. INCLUDE PERFETTO MODULE chrome.page_loads; +INCLUDE PERFETTO MODULE chrome.scroll_interactions; INCLUDE PERFETTO MODULE chrome.startups; INCLUDE PERFETTO MODULE chrome.web_content_interactions; @@ -55,4 +56,12 @@ SELECT 'InteractionToFirstPaint' AS name, ts, dur -FROM chrome_web_content_interactions; +FROM chrome_web_content_interactions +UNION ALL +SELECT + id AS scoped_id, + 'chrome_scroll_interactions' AS type, + 'Scroll' AS name, + ts, + dur +FROM chrome_scroll_interactions; diff --git a/src/trace_processor/perfetto_sql/stdlib/chrome/perfetto_sql_files.gni b/src/trace_processor/perfetto_sql/stdlib/chrome/perfetto_sql_files.gni index 9a84d4a65d..77eeb14ba1 100644 --- a/src/trace_processor/perfetto_sql/stdlib/chrome/perfetto_sql_files.gni +++ b/src/trace_processor/perfetto_sql/stdlib/chrome/perfetto_sql_files.gni @@ -6,16 +6,21 @@ chrome_stdlib_sql_files = [ "chrome_scrolls.sql", "cpu_powerups.sql", + "event_latency.sql", "event_latency_description.sql", "histograms.sql", "interactions.sql", "metadata.sql", "page_loads.sql", + "scroll_interactions.sql", "speedometer.sql", + "speedometer_2_1.sql", + "speedometer_3.sql", "startups.sql", "tasks.sql", "vsync_intervals.sql", "web_content_interactions.sql", + "scroll_jank/predictor_error.sql", "scroll_jank/scroll_jank_cause_map.sql", "scroll_jank/scroll_jank_cause_utils.sql", "scroll_jank/scroll_jank_intervals.sql", diff --git a/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_interactions.sql b/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_interactions.sql new file mode 100644 index 0000000000..9319849e76 --- /dev/null +++ b/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_interactions.sql @@ -0,0 +1,66 @@ +-- Copyright 2024 The Chromium Authors +-- Use of this source code is governed by a BSD-style license that can be +-- found in the LICENSE file. + +INCLUDE PERFETTO MODULE slices.with_context; + +-- Top level scroll events, with metrics. +CREATE PERFETTO TABLE chrome_scroll_interactions( + -- Unique id for an individual scroll. + id INT, + -- Name of the scroll event. + name STRING, + -- Start timestamp of the scroll. + ts INT, + -- Duration of the scroll. + dur INT, + -- The total number of frames in the scroll. + frame_count INT, + -- The total number of vsyncs in the scroll. + vsync_count INT, + -- The maximum number of vsyncs missed during any and all janks. + missed_vsync_max INT, + -- The total number of vsyncs missed during any and all janks. + missed_vsync_sum INT, + -- The number of delayed frames. + delayed_frame_count INT, + -- The number of frames that are deemed janky to the human eye after Chrome + -- has applied its scroll prediction algorithm. + predictor_janky_frame_count INT, + -- The process id this event occurred on. + renderer_upid INT +) AS +WITH scroll_metrics AS ( + SELECT + id, + ts, + dur, + EXTRACT_ARG(arg_set_id, 'scroll_metrics.frame_count') + AS frame_count, + EXTRACT_ARG(arg_set_id, 'scroll_metrics.vsync_count') + AS vsync_count, + EXTRACT_ARG(arg_set_id, 'scroll_metrics.missed_vsync_max') + AS missed_vsync_max, + EXTRACT_ARG(arg_set_id, 'scroll_metrics.missed_vsync_sum') + AS missed_vsync_sum, + EXTRACT_ARG(arg_set_id, 'scroll_metrics.delayed_frame_count') + AS delayed_frame_count, + EXTRACT_ARG(arg_set_id, 'scroll_metrics.predictor_janky_frame_count') + AS predictor_janky_frame_count, + upid AS renderer_upid + FROM process_slice + WHERE name = 'Scroll' +) +SELECT + id, + 'Scroll' AS name, + ts, + dur, + frame_count, + vsync_count, + missed_vsync_max, + missed_vsync_sum, + delayed_frame_count, + predictor_janky_frame_count, + renderer_upid +FROM scroll_metrics; \ No newline at end of file diff --git a/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/predictor_error.sql b/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/predictor_error.sql new file mode 100644 index 0000000000..fc8415a3d9 --- /dev/null +++ b/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/predictor_error.sql @@ -0,0 +1,147 @@ +-- Copyright 2024 The Chromium Authors +-- Use of this source code is governed by a BSD-style license that can be +-- found in the LICENSE file. + +-- This file implements the scrolling predictor jank metric, as is +-- implemented in cc/metrics/predictor_jank_tracker.cc. See comments in that +-- file to get additional context on how the metric is implemented. +-- +-- "Delta" here refers to how much (in pixels) the page offset changed for a +-- given frame due to a scroll. +-- +-- For more details please check the following document: +-- http://doc/1Y0u0Tq5eUZff75nYUzQVw6JxmbZAW9m64pJidmnGWsY. + +INCLUDE PERFETTO MODULE chrome.scroll_jank.scroll_offsets; + +-- The maximum delta value between three consecutive frames, used to determine +-- whether the sequence in the scroll is fast or slow; the sequence speed is +-- used to determine whether the sequence includes any predictor jank. +CREATE PERFETTO FUNCTION _get_slow_scroll_delta_threshold() +RETURNS DOUBLE AS +SELECT 7.0; + +-- The threshold for the ratio of the delta of middle frame to tbe deltas of its +-- neighbors in a sequence of three frames, if the sequence is considered +-- "slow". +CREATE PERFETTO FUNCTION _get_slow_scroll_janky_threshold() +RETURNS DOUBLE AS +SELECT 1.4; + +-- The threshold for the ratio of the delta of middle frame to tbe deltas of its +-- neighbors in a sequence of three frames, if the sequence is considered +-- "fast". +CREATE PERFETTO FUNCTION _get_fast_scroll_janky_threshold() +RETURNS DOUBLE AS +SELECT 1.2; + +-- Determine the acceptable threshold (see _get_slow_scroll_janky_threshold() +-- and _get_fast_scroll_janky_threshold()) based on the maximum delta value +-- between three consecutive frames. +CREATE PERFETTO FUNCTION _get_scroll_jank_threshold( + d1 DOUBLE, + d2 DOUBLE, + d3 DOUBLE +) +RETURNS DOUBLE AS +SELECT + CASE + WHEN MAX(MAX($d1, $d2), $d3) <= _get_slow_scroll_delta_threshold() + THEN _get_slow_scroll_janky_threshold() + ELSE _get_fast_scroll_janky_threshold() + END; + +-- Calculate the predictor jank of three consecutive frames, if it is above the +-- threshold. Anything below the threshold is not considered jank. +CREATE PERFETTO FUNCTION _get_predictor_jank( + d1 DOUBLE, + d2 DOUBLE, + d3 DOUBLE, + threshold DOUBLE +) +RETURNS DOUBLE AS +SELECT + CASE + WHEN $d2/MAX($d1, $d3) >= $threshold + THEN $d2/MAX($d1, $d3) - $threshold + WHEN MIN($d1, $d3)/$d2 >= $threshold + THEN MIN($d1, $d3)/$d2 - $threshold + ELSE 0 + END; + +CREATE PERFETTO TABLE _deltas_and_neighbors AS +SELECT + scroll_id, + event_latency_slice_id, + scroll_update_id, + ts, + delta_y, + relative_offset_y, + LAG(IFNULL(delta_y, 0.0)) + OVER (PARTITION BY scroll_id ORDER BY ts ASC) AS prev_delta, + LEAD(IFNULL(delta_y, 0.0)) + OVER (PARTITION BY scroll_id ORDER BY ts ASC) AS next_delta +FROM chrome_presented_scroll_offsets; + +CREATE PERFETTO TABLE _deltas_and_neighbors_with_threshold AS +SELECT + scroll_id, + event_latency_slice_id, + scroll_update_id, + ts, + delta_y, + relative_offset_y, + prev_delta, + next_delta, + _get_scroll_jank_threshold(ABS(prev_delta), ABS(delta_y), ABS(next_delta)) + AS delta_threshold +FROM _deltas_and_neighbors +WHERE delta_y IS NOT NULL + AND prev_delta IS NOT NULL + AND next_delta IS NOT NULL; + +-- The scrolling offsets and predictor jank values for the actual (applied) +-- scroll events. +CREATE PERFETTO TABLE chrome_predictor_error( + -- An ID that ties all EventLatencies in a particular scroll. (implementation + -- note: This is the EventLatency TraceId of the GestureScrollbegin). + scroll_id INT, + -- An ID for this particular EventLatency regardless of it being presented or + -- not. + event_latency_slice_id INT, + -- An ID that ties this |event_latency_id| with the Trace Id (another + -- event_latency_id) that it was presented with. + scroll_update_id INT, + -- Presentation timestamp. + present_ts INT, + -- The delta in raw coordinates between this presented EventLatency and the + -- previous presented frame. + delta_y DOUBLE, + -- The pixel offset of this presented EventLatency compared to the initial + -- one. + relative_offset_y DOUBLE, + -- The delta in raw coordinates of the previous scroll update event. + prev_delta DOUBLE, + -- The delta in raw coordinates of the subsequent scroll update event. + next_delta DOUBLE, + -- The jank value based on the discrepancy between scroll predictor + -- coordinates and the actual deltas between scroll update events. + predictor_jank DOUBLE, + -- The threshold used to determine if jank occurred. + delta_threshold DOUBLE +) +AS +SELECT + scroll_id, + event_latency_slice_id, + scroll_update_id, + ts AS present_ts, + delta_y, + relative_offset_y, + prev_delta, + next_delta, + _get_predictor_jank( + ABS(prev_delta), ABS(delta_y), ABS(next_delta), delta_threshold) + AS predictor_jank, + delta_threshold +FROM _deltas_and_neighbors_with_threshold; diff --git a/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/scroll_jank_intervals.sql b/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/scroll_jank_intervals.sql index 5fb66d6141..ab15041aa5 100644 --- a/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/scroll_jank_intervals.sql +++ b/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/scroll_jank_intervals.sql @@ -37,10 +37,13 @@ SELECT s.name, e.cause_of_jank, e.sub_cause_of_jank, - CAST((e.delay_since_last_frame/e.vsync_interval) - 1 AS INT) AS delayed_frame_count, - CAST(s.ts + s.dur - ((e.delay_since_last_frame - e.vsync_interval) * 1e6) AS INT) AS frame_jank_ts, - CAST((e.delay_since_last_frame - e.vsync_interval) * 1e6 AS INT) AS frame_jank_dur -FROM slice s + CAST((e.delay_since_last_frame/e.vsync_interval) - 1 AS INT) + AS delayed_frame_count, + CAST(s.ts + s.dur - ((e.delay_since_last_frame - e.vsync_interval) * 1e6) + AS INT) AS frame_jank_ts, + CAST((e.delay_since_last_frame - e.vsync_interval) * 1e6 AS INT) + AS frame_jank_dur +FROM chrome_gesture_scroll_events s JOIN chrome_janky_frames e ON s.id = e. event_latency_id; @@ -56,9 +59,9 @@ CREATE PERFETTO VIEW chrome_janky_frame_presentation_intervals( -- How many vsyncs this frame missed its deadline by. delayed_frame_count INT, -- The stage of EventLatency that the caused the jank. - cause_of_jank INT, + cause_of_jank STRING, -- The stage of cause_of_jank that caused the jank. - sub_cause_of_jank INT, + sub_cause_of_jank STRING, -- The id of the associated event latency in the slice table. event_latency_id INT ) AS @@ -73,7 +76,7 @@ SELECT FROM chrome_janky_event_latencies_v3; -- Scroll jank frame presentation stats for individual scrolls. -CREATE PERFETTO VIEW chrome_scroll_stats( +CREATE PERFETTO TABLE chrome_scroll_stats( -- Id of the individual scroll. scroll_id INT, -- The number of frames in the scroll. @@ -98,7 +101,8 @@ WITH vsyncs AS ( GROUP BY scroll_id), missed_vsyncs AS ( SELECT - CAST(SUM((delay_since_last_frame / vsync_interval) - 1) AS INT) AS total_missed_vsyncs, + CAST(SUM((delay_since_last_frame / vsync_interval) - 1) AS INT) + AS total_missed_vsyncs, scroll_id FROM chrome_janky_frames GROUP BY scroll_id), @@ -157,7 +161,8 @@ range_starts AS ( -- This is a two-pass calculation to calculate the first event in the -- group. An event is considered the first event in a group if all events -- which started before it also finished the current one started. - WHEN start_ts <= 1 + LAG(max_end_ts_so_far) OVER (ORDER BY start_ts) THEN 0 + WHEN start_ts <= 1 + LAG(max_end_ts_so_far) OVER (ORDER BY start_ts) + THEN 0 ELSE 1 END AS range_start FROM ordered_jank_end_ts), 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 589adecd15..7da57d356f 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 @@ -2,35 +2,12 @@ -- Use of this source code is governed by a BSD-style license that can be -- found in the LICENSE file. -INCLUDE PERFETTO MODULE deprecated.v42.common.slices; - -- Hardware info is useful when using sql metrics for analysis -- in BTP. INCLUDE PERFETTO MODULE chrome.metadata; INCLUDE PERFETTO MODULE chrome.scroll_jank.scroll_jank_v3_cause; 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 --- first one, so it's most useful when working with a timeline broken down into phases, --- where each subphase can happen only once. -CREATE PERFETTO FUNCTION _descendant_slice_end( - -- Id of the parent slice. - parent_id INT, - -- Name of the child with the desired end TS. - child_name STRING -) --- End timestamp of the child or NULL if it doesn't exist. -RETURNS INT AS -SELECT - CASE WHEN s.dur - IS NOT -1 THEN s.ts + s.dur - ELSE NULL - END -FROM descendant_slice($parent_id) s -WHERE s.name = $child_name -LIMIT 1; - -- Given a slice id, returns the name of the slice. CREATE PERFETTO FUNCTION _slice_name_from_id( -- The slice id which we need the name for. @@ -62,21 +39,24 @@ CREATE PERFETTO TABLE chrome_gesture_scroll_updates( -- SwapEndToPresentationCompositorFrame substage. presentation_timestamp INT, -- EventLatency event type. - event_type INT + event_type STRING ) AS SELECT - 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"; + ts, + dur, + id, + scroll_update_id, + scroll_id, + is_presented, + presentation_timestamp, + event_type +FROM chrome_gesture_scroll_events +WHERE event_type IN ( + 'GESTURE_SCROLL_UPDATE', + 'FIRST_GESTURE_SCROLL_UPDATE', + 'INERTIAL_GESTURE_SCROLL_UPDATE', + 'GESTURE_PINCH_UPDATE' +); CREATE PERFETTO TABLE _presented_gesture_scrolls AS SELECT @@ -109,7 +89,7 @@ CREATE PERFETTO TABLE chrome_presented_gesture_scrolls( -- Frame presentation timestamp. presentation_timestamp INT, -- EventLatency event type. - event_type INT + event_type STRING ) AS WITH scroll_updates_with_presentation_info as MATERIALIZED ( @@ -146,10 +126,7 @@ SELECT 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; +FROM _presented_gesture_scrolls; -- Associate every trace_id with it's perceived delta_y on the screen after -- prediction. @@ -157,7 +134,7 @@ CREATE PERFETTO TABLE chrome_scroll_updates_with_deltas( -- The id of the scroll update event. scroll_update_id INT, -- The perceived delta_y on the screen post prediction. - delta_y INT + delta_y DOUBLE ) AS SELECT EXTRACT_ARG(arg_set_id, 'scroll_deltas.trace_id') AS scroll_update_id, @@ -165,8 +142,7 @@ SELECT FROM slice WHERE name = "InputHandlerProxy::HandleGestureScrollUpdate_Result"; --- Obtain the subset of input events that were fully presented, as indicated --- by the presence of SwapEndToPresentationCompositorFrame. +-- Obtain the subset of input events that were fully presented. CREATE PERFETTO TABLE chrome_full_frame_view( -- ID of the frame. id INT, @@ -196,10 +172,11 @@ SELECT frames.presentation_timestamp FROM chrome_presented_gesture_scrolls frames 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"); + 'GESTURE_SCROLL_UPDATE', + 'FIRST_GESTURE_SCROLL_UPDATE', + 'INERTIAL_GESTURE_SCROLL_UPDATE', + 'GESTURE_PINCH_UPDATE') +AND frames.presentation_timestamp IS NOT NULL; -- Join deltas with EventLatency data. CREATE PERFETTO TABLE chrome_full_frame_delta_view( @@ -214,7 +191,7 @@ CREATE PERFETTO TABLE chrome_full_frame_delta_view( -- The timestamp of the last presented input. last_presented_input_ts INT, -- The perceived delta_y on the screen post prediction. - delta_y INT, + delta_y DOUBLE, -- ID of the associated EventLatency. event_latency_id INT, -- Duration of the associated EventLatency. @@ -238,7 +215,7 @@ LEFT JOIN chrome_scroll_updates_with_deltas deltas -- Group all gestures presented at the same timestamp together in -- a single row. -CREATE PERFETTO VIEW chrome_merged_frame_view( +CREATE PERFETTO TABLE chrome_merged_frame_view( -- ID of the frame. id INT, -- The timestamp of the last presented input. @@ -250,11 +227,11 @@ CREATE PERFETTO VIEW chrome_merged_frame_view( -- ID of the associated scroll update. scroll_update_id INT, -- All scroll updates associated with the frame presentation timestamp. - encapsulated_scroll_ids INT, + encapsulated_scroll_ids STRING, -- Sum of all perceived delta_y values at the frame presentation timestamp. - total_delta INT, + total_delta DOUBLE, -- Lists all of the perceived delta_y values at the frame presentation timestamp. - segregated_delta_y INT, + segregated_delta_y STRING, -- ID of the associated EventLatency. event_latency_id INT, -- Maximum duration of the associated EventLatency. @@ -281,7 +258,7 @@ ORDER BY presentation_timestamp; -- View contains all chrome presented frames during gesture updates -- while calculating delay since last presented which usually should -- equal to |VSYNC_INTERVAL| if no jank is present. -CREATE PERFETTO VIEW chrome_frame_info_with_delay( +CREATE PERFETTO TABLE chrome_frame_info_with_delay( -- gesture scroll slice id. id INT, -- OS timestamp of the last touch move arrival within a frame. @@ -293,11 +270,11 @@ CREATE PERFETTO VIEW chrome_frame_info_with_delay( -- ID of the associated scroll update. scroll_update_id INT, -- Trace ids of all frames presented in at this vsync. - encapsulated_scroll_ids INT, + encapsulated_scroll_ids STRING, -- Summation of all delta_y of all gesture scrolls in this frame. - total_delta INT, + total_delta DOUBLE, -- All delta y of all gesture scrolls comma separated, summing those gives |total_delta|. - segregated_delta_y INT, + segregated_delta_y STRING, -- Event latency id of the presented frame. event_latency_id INT, -- Duration of the EventLatency. @@ -306,9 +283,9 @@ CREATE PERFETTO VIEW chrome_frame_info_with_delay( presentation_timestamp INT, -- Time elapsed since the previous frame was presented, usually equals |VSYNC| -- if no frame drops happened. - delay_since_last_frame INT, + delay_since_last_frame DOUBLE, -- Difference in OS timestamps of inputs in the current and the previous frame. - delay_since_last_input INT, + delay_since_last_input DOUBLE, -- The event latency id that will be used as a reference to determine the -- jank cause. prev_event_latency_id INT @@ -324,26 +301,37 @@ SELECT LAG(event_latency_id, 1, -1) OVER (PARTITION BY scroll_id ORDER BY min_start_ts) AS prev_event_latency_id FROM chrome_merged_frame_view; --- Calculate |VSYNC_INTERVAL| as the lowest delay between frames larger than --- zero. --- TODO(b/286222128): Emit this data from Chrome instead of calculating it. -CREATE PERFETTO VIEW chrome_vsyncs( +-- Calculate |VSYNC_INTERVAL| as the lowest vsync seen in the trace or the +-- minimum delay between frames larger than zero. +-- +-- TODO(~M130): Remove the lowest vsync since we should always have vsync_interval_ms. +CREATE PERFETTO TABLE chrome_vsyncs( -- The lowest delay between frames larger than zero. - vsync_interval INT + vsync_interval DOUBLE ) AS +WITH + trace_vsyncs AS ( + SELECT EXTRACT_ARG(slice.arg_set_id, 'event_latency.vsync_interval_ms') AS vsync_interval_ms + FROM + slice JOIN chrome_frame_info_with_delay + ON chrome_frame_info_with_delay.event_latency_id = slice.id + WHERE EXTRACT_ARG(slice.arg_set_id, 'event_latency.vsync_interval_ms') > 0 + ) SELECT - MIN(delay_since_last_frame) AS vsync_interval + COALESCE( + (SELECT MIN(vsync_interval_ms) FROM trace_vsyncs), + MIN(delay_since_last_frame)) AS vsync_interval FROM chrome_frame_info_with_delay WHERE delay_since_last_frame > 0; -- Filter the frame view only to frames that had missed vsyncs. -CREATE PERFETTO VIEW chrome_janky_frames_no_cause( +CREATE PERFETTO TABLE chrome_janky_frames_no_cause( -- Time elapsed since the previous frame was presented, will be more than |VSYNC| in this view. - delay_since_last_frame INT, + delay_since_last_frame DOUBLE, -- Event latency id of the presented frame. event_latency_id INT, -- Vsync interval at the time of recording the trace. - vsync_interval INT, + vsync_interval DOUBLE, -- Device brand and model. hardware_class STRING, -- The scroll corresponding to this frame. @@ -363,13 +351,13 @@ WHERE delay_since_last_frame > (select vsync_interval + vsync_interval / 2 from AND delay_since_last_input < (select vsync_interval + vsync_interval / 2 from chrome_vsyncs); -- Janky frame information including the jank cause. -CREATE PERFETTO VIEW chrome_janky_frames_no_subcause( +CREATE PERFETTO TABLE chrome_janky_frames_no_subcause( -- Time elapsed since the previous frame was presented, will be more than |VSYNC| in this view. - delay_since_last_frame INT, + delay_since_last_frame DOUBLE, -- Event latency id of the presented frame. event_latency_id INT, -- Vsync interval at the time of recording the trace. - vsync_interval INT, + vsync_interval DOUBLE, -- Device brand and model. hardware_class STRING, -- The scroll corresponding to this frame. @@ -386,17 +374,17 @@ FROM chrome_janky_frames_no_cause; -- Finds all causes of jank for all janky frames, and a cause of sub jank -- if the cause of jank was GPU related. -CREATE PERFETTO VIEW chrome_janky_frames( +CREATE PERFETTO TABLE chrome_janky_frames( -- The reason the Vsync was missed. - cause_of_jank INT, + cause_of_jank STRING, -- Further breakdown if the root cause was GPU related. - sub_cause_of_jank INT, + sub_cause_of_jank STRING, -- Time elapsed since the previous frame was presented, will be more than |VSYNC| in this view. - delay_since_last_frame INT, + delay_since_last_frame DOUBLE, -- Event latency id of the presented frame. event_latency_id INT, -- Vsync interval at the time of recording the trace. - vsync_interval INT, + vsync_interval DOUBLE, -- Device brand and model. hardware_class STRING, -- The scroll corresponding to this frame. @@ -424,7 +412,7 @@ SELECT FROM chrome_janky_frames_no_subcause; -- Counting all unique frame presentation timestamps. -CREATE PERFETTO VIEW chrome_unique_frame_presentation_ts( +CREATE PERFETTO TABLE chrome_unique_frame_presentation_ts( -- The unique frame presentation timestamp. presentation_timestamp INT ) AS @@ -435,7 +423,7 @@ FROM chrome_presented_gesture_scrolls; -- Dividing missed frames over total frames to get janky frame percentage. -- This represents the v3 scroll jank metrics. -- Reflects Event.Jank.DelayedFramesPercentage UMA metric. -CREATE PERFETTO VIEW chrome_janky_frames_percentage( +CREATE PERFETTO TABLE chrome_janky_frames_percentage( -- The percent of missed frames relative to total frames - aka the percent of janky frames. delayed_frame_percentage FLOAT ) AS @@ -448,7 +436,7 @@ SELECT FROM chrome_unique_frame_presentation_ts) * 100 AS delayed_frame_percentage; -- Number of frames and janky frames per scroll. -CREATE PERFETTO VIEW chrome_frames_per_scroll( +CREATE PERFETTO TABLE chrome_frames_per_scroll( -- The ID of the scroll. scroll_id INT, -- The number of frames in the scroll. @@ -456,7 +444,7 @@ CREATE PERFETTO VIEW chrome_frames_per_scroll( -- The number of delayed/janky frames. num_janky_frames INT, -- The percentage of janky frames relative to total frames. - scroll_jank_percentage INT + scroll_jank_percentage DOUBLE ) AS WITH frames AS ( @@ -487,9 +475,9 @@ CREATE PERFETTO VIEW chrome_causes_per_scroll( scroll_id INT, -- The maximum time a frame was delayed after the presentation of the previous -- frame. - max_delay_since_last_frame INT, + max_delay_since_last_frame DOUBLE, -- The expected vsync interval. - vsync_interval INT, + vsync_interval DOUBLE, -- A proto amalgamation of each scroll jank cause including cause name, sub -- cause and the duration of the delay since the previous frame was presented. scroll_jank_causes BYTES @@ -512,4 +500,4 @@ SELECT AS scroll_jank_causes FROM chrome_janky_frames -GROUP BY scroll_id; \ No newline at end of file +GROUP BY scroll_id; 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 f1bfd37234..5904a54ebe 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 @@ -48,6 +48,7 @@ WHERE slice.name = 'TranslateAndScaleWebInputEvent'; -- Associate the gesture scroll update OS timestamp with the delta. CREATE PERFETTO TABLE _scroll_deltas_with_timestamp AS SELECT + slice.id AS event_latency_slice_id, slice.ts AS input_ts, data.scroll_update_id, data.delta_y @@ -60,6 +61,7 @@ FROM _translate_and_scale_scroll_deltas data CREATE PERFETTO TABLE _scroll_deltas_with_scroll_id AS SELECT scrolls.id AS scroll_id, + deltas.event_latency_slice_id, deltas.input_ts, deltas.scroll_update_id, deltas.delta_y @@ -73,7 +75,8 @@ CREATE PERFETTO TABLE _scroll_deltas_with_delays AS SELECT deltas.scroll_id, delay.total_delta, - delay.scroll_update_id, + deltas.scroll_update_id, + deltas.event_latency_slice_id, delay.presentation_timestamp AS presentation_timestamp, deltas.input_ts, deltas.delta_y @@ -83,20 +86,26 @@ FROM _scroll_deltas_with_scroll_id AS deltas -- The raw coordinates and pixel offsets for all input events which were part of -- a scroll. CREATE PERFETTO TABLE chrome_scroll_input_offsets( - -- Trace id associated with the scroll. + -- An ID that ties all EventLatencies in a particular scroll. (implementation + -- note: This is the EventLatency TraceId of the GestureScrollbegin). scroll_id INT, - -- Trace id associated with the scroll. + -- An ID for this particular EventLatency regardless of it being presented or + -- not. + event_latency_slice_id INT, + -- An ID that ties this |event_latency_id| with the Trace Id (another + -- event_latency_id) that it was presented with. 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. - delta_y INT, + delta_y DOUBLE, -- The pixel offset of this scroll update event compared to the initial one. - relative_offset_y INT + relative_offset_y DOUBLE ) AS SELECT scroll_id, + event_latency_slice_id, scroll_update_id, input_ts AS ts, delta_y, @@ -109,24 +118,32 @@ FROM _scroll_deltas_with_delays; -- 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. + -- An ID that ties all EventLatencies in a particular scroll. (implementation + -- note: This is the EventLatency TraceId of the GestureScrollbegin). scroll_id INT, - -- Trace id associated with the scroll update event. + -- An ID for this particular EventLatency regardless of it being presented or + -- not. + event_latency_slice_id INT, + -- An ID that ties this |event_latency_id| with the Trace Id (another + -- event_latency_id) that it was presented with. scroll_update_id INT, -- Presentation timestamp. ts INT, -- The delta in raw coordinates between this scroll update event and the -- previous. - delta_y INT, + delta_y DOUBLE, -- The pixel offset of this scroll update event compared to the initial one. - relative_offset_y INT + relative_offset_y DOUBLE ) AS SELECT scroll_id, + event_latency_slice_id, scroll_update_id, 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 +FROM _scroll_deltas_with_delays +WHERE presentation_timestamp IS NOT NULL +; \ 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 b7b6badd1d..559638bcb7 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 @@ -2,7 +2,9 @@ -- Use of this source code is governed by a BSD-style license that can be -- found in the LICENSE file. -- --- Those are helper functions used in computing jank metrics +-- These are helper functions/tables used in computing jank metrics + +INCLUDE PERFETTO MODULE chrome.event_latency; -- This function takes timestamps of two consecutive frames and determines if -- its janky by a delay of more than 0.5 of a frame in order to make sure that @@ -103,18 +105,3 @@ 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/chrome/speedometer.sql b/src/trace_processor/perfetto_sql/stdlib/chrome/speedometer.sql index 79b820f88f..9cfcaa8aec 100644 --- a/src/trace_processor/perfetto_sql/stdlib/chrome/speedometer.sql +++ b/src/trace_processor/perfetto_sql/stdlib/chrome/speedometer.sql @@ -2,202 +2,92 @@ -- Use of this source code is governed by a BSD-style license that can be -- found in the LICENSE file. --- Annotates a trace with Speedometer 2.1 related information. --- --- The scripts below analyse traces with the following tracing options --- enabled: --- --- - Chromium: --- "blink.user_timing". --- --- NOTE: A regular speedometer run (e.g. from the website) will generate the --- required events. No need to add any extra JS or anything. --- --- Noteworthy tables: --- speedometer_mark: List of marks (event slices) emitted by Speedometer. --- These are the points in time Speedometer makes a clock reading to --- compute intervals of time for the final score. --- speedometer_measure_slice: Augmented slices for Speedometer measurements. --- These are the intervals of time Speedometer uses to compute the final --- score. --- speedometer_iteration_slice: Slice that covers one Speedometer iteration --- and has the total_time and score for it. If you average all the scores --- over all iterations you get the final Speedometer score for the run. +INCLUDE PERFETTO MODULE chrome.speedometer_2_1; +INCLUDE PERFETTO MODULE chrome.speedometer_3; --- List of marks (event slices) emitted by Speedometer. --- These are the points in time Speedometer makes a clock reading to compute --- intervals of time for the final score. --- --- @column slice_id Slice this data refers to. --- @column iteration Speedometer iteration the mark belongs to. --- @column suite_name Suite name --- @column test_name Test name --- @column mark_type Type of mark (start, sync-end, async-end) -CREATE PERFETTO VIEW _chrome_speedometer_mark +CREATE PERFETTO FUNCTION _chrome_speedometer_version() +RETURNS STRING AS WITH - speedometer_21_suite_name(suite_name) AS ( - VALUES - ('VanillaJS-TodoMVC'), - ('Vanilla-ES2015-TodoMVC'), - ('Vanilla-ES2015-Babel-Webpack-TodoMVC'), - ('React-TodoMVC'), - ('React-Redux-TodoMVC'), - ('EmberJS-TodoMVC'), - ('EmberJS-Debug-TodoMVC'), - ('BackboneJS-TodoMVC'), - ('AngularJS-TodoMVC'), - ('Angular2-TypeScript-TodoMVC'), - ('VueJS-TodoMVC'), - ('jQuery-TodoMVC'), - ('Preact-TodoMVC'), - ('Inferno-TodoMVC'), - ('Elm-TodoMVC'), - ('Flight-TodoMVC') - ), - speedometer_21_test_name(test_name) AS ( - VALUES - ('Adding100Items'), - ('CompletingAllItems'), - -- This seems to be an issue with Speedometer 2.1. All tests delete all items, - -- but for some reason the test names do not match for all suites. - ('DeletingAllItems'), - ('DeletingItems') - ), - speedometer_21_test_mark_type(mark_type) AS ( - VALUES - ('start'), - ('sync-end'), - ('async-end') - ), - -- Make sure we only look at slices with names we expect. - speedometer_mark_name AS ( - SELECT - s.suite_name || '.' || t.test_name || '-' || m.mark_type AS name, - s.suite_name, - t.test_name, - m.mark_type - FROM - speedometer_21_suite_name AS s, - speedometer_21_test_name AS t, - speedometer_21_test_mark_type AS m + num_measures AS ( + SELECT '2.1' AS version, COUNT(*) AS num_measures + FROM chrome_speedometer_2_1_measure + UNION ALL + SELECT '3' AS version, COUNT(*) AS num_measures + FROM chrome_speedometer_3_measure ) -SELECT - s.id AS slice_id, - RANK() OVER (PARTITION BY name ORDER BY ts ASC) AS iteration, - m.suite_name, - m.test_name, - m.mark_type -FROM slice AS s -JOIN speedometer_mark_name AS m - USING (name) -WHERE category = 'blink.user_timing'; +SELECT version +FROM num_measures +ORDER BY num_measures DESC +LIMIT 1; -- Augmented slices for Speedometer measurements. -- These are the intervals of time Speedometer uses to compute the final score. -- There are two intervals that are measured for every test: sync and async --- sync is the time between the start and sync-end marks, async is the time --- between the sync-end and async-end marks. CREATE PERFETTO TABLE chrome_speedometer_measure( - -- Speedometer iteration the mark belongs to. + -- Start timestamp of the measure slice + ts INT, + -- Duration of the measure slice + dur INT, + -- Full measure name + name STRING, + -- Speedometer iteration the slice belongs to. iteration INT, -- Suite name suite_name STRING, -- Test name test_name STRING, -- Type of the measure (sync or async) - measure_type STRING, - -- Start timestamp of the measure - ts INT, - -- Duration of the measure - dur INT -) + measure_type STRING) AS WITH - -- Get the 3 test timestamps (start, sync-end, async-end) in one row. Using a - -- the LAG window function and partitioning by test. 2 out of the 3 rows - -- generated per test will have some NULL ts values. - augmented AS ( - SELECT - iteration, - suite_name, - test_name, - ts AS async_end_ts, - LAG(ts, 1) - OVER (PARTITION BY iteration, suite_name, test_name ORDER BY ts ASC) - AS sync_end_ts, - LAG(ts, 2) - OVER (PARTITION BY iteration, suite_name, test_name ORDER BY ts ASC) - AS start_ts, - COUNT() - OVER (PARTITION BY iteration, suite_name, test_name ORDER BY ts ASC) - AS mark_count - FROM _chrome_speedometer_mark - JOIN slice - USING (slice_id) - ), - filtered AS ( - SELECT * - FROM augmented - -- This server 2 purposes: make sure we have all the marks (think truncated - -- trace), and remove the NULL ts values due to the LAG window function. - WHERE mark_count = 3 + all_versions AS ( + SELECT '2.1' AS version, * FROM chrome_speedometer_2_1_measure + UNION ALL + SELECT '3' AS version, * FROM chrome_speedometer_3_measure ) -SELECT - iteration, - suite_name, - test_name, - 'async' AS measure_type, - sync_end_ts AS ts, - async_end_ts - sync_end_ts AS dur -FROM filtered -UNION ALL -SELECT - iteration, - suite_name, - test_name, - 'sync' AS measure_type, - start_ts AS ts, - sync_end_ts - start_ts AS dur -FROM filtered; +SELECT ts, dur, name, iteration, suite_name, test_name, measure_type +FROM all_versions +WHERE version = _chrome_speedometer_version(); -- Slice that covers one Speedometer iteration. --- This slice is actually estimated as a default Speedometer run will not emit --- marks to cover this interval. The metrics associated are the same ones --- Speedometer would output, but note we use ns precision (Speedometer uses --- ~100us) so the actual values might differ a bit. Also note Speedometer --- returns the values in ms these here and in ns. +-- Depending on the Speedometer version these slices might need to be estimated +-- as older versions of Speedometer to not emit marks for this interval. The +-- metrics associated are the same ones Speedometer would output, but note we +-- use ns precision (Speedometer uses ~100us) so the actual values might differ +-- a bit. CREATE PERFETTO TABLE chrome_speedometer_iteration( - -- Speedometer iteration. - iteration INT, -- Start timestamp of the iteration ts INT, -- Duration of the iteration dur INT, - -- Total duration of the measures in this iteration - total INT, - -- Average suite duration for this iteration. - mean INT, + -- Iteration name + name STRING, + -- Iteration number + iteration INT, -- Geometric mean of the suite durations for this iteration. - geomean INT, - -- Speedometer score for this iteration (The total score for a run in the average of all iteration scores). - score INT -) AS -SELECT - iteration, - MIN(start) AS ts, - MAX(end) - MIN(start) AS dur, - SUM(suite_total) AS total, - AVG(suite_total)AS mean, - -- Compute geometric mean using LN instead of multiplication to prevent - -- overflows - EXP(AVG(LN(suite_total))) AS geomean, - 1e9 / EXP(AVG(LN(suite_total))) * 60 / 3 AS score -FROM - ( - SELECT - iteration, SUM(dur) AS suite_total, MIN(ts) AS start, MAX(ts + dur) AS end - FROM chrome_speedometer_measure - GROUP BY suite_name, iteration + geomean DOUBLE, + -- Speedometer score for this iteration (The total score for a run in the + -- average of all iteration scores). + score DOUBLE) +AS +WITH + all_versions AS ( + SELECT '2.1' AS version, * FROM chrome_speedometer_2_1_iteration + UNION ALL + SELECT '3' AS version, * FROM chrome_speedometer_3_iteration ) -GROUP BY iteration; +SELECT ts, dur, name, iteration, geomean, score +FROM all_versions +WHERE version = _chrome_speedometer_version(); + +-- Returns the Speedometer score for all iterations in the trace +CREATE PERFETTO FUNCTION chrome_speedometer_score() +-- Speedometer score +RETURNS DOUBLE +AS +SELECT + IIF( + _chrome_speedometer_version() = '3', + chrome_speedometer_3_score(), + chrome_speedometer_2_1_score()); diff --git a/src/trace_processor/perfetto_sql/stdlib/chrome/speedometer_2_1.sql b/src/trace_processor/perfetto_sql/stdlib/chrome/speedometer_2_1.sql new file mode 100644 index 0000000000..bf89628921 --- /dev/null +++ b/src/trace_processor/perfetto_sql/stdlib/chrome/speedometer_2_1.sql @@ -0,0 +1,220 @@ +-- Copyright 2024 The Chromium Authors +-- Use of this source code is governed by a BSD-style license that can be +-- found in the LICENSE file. + +-- List Speedometer 2.1 tests. +CREATE PERFETTO VIEW _chrome_speedometer_2_1_test_name( + -- Suite name + suite_name STRING, + -- Test name + test_name STRING) +AS +WITH + data(suite_name, test_name) AS ( + VALUES('Angular2-TypeScript-TodoMVC', 'Adding100Items'), + ('Angular2-TypeScript-TodoMVC', 'CompletingAllItems'), + ('Angular2-TypeScript-TodoMVC', 'DeletingItems'), + ('AngularJS-TodoMVC', 'Adding100Items'), + ('AngularJS-TodoMVC', 'CompletingAllItems'), + ('AngularJS-TodoMVC', 'DeletingAllItems'), + ('BackboneJS-TodoMVC', 'Adding100Items'), + ('BackboneJS-TodoMVC', 'CompletingAllItems'), + ('BackboneJS-TodoMVC', 'DeletingAllItems'), + ('Elm-TodoMVC', 'Adding100Items'), + ('Elm-TodoMVC', 'CompletingAllItems'), + ('Elm-TodoMVC', 'DeletingItems'), + ('EmberJS-Debug-TodoMVC', 'Adding100Items'), + ('EmberJS-Debug-TodoMVC', 'CompletingAllItems'), + ('EmberJS-Debug-TodoMVC', 'DeletingItems'), + ('EmberJS-TodoMVC', 'Adding100Items'), + ('EmberJS-TodoMVC', 'CompletingAllItems'), + ('EmberJS-TodoMVC', 'DeletingItems'), + ('Flight-TodoMVC', 'Adding100Items'), + ('Flight-TodoMVC', 'CompletingAllItems'), + ('Flight-TodoMVC', 'DeletingItems'), + ('Inferno-TodoMVC', 'Adding100Items'), + ('Inferno-TodoMVC', 'CompletingAllItems'), + ('Inferno-TodoMVC', 'DeletingItems'), + ('Preact-TodoMVC', 'Adding100Items'), + ('Preact-TodoMVC', 'CompletingAllItems'), + ('Preact-TodoMVC', 'DeletingItems'), + ('React-Redux-TodoMVC', 'Adding100Items'), + ('React-Redux-TodoMVC', 'CompletingAllItems'), + ('React-Redux-TodoMVC', 'DeletingItems'), + ('React-TodoMVC', 'Adding100Items'), + ('React-TodoMVC', 'CompletingAllItems'), + ('React-TodoMVC', 'DeletingAllItems'), + ('Vanilla-ES2015-Babel-Webpack-TodoMVC', 'Adding100Items'), + ('Vanilla-ES2015-Babel-Webpack-TodoMVC', 'CompletingAllItems'), + ('Vanilla-ES2015-Babel-Webpack-TodoMVC', 'DeletingItems'), + ('Vanilla-ES2015-TodoMVC', 'Adding100Items'), + ('Vanilla-ES2015-TodoMVC', 'CompletingAllItems'), + ('Vanilla-ES2015-TodoMVC', 'DeletingItems'), + ('VanillaJS-TodoMVC', 'Adding100Items'), + ('VanillaJS-TodoMVC', 'CompletingAllItems'), + ('VanillaJS-TodoMVC', 'DeletingAllItems'), + ('VueJS-TodoMVC', 'Adding100Items'), + ('VueJS-TodoMVC', 'CompletingAllItems'), + ('VueJS-TodoMVC', 'DeletingAllItems'), + ('jQuery-TodoMVC', 'Adding100Items'), + ('jQuery-TodoMVC', 'CompletingAllItems'), + ('jQuery-TodoMVC', 'DeletingAllItems') + ) +SELECT suite_name, test_name FROM data; + +-- Augmented slices for Speedometer measurements. +-- These are the intervals of time Speedometer uses to compute the final score. +-- There are two intervals that are measured for every test: sync and async +-- sync is the time between the start and sync-end marks, async is the time +-- between the sync-end and async-end marks. +CREATE PERFETTO TABLE chrome_speedometer_2_1_measure( + -- Start timestamp of the measure slice + ts INT, + -- Duration of the measure slice + dur INT, + -- Full measure name + name STRING, + -- Speedometer iteration the slice belongs to. + iteration INT, + -- Suite name + suite_name STRING, + -- Test name + test_name STRING, + -- Type of the measure (sync or async) + measure_type STRING) +AS +WITH + mark_type(mark_type) AS ( + VALUES('start'), + ('sync-end'), + ('async-end') + ), + -- Make sure we only look at slices with names we expect. + mark_name AS ( + SELECT + suite_name || '.' || test_name || '-' || mark_type AS name, + suite_name, + test_name, + mark_type + FROM + _chrome_speedometer_2_1_test_name, + mark_type + ), + mark AS ( + SELECT + s.id AS slice_id, + RANK() OVER (PARTITION BY name ORDER BY ts ASC) AS iteration, + m.suite_name, + m.test_name, + m.mark_type + FROM slice AS s + JOIN mark_name AS m + USING (name) + WHERE category = 'blink.user_timing' + ), + -- Get the 3 test timestamps (start, sync-end, async-end) in one row. Using a + -- the LAG window function and partitioning by test. 2 out of the 3 rows + -- generated per test will have some NULL ts values. + augmented AS ( + SELECT + iteration, + suite_name, + test_name, + ts AS async_end_ts, + LAG(ts, 1) + OVER (PARTITION BY iteration, suite_name, test_name ORDER BY ts ASC) + AS sync_end_ts, + LAG(ts, 2) + OVER (PARTITION BY iteration, suite_name, test_name ORDER BY ts ASC) + AS start_ts, + COUNT() + OVER (PARTITION BY iteration, suite_name, test_name ORDER BY ts ASC) + AS mark_count + FROM mark + JOIN slice + USING (slice_id) + ), + filtered AS ( + SELECT * + FROM augmented + -- This server 2 purposes: make sure we have all the marks (think truncated + -- trace), and remove the NULL ts values due to the LAG window function. + WHERE mark_count = 3 + ), + base AS ( + SELECT + sync_end_ts AS ts, + async_end_ts - sync_end_ts AS dur, + iteration, + suite_name, + test_name, + 'async' AS measure_type + FROM filtered + UNION ALL + SELECT + start_ts AS ts, + sync_end_ts - start_ts AS dur, + iteration, + suite_name, + test_name, + 'sync' AS measure_type + FROM filtered + ) +SELECT + ts, + dur, + suite_name || '.' || test_name || '-' || measure_type AS name, + iteration, + suite_name, + test_name, + measure_type +FROM base; + +-- Slice that covers one Speedometer iteration. +-- This slice is actually estimated as a default Speedometer run will not emit +-- marks to cover this interval. The metrics associated are the same ones +-- Speedometer would output, but note we use ns precision (Speedometer uses +-- ~100us) so the actual values might differ a bit. Also note Speedometer +-- returns the values in ms these here and in ns. +CREATE PERFETTO TABLE chrome_speedometer_2_1_iteration( + -- Start timestamp of the iteration + ts INT, + -- Duration of the iteration + dur INT, + -- Iteration name + name STRING, + -- Iteration number + iteration INT, + -- Geometric mean of the suite durations for this iteration. + geomean DOUBLE, + -- Speedometer score for this iteration (The total score for a run in the + -- average of all iteration scores). + score DOUBLE) +AS +SELECT + MIN(start) AS ts, + MAX(END) - MIN(start) AS dur, + 'iteration-' || iteration AS name, + iteration, + -- Compute geometric mean using LN instead of multiplication to prevent + -- overflows + EXP(AVG(LN(suite_total))) AS geomean, + 1000 / EXP(AVG(LN(suite_total))) * 60 / 3 AS score +FROM + ( + SELECT + iteration, + SUM(dur / (1000.0 * 1000.0)) AS suite_total, + MIN(ts) AS start, + MAX(ts + dur) AS END + FROM chrome_speedometer_2_1_measure + GROUP BY suite_name, iteration + ) +GROUP BY iteration; + +-- Returns the Speedometer 2.1 score for all iterations in the trace +CREATE PERFETTO FUNCTION chrome_speedometer_2_1_score() +-- Speedometer 2.1 score +RETURNS DOUBLE +AS +SELECT AVG(score) FROM chrome_speedometer_2_1_iteration; diff --git a/src/trace_processor/perfetto_sql/stdlib/chrome/speedometer_3.sql b/src/trace_processor/perfetto_sql/stdlib/chrome/speedometer_3.sql new file mode 100644 index 0000000000..cc855d8032 --- /dev/null +++ b/src/trace_processor/perfetto_sql/stdlib/chrome/speedometer_3.sql @@ -0,0 +1,206 @@ +-- Copyright 2024 The Chromium Authors +-- Use of this source code is governed by a BSD-style license that can be +-- found in the LICENSE file. + +-- List Speedometer 3 tests. +CREATE PERFETTO VIEW _chrome_speedometer_3_test_name( + -- Suite name + suite_name STRING, + -- Test name + test_name STRING) +AS +WITH + data(suite_name, test_name) AS ( + VALUES('TodoMVC-JavaScript-ES5', 'Adding100Items'), + ('TodoMVC-JavaScript-ES5', 'CompletingAllItems'), + ('TodoMVC-JavaScript-ES5', 'DeletingAllItems'), + ('TodoMVC-JavaScript-ES6-Webpack-Complex-DOM', 'Adding100Items'), + ('TodoMVC-JavaScript-ES6-Webpack-Complex-DOM', 'CompletingAllItems'), + ('TodoMVC-JavaScript-ES6-Webpack-Complex-DOM', 'DeletingAllItems'), + ('TodoMVC-WebComponents', 'Adding100Items'), + ('TodoMVC-WebComponents', 'CompletingAllItems'), + ('TodoMVC-WebComponents', 'DeletingAllItems'), + ('TodoMVC-React-Complex-DOM', 'Adding100Items'), + ('TodoMVC-React-Complex-DOM', 'CompletingAllItems'), + ('TodoMVC-React-Complex-DOM', 'DeletingAllItems'), + ('TodoMVC-React-Redux', 'Adding100Items'), + ('TodoMVC-React-Redux', 'CompletingAllItems'), + ('TodoMVC-React-Redux', 'DeletingAllItems'), + ('TodoMVC-Backbone', 'Adding100Items'), + ('TodoMVC-Backbone', 'CompletingAllItems'), + ('TodoMVC-Backbone', 'DeletingAllItems'), + ('TodoMVC-Angular-Complex-DOM', 'Adding100Items'), + ('TodoMVC-Angular-Complex-DOM', 'CompletingAllItems'), + ('TodoMVC-Angular-Complex-DOM', 'DeletingAllItems'), + ('TodoMVC-Vue', 'Adding100Items'), + ('TodoMVC-Vue', 'CompletingAllItems'), + ('TodoMVC-Vue', 'DeletingAllItems'), + ('TodoMVC-jQuery', 'Adding100Items'), + ('TodoMVC-jQuery', 'CompletingAllItems'), + ('TodoMVC-jQuery', 'DeletingAllItems'), + ('TodoMVC-Preact-Complex-DOM', 'Adding100Items'), + ('TodoMVC-Preact-Complex-DOM', 'CompletingAllItems'), + ('TodoMVC-Preact-Complex-DOM', 'DeletingAllItems'), + ('TodoMVC-Svelte-Complex-DOM', 'Adding100Items'), + ('TodoMVC-Svelte-Complex-DOM', 'CompletingAllItems'), + ('TodoMVC-Svelte-Complex-DOM', 'DeletingAllItems'), + ('TodoMVC-Lit-Complex-DOM', 'Adding100Items'), + ('TodoMVC-Lit-Complex-DOM', 'CompletingAllItems'), + ('TodoMVC-Lit-Complex-DOM', 'DeletingAllItems'), + ('NewsSite-Next', 'NavigateToUS'), + ('NewsSite-Next', 'NavigateToWorld'), + ('NewsSite-Next', 'NavigateToPolitics'), + ('NewsSite-Nuxt', 'NavigateToUS'), + ('NewsSite-Nuxt', 'NavigateToWorld'), + ('NewsSite-Nuxt', 'NavigateToPolitics'), + ('Editor-CodeMirror', 'Long'), + ('Editor-CodeMirror', 'Highlight'), + ('Editor-TipTap', 'Long'), + ('Editor-TipTap', 'Highlight'), + ('Charts-observable-plot', 'Stacked by 6'), + ('Charts-observable-plot', 'Stacked by 20'), + ('Charts-observable-plot', 'Dotted'), + ('Charts-chartjs', 'Draw scatter'), + ('Charts-chartjs', 'Show tooltip'), + ('Charts-chartjs', 'Draw opaque scatter'), + ('React-Stockcharts-SVG', 'Render'), + ('React-Stockcharts-SVG', 'PanTheChart'), + ('React-Stockcharts-SVG', 'ZoomTheChart'), + ('Perf-Dashboard', 'Render'), + ('Perf-Dashboard', 'SelectingPoints'), + ('Perf-Dashboard', 'SelectingRange') + ) +SELECT suite_name, test_name FROM data; + +CREATE PERFETTO VIEW _chrome_speedometer_iteration_slice +AS +WITH data AS ( + SELECT + *, + substr(name, 1 + length('iteration-')) AS iteration_str + FROM + slice + WHERE + category = 'blink.user_timing' + AND name GLOB 'iteration-*' +) +SELECT + *, + CAST(iteration_str AS INT) as iteration +FROM data +WHERE iteration_str = iteration; + +-- Augmented slices for Speedometer measurements. +-- These are the intervals of time Speedometer uses to compute the final score. +-- There are two intervals that are measured for every test: sync and async. +CREATE PERFETTO TABLE chrome_speedometer_3_measure( + -- Start timestamp of the measure slice + ts INT, + -- Duration of the measure slice + dur INT, + -- Full measure name + name STRING, + -- Speedometer iteration the slice belongs to. + iteration INT, + -- Suite name + suite_name STRING, + -- Test name + test_name STRING, + -- Type of the measure (sync or async) + measure_type STRING) +AS +WITH + measure_type(measure_type) AS ( + VALUES('sync'), + ('async') + ), + measure_name AS ( + SELECT + suite_name || '.' || test_name || '-' || measure_type AS name, + suite_name, + test_name, + measure_type + FROM + _chrome_speedometer_3_test_name, + measure_type + ), + measure_slice AS ( + SELECT + s.ts, + s.dur, + s.name, + m.suite_name, + m.test_name, + m.measure_type + FROM + slice s, + measure_name AS m + USING (name) + WHERE + s.category = 'blink.user_timing' + ) +SELECT + s.ts, + s.dur, + s.name, + i.iteration, + s.suite_name, + s.test_name, + s.measure_type +FROM + measure_slice AS s, + _chrome_speedometer_iteration_slice i +ON (s.ts >= i.ts AND s.ts < i.ts + i.dur) +ORDER BY s.ts ASC; + +-- Slice that covers one Speedometer iteration. +-- The metrics associated are the same ones +-- Speedometer would output, but note we use ns precision (Speedometer uses +-- ~100us) so the actual values might differ a bit. +CREATE PERFETTO TABLE chrome_speedometer_3_iteration( + -- Start timestamp of the iteration + ts INT, + -- Duration of the iteration + dur INT, + -- Iteration name + name STRING, + -- Iteration number + iteration INT, + -- Geometric mean of the suite durations for this iteration. + geomean DOUBLE, + -- Speedometer score for this iteration (The total score for a run in the + -- average of all iteration scores). + score DOUBLE) +AS +WITH + suite AS ( + SELECT + iteration, suite_name, SUM(dur / (1000.0 * 1000.0)) AS suite_total + FROM chrome_speedometer_3_measure + GROUP BY iteration, suite_name + ), + iteration AS ( + SELECT + iteration, + -- Compute geometric mean using LN instead of multiplication to prevent + -- overflows + EXP(AVG(LN(suite_total))) AS geomean + FROM suite + GROUP BY iteration + ) +SELECT + s.ts, + s.dur, + s.name, + i.iteration, + i.geomean, + 1000.0 / i.geomean AS score +FROM iteration AS i, _chrome_speedometer_iteration_slice AS s +USING (iteration); + +-- Returns the Speedometer 3 score for all iterations in the trace +CREATE PERFETTO FUNCTION chrome_speedometer_3_score() +-- Speedometer 3 score +RETURNS DOUBLE +AS +SELECT AVG(score) FROM chrome_speedometer_3_iteration; diff --git a/src/trace_processor/perfetto_sql/stdlib/chrome/tasks.sql b/src/trace_processor/perfetto_sql/stdlib/chrome/tasks.sql index a177cbaa66..7c09a643f9 100644 --- a/src/trace_processor/perfetto_sql/stdlib/chrome/tasks.sql +++ b/src/trace_processor/perfetto_sql/stdlib/chrome/tasks.sql @@ -105,7 +105,7 @@ SELECT -- -- Note: this might include messages received within a sync mojo call. -- TODO(altimin): This should use EXTEND_TABLE when it becomes available. -CREATE TABLE _chrome_mojo_slices AS +CREATE PERFETTO TABLE _chrome_mojo_slices AS WITH -- Select all new-style (post crrev.com/c/3270337) mojo slices and -- generate |task_name| for them. @@ -153,17 +153,16 @@ old_non_associated_mojo_slices AS ( FROM slice WHERE category GLOB "*toplevel*" AND name = "Connector::DispatchMessage" -) +), +merged AS ( -- Merge all mojo slices. SELECT * FROM new_mojo_slices UNION ALL SELECT * FROM old_associated_mojo_slices UNION ALL -SELECT * FROM old_non_associated_mojo_slices; - --- As we lookup by ID on |_chrome_mojo_slices| table, add an index on --- id to make lookups fast. -CREATE INDEX _chrome_mojo_slices_idx ON _chrome_mojo_slices(id); +SELECT * FROM old_non_associated_mojo_slices +) +SELECT * FROM merged ORDER BY id; -- This table contains a list of slices corresponding to the _representative_ -- Chrome Java view operations. @@ -178,7 +177,7 @@ CREATE INDEX _chrome_mojo_slices_idx ON _chrome_mojo_slices(id); -- capture toolbar screenshot. -- @column is_hardware_screenshot BOOL Whether this slice is a part of accelerated -- capture toolbar screenshot. -CREATE TABLE _chrome_java_views AS +CREATE PERFETTO TABLE _chrome_java_views AS WITH -- .draw, .onLayout and .onMeasure parts of the java view names don't add much, strip them. java_slices_with_trimmed_names AS ( @@ -363,7 +362,7 @@ LEFT JOIN root_slice_and_java_view_not_grouped java_view USING (id) GROUP BY root.id; -- A list of tasks executed by Chrome scheduler. -CREATE TABLE _chrome_scheduler_tasks AS +CREATE PERFETTO TABLE _chrome_scheduler_tasks AS SELECT id FROM slice @@ -476,7 +475,7 @@ WHERE task.id = $slice_id; -- @column task_name STRING Name for the given task. -- @column task_type STRING Type of the task (e.g. "scheduler"). -- @column scheduling_delay INT -CREATE TABLE _chrome_tasks AS +CREATE PERFETTO TABLE _chrome_tasks AS WITH -- Select slices from "toplevel" category which do not have another -- "toplevel" slice as ancestor. The possible cases include sync mojo messages @@ -614,7 +613,7 @@ CREATE PERFETTO VIEW chrome_tasks( -- Alias of |slice.track_id|. track_id INT, -- Alias of |slice.category|. - category INT, + category STRING, -- Alias of |slice.arg_set_id|. arg_set_id INT, -- Alias of |slice.thread_ts|. diff --git a/src/trace_processor/perfetto_sql/stdlib/common/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/common/BUILD.gn index bb4d75878c..73db0a7878 100644 --- a/src/trace_processor/perfetto_sql/stdlib/common/BUILD.gn +++ b/src/trace_processor/perfetto_sql/stdlib/common/BUILD.gn @@ -18,11 +18,9 @@ perfetto_sql_source_set("common") { sources = [ "args.sql", "counters.sql", - "cpus.sql", "metadata.sql", "percentiles.sql", "slices.sql", - "thread_states.sql", "timestamps.sql", ] } diff --git a/src/trace_processor/perfetto_sql/stdlib/counters/intervals.sql b/src/trace_processor/perfetto_sql/stdlib/counters/intervals.sql index 4b11c7c242..5473892d29 100644 --- a/src/trace_processor/perfetto_sql/stdlib/counters/intervals.sql +++ b/src/trace_processor/perfetto_sql/stdlib/counters/intervals.sql @@ -45,19 +45,27 @@ CREATE PERFETTO MACRO counter_leading_intervals( -- This table must have the columns "id" and "ts" and "track_id" and "value" corresponding -- to an id, timestamp, counter track_id and associated counter value. counter_table TableOrSubquery) --- Table with the schema (id UINT32, ts UINT64, track_id UINT64, value DOUBLE, dur INT). +-- Table with the schema (id UINT32, ts UINT64, dur UINT64, track_id UINT64, +-- value DOUBLE, next_value DOUBLE, delta_value DOUBLE). RETURNS TableOrSubquery AS ( WITH base AS ( - SELECT id, ts, track_id, value, LAG(value) OVER (PARTITION BY track_id ORDER BY ts) AS lag_value + SELECT + id, + ts, + track_id, + value, + LAG(value) OVER (PARTITION BY track_id ORDER BY ts) AS lag_value FROM $counter_table ) SELECT id, ts, - track_id, LEAD(ts, 1, trace_end()) OVER(PARTITION BY track_id ORDER BY ts) - ts AS dur, - CAST(value AS INT) AS value + track_id, + value, + LEAD(value) OVER(PARTITION BY track_id ORDER BY ts) AS next_value, + value - lag_value AS delta_value FROM base WHERE value != lag_value OR lag_value IS NULL -); +); \ No newline at end of file diff --git a/src/trace_processor/perfetto_sql/stdlib/cpu/size.sql b/src/trace_processor/perfetto_sql/stdlib/cpu/size.sql deleted file mode 100644 index 6536172577..0000000000 --- a/src/trace_processor/perfetto_sql/stdlib/cpu/size.sql +++ /dev/null @@ -1,55 +0,0 @@ --- --- 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 _cpu_sizes AS -SELECT 0 AS n, 'little' AS size -UNION -SELECT 1 AS n, 'mid' AS size -UNION -SELECT 2 AS n, 'big' AS size; - -CREATE PERFETTO TABLE _ranked_cpus AS -SELECT - (DENSE_RANK() OVER win) - 1 AS n, - cpu -FROM ( - SELECT - track.cpu AS cpu, - MAX(counter.value) AS maxfreq - FROM counter - JOIN cpu_counter_track AS track - ON (counter.track_id = track.id) - WHERE track.name = "cpufreq" - GROUP BY track.cpu -) -WINDOW win AS (ORDER BY maxfreq); - --- Guess size of CPU. --- On some multicore devices the cores are heterogeneous and divided --- into two or more 'sizes'. In a typical case a device might have 8 --- cores of which 4 are 'little' (low power & low performance) and 4 --- are 'big' (high power & high performance). This functions attempts --- to map a given CPU index onto the relevant descriptor. For --- homogeneous systems this returns NULL. -CREATE PERFETTO FUNCTION cpu_guess_core_type( - -- Index of the CPU whose size we will guess. - cpu_index INT) --- A descriptive size ('little', 'mid', 'big', etc) or NULL if we have insufficient information. -RETURNS STRING AS -SELECT - IIF((SELECT COUNT(DISTINCT n) FROM _ranked_cpus) >= 2, size, null) as size -FROM _ranked_cpus -LEFT JOIN _cpu_sizes USING(n) -WHERE cpu = $cpu_index; \ No newline at end of file diff --git a/src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/BUILD.gn index 08597bb8a8..a99b51bf83 100644 --- a/src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/BUILD.gn +++ b/src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/BUILD.gn @@ -18,11 +18,9 @@ perfetto_sql_source_set("common") { sources = [ "args.sql", "counters.sql", - "cpus.sql", "metadata.sql", "percentiles.sql", "slices.sql", - "thread_states.sql", "timestamps.sql", ] } diff --git a/src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/thread_states.sql b/src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/thread_states.sql deleted file mode 100644 index 80b3c2e9e6..0000000000 --- a/src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/thread_states.sql +++ /dev/null @@ -1,66 +0,0 @@ --- --- Copyright 2022 The Android Open Source Project --- --- Licensed under the Apache License, Version 2.0 (the "License"); --- you may not use this file except in compliance with the License. --- You may obtain a copy of the License at --- --- 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 deprecated.v42.common.timestamps; -INCLUDE PERFETTO MODULE sched.time_in_state; -INCLUDE PERFETTO MODULE sched.states; -INCLUDE PERFETTO MODULE cpu.size; - -CREATE PERFETTO FUNCTION _translate_thread_state_name(name STRING) -RETURNS STRING AS -SELECT sched_state_to_human_readable_string($name); - - --- Returns a human-readable name for a thread state. -CREATE PERFETTO FUNCTION human_readable_thread_state_name( - -- Thread state id. - id INT) --- Human-readable name for the thread state. -RETURNS STRING AS -SELECT sched_state_io_to_human_readable_string(state, io_wait) -FROM thread_state -WHERE id = $id; - --- Returns an aggregation of thread states (by state and cpu) for a given --- interval of time for a given thread. -CREATE PERFETTO FUNCTION thread_state_summary_for_interval( - -- The start of the interval. - ts INT, - -- The duration of the interval. - dur INT, - -- The utid of the thread. - utid INT) -RETURNS TABLE( - -- Human-readable thread state name. - state STRING, - -- Raw thread state name, alias of `thread_state.state`. - raw_state STRING, - -- The type of CPU if available (e.g. "big" / "mid" / "little"). - cpu_type STRING, - -- The CPU index. - cpu INT, - -- The name of the kernel function execution is blocked in. - blocked_function STRING, - -- The total duration. - dur INT -) AS -SELECT - sched_state_io_to_human_readable_string(state, io_wait) as state, - state AS raw_state, - cpu_guess_core_type(cpu) as cpu_type, - cpu, - blocked_function, - dur -FROM sched_time_in_state_and_cpu_for_thread_in_interval($ts, $dur, $utid); \ No newline at end of file diff --git a/src/trace_processor/perfetto_sql/stdlib/export/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/export/BUILD.gn new file mode 100644 index 0000000000..7cfcfc76a2 --- /dev/null +++ b/src/trace_processor/perfetto_sql/stdlib/export/BUILD.gn @@ -0,0 +1,24 @@ +# Copyright (C) 2024 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("../../../../../gn/perfetto_sql.gni") + +perfetto_sql_source_set("export") { + sources = [] + + # Chrmouim builds set DSQLITE_OMIT_JSON so SQL json functions will not exist. + if (!build_with_chromium) { + sources += [ "to_firefox_profile.sql" ] + } +} diff --git a/src/trace_processor/perfetto_sql/stdlib/export/to_firefox_profile.sql b/src/trace_processor/perfetto_sql/stdlib/export/to_firefox_profile.sql new file mode 100644 index 0000000000..bbb1fe6e98 --- /dev/null +++ b/src/trace_processor/perfetto_sql/stdlib/export/to_firefox_profile.sql @@ -0,0 +1,448 @@ +-- +-- 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. + + +-- Returns an instance of `RawMarkerTable` as defined in +-- https://github.com/firefox-devtools/profiler/blob/main/src/types/profile.js +CREATE PERFETTO FUNCTION _export_firefox_thread_markers() +RETURNS STRING +AS +SELECT json_object( + 'data', json_array(), + 'name', json_array(), + 'startTime', json_array(), + 'endTime', json_array(), + 'phase', json_array(), + 'category', json_array(), + -- threadId?: Tid[] + 'length', 0); + +-- Returns an empty instance of `NativeSymbolTable` as defined in +-- https://github.com/firefox-devtools/profiler/blob/main/src/types/profile.js +CREATE PERFETTO FUNCTION _export_firefox_native_symbol_table() +RETURNS STRING +AS +SELECT + json_object( + 'libIndex', json_array(), + 'address', json_array(), + 'name', json_array(), + 'functionSize', json_array(), + 'length', 0); + + +-- Returns an empty instance of `ResourceTable` as defined in +-- https://github.com/firefox-devtools/profiler/blob/main/src/types/profile.js +CREATE PERFETTO FUNCTION _export_firefox_resource_table() +RETURNS STRING +AS +SELECT + json_object( + 'length', 0, + 'lib', json_array(), + 'name', json_array(), + 'host', json_array(), + 'type', json_array()); + +-- Materialize this intermediate table and sort by `callsite_id` to speedup the +-- generation of the stack_table further down. +CREATE PERFETTO TABLE _export_to_firefox_table +AS +WITH + symbol AS ( + SELECT + symbol_set_id, + RANK() + OVER ( + PARTITION BY symbol_set_id + ORDER BY id DESC + RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING + ) + - 1 AS inline_depth, + COUNT() OVER (PARTITION BY symbol_set_id) - 1 AS max_inline_depth, + name + FROM stack_profile_symbol + ), + callsite_base AS ( + SELECT + id, + parent_id, + name, + symbol_set_id, + IIF(inline_count = 0, 0, inline_count - 1) AS max_inline_depth + FROM + ( + SELECT + spc.id, + spc.parent_id, + COALESCE(s.name, spf.name, '') AS name, + sfp.symbol_set_id, + ( + SELECT COUNT(*) + FROM stack_profile_symbol s + WHERE s.symbol_set_id = sfp.symbol_set_id + ) AS inline_count + FROM stack_profile_callsite sfc, stack_profile_frame AS spf + ON (c.frame_id = spf.id) + ) + ), + callsite_recursive AS ( + SELECT + s.utid, + spc.id, + spc.parent_id, + spc.frame_id + FROM + (SELECT DISTINCT callsite_id, utid FROM perf_sample) s, + stack_profile_callsite spc + ON (spc.id = s.callsite_id) + UNION ALL + SELECT + child.utid, + parent.id, + parent.parent_id, + parent.frame_id + FROM callsite_recursive AS child, stack_profile_callsite AS parent + ON (child.parent_id = parent.id) + ), + unique_callsite AS ( + SELECT DISTINCT * FROM callsite_recursive + ), + expanded_callsite AS ( + SELECT + c.utid, + c.id, + c.parent_id, + c.frame_id, + COALESCE(s.name, spf.name, '') AS name, + COALESCE(s.inline_depth, 0) AS inline_depth, + COALESCE(s.max_inline_depth, 0) AS max_inline_depth + FROM unique_callsite c, stack_profile_frame AS spf + ON (c.frame_id = spf.id) + LEFT JOIN symbol s + USING (symbol_set_id) + ) +SELECT + utid, + id AS callsite_id, + parent_id AS parent_callsite_id, + name, + inline_depth, + inline_depth = max_inline_depth AS is_most_inlined, + DENSE_RANK() + OVER (PARTITION BY utid ORDER BY id, inline_depth) - 1 AS stack_table_index, + DENSE_RANK() + OVER (PARTITION BY utid ORDER BY frame_id, inline_depth) - 1 + AS frame_table_index, + DENSE_RANK() OVER (PARTITION BY utid ORDER BY name) - 1 AS func_table_index, + DENSE_RANK() OVER (PARTITION BY utid ORDER BY name) - 1 AS string_table_index +FROM expanded_callsite +ORDER BY utid, id; + +-- Returns an instance of `SamplesTable` as defined in +-- https://github.com/firefox-devtools/profiler/blob/main/src/types/profile.js +-- for the given `utid`. +CREATE PERFETTO FUNCTION _export_firefox_samples_table(utid INT) +RETURNS STRING +AS +WITH + samples_table AS ( + SELECT + ROW_NUMBER() OVER (ORDER BY s.id) - 1 AS idx, + s.ts AS time, + t.stack_table_index AS stack + FROM + perf_sample AS s, + _export_to_firefox_table AS t + USING (utid, callsite_id) + WHERE utid = $utid AND t.is_most_inlined + ) +SELECT + json_object( + -- responsiveness?: Array + -- eventDelay?: Array + 'stack', json_group_array(stack ORDER BY idx), + 'time', json_group_array(time ORDER BY idx), + 'weight', NULL, + 'weightType', 'samples', + -- threadCPUDelta?: Array + -- threadId?: Tid[] + 'length', COUNT(*)) +FROM samples_table; + +-- Returns an instance of `StackTable` as defined in +-- https://github.com/firefox-devtools/profiler/blob/main/src/types/profile.js +-- for the given `utid`. +CREATE PERFETTO FUNCTION _export_firefox_stack_table(utid INT) +RETURNS STRING +AS +WITH + parent AS ( + SELECT * + FROM _export_to_firefox_table + WHERE utid = $utid + ), + stack_table AS ( + SELECT + stack_table_index AS idx, + frame_table_index AS frame, + 0 AS category, + 0 AS subcategory, + -- It is key that this lookup is fast. That is why we have materialized + -- the `_export_to_firefox_table` table and sorted it by `utid` and + -- `callsite_id`. + IIF( + child.inline_depth = 0, + ( + SELECT stack_table_index + FROM parent + WHERE + child.parent_callsite_id = parent.callsite_id + AND parent.is_most_inlined + ), + ( + SELECT stack_table_index + FROM parent + WHERE + child.callsite_id = parent.callsite_id + AND child.inline_depth - 1 = parent.inline_depth + )) AS prefix + FROM _export_to_firefox_table AS child + WHERE child.utid = $utid + ) +SELECT + json_object( + 'frame', json_group_array(frame ORDER BY idx), + 'category', json_group_array(category ORDER BY idx), + 'subcategory', json_group_array(subcategory ORDER BY idx), + 'prefix', json_group_array(prefix ORDER BY idx), + 'length', COUNT(*)) +FROM stack_table; + + +-- Returns an instance of `FrameTable` as defined in +-- https://github.com/firefox-devtools/profiler/blob/main/src/types/profile.js +-- for the given `utid`. +CREATE PERFETTO FUNCTION _export_firefox_frame_table(utid INT) +RETURNS STRING +AS +WITH + frame_table AS ( + SELECT DISTINCT + frame_table_index AS idx, + -1 AS address, + inline_depth, + 0 AS category, + 0 AS subcategory, + func_table_index AS func, + NULL AS native_symbol, + NULL AS inner_window_id, + NULL AS implementation, + NULL AS line, + NULL AS column + FROM _export_to_firefox_table + WHERE utid = $utid + ) +SELECT + json_object( + 'address', json_group_array(address ORDER BY idx), + 'inlineDepth', json_group_array(inline_depth ORDER BY idx), + 'category', json_group_array(category ORDER BY idx), + 'subcategory', json_group_array(subcategory ORDER BY idx), + 'func', json_group_array(func ORDER BY idx), + 'nativeSymbol', json_group_array(native_symbol ORDER BY idx), + 'innerWindowID', json_group_array(inner_window_id ORDER BY idx), + 'implementation', json_group_array(implementation ORDER BY idx), + 'line', json_group_array(line ORDER BY idx), + 'column', json_group_array(column ORDER BY idx), + 'length', COUNT(*)) +FROM frame_table; + +-- Returns an array of strings for the given `utid`. +CREATE PERFETTO FUNCTION _export_firefox_string_array(utid INT) +RETURNS STRING +AS +WITH + string_table AS ( + SELECT DISTINCT + string_table_index AS idx, + name AS str + FROM + _export_to_firefox_table + WHERE utid = $utid + ) +SELECT json_group_array(str ORDER BY idx) +FROM string_table; + +-- Returns an instance of `FuncTable` as defined in +-- https://github.com/firefox-devtools/profiler/blob/main/src/types/profile.js +-- for the given `utid`. +CREATE PERFETTO FUNCTION _export_firefox_func_table(utid INT) +RETURNS STRING +AS +WITH + func_table AS ( + SELECT DISTINCT + func_table_index AS idx, + string_table_index AS name, + FALSE AS is_js, + FALSE AS relevant_for_js, + -1 AS resource, + NULL AS file_name, + NULL AS line_number, + NULL AS column_number + FROM _export_to_firefox_table + WHERE utid = $utid + ) +SELECT + json_object( + 'name', json_group_array(name ORDER BY idx), + 'isJS', json_group_array(is_js ORDER BY idx), + 'relevantForJS', json_group_array(relevant_for_js ORDER BY idx), + 'resource', json_group_array(resource ORDER BY idx), + 'fileName', json_group_array(file_name ORDER BY idx), + 'lineNumber', json_group_array(line_number ORDER BY idx), + 'columnNumber', json_group_array(column_number ORDER BY idx), + 'length', COUNT(*)) +FROM func_table; + + +-- Returns an instance of `Thread` as defined in +-- https://github.com/firefox-devtools/profiler/blob/main/src/types/profile.js +-- for the given `utid`. +CREATE PERFETTO FUNCTION _export_firefox_thread(utid INT) +RETURNS STRING +AS +SELECT + json_object( + 'processType', 'default', + -- processStartupTime: Milliseconds + -- processShutdownTime: Milliseconds | null + -- registerTime: Milliseconds + -- unregisterTime: Milliseconds | null + -- pausedRanges: PausedRange[] + -- showMarkersInTimeline?: boolean + 'name', COALESCE(thread.name, ''), + 'isMainThread', FALSE, + -- 'eTLD+1'?: string + -- processName?: string + -- isJsTracer?: boolean + 'pid', COALESCE(process.pid, 0), + 'tid', COALESCE(thread.tid, 0), + 'samples', json(_export_firefox_samples_table($utid)), + -- jsAllocations?: JsAllocationsTable + -- nativeAllocations?: NativeAllocationsTable + 'markers', json(_export_firefox_thread_markers()), + 'stackTable', json(_export_firefox_stack_table($utid)), + 'frameTable', json(_export_firefox_frame_table($utid)), + 'stringArray', json(_export_firefox_string_array($utid)), + 'funcTable', json(_export_firefox_func_table($utid)), + 'resourceTable', json(_export_firefox_resource_table()), + 'nativeSymbols', json(_export_firefox_native_symbol_table()) + -- jsTracer?: JsTracerTable + -- isPrivateBrowsing?: boolean + -- userContextId?: number + ) +FROM thread, process +USING (upid) +WHERE utid = $utid; + +-- Returns an array of `Thread` instances as defined in +-- https://github.com/firefox-devtools/profiler/blob/main/src/types/profile.js +-- for each thread present in the trace. +CREATE PERFETTO FUNCTION _export_firefox_threads() +RETURNS STRING +AS +SELECT json_group_array(json(_export_firefox_thread(utid))) FROM thread; + +-- Returns an instance of `ProfileMeta` as defined in +-- https://github.com/firefox-devtools/profiler/blob/main/src/types/profile.js +CREATE PERFETTO FUNCTION _export_firefox_meta() +RETURNS STRING +AS +SELECT + json_object( + 'interval', 1, + 'startTime', 0, + -- endTime?: Milliseconds + -- profilingStartTime?: Milliseconds + -- profilingEndTime?: Milliseconds + 'processType', 0, -- default + -- extensions?: ExtensionTable + 'categories', json_array(json_object( + 'name', 'Other', + 'color', 'grey', + 'subcategories', json_array('Other'))), + 'product', 'Perfetto', + 'stackwalk', 1, + -- debug?: boolean + 'version', 29, -- Taken from a generated profile + 'preprocessedProfileVersion', 48, -- Taken from a generated profile + -- abi?: string + -- misc?: string + -- oscpu?: string + -- mainMemory?: Bytes + -- platform?: 'Android' | 'Windows' | 'Macintosh' | 'X11' | string + -- toolkit?: 'gtk' | 'gtk3' | 'windows' | 'cocoa' | 'android' | string + -- appBuildID?: string + -- arguments?: string + -- sourceURL?: string + -- physicalCPUs?: number + -- logicalCPUs?: number + -- CPUName?: string + -- symbolicated?: boolean + -- symbolicationNotSupported?: boolean + -- updateChannel?: 'default' | 'nightly' | 'nightly-try' | 'aurora' | 'beta' | 'release' | 'esr' | string + -- visualMetrics?: VisualMetrics + -- configuration?: ProfilerConfiguration + 'markerSchema', json_array(), + 'sampleUnits', json_object('time', 'ms', 'eventDelay', 'ms', 'threadCPUDelta', 'µs') + -- device?: string + -- importedFrom?: string + -- usesOnlyOneStackType?: boolean + -- doesNotUseFrameImplementation?: boolean + -- sourceCodeIsNotOnSearchfox?: boolean + -- extra?: ExtraProfileInfoSection[] + -- initialVisibleThreads?: ThreadIndex[] + -- initialSelectedThreads?: ThreadIndex[] + -- keepProfileThreadOrder?: boolean + -- gramsOfCO2ePerKWh?: number + ); + +-- Dumps all trace data as a Firefox profile json string +-- See `Profile` in +-- https://github.com/firefox-devtools/profiler/blob/main/src/types/profile.js +-- Also +-- https://firefox-source-docs.mozilla.org/tools/profiler/code-overview.html +-- +-- You would probably want to download the generated json and then open at +-- https://https://profiler.firefox.com +-- You can easily do this from the UI via the following SQL +-- `SELECT CAST(export_to_firefox_profile() AS BLOB) AS profile;` +-- The result will have a link for you to download this json as a file. +CREATE PERFETTO FUNCTION export_to_firefox_profile() +-- Json profile +RETURNS STRING +AS +SELECT + json_object( + 'meta', json(_export_firefox_meta()), + 'libs', json_array(), + 'pages', NULL, + 'counters', NULL, + 'profilerOverhead', NULL, + 'threads', json(_export_firefox_threads()), + 'profilingLog', NULL, + 'profileGatheringLog', NULL); diff --git a/src/trace_processor/perfetto_sql/stdlib/graphs/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/graphs/BUILD.gn index ba5bb3e5e4..cfab2ac668 100644 --- a/src/trace_processor/perfetto_sql/stdlib/graphs/BUILD.gn +++ b/src/trace_processor/perfetto_sql/stdlib/graphs/BUILD.gn @@ -16,8 +16,11 @@ import("../../../../../gn/perfetto_sql.gni") perfetto_sql_source_set("graphs") { sources = [ + "critical_path.sql", "dominator_tree.sql", + "hierarchy.sql", "partition.sql", + "scan.sql", "search.sql", ] } diff --git a/src/trace_processor/perfetto_sql/stdlib/graphs/critical_path.sql b/src/trace_processor/perfetto_sql/stdlib/graphs/critical_path.sql new file mode 100644 index 0000000000..652f8c086f --- /dev/null +++ b/src/trace_processor/perfetto_sql/stdlib/graphs/critical_path.sql @@ -0,0 +1,190 @@ +-- +-- 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 graphs.search; + +-- Computes critical paths, the dependency graph of a task. +-- The critical path is a set of edges reachable from a root node with the sum of the edge +-- weights just exceeding the root node capacity. This ensures that the tasks in the critical path +-- completely 'covers' the root capacity. +-- Typically, every node represents a point in time on some task where it transitioned from +-- idle to active state. +-- +-- Example usage on traces with Linux sched information: +-- ``` +-- -- Compute the userspace critical path from every task sleep. +-- SELECT * FROM +-- critical_path_intervals!( +-- _wakeup_userspace_edges, +-- (SELECT id AS root_node_id, prev_id - id FROM _wakeup_graph WHERE prev_id IS NOT NULL)); +-- ``` +CREATE PERFETTO MACRO _critical_path( + -- A table/view/subquery corresponding to a directed graph on which the + -- reachability search should be performed. This table must have the columns + -- "source_node_id", "dest_node_id" and "edge_weight" corresponding to the two nodes on + -- either end of the edges in the graph and the edge weight. + -- + -- Note: the columns must contain uint32 similar to ids in trace processor + -- tables (i.e. the values should be relatively dense and close to zero). The + -- implementation makes assumptions on this for performance reasons and, if + -- this criteria is not, can lead to enormous amounts of memory being + -- allocated. + -- An edge weight is the absolute difference between the node ids forming the edge. + graph_table TableOrSubQuery, + -- A table/view/subquery corresponding to start nodes to |graph_table| which will be the + -- roots of the reachability trees. This table must have the columns + -- "root_node_id" and "capacity" corresponding to the starting node id and the capacity + -- of the root node to contain edge weights. + -- + -- Note: the columns must contain uint32 similar to ids in trace processor + -- tables (i.e. the values should be relatively dense and close to zero). The + -- implementation makes assumptions on this for performance reasons and, if + -- this criteria is not, can lead to enormous amounts of memory being + -- allocated. + root_table TableOrSubQuery) + -- The returned table has the schema (root_id UINT32, id UINT32, parent_id UINT32). + -- |root_id| is the id of the root where the critical path computation started. + -- |id| is the id of a node in the critical path and |parent_id| is the predecessor of |id|. +RETURNS TableOrSubQuery +AS ( + WITH + _edges AS ( + SELECT source_node_id, dest_node_id, edge_weight FROM $graph_table + ), + _roots AS ( + SELECT + root_node_id, + capacity AS root_target_weight + FROM $root_table + ), + _search_bounds AS ( + SELECT MIN(root_node_id - root_target_weight) AS min_wakeup, + MAX(root_node_id + root_target_weight) AS max_wakeup + FROM _roots + ), + _graph AS ( + SELECT + source_node_id, + COALESCE(dest_node_id, source_node_id) AS dest_node_id, + edge_weight + FROM _edges + JOIN _search_bounds + WHERE source_node_id BETWEEN min_wakeup AND max_wakeup AND source_node_id IS NOT NULL + ) + SELECT DISTINCT + root_node_id AS root_id, + parent_node_id AS parent_id, + node_id AS id + FROM graph_reachable_weight_bounded_dfs !(_graph, _roots, 1) cr +); + +-- Flattens overlapping tasks within a critical path and flattens overlapping critical paths. +CREATE PERFETTO MACRO _critical_path_to_intervals(critical_path_table TableOrSubquery, + node_table TableOrSubquery) +RETURNS TableOrSubquery +AS ( + WITH flat_tasks AS ( + SELECT + node.ts, + cr.root_id, + cr.id, + LEAD(node.ts) OVER (PARTITION BY cr.root_id ORDER BY cr.id) - node.ts AS dur + FROM $critical_path_table cr + JOIN $node_table node USING(id) + ), span_starts AS ( + SELECT + MAX(cr.ts, idle.ts - idle_dur) AS ts, + idle.ts AS idle_end_ts, + cr.ts + cr.dur AS cr_end_ts, + cr.id, + cr.root_id + FROM flat_tasks cr + JOIN $node_table idle ON cr.root_id = idle.id + ) + SELECT + ts, + MIN(cr_end_ts, idle_end_ts) - ts AS dur, + id, + root_id + FROM span_starts + WHERE MIN(idle_end_ts, cr_end_ts) - ts > 0 +); + +-- Computes critical paths, the dependency graph of a task and returns a flattened view suitable +-- for displaying in a UI track without any overlapping intervals. +-- See the _critical_path MACRO above. +-- +-- Example usage on traces with Linux sched information: +-- ``` +-- -- Compute the userspace critical path from every task sleep. +-- SELECT * FROM +-- critical_path_intervals!( +-- _wakeup_userspace_edges, +-- (SELECT id AS root_node_id, prev_id - id FROM _wakeup_graph WHERE prev_id IS NOT NULL), +-- _wakeup_intervals); +-- ``` +CREATE PERFETTO MACRO _critical_path_intervals( + -- A table/view/subquery corresponding to a directed graph on which the + -- reachability search should be performed. This table must have the columns + -- "source_node_id", "dest_node_id" and "edge_weight" corresponding to the two nodes on + -- either end of the edges in the graph and the edge weight. + -- + -- Note: the columns must contain uint32 similar to ids in trace processor + -- tables (i.e. the values should be relatively dense and close to zero). The + -- implementation makes assumptions on this for performance reasons and, if + -- this criteria is not, can lead to enormous amounts of memory being + -- allocated. + -- An edge weight is the absolute difference between the node ids forming the edge. + graph_table TableOrSubQuery, + -- A table/view/subquery corresponding to start nodes to |graph_table| which will be the + -- roots of the reachability trees. This table must have the columns + -- "root_node_id" and "capacity" corresponding to the starting node id and the capacity + -- of the root node to contain edge weights. + -- + -- Note: the columns must contain uint32 similar to ids in trace processor + -- tables (i.e. the values should be relatively dense and close to zero). The + -- implementation makes assumptions on this for performance reasons and, if + -- this criteria is not, can lead to enormous amounts of memory being + -- allocated. + root_table TableOrSubQuery, + -- A table/view/subquery corresponding to the idle to active transition points on a task. + -- This table must have the columns, "id", "ts", "dur" and "idle_dur". ts and dur is the + -- timestamp when the task became active and how long it was active for respectively. idle_dur + -- is the duration it was idle for before it became active at "ts". + -- + -- Note: the columns must contain uint32 similar to ids in trace processor + -- tables (i.e. the values should be relatively dense and close to zero). The + -- implementation makes assumptions on this for performance reasons and, if + -- this criteria is not, can lead to enormous amounts of memory being + -- allocated. + -- There should be one row for every node id encountered in the |graph_table|. + interval_table TableOrSubQuery) +-- The returned table has the schema (id UINT32, ts INT64, dur INT64, idle_dur INT64). +-- |root_node_id| is the id of the starting node under which this edge was encountered. +-- |node_id| is the id of the node from the input graph and |parent_node_id| +-- is the id of the node which was the first encountered predecessor in a DFS +-- search of the graph. +RETURNS TableOrSubQuery +AS ( + WITH _critical_path_nodes AS ( + SELECT root_id, id FROM _critical_path!($graph_table, $root_table) + ) SELECT root_id, id, ts, dur + FROM _critical_path_to_intervals !(_critical_path_nodes, $interval_table) + UNION ALL + SELECT node.id AS root_id, node.id, node.ts, node.dur + FROM $interval_table node + JOIN $root_table ON root_node_id = id +); diff --git a/src/trace_processor/perfetto_sql/stdlib/graphs/hierarchy.sql b/src/trace_processor/perfetto_sql/stdlib/graphs/hierarchy.sql new file mode 100644 index 0000000000..771bd50904 --- /dev/null +++ b/src/trace_processor/perfetto_sql/stdlib/graphs/hierarchy.sql @@ -0,0 +1,33 @@ +-- +-- 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 graphs.search; + +-- Given a table containing the edges in a tree and a table of start_nodes, +-- returns the ids of all the nodes reachable by walking up the tree from each +-- of the start nodes. +CREATE PERFETTO MACRO _tree_reachable_ancestors_or_self( + tree TableOrSubquery, + start_nodes TableOrSubquery +) +RETURNS TableOrSubquery +AS +( + SELECT node_id AS id + FROM graph_reachable_dfs!( + (SELECT id AS source_node_id, parent_id AS dest_node_id FROM $tree), + (SELECT id AS node_id FROM $start_nodes) + ) +); diff --git a/src/trace_processor/perfetto_sql/stdlib/graphs/scan.sql b/src/trace_processor/perfetto_sql/stdlib/graphs/scan.sql new file mode 100644 index 0000000000..320a73b46f --- /dev/null +++ b/src/trace_processor/perfetto_sql/stdlib/graphs/scan.sql @@ -0,0 +1,149 @@ +-- +-- 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 MACRO _graph_scan_df_agg(x Expr, y Expr) +RETURNS Expr AS __intrinsic_stringify!($x), init_table.$y; + +CREATE PERFETTO MACRO _graph_scan_bind(x Expr, y Expr) +RETURNS Expr AS __intrinsic_table_ptr_bind(result.$x, __intrinsic_stringify!($y)); + +CREATE PERFETTO MACRO _graph_scan_select(x Expr, y Expr) +RETURNS Expr AS result.$x as $y; + +-- Performs a "scan" over the graph starting at `init_table` and using `graph_table` +-- for edges to follow. +-- +-- See https://en.wikipedia.org/wiki/Prefix_sum#Scan_higher_order_function for +-- details of what a scan means. +CREATE PERFETTO MACRO _graph_scan( + -- The table containing the edges of the graph. Needs to have the columns + -- `source_node_id` and `dest_node_id`. + graph_table TableOrSubquery, + -- The table of nodes to start the scan from. Needs to have the column `id` + -- and all columns specified by `scan_columns`. + init_table TableOrSubquery, + -- A parenthesised and comma separated list of columns which will be returned + -- by the scan. Should match exactly both the names and order of the columns + -- in both `init_table` and `step_query`. + -- + -- Example: (cumulative_sum, cumulative_count). + scan_columns _ColumnNameList, + -- A subquery which is reads all the data (from a variable table called $table) + -- for a single step of the scan and performs some computation for each node in + -- the step. + -- + -- Should return a column `id` and all columns specified by `scan_columns`. + step_query TableOrSubquery +) +RETURNS TableOrSubquery AS +( + select + c0 as id, + __intrinsic_token_zip_join!( + (c1, c2, c3, c4, c5, c6, c7), + $scan_columns, + _graph_scan_select, + __intrinsic_token_comma!() + ) + from __intrinsic_table_ptr(__intrinsic_graph_scan( + ( + select __intrinsic_graph_agg(g.source_node_id, g.dest_node_id) + from $graph_table g + ), + ( + select __intrinsic_row_dataframe_agg( + 'id', init_table.id, + __intrinsic_token_zip_join!( + $scan_columns, + $scan_columns, + _graph_scan_df_agg, + __intrinsic_token_comma!() + ) + ) + from $init_table AS init_table + ), + __intrinsic_stringify!($step_query, table), + __intrinsic_stringify!($scan_columns) + )) result + where __intrinsic_table_ptr_bind(result.c0, 'id') + and __intrinsic_token_zip_join!( + (c1, c2, c3, c4, c5, c6, c7), + $scan_columns, + _graph_scan_bind, + AND + ) +); + +-- Performs a "scan" over the graph starting at `init_table` and using `graph_table` +-- for edges to follow, aggregating on each node wherever possible using `agg_query`. +-- +-- See https://en.wikipedia.org/wiki/Prefix_sum#Scan_higher_order_function for +-- details of what a scan means. +CREATE PERFETTO MACRO _graph_aggregating_scan( + -- The table containing the edges of the graph. Needs to have the columns + -- `source_node_id` and `dest_node_id`. + graph_table TableOrSubquery, + -- The table of nodes to start the scan from. Needs to have the column `id` + -- and all columns specified by `agg_columns`. + init_table TableOrSubquery, + -- A parenthesised and comma separated list of columns which will be returned + -- by the scan. Should match exactly both the names and order of the columns + -- in both `init_table` and `agg_query`. + -- + -- Example: (cumulative_sum, cumulative_count). + agg_columns _ColumnNameList, + -- A subquery which aggregates the data for one step of the scan. Should contain + -- the column `id` and all columns specified by `agg_columns`. Should read from + -- a variable table labelled `$table`. + agg_query TableOrSubquery +) +RETURNS TableOrSubquery AS +( + select + c0 as id, + __intrinsic_token_zip_join!( + (c1, c2, c3, c4, c5, c6, c7), + $agg_columns, + _graph_scan_select, + __intrinsic_token_comma!() + ) + from __intrinsic_table_ptr(__intrinsic_graph_aggregating_scan( + ( + select __intrinsic_graph_agg(g.source_node_id, g.dest_node_id) + from $graph_table g + ), + ( + select __intrinsic_row_dataframe_agg( + 'id', init_table.id, + __intrinsic_token_zip_join!( + $agg_columns, + $agg_columns, + _graph_scan_df_agg, + __intrinsic_token_comma!() + ) + ) + from $init_table AS init_table + ), + __intrinsic_stringify!($agg_query, table), + __intrinsic_stringify!($agg_columns) + )) result + where __intrinsic_table_ptr_bind(result.c0, 'id') + and __intrinsic_token_zip_join!( + (c1, c2, c3, c4, c5, c6, c7), + $agg_columns, + _graph_scan_bind, + AND + ) +); diff --git a/src/trace_processor/perfetto_sql/stdlib/graphs/search.sql b/src/trace_processor/perfetto_sql/stdlib/graphs/search.sql index 19a5ec89fb..e25e45f8a8 100644 --- a/src/trace_processor/perfetto_sql/stdlib/graphs/search.sql +++ b/src/trace_processor/perfetto_sql/stdlib/graphs/search.sql @@ -13,10 +13,10 @@ -- See the License for the specific language governing permissions and -- limitations under the License. --- Computes the "reachable" set of nodes in a directed graph from a given --- starting node by performing a depth-first search on the graph. The returned --- nodes are structured as a tree with parent-child relationships corresponding --- to the order in which nodes were encountered by the DFS. +-- Computes the "reachable" set of nodes in a directed graph from a given set +-- of starting nodes by performing a depth-first search on the graph. The +-- returned nodes are structured as a tree with parent-child relationships +-- corresponding to the order in which nodes were encountered by the DFS. -- -- While this macro can be used directly by end users (hence being public), -- it is primarily intended as a lower-level building block upon which higher @@ -34,7 +34,7 @@ -- FROM heap_graph_reference -- WHERE owned_id IS NOT NULL -- ), --- (SELECT id FROM heap_graph_object WHERE root_type IS NOT NULL LIMIT 1) +-- (SELECT id FROM heap_graph_object WHERE root_type IS NOT NULL) -- ); -- ``` CREATE PERFETTO MACRO graph_reachable_dfs( @@ -49,9 +49,9 @@ CREATE PERFETTO MACRO graph_reachable_dfs( -- this criteria is not, can lead to enormous amounts of memory being -- allocated. graph_table TableOrSubquery, - -- The start node to |graph_table| which will be the root of the reachability - -- tree. - start_node_id Expr + -- A table/view/subquery corresponding to the list of start nodes for + -- the BFS. This table must have a single column "node_id". + start_nodes TableOrSubquery ) -- The returned table has the schema (node_id UINT32, parent_node_id UINT32). -- |node_id| is the id of the node from the input graph and |parent_node_id| @@ -61,10 +61,67 @@ RETURNS TableOrSubquery AS ( -- Rename the generic columns of __intrinsic_table_ptr to the actual columns. SELECT c0 AS node_id, c1 AS parent_node_id - FROM __intrinsic_table_ptr(( - -- Aggregate function to perform a DFS on the nodes on the input graph. - SELECT __intrinsic_dfs(g.source_node_id, g.dest_node_id, $start_node_id) - FROM $graph_table g + FROM __intrinsic_table_ptr(__intrinsic_dfs( + (SELECT __intrinsic_graph_agg(g.source_node_id, g.dest_node_id) FROM $graph_table g), + (SELECT __intrinsic_array_agg(t.node_id) arr FROM $start_nodes t) + )) + -- Bind the dynamic columns in the |__intrinsic_table_ptr| to the columns of + -- the DFS table. + WHERE __intrinsic_table_ptr_bind(c0, 'node_id') + AND __intrinsic_table_ptr_bind(c1, 'parent_node_id') +); + +-- Computes the "reachable" set of nodes in a directed graph from a given +-- starting node by performing a breadth-first search on the graph. The returned +-- nodes are structured as a tree with parent-child relationships corresponding +-- to the order in which nodes were encountered by the BFS. +-- +-- While this macro can be used directly by end users (hence being public), +-- it is primarily intended as a lower-level building block upon which higher +-- level functions/macros in the standard library can be built. +-- +-- Example usage on traces containing heap graphs: +-- ``` +-- -- Compute the reachable nodes from all heap roots. +-- SELECT * +-- FROM graph_reachable_bfs!( +-- ( +-- SELECT +-- owner_id AS source_node_id, +-- owned_id as dest_node_id +-- FROM heap_graph_reference +-- WHERE owned_id IS NOT NULL +-- ), +-- (SELECT id FROM heap_graph_object WHERE root_type IS NOT NULL) +-- ); +-- ``` +CREATE PERFETTO MACRO graph_reachable_bfs( + -- A table/view/subquery corresponding to a directed graph on which the + -- reachability search should be performed. This table must have the columns + -- "source_node_id" and "dest_node_id" corresponding to the two nodes on + -- either end of the edges in the graph. + -- + -- Note: the columns must contain uint32 similar to ids in trace processor + -- tables (i.e. the values should be relatively dense and close to zero). The + -- implementation makes assumptions on this for performance reasons and, if + -- this criteria is not, can lead to enormous amounts of memory being + -- allocated. + graph_table TableOrSubquery, + -- A table/view/subquery corresponding to the list of start nodes for + -- the BFS. This table must have a single column "node_id". + start_nodes TableOrSubquery +) +-- The returned table has the schema (node_id UINT32, parent_node_id UINT32). +-- |node_id| is the id of the node from the input graph and |parent_node_id| +-- is the id of the node which was the first encountered predecessor in a BFS +-- search of the graph. +RETURNS TableOrSubquery AS +( + -- Rename the generic columns of __intrinsic_table_ptr to the actual columns. + SELECT c0 AS node_id, c1 AS parent_node_id + FROM __intrinsic_table_ptr(__intrinsic_bfs( + (SELECT __intrinsic_graph_agg(g.source_node_id, g.dest_node_id) FROM $graph_table g), + (SELECT __intrinsic_array_agg(t.node_id) arr FROM $start_nodes t) )) -- Bind the dynamic columns in the |__intrinsic_table_ptr| to the columns of -- the DFS table. diff --git a/src/trace_processor/perfetto_sql/stdlib/intervals/intersect.sql b/src/trace_processor/perfetto_sql/stdlib/intervals/intersect.sql index 4d867d0c3e..bb5ef2e9c0 100644 --- a/src/trace_processor/perfetto_sql/stdlib/intervals/intersect.sql +++ b/src/trace_processor/perfetto_sql/stdlib/intervals/intersect.sql @@ -13,41 +13,106 @@ -- See the License for the specific language governing permissions and -- limitations under the License. +INCLUDE PERFETTO MODULE metasql.table_list; + +CREATE PERFETTO MACRO _ii_df_agg(x Expr, y Expr) +RETURNS Expr AS __intrinsic_stringify!($x), $y; + +CREATE PERFETTO MACRO _ii_df_bind(x Expr, y Expr) +RETURNS Expr AS __intrinsic_table_ptr_bind($x, __intrinsic_stringify!($y)); + +CREATE PERFETTO MACRO _ii_df_select(x Expr, y Expr) +RETURNS Expr AS $x AS $y; + +CREATE PERFETTO MACRO __first_arg(x Expr, y Expr) +RETURNS Expr AS $x; + +CREATE PERFETTO MACRO _interval_agg( + tab TableOrSubquery, + agg_columns _ColumnNameList +) +RETURNS TableOrSubquery AS +( + SELECT + __intrinsic_interval_tree_intervals_agg( + id, + ts, + dur + __intrinsic_prefixed_token_zip_join!( + $agg_columns, + $agg_columns, + _ii_df_agg, + __intrinsic_token_comma!() + ) + ) + FROM $tab + ORDER BY ts +); + CREATE PERFETTO MACRO _interval_intersect( - left_table TableOrSubquery, - right_table TableOrSubquery + tabs _TableNameList, + agg_columns _ColumnNameList ) RETURNS TableOrSubquery AS ( - WITH - __temp_left_table AS (SELECT * FROM $left_table ORDER BY ts), - __temp_right_table AS (SELECT * FROM $right_table ORDER BY ts) - SELECT ii.ts, ii.dur, ii.left_id, ii.right_id - FROM __intrinsic_interval_intersect( - (SELECT RepeatedField(id) FROM __temp_left_table), - (SELECT RepeatedField(ts) FROM __temp_left_table), - (SELECT RepeatedField(dur) FROM __temp_left_table), - (SELECT RepeatedField(id) FROM __temp_right_table), - (SELECT RepeatedField(ts) FROM __temp_right_table), - (SELECT RepeatedField(dur) FROM __temp_right_table) - ) ii + SELECT + c0 AS ts, + c1 AS dur, + -- Columns for tables ids, in the order of provided tables. + __intrinsic_token_zip_join!( + (c2 AS id_0, c3 AS id_1, c4 AS id_2, c5 AS id_3, c6 AS id_4), + $tabs, + __first_arg, + __intrinsic_token_comma!() + ) + -- Columns for partitions, one for each column with partition. Prefixed to + -- handle case of no partitions. + __intrinsic_prefixed_token_zip_join!( + (c7, c8, c9, c10), + $agg_columns, + _ii_df_select, + __intrinsic_token_comma!() + ) + -- Interval intersect result table. + FROM __intrinsic_table_ptr( + __intrinsic_interval_intersect( + _metasql_map_join_table_list_with_capture!($tabs, _interval_agg, ($agg_columns)), + __intrinsic_stringify!($agg_columns) + ) + ) + + -- Bind the resulting columns + WHERE __intrinsic_table_ptr_bind(c0, 'ts') + AND __intrinsic_table_ptr_bind(c1, 'dur') + -- Id columns + AND __intrinsic_table_ptr_bind(c2, 'id_0') + AND __intrinsic_table_ptr_bind(c3, 'id_1') + AND __intrinsic_table_ptr_bind(c4, 'id_2') + AND __intrinsic_table_ptr_bind(c5, 'id_3') + AND __intrinsic_table_ptr_bind(c6, 'id_4') + + -- Partition columns. Prefixed to handle case of no partitions. + __intrinsic_prefixed_token_zip_join!( + (c7, c8, c9, c10), + $agg_columns, + _ii_df_bind, + AND + ) ); CREATE PERFETTO MACRO _interval_intersect_single( ts Expr, dur Expr, - intervals_table TableOrSubquery -) RETURNS TableOrSubquery AS ( + t TableOrSubquery +) +RETURNS TableOrSubquery AS +( SELECT - left_id AS id, - ts, - dur + id_0 AS id, + ts, + dur FROM _interval_intersect!( - $intervals_table, - (SELECT - 0 AS id, - $ts AS ts, - $dur AS dur - ) + ($t, (SELECT 0 AS id, $ts AS ts, $dur AS dur)), + () ) -) +); diff --git a/src/trace_processor/perfetto_sql/stdlib/intervals/overlap.sql b/src/trace_processor/perfetto_sql/stdlib/intervals/overlap.sql index 3db21d4270..cd8416422d 100644 --- a/src/trace_processor/perfetto_sql/stdlib/intervals/overlap.sql +++ b/src/trace_processor/perfetto_sql/stdlib/intervals/overlap.sql @@ -90,4 +90,144 @@ WITH ts_with_next AS ( ) SELECT count() AS has_overlaps FROM filtered -); \ No newline at end of file +); + +-- Merges a |roots_table| and |children_table| into one table. See _intervals_flatten +-- that accepts the output of this macro to flatten intervals. +CREATE PERFETTO MACRO _intervals_merge_root_and_children( + -- Table or subquery containing all the root intervals: (id, ts, dur). + -- Note that parent_id is not necessary in this table as it will be NULL anyways. + roots_table TableOrSubquery, + -- Table or subquery containing all the child intervals: + -- (root_id, id, parent_id, ts, dur) + children_table TableOrSubquery) +-- The returned table has the schema (root_id UINT32, root_ts INT64, root_dur, INT64, +-- id UINT32, parent_id UINT32, ts INT64, dur INT64). +RETURNS TableOrSubquery +AS ( + WITH + _roots AS ( + SELECT id AS root_id, ts AS root_ts, dur AS root_dur FROM ($roots_table) WHERE dur > 0 + ), + _children AS ( + SELECT * FROM ($children_table) WHERE dur > 0 + ), + _roots_without_children AS ( + SELECT root_id FROM _roots + EXCEPT + SELECT DISTINCT parent_id AS root_id FROM _children + ) + SELECT + _roots.root_id, + _roots.root_ts, + _roots.root_dur, + _children.id, + _children.parent_id, + _children.ts, + _children.dur + FROM _children + JOIN _roots USING(root_id) + UNION ALL + -- Handle singleton roots + SELECT + root_id, + root_ts, + root_dur, + NULL AS id, + NULL AS parent_id, + NULL AS ts, + NULL AS dur + FROM _roots_without_children + JOIN _roots USING(root_id) +); + +-- Partition and flatten a hierarchy of intervals into non-overlapping intervals where +-- each resulting interval is the leaf in the hierarchy at any given time. The result also +-- denotes the 'self-time' of each interval. +-- +-- Each interval is a (root_id, root_ts, root_dur, id, parent_id, ts, dur) and the overlap is +-- represented as a (root_id, id, parent_id, ts, dur). +-- Note that, children intervals must not be longer than any ancestor interval. +-- See _intervals_merge_root_and_children that can be used to generate input to this macro +-- from two different root and children tables. +CREATE PERFETTO MACRO _intervals_flatten(children_with_roots_table TableOrSubquery) +-- The returned table has the schema (root_id UINT32, id UINT32, ts INT64, dur INT64). +RETURNS TableOrSubquery +AS ( + -- Algorithm: Sort all the start and end timestamps of the children within a root. + -- The interval duration between one timestamp and the next is one result. + -- If the timestamp is a start, the id is the id of the interval, if it's an end, + -- it's the parent_id. + -- Special case the edges of the roots and roots without children. + WITH + _children_with_roots AS ( + SELECT * FROM ($children_with_roots_table) WHERE root_dur > 0 AND (dur IS NULL OR dur > 0) + ), + _ends AS ( + SELECT + root_id, + root_ts, + root_dur, + IFNULL(parent_id, root_id) AS id, + ts + dur AS ts + FROM _children_with_roots WHERE id IS NOT NULL + ), + _events AS ( + SELECT root_id, root_ts, root_dur, id, ts, 1 AS priority + FROM _children_with_roots + UNION ALL + SELECT root_id, root_ts, root_dur, id, ts, 0 AS priority FROM _ends + ), + _events_deduped AS ( + SELECT root_id, root_ts, root_dur, id, ts + FROM _events + GROUP BY root_id, ts + HAVING priority = MAX(priority) + ), + _intervals AS ( + SELECT + root_id, + root_ts, + root_dur, + id, + ts, + LEAD(ts) + OVER (PARTITION BY root_id ORDER BY ts) - ts AS dur + FROM _events_deduped + ), + _only_middle AS ( + SELECT * FROM _intervals WHERE dur > 0 + ), + _only_start AS ( + SELECT + root_id, + root_id AS id, + root_ts AS ts, + MIN(ts) - root_ts AS dur + FROM _only_middle + GROUP BY root_id + HAVING dur > 0 + ), + _only_end AS ( + SELECT + root_id, + root_id AS id, + MAX(ts + dur) AS ts, + root_ts + root_dur - MAX(ts + dur) AS dur + FROM _only_middle + GROUP BY root_id + HAVING dur > 0 + ), + _only_singleton AS ( + SELECT root_id, root_id AS id, root_ts AS ts, root_dur AS dur + FROM _children_with_roots WHERE id IS NULL + GROUP BY root_id + ) + SELECT root_id, id, ts, dur FROM _only_middle + UNION ALL + SELECT root_id, id, ts, dur FROM _only_start + UNION ALL + SELECT root_id, id, ts, dur FROM _only_end + UNION ALL + SELECT root_id, id, ts, dur FROM _only_singleton +); diff --git a/src/trace_processor/perfetto_sql/stdlib/linux/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/linux/BUILD.gn index 6edf9ba8cc..e0fd42f5f7 100644 --- a/src/trace_processor/perfetto_sql/stdlib/linux/BUILD.gn +++ b/src/trace_processor/perfetto_sql/stdlib/linux/BUILD.gn @@ -15,5 +15,10 @@ import("../../../../../gn/perfetto_sql.gni") perfetto_sql_source_set("linux") { - sources = [ "cpu_idle.sql" ] + sources = [] + deps = [ + "cpu", + "memory", + "perf", + ] } diff --git a/ui/eslint-all b/src/trace_processor/perfetto_sql/stdlib/linux/cpu/BUILD.gn old mode 100755 new mode 100644 similarity index 78% rename from ui/eslint-all rename to src/trace_processor/perfetto_sql/stdlib/linux/cpu/BUILD.gn index 302987583e..a9d870a860 --- a/ui/eslint-all +++ b/src/trace_processor/perfetto_sql/stdlib/linux/cpu/BUILD.gn @@ -1,4 +1,3 @@ -#!/bin/bash # Copyright (C) 2022 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,7 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -set -e -u -ROOT_DIR="$(dirname $(cd -P ${BASH_SOURCE[0]%/*}; pwd))" +import("../../../../../../gn/perfetto_sql.gni") -exec "$ROOT_DIR/ui/eslint" "$ROOT_DIR/ui" --ext ".js,.ts" "$@" +perfetto_sql_source_set("cpu") { + sources = [ + "frequency.sql", + "idle.sql", + ] + deps = [ "utilization" ] +} diff --git a/src/trace_processor/perfetto_sql/stdlib/cpu/freq.sql b/src/trace_processor/perfetto_sql/stdlib/linux/cpu/frequency.sql similarity index 94% rename from src/trace_processor/perfetto_sql/stdlib/cpu/freq.sql rename to src/trace_processor/perfetto_sql/stdlib/linux/cpu/frequency.sql index c0226bfd50..4d1c56678a 100644 --- a/src/trace_processor/perfetto_sql/stdlib/cpu/freq.sql +++ b/src/trace_processor/perfetto_sql/stdlib/linux/cpu/frequency.sql @@ -17,7 +17,7 @@ INCLUDE PERFETTO MODULE counters.intervals; -- Counter information for each frequency change for each CPU. Finds each time -- region where a CPU frequency is constant. -CREATE PERFETTO TABLE cpu_freq_counters( +CREATE PERFETTO TABLE cpu_frequency_counters( -- Counter id. id INT, -- Joinable with 'counter_track.id'. @@ -37,7 +37,7 @@ SELECT count_w_dur.track_id, count_w_dur.ts, count_w_dur.dur, - count_w_dur.value as freq, + cast_int!(count_w_dur.value) as freq, cct.cpu FROM counter_leading_intervals!(( @@ -48,4 +48,3 @@ counter_leading_intervals!(( )) count_w_dur JOIN cpu_counter_track cct ON track_id = cct.id; - diff --git a/src/trace_processor/perfetto_sql/stdlib/cpu/idle.sql b/src/trace_processor/perfetto_sql/stdlib/linux/cpu/idle.sql similarity index 55% rename from src/trace_processor/perfetto_sql/stdlib/cpu/idle.sql rename to src/trace_processor/perfetto_sql/stdlib/linux/cpu/idle.sql index 7f837a2aca..70191870dd 100644 --- a/src/trace_processor/perfetto_sql/stdlib/cpu/idle.sql +++ b/src/trace_processor/perfetto_sql/stdlib/linux/cpu/idle.sql @@ -14,6 +14,7 @@ -- limitations under the License. INCLUDE PERFETTO MODULE counters.intervals; +INCLUDE PERFETTO MODULE linux.cpu.frequency; -- Counter information for each idle state change for each CPU. Finds each time -- region where a CPU idle state is constant. @@ -39,7 +40,7 @@ SELECT count_w_dur.track_id, count_w_dur.ts, count_w_dur.dur, - IIF(count_w_dur.value = 4294967295, -1, count_w_dur.value) AS idle, + cast_int!(IIF(count_w_dur.value = 4294967295, -1, count_w_dur.value)) AS idle, cct.cpu FROM counter_leading_intervals!(( @@ -51,3 +52,52 @@ counter_leading_intervals!(( JOIN cpu_counter_track cct ON track_id = cct.id; +CREATE PERFETTO VIEW _freq_counters_for_sp_jn AS +SELECT ts, dur, cpu +FROM cpu_frequency_counters; + +CREATE PERFETTO VIEW _idle_counters_for_sp_jn AS +SELECT ts, dur, cpu, idle +FROM cpu_idle_counters; + +-- Combined cpu freq & idle counter +CREATE VIRTUAL TABLE _freq_idle_counters +USING span_join( + _freq_counters_for_sp_jn PARTITIONED cpu, + _idle_counters_for_sp_jn PARTITIONED cpu +); + +-- Aggregates cpu idle statistics per core. +CREATE PERFETTO TABLE cpu_idle_stats( + -- CPU core number. + cpu INT, + -- CPU idle state (C-states). + state INT, + -- The count of entering idle state. + count INT, + -- Total CPU core idle state duration in nanoseconds. + dur INT, + -- Average CPU core idle state duration in nanoseconds. + avg_dur INT, + -- Idle state percentage of non suspend time (C-states + P-states). + idle_percent FLOAT +) +AS +WITH +total AS ( + SELECT + cpu, + sum(dur) AS dur + FROM _freq_idle_counters + GROUP BY cpu +) +SELECT + cpu, + (idle + 1) AS state, + COUNT(idle) AS count, + SUM(dur) AS dur, + SUM(dur) / COUNT(idle) AS avg_dur, + SUM(dur) * 100.0 / (SELECT dur FROM total t WHERE t.cpu = ific.cpu) AS idle_percent +FROM _freq_idle_counters ific +WHERE idle >=0 +GROUP BY cpu, idle; diff --git a/src/trace_processor/perfetto_sql/stdlib/sched/utilization/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/linux/cpu/utilization/BUILD.gn similarity index 93% rename from src/trace_processor/perfetto_sql/stdlib/sched/utilization/BUILD.gn rename to src/trace_processor/perfetto_sql/stdlib/linux/cpu/utilization/BUILD.gn index d4be2cc877..55294e7368 100644 --- a/src/trace_processor/perfetto_sql/stdlib/sched/utilization/BUILD.gn +++ b/src/trace_processor/perfetto_sql/stdlib/linux/cpu/utilization/BUILD.gn @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -import("../../../../../../gn/perfetto_sql.gni") +import("../../../../../../../gn/perfetto_sql.gni") perfetto_sql_source_set("utilization") { sources = [ diff --git a/src/trace_processor/perfetto_sql/stdlib/sched/utilization/general.sql b/src/trace_processor/perfetto_sql/stdlib/linux/cpu/utilization/general.sql similarity index 83% rename from src/trace_processor/perfetto_sql/stdlib/sched/utilization/general.sql rename to src/trace_processor/perfetto_sql/stdlib/linux/cpu/utilization/general.sql index 629e5a3065..e8d05e270b 100644 --- a/src/trace_processor/perfetto_sql/stdlib/sched/utilization/general.sql +++ b/src/trace_processor/perfetto_sql/stdlib/linux/cpu/utilization/general.sql @@ -13,6 +13,8 @@ -- See the License for the specific language governing permissions and -- limitations under the License. +INCLUDE PERFETTO MODULE linux.cpu.frequency; + -- Returns the timestamp of the start of the partition that contains the |ts|. CREATE PERFETTO FUNCTION _partition_start(ts INT, size INT) RETURNS INT AS -- Division of two ints would result in floor(ts/size). @@ -58,7 +60,7 @@ ON (p.ts <= i.ts AND i.ts < p.ts_end)); -- Utilization is calculated as sum of average utilization of each CPU in each -- period, which is defined as a multiply of |interval|. For this reason -- first and last period might have lower then real utilization. -CREATE PERFETTO MACRO _sched_avg_utilization_per_period( +CREATE PERFETTO MACRO _cpu_avg_utilization_per_period( -- Length of the period on which utilization should be averaged. interval Expr, -- Either sched table or its filtered down version. @@ -73,3 +75,26 @@ SELECT SUM(ts_end - ts)/cast_double!($interval) AS unnormalized_utilization FROM _interval_partitions!(_partitions($interval), $sched_table) GROUP BY 1); + +CREATE PERFETTO VIEW _cpu_freq_for_metrics AS +SELECT + id, + ts, + dur, + cpu, + freq +FROM cpu_frequency_counters; + +CREATE PERFETTO VIEW _sched_without_id AS +SELECT ts, dur, utid, cpu +FROM sched +WHERE utid != 0 AND dur != -1; + +CREATE VIRTUAL TABLE _cpu_freq_per_thread_span_join +USING SPAN_LEFT_JOIN( + _sched_without_id PARTITIONED cpu, + _cpu_freq_for_metrics PARTITIONED cpu); + +CREATE PERFETTO TABLE _cpu_freq_per_thread +AS SELECT * FROM _cpu_freq_per_thread_span_join; + diff --git a/src/trace_processor/perfetto_sql/stdlib/sched/utilization/process.sql b/src/trace_processor/perfetto_sql/stdlib/linux/cpu/utilization/process.sql similarity index 67% rename from src/trace_processor/perfetto_sql/stdlib/sched/utilization/process.sql rename to src/trace_processor/perfetto_sql/stdlib/linux/cpu/utilization/process.sql index 8437cb49c3..3b998d4892 100644 --- a/src/trace_processor/perfetto_sql/stdlib/sched/utilization/process.sql +++ b/src/trace_processor/perfetto_sql/stdlib/linux/cpu/utilization/process.sql @@ -13,14 +13,14 @@ -- See the License for the specific language governing permissions and -- limitations under the License. -INCLUDE PERFETTO MODULE sched.utilization.general; +INCLUDE PERFETTO MODULE linux.cpu.utilization.general; INCLUDE PERFETTO MODULE time.conversion; -- Returns a table of process utilization per given period. -- Utilization is calculated as sum of average utilization of each CPU in each -- period, which is defined as a multiply of |interval|. For this reason -- first and last period might have lower then real utilization. -CREATE PERFETTO FUNCTION sched_process_utilization_per_period( +CREATE PERFETTO FUNCTION cpu_process_utilization_per_period( -- Length of the period on which utilization should be averaged. interval INT, -- Upid of the process. @@ -47,13 +47,13 @@ WITH sched_for_upid AS ( JOIN thread USING (utid) JOIN process USING (upid) WHERE upid = $upid AND utid != 0) -SELECT * FROM _sched_avg_utilization_per_period!($interval, sched_for_upid); +SELECT * FROM _cpu_avg_utilization_per_period!($interval, sched_for_upid); -- Returns a table of process utilization per second. -- Utilization is calculated as sum of average utilization of each CPU in each -- period, which is defined as a multiply of |interval|. For this reason -- first and last period might have lower then real utilization. -CREATE PERFETTO FUNCTION sched_process_utilization_per_second( +CREATE PERFETTO FUNCTION cpu_process_utilization_per_second( -- Upid of the process. upid INT ) @@ -69,4 +69,36 @@ RETURNS TABLE ( -- [0, cpu_count] range. unnormalized_utilization DOUBLE ) AS -SELECT * FROM sched_process_utilization_per_period(time_from_s(1), $upid); \ No newline at end of file +SELECT * FROM cpu_process_utilization_per_period(time_from_s(1), $upid); + +-- Aggregated CPU statistics for each process. +CREATE PERFETTO TABLE cpu_cycles_per_process( + -- Unique process id + upid INT, + -- Sum of CPU millicycles + millicycles INT, + -- Sum of CPU megacycles + megacycles INT, + -- Total runtime duration + runtime INT, + -- Minimum CPU frequency in kHz + min_freq INT, + -- Maximum CPU frequency in kHz + max_freq INT, + -- Average CPU frequency in kHz + avg_freq INT +) AS +WITH threads AS ( + SELECT upid, utid FROM thread +) +SELECT + upid, + cast_int!(SUM(dur * freq) / 1000) AS millicycles, + cast_int!(SUM(dur * freq) / 1000 / 1e9) AS megacycles, + SUM(dur) AS runtime, + MIN(freq) AS min_freq, + MAX(freq) AS max_freq, + cast_int!(SUM((dur * freq) / 1000) / SUM(dur / 1000)) AS avg_freq +FROM _cpu_freq_per_thread +JOIN threads USING (utid) +GROUP BY upid; \ No newline at end of file diff --git a/src/trace_processor/perfetto_sql/stdlib/sched/utilization/system.sql b/src/trace_processor/perfetto_sql/stdlib/linux/cpu/utilization/system.sql similarity index 50% rename from src/trace_processor/perfetto_sql/stdlib/sched/utilization/system.sql rename to src/trace_processor/perfetto_sql/stdlib/linux/cpu/utilization/system.sql index 7dd671d6a6..97e96b92e7 100644 --- a/src/trace_processor/perfetto_sql/stdlib/sched/utilization/system.sql +++ b/src/trace_processor/perfetto_sql/stdlib/linux/cpu/utilization/system.sql @@ -13,7 +13,7 @@ -- See the License for the specific language governing permissions and -- limitations under the License. -INCLUDE PERFETTO MODULE sched.utilization.general; +INCLUDE PERFETTO MODULE linux.cpu.utilization.general; INCLUDE PERFETTO MODULE time.conversion; -- The purpose of this module is to provide high level aggregates of system @@ -23,7 +23,7 @@ INCLUDE PERFETTO MODULE time.conversion; -- Utilization is calculated as sum of average utilization of each CPU in each -- period, which is defined as a multiply of |interval|. For this reason -- first and last period might have lower then real utilization. -CREATE PERFETTO FUNCTION sched_utilization_per_period( +CREATE PERFETTO FUNCTION cpu_utilization_per_period( -- Length of the period on which utilization should be averaged. interval INT) RETURNS TABLE ( @@ -39,7 +39,7 @@ RETURNS TABLE ( unnormalized_utilization DOUBLE ) AS SELECT * -FROM _sched_avg_utilization_per_period!( +FROM _cpu_avg_utilization_per_period!( $interval, (SELECT * FROM sched WHERE utid != 0) ); @@ -48,7 +48,7 @@ FROM _sched_avg_utilization_per_period!( -- Utilization is calculated by sum of average utilization of each CPU every -- second. For this reason first and last second might have lower then real -- utilization. -CREATE PERFETTO TABLE sched_utilization_per_second( +CREATE PERFETTO TABLE cpu_utilization_per_second( -- Timestamp of start of a second. ts INT, -- Sum of average utilization over period. @@ -64,4 +64,72 @@ SELECT ts, utilization, unnormalized_utilization -FROM sched_utilization_per_period(time_from_s(1)); \ No newline at end of file +FROM cpu_utilization_per_period(time_from_s(1)); + +-- Aggregated CPU statistics for runtime of each thread on a CPU. +CREATE PERFETTO TABLE _cpu_cycles_raw( + -- The id of CPU + cpu INT, + -- Unique thread id + utid INT, + -- Sum of CPU millicycles + millicycles INT, + -- Sum of CPU megacycles + megacycles INT, + -- Total runtime duration + runtime INT, + -- Minimum CPU frequency in kHz + min_freq INT, + -- Maximum CPU frequency in kHz + max_freq INT, + -- Average CPU frequency in kHz + avg_freq INT +) AS +SELECT + cpu, + utid, + -- We divide by 1e3 here as dur is in ns and freq in khz. In total + -- this means we need to divide the duration by 1e9 and multiply the + -- frequency by 1e3 then multiply again by 1e3 to get millicycles + -- i.e. divide by 1e3 in total. + -- We use millicycles as we want to preserve this level of precision + -- for future calculations. + cast_int!(SUM(dur * freq) / 1000) AS millicycles, + cast_int!(SUM(dur * freq) / 1000 / 1e9) AS megacycles, + SUM(dur) AS runtime, + MIN(freq) AS min_freq, + MAX(freq) AS max_freq, + -- We choose to work in micros space in both the numerator and + -- denominator as this gives us good enough precision without risking + -- overflows. + cast_int!(SUM((dur * freq) / 1000) / SUM(dur / 1000)) AS avg_freq +FROM _cpu_freq_per_thread +GROUP BY utid, cpu; + +-- Aggregated CPU statistics for each CPU. +CREATE PERFETTO TABLE cpu_cycles_per_cpu( + -- The id of CPU + cpu INT, + -- Sum of CPU millicycles + millicycles INT, + -- Sum of CPU megacycles + megacycles INT, + -- Total runtime of all threads running on CPU + runtime INT, + -- Minimum CPU frequency in kHz + min_freq INT, + -- Maximum CPU frequency in kHz + max_freq INT, + -- Average CPU frequency in kHz + avg_freq INT +) AS +SELECT + cpu, + cast_int!(SUM(dur * freq) / 1000) AS millicycles, + cast_int!(SUM(dur * freq) / 1000 / 1e9) AS megacycles, + SUM(dur) AS runtime, + MIN(freq) AS min_freq, + MAX(freq) AS max_freq, + cast_int!(SUM((dur * freq) / 1000) / SUM(dur / 1000)) AS avg_freq +FROM _cpu_freq_per_thread +GROUP BY cpu; \ No newline at end of file diff --git a/src/trace_processor/perfetto_sql/stdlib/sched/utilization/thread.sql b/src/trace_processor/perfetto_sql/stdlib/linux/cpu/utilization/thread.sql similarity index 68% rename from src/trace_processor/perfetto_sql/stdlib/sched/utilization/thread.sql rename to src/trace_processor/perfetto_sql/stdlib/linux/cpu/utilization/thread.sql index ae1d3f673e..376c06ddbe 100644 --- a/src/trace_processor/perfetto_sql/stdlib/sched/utilization/thread.sql +++ b/src/trace_processor/perfetto_sql/stdlib/linux/cpu/utilization/thread.sql @@ -13,14 +13,14 @@ -- See the License for the specific language governing permissions and -- limitations under the License. -INCLUDE PERFETTO MODULE sched.utilization.general; +INCLUDE PERFETTO MODULE linux.cpu.utilization.general; INCLUDE PERFETTO MODULE time.conversion; -- Returns a table of thread utilization per given period. -- Utilization is calculated as sum of average utilization of each CPU in each -- period, which is defined as a multiply of |interval|. For this reason -- first and last period might have lower then real utilization. -CREATE PERFETTO FUNCTION sched_thread_utilization_per_period( +CREATE PERFETTO FUNCTION cpu_thread_utilization_per_period( -- Length of the period on which utilization should be averaged. interval INT, -- Utid of the thread. @@ -45,13 +45,13 @@ WITH sched_for_utid AS ( utid FROM sched WHERE utid = $utid -) SELECT * FROM _sched_avg_utilization_per_period!($interval, sched_for_utid); +) SELECT * FROM _cpu_avg_utilization_per_period!($interval, sched_for_utid); -- Returns a table of thread utilization per second. -- Utilization is calculated as sum of average utilization of each CPU in each -- period, which is defined as a multiply of |interval|. For this reason -- first and last period might have lower then real utilization. -CREATE PERFETTO FUNCTION sched_thread_utilization_per_second( +CREATE PERFETTO FUNCTION cpu_thread_utilization_per_second( -- Utid of the thread. utid INT ) @@ -67,4 +67,32 @@ RETURNS TABLE ( -- [0, cpu_count] range. unnormalized_utilization DOUBLE ) AS -SELECT * FROM sched_thread_utilization_per_period(time_from_s(1), $utid); \ No newline at end of file +SELECT * FROM cpu_thread_utilization_per_period(time_from_s(1), $utid); + +-- Aggregated CPU statistics for each thread. +CREATE PERFETTO TABLE cpu_cycles_per_thread( + -- Unique thread id + utid INT, + -- Sum of CPU millicycles + millicycles INT, + -- Sum of CPU megacycles + megacycles INT, + -- Total runtime duration + runtime INT, + -- Minimum CPU frequency in kHz + min_freq INT, + -- Maximum CPU frequency in kHz + max_freq INT, + -- Average CPU frequency in kHz + avg_freq INT +) AS +SELECT + utid, + cast_int!(SUM(dur * freq) / 1000) AS millicycles, + cast_int!(SUM(dur * freq) / 1000 / 1e9) AS megacycles, + SUM(dur) AS runtime, + MIN(freq) AS min_freq, + MAX(freq) AS max_freq, + cast_int!(SUM((dur * freq) / 1000) / SUM(dur / 1000)) AS avg_freq +FROM _cpu_freq_per_thread +GROUP BY utid; \ No newline at end of file diff --git a/src/trace_processor/perfetto_sql/stdlib/linux/cpu_idle.sql b/src/trace_processor/perfetto_sql/stdlib/linux/cpu_idle.sql deleted file mode 100644 index d4aeeb2d93..0000000000 --- a/src/trace_processor/perfetto_sql/stdlib/linux/cpu_idle.sql +++ /dev/null @@ -1,81 +0,0 @@ --- --- Copyright 2023 The Android Open Source Project --- --- Licensed under the Apache License, Version 2.0 (the "License"); --- you may not use this file except in compliance with the License. --- You may obtain a copy of the License at --- --- https://www.apache.org/licenses/LICENSE-2.0 --- --- Unless required by applicable law or agreed to in writing, software --- distributed under the License is distributed on an "AS IS" BASIS, --- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. --- See the License for the specific language governing permissions and --- limitations under the License. - --- CPU frequency counter per core. -CREATE PERFETTO VIEW _cpu_freq_counters -AS -SELECT - ts, - dur, - value AS freq_value, - cct.cpu -FROM experimental_counter_dur ecd -LEFT JOIN cpu_counter_track cct - ON ecd.track_id = cct.id -WHERE cct.name = 'cpufreq'; - --- CPU idle counter per core. -CREATE PERFETTO VIEW _cpu_idle_counters -AS -SELECT - ts, - dur, - -- Correct 4294967295 to -1 (both of them means an exit from the current state). - iif(value = 4294967295, -1, CAST(value AS int)) AS idle_value, - cct.cpu -FROM experimental_counter_dur ecd -LEFT JOIN cpu_counter_track cct - ON ecd.track_id = cct.id -WHERE cct.name = 'cpuidle'; - --- Combined cpu freq & idle counter -CREATE VIRTUAL TABLE _freq_idle_counters -USING - span_join(_cpu_freq_counters PARTITIONED cpu, _cpu_idle_counters PARTITIONED cpu); - --- Aggregates cpu idle statistics per core. -CREATE PERFETTO TABLE linux_cpu_idle_stats( - -- CPU core number. - cpu INT, - -- CPU idle state (C-states). - state INT, - -- The count of entering idle state. - count INT, - -- Total CPU core idle state duration in nanoseconds. - dur INT, - -- Average CPU core idle state duration in nanoseconds. - avg_dur INT, - -- Idle state percentage of non suspend time (C-states + P-states). - idle_percent FLOAT -) -AS -WITH -total AS ( - SELECT - cpu, - sum(dur) AS dur - FROM _freq_idle_counters - GROUP BY cpu -) -SELECT - cpu, - (idle_value + 1) AS state, - COUNT(idle_value) AS count, - SUM(dur) AS dur, - SUM(dur) / COUNT(idle_value) AS avg_dur, - SUM(dur) * 100.0 / (SELECT dur FROM total t WHERE t.cpu = ific.cpu) AS idle_percent -FROM _freq_idle_counters ific -WHERE idle_value >=0 -GROUP BY cpu, idle_value; diff --git a/src/trace_processor/perfetto_sql/stdlib/linux/memory/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/linux/memory/BUILD.gn new file mode 100644 index 0000000000..050d3876d9 --- /dev/null +++ b/src/trace_processor/perfetto_sql/stdlib/linux/memory/BUILD.gn @@ -0,0 +1,23 @@ +# 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("../../../../../../gn/perfetto_sql.gni") + +perfetto_sql_source_set("memory") { + sources = [ + "general.sql", + "high_watermark.sql", + "process.sql", + ] +} diff --git a/src/trace_processor/perfetto_sql/stdlib/linux/memory/general.sql b/src/trace_processor/perfetto_sql/stdlib/linux/memory/general.sql new file mode 100644 index 0000000000..372cbf9b45 --- /dev/null +++ b/src/trace_processor/perfetto_sql/stdlib/linux/memory/general.sql @@ -0,0 +1,30 @@ +-- +-- 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 VIEW _all_counters_per_process AS +SELECT + ts, + LEAD( + ts, 1, + (SELECT COALESCE(end_ts, trace_end()) + FROM process p WHERE p.upid = t.upid) + 1) + OVER (PARTITION BY track_id ORDER BY ts) - ts AS dur, + upid, + value, + track_id, + name +FROM counter c JOIN process_counter_track t +ON t.id = c.track_id +WHERE upid IS NOT NULL; \ No newline at end of file diff --git a/src/trace_processor/perfetto_sql/stdlib/linux/memory/high_watermark.sql b/src/trace_processor/perfetto_sql/stdlib/linux/memory/high_watermark.sql new file mode 100644 index 0000000000..b43a75c8f1 --- /dev/null +++ b/src/trace_processor/perfetto_sql/stdlib/linux/memory/high_watermark.sql @@ -0,0 +1,67 @@ +-- +-- 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 counters.intervals; +INCLUDE PERFETTO MODULE linux.memory.process; + +CREATE PERFETTO TABLE _memory_rss_high_watermark_per_process_table AS +WITH with_rss AS ( + SELECT + ts, + dur, + upid, + COALESCE(file_rss, 0) + COALESCE(anon_rss, 0) + COALESCE(shmem_rss, 0) AS rss + FROM _memory_rss_and_swap_per_process_table +), +high_watermark_as_counter AS ( +SELECT + ts, + MAX(rss) OVER (PARTITION BY upid ORDER BY ts) AS value, + -- `id` and `track_id` are hacks to use this table in + -- `counter_leading_intervals` macro. As `track_id` is using for looking + -- for duplicates, we are aliasing `upid` with it. `Id` is ignored by the macro. + upid AS track_id, + 0 AS id +FROM with_rss +) +SELECT ts, dur, track_id AS upid, cast_int!(value) AS rss_high_watermark +FROM counter_leading_intervals!(high_watermark_as_counter); + +-- For each process fetches the memory high watermark until or during +-- timestamp. +CREATE PERFETTO VIEW memory_rss_high_watermark_per_process +( + -- Timestamp + ts INT, + -- Duration + dur INT, + -- Upid of the process + upid INT, + -- Pid of the process + pid INT, + -- Name of the process + process_name STRING, + -- Maximum `rss` value until now + rss_high_watermark INT +) AS +SELECT + ts, + dur, + upid, + pid, + name AS process_name, + rss_high_watermark +FROM _memory_rss_high_watermark_per_process_table +JOIN process USING (upid); diff --git a/src/trace_processor/perfetto_sql/stdlib/linux/memory/process.sql b/src/trace_processor/perfetto_sql/stdlib/linux/memory/process.sql new file mode 100644 index 0000000000..771843c78f --- /dev/null +++ b/src/trace_processor/perfetto_sql/stdlib/linux/memory/process.sql @@ -0,0 +1,134 @@ +-- +-- 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 linux.memory.general; + +-- All memory counters tables. + +CREATE PERFETTO VIEW _anon_rss AS +SELECT + ts, + dur, + upid, + value AS anon_rss_val +FROM _all_counters_per_process +WHERE name = 'mem.rss.anon'; + +CREATE PERFETTO VIEW _file_rss AS +SELECT + ts, + dur, + upid, + value AS file_rss_val +FROM _all_counters_per_process +WHERE name = 'mem.rss.file'; + +CREATE PERFETTO VIEW _shmem_rss AS +SELECT + ts, + dur, + upid, + value AS shmem_rss_val +FROM _all_counters_per_process +WHERE name = 'mem.rss.shmem'; + +CREATE PERFETTO VIEW _swap AS +SELECT + ts, + dur, + upid, + value AS swap_val +FROM _all_counters_per_process +WHERE name = 'mem.swap'; + +-- Span joins + +CREATE VIRTUAL TABLE _anon_swap_sj +USING SPAN_OUTER_JOIN( + _anon_rss PARTITIONED upid, + _swap PARTITIONED upid); + +CREATE VIRTUAL TABLE _anon_swap_file_sj +USING SPAN_OUTER_JOIN( + _anon_swap_sj PARTITIONED upid, + _file_rss PARTITIONED upid +); + +CREATE VIRTUAL TABLE _rss_swap_sj +USING SPAN_OUTER_JOIN( + _anon_swap_file_sj PARTITIONED upid, + _shmem_rss PARTITIONED upid +); + +CREATE PERFETTO TABLE _memory_rss_and_swap_per_process_table AS +SELECT + ts, dur, upid, + cast_int!(anon_rss_val) AS anon_rss, + cast_int!(file_rss_val) AS file_rss, + cast_int!(shmem_rss_val) AS shmem_rss, + cast_int!(swap_val) AS swap +FROM _rss_swap_sj; + + +-- Memory metrics timeline for each process. +CREATE PERFETTO VIEW memory_rss_and_swap_per_process( + -- Timestamp + ts INT, + -- Duration + dur INT, + -- Upid of the process + upid INT, + -- Pid of the process + pid INT, + -- Name of the process + process_name STRING, + -- Anon RSS counter value + anon_rss INT, + -- File RSS counter value + file_rss INT, + -- Shared memory RSS counter value + shmem_rss INT, + -- Total RSS value. Sum of `anon_rss`, `file_rss` and `shmem_rss`. Returns + -- value even if one of the values is NULL. + rss INT, + -- Swap counter value + swap INT, + -- Sum or `anon_rss` and `swap`. Returns value even if one of the values is + -- NULL. + anon_rss_and_swap INT, + -- Sum or `rss` and `swap`. Returns value even if one of the values is NULL. + rss_and_swap INT +) AS +SELECT + ts, + dur, + upid, + pid, + name AS process_name, + anon_rss, + file_rss, + shmem_rss, + -- We do COALESCE only on `shmem_rss` and `swap`, as it can be expected all + -- process start to emit anon rss and file rss events (you'll need to at + -- least read code and have some memory to work with) - so the NULLs are real + -- values. But it is possible that you will never swap or never use shmem, + -- so those values are expected to often be NULLs, which shouldn't propagate + -- into the values like `anon_and_swap` or `rss`. + file_rss + anon_rss + COALESCE(shmem_rss, 0) AS rss, + swap, + anon_rss + COALESCE(swap, 0) AS anon_rss_and_swap, + anon_rss + file_rss + COALESCE(shmem_rss, 0) + COALESCE(swap, 0) AS rss_and_swap +FROM _memory_rss_and_swap_per_process_table +JOIN process USING (upid); diff --git a/src/trace_processor/perfetto_sql/stdlib/linux/perf/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/linux/perf/BUILD.gn new file mode 100644 index 0000000000..ca5843833a --- /dev/null +++ b/src/trace_processor/perfetto_sql/stdlib/linux/perf/BUILD.gn @@ -0,0 +1,19 @@ +# 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("../../../../../../gn/perfetto_sql.gni") + +perfetto_sql_source_set("perf") { + sources = [ "samples.sql" ] +} diff --git a/src/trace_processor/perfetto_sql/stdlib/linux/perf/samples.sql b/src/trace_processor/perfetto_sql/stdlib/linux/perf/samples.sql new file mode 100644 index 0000000000..1a22bc115e --- /dev/null +++ b/src/trace_processor/perfetto_sql/stdlib/linux/perf/samples.sql @@ -0,0 +1,41 @@ +-- +-- 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 callstacks.stack_profile; + +CREATE PERFETTO MACRO _linux_perf_callstacks_for_samples( + samples TableOrSubquery +) +RETURNS TableOrSubquery +AS +( + WITH metrics AS MATERIALIZED ( + SELECT + callsite_id, + COUNT() AS self_count + FROM $samples + GROUP BY callsite_id + ) + SELECT + c.id, + c.parent_id, + c.name, + c.mapping_name, + c.source_file, + c.line_number, + IFNULL(m.self_count, 0) AS self_count + FROM _callstacks_for_stack_profile_samples!(metrics) c + LEFT JOIN metrics m USING (callsite_id) +); 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 deleted file mode 100644 index f0cfae1063..0000000000 --- a/src/trace_processor/perfetto_sql/stdlib/memory/heap_graph_dominator_tree.sql +++ /dev/null @@ -1,166 +0,0 @@ --- --- 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 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 _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 --- "super root" node which connects all the roots of the forest into a single --- connected component, so that the dominator tree algorithm can be performed. -CREATE PERFETTO FUNCTION memory_heap_graph_super_root_fn() --- The assigned id of the "super root". -RETURNS INT AS -SELECT max(id) + 1 FROM heap_graph_object; - -CREATE PERFETTO VIEW _dominator_compatible_heap_graph AS -SELECT - ref.owner_id AS source_node_id, - 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 ref.id NOT IN _excluded_refs - AND ref.owned_id IS NOT NULL -UNION ALL -SELECT - (SELECT memory_heap_graph_super_root_fn()) as source_node_id, - id AS dest_node_id -FROM heap_graph_object -WHERE root_type IS NOT NULL; - -CREATE PERFETTO TABLE _heap_graph_dominator_tree AS -SELECT - node_id AS id, - dominator_node_id AS idom_id -FROM graph_dominator_tree!( - _dominator_compatible_heap_graph, - (SELECT memory_heap_graph_super_root_fn()) -) --- Excluding the imaginary root. -WHERE dominator_node_id IS NOT NULL --- Ordering by idom_id so queries below are faster when joining on idom_id. --- TODO(lalitm): support create index for Perfetto tables. -ORDER BY idom_id; - -CREATE PERFETTO TABLE _heap_graph_dominator_tree_depth AS -WITH RECURSIVE _tree_visitor(id, depth) AS ( - -- Let the super root have depth 0. - SELECT id, 1 AS depth - FROM _heap_graph_dominator_tree - WHERE idom_id IN (SELECT memory_heap_graph_super_root_fn()) - UNION ALL - SELECT child.id, parent.depth + 1 - FROM _heap_graph_dominator_tree child - JOIN _tree_visitor parent ON child.idom_id = parent.id -) -SELECT * FROM _tree_visitor -ORDER BY id; - --- A performance note: we need 3 memoize functions because EXPERIMENTAL_MEMOIZE --- limits the function to return only 1 int. --- This means the exact same "memoized dfs pass" on the tree is done 3 times, so --- it takes 3x the time taken by only doing 1 pass. Doing only 1 pass would be --- possible if EXPERIMENTAL_MEMOIZE could return more than 1 int. - -CREATE PERFETTO FUNCTION _subtree_obj_count(id INT) -RETURNS INT AS -SELECT 1 + IFNULL(( - SELECT - SUM(_subtree_obj_count(child.id)) - FROM _heap_graph_dominator_tree child - WHERE child.idom_id = $id -), 0); -SELECT EXPERIMENTAL_MEMOIZE('_subtree_obj_count'); - -CREATE PERFETTO FUNCTION _subtree_size_bytes(id INT) -RETURNS INT AS -SELECT ( - SELECT self_size - FROM heap_graph_object - WHERE heap_graph_object.id = $id -) + -IFNULL(( - SELECT - SUM(_subtree_size_bytes(child.id)) - FROM _heap_graph_dominator_tree child - WHERE child.idom_id = $id -), 0); -SELECT EXPERIMENTAL_MEMOIZE('_subtree_size_bytes'); - -CREATE PERFETTO FUNCTION _subtree_native_size_bytes(id INT) -RETURNS INT AS -SELECT ( - SELECT native_size - FROM heap_graph_object - WHERE heap_graph_object.id = $id -) + -IFNULL(( - SELECT - SUM(_subtree_native_size_bytes(child.id)) - FROM _heap_graph_dominator_tree child - WHERE child.idom_id = $id -), 0); -SELECT EXPERIMENTAL_MEMOIZE('_subtree_native_size_bytes'); - --- All reachable heap graph objects, their immediate dominators and summary of --- their dominated sets. --- The heap graph dominator tree is calculated by stdlib graphs.dominator_tree. --- Each reachable object is a node in the dominator tree, their immediate --- dominator is their parent node in the tree, and their dominated set is all --- their descendants in the tree. All size information come from the --- heap_graph_object prelude table. -CREATE PERFETTO TABLE memory_heap_graph_dominator_tree ( - -- Heap graph object id. - id INT, - -- Immediate dominator object id of the object. - idom_id INT, - -- Count of all objects dominated by this object, self inclusive. - dominated_obj_count INT, - -- Total self_size of all objects dominated by this object, self inclusive. - dominated_size_bytes INT, - -- Total native_size of all objects dominated by this object, self inclusive. - dominated_native_size_bytes INT, - -- Depth of the object in the dominator tree. Depth of root objects are 1. - depth INT -) AS -SELECT - t.id, - t.idom_id, - _subtree_obj_count(t.id) AS dominated_obj_count, - _subtree_size_bytes(t.id) AS dominated_size_bytes, - _subtree_native_size_bytes(t.id) AS dominated_native_size_bytes, - d.depth -FROM _heap_graph_dominator_tree t -JOIN _heap_graph_dominator_tree_depth d USING(id) -ORDER BY id; diff --git a/src/trace_processor/perfetto_sql/stdlib/cpu/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/metasql/BUILD.gn similarity index 87% rename from src/trace_processor/perfetto_sql/stdlib/cpu/BUILD.gn rename to src/trace_processor/perfetto_sql/stdlib/metasql/BUILD.gn index 28fc5a00d7..a82b2d287e 100644 --- a/src/trace_processor/perfetto_sql/stdlib/cpu/BUILD.gn +++ b/src/trace_processor/perfetto_sql/stdlib/metasql/BUILD.gn @@ -14,11 +14,9 @@ import("../../../../../gn/perfetto_sql.gni") -perfetto_sql_source_set("cpu") { +perfetto_sql_source_set("metasql") { sources = [ - "cpus.sql", - "freq.sql", - "idle.sql", - "size.sql", + "column_list.sql", + "table_list.sql", ] } diff --git a/src/trace_processor/perfetto_sql/stdlib/metasql/column_list.sql b/src/trace_processor/perfetto_sql/stdlib/metasql/column_list.sql new file mode 100644 index 0000000000..c78aab5fd6 --- /dev/null +++ b/src/trace_processor/perfetto_sql/stdlib/metasql/column_list.sql @@ -0,0 +1,34 @@ +-- +-- 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 MACRO _col_list_id(a ColumnName) +RETURNS _SqlFragment AS $a; + +-- Given a list of column names, applies an arbitrary macro to each column +-- and joins the result with a comma. +CREATE PERFETTO MACRO _metasql_map_join_column_list( + columns _ColumnNameList, + map_macro _Macro +) +RETURNS _SqlFragment +AS __intrinsic_token_map_join!($columns, $map_macro, __intrinsic_token_comma!()); + +-- Given a list of column names, removes the parentheses allowing the usage +-- of these in a select statement, window function etc. +CREATE PERFETTO MACRO _metasql_unparenthesize_column_list( + columns _ColumnNameList +) +RETURNS _SqlFragment +AS _metasql_map_join_column_list!($columns, _col_list_id); diff --git a/src/trace_processor/perfetto_sql/stdlib/metasql/table_list.sql b/src/trace_processor/perfetto_sql/stdlib/metasql/table_list.sql new file mode 100644 index 0000000000..32d327bfe8 --- /dev/null +++ b/src/trace_processor/perfetto_sql/stdlib/metasql/table_list.sql @@ -0,0 +1,33 @@ +-- +-- 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. + +-- Given a list of table names, applies an arbitrary macro to each table +-- and joins the result with a comma. +CREATE PERFETTO MACRO _metasql_map_join_table_list( + tables _TableNameList, + map_macro _Macro +) +RETURNS _SqlFragment +AS __intrinsic_token_map_join!($tables, $map_macro, __intrinsic_token_comma!()); + +-- Given a list of table names, applies an arbitrary macro to each table +-- and joins the result with a comma. +CREATE PERFETTO MACRO _metasql_map_join_table_list_with_capture( + tables _TableNameList, + map_macro _Macro, + args _ArgumentList +) +RETURNS _SqlFragment +AS __intrinsic_token_map_join_with_capture!($tables, $map_macro, $args, __intrinsic_token_comma!()); diff --git a/src/trace_processor/perfetto_sql/stdlib/prelude/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/prelude/BUILD.gn index 499c2e6dbd..596d327e51 100644 --- a/src/trace_processor/perfetto_sql/stdlib/prelude/BUILD.gn +++ b/src/trace_processor/perfetto_sql/stdlib/prelude/BUILD.gn @@ -18,6 +18,7 @@ perfetto_sql_source_set("prelude") { sources = [ "casts.sql", "slices.sql", + "tables_views.sql", "trace_bounds.sql", ] } diff --git a/src/trace_processor/perfetto_sql/stdlib/prelude/tables_views.sql b/src/trace_processor/perfetto_sql/stdlib/prelude/tables_views.sql new file mode 100644 index 0000000000..d8e22fcabd --- /dev/null +++ b/src/trace_processor/perfetto_sql/stdlib/prelude/tables_views.sql @@ -0,0 +1,379 @@ +-- +-- 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. + +-- Contains information about the CPUs on the device this trace was taken on. +CREATE PERFETTO VIEW cpu ( + -- Unique identifier for this CPU. Identical to |ucpu|, prefer using |ucpu| + -- instead. + id UINT, + -- Unique identifier for this CPU. Isn't equal to |cpu| for remote machines + -- and is equal to |cpu| for the host machine. + ucpu UINT, + -- The 0-based CPU core identifier. + cpu UINT, + -- The name of the "most-specific" child table containing this row. + type STRING, + -- The cluster id is shared by CPUs in the same cluster. + cluster_id UINT, + -- A string describing this core. + processor STRING, + -- Machine identifier, non-null for CPUs on a remote machine. + machine_id UINT, + -- Capacity of a CPU of a device, a metric which indicates the + -- relative performance of a CPU on a device + -- For details see: + -- https://www.kernel.org/doc/Documentation/devicetree/bindings/arm/cpu-capacity.txt + capacity UINT +) AS +SELECT + id, + id AS ucpu, + cpu, + type AS type, + cluster_id, + processor, + machine_id, + capacity +FROM + __intrinsic_cpu +WHERE + cpu IS NOT NULL; + +-- Contains the frequency values that the CPUs on the device are capable of +-- running at. +CREATE PERFETTO VIEW cpu_available_frequencies ( + -- Unique identifier for this cpu frequency. + id UINT, + -- The CPU for this frequency, meaningful only in single machine traces. + -- For multi-machine, join with the `cpu` table on `ucpu` to get the CPU + -- identifier of each machine. + cpu UINT, + -- CPU frequency in KHz. + freq UINT, + -- The CPU that the slice executed on (meaningful only in single machine + -- traces). For multi-machine, join with the `cpu` table on `ucpu` to get the + -- CPU identifier of each machine. + ucpu UINT +) AS +SELECT + id, + ucpu AS cpu, + freq, + ucpu +FROM + __intrinsic_cpu_freq; + +-- This table holds slices with kernel thread scheduling information. These +-- slices are collected when the Linux "ftrace" data source is used with the +-- "sched/switch" and "sched/wakeup*" events enabled. +-- +-- The rows in this table will always have a matching row in the |thread_state| +-- table with |thread_state.state| = 'Running' +CREATE PERFETTO VIEW sched_slice ( + -- Unique identifier for this scheduling slice. + id UINT, + -- The name of the "most-specific" child table containing this row. + type STRING, + -- The timestamp at the start of the slice (in nanoseconds). + ts LONG, + -- The duration of the slice (in nanoseconds). + dur LONG, + -- The CPU that the slice executed on (meaningful only in single machine + -- traces). For multi-machine, join with the `cpu` table on `ucpu` to get the + -- CPU identifier of each machine. + cpu UINT, + -- The thread's unique id in the trace. + utid UINT, + -- A string representing the scheduling state of the kernel + -- thread at the end of the slice. The individual characters in + -- the string mean the following: R (runnable), S (awaiting a + -- wakeup), D (in an uninterruptible sleep), T (suspended), + -- t (being traced), X (exiting), P (parked), W (waking), + -- I (idle), N (not contributing to the load average), + -- K (wakeable on fatal signals) and Z (zombie, awaiting + -- cleanup). + end_state STRING, + -- The kernel priority that the thread ran at. + priority INT, + -- The unique CPU identifier that the slice executed on. + ucpu UINT +) AS +SELECT + id, + type, + ts, + dur, + ucpu AS cpu, + utid, + end_state, + priority, + ucpu +FROM + __intrinsic_sched_slice; + +-- This table contains the scheduling state of every thread on the system during +-- the trace. +-- +-- The rows in this table which have |state| = 'Running', will have a +-- corresponding row in the |sched_slice| table. +CREATE PERFETTO VIEW thread_state ( + -- Unique identifier for this thread state. + id UINT, + -- The name of the "most-specific" child table containing this row. + type STRING, + -- The timestamp at the start of the slice (in nanoseconds). + ts LONG, + -- The duration of the slice (in nanoseconds). + dur LONG, + -- The CPU that the thread executed on (meaningful only in single machine + -- traces). For multi-machine, join with the `cpu` table on `ucpu` to get the + -- CPU identifier of each machine. + cpu UINT, + -- The thread's unique id in the trace. + utid UINT, + -- The scheduling state of the thread. Can be "Running" or any of the states + -- described in |sched_slice.end_state|. + state STRING, + -- Indicates whether this thread was blocked on IO. + io_wait UINT, + -- The function in the kernel this thread was blocked on. + blocked_function STRING, + -- The unique thread id of the thread which caused a wakeup of this thread. + waker_utid UINT, + -- The unique thread state id which caused a wakeup of this thread. + waker_id UINT, + -- Whether the wakeup was from interrupt context or process context. + irq_context UINT, + -- The unique CPU identifier that the thread executed on. + ucpu UINT +) AS +SELECT + id, + type, + ts, + dur, + ucpu AS cpu, + utid, + state, + io_wait, + blocked_function, + waker_utid, + waker_id, + irq_context, + ucpu +FROM + __intrinsic_thread_state; + +-- Contains 'raw' events from the trace for some types of events. This table +-- only exists for debugging purposes and should not be relied on in production +-- usecases (i.e. metrics, standard library etc.) +CREATE PERFETTO VIEW raw ( + -- Unique identifier for this raw event. + id UINT, + -- The name of the "most-specific" child table containing this row. + type STRING, + -- The timestamp of this event. + ts LONG, + -- The name of the event. For ftrace events, this will be the ftrace event + -- name. + name STRING, + -- The CPU this event was emitted on (meaningful only in single machine + -- traces). For multi-machine, join with the `cpu` table on `ucpu` to get the + -- CPU identifier of each machine. + cpu UINT, + -- The thread this event was emitted on. + utid UINT, + -- The set of key/value pairs associated with this event. + arg_set_id UINT, + -- Ftrace event flags for this event. Currently only emitted for sched_waking + -- events. + common_flags UINT, + -- The unique CPU identifier that this event was emitted on. + ucpu UINT +) AS +SELECT + id, + type, + ts, + name, + ucpu AS cpu, + utid, + arg_set_id, + common_flags, + ucpu +FROM + __intrinsic_raw; + +-- Contains all the ftrace events in the trace. This table exists only for +-- debugging purposes and should not be relied on in production usecases (i.e. +-- metrics, standard library etc). Note also that this table might be empty if +-- raw ftrace parsing has been disabled. +CREATE PERFETTO VIEW ftrace_event ( + -- Unique identifier for this ftrace event. + id UINT, + -- The name of the "most-specific" child table containing this row. + type STRING, + -- The timestamp of this event. + ts LONG, + -- The ftrace event name. + name STRING, + -- The CPU this event was emitted on (meaningful only in single machine + -- traces). For multi-machine, join with the `cpu` table on `ucpu` to get the + -- CPU identifier of each machine. + cpu UINT, + -- The thread this event was emitted on. + utid UINT, + -- The set of key/value pairs associated with this event. + arg_set_id UINT, + -- Ftrace event flags for this event. Currently only emitted for + -- sched_waking events. + common_flags UINT, + -- The unique CPU identifier that this event was emitted on. + ucpu UINT +) AS +SELECT + id, + type, + ts, + name, + ucpu AS cpu, + utid, + arg_set_id, + common_flags, + ucpu +FROM + __intrinsic_ftrace_event; + +-- The sched_slice table with the upid column. +CREATE PERFETTO VIEW experimental_sched_upid ( + -- Unique identifier for this scheduling slice. + id UINT, + -- The name of the "most-specific" child table containing this row. + type STRING, + -- The timestamp at the start of the slice (in nanoseconds). + ts LONG, + -- The duration of the slice (in nanoseconds). + dur LONG, + -- The CPU that the slice executed on (meaningful only in single machine + -- traces). For multi-machine, join with the `cpu` table on `ucpu` to get the + -- CPU identifier of each machine. + cpu UINT, + -- The thread's unique id in the trace. + utid UINT, + -- A string representing the scheduling state of the kernel thread at the end + -- of the slice. The individual characters in the string mean the following: R + -- (runnable), S (awaiting a wakeup), D (in an uninterruptible sleep), T + -- (suspended), t (being traced), X (exiting), P (parked), W (waking), I + -- (idle), N (not contributing to the load average), K (wakeable on fatal + -- signals) and Z (zombie, awaiting cleanup). + end_state STRING, + -- The kernel priority that the thread ran at. + priority INT, + -- The unique CPU identifier that the slice executed on. + ucpu UINT, + -- The process's unique id in the trace. + upid UINT +) AS +SELECT + id, + type, + ts, + dur, + ucpu AS cpu, + utid, + end_state, + priority, + ucpu, + upid +FROM + __intrinsic_sched_upid; + +-- Tracks which are associated to a single CPU. +CREATE PERFETTO VIEW cpu_track ( + -- Unique identifier for this cpu track. + id UINT, + -- The name of the "most-specific" child table containing this row. + type STRING, + -- Name of the track. + name STRING, + -- The track which is the "parent" of this track. Only non-null for tracks + -- created using Perfetto's track_event API. + parent_id UINT, + -- Args for this track which store information about "source" of this track in + -- the trace. For example: whether this track orginated from atrace, Chrome + -- tracepoints etc. + source_arg_set_id UINT, + -- Machine identifier, non-null for tracks on a remote machine. + machine_id UINT, + -- The CPU that the track is associated with (meaningful only in single + -- machine traces). For multi-machine, join with the `cpu` table on `ucpu` to + -- get the CPU identifier of each machine. + cpu UINT, + -- The unique CPU identifier that this track is associated with. + ucpu UINT +) AS +SELECT + id, + type, + name, + parent_id, + source_arg_set_id, + machine_id, + ucpu AS cpu, + ucpu +FROM + __intrinsic_cpu_track; + +-- Tracks containing counter-like events associated to a CPU. +CREATE PERFETTO VIEW cpu_counter_track ( + -- Unique identifier for this cpu counter track. + id UINT, + -- The name of the "most-specific" child table containing this row. + type STRING, + -- Name of the track. + name STRING, + -- The track which is the "parent" of this track. Only non-null for tracks + -- created using Perfetto's track_event API. + parent_id UINT, + -- Args for this track which store information about "source" of this track in + -- the trace. For example: whether this track orginated from atrace, Chrome + -- tracepoints etc. + source_arg_set_id UINT, + -- Machine identifier, non-null for tracks on a remote machine. + machine_id UINT, + -- The units of the counter. This column is rarely filled. + unit STRING, + -- The description for this track. For debugging purposes only. + description STRING, + -- The CPU that the track is associated with (meaningful only in single + -- machine traces). For multi-machine, join with the `cpu` table on `ucpu` to + -- get the CPU identifier of each machine. + cpu UINT, + -- The unique CPU identifier that this track is associated with. + ucpu UINT +) AS +SELECT + id, + type, + name, + parent_id, + source_arg_set_id, + machine_id, + unit, + description, + ucpu AS cpu, + ucpu +FROM + __intrinsic_cpu_counter_track; diff --git a/src/trace_processor/perfetto_sql/stdlib/sched/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/sched/BUILD.gn index 916892692f..d7e96087b7 100644 --- a/src/trace_processor/perfetto_sql/stdlib/sched/BUILD.gn +++ b/src/trace_processor/perfetto_sql/stdlib/sched/BUILD.gn @@ -15,7 +15,6 @@ import("../../../../../gn/perfetto_sql.gni") perfetto_sql_source_set("sched") { - deps = [ "utilization" ] sources = [ "runnable.sql", "states.sql", diff --git a/src/trace_processor/perfetto_sql/stdlib/sched/thread_executing_span.sql b/src/trace_processor/perfetto_sql/stdlib/sched/thread_executing_span.sql index e3a92e2d68..2224617985 100644 --- a/src/trace_processor/perfetto_sql/stdlib/sched/thread_executing_span.sql +++ b/src/trace_processor/perfetto_sql/stdlib/sched/thread_executing_span.sql @@ -14,7 +14,10 @@ -- limitations under the License. -- +INCLUDE PERFETTO MODULE graphs.critical_path; INCLUDE PERFETTO MODULE graphs.search; +INCLUDE PERFETTO MODULE intervals.overlap; +INCLUDE PERFETTO MODULE intervals.intersect; -- A 'thread_executing_span' is thread_state span starting with a runnable slice -- until the next runnable slice that's woken up by a process (as opposed @@ -42,12 +45,13 @@ SELECT thread_state.state, thread_state.utid, thread_state.waker_id, - thread_state.waker_utid + thread_state.waker_utid, + IIF(thread_state.irq_context = 0 OR thread_state.irq_context IS NULL, + IFNULL(thread_state.io_wait, 0), 1) AS is_irq FROM thread_state WHERE thread_state.dur != -1 - AND thread_state.waker_utid IS NOT NULL - AND (thread_state.irq_context = 0 OR thread_state.irq_context IS NULL); + AND thread_state.waker_id IS NOT NULL; -- Similar to |_runnable_state| but finds the first runnable state at thread. CREATE PERFETTO TABLE _first_runnable_state @@ -66,14 +70,15 @@ SELECT thread_state.state, thread_state.utid, thread_state.waker_id, - thread_state.waker_utid + thread_state.waker_utid, + IIF(thread_state.irq_context = 0 OR thread_state.irq_context IS NULL, + IFNULL(thread_state.io_wait, 0), 1) AS is_irq FROM thread_state JOIN first_state USING (id) WHERE thread_state.dur != -1 - AND thread_state.state = 'R' - AND (thread_state.irq_context = 0 OR thread_state.irq_context IS NULL); + AND thread_state.state = 'R'; -- -- Finds all sleep states including interruptible (S) and uninterruptible (D). @@ -155,7 +160,8 @@ WITH r.utid AS utid, r.waker_id, r.waker_utid, - s.ts AS prev_end_ts + s.ts AS prev_end_ts, + is_irq FROM _runnable_state r JOIN _sleep_state s ON s.utid = r.utid AND (s.ts + s.dur = r.ts) @@ -168,15 +174,14 @@ WITH r.utid AS utid, r.waker_id, r.waker_utid, - NULL AS prev_end_ts + NULL AS prev_end_ts, + is_irq FROM _first_runnable_state r LEFT JOIN _first_sleep_state s ON s.utid = r.utid ) SELECT - all_wakeups.*, - LAG(id) OVER (PARTITION BY utid ORDER BY ts) AS prev_id, - IFNULL(LEAD(prev_end_ts) OVER (PARTITION BY utid ORDER BY ts), thread_end.end_ts) AS end_ts + all_wakeups.*, thread_end.end_ts AS thread_end_ts FROM all_wakeups LEFT JOIN _thread_end_ts thread_end USING (utid); @@ -203,189 +208,211 @@ SELECT id, waker_id, utid, state FROM _runnable_state SELECT id, waker_id FROM y WHERE state = 'Running' ORDER BY waker_id; -- --- Builds the parent-child chain from all thread_executing_spans. The parent is the waker and --- child is the wakee. +-- Builds the waker and prev relationships for all thread_executing_spans. -- --- Note that this doesn't include the roots. We'll compute the roots below. --- This two step process improves performance because it's more efficient to scan --- parent and find a child between than to scan child and find the parent it lies between. CREATE PERFETTO TABLE _wakeup_graph AS -SELECT - _wakeup_map.id AS waker_id, - prev_id, - prev_end_ts, - _wakeup.id AS id, - _wakeup.ts AS ts, - _wakeup.end_ts, - IIF(_wakeup.state IS NULL OR _wakeup.state = 'S', 0, 1) AS is_kernel, - _wakeup.utid, - _wakeup.state, - _wakeup.blocked_function -FROM _wakeup -JOIN _wakeup_map USING(waker_id) -ORDER BY id; - --- The inverse of thread_executing_spans. All the sleeping periods between thread_executing_spans. -CREATE PERFETTO TABLE _sleep -AS WITH - x AS ( + _wakeup_events AS ( SELECT - id, - ts, - prev_end_ts, utid, - state, - blocked_function - FROM _wakeup_graph + thread_end_ts, + IIF(is_irq, 'IRQ', state) AS idle_state, + blocked_function AS idle_reason, + _wakeup.id, + IIF(is_irq, NULL, _wakeup_map.id) AS waker_id, + _wakeup.ts, + prev_end_ts AS idle_ts, + IIF(is_irq OR _wakeup_map.id IS NULL OR (state IS NOT NULL AND state != 'S'), 1, 0) + AS is_idle_reason_self + FROM _wakeup + LEFT JOIN _wakeup_map + USING (waker_id) ) SELECT - ts - prev_end_ts AS dur, - prev_end_ts AS ts, - id AS root_node_id, - utid AS critical_path_utid, - id AS critical_path_id, - ts - prev_end_ts AS critical_path_blocked_dur, - state AS critical_path_blocked_state, - blocked_function AS critical_path_blocked_function -FROM x -WHERE ts IS NOT NULL; + utid, + id, + waker_id, + ts, + idle_state, + idle_reason, + ts - idle_ts AS idle_dur, + is_idle_reason_self, + LAG(id) OVER (PARTITION BY utid ORDER BY ts) AS prev_id, + LEAD(id) OVER (PARTITION BY utid ORDER BY ts) AS next_id, + IFNULL(LEAD(idle_ts) OVER (PARTITION BY utid ORDER BY ts), thread_end_ts) - ts AS dur, + LEAD(is_idle_reason_self) OVER (PARTITION BY utid ORDER BY ts) AS is_next_idle_reason_self +FROM _wakeup_events +ORDER BY id; + +-- View of all the edges for the userspace critical path. +CREATE PERFETTO VIEW _wakeup_userspace_edges +AS +SELECT + id AS source_node_id, + COALESCE(IIF(is_idle_reason_self, prev_id, waker_id), id) AS dest_node_id, + id - COALESCE(IIF(is_idle_reason_self, prev_id, waker_id), id) AS edge_weight +FROM _wakeup_graph; --- Given a set of critical paths identified by their |root_node_ids|, flattens --- the critical path tasks such that there are no overlapping intervals. The end of a --- task in the critical path is the start of the following task in the critical path. -CREATE PERFETTO MACRO _flatten_critical_path_tasks(_critical_path_table TableOrSubquery) -RETURNS TableOrSubquery +-- View of all the edges for the kernel critical path. +CREATE PERFETTO VIEW _wakeup_kernel_edges +AS +SELECT + id AS source_node_id, + COALESCE(waker_id, id) AS dest_node_id, + id - COALESCE(waker_id, id) AS edge_weight +FROM _wakeup_graph; + +-- View of the relevant timestamp and intervals for all nodes in the critical path. +CREATE PERFETTO VIEW _wakeup_intervals +AS +SELECT id, ts, dur, idle_dur FROM _wakeup_graph; + +-- Converts a table with columns to a unique set of wakeup roots that +-- completely cover the time intervals. +CREATE PERFETTO MACRO _intervals_to_roots(_source_table TableOrSubQuery, + _node_table TableOrSubQuery) +RETURNS TableOrSubQuery AS ( - WITH - x AS ( + WITH _interval_to_root_nodes AS ( + SELECT * FROM $_node_table + ), + _source AS ( + SELECT * FROM $_source_table + ), + _thread_bounds AS ( + SELECT utid, MIN(ts) AS min_start, MAX(ts) AS max_start + FROM _interval_to_root_nodes + GROUP BY utid + ), + _start AS ( + SELECT + _interval_to_root_nodes.utid, + MAX(_interval_to_root_nodes.id) AS _start_id, + _source.ts, + _source.dur + FROM _interval_to_root_nodes + JOIN _thread_bounds USING (utid) + JOIN _source + ON _source.utid = _interval_to_root_nodes.utid + AND MAX(_source.ts, min_start) >= _interval_to_root_nodes.ts + GROUP BY _source.ts, _source.utid + ), + _end AS ( SELECT - LEAD(ts) OVER (PARTITION BY root_node_id ORDER BY node_id) AS ts, - node_id, - ts AS node_ts, - root_node_id, - utid AS node_utid, - _wakeup_graph.prev_end_ts - FROM $_critical_path_table - JOIN _wakeup_graph - ON node_id = id + _interval_to_root_nodes.utid, + MIN(_interval_to_root_nodes.id) AS _end_id, + _source.ts, + _source.dur + FROM _interval_to_root_nodes + JOIN _thread_bounds USING (utid) + JOIN _source + ON _source.utid = _interval_to_root_nodes.utid + AND MIN((_source.ts + _source.dur), max_start) <= _interval_to_root_nodes.ts + GROUP BY _source.ts, _source.utid + ), + _bound AS ( + SELECT _start.utid, _start.ts, _start.dur, _start_id, _end_id + FROM _start + JOIN _end + ON _start.ts = _end.ts AND _start.dur = _end.dur AND _start.utid = _end.utid ) - SELECT node_ts AS ts, root_node_id, node_id, ts - node_ts AS dur, node_utid, prev_end_ts FROM x + SELECT DISTINCT id AS root_node_id, id - COALESCE(prev_id, id) AS capacity + FROM _bound + JOIN _interval_to_root_nodes + ON _interval_to_root_nodes.id BETWEEN _start_id AND _end_id + AND _interval_to_root_nodes.utid = _bound.utid ); --- Converts a table with columns to a unique set of wakeup roots that --- completely cover the time intervals. -CREATE PERFETTO MACRO _intervals_to_roots(source_table TableOrSubQuery) +-- Adjusts the userspace critical path such that any interval that includes a kernel stall +-- gets the next id, the root id of the kernel critical path. This ensures that the merge +-- step associates the userspace critical path and kernel critical path on the same interval +-- correctly. +CREATE PERFETTO MACRO _critical_path_userspace_adjusted(_critical_path_table TableOrSubQuery, + _node_table TableOrSubQuery) RETURNS TableOrSubQuery AS ( - WITH source AS ( - SELECT * FROM $source_table - ), thread_bounds AS ( - SELECT utid, MIN(ts) AS min_start, MAX(ts) AS max_start FROM _wakeup_graph GROUP BY utid - ), start AS ( SELECT - _wakeup_graph.utid, max(_wakeup_graph.id) AS start_id, source.ts, source.dur - FROM _wakeup_graph - JOIN thread_bounds - USING (utid) - JOIN source - ON source.utid = _wakeup_graph.utid AND MAX(source.ts, min_start) >= _wakeup_graph.ts - GROUP BY source.ts, source.utid - ), end AS ( - SELECT - _wakeup_graph.utid, min(_wakeup_graph.id) AS end_id, source.ts, source.dur - FROM _wakeup_graph - JOIN thread_bounds - USING (utid) - JOIN source ON source.utid = _wakeup_graph.utid - AND MIN((source.ts + source.dur), max_start) <= _wakeup_graph.ts - GROUP BY source.ts, source.utid - ), bound AS ( - SELECT start.utid, start.ts, start.dur, start_id, end_id - FROM start - JOIN end ON start.ts = end.ts AND start.dur = end.dur AND start.utid = end.utid - ) - SELECT DISTINCT _wakeup_graph.id FROM bound - JOIN _wakeup_graph ON _wakeup_graph.id BETWEEN start_id AND end_id + cr.root_id, + cr.root_id AS parent_id, + IIF(node.is_next_idle_reason_self, node.next_id, cr.id) AS id, + cr.ts, + cr.dur + FROM (SELECT * FROM $_critical_path_table) cr + JOIN $_node_table node + USING (id) ); --- Flattens overlapping tasks within a critical path and flattens overlapping critical paths. -CREATE PERFETTO MACRO _flatten_critical_paths(critical_path_table TableOrSubquery, sleeping_table TableOrSubquery) -RETURNS TableOrSubquery +-- Adjusts the start and end of the kernel critical path such that it is completely bounded within +-- its corresponding userspace critical path. +CREATE PERFETTO MACRO _critical_path_kernel_adjusted(_userspace_critical_path_table TableOrSubQuery, + _kernel_critical_path_table TableOrSubQuery, + _node_table TableOrSubQuery) +RETURNS TableOrSubQuery AS ( - WITH - span_starts AS ( - SELECT - cr.node_utid AS utid, - MAX(cr.ts, sleep.ts) AS ts, - sleep.ts + sleep.dur AS sleep_end_ts, - cr.ts + cr.dur AS cr_end_ts, - cr.node_id AS id, - cr.root_node_id AS root_id, - cr.prev_end_ts AS prev_end_ts, - critical_path_utid, - critical_path_id, - critical_path_blocked_dur, - critical_path_blocked_state, - critical_path_blocked_function - FROM - _flatten_critical_path_tasks!($critical_path_table) cr - JOIN $sleeping_table sleep - USING (root_node_id) - ) - SELECT - ts, - MIN(cr_end_ts, sleep_end_ts) - ts AS dur, - utid, - id, - root_id, - prev_end_ts, - critical_path_utid, - critical_path_id, - critical_path_blocked_dur, - critical_path_blocked_state, - critical_path_blocked_function - FROM span_starts - WHERE MIN(sleep_end_ts, cr_end_ts) - ts > 0 + SELECT + kernel_cr.root_id, + kernel_cr.root_id AS parent_id, + kernel_cr.id, + MAX(kernel_cr.ts, userspace_cr.ts) AS ts, + MIN(kernel_cr.ts + kernel_cr.dur, userspace_cr.ts + userspace_cr.dur) + - MAX(kernel_cr.ts, userspace_cr.ts) AS dur + FROM $_kernel_critical_path_table kernel_cr + JOIN $_node_table node + ON kernel_cr.parent_id = node.id + JOIN $_userspace_critical_path_table userspace_cr + ON userspace_cr.id = kernel_cr.parent_id AND userspace_cr.root_id = kernel_cr.root_id ); --- Generates a critical path. -CREATE PERFETTO MACRO _critical_path( - graph_table TableOrSubquery, root_table TableOrSubquery, sleeping_table TableOrSubquery) -RETURNS TableOrSubquery +-- Merge the kernel and userspace critical path such that the corresponding kernel critical path +-- has priority over userpsace critical path it overlaps. +CREATE PERFETTO MACRO _critical_path_merged(_userspace_critical_path_table TableOrSubQuery, + _kernel_critical_path_table TableOrSubQuery, + _node_table TableOrSubQuery) +RETURNS TableOrSubQuery AS ( - WITH - critical_path AS ( - SELECT * FROM graph_reachable_weight_bounded_dfs !($graph_table, $root_table, 1) +WITH _userspace_critical_path AS ( + SELECT DISTINCT * + FROM _critical_path_userspace_adjusted!( + $_userspace_critical_path_table, + $_node_table) + ), + _merged_critical_path AS ( + SELECT * FROM _userspace_critical_path + UNION ALL + SELECT DISTINCT * + FROM _critical_path_kernel_adjusted!( + _userspace_critical_path, + $_kernel_critical_path_table, + $_node_table) + WHERE id != parent_id + ), + _roots_critical_path AS ( + SELECT root_id, MIN(ts) AS root_ts, MAX(ts + dur) - MIN(ts) AS root_dur + FROM _userspace_critical_path + GROUP BY root_id + ), + _roots_and_merged_critical_path AS ( + SELECT + root_id, + root_ts, + root_dur, + parent_id, + id, + ts, + dur + FROM _merged_critical_path + JOIN _roots_critical_path USING(root_id) ) - SELECT - ts, - dur, - root_id, - id, - utid, - critical_path_utid, - critical_path_id, - critical_path_blocked_dur, - critical_path_blocked_state, - critical_path_blocked_function - FROM _flatten_critical_paths!(critical_path, $sleeping_table) - UNION ALL - -- Add roots - SELECT - ts, - end_ts - ts AS dur, - id AS root_id, - id, - utid, - utid AS critical_path_utid, - NULL AS critical_path_id, - NULL AS critical_path_blocked_dur, - NULL AS critical_path_blocked_state, - NULL AS critical_path_blocked_function - FROM $root_table - ORDER BY root_id + SELECT + flat.root_id, + flat.id, + flat.ts, + flat.dur + FROM + _intervals_flatten!(_roots_and_merged_critical_path) flat + WHERE flat.dur > 0 + GROUP BY flat.root_id, flat.ts ); -- Generates the critical path for only the set of roots passed in. @@ -394,96 +421,110 @@ AS ( -- binder transactions. It might be more efficient to generate the _critical_path -- for the entire trace, see _thread_executing_span_critical_path_all, but for a -- per-process susbset of binder txns for instance, this is likely faster. -CREATE PERFETTO MACRO _critical_path_by_roots(roots_table TableOrSubQuery) +CREATE PERFETTO MACRO _critical_path_by_roots(_roots_table TableOrSubQuery, + _node_table TableOrSubQuery) RETURNS TableOrSubQuery AS ( - WITH roots AS ( - SELECT * FROM $roots_table - ), root_bounds AS ( - SELECT MIN(id) AS min_root_id, MAX(id) AS max_root_id FROM roots - ), wakeup_bounds AS ( - SELECT COALESCE(_wakeup_graph.prev_id, min_root_id) AS min_wakeup, max_root_id AS max_wakeup - FROM root_bounds - JOIN _wakeup_graph ON id = min_root_id - ) SELECT - id, - ts, - dur, - utid, - critical_path_id, - critical_path_blocked_dur, - critical_path_blocked_state, - critical_path_blocked_function, - critical_path_utid - FROM - _critical_path - !( - ( - SELECT - id AS source_node_id, - COALESCE(waker_id, id) AS dest_node_id, - id - COALESCE(waker_id, id) AS edge_weight - FROM _wakeup_graph - JOIN wakeup_bounds WHERE id BETWEEN min_wakeup AND max_wakeup - ), + WITH _userspace_critical_path_by_roots AS ( + SELECT * + FROM + _critical_path_intervals + !(_wakeup_userspace_edges, + $_roots_table, + _wakeup_intervals) + ), + _kernel_nodes AS ( + SELECT id, root_id FROM _userspace_critical_path_by_roots + JOIN $_node_table node USING (id) WHERE is_idle_reason_self = 1 + ), + _kernel_critical_path_by_roots AS ( + SELECT _kernel_nodes.root_id, cr.root_id AS parent_id, cr.id, cr.ts, cr.dur + FROM + _critical_path_intervals + !(_wakeup_kernel_edges, ( - SELECT - _wakeup_graph.id AS root_node_id, - _wakeup_graph.id - COALESCE(prev_id, _wakeup_graph.id) AS root_target_weight, - id, - ts, - end_ts, - utid - FROM _wakeup_graph - JOIN (SELECT * FROM roots) USING (id) + SELECT graph.id AS root_node_id, graph.id - COALESCE(graph.prev_id, graph.id) AS capacity + FROM _kernel_nodes + JOIN _wakeup_graph graph USING(id) ), - _sleep)); + _wakeup_intervals) + cr + JOIN _kernel_nodes + ON _kernel_nodes.id = cr.root_id + ) SELECT * FROM _critical_path_merged!( + _userspace_critical_path_by_roots, + _kernel_critical_path_by_roots, + $_node_table) +); -- Generates the critical path for only the time intervals for the utids given. -- Currently expensive because of naive interval_intersect implementation. -- Prefer _critical_paths_by_roots for performance. This is useful for a small -- set of intervals, e.g app startups in a trace. -CREATE PERFETTO MACRO _critical_path_by_intervals(intervals_table TableOrSubQuery) +CREATE PERFETTO MACRO _critical_path_by_intervals(_intervals_table TableOrSubQuery, + _node_table TableOrSubQuery) RETURNS TableOrSubQuery AS ( -WITH span_starts AS ( + WITH _nodes AS ( + SELECT * FROM $_node_table + ), _intervals AS ( SELECT - id, - MAX(span.ts, intervals.ts) AS ts, - MIN(span.ts + span.dur, intervals.ts + intervals.dur) AS end_ts, - span.utid, - critical_path_id, - critical_path_blocked_dur, - critical_path_blocked_state, - critical_path_blocked_function, - critical_path_utid - FROM _critical_path_by_roots!(_intervals_to_roots!($intervals_table)) span - -- TODO(zezeozue): Replace with interval_intersect when partitions are supported - JOIN (SELECT * FROM $intervals_table) intervals ON span.critical_path_utid = intervals.utid - AND ((span.ts BETWEEN intervals.ts AND intervals.ts + intervals.dur) - OR (intervals.ts BETWEEN span.ts AND span.ts + span.dur)) -) SELECT - id, + ROW_NUMBER() OVER(ORDER BY ts) AS id, ts, - end_ts - ts AS dur, - utid, - critical_path_id, - critical_path_blocked_dur, - critical_path_blocked_state, - critical_path_blocked_function, - critical_path_utid - FROM span_starts); + dur, + utid AS root_utid + FROM $_intervals_table + ), _critical_path AS ( + SELECT + ROW_NUMBER() OVER(ORDER BY ts) AS id, + root_id, + id AS cr_id, + ts, + dur + FROM _critical_path_by_roots!( + _intervals_to_roots!($_intervals_table, $_node_table), + _nodes) + ), _span AS ( + SELECT + _root_nodes.utid AS root_utid, + _nodes.utid, + cr.root_id, + cr.cr_id, + cr.id, + cr.ts, + cr.dur + FROM _critical_path cr + JOIN _nodes _root_nodes ON _root_nodes.id = cr.root_id + JOIN _nodes ON _nodes.id = cr.cr_id + ) SELECT DISTINCT + _span.root_utid, + _span.utid, + _span.root_id, + _span.cr_id AS id, + ii.ts, + ii.dur, + _intervals.ts AS interval_ts, + _intervals.dur AS interval_dur + FROM _interval_intersect!((_span, _intervals), (root_utid)) ii + JOIN _span ON _span.id = ii.id_0 + JOIN _intervals ON _intervals.id = ii.id_1 +); -- Generates the critical path for a given utid over the interval. -- The duration of a thread executing span in the critical path is the range between the -- start of the thread_executing_span and the start of the next span in the critical path. CREATE PERFETTO FUNCTION _thread_executing_span_critical_path( -- Utid of the thread to compute the critical path for. - critical_path_utid INT, + root_utid INT, -- Timestamp. ts LONG, -- Duration. dur LONG) RETURNS TABLE( + -- Thread Utid the critical path was filtered to. + root_utid INT, + -- Id of thread executing span following the sleeping thread state for which the critical path is + -- computed. + root_id INT, -- Id of the first (runnable) thread state in thread_executing_span. id INT, -- Timestamp of first thread_state in thread_executing_span. @@ -491,53 +532,8 @@ RETURNS TABLE( -- Duration of thread_executing_span. dur LONG, -- Utid of thread with thread_state. - utid INT, - -- Id of thread executing span following the sleeping thread state for which the critical path is computed. - critical_path_id INT, - -- Critical path duration. - critical_path_blocked_dur LONG, - -- Sleeping thread state in critical path. - critical_path_blocked_state STRING, - -- Kernel blocked_function of the critical path. - critical_path_blocked_function STRING, - -- Thread Utid the critical path was filtered to. - critical_path_utid INT + utid INT ) AS -SELECT * FROM _critical_path_by_intervals!((SELECT $critical_path_utid AS utid, $ts as ts, $dur AS dur)); - --- Generates the critical path for all threads for the entire trace duration. --- The duration of a thread executing span in the critical path is the range between the --- start of the thread_executing_span and the start of the next span in the critical path. -CREATE PERFETTO FUNCTION _thread_executing_span_critical_path_all() -RETURNS - TABLE( - -- Id of the first (runnable) thread state in thread_executing_span. - id INT, - -- Timestamp of first thread_state in thread_executing_span. - ts LONG, - -- Duration of thread_executing_span. - dur LONG, - -- Utid of thread with thread_state. - utid INT, - -- Id of thread executing span following the sleeping thread state for which the critical path is computed. - critical_path_id INT, - -- Critical path duration. - critical_path_blocked_dur LONG, - -- Sleeping thread state in critical path. - critical_path_blocked_state STRING, - -- Kernel blocked_function of the critical path. - critical_path_blocked_function STRING, - -- Thread Utid the critical path was filtered to. - critical_path_utid INT) -AS -SELECT - id, - ts, - dur, - utid, - critical_path_id, - critical_path_blocked_dur, - critical_path_blocked_state, - critical_path_blocked_function, - critical_path_utid -FROM _critical_path_by_roots!((SELECT id FROM _wakeup_graph)); +SELECT root_utid, root_id, id, ts, dur, utid FROM _critical_path_by_intervals!( + (SELECT $root_utid AS utid, $ts as ts, $dur AS dur), + _wakeup_graph); diff --git a/src/trace_processor/perfetto_sql/stdlib/sched/thread_executing_span_with_slice.sql b/src/trace_processor/perfetto_sql/stdlib/sched/thread_executing_span_with_slice.sql index 89085f374c..6961f87308 100644 --- a/src/trace_processor/perfetto_sql/stdlib/sched/thread_executing_span_with_slice.sql +++ b/src/trace_processor/perfetto_sql/stdlib/sched/thread_executing_span_with_slice.sql @@ -17,14 +17,93 @@ INCLUDE PERFETTO MODULE slices.flat_slices; INCLUDE PERFETTO MODULE sched.thread_executing_span; -CREATE PERFETTO TABLE _critical_path_all AS -SELECT * FROM _thread_executing_span_critical_path_all(); +CREATE PERFETTO TABLE _critical_path_userspace +AS +SELECT * +FROM + _critical_path_intervals + !(_wakeup_userspace_edges, + (SELECT id AS root_node_id, id - COALESCE(prev_id, id) AS capacity FROM _wakeup_graph), + _wakeup_intervals); + +CREATE PERFETTO TABLE _critical_path_kernel +AS +WITH _kernel_nodes AS ( + SELECT id, root_id FROM _critical_path_userspace + JOIN _wakeup_graph USING (id) WHERE is_idle_reason_self = 1 +) +SELECT _kernel_nodes.root_id, cr.root_id AS parent_id, cr.id, cr.ts, cr.dur +FROM + _critical_path_intervals + !(_wakeup_kernel_edges, + ( + SELECT graph.id AS root_node_id, graph.id - COALESCE(graph.prev_id, graph.id) AS capacity + FROM _kernel_nodes + JOIN _wakeup_graph graph USING(id) + ), + _wakeup_intervals) cr +JOIN _kernel_nodes + ON _kernel_nodes.id = cr.root_id; + +CREATE PERFETTO TABLE _critical_path_userspace_adjusted AS +SELECT DISTINCT * FROM _critical_path_userspace_adjusted!(_critical_path_userspace, _wakeup_graph); + +CREATE PERFETTO TABLE _critical_path_kernel_adjusted AS +SELECT DISTINCT * FROM _critical_path_kernel_adjusted!(_critical_path_userspace_adjusted, _critical_path_kernel, _wakeup_graph); + +CREATE PERFETTO TABLE _critical_path_merged_adjusted AS + SELECT root_id, parent_id, id, ts, dur FROM _critical_path_userspace_adjusted + UNION ALL + SELECT root_id, parent_id, id, ts, dur FROM _critical_path_kernel_adjusted WHERE id != parent_id; + +CREATE PERFETTO TABLE _critical_path_roots AS + SELECT root_id, min(ts) AS root_ts, max(ts + dur) - min(ts) AS root_dur + FROM _critical_path_userspace_adjusted + GROUP BY root_id; + +CREATE PERFETTO TABLE _critical_path_roots_and_merged AS + WITH roots_and_merged_critical_path AS ( + SELECT + root_id, + root_ts, + root_dur, + parent_id, + id, + ts, + dur + FROM _critical_path_merged_adjusted + JOIN _critical_path_roots USING(root_id) + ) + SELECT + flat.root_id, + flat.id, + flat.ts, + flat.dur + FROM + _intervals_flatten!(roots_and_merged_critical_path) flat + WHERE flat.dur > 0 + GROUP BY flat.root_id, flat.ts; + +CREATE PERFETTO TABLE _critical_path_all +AS +SELECT + ROW_NUMBER() OVER(ORDER BY cr.ts) AS id, + cr.ts, + cr.dur, + cr.ts + cr.dur AS ts_end, + id_graph.utid, + root_id_graph.utid AS root_utid + FROM _critical_path_roots_and_merged cr + JOIN _wakeup_graph id_graph ON cr.id = id_graph.id + JOIN _wakeup_graph root_id_graph ON cr.root_id = root_id_graph.id ORDER BY cr.ts; -- Limited thread_state view that will later be span joined with the |_thread_executing_span_graph|. CREATE PERFETTO VIEW _span_thread_state_view -AS SELECT id AS thread_state_id, ts, dur, utid, state, blocked_function as function, io_wait, cpu FROM thread_state; +AS +SELECT id AS thread_state_id, ts, dur, utid, state, blocked_function AS function, io_wait, cpu +FROM thread_state; --- Limited slice_view that will later be span joined with the |_thread_executing_span_graph|. +-- Limited slice_view that will later be span joined with the critical path. CREATE PERFETTO VIEW _span_slice_view AS SELECT @@ -36,50 +115,47 @@ SELECT utid FROM _slice_flattened; -CREATE VIRTUAL TABLE _span_thread_state_slice_view +-- thread state span joined with slice. +CREATE VIRTUAL TABLE _span_thread_state_slice_sp USING SPAN_LEFT_JOIN( _span_thread_state_view PARTITIONED utid, _span_slice_view PARTITIONED utid); --- |_thread_executing_span_graph| span joined with thread_state information. -CREATE VIRTUAL TABLE _span_critical_path_thread_state_slice_sp -USING - SPAN_JOIN( - _critical_path_all PARTITIONED utid, - _span_thread_state_slice_view PARTITIONED utid); - --- |_thread_executing_span_graph| + thread_state view joined with critical_path information. -CREATE PERFETTO TABLE _critical_path_thread_state_slice AS -WITH span_starts AS ( - SELECT - span.id, - span.utid, - span.critical_path_id, - span.critical_path_blocked_dur, - span.critical_path_blocked_state, - span.critical_path_blocked_function, - span.critical_path_utid, - thread_state_id, - MAX(thread_state.ts, span.ts) AS ts, - span.ts + span.dur AS span_end_ts, - thread_state.ts + thread_state.dur AS thread_state_end_ts, - thread_state.state, - thread_state.function, - thread_state.cpu, - thread_state.io_wait, - thread_state.slice_id, - thread_state.slice_name, - thread_state.slice_depth - FROM _critical_path_all span - JOIN _span_critical_path_thread_state_slice_sp thread_state USING(id) - ) +CREATE PERFETTO TABLE _span_thread_state_slice +AS SELECT - id, - thread_state_id, + ROW_NUMBER() OVER(ORDER BY ts) AS id, ts, - MIN(span_end_ts, thread_state_end_ts) - ts AS dur, + dur, + ts + dur AS ts_end, utid, + thread_state_id, + state, + function, + cpu, + io_wait, + slice_id, + slice_name, + slice_depth + FROM _span_thread_state_slice_sp WHERE dur > 0 ORDER BY ts; + +CREATE PERFETTO TABLE _critical_path_thread_state_slice_raw +AS +SELECT + id_0 AS cr_id, + id_1 AS th_id, + ts, + dur +FROM _interval_intersect!((_critical_path_all, _span_thread_state_slice), (utid)); + +CREATE PERFETTO TABLE _critical_path_thread_state_slice +AS +SELECT + raw.ts, + raw.dur, + cr.utid, + thread_state_id, state, function, cpu, @@ -87,13 +163,12 @@ SELECT slice_id, slice_name, slice_depth, - critical_path_id, - critical_path_blocked_dur, - critical_path_blocked_state, - critical_path_blocked_function, - critical_path_utid -FROM span_starts -WHERE MIN(span_end_ts, thread_state_end_ts) - ts > 0; + root_utid +FROM _critical_path_thread_state_slice_raw raw +JOIN _critical_path_all cr + ON cr.id = raw.cr_id +JOIN _span_thread_state_slice th + ON th.id = raw.th_id; -- Flattened slices span joined with their thread_states. This contains the 'self' information -- without 'critical_path' (blocking) information. @@ -108,7 +183,7 @@ CREATE PERFETTO VIEW _self_view slice_id AS self_slice_id, ts, dur, - utid AS critical_path_utid, + utid AS root_utid, state AS self_state, blocked_function AS self_function, cpu AS self_cpu, @@ -123,8 +198,8 @@ CREATE PERFETTO VIEW _self_view CREATE VIRTUAL TABLE _self_and_critical_path_sp USING SPAN_JOIN( - _self_view PARTITIONED critical_path_utid, - _critical_path_thread_state_slice PARTITIONED critical_path_utid); + _self_view PARTITIONED root_utid, + _critical_path_thread_state_slice PARTITIONED root_utid); -- Returns a view of |_self_and_critical_path_sp| unpivoted over the following columns: -- self thread_state. @@ -138,7 +213,7 @@ USING -- critical_path slice_stack (enabled with |enable_critical_path_slice|). -- running cpu (if one exists). -- A 'stack' is the group of resulting unpivoted rows sharing the same timestamp. -CREATE PERFETTO FUNCTION _critical_path_stack(critical_path_utid INT, ts LONG, dur LONG, enable_process_name INT, enable_thread_name INT, enable_self_slice INT, enable_critical_path_slice INT) +CREATE PERFETTO FUNCTION _critical_path_stack(root_utid INT, ts LONG, dur LONG, enable_process_name INT, enable_thread_name INT, enable_self_slice INT, enable_critical_path_slice INT) RETURNS TABLE( id INT, @@ -148,8 +223,8 @@ RETURNS stack_depth INT, name STRING, table_name STRING, - critical_path_utid INT) AS - -- Spans filtered to the query time window and critical_path_utid. + root_utid INT) AS + -- Spans filtered to the query time window and root_utid. -- This is a preliminary step that gets the start and end ts of all the rows -- so that we can chop the ends of each interval correctly if it overlaps with the query time interval. WITH relevant_spans_starts AS ( @@ -172,9 +247,9 @@ RETURNS utid, MAX(ts, $ts) AS ts, MIN(ts + dur, $ts + $dur) AS end_ts, - critical_path_utid + root_utid FROM _self_and_critical_path_sp - WHERE dur > 0 AND critical_path_utid = $critical_path_utid + WHERE dur > 0 AND root_utid = $root_utid ), -- This is the final step that gets the |dur| of each span from the start and -- and end ts of the previous step. @@ -201,7 +276,7 @@ RETURNS utid, ts, end_ts - ts AS dur, - critical_path_utid, + root_utid, utid FROM relevant_spans_starts WHERE dur > 0 @@ -213,11 +288,11 @@ RETURNS self_thread_state_id AS id, ts, dur, - utid, + root_utid AS utid, 0 AS stack_depth, 'thread_state: ' || self_state AS name, 'thread_state' AS table_name, - critical_path_utid + root_utid FROM relevant_spans UNION ALL -- Builds the self kernel blocked_function @@ -225,11 +300,11 @@ RETURNS self_thread_state_id AS id, ts, dur, - utid, + root_utid AS utid, 1 AS stack_depth, IIF(self_state GLOB 'R*', NULL, 'kernel function: ' || self_function) AS name, 'thread_state' AS table_name, - critical_path_utid + root_utid FROM relevant_spans UNION ALL -- Builds the self kernel io_wait @@ -237,11 +312,11 @@ RETURNS self_thread_state_id AS id, ts, dur, - utid, + root_utid AS utid, 2 AS stack_depth, IIF(self_state GLOB 'R*', NULL, 'io_wait: ' || self_io_wait) AS name, 'thread_state' AS table_name, - critical_path_utid + root_utid FROM relevant_spans UNION ALL -- Builds the self process_name @@ -253,10 +328,10 @@ RETURNS 3 AS stack_depth, IIF($enable_process_name, 'process_name: ' || process.name, NULL) AS name, 'thread_state' AS table_name, - critical_path_utid + root_utid FROM relevant_spans LEFT JOIN thread - ON thread.utid = critical_path_utid + ON thread.utid = root_utid LEFT JOIN process USING (upid) -- Builds the self thread_name @@ -269,10 +344,10 @@ RETURNS 4 AS stack_depth, IIF($enable_thread_name, 'thread_name: ' || thread.name, NULL) AS name, 'thread_state' AS table_name, - critical_path_utid + root_utid FROM relevant_spans LEFT JOIN thread - ON thread.utid = critical_path_utid + ON thread.utid = root_utid JOIN process USING (upid) UNION ALL @@ -281,11 +356,11 @@ RETURNS anc.id, slice.ts, slice.dur, - slice.utid, + root_utid AS utid, anc.depth + 5 AS stack_depth, IIF($enable_self_slice, anc.name, NULL) AS name, 'slice' AS table_name, - critical_path_utid + root_utid FROM relevant_spans slice JOIN ancestor_slice(self_slice_id) anc WHERE anc.dur != -1 UNION ALL @@ -294,11 +369,11 @@ RETURNS self_slice_id AS id, ts, dur, - utid, + root_utid AS utid, self_slice_depth + 5 AS stack_depth, IIF($enable_self_slice, self_slice_name, NULL) AS name, 'slice' AS table_name, - critical_path_utid + root_utid FROM relevant_spans slice -- Ordering by stack depth is important to ensure the items can -- be renedered in the UI as a debug track in the order in which @@ -310,9 +385,9 @@ RETURNS -- each self slice stack has variable depth and the depth in each stack -- most be contiguous in order to efficiently generate a pprof in the future. critical_path_start_depth AS MATERIALIZED ( - SELECT critical_path_utid, ts, MAX(stack_depth) + 1 AS start_depth + SELECT root_utid, ts, MAX(stack_depth) + 1 AS start_depth FROM self_stack - GROUP BY critical_path_utid, ts + GROUP BY root_utid, ts ), critical_path_span AS MATERIALIZED ( SELECT @@ -325,15 +400,15 @@ RETURNS slice_depth, spans.ts, spans.dur, - spans.critical_path_utid, + spans.root_utid, utid, start_depth FROM relevant_spans spans JOIN critical_path_start_depth ON - critical_path_start_depth.critical_path_utid = spans.critical_path_utid + critical_path_start_depth.root_utid = spans.root_utid AND critical_path_start_depth.ts = spans.ts - WHERE critical_path_start_depth.critical_path_utid = $critical_path_utid AND spans.critical_path_utid != spans.utid + WHERE critical_path_start_depth.root_utid = $root_utid AND spans.root_utid != spans.utid ), -- 2. Builds the 'critical_path' stack of items as an ordered UNION ALL critical_path_stack AS MATERIALIZED ( @@ -346,7 +421,7 @@ RETURNS start_depth AS stack_depth, 'blocking thread_state: ' || state AS name, 'thread_state' AS table_name, - critical_path_utid + root_utid FROM critical_path_span UNION ALL -- Builds the critical_path process_name @@ -358,7 +433,7 @@ RETURNS start_depth + 1 AS stack_depth, 'blocking process_name: ' || process.name, 'thread_state' AS table_name, - critical_path_utid + root_utid FROM critical_path_span JOIN thread USING (utid) LEFT JOIN process USING (upid) @@ -372,7 +447,7 @@ RETURNS start_depth + 2 AS stack_depth, 'blocking thread_name: ' || thread.name, 'thread_state' AS table_name, - critical_path_utid + root_utid FROM critical_path_span JOIN thread USING (utid) UNION ALL @@ -385,7 +460,7 @@ RETURNS start_depth + 3 AS stack_depth, 'blocking kernel_function: ' || function, 'thread_state' AS table_name, - critical_path_utid + root_utid FROM critical_path_span JOIN thread USING (utid) UNION ALL @@ -398,7 +473,7 @@ RETURNS start_depth + 4 AS stack_depth, 'blocking io_wait: ' || io_wait, 'thread_state' AS table_name, - critical_path_utid + root_utid FROM critical_path_span JOIN thread USING (utid) UNION ALL @@ -411,7 +486,7 @@ RETURNS anc.depth + start_depth + 5 AS stack_depth, IIF($enable_critical_path_slice, anc.name, NULL) AS name, 'slice' AS table_name, - critical_path_utid + root_utid FROM critical_path_span slice JOIN ancestor_slice(slice_id) anc WHERE anc.dur != -1 UNION ALL @@ -424,7 +499,7 @@ RETURNS slice_depth + start_depth + 5 AS stack_depth, IIF($enable_critical_path_slice, slice_name, NULL) AS name, 'slice' AS table_name, - critical_path_utid + root_utid FROM critical_path_span slice -- Ordering is also important as in the 'self' step above. ORDER BY stack_depth @@ -434,16 +509,16 @@ RETURNS -- the critical_path stack and self stack. The self stack depth is -- already computed and materialized in |critical_path_start_depth|. cpu_start_depth_raw AS ( - SELECT critical_path_utid, ts, MAX(stack_depth) + 1 AS start_depth + SELECT root_utid, ts, MAX(stack_depth) + 1 AS start_depth FROM critical_path_stack - GROUP BY critical_path_utid, ts + GROUP BY root_utid, ts UNION ALL SELECT * FROM critical_path_start_depth ), cpu_start_depth AS ( - SELECT critical_path_utid, ts, MAX(start_depth) AS start_depth + SELECT root_utid, ts, MAX(start_depth) AS start_depth FROM cpu_start_depth_raw - GROUP BY critical_path_utid, ts + GROUP BY root_utid, ts ), -- 3. Builds the 'CPU' stack for 'Running' states in either the self or critical path stack. cpu_stack AS ( @@ -455,13 +530,13 @@ RETURNS start_depth AS stack_depth, 'cpu: ' || cpu AS name, 'thread_state' AS table_name, - spans.critical_path_utid + spans.root_utid FROM relevant_spans spans JOIN cpu_start_depth ON - cpu_start_depth.critical_path_utid = spans.critical_path_utid + cpu_start_depth.root_utid = spans.root_utid AND cpu_start_depth.ts = spans.ts - WHERE cpu_start_depth.critical_path_utid = $critical_path_utid AND state = 'Running' OR self_state = 'Running' + WHERE cpu_start_depth.root_utid = $root_utid AND state = 'Running' OR self_state = 'Running' ), merged AS ( SELECT * FROM self_stack @@ -478,7 +553,7 @@ SELECT * FROM merged WHERE id IS NOT NULL; -- critical_path thread_name, critical_path slice_stack, running_cpu. CREATE PERFETTO FUNCTION _thread_executing_span_critical_path_stack( -- Thread utid to filter critical paths to. - critical_path_utid INT, + root_utid INT, -- Timestamp of start of time range to filter critical paths to. ts LONG, -- Duration of time range to filter critical paths to. @@ -500,25 +575,25 @@ RETURNS -- Table name of entity in the critical path (could be either slice or thread_state). table_name STRING, -- Utid of the thread the critical path was filtered to. - critical_path_utid INT + root_utid INT ) AS -SELECT * FROM _critical_path_stack($critical_path_utid, $ts, $dur, 1, 1, 1, 1); +SELECT * FROM _critical_path_stack($root_utid, $ts, $dur, 1, 1, 1, 1); -- Returns a pprof aggregation of the stacks in |_critical_path_stack|. -CREATE PERFETTO FUNCTION _critical_path_graph(graph_title STRING, critical_path_utid INT, ts LONG, dur LONG, enable_process_name INT, enable_thread_name INT, enable_self_slice INT, enable_critical_path_slice INT) +CREATE PERFETTO FUNCTION _critical_path_graph(graph_title STRING, root_utid INT, ts LONG, dur LONG, enable_process_name INT, enable_thread_name INT, enable_self_slice INT, enable_critical_path_slice INT) RETURNS TABLE(pprof BYTES) AS WITH stack AS MATERIALIZED ( SELECT ts, - dur - IFNULL(LEAD(dur) OVER (PARTITION BY critical_path_utid, ts ORDER BY stack_depth), 0) AS dur, + dur - IFNULL(LEAD(dur) OVER (PARTITION BY root_utid, ts ORDER BY stack_depth), 0) AS dur, name, utid, - critical_path_utid, + root_utid, stack_depth FROM - _critical_path_stack($critical_path_utid, $ts, $dur, $enable_process_name, $enable_thread_name, $enable_self_slice, $enable_critical_path_slice) + _critical_path_stack($root_utid, $ts, $dur, $enable_process_name, $enable_thread_name, $enable_self_slice, $enable_critical_path_slice) ), graph AS ( SELECT CAT_STACKS($graph_title) AS stack @@ -531,7 +606,7 @@ WITH cr.utid, cr.stack_depth, CAT_STACKS(graph.stack, cr.name) AS stack, - cr.critical_path_utid + cr.root_utid FROM stack cr, graph WHERE stack_depth = 0 UNION ALL @@ -542,11 +617,11 @@ WITH child.utid, child.stack_depth, CAT_STACKS(stack, child.name) AS stack, - child.critical_path_utid + child.root_utid FROM stack child JOIN parent ON - parent.critical_path_utid = child.critical_path_utid + parent.root_utid = child.root_utid AND parent.ts = child.ts AND child.stack_depth = parent.stack_depth + 1 ), @@ -560,7 +635,7 @@ CREATE PERFETTO FUNCTION _thread_executing_span_critical_path_graph( -- Descriptive name for the graph. graph_title STRING, -- Thread utid to filter critical paths to. - critical_path_utid INT, + root_utid INT, -- Timestamp of start of time range to filter critical paths to. ts INT, -- Duration of time range to filter critical paths to. @@ -570,4 +645,4 @@ RETURNS TABLE( pprof BYTES ) AS -SELECT * FROM _critical_path_graph($graph_title, $critical_path_utid, $ts, $dur, 1, 1, 1, 1); +SELECT * FROM _critical_path_graph($graph_title, $root_utid, $ts, $dur, 1, 1, 1, 1); diff --git a/src/trace_processor/perfetto_sql/stdlib/sched/thread_level_parallelism.sql b/src/trace_processor/perfetto_sql/stdlib/sched/thread_level_parallelism.sql index 19b0508063..e7081eed70 100644 --- a/src/trace_processor/perfetto_sql/stdlib/sched/thread_level_parallelism.sql +++ b/src/trace_processor/perfetto_sql/stdlib/sched/thread_level_parallelism.sql @@ -18,7 +18,6 @@ -- where running at a given point in time. INCLUDE PERFETTO MODULE intervals.overlap; -INCLUDE PERFETTO MODULE cpu.cpus; -- The count of runnable threads over time. CREATE PERFETTO TABLE sched_runnable_thread_count( @@ -58,35 +57,3 @@ SELECT ts, value as active_cpu_count FROM intervals_overlap_count!(tasks, ts, dur) ORDER BY ts; - --- The count of active CPUs with a given core type over time. -CREATE PERFETTO FUNCTION sched_active_cpu_count_for_core_type( - -- Type of the CPU core as reported by GUESS_CPU_SIZE. Usually 'big', 'mid' or 'little'. - core_type STRING -) RETURNS TABLE( - -- Timestamp when the number of active CPU changed. - ts LONG, - -- Number of active CPUs, covering the range from this timestamp to the next - -- row's timestamp. - active_cpu_count LONG -) AS -WITH --- Materialise the relevant cores to avoid calling a function for each row of the sched table. -cores AS MATERIALIZED ( - SELECT cpu_index - FROM cpu_core_types - WHERE size = $core_type -), --- Filter sched events corresponding to running tasks. --- utid=0 is the swapper thread / idle task. -tasks AS ( - SELECT ts, dur - FROM sched - WHERE - cpu IN (SELECT cpu_index FROM cores) - AND utid != 0 -) -SELECT - ts, value as active_cpu_count -FROM intervals_overlap_count!(tasks, ts, dur) -ORDER BY ts; diff --git a/src/trace_processor/perfetto_sql/stdlib/sched/time_in_state.sql b/src/trace_processor/perfetto_sql/stdlib/sched/time_in_state.sql index a12a95a14b..e6279919f7 100644 --- a/src/trace_processor/perfetto_sql/stdlib/sched/time_in_state.sql +++ b/src/trace_processor/perfetto_sql/stdlib/sched/time_in_state.sql @@ -122,7 +122,7 @@ JOIN $ts, $dur, (SELECT id, ts, dur FROM thread_state - WHERE utid = $utid))) ii USING (id) + WHERE utid = $utid AND dur > 0))) ii USING (id) GROUP BY 1, 2, 3 ORDER BY 4 DESC; @@ -142,7 +142,6 @@ RETURNS TABLE( -- sleep, if it was an IO sleep. io_wait BOOL, -- Id of the CPU. - -- Use `cpu_guess_core_type` to get the CPU size (little/mid/big). cpu INT, -- Some states can specify the blocked function. Usually NULL. blocked_function INT, @@ -160,7 +159,7 @@ JOIN $ts, $dur, (SELECT id, ts, dur FROM thread_state - WHERE utid = $utid))) ii USING (id) + WHERE utid = $utid AND dur > 0))) ii USING (id) GROUP BY 1, 2, 3, 4 ORDER BY 5 DESC; diff --git a/src/trace_processor/perfetto_sql/stdlib/slices/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/slices/BUILD.gn index f69eafd694..2e58615733 100644 --- a/src/trace_processor/perfetto_sql/stdlib/slices/BUILD.gn +++ b/src/trace_processor/perfetto_sql/stdlib/slices/BUILD.gn @@ -18,6 +18,8 @@ perfetto_sql_source_set("slices") { sources = [ "cpu_time.sql", "flat_slices.sql", + "flow.sql", + "hierarchy.sql", "slices.sql", "with_context.sql", ] diff --git a/src/trace_processor/perfetto_sql/stdlib/slices/flat_slices.sql b/src/trace_processor/perfetto_sql/stdlib/slices/flat_slices.sql index 4353ed2109..066314b1e5 100644 --- a/src/trace_processor/perfetto_sql/stdlib/slices/flat_slices.sql +++ b/src/trace_processor/perfetto_sql/stdlib/slices/flat_slices.sql @@ -12,6 +12,8 @@ -- 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 slices.with_context; +INCLUDE PERFETTO MODULE intervals.overlap; -- The concept of a "flat slice" is to take the data in the slice table and -- remove all notion of nesting; we do this by projecting every slice in a stack to @@ -51,60 +53,41 @@ -- @column upid Alias for `process.upid`. -- @column pid Alias for `process.pid`. -- @column process_name Alias for `process.name`. -CREATE TABLE _slice_flattened AS --- The algorithm proceeds as follows: --- 1. Find the start and end timestamps of all slices. --- 2. Iterate the generated timestamps within a stack in chronoligical order. --- 3. Generate a slice for each timestamp pair (regardless of if it was a start or end) . --- 4. If the first timestamp in the pair was originally a start, the slice is the 'current' slice, --- otherwise, the slice is the parent slice. +CREATE PERFETTO TABLE _slice_flattened +AS WITH - begins AS ( - SELECT id AS slice_id, ts, name, track_id, depth - FROM slice - WHERE dur > 0 - ), - ends AS ( - SELECT - parent.id AS slice_id, - current.ts + current.dur AS ts, - parent.name as name, - current.track_id, - current.depth - 1 AS depth - FROM slice current - LEFT JOIN slice parent - ON current.parent_id = parent.id - WHERE current.dur > 0 + root_slices AS ( + SELECT * FROM slice WHERE parent_id IS NULL ), - events AS ( - SELECT * FROM begins - UNION ALL - SELECT * FROM ends + child_slices AS ( + SELECT anc.id AS root_id, slice.* + FROM slice + JOIN ancestor_slice(slice.id) anc + WHERE slice.parent_id IS NOT NULL ), - data AS ( - SELECT - events.slice_id, - events.ts, - LEAD(events.ts) OVER ( - PARTITION BY events.track_id - ORDER BY events.ts) - events.ts AS dur, - events.depth, - events.name, - events.track_id - FROM events + flat_slices AS ( + SELECT id, ts, dur + FROM _intervals_flatten !(_intervals_merge_root_and_children!(root_slices, child_slices)) ) -SELECT data.slice_id, data.ts, data.dur, data.depth, - data.name, data.track_id, thread.utid, thread.tid, thread.name as thread_name, - process.upid, process.pid, process.name as process_name - FROM data JOIN thread_track ON data.track_id = thread_track.id -JOIN thread USING(utid) -JOIN process USING(upid) -WHERE depth != -1; +SELECT + id AS slice_id, + flat_slices.ts, + flat_slices.dur, + depth, + name, + track_id, + utid, + tid, + thread_name, + upid, + pid, + process_name +FROM flat_slices +JOIN thread_slice + USING (id); -CREATE - INDEX _slice_flattened_id_idx +CREATE PERFETTO INDEX _slice_flattened_id_idx ON _slice_flattened(slice_id); -CREATE - INDEX _slice_flattened_ts_idx +CREATE PERFETTO INDEX _slice_flattened_ts_idx ON _slice_flattened(ts); diff --git a/src/trace_processor/perfetto_sql/stdlib/slices/flow.sql b/src/trace_processor/perfetto_sql/stdlib/slices/flow.sql new file mode 100644 index 0000000000..f93947859f --- /dev/null +++ b/src/trace_processor/perfetto_sql/stdlib/slices/flow.sql @@ -0,0 +1,44 @@ +-- +-- 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 graphs.search; + +-- Computes the "reachable" set of slices from the |flows| table, starting from slice ids +-- specified in |source_table|. This provides a more efficient result than with the in-built +-- following_flow operator. +CREATE PERFETTO MACRO _slice_following_flow( + -- A table/view/subquery corresponding to the nodes to start the reachability search. + -- This table must have a uint32 "id" column. + source_table TableOrSubquery +) +-- The returned table has the schema (root_node_id, node_id UINT32, parent_node_id UINT32). +-- |root_node_id| is the id of the starting node under which this edge was encountered. +-- |node_id| is the id of the node from the input graph and |parent_node_id| +-- is the id of the node which was the first encountered predecessor in a DFS +-- search of the graph. +RETURNS TableOrSubquery AS +( +SELECT * +FROM + graph_reachable_weight_bounded_dfs + !((SELECT slice_out AS source_node_id, slice_in AS dest_node_id, 0 AS edge_weight FROM flow), + ( + SELECT slice_out AS root_node_id, 1 AS root_target_weight + FROM flow + JOIN (SELECT id FROM $source_table) source + ON slice_out = source.id + ), + 1) +); diff --git a/src/trace_processor/perfetto_sql/stdlib/slices/hierarchy.sql b/src/trace_processor/perfetto_sql/stdlib/slices/hierarchy.sql new file mode 100644 index 0000000000..dff6475c08 --- /dev/null +++ b/src/trace_processor/perfetto_sql/stdlib/slices/hierarchy.sql @@ -0,0 +1,94 @@ +-- +-- 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. + +-- Similar to `ancestor_slice`, but returns the slice itself in addition to strict ancestors. +CREATE PERFETTO FUNCTION _slice_ancestor_and_self( + -- Id of the slice. + slice_id LONG +) +RETURNS TABLE( + -- Alias of `slice.id`. + id LONG, + -- Alias of `slice.type`. + type STRING, + -- Alias of `slice.ts`. + ts LONG, + -- Alias of `slice.dur`. + dur LONG, + -- Alias of `slice.track_id`. + track_id LONG, + -- Alias of `slice.category`. + category STRING, + -- Alias of `slice.name`. + name STRING, + -- Alias of `slice.depth`. + depth LONG, + -- Alias of `slice.parent_id`. + parent_id LONG, + -- Alias of `slice.arg_set_id`. + arg_set_id LONG, + -- Alias of `slice.thread_ts`. + thread_ts LONG, + -- Alias of `slice.thread_dur`. + thread_dur LONG +) AS +SELECT + id, type, ts, dur, track_id, category, name, depth, parent_id, arg_set_id, thread_ts, thread_dur +FROM slice +WHERE id = $slice_id +UNION ALL +SELECT + id, type, ts, dur, track_id, category, name, depth, parent_id, arg_set_id, thread_ts, thread_dur +FROM ancestor_slice($slice_id); + +-- Similar to `descendant_slice`, but returns the slice itself in addition to strict descendants. +CREATE PERFETTO FUNCTION _slice_descendant_and_self( + -- Id of the slice. + slice_id LONG +) +RETURNS TABLE( + -- Alias of `slice.id`. + id LONG, + -- Alias of `slice.type`. + type STRING, + -- Alias of `slice.ts`. + ts LONG, + -- Alias of `slice.dur`. + dur LONG, + -- Alias of `slice.track_id`. + track_id LONG, + -- Alias of `slice.category`. + category STRING, + -- Alias of `slice.name`. + name STRING, + -- Alias of `slice.depth`. + depth LONG, + -- Alias of `slice.parent_id`. + parent_id LONG, + -- Alias of `slice.arg_set_id`. + arg_set_id LONG, + -- Alias of `slice.thread_ts`. + thread_ts LONG, + -- Alias of `slice.thread_dur`. + thread_dur LONG +) AS +SELECT + id, type, ts, dur, track_id, category, name, depth, parent_id, arg_set_id, thread_ts, thread_dur +FROM slice +WHERE id = $slice_id +UNION ALL +SELECT + id, type, ts, dur, track_id, category, name, depth, parent_id, arg_set_id, thread_ts, thread_dur +FROM descendant_slice($slice_id); \ No newline at end of file diff --git a/src/trace_processor/perfetto_sql/stdlib/viz/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/viz/BUILD.gn new file mode 100644 index 0000000000..4584f8f8c4 --- /dev/null +++ b/src/trace_processor/perfetto_sql/stdlib/viz/BUILD.gn @@ -0,0 +1,20 @@ +# 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("../../../../../gn/perfetto_sql.gni") + +perfetto_sql_source_set("viz") { + sources = [ "flamegraph.sql" ] + deps = [ "summary" ] +} diff --git a/src/trace_processor/perfetto_sql/stdlib/viz/flamegraph.sql b/src/trace_processor/perfetto_sql/stdlib/viz/flamegraph.sql new file mode 100644 index 0000000000..338e0c3728 --- /dev/null +++ b/src/trace_processor/perfetto_sql/stdlib/viz/flamegraph.sql @@ -0,0 +1,407 @@ +-- +-- 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 graphs.scan; +INCLUDE PERFETTO MODULE metasql.column_list; + +CREATE PERFETTO MACRO _viz_flamegraph_hash_coalesce(col ColumnName) +RETURNS _SqlFragment AS IFNULL($col, 0); + +-- For each frame in |tab|, returns a row containing the result of running +-- all the filtering operations over that frame's name. +CREATE PERFETTO MACRO _viz_flamegraph_prepare_filter( + tab TableOrSubquery, + show_stack Expr, + hide_stack Expr, + show_from_frame Expr, + hide_frame Expr, + pivot Expr, + impossible_stack_bits Expr, + grouping _ColumnNameList +) +RETURNS TableOrSubquery +AS ( + SELECT + *, + IIF($hide_stack, $impossible_stack_bits, $show_stack) AS stackBits, + $show_from_frame As showFromFrameBits, + $hide_frame = 0 AS showFrame, + $pivot AS isPivot, + HASH( + name, + _metasql_map_join_column_list!($grouping, _viz_flamegraph_hash_coalesce) + ) AS groupingHash + FROM $tab + ORDER BY id +); + +-- Walks the forest from root to leaf and performs the following operations: +-- 1) removes frames which were filtered out +-- 2) make any pivot nodes become the roots +-- 3) computes whether the stack as a whole should be retained or not +CREATE PERFETTO MACRO _viz_flamegraph_filter_frames( + source TableOrSubquery, + show_from_frame_bits Expr +) +RETURNS TableOrSubquery +AS ( + WITH edges AS ( + SELECT parentId AS source_node_id, id AS dest_node_id + FROM $source + WHERE parentId IS NOT NULL + ), + inits AS ( + SELECT + id, + IIF( + showFrame AND showFromFrameBits = $show_from_frame_bits, + id, + NULL + ) AS filteredId, + NULL AS filteredParentId, + NULL AS filteredUnpivotedParentId, + IIF( + showFrame, + showFromFrameBits, + 0 + ) AS showFromFrameBits, + IIF( + showFrame AND showFromFrameBits = $show_from_frame_bits, + stackBits, + 0 + ) AS stackBits + FROM $source + WHERE parentId IS NULL + ) + SELECT + g.filteredId AS id, + g.filteredParentId AS parentId, + g.filteredUnpivotedParentId AS unpivotedParentId, + g.stackBits, + SUM(t.value) AS value + FROM _graph_scan!( + edges, + inits, + (filteredId, filteredParentId, filteredUnpivotedParentId, showFromFrameBits, stackBits), + ( + SELECT + t.id, + IIF( + x.showFrame AND (t.showFromFrameBits | x.showFromFrameBits) = $show_from_frame_bits, + t.id, + t.filteredId + ) AS filteredId, + IIF( + x.showFrame AND (t.showFromFrameBits | x.showFromFrameBits) = $show_from_frame_bits, + IIF(x.isPivot, NULL, t.filteredId), + t.filteredParentId + ) AS filteredParentId, + IIF( + x.showFrame AND (t.showFromFrameBits | x.showFromFrameBits) = $show_from_frame_bits, + t.filteredId, + t.filteredParentId + ) AS filteredUnpivotedParentId, + IIF( + x.showFrame, + (t.showFromFrameBits | x.showFromFrameBits), + t.showFromFrameBits + ) AS showFromFrameBits, + IIF( + x.showFrame AND (t.showFromFrameBits | x.showFromFrameBits) = $show_from_frame_bits, + (t.stackBits | x.stackBits), + t.stackBits + ) AS stackBits + FROM $table t + JOIN $source x USING (id) + ) + ) g + JOIN $source t USING (id) + WHERE filteredId IS NOT NULL + GROUP BY filteredId + ORDER BY filteredId +); + +-- Walks the forest from leaves to root and does the following: +-- 1) removes nodes whose stacks are filtered out +-- 2) computes the cumulative value for each node (i.e. the sum of the self +-- value of the node and all descendants). +CREATE PERFETTO MACRO _viz_flamegraph_accumulate( + filtered TableOrSubquery, + showStackBits Expr +) +RETURNS TableOrSubquery +AS ( + WITH edges AS ( + SELECT id AS source_node_id, parentId AS dest_node_id + FROM $filtered + WHERE parentId IS NOT NULL + ), inits AS ( + SELECT f.id, f.value AS cumulativeValue + FROM $filtered f + LEFT JOIN $filtered c ON c.parentId = f.id + WHERE c.id IS NULL AND f.stackBits = $showStackBits + ) + SELECT id, cumulativeValue + FROM _graph_aggregating_scan!( + edges, + inits, + (cumulativeValue), + ( + SELECT + x.id, + x.childValue + IIF( + t.stackBits = $showStackBits, + t.value, + 0 + ) AS cumulativeValue + FROM ( + SELECT id, SUM(cumulativeValue) AS childValue + FROM $table + GROUP BY id + ) x + JOIN $filtered t USING (id) + ) + ) + ORDER BY id +); + +CREATE PERFETTO MACRO _viz_flamegraph_s_prefix(col ColumnName) +RETURNS _SqlFragment AS s.$col; + +-- Propogates the cumulative value of the pivot nodes to the roots +-- and computes the "fingerprint" of the path. +CREATE PERFETTO MACRO _viz_flamegraph_upwards_hash( + source TableOrSubquery, + filtered TableOrSubquery, + accumulated TableOrSubquery, + grouping _ColumnNameList, + grouped _ColumnNameList +) +RETURNS TableOrSubquery +AS ( + WITH edges AS ( + SELECT id AS source_node_id, unpivotedParentId AS dest_node_id + FROM $filtered + WHERE unpivotedParentId IS NOT NULL + ), + inits AS ( + SELECT + f.id, + HASH(-1, s.groupingHash) AS hash, + NULL AS parentHash, + -1 AS depth, + a.cumulativeValue + FROM $filtered f + JOIN $source s USING (id) + JOIN $accumulated a USING (id) + WHERE f.parentId IS NULL + AND f.unpivotedParentId IS NOT NULL + AND a.cumulativeValue > 0 + ) + SELECT + g.id, + g.hash, + g.parentHash, + g.depth, + s.name, + _metasql_map_join_column_list!($grouping, _viz_flamegraph_s_prefix), + _metasql_map_join_column_list!($grouped, _viz_flamegraph_s_prefix), + f.value, + g.cumulativeValue + FROM _graph_scan!( + edges, + inits, + (hash, parentHash, depth, cumulativeValue), + ( + SELECT + t.id, + HASH(t.hash, x.groupingHash) AS hash, + t.hash AS parentHash, + t.depth - 1 AS depth, + t.cumulativeValue + FROM $table t + JOIN $source x USING (id) + ) + ) g + JOIN $source s USING (id) + JOIN $filtered f USING (id) +); + +-- Computes the "fingerprint" of the path by walking from the laves +-- to the root. +CREATE PERFETTO MACRO _viz_flamegraph_downwards_hash( + source TableOrSubquery, + filtered TableOrSubquery, + accumulated TableOrSubquery, + grouping _ColumnNameList, + grouped _ColumnNameList +) +RETURNS TableOrSubquery +AS ( + WITH + edges AS ( + SELECT parentId AS source_node_id, id AS dest_node_id + FROM $filtered + WHERE parentId IS NOT NULL + ), + inits AS ( + SELECT + f.id, + HASH(1, s.groupingHash) AS hash, + NULL AS parentHash, + 1 AS depth + FROM $filtered f + JOIN $source s USING (id) + WHERE f.parentId IS NULL + ) + SELECT + g.id, + g.hash, + g.parentHash, + g.depth, + s.name, + _metasql_map_join_column_list!($grouping, _viz_flamegraph_s_prefix), + _metasql_map_join_column_list!($grouped, _viz_flamegraph_s_prefix), + f.value, + a.cumulativeValue + FROM _graph_scan!( + edges, + inits, + (hash, parentHash, depth), + ( + SELECT + t.id, + HASH(t.hash, x.groupingHash) AS hash, + t.hash AS parentHash, + t.depth + 1 AS depth + FROM $table t + JOIN $source x USING (id) + ) + ) g + JOIN $source s USING (id) + JOIN $filtered f USING (id) + JOIN $accumulated a USING (id) + ORDER BY hash +); + +CREATE PERFETTO MACRO _viz_flamegraph_merge_grouped( + col ColumnName +) +RETURNS _SqlFragment +AS IIF(COUNT() = 1, $col, NULL) AS $col; + +-- Converts a table of hashes and paretn hashes into ids and parent +-- ids, grouping all hashes together. +CREATE PERFETTO MACRO _viz_flamegraph_merge_hashes( + hashed TableOrSubquery, + grouping _ColumnNameList, + grouped _ColumnNameList +) +RETURNS TableOrSubquery +AS ( + SELECT + _auto_id AS id, + ( + SELECT p._auto_id + FROM $hashed p + WHERE p.hash = c.parentHash + LIMIT 1 + ) AS parentId, + depth, + name, + -- The grouping columns should be passed through as-is because the + -- hash took them into account: we would not merged any nodes where + -- the grouping columns were different. + _metasql_unparenthesize_column_list!($grouping), + _metasql_map_join_column_list!($grouped, _viz_flamegraph_merge_grouped), + SUM(value) AS value, + SUM(cumulativeValue) AS cumulativeValue + FROM $hashed c + GROUP BY hash +); + +-- Performs a "layout" of nodes in the flamegraph relative to their +-- siblings. +CREATE PERFETTO MACRO _viz_flamegraph_local_layout( + merged TableOrSubquery +) +RETURNS TableOrSubquery +AS ( + WITH partial_layout AS ( + SELECT + id, + cumulativeValue, + SUM(cumulativeValue) OVER win AS xEnd + FROM $merged + WHERE cumulativeValue > 0 + WINDOW win AS ( + PARTITION BY parentId, depth + ORDER BY cumulativeValue DESC + ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW + ) + ) + SELECT id, xEnd - cumulativeValue as xStart, xEnd + FROM partial_layout + ORDER BY id +); + +-- Walks the graph from root to leaf, propogating the layout of +-- parents to their children. +CREATE PERFETTO MACRO _viz_flamegraph_global_layout( + merged TableOrSubquery, + layout TableOrSubquery, + grouping _ColumnNameList, + grouped _ColumnNameList +) +RETURNS TableOrSubquery +AS ( + WITH edges AS ( + SELECT parentId AS source_node_id, id AS dest_node_id + FROM $merged + WHERE parentId IS NOT NULL + ), + inits AS ( + SELECT h.id, l.xStart, l.xEnd + FROM $merged h + JOIN $layout l USING (id) + WHERE h.parentId IS NULL + ) + SELECT + s.id, + IFNULL(s.parentId, -1) AS parentId, + IIF(s.name = '', 'unknown', s.name) AS name, + _metasql_map_join_column_list!($grouping, _viz_flamegraph_s_prefix), + _metasql_map_join_column_list!($grouped, _viz_flamegraph_s_prefix), + s.value AS selfValue, + s.cumulativeValue, + s.depth, + g.xStart, + g.xEnd + FROM _graph_scan!( + edges, + inits, + (xStart, xEnd), + ( + SELECT + t.id, + t.xStart + w.xStart AS xStart, + t.xStart + w.xEnd AS xEnd + FROM $table t + JOIN $layout w USING (id) + ) + ) g + JOIN $merged s USING (id) + ORDER BY depth, xStart +); diff --git a/src/trace_processor/perfetto_sql/stdlib/viz/summary/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/viz/summary/BUILD.gn index 3f664bd723..b02d5a7d2c 100644 --- a/src/trace_processor/perfetto_sql/stdlib/viz/summary/BUILD.gn +++ b/src/trace_processor/perfetto_sql/stdlib/viz/summary/BUILD.gn @@ -16,6 +16,7 @@ import("../../../../../../gn/perfetto_sql.gni") perfetto_sql_source_set("summary") { sources = [ + "counters.sql", "processes.sql", "slices.sql", "threads.sql", diff --git a/src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/cpus.sql b/src/trace_processor/perfetto_sql/stdlib/viz/summary/counters.sql similarity index 69% rename from src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/cpus.sql rename to src/trace_processor/perfetto_sql/stdlib/viz/summary/counters.sql index 087060377b..7ae6d13097 100644 --- a/src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/cpus.sql +++ b/src/trace_processor/perfetto_sql/stdlib/viz/summary/counters.sql @@ -13,14 +13,6 @@ -- See the License for the specific language governing permissions and -- limitations under the License. -INCLUDE PERFETTO MODULE cpu.size; -INCLUDE PERFETTO MODULE cpu.cpus; - -CREATE PERFETTO TABLE cpus(cpu_index INT, size STRING) -AS -SELECT * FROM cpu_core_types; - - -CREATE PERFETTO FUNCTION guess_cpu_size(cpu_index INT) -RETURNS STRING AS -SELECT cpu_guess_core_type($cpu_index); \ No newline at end of file +CREATE PERFETTO TABLE _counter_track_summary AS +SELECT DISTINCT track_id as id +FROM counter; diff --git a/src/trace_processor/perfetto_sql/stdlib/wattson/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/wattson/BUILD.gn index 4167d8c32f..cd7377aa15 100644 --- a/src/trace_processor/perfetto_sql/stdlib/wattson/BUILD.gn +++ b/src/trace_processor/perfetto_sql/stdlib/wattson/BUILD.gn @@ -17,8 +17,13 @@ import("../../../../../gn/perfetto_sql.gni") perfetto_sql_source_set("wattson") { sources = [ "arm_dsu.sql", - "cpu_freq.sql", "cpu_idle.sql", + "cpu_split.sql", + "curves/device.sql", + "curves/grouped.sql", + "curves/ungrouped.sql", + "curves/utils.sql", + "device_infos.sql", "system_state.sql", ] } diff --git a/src/trace_processor/perfetto_sql/stdlib/wattson/cpu_freq.sql b/src/trace_processor/perfetto_sql/stdlib/wattson/cpu_freq.sql deleted file mode 100644 index 93738f6ab2..0000000000 --- a/src/trace_processor/perfetto_sql/stdlib/wattson/cpu_freq.sql +++ /dev/null @@ -1,92 +0,0 @@ --- --- 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 cpu.freq; - --- Filters for CPU specific frequency slices -CREATE PERFETTO FUNCTION _per_cpu_freq_slice(cpu_match INT) -RETURNS TABLE(ts LONG, dur INT, freq INT) -AS -SELECT ts, dur, freq -FROM cpu_freq_counters WHERE cpu = $cpu_match; - --- _freq_slices_cpux has CPUx specific frequency slices. -CREATE PERFETTO TABLE _freq_slices_cpu0 -AS -SELECT ts, dur, freq AS freq_0 FROM _per_cpu_freq_slice(0); - -CREATE PERFETTO TABLE _freq_slices_cpu1 -AS -SELECT ts, dur, freq AS freq_1 FROM _per_cpu_freq_slice(1); - -CREATE PERFETTO TABLE _freq_slices_cpu2 -AS -SELECT ts, dur, freq AS freq_2 FROM _per_cpu_freq_slice(2); - -CREATE PERFETTO TABLE _freq_slices_cpu3 -AS -SELECT ts, dur, freq AS freq_3 FROM _per_cpu_freq_slice(3); - -CREATE PERFETTO TABLE _freq_slices_cpu4 -AS -SELECT ts, dur, freq AS freq_4 FROM _per_cpu_freq_slice(4); - -CREATE PERFETTO TABLE _freq_slices_cpu5 -AS -SELECT ts, dur, freq AS freq_5 FROM _per_cpu_freq_slice(5); - -CREATE PERFETTO TABLE _freq_slices_cpu6 -AS -SELECT ts, dur, freq AS freq_6 FROM _per_cpu_freq_slice(6); - -CREATE PERFETTO TABLE _freq_slices_cpu7 -AS -SELECT ts, dur, freq AS freq_7 FROM _per_cpu_freq_slice(7); - --- SPAN_OUTER_JOIN of all CPUs' frequency tables. -CREATE VIRTUAL TABLE _freq_slices_cpu01 -USING - SPAN_OUTER_JOIN(_freq_slices_cpu0, _freq_slices_cpu1); - -CREATE VIRTUAL TABLE _freq_slices_cpu012 -USING - SPAN_OUTER_JOIN(_freq_slices_cpu01, _freq_slices_cpu2); - -CREATE VIRTUAL TABLE _freq_slices_cpu0123 -USING - SPAN_OUTER_JOIN(_freq_slices_cpu012, _freq_slices_cpu3); - -CREATE VIRTUAL TABLE _freq_slices_cpu01234 -USING - SPAN_OUTER_JOIN(_freq_slices_cpu0123, _freq_slices_cpu4); - -CREATE VIRTUAL TABLE _freq_slices_cpu012345 -USING - SPAN_OUTER_JOIN(_freq_slices_cpu01234, _freq_slices_cpu5); - -CREATE VIRTUAL TABLE _freq_slices_cpu0123456 -USING - SPAN_OUTER_JOIN(_freq_slices_cpu012345, _freq_slices_cpu6); - -CREATE VIRTUAL TABLE _freq_slices_cpu01234567 -USING - SPAN_OUTER_JOIN(_freq_slices_cpu0123456, _freq_slices_cpu7); - --- Table that holds time slices of the trace with the frequency transition --- information of every CPU in the system. -CREATE PERFETTO TABLE _cpu_freq_all -AS -SELECT * FROM _freq_slices_cpu01234567; - diff --git a/src/trace_processor/perfetto_sql/stdlib/wattson/cpu_idle.sql b/src/trace_processor/perfetto_sql/stdlib/wattson/cpu_idle.sql index 006c876770..78f0781091 100644 --- a/src/trace_processor/perfetto_sql/stdlib/wattson/cpu_idle.sql +++ b/src/trace_processor/perfetto_sql/stdlib/wattson/cpu_idle.sql @@ -13,39 +13,22 @@ -- See the License for the specific language governing permissions and -- limitations under the License. -INCLUDE PERFETTO MODULE android.device; -INCLUDE PERFETTO MODULE cpu.idle; - --- Device specific info for deep idle time offsets -CREATE PERFETTO TABLE _device_cpu_deep_idle_offsets -AS -WITH data(device, cpu, offset_ns) AS ( - VALUES - ("oriole", 6, 200000), - ("oriole", 7, 200000), - ("raven", 6, 200000), - ("raven", 7, 200000), - ("eos", 0, 450000), - ("eos", 1, 450000), - ("eos", 2, 450000), - ("eos", 3, 450000) -) -select * from data; +INCLUDE PERFETTO MODULE linux.cpu.idle; +INCLUDE PERFETTO MODULE wattson.device_infos; -- Get the corresponding deep idle time offset based on device and CPU. -CREATE PERFETTO FUNCTION _get_deep_idle_offset(cpu INT) -RETURNS INT +CREATE PERFETTO TABLE _filtered_deep_idle_offsets AS -SELECT offset_ns -FROM _device_cpu_deep_idle_offsets as offsets, android_device_name as device -WHERE - offsets.device = device.name AND cpu = $cpu; +SELECT cpu, offset_ns +FROM _device_cpu_deep_idle_offsets as offsets +JOIN _wattson_device as device +ON offsets.device = device.name; -- Adjust duration of active portion to be slightly longer to account for -- overhead cost of transitioning out of deep idle. This is done because the -- device is active and consumes power for longer than the logs actually report. -CREATE PERFETTO FUNCTION _adjust_deep_idle(cpu_match INT) -RETURNS TABLE(ts LONG, dur INT, idle INT) AS +CREATE PERFETTO TABLE _adjusted_deep_idle +AS WITH idle_prev AS ( SELECT @@ -56,9 +39,6 @@ WITH cpu FROM cpu_idle_counters ), - offset_ns AS ( - SELECT IFNULL(_get_deep_idle_offset($cpu_match), 0) as offset_ns - ), -- Adjusted ts if applicable, which makes the current deep idle state -- slightly shorter. idle_mod AS ( @@ -68,93 +48,19 @@ WITH IIF(dur > offset_ns, ts + offset_ns, ts + dur), ts ) as ts, - -- ts_next is the starting timestamp of the next slice (e.g. end ts of + -- ts_next is the starting timestamp of the next slice (i.e. end ts of -- current slice) ts + dur as ts_next, + cpu, idle - FROM idle_prev, offset_ns - WHERE cpu = $cpu_match + FROM idle_prev + JOIN _filtered_deep_idle_offsets using (cpu) ) SELECT ts, - lead(ts, 1, trace_end()) OVER (ORDER by ts) - ts as dur, + lead(ts, 1, trace_end()) OVER (PARTITION BY cpu ORDER by ts) - ts as dur, + cpu, idle FROM idle_mod WHERE ts != ts_next; --- idle_slices_cpux has CPUx specific idle state slices. -CREATE PERFETTO TABLE _idle_slices_cpu0 -AS -SELECT idle as idle_0, ts, dur -FROM _adjust_deep_idle(0); - -CREATE PERFETTO TABLE _idle_slices_cpu1 -AS -SELECT idle as idle_1, ts, dur -FROM _adjust_deep_idle(1); - -CREATE PERFETTO TABLE _idle_slices_cpu2 -AS -SELECT idle as idle_2, ts, dur -FROM _adjust_deep_idle(2); - -CREATE PERFETTO TABLE _idle_slices_cpu3 -AS -SELECT idle as idle_3, ts, dur -FROM _adjust_deep_idle(3); - -CREATE PERFETTO TABLE _idle_slices_cpu4 -AS -SELECT idle as idle_4, ts, dur -FROM _adjust_deep_idle(4); - -CREATE PERFETTO TABLE _idle_slices_cpu5 -AS -SELECT idle as idle_5, ts, dur -FROM _adjust_deep_idle(5); - -CREATE PERFETTO TABLE _idle_slices_cpu6 -AS -SELECT idle as idle_6, ts, dur -FROM _adjust_deep_idle(6); - -CREATE PERFETTO TABLE _idle_slices_cpu7 -AS -SELECT idle as idle_7, ts, dur -FROM _adjust_deep_idle(7); - --- SPAN_OUTER_JOIN of all CPUs' idle state tables. -CREATE VIRTUAL TABLE _idle_slices_cpu01 -USING - SPAN_OUTER_JOIN(_idle_slices_cpu0, _idle_slices_cpu1); - -CREATE VIRTUAL TABLE _idle_slices_cpu012 -USING - SPAN_OUTER_JOIN(_idle_slices_cpu01, _idle_slices_cpu2); - -CREATE VIRTUAL TABLE _idle_slices_cpu0123 -USING - SPAN_OUTER_JOIN(_idle_slices_cpu012, _idle_slices_cpu3); - -CREATE VIRTUAL TABLE _idle_slices_cpu01234 -USING - SPAN_OUTER_JOIN(_idle_slices_cpu0123, _idle_slices_cpu4); - -CREATE VIRTUAL TABLE _idle_slices_cpu012345 -USING - SPAN_OUTER_JOIN(_idle_slices_cpu01234, _idle_slices_cpu5); - -CREATE VIRTUAL TABLE _idle_slices_cpu0123456 -USING - SPAN_OUTER_JOIN(_idle_slices_cpu012345, _idle_slices_cpu6); - -CREATE VIRTUAL TABLE _idle_slices_cpu01234567 -USING - SPAN_OUTER_JOIN(_idle_slices_cpu0123456, _idle_slices_cpu7); - --- Table that holds time slices of the entire trace with the idle state --- transition information of every CPU in the system. -CREATE PERFETTO TABLE _cpu_idle_all -AS -SELECT * FROM _idle_slices_cpu01234567; - diff --git a/src/trace_processor/perfetto_sql/stdlib/wattson/cpu_split.sql b/src/trace_processor/perfetto_sql/stdlib/wattson/cpu_split.sql new file mode 100644 index 0000000000..8c496d9b5e --- /dev/null +++ b/src/trace_processor/perfetto_sql/stdlib/wattson/cpu_split.sql @@ -0,0 +1,220 @@ +-- +-- 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 linux.cpu.frequency; +INCLUDE PERFETTO MODULE time.conversion; +INCLUDE PERFETTO MODULE wattson.arm_dsu; +INCLUDE PERFETTO MODULE wattson.cpu_idle; +INCLUDE PERFETTO MODULE wattson.curves.utils; +INCLUDE PERFETTO MODULE wattson.device_infos; + +CREATE PERFETTO TABLE _cpu_freq +AS +SELECT + ts, + dur, + freq, + cf.cpu, + d_map.policy +FROM cpu_frequency_counters as cf +JOIN _dev_cpu_policy_map as d_map +ON cf.cpu = d_map.cpu; + +-- Combines idle and freq tables of all CPUs to create system state. +CREATE VIRTUAL TABLE _idle_freq +USING + SPAN_OUTER_JOIN( + _cpu_freq partitioned cpu, _adjusted_deep_idle partitioned cpu + ); + +-- Add extra column indicating that frequency info are present +CREATE PERFETTO TABLE _valid_window +AS +WITH window_start AS ( + SELECT ts as start_ts + FROM _idle_freq + WHERE cpu = 0 and freq GLOB '*[0-9]*' + ORDER BY ts ASC + LIMIT 1 +), +window_end AS ( + SELECT ts + dur as end_ts + FROM cpu_frequency_counters + ORDER by ts DESC + LIMIT 1 +) +SELECT + start_ts as ts, + end_ts - start_ts as dur +FROM window_start, window_end; + +CREATE VIRTUAL TABLE _idle_freq_filtered +USING + SPAN_JOIN(_valid_window, _idle_freq); + +-- Start matching split CPUs with curves +CREATE PERFETTO TABLE _idle_freq_materialized +AS +SELECT + iff.ts, iff.dur, iff.cpu, iff.policy, iff.freq, iff.idle, lut.curve_value +FROM _idle_freq_filtered iff +-- Left join since some CPUs may only match the 2D LUT +LEFT JOIN _filtered_curves_1d lut ON + iff.policy = lut.policy AND + iff.freq = lut.freq_khz AND + iff.idle = lut.idle; + +CREATE PERFETTO TABLE _stats_cpu0 +AS +SELECT + ts, + dur, + curve_value as cpu0_curve, + freq as freq_0, + idle as idle_0 +FROM _idle_freq_materialized +WHERE cpu = 0; + +CREATE PERFETTO TABLE _stats_cpu1 +AS +SELECT + ts, + dur, + curve_value as cpu1_curve, + freq as freq_1, + idle as idle_1 +FROM _idle_freq_materialized +WHERE cpu = 1; + +CREATE PERFETTO TABLE _stats_cpu2 +AS +SELECT + ts, + dur, + curve_value as cpu2_curve, + freq as freq_2, + idle as idle_2 +FROM _idle_freq_materialized +WHERE cpu = 2; + +CREATE PERFETTO TABLE _stats_cpu3 +AS +SELECT + ts, + dur, + curve_value as cpu3_curve, + freq as freq_3, + idle as idle_3 +FROM _idle_freq_materialized +WHERE cpu = 3; + +CREATE PERFETTO TABLE _stats_cpu4 +AS +SELECT + ts, + dur, + policy as policy_4, + curve_value as cpu4_curve, + freq as freq_4, + idle as idle_4 +FROM _idle_freq_materialized +WHERE cpu = 4; + +CREATE PERFETTO TABLE _stats_cpu5 +AS +SELECT + ts, + dur, + policy as policy_5, + curve_value as cpu5_curve, + freq as freq_5, + idle as idle_5 +FROM _idle_freq_materialized +WHERE cpu = 5; + +CREATE PERFETTO TABLE _stats_cpu6 +AS +SELECT + ts, + dur, + policy as policy_6, + curve_value as cpu6_curve, + freq as freq_6, + idle as idle_6 +FROM _idle_freq_materialized +WHERE cpu = 6; + +CREATE PERFETTO TABLE _stats_cpu7 +AS +SELECT + ts, + dur, + policy as policy_7, + curve_value as cpu7_curve, + freq as freq_7, + idle as idle_7 +FROM _idle_freq_materialized +WHERE cpu = 7; + +CREATE VIRTUAL TABLE _stats_cpu01 +USING + SPAN_OUTER_JOIN(_stats_cpu1, _stats_cpu0); + +CREATE VIRTUAL TABLE _stats_cpu012 +USING + SPAN_OUTER_JOIN(_stats_cpu2, _stats_cpu01); + +CREATE VIRTUAL TABLE _stats_cpu0123 +USING + SPAN_OUTER_JOIN(_stats_cpu3, _stats_cpu012); + +CREATE VIRTUAL TABLE _stats_cpu01234 +USING + SPAN_OUTER_JOIN(_stats_cpu4, _stats_cpu0123); + +CREATE VIRTUAL TABLE _stats_cpu012345 +USING + SPAN_OUTER_JOIN(_stats_cpu5, _stats_cpu01234); + +CREATE VIRTUAL TABLE _stats_cpu0123456 +USING + SPAN_OUTER_JOIN(_stats_cpu6, _stats_cpu012345); + +CREATE VIRTUAL TABLE _stats_cpu01234567 +USING + SPAN_OUTER_JOIN(_stats_cpu7, _stats_cpu0123456); + +-- get suspend resume state as logged by ftrace. +CREATE PERFETTO TABLE _suspend_slice +AS +SELECT + ts, dur, TRUE AS suspended +FROM slice +WHERE name GLOB "timekeeping_freeze(0)"; + +-- Combine suspend information with CPU idle and frequency system states. +CREATE VIRTUAL TABLE _idle_freq_suspend_slice +USING + SPAN_OUTER_JOIN(_stats_cpu01234567, _suspend_slice); + +-- Combine system state so that it has idle, freq, and L3 hit info. +CREATE VIRTUAL TABLE _idle_freq_l3_hit_slice +USING + SPAN_OUTER_JOIN(_idle_freq_suspend_slice, _arm_l3_hit_rate); + +-- Combine system state so that it has idle, freq, L3 hit, and L3 miss info. +CREATE VIRTUAL TABLE _idle_freq_l3_hit_l3_miss_slice +USING + SPAN_OUTER_JOIN(_idle_freq_l3_hit_slice, _arm_l3_miss_rate); diff --git a/src/trace_processor/perfetto_sql/stdlib/wattson/curves/device.sql b/src/trace_processor/perfetto_sql/stdlib/wattson/curves/device.sql new file mode 100644 index 0000000000..de8e173188 --- /dev/null +++ b/src/trace_processor/perfetto_sql/stdlib/wattson/curves/device.sql @@ -0,0 +1,578 @@ +-- +-- 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. + +-- Device specific device curves with 1D dependency (i.e. curve characteristics +-- are dependent only on one CPU policy). See go/wattson for more info. +CREATE PERFETTO TABLE _device_curves_1d +AS +WITH data(device, policy, freq_khz, static, active, idle0, idle1) AS ( + VALUES + -- 255 represents static curve; needs to be same type as rest + ("monaco", 0, 614400, 4.8, 9.41, 0.76, 0), + ("monaco", 0, 864000, 6.68, 13.64, 0.83, 0), + ("monaco", 0, 1363200, 12.6, 24.83, 1.1, 0), + ("monaco", 0, 1708800, 18.39, 39.69, 1.34, 0), + ("Tensor", 4, 400000, 0, 28.51, 5.24, 0), + ("Tensor", 4, 553000, 0, 43.63, 6.1, 0), + ("Tensor", 4, 696000, 0, 54.73, 6.76, 0), + ("Tensor", 4, 799000, 0, 65.01, 6.89, 0), + ("Tensor", 4, 910000, 0, 80.33, 7.61, 0), + ("Tensor", 4, 1024000, 0, 92.91, 8.32, 0), + ("Tensor", 4, 1197000, 0, 132.46, 8.09, 0), + ("Tensor", 4, 1328000, 0, 146.82, 9.6, 0), + ("Tensor", 4, 1491000, 0, 183.2, 11.58, 0), + ("Tensor", 4, 1663000, 0, 238.55, 12.02, 0), + ("Tensor", 4, 1836000, 0, 259.04, 16.11, 0), + ("Tensor", 4, 1999000, 0, 361.98, 15.7, 0), + ("Tensor", 4, 2130000, 0, 428.51, 18.94, 0), + ("Tensor", 4, 2253000, 0, 527.05, 23.5, 0), + ("Tensor", 6, 500000, 0, 87.31, 16.14, 0), + ("Tensor", 6, 851000, 0, 170.29, 19.88, 0), + ("Tensor", 6, 984000, 0, 207.43, 20.55, 0), + ("Tensor", 6, 1106000, 0, 251.88, 23.06, 0), + ("Tensor", 6, 1277000, 0, 306.57, 25.12, 0), + ("Tensor", 6, 1426000, 0, 382.61, 26.76, 0), + ("Tensor", 6, 1582000, 0, 465.9, 29.74, 0), + ("Tensor", 6, 1745000, 0, 556.25, 32.87, 0), + ("Tensor", 6, 1826000, 0, 613.51, 36.01, 0), + ("Tensor", 6, 2048000, 0, 758.89, 41.67, 0), + ("Tensor", 6, 2188000, 0, 874.03, 47.92, 0), + ("Tensor", 6, 2252000, 0, 949.55, 51.07, 0), + ("Tensor", 6, 2401000, 0, 1099.53, 57.42, 0), + ("Tensor", 6, 2507000, 0, 1267.19, 66.14, 0), + ("Tensor", 6, 2630000, 0, 1500.6, 82.36, 0), + ("Tensor", 6, 2704000, 0, 1660.81, 95.11, 0), + ("Tensor", 6, 2802000, 0, 1942.89, 121.43, 0) +) +select * from data; + +-- Device specific device curves with 2D dependency (i.e. curve characteristics +-- are dependent on another CPU policy). See go/wattson for more info. +CREATE PERFETTO TABLE _device_curves_2d +AS +WITH data(device, freq_khz, other_policy, other_freq_khz, static, active, idle0, idle1) +AS ( + VALUES + -- 255 represents static curve; needs to be same type as rest + ("Tensor", 300000, 4, 400000, 3.73, 21.84, 0.47, 0), + ("Tensor", 300000, 4, 553000, 5.66, 18.97, 0.99, 0), + ("Tensor", 300000, 6, 500000, 2.61, 22.89, 0.76, 0), + ("Tensor", 574000, 4, 400000, 5.73, 35.85, 0.93, 0), + ("Tensor", 574000, 4, 553000, 5.41, 36.54, 0.98, 0), + ("Tensor", 574000, 4, 696000, 5.61, 32.98, 0.99, 0), + ("Tensor", 574000, 4, 799000, 9.7, 40.29, 1.33, 0), + ("Tensor", 574000, 4, 910000, 9.81, 44.42, 1.24, 0), + ("Tensor", 574000, 4, 1024000, 9.71, 43.95, 1.31, 0), + ("Tensor", 574000, 6, 500000, 5.6, 34.69, 1.03, 0), + ("Tensor", 574000, 6, 851000, 5.57, 33.66, 1.02, 0), + ("Tensor", 574000, 6, 984000, 5.68, 36.2, 0.98, 0), + ("Tensor", 574000, 6, 1106000, 5.59, 36.27, 1.02, 0), + ("Tensor", 738000, 4, 400000, 6.62, 47.66, 1.08, 0), + ("Tensor", 738000, 4, 553000, 6.7, 45.71, 1.03, 0), + ("Tensor", 738000, 4, 696000, 6.7, 46.21, 1.04, 0), + ("Tensor", 738000, 4, 799000, 9.8, 55.47, 1.23, 0), + ("Tensor", 738000, 4, 910000, 9.69, 52.58, 1.31, 0), + ("Tensor", 738000, 4, 1024000, 9.77, 54.81, 1.3, 0), + ("Tensor", 738000, 4, 1197000, 18.75, 75.3, 2.05, 0), + ("Tensor", 738000, 4, 1328000, 18.98, 75.84, 1.91, 0), + ("Tensor", 738000, 6, 500000, 6.63, 44.56, 1.11, 0), + ("Tensor", 738000, 6, 851000, 6.65, 46.62, 1.08, 0), + ("Tensor", 738000, 6, 984000, 6.63, 50.28, 1.08, 0), + ("Tensor", 738000, 6, 1106000, 6.74, 44.83, 1.07, 0), + ("Tensor", 738000, 6, 1277000, 6.6, 44.15, 1.09, 0), + ("Tensor", 738000, 6, 1426000, 18.97, 74.73, 1.94, 0), + ("Tensor", 930000, 4, 400000, 9.64, 81.16, 1.27, 0), + ("Tensor", 930000, 4, 553000, 9.88, 67.4, 1.28, 0), + ("Tensor", 930000, 4, 696000, 9.69, 67.33, 1.3, 0), + ("Tensor", 930000, 4, 799000, 9.69, 67.82, 1.3, 0), + ("Tensor", 930000, 4, 910000, 9.79, 67.52, 1.29, 0), + ("Tensor", 930000, 4, 1024000, 9.75, 65.44, 1.28, 0), + ("Tensor", 930000, 4, 1197000, 18.84, 83.73, 2.0, 0), + ("Tensor", 930000, 4, 1328000, 18.88, 101.57, 1.97, 0), + ("Tensor", 930000, 4, 1491000, 18.86, 94.45, 1.99, 0), + ("Tensor", 930000, 4, 1663000, 35.46, 134.93, 3.29, 0), + ("Tensor", 930000, 4, 1836000, 35.34, 135.55, 3.36, 0), + ("Tensor", 930000, 6, 500000, 9.76, 66.0, 1.28, 0), + ("Tensor", 930000, 6, 851000, 9.8, 73.08, 1.24, 0), + ("Tensor", 930000, 6, 984000, 9.75, 74.87, 1.25, 0), + ("Tensor", 930000, 6, 1106000, 9.68, 77.31, 1.3, 0), + ("Tensor", 930000, 6, 1277000, 9.83, 80.03, 1.25, 0), + ("Tensor", 930000, 6, 1426000, 19.01, 98.31, 1.94, 0), + ("Tensor", 930000, 6, 1582000, 18.94, 94.51, 1.98, 0), + ("Tensor", 930000, 6, 1745000, 19.0, 94.38, 1.93, 0), + ("Tensor", 930000, 6, 1826000, 18.98, 100.84, 1.92, 0), + ("Tensor", 1098000, 4, 400000, 12.93, 109.45, 1.47, 0), + ("Tensor", 1098000, 4, 553000, 12.92, 120.82, 1.48, 0), + ("Tensor", 1098000, 4, 696000, 13.09, 107.17, 1.41, 0), + ("Tensor", 1098000, 4, 799000, 12.82, 91.84, 1.56, 0), + ("Tensor", 1098000, 4, 910000, 12.88, 99.1, 1.52, 0), + ("Tensor", 1098000, 4, 1024000, 12.81, 87.32, 1.57, 0), + ("Tensor", 1098000, 4, 1197000, 18.92, 115.83, 1.97, 0), + ("Tensor", 1098000, 4, 1328000, 18.97, 137.08, 1.93, 0), + ("Tensor", 1098000, 4, 1491000, 18.94, 120.36, 1.94, 0), + ("Tensor", 1098000, 4, 1663000, 35.21, 156.0, 3.43, 0), + ("Tensor", 1098000, 4, 1836000, 35.21, 155.3, 3.42, 0), + ("Tensor", 1098000, 4, 1999000, 35.49, 157.04, 3.24, 0), + ("Tensor", 1098000, 4, 2130000, 35.17, 156.91, 3.41, 0), + ("Tensor", 1098000, 6, 500000, 13.0, 93.54, 1.45, 0), + ("Tensor", 1098000, 6, 851000, 13.12, 104.28, 1.4, 0), + ("Tensor", 1098000, 6, 984000, 12.85, 94.73, 1.52, 0), + ("Tensor", 1098000, 6, 1106000, 12.68, 95.73, 1.6, 0), + ("Tensor", 1098000, 6, 1277000, 12.94, 92.78, 1.46, 0), + ("Tensor", 1098000, 6, 1426000, 18.81, 128.5, 2.03, 0), + ("Tensor", 1098000, 6, 1582000, 19.0, 124.51, 1.89, 0), + ("Tensor", 1098000, 6, 1745000, 18.75, 121.84, 2.0, 0), + ("Tensor", 1098000, 6, 1826000, 19.01, 117.69, 1.9, 0), + ("Tensor", 1098000, 6, 2048000, 18.97, 107.49, 1.89, 0), + ("Tensor", 1098000, 6, 2188000, 18.95, 124.24, 1.92, 0), + ("Tensor", 1197000, 4, 400000, 14.5, 128.64, 1.54, 0), + ("Tensor", 1197000, 4, 553000, 14.41, 126.94, 1.58, 0), + ("Tensor", 1197000, 4, 696000, 14.43, 123.96, 1.63, 0), + ("Tensor", 1197000, 4, 799000, 14.39, 125.32, 1.59, 0), + ("Tensor", 1197000, 4, 910000, 14.42, 126.37, 1.55, 0), + ("Tensor", 1197000, 4, 1024000, 14.5, 110.43, 1.54, 0), + ("Tensor", 1197000, 4, 1197000, 19.0, 121.68, 1.9, 222.0), + ("Tensor", 1197000, 4, 1328000, 18.88, 122.27, 1.96, 0), + ("Tensor", 1197000, 4, 1491000, 18.84, 118.62, 1.98, 0), + ("Tensor", 1197000, 4, 1663000, 35.35, 175.31, 3.32, 0), + ("Tensor", 1197000, 4, 1836000, 35.37, 178.17, 3.38, 0), + ("Tensor", 1197000, 4, 1999000, 35.34, 186.68, 3.38, 0), + ("Tensor", 1197000, 4, 2130000, 35.37, 176.06, 3.34, 0), + ("Tensor", 1197000, 4, 2253000, 35.29, 169.24, 3.38, 111.0), + ("Tensor", 1197000, 6, 500000, 14.47, 95.77, 1.55, 0), + ("Tensor", 1197000, 6, 851000, 14.42, 101.17, 1.6, 0), + ("Tensor", 1197000, 6, 984000, 14.21, 116.52, 1.68, 0), + ("Tensor", 1197000, 6, 1106000, 14.32, 111.16, 1.62, 0), + ("Tensor", 1197000, 6, 1277000, 14.42, 84.46, 1.6, 0), + ("Tensor", 1197000, 6, 1426000, 18.83, 130.44, 2.01, 0), + ("Tensor", 1197000, 6, 1582000, 18.98, 140.9, 1.9, 0), + ("Tensor", 1197000, 6, 1745000, 18.82, 143.87, 1.94, 0), + ("Tensor", 1197000, 6, 1826000, 18.91, 131.75, 1.96, 0), + ("Tensor", 1197000, 6, 2048000, 18.99, 128.36, 1.96, 0), + ("Tensor", 1197000, 6, 2188000, 18.71, 132.46, 2.07, 0), + ("Tensor", 1197000, 6, 2252000, 18.82, 130.95, 2.0, 0), + ("Tensor", 1328000, 4, 400000, 17.0, 135.89, 1.84, 0), + ("Tensor", 1328000, 4, 553000, 17.1, 161.84, 1.78, 0), + ("Tensor", 1328000, 4, 696000, 16.99, 142.03, 1.87, 0), + ("Tensor", 1328000, 4, 799000, 17.07, 169.36, 1.83, 0), + ("Tensor", 1328000, 4, 910000, 17.19, 111.73, 1.81, 0), + ("Tensor", 1328000, 4, 1024000, 17.21, 128.66, 1.78, 0), + ("Tensor", 1328000, 4, 1197000, 18.83, 129.66, 2.02, 0), + ("Tensor", 1328000, 4, 1328000, 18.88, 132.55, 1.96, 0), + ("Tensor", 1328000, 4, 1491000, 18.87, 146.14, 2.0, 0), + ("Tensor", 1328000, 4, 1663000, 35.43, 185.94, 3.27, 0), + ("Tensor", 1328000, 4, 1836000, 35.46, 165.55, 3.27, 0), + ("Tensor", 1328000, 4, 1999000, 35.37, 186.76, 3.29, 0), + ("Tensor", 1328000, 4, 2130000, 35.35, 207.2, 3.34, 0), + ("Tensor", 1328000, 4, 2253000, 35.31, 209.73, 3.42, 0), + ("Tensor", 1328000, 6, 500000, 17.15, 130.76, 1.77, 0), + ("Tensor", 1328000, 6, 851000, 17.06, 123.6, 1.84, 0), + ("Tensor", 1328000, 6, 984000, 17.21, 130.23, 1.77, 0), + ("Tensor", 1328000, 6, 1106000, 17.16, 139.65, 1.84, 0), + ("Tensor", 1328000, 6, 1277000, 17.14, 123.95, 1.83, 0), + ("Tensor", 1328000, 6, 1426000, 19.15, 141.04, 1.91, 0), + ("Tensor", 1328000, 6, 1582000, 19.13, 108.29, 1.91, 0), + ("Tensor", 1328000, 6, 1745000, 19.12, 133.38, 1.9, 0), + ("Tensor", 1328000, 6, 1826000, 18.87, 137.51, 2.06, 0), + ("Tensor", 1328000, 6, 2048000, 19.02, 145.9, 1.96, 0), + ("Tensor", 1328000, 6, 2188000, 19.06, 129.5, 1.94, 0), + ("Tensor", 1328000, 6, 2252000, 19.05, 125.72, 1.91, 0), + ("Tensor", 1328000, 6, 2401000, 35.57, 187.29, 3.33, 0), + ("Tensor", 1328000, 6, 2507000, 35.38, 213.14, 3.44, 0), + ("Tensor", 1328000, 6, 2630000, 35.47, 181.15, 3.41, 0), + ("Tensor", 1401000, 4, 400000, 18.85, 184.12, 2.06, 0), + ("Tensor", 1401000, 4, 553000, 18.91, 168.23, 1.98, 0), + ("Tensor", 1401000, 4, 696000, 19.11, 184.69, 1.92, 0), + ("Tensor", 1401000, 4, 799000, 19.16, 175.13, 1.91, 0), + ("Tensor", 1401000, 4, 910000, 19.02, 161.7, 1.97, 0), + ("Tensor", 1401000, 4, 1024000, 18.97, 156.68, 2.01, 0), + ("Tensor", 1401000, 4, 1197000, 19.07, 155.0, 1.97, 0), + ("Tensor", 1401000, 4, 1328000, 18.95, 159.64, 1.96, 0), + ("Tensor", 1401000, 4, 1491000, 19.13, 136.78, 1.95, 0), + ("Tensor", 1401000, 4, 1663000, 35.67, 186.73, 3.29, 0), + ("Tensor", 1401000, 4, 1836000, 35.51, 220.26, 3.45, 0), + ("Tensor", 1401000, 4, 1999000, 35.75, 249.18, 3.3, 0), + ("Tensor", 1401000, 4, 2130000, 35.65, 217.48, 3.4, 0), + ("Tensor", 1401000, 4, 2253000, 35.66, 248.9, 3.41, 0), + ("Tensor", 1401000, 6, 500000, 19.05, 152.39, 1.98, 0), + ("Tensor", 1401000, 6, 851000, 19.0, 148.12, 2.03, 0), + ("Tensor", 1401000, 6, 984000, 19.01, 128.71, 2.0, 0), + ("Tensor", 1401000, 6, 1106000, 18.18, 132.83, 2.01, 0), + ("Tensor", 1401000, 6, 1277000, 19.07, 138.09, 1.95, 0), + ("Tensor", 1401000, 6, 1426000, 18.92, 144.69, 2.05, 0), + ("Tensor", 1401000, 6, 1582000, 18.95, 151.34, 2.05, 0), + ("Tensor", 1401000, 6, 1745000, 18.98, 152.04, 2.01, 0), + ("Tensor", 1401000, 6, 1826000, 19.11, 151.71, 1.95, 0), + ("Tensor", 1401000, 6, 2048000, 19.04, 136.69, 1.98, 0), + ("Tensor", 1401000, 6, 2188000, 18.97, 152.56, 2.0, 0), + ("Tensor", 1401000, 6, 2252000, 19.09, 149.02, 1.97, 0), + ("Tensor", 1401000, 6, 2401000, 35.91, 210.3, 3.23, 0), + ("Tensor", 1401000, 6, 2507000, 35.64, 188.64, 3.32, 0), + ("Tensor", 1401000, 6, 2630000, 35.41, 202.75, 3.5, 0), + ("Tensor", 1401000, 6, 2704000, 35.69, 204.49, 3.4, 0), + ("Tensor", 1401000, 6, 2802000, 35.64, 208.14, 3.45, 0), + ("Tensor", 1598000, 4, 400000, 24.83, 196.05, 2.36, 0), + ("Tensor", 1598000, 4, 553000, 24.68, 234.53, 2.37, 0), + ("Tensor", 1598000, 4, 696000, 24.71, 230.15, 2.34, 0), + ("Tensor", 1598000, 4, 799000, 24.87, 175.64, 2.34, 0), + ("Tensor", 1598000, 4, 910000, 24.76, 228.23, 2.36, 0), + ("Tensor", 1598000, 4, 1024000, 24.6, 228.37, 2.47, 0), + ("Tensor", 1598000, 4, 1197000, 24.77, 201.12, 2.43, 0), + ("Tensor", 1598000, 4, 1328000, 24.68, 202.37, 2.41, 0), + ("Tensor", 1598000, 4, 1491000, 24.58, 199.78, 2.52, 0), + ("Tensor", 1598000, 4, 1663000, 35.59, 210.2, 3.46, 0), + ("Tensor", 1598000, 4, 1836000, 35.74, 315.02, 3.33, 0), + ("Tensor", 1598000, 4, 1999000, 35.65, 285.37, 3.44, 0), + ("Tensor", 1598000, 4, 2130000, 35.31, 256.84, 3.7, 0), + ("Tensor", 1598000, 4, 2253000, 35.91, 255.65, 3.37, 0), + ("Tensor", 1598000, 6, 500000, 24.78, 184.21, 2.34, 0), + ("Tensor", 1598000, 6, 851000, 24.73, 175.69, 2.41, 0), + ("Tensor", 1598000, 6, 984000, 24.68, 195.14, 2.43, 0), + ("Tensor", 1598000, 6, 1106000, 24.65, 194.89, 2.46, 0), + ("Tensor", 1598000, 6, 1277000, 24.63, 167.1, 2.49, 0), + ("Tensor", 1598000, 6, 1426000, 24.7, 190.42, 2.45, 0), + ("Tensor", 1598000, 6, 1582000, 24.79, 190.72, 2.39, 0), + ("Tensor", 1598000, 6, 1745000, 24.73, 180.52, 2.44, 0), + ("Tensor", 1598000, 6, 1826000, 24.72, 203.15, 2.4, 0), + ("Tensor", 1598000, 6, 2048000, 24.82, 197.7, 2.39, 0), + ("Tensor", 1598000, 6, 2188000, 24.7, 185.45, 2.47, 0), + ("Tensor", 1598000, 6, 2252000, 24.83, 155.38, 2.35, 0), + ("Tensor", 1598000, 6, 2401000, 36.0, 237.12, 3.25, 0), + ("Tensor", 1598000, 6, 2507000, 35.89, 253.55, 3.34, 0), + ("Tensor", 1598000, 6, 2630000, 35.76, 208.38, 3.45, 0), + ("Tensor", 1598000, 6, 2704000, 35.7, 218.73, 3.46, 0), + ("Tensor", 1598000, 6, 2802000, 35.65, 248.51, 3.47, 0), + ("Tensor", 1704000, 4, 400000, 28.98, 234.84, 2.73, 0), + ("Tensor", 1704000, 4, 553000, 29.01, 210.31, 2.66, 0), + ("Tensor", 1704000, 4, 696000, 28.95, 300.74, 2.73, 0), + ("Tensor", 1704000, 4, 799000, 28.77, 270.96, 2.79, 0), + ("Tensor", 1704000, 4, 910000, 28.84, 284.84, 2.76, 0), + ("Tensor", 1704000, 4, 1024000, 28.76, 251.86, 2.85, 0), + ("Tensor", 1704000, 4, 1197000, 28.75, 256.3, 2.78, 0), + ("Tensor", 1704000, 4, 1328000, 28.65, 246.88, 2.86, 0), + ("Tensor", 1704000, 4, 1491000, 28.73, 267.07, 2.88, 0), + ("Tensor", 1704000, 4, 1663000, 35.81, 266.03, 3.49, 0), + ("Tensor", 1704000, 4, 1836000, 35.78, 274.06, 3.35, 0), + ("Tensor", 1704000, 4, 1999000, 35.67, 268.14, 3.46, 0), + ("Tensor", 1704000, 4, 2130000, 35.75, 273.4, 3.41, 0), + ("Tensor", 1704000, 4, 2253000, 35.42, 276.92, 3.72, 0), + ("Tensor", 1704000, 6, 500000, 29.1, 239.74, 2.65, 0), + ("Tensor", 1704000, 6, 851000, 28.79, 216.53, 2.74, 0), + ("Tensor", 1704000, 6, 984000, 28.9, 259.03, 2.76, 0), + ("Tensor", 1704000, 6, 1106000, 28.71, 211.76, 2.82, 0), + ("Tensor", 1704000, 6, 1277000, 28.79, 216.77, 2.8, 0), + ("Tensor", 1704000, 6, 1426000, 28.94, 207.8, 2.71, 0), + ("Tensor", 1704000, 6, 1582000, 28.96, 232.83, 2.67, 0), + ("Tensor", 1704000, 6, 1745000, 28.67, 237.37, 2.85, 0), + ("Tensor", 1704000, 6, 1826000, 29.0, 224.71, 2.71, 0), + ("Tensor", 1704000, 6, 2048000, 28.86, 239.69, 2.73, 0), + ("Tensor", 1704000, 6, 2188000, 28.88, 218.8, 2.76, 0), + ("Tensor", 1704000, 6, 2252000, 28.87, 272.23, 2.76, 0), + ("Tensor", 1704000, 6, 2401000, 35.74, 258.98, 3.33, 0), + ("Tensor", 1704000, 6, 2507000, 35.74, 276.92, 3.4, 0), + ("Tensor", 1704000, 6, 2630000, 35.71, 249.7, 3.45, 0), + ("Tensor", 1704000, 6, 2704000, 36.01, 253.04, 3.29, 0), + ("Tensor", 1704000, 6, 2802000, 35.91, 266.15, 3.4, 0), + ("Tensor", 1803000, 4, 400000, 35.71, 342.95, 3.49, 0), + ("Tensor", 1803000, 4, 553000, 35.76, 330.57, 3.41, 0), + ("Tensor", 1803000, 4, 696000, 35.71, 355.0, 3.41, 0), + ("Tensor", 1803000, 4, 799000, 35.67, 310.42, 3.45, 0), + ("Tensor", 1803000, 4, 910000, 35.95, 309.22, 3.38, 0), + ("Tensor", 1803000, 4, 1024000, 35.6, 303.94, 3.55, 0), + ("Tensor", 1803000, 4, 1197000, 36.0, 346.31, 3.26, 0), + ("Tensor", 1803000, 4, 1328000, 35.9, 300.16, 3.36, 0), + ("Tensor", 1803000, 4, 1491000, 35.88, 215.33, 3.33, 0), + ("Tensor", 1803000, 4, 1663000, 35.72, 284.35, 3.47, 0), + ("Tensor", 1803000, 4, 1836000, 35.9, 289.0, 3.32, 0), + ("Tensor", 1803000, 4, 1999000, 34.96, 293.38, 3.33, 0), + ("Tensor", 1803000, 4, 2130000, 35.07, 359.86, 3.19, 0), + ("Tensor", 1803000, 4, 2253000, 35.07, 295.24, 3.23, 0), + ("Tensor", 1803000, 6, 500000, 34.68, 223.89, 3.4, 0), + ("Tensor", 1803000, 6, 851000, 34.74, 261.39, 3.4, 0), + ("Tensor", 1803000, 6, 984000, 35.08, 269.51, 3.26, 0), + ("Tensor", 1803000, 6, 1106000, 35.06, 269.58, 3.21, 0), + ("Tensor", 1803000, 6, 1277000, 34.87, 218.3, 3.39, 0), + ("Tensor", 1803000, 6, 1426000, 34.86, 264.34, 3.36, 0), + ("Tensor", 1803000, 6, 1582000, 34.9, 263.56, 3.36, 0), + ("Tensor", 1803000, 6, 1745000, 35.09, 210.36, 3.29, 0), + ("Tensor", 1803000, 6, 1826000, 35.06, 256.1, 3.34, 0), + ("Tensor", 1803000, 6, 2048000, 35.18, 269.91, 3.16, 0), + ("Tensor", 1803000, 6, 2188000, 35.16, 261.04, 3.25, 0), + ("Tensor", 1803000, 6, 2252000, 34.84, 272.92, 3.49, 0), + ("Tensor", 1803000, 6, 2401000, 35.2, 260.24, 3.38, 0), + ("Tensor", 1803000, 6, 2507000, 34.89, 240.7, 3.58, 0), + ("Tensor", 1803000, 6, 2630000, 35.21, 150.76, 3.42, 0), + ("Tensor", 1803000, 6, 2704000, 35.2, 277.28, 3.44, 0), + ("Tensor", 1803000, 6, 2802000, 35.12, 269.2, 3.62, 0) +) +select * from data; + +CREATE PERFETTO TABLE _device_curves_l3 +AS +WITH data(device, freq_khz, other_policy, other_freq_khz, l3_hit, l3_miss) AS ( + VALUES + ("Tensor", 300000, 4, 400000, 0.3989, 0.0629), + ("Tensor", 300000, 4, 553000, 0.4119, 0.0656), + ("Tensor", 300000, 6, 500000, 0.3298, 0.1029), + ("Tensor", 574000, 4, 400000, 0.4894, 0.0239), + ("Tensor", 574000, 4, 553000, 0.4991, 0.0960), + ("Tensor", 574000, 4, 696000, 0.4949, 0.0971), + ("Tensor", 574000, 4, 799000, 0.6116, 0.1266), + ("Tensor", 574000, 4, 910000, 0.5897, 0.1385), + ("Tensor", 574000, 4, 1024000, 0.5619, 0.0635), + ("Tensor", 574000, 6, 500000, 0.5377, 0.1210), + ("Tensor", 574000, 6, 851000, 0.5271, 0.1591), + ("Tensor", 574000, 6, 984000, 0.5395, 0.1599), + ("Tensor", 574000, 6, 1106000, 0.5552, 0.1393), + ("Tensor", 738000, 4, 400000, 0.5825, 0.1271), + ("Tensor", 738000, 4, 553000, 0.5751, 0.0396), + ("Tensor", 738000, 4, 696000, 0.6433, 0.1050), + ("Tensor", 738000, 4, 799000, 0.6401, 0.1293), + ("Tensor", 738000, 4, 910000, 0.7069, 0.1252), + ("Tensor", 738000, 4, 1024000, 0.6999, 0.1143), + ("Tensor", 738000, 4, 1197000, 0.9076, 0.1960), + ("Tensor", 738000, 4, 1328000, 0.9708, 0.1953), + ("Tensor", 738000, 6, 500000, 0.6437, 0.2086), + ("Tensor", 738000, 6, 851000, 0.6274, 0.1852), + ("Tensor", 738000, 6, 984000, 0.6231, 0.2066), + ("Tensor", 738000, 6, 1106000, 0.6256, 0.2199), + ("Tensor", 738000, 6, 1277000, 0.6719, 0.2485), + ("Tensor", 738000, 6, 1426000, 1.1072, 0.3483), + ("Tensor", 930000, 4, 400000, 0.7812, 0.1727), + ("Tensor", 930000, 4, 553000, 0.7343, 0.1846), + ("Tensor", 930000, 4, 696000, 0.7551, 0.2006), + ("Tensor", 930000, 4, 799000, 0.7330, 0.1864), + ("Tensor", 930000, 4, 910000, 0.8250, 0.1451), + ("Tensor", 930000, 4, 1024000, 0.7331, 0.2092), + ("Tensor", 930000, 4, 1197000, 1.0791, 0.4804), + ("Tensor", 930000, 4, 1328000, 1.0172, 0.0844), + ("Tensor", 930000, 4, 1491000, 1.0396, 0.2614), + ("Tensor", 930000, 4, 1663000, 1.6492, 0.3497), + ("Tensor", 930000, 4, 1836000, 1.5561, 0.3407), + ("Tensor", 930000, 6, 500000, 0.8530, 0.4182), + ("Tensor", 930000, 6, 851000, 0.8694, 0.2854), + ("Tensor", 930000, 6, 984000, 0.8620, 0.2568), + ("Tensor", 930000, 6, 1106000, 0.8763, 0.2336), + ("Tensor", 930000, 6, 1277000, 0.8717, 0.3756), + ("Tensor", 930000, 6, 1426000, 1.1774, 0.5021), + ("Tensor", 930000, 6, 1582000, 1.1264, 0.5799), + ("Tensor", 930000, 6, 1745000, 1.2303, 0.5421), + ("Tensor", 930000, 6, 1826000, 1.2330, 0.4498), + ("Tensor", 1098000, 4, 400000, 0.9744, 0.2106), + ("Tensor", 1098000, 4, 553000, 0.9980, 0.0500), + ("Tensor", 1098000, 4, 696000, 0.9500, 0.1928), + ("Tensor", 1098000, 4, 799000, 0.9132, 0.2391), + ("Tensor", 1098000, 4, 910000, 0.9922, 0.2576), + ("Tensor", 1098000, 4, 1024000, 0.9607, 0.2397), + ("Tensor", 1098000, 4, 1197000, 1.1253, 0.6195), + ("Tensor", 1098000, 4, 1328000, 1.1609, 0.0960), + ("Tensor", 1098000, 4, 1491000, 1.1783, 0.0851), + ("Tensor", 1098000, 4, 1663000, 1.6941, 0.4295), + ("Tensor", 1098000, 4, 1836000, 1.7152, 0.4610), + ("Tensor", 1098000, 4, 1999000, 1.7941, 0.4293), + ("Tensor", 1098000, 4, 2130000, 1.6758, 0.4437), + ("Tensor", 1098000, 6, 500000, 1.0485, 0.4038), + ("Tensor", 1098000, 6, 851000, 1.0510, 0.2815), + ("Tensor", 1098000, 6, 984000, 1.0785, 0.4137), + ("Tensor", 1098000, 6, 1106000, 1.0909, 0.3933), + ("Tensor", 1098000, 6, 1277000, 1.1533, 0.3811), + ("Tensor", 1098000, 6, 1426000, 1.2718, 0.3814), + ("Tensor", 1098000, 6, 1582000, 1.3463, 0.4100), + ("Tensor", 1098000, 6, 1745000, 1.3065, 0.5207), + ("Tensor", 1098000, 6, 1826000, 1.3456, 0.4903), + ("Tensor", 1098000, 6, 2048000, 1.3466, 0.7218), + ("Tensor", 1098000, 6, 2188000, 1.3132, 0.4923), + ("Tensor", 1197000, 4, 400000, 1.0507, 0.2411), + ("Tensor", 1197000, 4, 553000, 1.0387, 0.2875), + ("Tensor", 1197000, 4, 696000, 1.0173, 0.2232), + ("Tensor", 1197000, 4, 799000, 1.0160, 0.2418), + ("Tensor", 1197000, 4, 910000, 1.0555, 0.0966), + ("Tensor", 1197000, 4, 1024000, 1.0663, 0.0987), + ("Tensor", 1197000, 4, 1197000, 1.1885, 0.2852), + ("Tensor", 1197000, 4, 1328000, 1.2442, 0.2724), + ("Tensor", 1197000, 4, 1491000, 1.2474, 0.3269), + ("Tensor", 1197000, 4, 1663000, 1.8142, 0.3429), + ("Tensor", 1197000, 4, 1836000, 1.7692, 1.0737), + ("Tensor", 1197000, 4, 1999000, 1.7939, 0.1120), + ("Tensor", 1197000, 4, 2130000, 1.8126, 0.3744), + ("Tensor", 1197000, 4, 2253000, 1.7413, 0.5198), + ("Tensor", 1197000, 6, 500000, 1.1288, 0.6817), + ("Tensor", 1197000, 6, 851000, 1.1779, 0.5681), + ("Tensor", 1197000, 6, 984000, 1.1835, 0.3389), + ("Tensor", 1197000, 6, 1106000, 1.2115, 0.4506), + ("Tensor", 1197000, 6, 1277000, 1.1726, 0.8719), + ("Tensor", 1197000, 6, 1426000, 1.3825, 0.5140), + ("Tensor", 1197000, 6, 1582000, 1.4179, 0.3585), + ("Tensor", 1197000, 6, 1745000, 1.3804, 0.3197), + ("Tensor", 1197000, 6, 1826000, 1.3379, 0.5614), + ("Tensor", 1197000, 6, 2048000, 1.3335, 0.5443), + ("Tensor", 1197000, 6, 2188000, 1.4382, 0.5255), + ("Tensor", 1197000, 6, 2252000, 1.3961, 0.5423), + ("Tensor", 1328000, 4, 400000, 1.2307, 0.5565), + ("Tensor", 1328000, 4, 553000, 1.2186, 0.2366), + ("Tensor", 1328000, 4, 696000, 1.2243, 0.4145), + ("Tensor", 1328000, 4, 799000, 1.2620, 0.0973), + ("Tensor", 1328000, 4, 910000, 1.2462, 0.5669), + ("Tensor", 1328000, 4, 1024000, 1.2787, 0.2332), + ("Tensor", 1328000, 4, 1197000, 1.4364, 0.3260), + ("Tensor", 1328000, 4, 1328000, 1.3636, 0.3354), + ("Tensor", 1328000, 4, 1491000, 1.3733, 0.0512), + ("Tensor", 1328000, 4, 1663000, 1.9295, 0.4588), + ("Tensor", 1328000, 4, 1836000, 1.8278, 0.9316), + ("Tensor", 1328000, 4, 1999000, 1.9043, 0.4921), + ("Tensor", 1328000, 4, 2130000, 1.9144, 0.1139), + ("Tensor", 1328000, 4, 2253000, 1.9550, 0.0603), + ("Tensor", 1328000, 6, 500000, 1.3772, 0.5737), + ("Tensor", 1328000, 6, 851000, 1.3985, 0.6368), + ("Tensor", 1328000, 6, 984000, 1.3933, 0.5311), + ("Tensor", 1328000, 6, 1106000, 1.3932, 0.4567), + ("Tensor", 1328000, 6, 1277000, 1.3984, 0.6616), + ("Tensor", 1328000, 6, 1426000, 1.5067, 0.5776), + ("Tensor", 1328000, 6, 1582000, 1.5167, 1.0309), + ("Tensor", 1328000, 6, 1745000, 1.5021, 0.6845), + ("Tensor", 1328000, 6, 1826000, 1.4775, 0.6285), + ("Tensor", 1328000, 6, 2048000, 1.5237, 0.5402), + ("Tensor", 1328000, 6, 2188000, 1.5349, 0.7490), + ("Tensor", 1328000, 6, 2252000, 1.5436, 0.7984), + ("Tensor", 1328000, 6, 2401000, 2.1755, 1.0387), + ("Tensor", 1328000, 6, 2507000, 2.2320, 0.7382), + ("Tensor", 1328000, 6, 2630000, 2.2489, 1.1762), + ("Tensor", 1401000, 4, 400000, 1.3279, 0.2793), + ("Tensor", 1401000, 4, 553000, 1.3065, 0.3853), + ("Tensor", 1401000, 4, 696000, 1.3290, 0.3016), + ("Tensor", 1401000, 4, 799000, 1.2483, 0.3683), + ("Tensor", 1401000, 4, 910000, 1.4059, 0.2825), + ("Tensor", 1401000, 4, 1024000, 1.3702, 0.3389), + ("Tensor", 1401000, 4, 1197000, 1.3920, 0.3614), + ("Tensor", 1401000, 4, 1328000, 1.3752, 0.3310), + ("Tensor", 1401000, 4, 1491000, 1.4015, 0.6546), + ("Tensor", 1401000, 4, 1663000, 1.8982, 1.0324), + ("Tensor", 1401000, 4, 1836000, 1.9447, 0.5336), + ("Tensor", 1401000, 4, 1999000, 2.1219, 0.0662), + ("Tensor", 1401000, 4, 2130000, 1.9576, 0.5584), + ("Tensor", 1401000, 4, 2253000, 2.0221, 0.1254), + ("Tensor", 1401000, 6, 500000, 1.5283, 0.5764), + ("Tensor", 1401000, 6, 851000, 1.5211, 0.5643), + ("Tensor", 1401000, 6, 984000, 1.5574, 0.7558), + ("Tensor", 1401000, 6, 1106000, 1.5492, 0.7862), + ("Tensor", 1401000, 6, 1277000, 1.5389, 0.7523), + ("Tensor", 1401000, 6, 1426000, 1.6449, 0.5993), + ("Tensor", 1401000, 6, 1582000, 1.5953, 0.5512), + ("Tensor", 1401000, 6, 1745000, 1.5672, 0.5489), + ("Tensor", 1401000, 6, 1826000, 1.5639, 0.5507), + ("Tensor", 1401000, 6, 2048000, 1.5878, 0.7536), + ("Tensor", 1401000, 6, 2188000, 1.5562, 0.5431), + ("Tensor", 1401000, 6, 2252000, 1.5908, 0.6087), + ("Tensor", 1401000, 6, 2401000, 2.2693, 0.8953), + ("Tensor", 1401000, 6, 2507000, 2.3182, 1.2289), + ("Tensor", 1401000, 6, 2630000, 2.3090, 1.0687), + ("Tensor", 1401000, 6, 2704000, 2.2751, 0.9966), + ("Tensor", 1401000, 6, 2802000, 2.3278, 0.9065), + ("Tensor", 1598000, 4, 400000, 1.7424, 0.8926), + ("Tensor", 1598000, 4, 553000, 1.7003, 0.4482), + ("Tensor", 1598000, 4, 696000, 1.6099, 0.5281), + ("Tensor", 1598000, 4, 799000, 1.8018, 0.9634), + ("Tensor", 1598000, 4, 910000, 1.7615, 0.3445), + ("Tensor", 1598000, 4, 1024000, 1.7317, 0.3396), + ("Tensor", 1598000, 4, 1197000, 1.7293, 0.5079), + ("Tensor", 1598000, 4, 1328000, 1.8771, 0.4685), + ("Tensor", 1598000, 4, 1491000, 1.8724, 0.4693), + ("Tensor", 1598000, 4, 1663000, 1.9587, 1.2295), + ("Tensor", 1598000, 4, 1836000, 2.2287, 0.5220), + ("Tensor", 1598000, 4, 1999000, 2.1786, 0.1494), + ("Tensor", 1598000, 4, 2130000, 2.1631, 0.4924), + ("Tensor", 1598000, 4, 2253000, 2.1703, 0.5427), + ("Tensor", 1598000, 6, 500000, 1.9632, 0.9534), + ("Tensor", 1598000, 6, 851000, 1.9820, 0.9433), + ("Tensor", 1598000, 6, 984000, 1.9745, 0.8002), + ("Tensor", 1598000, 6, 1106000, 1.9514, 0.8323), + ("Tensor", 1598000, 6, 1277000, 1.9796, 1.1016), + ("Tensor", 1598000, 6, 1426000, 1.9432, 0.8556), + ("Tensor", 1598000, 6, 1582000, 2.0700, 0.8211), + ("Tensor", 1598000, 6, 1745000, 2.0052, 0.9492), + ("Tensor", 1598000, 6, 1826000, 2.0165, 0.7016), + ("Tensor", 1598000, 6, 2048000, 2.0881, 0.6641), + ("Tensor", 1598000, 6, 2188000, 2.1239, 0.8702), + ("Tensor", 1598000, 6, 2252000, 2.0952, 1.1728), + ("Tensor", 1598000, 6, 2401000, 2.4810, 0.9498), + ("Tensor", 1598000, 6, 2507000, 2.4644, 0.9131), + ("Tensor", 1598000, 6, 2630000, 2.4030, 1.3728), + ("Tensor", 1598000, 6, 2704000, 2.4271, 1.2680), + ("Tensor", 1598000, 6, 2802000, 2.4761, 0.9789), + ("Tensor", 1704000, 4, 400000, 1.9466, 0.9753), + ("Tensor", 1704000, 4, 553000, 1.9336, 1.0846), + ("Tensor", 1704000, 4, 696000, 1.9280, 0.2116), + ("Tensor", 1704000, 4, 799000, 1.9616, 0.4219), + ("Tensor", 1704000, 4, 910000, 1.9627, 0.1957), + ("Tensor", 1704000, 4, 1024000, 1.9763, 0.5599), + ("Tensor", 1704000, 4, 1197000, 1.9514, 0.4326), + ("Tensor", 1704000, 4, 1328000, 2.0093, 0.4861), + ("Tensor", 1704000, 4, 1491000, 1.9438, 0.1584), + ("Tensor", 1704000, 4, 1663000, 2.3012, 0.6019), + ("Tensor", 1704000, 4, 1836000, 2.2896, 0.5019), + ("Tensor", 1704000, 4, 1999000, 2.2292, 0.6076), + ("Tensor", 1704000, 4, 2130000, 2.2087, 0.5726), + ("Tensor", 1704000, 4, 2253000, 2.2317, 0.4878), + ("Tensor", 1704000, 6, 500000, 2.3606, 0.7822), + ("Tensor", 1704000, 6, 851000, 2.2564, 0.9656), + ("Tensor", 1704000, 6, 984000, 2.2618, 0.9988), + ("Tensor", 1704000, 6, 1106000, 2.2796, 0.9681), + ("Tensor", 1704000, 6, 1277000, 2.2224, 0.8812), + ("Tensor", 1704000, 6, 1426000, 2.2368, 1.0353), + ("Tensor", 1704000, 6, 1582000, 2.3125, 0.8402), + ("Tensor", 1704000, 6, 1745000, 2.3199, 0.7728), + ("Tensor", 1704000, 6, 1826000, 2.3633, 0.8597), + ("Tensor", 1704000, 6, 2048000, 2.2779, 0.6885), + ("Tensor", 1704000, 6, 2188000, 2.2575, 1.0289), + ("Tensor", 1704000, 6, 2252000, 2.2798, 0.9689), + ("Tensor", 1704000, 6, 2401000, 2.5202, 1.0626), + ("Tensor", 1704000, 6, 2507000, 2.4070, 0.8463), + ("Tensor", 1704000, 6, 2630000, 2.5998, 1.0795), + ("Tensor", 1704000, 6, 2704000, 2.6273, 1.0329), + ("Tensor", 1704000, 6, 2802000, 2.6179, 0.7569), + ("Tensor", 1803000, 4, 400000, 2.2197, 0.4673), + ("Tensor", 1803000, 4, 553000, 2.3144, 0.5120), + ("Tensor", 1803000, 4, 696000, 2.2720, 0.1952), + ("Tensor", 1803000, 4, 799000, 2.3472, 0.5479), + ("Tensor", 1803000, 4, 910000, 2.3035, 0.5622), + ("Tensor", 1803000, 4, 1024000, 2.2129, 0.6828), + ("Tensor", 1803000, 4, 1197000, 2.3176, 0.1645), + ("Tensor", 1803000, 4, 1328000, 2.3127, 0.4992), + ("Tensor", 1803000, 4, 1491000, 2.1449, 1.4705), + ("Tensor", 1803000, 4, 1663000, 2.3243, 0.6256), + ("Tensor", 1803000, 4, 1836000, 2.1328, 0.6293), + ("Tensor", 1803000, 4, 1999000, 2.3165, 0.5265), + ("Tensor", 1803000, 4, 2130000, 2.2775, 0.6412), + ("Tensor", 1803000, 4, 2253000, 2.4124, 0.5151), + ("Tensor", 1803000, 6, 500000, 2.5536, 1.5678), + ("Tensor", 1803000, 6, 851000, 2.5831, 1.1737), + ("Tensor", 1803000, 6, 984000, 2.6063, 1.0591), + ("Tensor", 1803000, 6, 1106000, 2.6951, 0.9158), + ("Tensor", 1803000, 6, 1277000, 2.5400, 1.5096), + ("Tensor", 1803000, 6, 1426000, 2.6623, 1.1037), + ("Tensor", 1803000, 6, 1582000, 2.6996, 1.0774), + ("Tensor", 1803000, 6, 1745000, 2.6692, 1.6543), + ("Tensor", 1803000, 6, 1826000, 2.7288, 1.1255), + ("Tensor", 1803000, 6, 2048000, 2.6649, 1.1010), + ("Tensor", 1803000, 6, 2188000, 2.6489, 1.1485), + ("Tensor", 1803000, 6, 2252000, 2.6389, 1.0942), + ("Tensor", 1803000, 6, 2401000, 2.6256, 1.0997), + ("Tensor", 1803000, 6, 2507000, 2.6630, 1.2641), + ("Tensor", 1803000, 6, 2630000, 2.7385, 2.3263), + ("Tensor", 1803000, 6, 2704000, 2.6901, 1.0629), + ("Tensor", 1803000, 6, 2802000, 2.7476, 1.0673) +) +select * from data; + diff --git a/src/trace_processor/perfetto_sql/stdlib/wattson/curves/grouped.sql b/src/trace_processor/perfetto_sql/stdlib/wattson/curves/grouped.sql new file mode 100644 index 0000000000..18d1770518 --- /dev/null +++ b/src/trace_processor/perfetto_sql/stdlib/wattson/curves/grouped.sql @@ -0,0 +1,66 @@ +-- +-- 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 wattson.curves.ungrouped; + +-- Wattson's estimated usage of the system, split out into cpu cluster based on +-- the natural grouping of the hardware. +CREATE PERFETTO TABLE wattson_estimate_per_component( + -- Starting timestamp of the slice + ts LONG, + -- Duration of the slice + dur INT, + -- Total L3 estimated usage in mW during this slice + l3 FLOAT, + -- Total little CPU estimated usage in mW during this slice + little_cpus FLOAT, + -- Total mid CPU cluster estimated usage in mW during this slice + mid_cpus FLOAT, + -- Total big CPU cluster estimated usage in mW during this slice + big_cpus FLOAT +) +AS +SELECT + ts, + dur, + IFNULL(l3_hit_value, 0.0) + IFNULL(l3_miss_value, 0.0) as l3, + IFNULL(cpu0_curve, 0.0) + IFNULL(cpu1_curve, 0.0) + IFNULL(cpu2_curve, 0.0) + + IFNULL(cpu3_curve, 0.0) + static_curve as little_cpus, + cpu4_curve + cpu5_curve as mid_cpus, + cpu6_curve + cpu7_curve as big_cpus +FROM _system_state_curves; + +-- Gives total contribution of each HW component for the entire trace, bringing +-- the output of the table to parity with the Python version of Wattson +CREATE PERFETTO TABLE _wattson_entire_trace +AS +WITH _individual_totals AS ( + SELECT + -- LUT for l3 is scaled by 10^6 to save resolution, so do the inversion + -- scaling by 10^6 after the summation to minimize losing resolution + SUM(l3) / 1000000 as total_l3, + SUM(dur * little_cpus) / 1000000000 as total_little_cpus, + SUM(dur * mid_cpus) / 1000000000 as total_mid_cpus, + SUM(dur * big_cpus) / 1000000000 as total_big_cpus + FROM wattson_estimate_per_component + ) +SELECT + ROUND(total_l3, 2) as total_l3, + ROUND(total_little_cpus, 2) as total_little_cpus, + ROUND(total_mid_cpus, 2) as total_mid_cpus, + ROUND(total_big_cpus, 2) as total_big_cpus, + ROUND(total_l3 + total_little_cpus + total_mid_cpus + total_big_cpus, 2) as total +FROM _individual_totals; + diff --git a/src/trace_processor/perfetto_sql/stdlib/wattson/curves/ungrouped.sql b/src/trace_processor/perfetto_sql/stdlib/wattson/curves/ungrouped.sql new file mode 100644 index 0000000000..37da258f01 --- /dev/null +++ b/src/trace_processor/perfetto_sql/stdlib/wattson/curves/ungrouped.sql @@ -0,0 +1,236 @@ +-- +-- 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 time.conversion; +INCLUDE PERFETTO MODULE wattson.arm_dsu; +INCLUDE PERFETTO MODULE wattson.cpu_split; +INCLUDE PERFETTO MODULE wattson.curves.utils; +INCLUDE PERFETTO MODULE wattson.device_infos; + +-- System state table with LUT for CPUs and intermediate values for calculations +CREATE PERFETTO TABLE _w_independent_cpus_calc +AS +SELECT + ts, + dur, + cast_int!(l3_hit_rate * dur) as l3_hit_count, + cast_int!(l3_miss_rate * dur) as l3_miss_count, + freq_0, + idle_0, + freq_1, + idle_1, + freq_2, + idle_2, + freq_3, + idle_3, + freq_4, + idle_4, + freq_5, + idle_5, + freq_6, + idle_6, + freq_7, + idle_7, + policy_4, + policy_5, + policy_6, + policy_7, + IIF( + suspended = 1, + 1, + MIN( + IFNULL(idle_0, 1), + IFNULL(idle_1, 1), + IFNULL(idle_2, 1), + IFNULL(idle_3, 1) + ) + ) as no_static, + IIF(suspended = 1, 0, cpu0_curve) as cpu0_curve, + IIF(suspended = 1, 0, cpu1_curve) as cpu1_curve, + IIF(suspended = 1, 0, cpu2_curve) as cpu2_curve, + IIF(suspended = 1, 0, cpu3_curve) as cpu3_curve, + IIF(suspended = 1, 0, cpu4_curve) as cpu4_curve, + IIF(suspended = 1, 0, cpu5_curve) as cpu5_curve, + IIF(suspended = 1, 0, cpu6_curve) as cpu6_curve, + IIF(suspended = 1, 0, cpu7_curve) as cpu7_curve, + -- If dependency CPUs are active, then that CPU could contribute static power + IIF(idle_4 = -1, lut4.curve_value, -1) as static_4, + IIF(idle_5 = -1, lut5.curve_value, -1) as static_5, + IIF(idle_6 = -1, lut6.curve_value, -1) as static_6, + IIF(idle_7 = -1, lut7.curve_value, -1) as static_7 +FROM _idle_freq_l3_hit_l3_miss_slice as base +LEFT JOIN _filtered_curves_2d lut4 ON + base.freq_0 = lut4.freq_khz AND + base.policy_4 = lut4.other_policy AND + base.freq_4 = lut4.other_freq_khz AND + lut4.idle = 255 +LEFT JOIN _filtered_curves_2d lut5 ON + base.freq_0 = lut5.freq_khz AND + base.policy_5 = lut5.other_policy AND + base.freq_5 = lut5.other_freq_khz AND + lut5.idle = 255 +LEFT JOIN _filtered_curves_2d lut6 ON + base.freq_0 = lut6.freq_khz AND + base.policy_6 = lut6.other_policy AND + base.freq_6 = lut6.other_freq_khz AND + lut6.idle = 255 +LEFT JOIN _filtered_curves_2d lut7 ON + base.freq_0 = lut7.freq_khz AND + base.policy_7 = lut7.other_policy AND + base.freq_7 = lut7.other_freq_khz AND + lut7.idle = 255 +-- Needs to be at least 1us to reduce inconsequential rows. +WHERE dur > time_from_us(1); + +-- Find the CPU states creating the max vote +CREATE PERFETTO TABLE _get_max_vote +AS +WITH max_power_tbl AS ( + SELECT + *, + -- Indicates if all CPUs are in deep idle + MIN( + no_static, + IFNULL(idle_4, 1), + IFNULL(idle_5, 1), + IFNULL(idle_6, 1), + IFNULL(idle_7, 1) + ) as all_cpu_deep_idle, + -- Determines which CPU has highest vote + MAX( + static_4, + static_5, + static_6, + static_7 + ) as max_static_vote + FROM _w_independent_cpus_calc +) +SELECT + *, + CASE max_static_vote + WHEN -1 THEN _get_min_freq_vote() + WHEN static_4 THEN freq_4 + WHEN static_5 THEN freq_5 + WHEN static_6 THEN freq_6 + WHEN static_7 THEN freq_7 + ELSE 400000 + END max_freq_vote, + CASE max_static_vote + WHEN -1 THEN _get_min_policy_vote() + WHEN static_4 THEN policy_4 + WHEN static_5 THEN policy_5 + WHEN static_6 THEN policy_6 + WHEN static_7 THEN policy_7 + ELSE 4 + END max_policy_vote +FROM max_power_tbl; + +-- Final table showing the curves per CPU per slice +CREATE PERFETTO TABLE _system_state_curves +AS +SELECT + base.ts, + base.dur, + COALESCE(lut0.curve_value, cpu0_curve) as cpu0_curve, + COALESCE(lut1.curve_value, cpu1_curve) as cpu1_curve, + COALESCE(lut2.curve_value, cpu2_curve) as cpu2_curve, + COALESCE(lut3.curve_value, cpu3_curve) as cpu3_curve, + COALESCE(base.cpu4_curve, 0.0) as cpu4_curve, + COALESCE(base.cpu5_curve, 0.0) as cpu5_curve, + COALESCE(base.cpu6_curve, 0.0) as cpu6_curve, + COALESCE(base.cpu7_curve, 0.0) as cpu7_curve, + IIF( + no_static = 1, + 0.0, + COALESCE(static_1d.curve_value, static_2d.curve_value) + ) as static_curve, + IIF( + all_cpu_deep_idle = 1, + 0, + base.l3_hit_count * l3_hit_lut.curve_value + ) as l3_hit_value, + IIF( + all_cpu_deep_idle = 1, + 0, + base.l3_miss_count * l3_miss_lut.curve_value + ) as l3_miss_value +FROM _get_max_vote as base +-- LUT for 2D dependencies +LEFT JOIN _filtered_curves_2d lut0 ON + lut0.freq_khz = base.freq_0 AND + lut0.other_policy = base.max_policy_vote AND + lut0.other_freq_khz = base.max_freq_vote AND + lut0.idle = base.idle_0 +LEFT JOIN _filtered_curves_2d lut1 ON + lut1.freq_khz = base.freq_1 AND + lut1.other_policy = base.max_policy_vote AND + lut1.other_freq_khz = base.max_freq_vote AND + lut1.idle = base.idle_1 +LEFT JOIN _filtered_curves_2d lut2 ON + lut2.freq_khz = base.freq_2 AND + lut2.other_policy = base.max_policy_vote AND + lut2.other_freq_khz = base.max_freq_vote AND + lut2.idle = base.idle_2 +LEFT JOIN _filtered_curves_2d lut3 ON + lut3.freq_khz = base.freq_3 AND + lut3.other_policy = base.max_policy_vote AND + lut3.other_freq_khz = base.max_freq_vote AND + lut3.idle = base.idle_3 +-- LUT for static curve lookup +LEFT JOIN _filtered_curves_2d static_2d ON + static_2d.freq_khz = base.freq_0 AND + static_2d.other_policy = base.max_policy_vote AND + static_2d.other_freq_khz = base.max_freq_vote AND + static_2d.idle = 255 +LEFT JOIN _filtered_curves_1d static_1d ON + static_1d.policy = 0 AND + static_1d.freq_khz = base.freq_0 AND + static_1d.idle = 255 +-- LUT joins for L3 cache +LEFT JOIN _filtered_curves_l3 l3_hit_lut ON + l3_hit_lut.freq_khz = base.freq_0 AND + l3_hit_lut.other_policy = base.max_policy_vote AND + l3_hit_lut.other_freq_khz = base.max_freq_vote AND + l3_hit_lut.action = 'hit' +LEFT JOIN _filtered_curves_l3 l3_miss_lut ON + l3_miss_lut.freq_khz = base.freq_0 AND + l3_miss_lut.other_policy = base.max_policy_vote AND + l3_miss_lut.other_freq_khz = base.max_freq_vote AND + l3_miss_lut.action = 'miss'; + +-- The most basic components of Wattson, all normalized to be in mW on a per +-- system state basis +CREATE PERFETTO TABLE _system_state_mw +AS +SELECT + ts, + dur, + cpu0_curve as cpu0_mw, + cpu1_curve as cpu1_mw, + cpu2_curve as cpu2_mw, + cpu3_curve as cpu3_mw, + cpu4_curve as cpu4_mw, + cpu5_curve as cpu5_mw, + cpu6_curve as cpu6_mw, + cpu7_curve as cpu7_mw, + -- LUT for l3 is scaled by 10^6 to save resolution and in units of kWs. Scale + -- this by 10^3 so when divided by ns, result is in units of mW + ( + ( + IFNULL(l3_hit_value, 0) + IFNULL(l3_miss_value, 0) + ) * 1000 / dur + ) + static_curve as dsu_scu_mw +FROM _system_state_curves; + diff --git a/src/trace_processor/perfetto_sql/stdlib/wattson/curves/utils.sql b/src/trace_processor/perfetto_sql/stdlib/wattson/curves/utils.sql new file mode 100644 index 0000000000..333a53258f --- /dev/null +++ b/src/trace_processor/perfetto_sql/stdlib/wattson/curves/utils.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 wattson.curves.device; +INCLUDE PERFETTO MODULE wattson.device_infos; + +-- 1D LUT +CREATE PERFETTO TABLE _filtered_curves_1d_raw AS +SELECT cp.policy, freq_khz, active, idle0, idle1, static +FROM _device_curves_1d as dc +JOIN _wattson_device as device ON dc.device = device.name +JOIN _dev_cpu_policy_map as cp ON dc.policy = cp.policy; + +CREATE PERFETTO TABLE _filtered_curves_1d AS +SELECT policy, freq_khz, -1 as idle, active as curve_value +FROM _filtered_curves_1d_raw +UNION +SELECT policy, freq_khz, 0, idle0 +FROM _filtered_curves_1d_raw +UNION +SELECT policy, freq_khz, 1, idle1 +FROM _filtered_curves_1d_raw +UNION +SELECT policy, freq_khz, 255, static +FROM _filtered_curves_1d_raw; + +CREATE PERFETTO INDEX freq_1d ON _filtered_curves_1d(policy, freq_khz, idle); + +-- 2D LUT; with dependency on another CPU +CREATE PERFETTO TABLE _filtered_curves_2d_raw AS +SELECT + cp.policy as other_policy, + dc.freq_khz, + dc.other_freq_khz, + dc.active, + dc.idle0, + dc.idle1, + dc.static +FROM _device_curves_2d as dc +JOIN _wattson_device as device ON dc.device = device.name +JOIN _dev_cpu_policy_map as cp ON dc.other_policy = cp.policy; + +CREATE PERFETTO TABLE _filtered_curves_2d AS +SELECT freq_khz, other_policy, other_freq_khz, -1 as idle, active as curve_value +FROM _filtered_curves_2d_raw +UNION +SELECT freq_khz, other_policy, other_freq_khz, 0, idle0 +FROM _filtered_curves_2d_raw +UNION +SELECT freq_khz, other_policy, other_freq_khz, 1, idle1 +FROM _filtered_curves_2d_raw +UNION +SELECT freq_khz, other_policy, other_freq_khz, 255, static +FROM _filtered_curves_2d_raw; + +CREATE PERFETTO INDEX freq_2d +ON _filtered_curves_2d(freq_khz, other_policy, other_freq_khz, idle); + +-- L3 cache LUT +CREATE PERFETTO TABLE _filtered_curves_l3_raw AS +SELECT + cp.policy as other_policy, + dc.freq_khz, + dc.other_freq_khz, + dc.l3_hit, + dc.l3_miss +FROM _device_curves_l3 as dc +JOIN _wattson_device as device ON dc.device = device.name +JOIN _dev_cpu_policy_map as cp ON dc.other_policy = cp.policy; + +CREATE PERFETTO TABLE _filtered_curves_l3 AS +SELECT + freq_khz, other_policy, other_freq_khz, 'hit' AS action, l3_hit as curve_value +FROM _filtered_curves_l3_raw +UNION +SELECT + freq_khz, other_policy, other_freq_khz, 'miss' AS action, l3_miss +FROM _filtered_curves_l3_raw; + +CREATE PERFETTO INDEX freq_l3 +ON _filtered_curves_l3(freq_khz, other_policy, other_freq_khz, action); diff --git a/src/trace_processor/perfetto_sql/stdlib/wattson/device_infos.sql b/src/trace_processor/perfetto_sql/stdlib/wattson/device_infos.sql new file mode 100644 index 0000000000..5c42a6d583 --- /dev/null +++ b/src/trace_processor/perfetto_sql/stdlib/wattson/device_infos.sql @@ -0,0 +1,120 @@ +-- +-- 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.device; + +-- Device specific info for deep idle time offsets +CREATE PERFETTO TABLE _device_cpu_deep_idle_offsets +AS +WITH data(device, cpu, offset_ns) AS ( + VALUES + ("Tensor", 0, 0), + ("Tensor", 1, 0), + ("Tensor", 2, 0), + ("Tensor", 3, 0), + ("Tensor", 4, 0), + ("Tensor", 5, 0), + ("Tensor", 6, 200000), + ("Tensor", 7, 200000), + ("monaco", 0, 450000), + ("monaco", 1, 450000), + ("monaco", 2, 450000), + ("monaco", 3, 450000) +) +select * from data; + +CREATE PERFETTO TABLE _wattson_device_map +AS +WITH data(device, wattson_device) AS ( + VALUES + ("oriole", "Tensor"), + ("raven", "Tensor"), + ("bluejay", "Tensor"), + ("eos", "monaco"), + ("aurora", "monaco") +) +select * from data; + +CREATE PERFETTO TABLE _wattson_device +AS +WITH soc AS ( + SELECT str_value as model + FROM metadata + WHERE name = 'android_soc_model' +) +SELECT + COALESCE(soc.model, map.wattson_device) as name +FROM _wattson_device_map as map +CROSS JOIN android_device_name as ad +LEFT JOIN soc ON TRUE +WHERE ad.name = map.device; + +-- Device specific mapping from CPU to policy +CREATE PERFETTO TABLE _cpu_to_policy_map +AS +WITH data(device, cpu, policy) AS ( + VALUES + ("monaco", 0, 0), + ("monaco", 1, 0), + ("monaco", 2, 0), + ("monaco", 3, 0), + ("Tensor", 0, 0), + ("Tensor", 1, 0), + ("Tensor", 2, 0), + ("Tensor", 3, 0), + ("Tensor", 4, 4), + ("Tensor", 5, 4), + ("Tensor", 6, 6), + ("Tensor", 7, 6) +) +select * from data; + +-- Prefilter table based on device +CREATE PERFETTO TABLE _dev_cpu_policy_map +AS +SELECT + cpu, policy +FROM _cpu_to_policy_map as cp_map +JOIN _wattson_device as device +ON cp_map.device = device.name +ORDER by cpu; + +-- Policy and freq that will give minimum volt vote +CREATE PERFETTO TABLE _device_min_volt_vote +AS +WITH data(device, policy, freq) AS ( + VALUES + ("monaco", 0, 614400), + ("Tensor", 4, 400000) +) +select * from data; + +-- Get policy corresponding to minimum volt vote +CREATE PERFETTO FUNCTION _get_min_policy_vote() +RETURNS INT AS +SELECT + vote_tbl.policy +FROM _device_min_volt_vote as vote_tbl +JOIN _wattson_device as device +WHERE vote_tbl.device = device.name; + +-- Get frequency corresponding to minimum volt vote +CREATE PERFETTO FUNCTION _get_min_freq_vote() +RETURNS INT AS +SELECT + vote_tbl.freq +FROM _device_min_volt_vote as vote_tbl +JOIN _wattson_device as device +WHERE vote_tbl.device = device.name; diff --git a/src/trace_processor/perfetto_sql/stdlib/wattson/system_state.sql b/src/trace_processor/perfetto_sql/stdlib/wattson/system_state.sql index 63eeb7a6e5..bfa12ca00f 100644 --- a/src/trace_processor/perfetto_sql/stdlib/wattson/system_state.sql +++ b/src/trace_processor/perfetto_sql/stdlib/wattson/system_state.sql @@ -13,46 +13,7 @@ -- See the License for the specific language governing permissions and -- limitations under the License. -INCLUDE PERFETTO MODULE time.conversion; -INCLUDE PERFETTO MODULE wattson.arm_dsu; -INCLUDE PERFETTO MODULE wattson.cpu_freq; -INCLUDE PERFETTO MODULE wattson.cpu_idle; - --- Combines idle and freq tables of all CPUs to create system state. -CREATE VIRTUAL TABLE _idle_freq_slice -USING - SPAN_OUTER_JOIN(_cpu_freq_all, _cpu_idle_all); - --- get suspend resume state as logged by ftrace. -CREATE PERFETTO TABLE _suspend_slice -AS -SELECT - ts, dur, TRUE AS suspended -FROM slice -WHERE name GLOB "timekeeping_freeze(0)"; - --- Combine suspend information with CPU idle and frequency system states. -CREATE VIRTUAL TABLE _idle_freq_suspend_slice -USING - SPAN_OUTER_JOIN(_idle_freq_slice, _suspend_slice); - --- Add extra column indicating that idle and frequency info are present before --- SPAN_OUTER_JOIN with the DSU PMU counters. -CREATE PERFETTO TABLE _idle_freq_filtered -AS -SELECT *, TRUE AS has_idle_freq -FROM _idle_freq_suspend_slice -WHERE freq_0 GLOB '*[0-9]*'; - --- Combine system state so that it has idle, freq, and L3 hit info. -CREATE VIRTUAL TABLE _idle_freq_l3_hit_slice -USING - SPAN_OUTER_JOIN(_idle_freq_filtered, _arm_l3_hit_rate); - --- Combine system state so that it has idle, freq, L3 hit, and L3 miss info. -CREATE VIRTUAL TABLE _idle_freq_l3_hit_l3_miss_slice -USING - SPAN_OUTER_JOIN(_idle_freq_l3_hit_slice, _arm_l3_miss_rate); +INCLUDE PERFETTO MODULE wattson.cpu_split; -- The final system state for the CPU subsystem, which has all the information -- needed by Wattson to estimate energy for the CPU subsystem. @@ -125,5 +86,5 @@ SELECT IFNULL(suspended, FALSE) as suspended FROM _idle_freq_l3_hit_l3_miss_slice -- Needs to be at least 1us to reduce inconsequential rows. -WHERE dur > time_from_us(1) and has_idle_freq IS NOT NULL; +WHERE dur > time_from_us(1); diff --git a/src/trace_processor/read_trace.cc b/src/trace_processor/read_trace.cc index 06bd39d443..9d5a1f7ea0 100644 --- a/src/trace_processor/read_trace.cc +++ b/src/trace_processor/read_trace.cc @@ -25,12 +25,12 @@ #include "perfetto/trace_processor/trace_blob.h" #include "perfetto/trace_processor/trace_blob_view.h" -#include "src/trace_processor/forwarding_trace_parser.h" #include "src/trace_processor/importers/gzip/gzip_trace_parser.h" #include "src/trace_processor/importers/proto/proto_trace_tokenizer.h" #include "src/trace_processor/read_trace_internal.h" #include "src/trace_processor/util/gzip_utils.h" #include "src/trace_processor/util/status_macros.h" +#include "src/trace_processor/util/trace_type.h" #include "protos/perfetto/trace/trace.pbzero.h" #include "protos/perfetto/trace/trace_packet.pbzero.h" diff --git a/src/trace_processor/read_trace_internal.cc b/src/trace_processor/read_trace_internal.cc index a632134da6..3b537cdf49 100644 --- a/src/trace_processor/read_trace_internal.cc +++ b/src/trace_processor/read_trace_internal.cc @@ -95,8 +95,8 @@ util::Status ReadTraceUnfinalized( RETURN_IF_ERROR(tp->Parse(std::move(slice))); bytes_read += slice_size; } // while (slices) - } // if (mapped.IsValid()) - } // if (use_mmap) + } // if (mapped.IsValid()) + } // if (use_mmap) if (bytes_read == 0) PERFETTO_LOG("Cannot use mmap on this system. Falling back on read()"); #endif // PERFETTO_HAS_MMAP() diff --git a/src/trace_processor/rpc/rpc.cc b/src/trace_processor/rpc/rpc.cc index b676e455df..4cfca2044c 100644 --- a/src/trace_processor/rpc/rpc.cc +++ b/src/trace_processor/rpc/rpc.cc @@ -225,9 +225,33 @@ void Rpc::ParseRpcRequest(const uint8_t* data, size_t len) { auto it = QueryInternal(args.data, args.size); QueryResultSerializer serializer(std::move(it)); for (bool has_more = true; has_more;) { - Response resp(tx_seq_id_++, req_type); + const auto seq_id = tx_seq_id_++; + Response resp(seq_id, req_type); has_more = serializer.Serialize(resp->set_query_result()); - resp.Send(rpc_response_fn_); + const uint32_t resp_size = resp->Finalize(); + if (resp_size < protozero::proto_utils::kMaxMessageLength) { + // This is the nominal case. + resp.Send(rpc_response_fn_); + continue; + } + // In rare cases a query can end up with a batch which is too big. + // Normally batches are automatically split before hitting the limit, + // but one can come up with a query where a single cell is > 256MB. + // If this happens, just bail out gracefully rather than creating an + // unparsable proto which will cause a RPC framing error. + // If we hit this, we have to discard `resp` because it's + // unavoidably broken (due to have overflown the 4-bytes size) and + // can't be parsed. Instead create a new response with the error. + Response err_resp(seq_id, req_type); + auto* qres = err_resp->set_query_result(); + qres->add_batch()->set_is_last_batch(true); + qres->set_error( + "The query ended up with a response that is too big (" + + std::to_string(resp_size) + + " bytes). This usually happens when a single row is >= 256 MiB. " + "See also WRITE_FILE for dealing with large rows."); + err_resp.Send(rpc_response_fn_); + break; } } break; @@ -393,7 +417,6 @@ void Rpc::Query(const uint8_t* args, Iterator Rpc::QueryInternal(const uint8_t* args, size_t len) { protos::pbzero::QueryArgs::Decoder query(args, len); std::string sql = query.sql_query().ToStdString(); - PERFETTO_DLOG("[RPC] Query < %s", sql.c_str()); PERFETTO_TP_TRACE(metatrace::Category::API_TIMELINE, "RPC_QUERY", [&](metatrace::Record* r) { r->AddArg("SQL", sql); diff --git a/src/trace_processor/sorter/BUILD.gn b/src/trace_processor/sorter/BUILD.gn index 8d4c9f97bd..a429c303ea 100644 --- a/src/trace_processor/sorter/BUILD.gn +++ b/src/trace_processor/sorter/BUILD.gn @@ -29,9 +29,11 @@ source_set("sorter") { "../../../gn:default_deps", "../../../include/perfetto/trace_processor:storage", "../../base", + "../importers/android_bugreport:android_log_event", "../importers/common:parser_types", "../importers/common:trace_parser_hdr", "../importers/fuchsia:fuchsia_record", + "../importers/perf:record", "../importers/systrace:systrace_line", "../storage", "../types", diff --git a/src/trace_processor/sorter/trace_sorter.cc b/src/trace_processor/sorter/trace_sorter.cc index a2d9861d9f..b684e1a4f3 100644 --- a/src/trace_processor/sorter/trace_sorter.cc +++ b/src/trace_processor/sorter/trace_sorter.cc @@ -15,20 +15,29 @@ */ #include +#include +#include +#include +#include +#include #include #include #include "perfetto/base/compiler.h" +#include "perfetto/base/logging.h" +#include "perfetto/public/compiler.h" +#include "src/trace_processor/importers/android_bugreport/android_log_event.h" #include "src/trace_processor/importers/common/parser_types.h" #include "src/trace_processor/importers/common/trace_parser.h" #include "src/trace_processor/importers/fuchsia/fuchsia_record.h" +#include "src/trace_processor/importers/perf/record.h" #include "src/trace_processor/sorter/trace_sorter.h" -#include "src/trace_processor/storage/trace_storage.h" +#include "src/trace_processor/sorter/trace_token_buffer.h" +#include "src/trace_processor/storage/stats.h" #include "src/trace_processor/types/trace_processor_context.h" #include "src/trace_processor/util/bump_allocator.h" -namespace perfetto { -namespace trace_processor { +namespace perfetto::trace_processor { TraceSorter::TraceSorter(TraceProcessorContext* context, SortingMode sorting_mode) @@ -116,10 +125,12 @@ void TraceSorter::SortAndExtractEventsUntilAllocId( auto& queue = sorter_data.queues[i]; if (queue.events_.empty()) continue; - all_queues_empty = false; - PERFETTO_DCHECK(queue.max_ts_ <= append_max_ts_); - if (queue.min_ts_ < min_queue_ts[0]) { + + // Checking for |all_queues_empty| is necessary here as in fuzzer cases + // we can end up with |int64::max()| as the value here. + // See https://crbug.com/oss-fuzz/69164 for an example. + if (all_queues_empty || queue.min_ts_ < min_queue_ts[0]) { min_queue_ts[1] = min_queue_ts[0]; min_queue_ts[0] = queue.min_ts_; min_queue_idx = i; @@ -127,6 +138,7 @@ void TraceSorter::SortAndExtractEventsUntilAllocId( } else if (queue.min_ts_ < min_queue_ts[1]) { min_queue_ts[1] = queue.min_ts_; } + all_queues_empty = false; } } if (all_queues_empty) @@ -188,7 +200,7 @@ void TraceSorter::ParseTracePacket(TraceProcessorContext& context, switch (static_cast(event.event_type)) { case TimestampedEvent::Type::kPerfRecord: context.perf_record_parser->ParsePerfRecord( - event.ts, token_buffer_.Extract(id)); + event.ts, token_buffer_.Extract(id)); return; case TimestampedEvent::Type::kTracePacket: context.proto_trace_parser->ParseTracePacket( @@ -210,6 +222,10 @@ void TraceSorter::ParseTracePacket(TraceProcessorContext& context, context.json_trace_parser->ParseSystraceLine( event.ts, token_buffer_.Extract(id)); return; + case TimestampedEvent::Type::kAndroidLogEvent: + context.android_log_event_parser->ParseAndroidLogEvent( + event.ts, token_buffer_.Extract(id)); + return; case TimestampedEvent::Type::kInlineSchedSwitch: case TimestampedEvent::Type::kInlineSchedWaking: case TimestampedEvent::Type::kEtwEvent: @@ -237,6 +253,7 @@ void TraceSorter::ParseEtwPacket(TraceProcessorContext& context, case TimestampedEvent::Type::kPerfRecord: case TimestampedEvent::Type::kJsonValue: case TimestampedEvent::Type::kFuchsiaRecord: + case TimestampedEvent::Type::kAndroidLogEvent: PERFETTO_FATAL("Invalid event type"); } PERFETTO_FATAL("For GCC"); @@ -266,6 +283,7 @@ void TraceSorter::ParseFtracePacket(TraceProcessorContext& context, case TimestampedEvent::Type::kPerfRecord: case TimestampedEvent::Type::kJsonValue: case TimestampedEvent::Type::kFuchsiaRecord: + case TimestampedEvent::Type::kAndroidLogEvent: PERFETTO_FATAL("Invalid event type"); } PERFETTO_FATAL("For GCC"); @@ -275,10 +293,9 @@ void TraceSorter::ExtractAndDiscardTokenizedObject( const TimestampedEvent& event) { TraceTokenBuffer::Id id = GetTokenBufferId(event); switch (static_cast(event.event_type)) { - case TimestampedEvent::Type::kPerfRecord: - base::ignore_result(token_buffer_.Extract(id)); - return; case TimestampedEvent::Type::kTracePacket: + case TimestampedEvent::Type::kFtraceEvent: + case TimestampedEvent::Type::kEtwEvent: base::ignore_result(token_buffer_.Extract(id)); return; case TimestampedEvent::Type::kTrackEvent: @@ -299,11 +316,11 @@ void TraceSorter::ExtractAndDiscardTokenizedObject( case TimestampedEvent::Type::kInlineSchedWaking: base::ignore_result(token_buffer_.Extract(id)); return; - case TimestampedEvent::Type::kFtraceEvent: - base::ignore_result(token_buffer_.Extract(id)); + case TimestampedEvent::Type::kPerfRecord: + base::ignore_result(token_buffer_.Extract(id)); return; - case TimestampedEvent::Type::kEtwEvent: - base::ignore_result(token_buffer_.Extract(id)); + case TimestampedEvent::Type::kAndroidLogEvent: + base::ignore_result(token_buffer_.Extract(id)); return; } PERFETTO_FATAL("For GCC"); @@ -342,5 +359,4 @@ void TraceSorter::MaybeExtractEvent(size_t min_machine_idx, } } -} // namespace trace_processor -} // namespace perfetto +} // namespace perfetto::trace_processor diff --git a/src/trace_processor/sorter/trace_sorter.h b/src/trace_processor/sorter/trace_sorter.h index 5ea0e0daa1..656b28358f 100644 --- a/src/trace_processor/sorter/trace_sorter.h +++ b/src/trace_processor/sorter/trace_sorter.h @@ -18,26 +18,34 @@ #define SRC_TRACE_PROCESSOR_SORTER_TRACE_SORTER_H_ #include +#include +#include +#include #include #include +#include +#include +#include #include #include +#include "perfetto/base/logging.h" #include "perfetto/ext/base/circular_queue.h" -#include "perfetto/ext/base/utils.h" #include "perfetto/public/compiler.h" -#include "perfetto/trace_processor/basic_types.h" +#include "perfetto/trace_processor/ref_counted.h" #include "perfetto/trace_processor/trace_blob_view.h" +#include "src/trace_processor/importers/android_bugreport/android_log_event.h" +#include "src/trace_processor/importers/common/parser_types.h" #include "src/trace_processor/importers/common/trace_parser.h" #include "src/trace_processor/importers/fuchsia/fuchsia_record.h" +#include "src/trace_processor/importers/perf/record.h" #include "src/trace_processor/importers/systrace/systrace_line.h" #include "src/trace_processor/sorter/trace_token_buffer.h" #include "src/trace_processor/storage/trace_storage.h" #include "src/trace_processor/types/trace_processor_context.h" #include "src/trace_processor/util/bump_allocator.h" -namespace perfetto { -namespace trace_processor { +namespace perfetto::trace_processor { // This class takes care of sorting events parsed from the trace stream in // arbitrary order and pushing them to the next pipeline stages (parsing) in @@ -102,9 +110,18 @@ class TraceSorter { sorter_data_by_machine_.emplace_back(context); } + inline void PushAndroidLogEvent( + int64_t timestamp, + AndroidLogEvent event, + std::optional machine_id = std::nullopt) { + TraceTokenBuffer::Id id = token_buffer_.Append(std::move(event)); + AppendNonFtraceEvent(timestamp, TimestampedEvent::Type::kAndroidLogEvent, + id, machine_id); + } + inline void PushPerfRecord( int64_t timestamp, - TraceBlobView record, + perf_importer::Record record, std::optional machine_id = std::nullopt) { TraceTokenBuffer::Id id = token_buffer_.Append(std::move(record)); AppendNonFtraceEvent(timestamp, TimestampedEvent::Type::kPerfRecord, id, @@ -218,7 +235,7 @@ class TraceSorter { SortAndExtractEventsUntilAllocId(end_id); for (auto& sorter_data : sorter_data_by_machine_) { for (const auto& queue : sorter_data.queues) { - PERFETTO_DCHECK(queue.events_.empty()); + PERFETTO_CHECK(queue.events_.empty()); } sorter_data.queues.clear(); } @@ -255,7 +272,8 @@ class TraceSorter { kTrackEvent, kSystraceLine, kEtwEvent, - kMax = kEtwEvent, + kAndroidLogEvent, + kMax = kAndroidLogEvent, }; // Number of bits required to store the max element in |Type|. @@ -294,13 +312,13 @@ class TraceSorter { static_assert(sizeof(TimestampedEvent) == 16, "TimestampedEvent must be equal to 16 bytes"); - static_assert(std::is_trivially_copyable::value, + static_assert(std::is_trivially_copyable_v, "TimestampedEvent must be trivially copyable"); - static_assert(std::is_trivially_move_assignable::value, + static_assert(std::is_trivially_move_assignable_v, "TimestampedEvent must be trivially move assignable"); - static_assert(std::is_trivially_move_constructible::value, + static_assert(std::is_trivially_move_constructible_v, "TimestampedEvent must be trivially move constructible"); - static_assert(std::is_nothrow_swappable::value, + static_assert(std::is_nothrow_swappable_v, "TimestampedEvent must be trivially swappable"); struct Queue { @@ -357,7 +375,7 @@ class TraceSorter { auto* queues = &sorter_data_by_machine_[0].queues; // Find the TraceSorterData instance when |machine_id| is not nullopt. - if (PERFETTO_UNLIKELY(!!machine_id)) { + if (PERFETTO_UNLIKELY(machine_id.has_value())) { auto it = std::find_if(sorter_data_by_machine_.begin() + 1, sorter_data_by_machine_.end(), [machine_id](const TraceSorterData& item) { @@ -400,7 +418,7 @@ class TraceSorter { const TimestampedEvent&); void ExtractAndDiscardTokenizedObject(const TimestampedEvent& event); - TraceTokenBuffer::Id GetTokenBufferId(const TimestampedEvent& event) { + static TraceTokenBuffer::Id GetTokenBufferId(const TimestampedEvent& event) { return TraceTokenBuffer::Id{event.alloc_id()}; } @@ -447,7 +465,6 @@ class TraceSorter { int64_t latest_pushed_event_ts_ = std::numeric_limits::min(); }; -} // namespace trace_processor -} // namespace perfetto +} // namespace perfetto::trace_processor #endif // SRC_TRACE_PROCESSOR_SORTER_TRACE_SORTER_H_ diff --git a/src/trace_processor/sqlite/BUILD.gn b/src/trace_processor/sqlite/BUILD.gn index 902c9d8655..fda30ba077 100644 --- a/src/trace_processor/sqlite/BUILD.gn +++ b/src/trace_processor/sqlite/BUILD.gn @@ -32,7 +32,6 @@ source_set("sqlite") { "sqlite_tokenizer.h", "sqlite_utils.cc", "sqlite_utils.h", - "sqlite_value.h", "stats_table.cc", "stats_table.h", ] diff --git a/src/trace_processor/sqlite/bindings/BUILD.gn b/src/trace_processor/sqlite/bindings/BUILD.gn index 07922a7146..0f30567bd1 100644 --- a/src/trace_processor/sqlite/bindings/BUILD.gn +++ b/src/trace_processor/sqlite/bindings/BUILD.gn @@ -19,8 +19,14 @@ assert(enable_perfetto_trace_processor_sqlite) source_set("bindings") { sources = [ "sqlite_aggregate_function.h", + "sqlite_bind.h", + "sqlite_column.h", + "sqlite_function.h", "sqlite_module.h", "sqlite_result.h", + "sqlite_stmt.h", + "sqlite_type.h", + "sqlite_value.h", "sqlite_window_function.h", ] deps = [ diff --git a/src/trace_processor/sqlite/bindings/sqlite_bind.h b/src/trace_processor/sqlite/bindings/sqlite_bind.h new file mode 100644 index 0000000000..630212ecff --- /dev/null +++ b/src/trace_processor/sqlite/bindings/sqlite_bind.h @@ -0,0 +1,50 @@ +/* + * 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_SQLITE_BINDINGS_SQLITE_BIND_H_ +#define SRC_TRACE_PROCESSOR_SQLITE_BINDINGS_SQLITE_BIND_H_ + +#include // IWYU pragma: export +#include +#include + +namespace perfetto::trace_processor::sqlite::bind { + +// This file contains wraps the SQLite functions which operate on stmt +// bindings and start with sqlite3_bind_*. + +using PointerDestructor = void(void*); +inline int Pointer(sqlite3_stmt* stmt, + uint32_t N, + void* ptr, + const char* name, + PointerDestructor destructor) { + return sqlite3_bind_pointer(stmt, static_cast(N), ptr, name, destructor); +} + +template +inline int UniquePointer(sqlite3_stmt* stmt, + uint32_t N, + std::unique_ptr ptr, + const char* name) { + return sqlite3_bind_pointer( + stmt, static_cast(N), ptr.release(), name, + [](void* tab) { std::unique_ptr(static_cast(tab)); }); +} + +} // namespace perfetto::trace_processor::sqlite::bind + +#endif // SRC_TRACE_PROCESSOR_SQLITE_BINDINGS_SQLITE_BIND_H_ diff --git a/src/trace_processor/sqlite/bindings/sqlite_column.h b/src/trace_processor/sqlite/bindings/sqlite_column.h new file mode 100644 index 0000000000..cf1c7c8fd3 --- /dev/null +++ b/src/trace_processor/sqlite/bindings/sqlite_column.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SRC_TRACE_PROCESSOR_SQLITE_BINDINGS_SQLITE_COLUMN_H_ +#define SRC_TRACE_PROCESSOR_SQLITE_BINDINGS_SQLITE_COLUMN_H_ + +#include // IWYU pragma: export +#include + +#include "src/trace_processor/sqlite/bindings/sqlite_type.h" + +namespace perfetto::trace_processor::sqlite::column { + +// This file contains wraps the SQLite functions which operate on stmt +// columns and start with sqlite3_column_*. + +inline const char* Name(sqlite3_stmt* stmt, uint32_t N) { + return sqlite3_column_name(stmt, static_cast(N)); +} + +inline uint32_t Count(sqlite3_stmt* stmt) { + return static_cast(sqlite3_column_count(stmt)); +} + +inline sqlite::Type Type(sqlite3_stmt* stmt, uint32_t N) { + return static_cast( + sqlite3_column_type(stmt, static_cast(N))); +} + +inline int64_t Int64(sqlite3_stmt* stmt, uint32_t N) { + return sqlite3_column_int64(stmt, static_cast(N)); +} + +inline const char* Text(sqlite3_stmt* stmt, uint32_t N) { + return reinterpret_cast( + sqlite3_column_text(stmt, static_cast(N))); +} + +inline double Double(sqlite3_stmt* stmt, uint32_t N) { + return sqlite3_column_double(stmt, static_cast(N)); +} + +inline sqlite3_value* Value(sqlite3_stmt* stmt, uint32_t N) { + return sqlite3_column_value(stmt, static_cast(N)); +} + +using PointerDestructor = void(void*); +inline int BindPointer(sqlite3_stmt* stmt, + uint32_t N, + void* ptr, + const char* name, + PointerDestructor destructor) { + return sqlite3_bind_pointer(stmt, static_cast(N), ptr, name, destructor); +} + +} // namespace perfetto::trace_processor::sqlite::column + +#endif // SRC_TRACE_PROCESSOR_SQLITE_BINDINGS_SQLITE_COLUMN_H_ diff --git a/src/trace_processor/sqlite/bindings/sqlite_function.h b/src/trace_processor/sqlite/bindings/sqlite_function.h new file mode 100644 index 0000000000..a97541fb80 --- /dev/null +++ b/src/trace_processor/sqlite/bindings/sqlite_function.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SRC_TRACE_PROCESSOR_SQLITE_BINDINGS_SQLITE_FUNCTION_H_ +#define SRC_TRACE_PROCESSOR_SQLITE_BINDINGS_SQLITE_FUNCTION_H_ + +#include // IWYU pragma: export + +namespace perfetto::trace_processor { + +// Prototype for a function which can be registered with SQLite. +// +// See https://www.sqlite.org/c3ref/create_function.html for details on how +// to implement the methods of this class. +template +class SqliteFunction { + public: + // The type of the context object which will be passed to the function. + // Can be redefined in any sub-classes to override the context. + using UserDataContext = void; + + // The xStep function which will be executed by SQLite to add a row of values + // to the current window. + // + // Implementations MUST define this function themselves; this function is + // declared but *not* defined so linker errors will be thrown if not defined. + static void Step(sqlite3_context*, int argc, sqlite3_value** argv); + + // Returns the pointer to the user data structure which is passed when + // creating the function. + static auto GetUserData(sqlite3_context* ctx) { + return static_cast(sqlite3_user_data(ctx)); + } +}; + +} // namespace perfetto::trace_processor + +#endif // SRC_TRACE_PROCESSOR_SQLITE_BINDINGS_SQLITE_FUNCTION_H_ diff --git a/src/trace_processor/sqlite/bindings/sqlite_result.h b/src/trace_processor/sqlite/bindings/sqlite_result.h index 5c1fb21ab3..a9bcc18327 100644 --- a/src/trace_processor/sqlite/bindings/sqlite_result.h +++ b/src/trace_processor/sqlite/bindings/sqlite_result.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,8 +17,9 @@ #ifndef SRC_TRACE_PROCESSOR_SQLITE_BINDINGS_SQLITE_RESULT_H_ #define SRC_TRACE_PROCESSOR_SQLITE_BINDINGS_SQLITE_RESULT_H_ -#include +#include // IWYU pragma: export #include +#include namespace perfetto::trace_processor::sqlite::result { @@ -89,6 +90,14 @@ inline void RawPointer(sqlite3_context* ctx, inline void StaticPointer(sqlite3_context* ctx, void* ptr, const char* name) { RawPointer(ctx, ptr, name, nullptr); } +template +inline void UniquePointer(sqlite3_context* ctx, + std::unique_ptr ptr, + const char* name) { + sqlite::result::RawPointer(ctx, ptr.release(), name, [](void* ptr) { + std::unique_ptr(static_cast(ptr)); + }); +} } // namespace perfetto::trace_processor::sqlite::result diff --git a/src/trace_processor/sqlite/bindings/sqlite_stmt.h b/src/trace_processor/sqlite/bindings/sqlite_stmt.h new file mode 100644 index 0000000000..3eec03e46f --- /dev/null +++ b/src/trace_processor/sqlite/bindings/sqlite_stmt.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SRC_TRACE_PROCESSOR_SQLITE_BINDINGS_SQLITE_STMT_H_ +#define SRC_TRACE_PROCESSOR_SQLITE_BINDINGS_SQLITE_STMT_H_ + +#include // IWYU pragma: export + +namespace perfetto::trace_processor::sqlite::stmt { + +// This file contains wraps the SQLite functions which operate on sqlite3_stmt +// objects. + +inline int Reset(sqlite3_stmt* stmt) { + return sqlite3_reset(stmt); +} + +} // namespace perfetto::trace_processor::sqlite::stmt + +#endif // SRC_TRACE_PROCESSOR_SQLITE_BINDINGS_SQLITE_STMT_H_ diff --git a/src/trace_processor/sqlite/bindings/sqlite_type.h b/src/trace_processor/sqlite/bindings/sqlite_type.h new file mode 100644 index 0000000000..9b07009f54 --- /dev/null +++ b/src/trace_processor/sqlite/bindings/sqlite_type.h @@ -0,0 +1,34 @@ +/* + * 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_SQLITE_BINDINGS_SQLITE_TYPE_H_ +#define SRC_TRACE_PROCESSOR_SQLITE_BINDINGS_SQLITE_TYPE_H_ + +#include // IWYU pragma: export + +namespace perfetto::trace_processor::sqlite { + +enum class Type : int { + kNull = SQLITE_NULL, + kInteger = SQLITE_INTEGER, + kText = SQLITE_TEXT, + kFloat = SQLITE_FLOAT, + kBlob = SQLITE_BLOB, +}; + +} // namespace perfetto::trace_processor::sqlite + +#endif // SRC_TRACE_PROCESSOR_SQLITE_BINDINGS_SQLITE_TYPE_H_ diff --git a/src/trace_processor/sqlite/bindings/sqlite_value.h b/src/trace_processor/sqlite/bindings/sqlite_value.h new file mode 100644 index 0000000000..a935acf692 --- /dev/null +++ b/src/trace_processor/sqlite/bindings/sqlite_value.h @@ -0,0 +1,57 @@ +/* + * 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_SQLITE_BINDINGS_SQLITE_VALUE_H_ +#define SRC_TRACE_PROCESSOR_SQLITE_BINDINGS_SQLITE_VALUE_H_ + +#include // IWYU pragma: export +#include + +#include "src/trace_processor/sqlite/bindings/sqlite_type.h" + +namespace perfetto::trace_processor::sqlite::value { + +// This file contains wraps the sqlite3_value_* functions which extract values +// from sqlite3_value structs. + +inline Type Type(sqlite3_value* value) { + return static_cast(sqlite3_value_type(value)); +} + +inline bool IsNull(sqlite3_value* value) { + return Type(value) == Type::kNull; +} + +inline int64_t Int64(sqlite3_value* value) { + return sqlite3_value_int64(value); +} + +inline double Double(sqlite3_value* value) { + return sqlite3_value_double(value); +} + +inline const char* Text(sqlite3_value* value) { + return reinterpret_cast(sqlite3_value_text(value)); +} + +template +inline T* Pointer(sqlite3_value* value, const char* type) { + return static_cast(sqlite3_value_pointer(value, type)); +} + +} // namespace perfetto::trace_processor::sqlite::value + +#endif // SRC_TRACE_PROCESSOR_SQLITE_BINDINGS_SQLITE_VALUE_H_ diff --git a/src/trace_processor/sqlite/db_sqlite_table.cc b/src/trace_processor/sqlite/db_sqlite_table.cc index 60a8122069..bead804967 100644 --- a/src/trace_processor/sqlite/db_sqlite_table.cc +++ b/src/trace_processor/sqlite/db_sqlite_table.cc @@ -118,7 +118,7 @@ std::string CreateTableStatementFromSchema(const Table::Schema& schema, std::string stmt = "CREATE TABLE x("; for (const auto& col : schema.columns) { std::string c = - col.name + " " + sqlite::utils::SqlValueTypeToString(col.type); + col.name + " " + sqlite::utils::SqlValueTypeToSqliteTypeName(col.type); if (col.is_hidden) { c += " HIDDEN"; } @@ -413,15 +413,24 @@ int DbSqliteModule::BestIndex(sqlite3_vtab* vtab, sqlite3_index_info* info) { auto* t = GetVtab(vtab); auto* s = sqlite::ModuleStateManager::GetState(t->state); + const Table* table = nullptr; + switch (s->computation) { + case TableComputation::kStatic: + table = s->static_table; + break; + case TableComputation::kRuntime: + table = s->runtime_table.get(); + break; + case TableComputation::kTableFunction: + break; + } + uint32_t row_count; int argv_index; switch (s->computation) { case TableComputation::kStatic: - row_count = s->static_table->row_count(); - argv_index = 1; - break; case TableComputation::kRuntime: - row_count = s->runtime_table->row_count(); + row_count = table->row_count(); argv_index = 1; break; case TableComputation::kTableFunction: @@ -447,6 +456,7 @@ int DbSqliteModule::BestIndex(sqlite3_vtab* vtab, sqlite3_index_info* info) { // constraints and only add them to `idx_str` later. int limit = -1; int offset = -1; + bool has_unknown_constraint = false; cs_idxes.reserve(static_cast(info->nConstraint)); for (int i = 0; i < info->nConstraint; ++i) { @@ -459,6 +469,8 @@ int DbSqliteModule::BestIndex(sqlite3_vtab* vtab, sqlite3_index_info* info) { limit = i; } else if (c.op == SQLITE_INDEX_CONSTRAINT_OFFSET) { offset = i; + } else { + has_unknown_constraint = true; } continue; } @@ -471,30 +483,47 @@ int DbSqliteModule::BestIndex(sqlite3_vtab* vtab, sqlite3_index_info* info) { // Reorder constraints to consider the constraints on columns which are // cheaper to filter first. { - std::sort(cs_idxes.begin(), cs_idxes.end(), [s, info](int a, int b) { - auto a_idx = static_cast(info->aConstraint[a].iColumn); - auto b_idx = static_cast(info->aConstraint[b].iColumn); - const auto& a_col = s->schema.columns[a_idx]; - const auto& b_col = s->schema.columns[b_idx]; - - // Id columns are always very cheap to filter on so try and get them - // first. - if (a_col.is_id || b_col.is_id) - return a_col.is_id && !b_col.is_id; - - // Set id columns are always very cheap to filter on so try and get them - // second. - if (a_col.is_set_id || b_col.is_set_id) - return a_col.is_set_id && !b_col.is_set_id; - - // Sorted columns are also quite cheap to filter so order them after - // any id/set id columns. - if (a_col.is_sorted || b_col.is_sorted) - return a_col.is_sorted && !b_col.is_sorted; - - // TODO(lalitm): introduce more orderings here based on empirical data. - return false; - }); + std::sort( + cs_idxes.begin(), cs_idxes.end(), [s, info, &table](int a, int b) { + auto a_idx = static_cast(info->aConstraint[a].iColumn); + auto b_idx = static_cast(info->aConstraint[b].iColumn); + const auto& a_col = s->schema.columns[a_idx]; + const auto& b_col = s->schema.columns[b_idx]; + + // Id columns are the most efficient to filter, as they don't have a + // support in real data. + if (a_col.is_id || b_col.is_id) + return a_col.is_id && !b_col.is_id; + + // Set id columns are inherently sorted and have fast filtering + // operations. + if (a_col.is_set_id || b_col.is_set_id) + return a_col.is_set_id && !b_col.is_set_id; + + // Intrinsically sorted column is more efficient to sort than + // extrinsically sorted column. + if (a_col.is_sorted || b_col.is_sorted) + return a_col.is_sorted && !b_col.is_sorted; + + // Extrinsically sorted column is more efficient to sort than unsorted + // column. + if (table) { + auto a_has_idx = table->GetIndex({a_idx}); + auto b_has_idx = table->GetIndex({b_idx}); + if (a_has_idx || b_has_idx) + return a_has_idx && !b_has_idx; + } + + bool a_is_eq = sqlite::utils::IsOpEq(info->aConstraint[a].op); + bool b_is_eq = sqlite::utils::IsOpEq(info->aConstraint[a].op); + if (a_is_eq || b_is_eq) { + return a_is_eq && !b_is_eq; + } + + // TODO(lalitm): introduce more orderings here based on empirical + // data. + return false; + }); } // Remove any order by constraints which also have an equality constraint. @@ -571,7 +600,7 @@ int DbSqliteModule::BestIndex(sqlite3_vtab* vtab, sqlite3_index_info* info) { // Distinct: idx_str += "D"; - if (ob_idxes.size() == 1) { + if (ob_idxes.size() == 1 && PERFETTO_POPCOUNT(info->colUsed) == 1) { switch (sqlite3_vtab_distinct(info)) { case 0: case 1: @@ -597,7 +626,7 @@ int DbSqliteModule::BestIndex(sqlite3_vtab* vtab, sqlite3_index_info* info) { // LIMIT. Save as "L1" if limit is present and "L0" if not. idx_str += "L"; - if (limit == -1) { + if (limit == -1 || has_unknown_constraint) { idx_str += "0"; } else { auto& o = info->aConstraintUsage[limit]; @@ -609,7 +638,7 @@ int DbSqliteModule::BestIndex(sqlite3_vtab* vtab, sqlite3_index_info* info) { // OFFSET. Save as "O1" if offset is present and "O0" if not. idx_str += "O"; - if (offset == -1) { + if (offset == -1 || has_unknown_constraint) { idx_str += "0"; } else { auto& o = info->aConstraintUsage[offset]; @@ -896,7 +925,7 @@ DbSqliteModule::QueryCost DbSqliteModule::EstimateCost( return QueryCost{final_cost, current_row_count}; } -DbSqliteModule::State::State(const Table* _table, Table::Schema _schema) +DbSqliteModule::State::State(Table* _table, Table::Schema _schema) : State(TableComputation::kStatic, std::move(_schema)) { static_table = _table; } diff --git a/src/trace_processor/sqlite/db_sqlite_table.h b/src/trace_processor/sqlite/db_sqlite_table.h index 1085e39dd6..4085e7c79a 100644 --- a/src/trace_processor/sqlite/db_sqlite_table.h +++ b/src/trace_processor/sqlite/db_sqlite_table.h @@ -48,7 +48,7 @@ enum class TableComputation { // Implements the SQLite table interface for db tables. struct DbSqliteModule : public sqlite::Module { struct State { - State(const Table*, Table::Schema); + State(Table*, Table::Schema); explicit State(std::unique_ptr); explicit State(std::unique_ptr); @@ -57,7 +57,7 @@ struct DbSqliteModule : public sqlite::Module { int argument_count = 0; // Only valid when computation == TableComputation::kStatic. - const Table* static_table = nullptr; + Table* static_table = nullptr; // Only valid when computation == TableComputation::kRuntime. std::unique_ptr runtime_table; diff --git a/src/trace_processor/sqlite/module_lifecycle_manager.h b/src/trace_processor/sqlite/module_lifecycle_manager.h index 66054d599d..f1f93e82ba 100644 --- a/src/trace_processor/sqlite/module_lifecycle_manager.h +++ b/src/trace_processor/sqlite/module_lifecycle_manager.h @@ -67,6 +67,11 @@ class ModuleStateManager { public: // Per-vtab state. The pointer to this class should be stored in the Vtab. struct PerVtabState { + private: + // The below fields should only be accessed by the manager, use GetState to + // access the state from outside this class. + friend class ModuleStateManager; + ModuleStateManager* manager; bool disconnected = false; std::string table_name; diff --git a/src/trace_processor/sqlite/sql_source.cc b/src/trace_processor/sqlite/sql_source.cc index 9de3a03cf8..6650d71b59 100644 --- a/src/trace_processor/sqlite/sql_source.cc +++ b/src/trace_processor/sqlite/sql_source.cc @@ -250,8 +250,8 @@ SqlSource::Node SqlSource::Node::Substr(uint32_t offset, uint32_t len) const { std::string new_rewritten = rewritten_sql.substr(offset, len); PERFETTO_DCHECK(ApplyRewrites(new_original, new_rewrites) == new_rewritten); - auto line_and_col = GetLineAndColumnForOffset(rewritten_sql, line, col, - original_offset_start); + auto line_and_col = + GetLineAndColumnForOffset(original_sql, line, col, original_offset_start); return Node{ name, include_traceback_header, diff --git a/src/trace_processor/sqlite/sqlite_utils.h b/src/trace_processor/sqlite/sqlite_utils.h index 77654e37b2..1e5d510240 100644 --- a/src/trace_processor/sqlite/sqlite_utils.h +++ b/src/trace_processor/sqlite/sqlite_utils.h @@ -34,6 +34,21 @@ #include "perfetto/trace_processor/basic_types.h" #include "src/trace_processor/sqlite/bindings/sqlite_result.h" +// Analogous to ASSIGN_OR_RETURN macro. Returns an sqlite error. +#define SQLITE_RETURN_IF_ERROR(vtab, expr) \ + do { \ + const base::Status& status_macro_internal_status = (expr); \ + if (!status_macro_internal_status.ok()) \ + return sqlite::utils::SetError((vtab), status_macro_internal_status); \ + } while (0) + +// Analogous to ASSIGN_OR_RETURN macro. Returns an sqlite error. +#define SQLITE_ASSIGN_OR_RETURN(vtab, lhs, rhs) \ + PERFETTO_INTERNAL_MACRO_CONCAT(auto status_or, __LINE__) = rhs; \ + SQLITE_RETURN_IF_ERROR( \ + vtab, PERFETTO_INTERNAL_MACRO_CONCAT(status_or, __LINE__).status()); \ + lhs = std::move(PERFETTO_INTERNAL_MACRO_CONCAT(status_or, __LINE__).value()) + namespace perfetto::trace_processor::sqlite::utils { const auto kSqliteStatic = reinterpret_cast(0); @@ -144,6 +159,14 @@ inline int SetError(sqlite3_vtab* tab, const char* status) { return SQLITE_ERROR; } +inline void SetError(sqlite3_context* ctx, const char* status) { + sqlite::result::Error(ctx, status); +} + +inline int SetError(sqlite3_vtab* tab, base::Status s) { + return SetError(tab, s.c_message()); +} + inline void SetError(sqlite3_context* ctx, const base::Status& status) { PERFETTO_CHECK(!status.ok()); sqlite::result::Error(ctx, status.c_message()); @@ -158,7 +181,7 @@ inline void SetError(sqlite3_context* ctx, // For a given |sqlite3_index_info| struct received in a BestIndex call, returns // whether all |arg_count| arguments (with |is_arg_column| indicating whether a -// given column is a function argument) have exactly one equaltiy constraint +// given column is a function argument) have exactly one equality constraint // associated with them. // // If so, the associated constraint is omitted and the argvIndex is mapped to @@ -204,8 +227,24 @@ inline base::Status ValidateFunctionArguments( return base::OkStatus(); } +inline const char* SqlValueTypeToString(SqlValue::Type type) { + switch (type) { + case SqlValue::Type::kString: + return "STRING"; + case SqlValue::Type::kDouble: + return "DOUBLE"; + case SqlValue::Type::kLong: + return "LONG"; + case SqlValue::Type::kBytes: + return "BYTES"; + case SqlValue::Type::kNull: + return "NULL"; + } + PERFETTO_FATAL("For GCC"); +} + // Converts the given SqlValue type to the type string SQLite understands. -inline std::string SqlValueTypeToString(SqlValue::Type type) { +inline std::string SqlValueTypeToSqliteTypeName(SqlValue::Type type) { switch (type) { case SqlValue::Type::kString: return "TEXT"; @@ -216,7 +255,10 @@ inline std::string SqlValueTypeToString(SqlValue::Type type) { case SqlValue::Type::kBytes: return "BLOB"; case SqlValue::Type::kNull: - PERFETTO_FATAL("Cannot map unknown column type"); + // Default to BIGINT for columns which contains only NULLs - if we don't + // specify the type, sqlite will default to BLOB, which is going to trip + // a number of various checks. + return "BIGINT"; } PERFETTO_FATAL("Not reached"); // For gcc } diff --git a/src/trace_processor/sqlite/sqlite_value.h b/src/trace_processor/sqlite/sqlite_value.h deleted file mode 100644 index 7f447a2907..0000000000 --- a/src/trace_processor/sqlite/sqlite_value.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef SRC_TRACE_PROCESSOR_SQLITE_SQLITE_VALUE_H_ -#define SRC_TRACE_PROCESSOR_SQLITE_SQLITE_VALUE_H_ - -#include -#include - -struct sqlite_value; - -namespace perfetto::trace_processor::sqlite::value { - -// This file contains thin wrappers around the sqlite3_value_* functions which -// fetches data from SQLite in fuction definitions, virtual table filter clauses -// etc. - -inline int64_t Long(sqlite3_value* value) { - return sqlite3_value_int64(value); -} - -inline bool IsNull(sqlite3_value* value) { - return sqlite3_value_type(value) == SQLITE_NULL; -} - -} // namespace perfetto::trace_processor::sqlite::value - -#endif // SRC_TRACE_PROCESSOR_SQLITE_SQLITE_VALUE_H_ diff --git a/src/trace_processor/storage/metadata.h b/src/trace_processor/storage/metadata.h index 864d22312d..36c781bd7d 100644 --- a/src/trace_processor/storage/metadata.h +++ b/src/trace_processor/storage/metadata.h @@ -32,6 +32,8 @@ namespace metadata { F(all_data_source_started_ns, KeyType::kSingle, Variadic::kInt), \ F(android_build_fingerprint, KeyType::kSingle, Variadic::kString), \ F(android_sdk_version, KeyType::kSingle, Variadic::kInt), \ + F(android_soc_model, KeyType::kSingle, Variadic::kString), \ + F(android_hardware_revision, KeyType::kSingle, Variadic::kString), \ F(benchmark_description, KeyType::kSingle, Variadic::kString), \ F(benchmark_had_failures, KeyType::kSingle, Variadic::kInt), \ F(benchmark_label, KeyType::kSingle, Variadic::kString), \ diff --git a/src/trace_processor/storage/stats.h b/src/trace_processor/storage/stats.h index 9067b0977b..a19e5be2d7 100644 --- a/src/trace_processor/storage/stats.h +++ b/src/trace_processor/storage/stats.h @@ -155,7 +155,13 @@ namespace stats { F(traced_buf_patches_succeeded, kIndexed, kInfo, kTrace, ""), \ F(traced_buf_readaheads_failed, kIndexed, kInfo, kTrace, ""), \ F(traced_buf_readaheads_succeeded, kIndexed, kInfo, kTrace, ""), \ - F(traced_buf_trace_writer_packet_loss, kIndexed, kDataLoss, kTrace, ""), \ + F(traced_buf_trace_writer_packet_loss, kIndexed, kDataLoss, kTrace, \ + "The tracing service observed packet loss for this buffer during this " \ + "tracing session. This also counts packet loss that happened before " \ + "the RING_BUFFER start or after the DISCARD buffer end."), \ + F(traced_buf_sequence_packet_loss, kIndexed, kDataLoss, kAnalysis, \ + "The number of groups of consecutive packets lost in each sequence for " \ + "this buffer"), \ F(traced_buf_write_wrap_count, kIndexed, kInfo, kTrace, ""), \ F(traced_chunks_discarded, kSingle, kInfo, kTrace, ""), \ F(traced_data_sources_registered, kSingle, kInfo, kTrace, ""), \ @@ -262,8 +268,15 @@ namespace stats { F(perf_process_shard_count, kIndexed, kInfo, kTrace, ""), \ F(perf_chosen_process_shard, kIndexed, kInfo, kTrace, ""), \ F(perf_guardrail_stop_ts, kIndexed, kDataLoss, kTrace, ""), \ - F(perf_samples_skipped, kSingle, kInfo, kTrace, ""), \ + F(perf_unknown_record_type, kIndexed, kInfo, kAnalysis, ""), \ + F(perf_record_skipped, kSingle, kError, kAnalysis, ""), \ + F(perf_samples_skipped, kSingle, kError, kAnalysis, ""), \ + F(perf_counter_skipped_because_no_cpu, kSingle, kError, kAnalysis, ""), \ + F(perf_features_skipped, kIndexed, kInfo, kAnalysis, ""), \ + F(perf_samples_cpu_mode_unknown, kSingle, kError, kAnalysis, ""), \ F(perf_samples_skipped_dataloss, kSingle, kDataLoss, kTrace, ""), \ + F(perf_dummy_mapping_used, kSingle, kInfo, kAnalysis, ""), \ + F(perf_invalid_event_id, kSingle, kError, kTrace, ""), \ F(memory_snapshot_parser_failure, kSingle, kError, kAnalysis, ""), \ F(thread_time_in_state_out_of_order, kSingle, kError, kAnalysis, ""), \ F(thread_time_in_state_unknown_cpu_freq, \ @@ -291,6 +304,10 @@ namespace stats { F(v8_intern_errors, \ kSingle, kDataLoss, kAnalysis, \ "Failed to resolve V8 interned data."), \ + F(v8_isolate_has_no_code_range, \ + kSingle, kError, kAnalysis, \ + "V8 isolate had no code range. THis is currently no supported and means" \ + "we will be unable to parse JS code events for this isolate."), \ F(v8_no_defaults, \ kSingle, kDataLoss, kAnalysis, \ "Failed to resolve V8 default data."), \ @@ -338,13 +355,31 @@ namespace stats { F(winscope_protolog_missing_interned_stacktrace_parse_errors, \ kSingle, kInfo, kAnalysis, \ "Failed to find interned ProtoLog stacktrace."), \ + F(winscope_viewcapture_parse_errors, \ + kSingle, kInfo, kAnalysis, \ + "ViewCapture packet has unknown fields, which results in some " \ + "arguments missing. You may need a newer version of trace processor " \ + "to parse them."), \ + F(winscope_viewcapture_missing_interned_string_parse_errors, \ + kSingle, kInfo, kAnalysis, \ + "Failed to find interned ViewCapture string."), \ + F(winscope_windowmanager_parse_errors, kSingle, kInfo, kAnalysis, \ + "WindowManager state packet has unknown fields, which results " \ + "in some arguments missing. You may need a newer version of trace " \ + "processor to parse them."), \ F(jit_unknown_frame, kSingle, kDataLoss, kTrace, \ "Indicates that we were unable to determine the function for a frame in "\ "a jitted memory region"), \ F(ftrace_missing_event_id, kSingle, kInfo, kAnalysis, \ "Indicates that the ftrace event was dropped because the event id was " \ "missing. This is an 'info' stat rather than an error stat because " \ - "this can be legitimately missing due to proto filtering.") + "this can be legitimately missing due to proto filtering."), \ + F(android_input_event_parse_errors, kSingle, kInfo, kAnalysis, \ + "Android input event packet has unknown fields, which results " \ + "in some arguments missing. You may need a newer version of trace " \ + "processor to parse them."), \ + F(mali_unknown_mcu_state_id, kSingle, kError, kAnalysis, \ + "An invalid Mali GPU MCU state ID was detected.") // clang-format on enum Type { diff --git a/src/trace_processor/storage/trace_storage.h b/src/trace_processor/storage/trace_storage.h index ff280e16c8..74862f1270 100644 --- a/src/trace_processor/storage/trace_storage.h +++ b/src/trace_processor/storage/trace_storage.h @@ -514,6 +514,29 @@ class TraceStorage { return &android_dumpstate_table_; } + const tables::AndroidKeyEventsTable& android_key_events_table() const { + return android_key_events_table_; + } + tables::AndroidKeyEventsTable* mutable_android_key_events_table() { + return &android_key_events_table_; + } + + const tables::AndroidMotionEventsTable& android_motion_events_table() const { + return android_motion_events_table_; + } + tables::AndroidMotionEventsTable* mutable_android_motion_events_table() { + return &android_motion_events_table_; + } + + const tables::AndroidInputEventDispatchTable& + android_input_event_dispatch_table() const { + return android_input_event_dispatch_table_; + } + tables::AndroidInputEventDispatchTable* + mutable_android_input_event_dispatch_table() { + return &android_input_event_dispatch_table_; + } + const StatsMap& stats() const { return stats_; } const tables::MetadataTable& metadata_table() const { @@ -603,6 +626,13 @@ class TraceStorage { return &profiler_smaps_table_; } + const tables::TraceFileTable& trace_file_table() const { + return trace_file_table_; + } + tables::TraceFileTable* mutable_trace_file_table() { + return &trace_file_table_; + } + const tables::StackSampleTable& stack_sample_table() const { return stack_sample_table_; } @@ -618,6 +648,13 @@ class TraceStorage { return &cpu_profile_stack_sample_table_; } + const tables::PerfSessionTable& perf_session_table() const { + return perf_session_table_; + } + tables::PerfSessionTable* mutable_perf_session_table() { + return &perf_session_table_; + } + const tables::PerfSampleTable& perf_sample_table() const { return perf_sample_table_; } @@ -740,6 +777,14 @@ class TraceStorage { return &actual_frame_timeline_slice_table_; } + const tables::AndroidNetworkPacketsTable& android_network_packets_table() + const { + return android_network_packets_table_; + } + tables::AndroidNetworkPacketsTable* mutable_android_network_packets_table() { + return &android_network_packets_table_; + } + const tables::V8IsolateTable& v8_isolate_table() const { return v8_isolate_table_; } @@ -845,6 +890,20 @@ class TraceStorage { return &surfaceflinger_transactions_table_; } + const tables::ViewCaptureTable& viewcapture_table() const { + return viewcapture_table_; + } + tables::ViewCaptureTable* mutable_viewcapture_table() { + return &viewcapture_table_; + } + + const tables::WindowManagerTable& windowmanager_table() const { + return windowmanager_table_; + } + tables::WindowManagerTable* mutable_windowmanager_table() { + return &windowmanager_table_; + } + const tables::WindowManagerShellTransitionsTable& window_manager_shell_transitions_table() const { return window_manager_shell_transitions_table_; @@ -1083,6 +1142,11 @@ class TraceStorage { tables::AndroidDumpstateTable android_dumpstate_table_{&string_pool_}; + tables::AndroidKeyEventsTable android_key_events_table_{&string_pool_}; + tables::AndroidMotionEventsTable android_motion_events_table_{&string_pool_}; + tables::AndroidInputEventDispatchTable + android_input_event_dispatch_table_{&string_pool_}; + tables::StackProfileMappingTable stack_profile_mapping_table_{&string_pool_}; tables::StackProfileFrameTable stack_profile_frame_table_{&string_pool_}; tables::StackProfileCallsiteTable stack_profile_callsite_table_{ @@ -1092,12 +1156,15 @@ class TraceStorage { &string_pool_}; tables::CpuProfileStackSampleTable cpu_profile_stack_sample_table_{ &string_pool_, &stack_sample_table_}; + tables::PerfSessionTable perf_session_table_{&string_pool_}; tables::PerfSampleTable perf_sample_table_{&string_pool_}; tables::PackageListTable package_list_table_{&string_pool_}; tables::AndroidGameInterventionListTable android_game_intervention_list_table_{&string_pool_}; tables::ProfilerSmapsTable profiler_smaps_table_{&string_pool_}; + tables::TraceFileTable trace_file_table_{&string_pool_}; + // Symbol tables (mappings from frames to symbol names) tables::SymbolTable symbol_table_{&string_pool_}; tables::HeapGraphObjectTable heap_graph_object_table_{&string_pool_}; @@ -1123,6 +1190,10 @@ class TraceStorage { tables::ActualFrameTimelineSliceTable actual_frame_timeline_slice_table_{ &string_pool_, &slice_table_}; + // AndroidNetworkPackets tables + tables::AndroidNetworkPacketsTable android_network_packets_table_{ + &string_pool_, &slice_table_}; + // V8 tables tables::V8IsolateTable v8_isolate_table_{&string_pool_}; tables::V8JsScriptTable v8_js_script_table_{&string_pool_}; @@ -1147,6 +1218,8 @@ class TraceStorage { tables::SurfaceFlingerLayerTable surfaceflinger_layer_table_{&string_pool_}; tables::SurfaceFlingerTransactionsTable surfaceflinger_transactions_table_{ &string_pool_}; + tables::ViewCaptureTable viewcapture_table_{&string_pool_}; + tables::WindowManagerTable windowmanager_table_{&string_pool_}; tables::WindowManagerShellTransitionsTable window_manager_shell_transitions_table_{&string_pool_}; tables::WindowManagerShellTransitionHandlersTable diff --git a/src/trace_processor/tables/android_tables.py b/src/trace_processor/tables/android_tables.py index 88f25921b2..e086d3d8f3 100644 --- a/src/trace_processor/tables/android_tables.py +++ b/src/trace_processor/tables/android_tables.py @@ -14,11 +14,11 @@ """Contains tables for relevant for Android.""" from python.generators.trace_processor_table.public import Column as C +from python.generators.trace_processor_table.public import ColumnDoc from python.generators.trace_processor_table.public import CppDouble from python.generators.trace_processor_table.public import CppInt32 from python.generators.trace_processor_table.public import CppInt64 from python.generators.trace_processor_table.public import CppOptional -from python.generators.trace_processor_table.public import CppSelfTableId from python.generators.trace_processor_table.public import CppString from python.generators.trace_processor_table.public import Table from python.generators.trace_processor_table.public import TableDoc @@ -156,9 +156,100 @@ ''' })) +ANDROID_MOTION_EVENTS_TABLE = Table( + python_module=__file__, + class_name='AndroidMotionEventsTable', + sql_name='__intrinsic_android_motion_events', + columns=[ + C('event_id', CppUint32()), + C('ts', CppInt64()), + C('arg_set_id', CppUint32()), + ], + tabledoc=TableDoc( + doc='Contains Android MotionEvents processed by the system', + group='Android', + columns={ + 'event_id': + ''' + The randomly-generated ID associated with each input event processed + by Android Framework, used to track the event through the input pipeline. + ''', + 'ts': + '''The timestamp of when the input event was processed by the system.''', + 'arg_set_id': + ColumnDoc( + doc='Details of the motion event parsed from the proto message.', + joinable='args.arg_set_id'), + })) + +ANDROID_KEY_EVENTS_TABLE = Table( + python_module=__file__, + class_name='AndroidKeyEventsTable', + sql_name='__intrinsic_android_key_events', + columns=[ + C('event_id', CppUint32()), + C('ts', CppInt64()), + C('arg_set_id', CppUint32()), + ], + tabledoc=TableDoc( + doc='Contains Android KeyEvents processed by the system', + group='Android', + columns={ + 'event_id': + ''' + The randomly-generated ID associated with each input event processed + by Android Framework, used to track the event through the input pipeline. + ''', + 'ts': + '''The timestamp of when the input event was processed by the system.''', + 'arg_set_id': + ColumnDoc( + doc='Details of the key event parsed from the proto message.', + joinable='args.arg_set_id'), + })) + +ANDROID_INPUT_EVENT_DISPATCH_TABLE = Table( + python_module=__file__, + class_name='AndroidInputEventDispatchTable', + sql_name='__intrinsic_android_input_event_dispatch', + columns=[ + C('event_id', CppUint32()), + C('arg_set_id', CppUint32()), + C('vsync_id', CppInt64()), + C('window_id', CppInt32()), + ], + tabledoc=TableDoc( + doc= + ''' + Contains records of Android input events being dispatched to input windows + by the Android Framework. + ''', + group='Android', + columns={ + 'event_id': + ColumnDoc( + doc='The id of the input event that was dispatched.', + joinable='__intrinsic_android_motion_events.event_id'), + 'arg_set_id': + ColumnDoc( + doc='Details of the dispatched event parsed from the proto message.', + joinable='args.arg_set_id'), + 'vsync_id': + ''' + The id of the vsync during which the Framework made the decision to + dispatch this input event, used to identify the state of the input windows + when the dispatching decision was made. + ''', + 'window_id': + 'The id of the window to which the event was dispatched.', + })) + # Keep this list sorted. ALL_TABLES = [ ANDROID_LOG_TABLE, ANDROID_DUMPSTATE_TABLE, ANDROID_GAME_INTERVENTION_LIST_TABLE, + ANDROID_KEY_EVENTS_TABLE, + ANDROID_MOTION_EVENTS_TABLE, + ANDROID_INPUT_EVENT_DISPATCH_TABLE, ] diff --git a/src/trace_processor/tables/counter_tables.py b/src/trace_processor/tables/counter_tables.py index 3a9e06251f..6892aec000 100644 --- a/src/trace_processor/tables/counter_tables.py +++ b/src/trace_processor/tables/counter_tables.py @@ -35,25 +35,15 @@ C('track_id', CppTableId(COUNTER_TRACK_TABLE)), C('value', CppDouble()), C('arg_set_id', CppOptional(CppUint32())), - C('machine_id', CppOptional(CppTableId(MACHINE_TABLE))), ], tabledoc=TableDoc( doc='''''', group='Events', columns={ - 'ts': - '''''', - 'track_id': - '''''', - 'value': - '''''', - 'arg_set_id': - '''''', - 'machine_id': - ''' - Machine identifier, non-null for counters from a remote - machine. - ''', + 'ts': '''''', + 'track_id': '''''', + 'value': '''''', + 'arg_set_id': '''''', })) # Keep this list sorted. diff --git a/src/trace_processor/tables/metadata_tables.py b/src/trace_processor/tables/metadata_tables.py index 0d91d8d7fa..c6db97d349 100644 --- a/src/trace_processor/tables/metadata_tables.py +++ b/src/trace_processor/tables/metadata_tables.py @@ -191,18 +191,54 @@ ''', })) +CPU_TABLE = Table( + python_module=__file__, + class_name='CpuTable', + sql_name='__intrinsic_cpu', + columns=[ + C('cpu', CppOptional(CppUint32())), + C('cluster_id', CppUint32()), + C('processor', CppString()), + C('machine_id', CppOptional(CppTableId(MACHINE_TABLE))), + C('capacity', CppOptional(CppUint32())), + ], + tabledoc=TableDoc( + doc=''' + Contains information of processes seen during the trace + ''', + group='Misc', + columns={ + 'cpu': + '''the index (0-based) of the CPU core on the device''', + 'cluster_id': + '''the cluster id is shared by CPUs in +the same cluster''', + 'processor': + '''a string describing this core''', + 'machine_id': + ''' + Machine identifier, non-null for CPUs on a remote machine. + ''', + 'capacity': + ''' + Capacity of a CPU of a device, a metric which indicates the + relative performance of a CPU on a device + For details see: + https://www.kernel.org/doc/Documentation/devicetree/bindings/arm/cpu-capacity.txt + ''', + })) + RAW_TABLE = Table( python_module=__file__, class_name='RawTable', - sql_name='raw', + sql_name='__intrinsic_raw', columns=[ C('ts', CppInt64(), flags=ColumnFlag.SORTED), C('name', CppString()), - C('cpu', CppUint32()), C('utid', CppTableId(THREAD_TABLE)), C('arg_set_id', CppUint32()), C('common_flags', CppUint32()), - C('machine_id', CppOptional(CppTableId(MACHINE_TABLE))), + C('ucpu', CppTableId(CPU_TABLE)) ], tabledoc=TableDoc( doc=''' @@ -223,8 +259,6 @@ The name of the event. For ftrace events, this will be the ftrace event name. ''', - 'cpu': - 'The CPU this event was emitted on.', 'utid': 'The thread this event was emitted on.', 'common_flags': @@ -232,17 +266,16 @@ Ftrace event flags for this event. Currently only emitted for sched_waking events. ''', - 'machine_id': + 'ucpu': ''' - Machine identifier, non-null for raw events on a remote - machine. + The unique CPU indentifier. ''', })) FTRACE_EVENT_TABLE = Table( python_module=__file__, class_name='FtraceEventTable', - sql_name='ftrace_event', + sql_name='__intrinsic_ftrace_event', parent=RAW_TABLE, columns=[], tabledoc=TableDoc( @@ -357,51 +390,18 @@ 'reliable_from': '''''' })) -CPU_TABLE = Table( - python_module=__file__, - class_name='CpuTable', - sql_name='cpu', - columns=[ - C('cluster_id', CppUint32()), - C('processor', CppString()), - C('machine_id', CppOptional(CppTableId(MACHINE_TABLE))), - ], - tabledoc=TableDoc( - doc=''' - Contains information of processes seen during the trace - ''', - group='Misc', - columns={ - 'cluster_id': - '''the cluster id is shared by CPUs in -the same cluster''', - 'processor': - '''a string describing this core''', - 'machine_id': - ''' - Machine identifier, non-null for CPUs on a remote machine. - ''', - })) - CPU_FREQ_TABLE = Table( python_module=__file__, class_name='CpuFreqTable', - sql_name='cpu_freq', + sql_name='__intrinsic_cpu_freq', columns=[ - C('cpu_id', CppTableId(CPU_TABLE)), + C('ucpu', CppTableId(CPU_TABLE)), C('freq', CppUint32()), - C('machine_id', CppOptional(CppTableId(MACHINE_TABLE))), ], tabledoc=TableDoc( - doc='''''', - group='Misc', - columns={ - 'cpu_id': '''''', + doc='''''', group='Misc', columns={ + 'ucpu': '''''', 'freq': '''''', - 'machine_id': - ''' - Machine identifier, non-null for CPUs on a remote machine. - ''', })) CLOCK_SNAPSHOT_TABLE = Table( @@ -443,6 +443,37 @@ ''', })) +TRACE_FILE_TABLE = Table( + python_module=__file__, + class_name='TraceFileTable', + sql_name='__intrinsic_trace_file', + columns=[ + C('parent_id', CppOptional(CppSelfTableId())), + C('name', CppOptional(CppString())), + C('size', CppInt64()), + C('trace_type', CppString()), + ], + tabledoc=TableDoc( + doc=''' + Metadata related to the trace file parsed. Note the order in which + the files appear in this table corresponds to the order in which + they are read and sent to the tokenization stage. + ''', + group='Misc', + columns={ + 'parent_id': + ''' + Parent file. E.g. files contained in a zip file will point to + the zip file. + ''', + 'name': + '''File name, if known, NULL otherwise''', + 'size': + '''Size in bytes''', + 'trace_type': + '''Trace type''', + })) + # Keep this list sorted. ALL_TABLES = [ ARG_TABLE, @@ -451,10 +482,11 @@ CPU_TABLE, EXP_MISSING_CHROME_PROC_TABLE, FILEDESCRIPTOR_TABLE, + FTRACE_EVENT_TABLE, + MACHINE_TABLE, METADATA_TABLE, PROCESS_TABLE, RAW_TABLE, THREAD_TABLE, - FTRACE_EVENT_TABLE, - MACHINE_TABLE, + TRACE_FILE_TABLE, ] diff --git a/src/trace_processor/tables/profiler_tables.py b/src/trace_processor/tables/profiler_tables.py index 8e9cbd7904..2c55640755 100644 --- a/src/trace_processor/tables/profiler_tables.py +++ b/src/trace_processor/tables/profiler_tables.py @@ -25,7 +25,7 @@ from python.generators.trace_processor_table.public import CppTableId from python.generators.trace_processor_table.public import CppUint32 -from src.trace_processor.tables.track_tables import TRACK_TABLE +from src.trace_processor.tables.track_tables import TRACK_TABLE, COUNTER_TRACK_TABLE PROFILER_SMAPS_TABLE = Table( python_module=__file__, @@ -246,6 +246,22 @@ 'process_priority': '''''' })) +PERF_SESSION_TABLE = Table( + python_module=__file__, + class_name='PerfSessionTable', + sql_name='__intrinsic_perf_session', + columns=[ + C('cmdline', CppOptional(CppString())), + ], + tabledoc=TableDoc( + doc=''' + Perf sessions. + ''', + group='Callstack profilers', + columns={ + 'cmdline': '''Command line used to collect the data.''', + })) + PERF_SAMPLE_TABLE = Table( python_module=__file__, class_name='PerfSampleTable', @@ -253,11 +269,11 @@ columns=[ C('ts', CppInt64(), flags=ColumnFlag.SORTED), C('utid', CppUint32()), - C('cpu', CppUint32()), + C('cpu', CppOptional(CppUint32())), C('cpu_mode', CppString()), C('callsite_id', CppOptional(CppTableId(STACK_PROFILE_CALLSITE_TABLE))), C('unwind_error', CppOptional(CppString())), - C('perf_session_id', CppUint32()), + C('perf_session_id', CppTableId(PERF_SESSION_TABLE)), ], tabledoc=TableDoc( doc=''' @@ -295,8 +311,8 @@ CppUint32(), flags=ColumnFlag.SORTED | ColumnFlag.SET_ID), C('name', CppString()), - C('source_file', CppString()), - C('line_number', CppUint32()), + C('source_file', CppOptional(CppString())), + C('line_number', CppOptional(CppUint32())), ], tabledoc=TableDoc( doc=''' @@ -610,6 +626,31 @@ 'track_id': '''''' })) +PERF_COUNTER_TRACK_TABLE = Table( + python_module=__file__, + class_name='PerfCounterTrackTable', + sql_name='perf_counter_track', + columns=[ + C('perf_session_id', CppTableId(PERF_SESSION_TABLE)), + C('cpu', CppUint32()), + C('is_timebase', CppUint32()), + ], + parent=COUNTER_TRACK_TABLE, + tabledoc=TableDoc( + doc='Sampled counters\' values for samples in the perf_sample table.', + group='Counter Tracks', + columns={ + 'perf_session_id': + 'id of a distict profiling stream', + 'cpu': + 'the core the sample was taken on', + 'is_timebase': + ''' + If true, indicates this counter was the sampling timebase for + this perf_session_id + ''' + })) + # Keep this list sorted. ALL_TABLES = [ CPU_PROFILE_STACK_SAMPLE_TABLE, @@ -621,6 +662,7 @@ HEAP_PROFILE_ALLOCATION_TABLE, PACKAGE_LIST_TABLE, PERF_SAMPLE_TABLE, + PERF_SESSION_TABLE, PROFILER_SMAPS_TABLE, STACK_PROFILE_CALLSITE_TABLE, STACK_PROFILE_FRAME_TABLE, @@ -628,4 +670,5 @@ STACK_SAMPLE_TABLE, SYMBOL_TABLE, VULKAN_MEMORY_ALLOCATIONS_TABLE, + PERF_COUNTER_TRACK_TABLE, ] diff --git a/src/trace_processor/tables/sched_tables.py b/src/trace_processor/tables/sched_tables.py index 4ef4de825d..35395437bd 100644 --- a/src/trace_processor/tables/sched_tables.py +++ b/src/trace_processor/tables/sched_tables.py @@ -27,20 +27,19 @@ from python.generators.trace_processor_table.public import TableDoc from python.generators.trace_processor_table.public import WrappingSqlView -from src.trace_processor.tables.metadata_tables import MACHINE_TABLE +from src.trace_processor.tables.metadata_tables import MACHINE_TABLE, CPU_TABLE SCHED_SLICE_TABLE = Table( python_module=__file__, class_name='SchedSliceTable', - sql_name='sched_slice', + sql_name='__intrinsic_sched_slice', columns=[ C('ts', CppInt64(), flags=ColumnFlag.SORTED), C('dur', CppInt64()), - C('cpu', CppUint32()), C('utid', CppUint32()), C('end_state', CppString()), C('priority', CppInt32()), - C('machine_id', CppOptional(CppTableId(MACHINE_TABLE))), + C('ucpu', CppTableId(CPU_TABLE)), ], tabledoc=TableDoc( doc=''' @@ -58,9 +57,7 @@ 'dur': '''The duration of the slice (in nanoseconds).''', 'utid': - '''The thread's unique id in the trace..''', - 'cpu': - '''The CPU that the slice executed on.''', + '''The thread's unique id in the trace.''', 'end_state': ''' A string representing the scheduling state of the kernel @@ -74,10 +71,9 @@ ''', 'priority': '''The kernel priority that the thread ran at.''', - 'machine_id': + 'ucpu': ''' - Machine identifier, non-null for scheduling slices on a remote - machine. + The unique CPU identifier that the slice executed on. ''', })) @@ -94,18 +90,24 @@ ], tabledoc=TableDoc( doc=''' - This table contains the scheduling wakeups that occurred while a thread was - not blocked, i.e. running or runnable. Such wakeups are not tracked in the - |thread_state_table|. + This table contains the scheduling wakeups that occurred while a + thread was not blocked, i.e. running or runnable. Such wakeups are not + tracked in the |thread_state_table|. ''', group='Events', columns={ 'ts': 'The timestamp at the start of the slice (in nanoseconds).', 'thread_state_id': - 'The id of the row in the thread_state table that this row is associated with.', + ''' + The id of the row in the thread_state table that this row is + associated with. + ''', 'irq_context': - '''Whether the wakeup was from interrupt context or process context.''', + ''' + Whether the wakeup was from interrupt context or process + context. + ''', 'utid': '''The thread's unique id in the trace..''', 'waker_utid': @@ -118,11 +120,10 @@ THREAD_STATE_TABLE = Table( python_module=__file__, class_name='ThreadStateTable', - sql_name='thread_state', + sql_name='__intrinsic_thread_state', columns=[ C('ts', CppInt64(), flags=ColumnFlag.SORTED), C('dur', CppInt64()), - C('cpu', CppOptional(CppUint32())), C('utid', CppUint32()), C('state', CppString()), C('io_wait', CppOptional(CppUint32())), @@ -130,7 +131,7 @@ C('waker_utid', CppOptional(CppUint32())), C('waker_id', CppOptional(CppSelfTableId())), C('irq_context', CppOptional(CppUint32())), - C('machine_id', CppOptional(CppTableId(MACHINE_TABLE))), + C('ucpu', CppOptional(CppTableId(CPU_TABLE))), ], tabledoc=TableDoc( doc=''' @@ -146,12 +147,8 @@ 'The timestamp at the start of the slice (in nanoseconds).', 'dur': 'The duration of the slice (in nanoseconds).', - 'cpu': - '''The CPU that the slice executed on.''', - 'irq_context': - '''Whether the wakeup was from interrupt context or process context.''', 'utid': - '''The thread's unique id in the trace..''', + '''The thread's unique id in the trace.''', 'state': ''' The scheduling state of the thread. Can be "Running" or any @@ -168,11 +165,17 @@ ''', 'waker_id': ''' - The unique thread state id which caused a wakeup of this thread. + The unique thread state id which caused a wakeup of this + thread. + ''', + 'irq_context': + ''' + Whether the wakeup was from interrupt context or process + context. ''', - 'machine_id': + 'ucpu': ''' - Machine identifier, non-null for threads on a remote machine. + The unique CPU identifier that the thread executed on. ''', })) diff --git a/src/trace_processor/tables/slice_tables.py b/src/trace_processor/tables/slice_tables.py index 90c81765f8..14c2aaf64f 100644 --- a/src/trace_processor/tables/slice_tables.py +++ b/src/trace_processor/tables/slice_tables.py @@ -135,6 +135,7 @@ C('frame_id', CppOptional(CppUint32())), C('submission_id', CppOptional(CppUint32())), C('hw_queue_id', CppOptional(CppInt64())), + C('upid', CppOptional(CppUint32())), C('render_subpasses', CppString()), ], parent=SLICE_TABLE, @@ -142,17 +143,33 @@ doc='''''', group='Slice', columns={ - 'context_id': '''''', - 'render_target': '''''', - 'render_target_name': '''''', - 'render_pass': '''''', - 'render_pass_name': '''''', - 'command_buffer': '''''', - 'command_buffer_name': '''''', - 'frame_id': '''''', - 'submission_id': '''''', - 'hw_queue_id': '''''', - 'render_subpasses': '''''' + 'context_id': + '''''', + 'render_target': + '''''', + 'render_target_name': + '''''', + 'render_pass': + '''''', + 'render_pass_name': + '''''', + 'command_buffer': + '''''', + 'command_buffer_name': + '''''', + 'frame_id': + '''''', + 'submission_id': + '''''', + 'hw_queue_id': + '''''', + 'upid': + ''' + Unique process id of the app that generates this gpu render + stage event. + ''', + 'render_subpasses': + '''''' })) GRAPHICS_FRAME_SLICE_TABLE = Table( @@ -314,9 +331,61 @@ 'The id of the slice which this row originated from.', })) +ANDROID_NETWORK_PACKETS_TABLE = Table( + python_module=__file__, + class_name='AndroidNetworkPacketsTable', + sql_name='__intrinsic_android_network_packets', + columns=[ + C('iface', CppString()), + C('direction', CppString()), + C('packet_transport', CppString()), + C('packet_length', CppInt64()), + C('packet_count', CppInt64()), + C('socket_tag', CppUint32()), + C('socket_tag_str', CppString()), + C('socket_uid', CppUint32()), + C('local_port', CppOptional(CppUint32())), + C('remote_port', CppOptional(CppUint32())), + C('packet_icmp_type', CppOptional(CppUint32())), + C('packet_icmp_code', CppOptional(CppUint32())), + C('packet_tcp_flags', CppOptional(CppUint32())), + C('packet_tcp_flags_str', CppOptional(CppString())), + ], + parent=SLICE_TABLE, + tabledoc=TableDoc( + doc=""" + This table contains details on Android Network activity. + """, + group='Slice', + columns={ + 'iface': 'The name of the network interface used', + 'direction': 'The direction of traffic (Received or Transmitted)', + 'packet_transport': + 'The transport protocol of packets in this event', + 'packet_length': 'The length (in bytes) of packets in this event', + 'packet_count': 'The number of packets contained in this event', + 'socket_tag': 'The Android network tag of the socket', + 'socket_tag_str': 'The socket tag formatted as a hex string', + 'socket_uid': 'The Linux user id of the socket', + 'local_port': 'The local udp/tcp port', + 'remote_port': 'The remote udp/tcp port', + 'packet_icmp_type': 'The 1-byte ICMP type identifier', + 'packet_icmp_code': 'The 1-byte ICMP code identifier', + 'packet_tcp_flags': + 'The TCP flags as an integer bitmask (FIN=0x1, SYN=0x2, etc)', + 'packet_tcp_flags_str': + ''' + The TCP flags formatted as a string bitmask (e.g. "f...a..." for + FIN & ACK) + ''', + }, + ), +) + # Keep this list sorted. ALL_TABLES = [ ACTUAL_FRAME_TIMELINE_SLICE_TABLE, + ANDROID_NETWORK_PACKETS_TABLE, EXPECTED_FRAME_TIMELINE_SLICE_TABLE, EXPERIMENTAL_FLAT_SLICE_TABLE, GPU_SLICE_TABLE, diff --git a/src/trace_processor/tables/table_destructors.cc b/src/trace_processor/tables/table_destructors.cc index 6093c839d1..229c724d70 100644 --- a/src/trace_processor/tables/table_destructors.cc +++ b/src/trace_processor/tables/table_destructors.cc @@ -40,6 +40,9 @@ namespace tables { AndroidDumpstateTable::~AndroidDumpstateTable() = default; AndroidGameInterventionListTable::~AndroidGameInterventionListTable() = default; AndroidLogTable::~AndroidLogTable() = default; +AndroidKeyEventsTable::~AndroidKeyEventsTable() = default; +AndroidMotionEventsTable::~AndroidMotionEventsTable() = default; +AndroidInputEventDispatchTable::~AndroidInputEventDispatchTable() = default; // counter_tables_py.h CounterTable::~CounterTable() = default; @@ -61,6 +64,7 @@ ProcessTable::~ProcessTable() = default; FiledescriptorTable::~FiledescriptorTable() = default; ClockSnapshotTable::~ClockSnapshotTable() = default; MachineTable::~MachineTable() = default; +TraceFileTable::~TraceFileTable() = default; // profiler_tables_py.h StackProfileMappingTable::~StackProfileMappingTable() = default; @@ -68,6 +72,7 @@ StackProfileFrameTable::~StackProfileFrameTable() = default; StackProfileCallsiteTable::~StackProfileCallsiteTable() = default; StackSampleTable::~StackSampleTable() = default; CpuProfileStackSampleTable::~CpuProfileStackSampleTable() = default; +PerfSessionTable::~PerfSessionTable() = default; PerfSampleTable::~PerfSampleTable() = default; SymbolTable::~SymbolTable() = default; HeapProfileAllocationTable::~HeapProfileAllocationTable() = default; @@ -93,6 +98,7 @@ GraphicsFrameSliceTable::~GraphicsFrameSliceTable() = default; ExpectedFrameTimelineSliceTable::~ExpectedFrameTimelineSliceTable() = default; ActualFrameTimelineSliceTable::~ActualFrameTimelineSliceTable() = default; ExperimentalFlatSliceTable::~ExperimentalFlatSliceTable() = default; +AndroidNetworkPacketsTable::~AndroidNetworkPacketsTable() = default; // track_tables_py.h TrackTable::~TrackTable() = default; @@ -139,15 +145,17 @@ V8RegexpCodeTable::~V8RegexpCodeTable() = default; InputMethodClientsTable::~InputMethodClientsTable() = default; InputMethodManagerServiceTable::~InputMethodManagerServiceTable() = default; InputMethodServiceTable::~InputMethodServiceTable() = default; +ProtoLogTable::~ProtoLogTable() = default; SurfaceFlingerLayersSnapshotTable::~SurfaceFlingerLayersSnapshotTable() = default; SurfaceFlingerLayerTable::~SurfaceFlingerLayerTable() = default; SurfaceFlingerTransactionsTable::~SurfaceFlingerTransactionsTable() = default; +ViewCaptureTable::~ViewCaptureTable() = default; +WindowManagerTable::~WindowManagerTable() = default; WindowManagerShellTransitionsTable::~WindowManagerShellTransitionsTable() = default; WindowManagerShellTransitionHandlersTable:: ~WindowManagerShellTransitionHandlersTable() = default; -ProtoLogTable::~ProtoLogTable() = default; } // namespace tables diff --git a/src/trace_processor/tables/track_tables.py b/src/trace_processor/tables/track_tables.py index 4d7bc93f64..3cf55a8789 100644 --- a/src/trace_processor/tables/track_tables.py +++ b/src/trace_processor/tables/track_tables.py @@ -25,7 +25,7 @@ from python.generators.trace_processor_table.public import CppTableId from python.generators.trace_processor_table.public import CppUint32 -from src.trace_processor.tables.metadata_tables import MACHINE_TABLE +from src.trace_processor.tables.metadata_tables import CPU_TABLE, MACHINE_TABLE TRACK_TABLE = Table( python_module=__file__, @@ -114,15 +114,17 @@ CPU_TRACK_TABLE = Table( python_module=__file__, class_name='CpuTrackTable', - sql_name='cpu_track', + sql_name='__intrinsic_cpu_track', columns=[ - C('cpu', CppUint32()), + C('ucpu', CppTableId(CPU_TABLE)), ], parent=TRACK_TABLE, tabledoc=TableDoc( doc='Tracks which are associated to a single CPU', group='Tracks', - columns={'cpu': 'The CPU associated with this track'})) + columns={ + 'ucpu': 'The unique CPU identifier associated with this track.', + })) GPU_TRACK_TABLE = Table( python_module=__file__, @@ -241,15 +243,17 @@ CPU_COUNTER_TRACK_TABLE = Table( python_module=__file__, class_name='CpuCounterTrackTable', - sql_name='cpu_counter_track', + sql_name='__intrinsic_cpu_counter_track', columns=[ - C('cpu', CppUint32()), + C('ucpu', CppTableId(CPU_TABLE)), ], parent=COUNTER_TRACK_TABLE, tabledoc=TableDoc( doc='Tracks containing counter-like events associated to a CPU.', group='Counter Tracks', - columns={'cpu': 'The CPU this track is associated with'})) + columns={ + 'ucpu': 'The unique CPU identifier associated with this track.' + })) IRQ_COUNTER_TRACK_TABLE = Table( python_module=__file__, @@ -290,30 +294,6 @@ group='Counter Tracks', columns={'gpu_id': 'The identifier for the GPU.'})) -PERF_COUNTER_TRACK_TABLE = Table( - python_module=__file__, - class_name='PerfCounterTrackTable', - sql_name='perf_counter_track', - columns=[ - C('perf_session_id', CppUint32()), - C('cpu', CppUint32()), - C('is_timebase', CppUint32()), - ], - parent=COUNTER_TRACK_TABLE, - tabledoc=TableDoc( - doc='Sampled counters\' values for samples in the perf_sample table.', - group='Counter Tracks', - columns={ - 'perf_session_id': - 'id of a distict profiling stream', - 'cpu': - 'the core the sample was taken on', - 'is_timebase': - ''' - If true, indicates this counter was the sampling timebase for - this perf_session_id - ''' - })) ENERGY_COUNTER_TRACK_TABLE = Table( python_module=__file__, @@ -393,7 +373,6 @@ GPU_WORK_PERIOD_TRACK_TABLE, IRQ_COUNTER_TRACK_TABLE, LINUX_DEVICE_TRACK_TABLE, - PERF_COUNTER_TRACK_TABLE, PROCESS_COUNTER_TRACK_TABLE, PROCESS_TRACK_TABLE, SOFTIRQ_COUNTER_TRACK_TABLE, diff --git a/src/trace_processor/tables/winscope_tables.py b/src/trace_processor/tables/winscope_tables.py index 8c058aa18d..f1a68212bc 100644 --- a/src/trace_processor/tables/winscope_tables.py +++ b/src/trace_processor/tables/winscope_tables.py @@ -14,6 +14,7 @@ from python.generators.trace_processor_table.public import Column as C from python.generators.trace_processor_table.public import CppInt64 +from python.generators.trace_processor_table.public import ColumnFlag from python.generators.trace_processor_table.public import Table from python.generators.trace_processor_table.public import CppTableId from python.generators.trace_processor_table.public import TableDoc @@ -25,7 +26,7 @@ class_name='InputMethodClientsTable', sql_name='__intrinsic_inputmethod_clients', columns=[ - C('ts', CppInt64()), + C('ts', CppInt64(), ColumnFlag.SORTED), C('arg_set_id', CppUint32()), ], tabledoc=TableDoc( @@ -41,7 +42,7 @@ class_name='InputMethodManagerServiceTable', sql_name='__intrinsic_inputmethod_manager_service', columns=[ - C('ts', CppInt64()), + C('ts', CppInt64(), ColumnFlag.SORTED), C('arg_set_id', CppUint32()), ], tabledoc=TableDoc( @@ -57,7 +58,7 @@ class_name='InputMethodServiceTable', sql_name='__intrinsic_inputmethod_service', columns=[ - C('ts', CppInt64()), + C('ts', CppInt64(), ColumnFlag.SORTED), C('arg_set_id', CppUint32()), ], tabledoc=TableDoc( @@ -73,7 +74,7 @@ class_name='SurfaceFlingerLayersSnapshotTable', sql_name='surfaceflinger_layers_snapshot', columns=[ - C('ts', CppInt64()), + C('ts', CppInt64(), ColumnFlag.SORTED), C('arg_set_id', CppUint32()), ], tabledoc=TableDoc( @@ -105,7 +106,7 @@ class_name='SurfaceFlingerTransactionsTable', sql_name='surfaceflinger_transactions', columns=[ - C('ts', CppInt64()), + C('ts', CppInt64(), ColumnFlag.SORTED), C('arg_set_id', CppUint32()), ], tabledoc=TableDoc( @@ -117,13 +118,29 @@ 'arg_set_id': 'Extra args parsed from the proto message', })) +VIEWCAPTURE_TABLE = Table( + python_module=__file__, + class_name='ViewCaptureTable', + sql_name='__intrinsic_viewcapture', + columns=[ + C('ts', CppInt64(), ColumnFlag.SORTED), + C('arg_set_id', CppUint32()), + ], + tabledoc=TableDoc( + doc='ViewCapture', + group='Winscope', + columns={ + 'ts': 'The timestamp the views were captured', + 'arg_set_id': 'Extra args parsed from the proto message', + })) + WINDOW_MANAGER_SHELL_TRANSITIONS_TABLE = Table( python_module=__file__, class_name='WindowManagerShellTransitionsTable', sql_name='window_manager_shell_transitions', columns=[ C('ts', CppInt64()), - C('transition_id', CppInt64()), + C('transition_id', CppInt64(), ColumnFlag.SORTED), C('arg_set_id', CppUint32()), ], tabledoc=TableDoc( @@ -151,12 +168,28 @@ 'handler_name': 'The name of the handler', })) +WINDOW_MANAGER_TABLE = Table( + python_module=__file__, + class_name='WindowManagerTable', + sql_name='__intrinsic_windowmanager', + columns=[ + C('ts', CppInt64(), ColumnFlag.SORTED), + C('arg_set_id', CppUint32()), + ], + tabledoc=TableDoc( + doc='WindowManager', + group='Winscope', + columns={ + 'ts': 'The timestamp the state snapshot was captured', + 'arg_set_id': 'Extra args parsed from the proto message', + })) + PROTOLOG_TABLE = Table( python_module=__file__, class_name='ProtoLogTable', sql_name='protolog', columns=[ - C('ts', CppInt64()), + C('ts', CppInt64(), ColumnFlag.SORTED), C('level', CppString()), C('tag', CppString()), C('message', CppString()), @@ -182,6 +215,8 @@ SURFACE_FLINGER_LAYERS_SNAPSHOT_TABLE, SURFACE_FLINGER_LAYER_TABLE, SURFACE_FLINGER_TRANSACTIONS_TABLE, + VIEWCAPTURE_TABLE, WINDOW_MANAGER_SHELL_TRANSITIONS_TABLE, WINDOW_MANAGER_SHELL_TRANSITION_HANDLERS_TABLE, + WINDOW_MANAGER_TABLE, ] diff --git a/src/trace_processor/trace_database_integrationtest.cc b/src/trace_processor/trace_database_integrationtest.cc index d09c8a7b44..731f4e375e 100644 --- a/src/trace_processor/trace_database_integrationtest.cc +++ b/src/trace_processor/trace_database_integrationtest.cc @@ -14,17 +14,23 @@ * limitations under the License. */ -#include +#include #include -#include -#include +#include +#include #include #include +#include +#include +#include "perfetto/base/build_config.h" #include "perfetto/base/logging.h" +#include "perfetto/base/status.h" #include "perfetto/ext/base/scoped_file.h" #include "perfetto/ext/base/string_utils.h" #include "perfetto/trace_processor/basic_types.h" +#include "perfetto/trace_processor/iterator.h" +#include "perfetto/trace_processor/status.h" #include "perfetto/trace_processor/trace_processor.h" #include "protos/perfetto/common/descriptor.pbzero.h" #include "protos/perfetto/trace_processor/trace_processor.pbzero.h" @@ -32,11 +38,12 @@ #include "src/base/test/utils.h" #include "test/gtest_and_gmock.h" -namespace perfetto { -namespace trace_processor { +namespace perfetto::trace_processor { namespace { -constexpr size_t kMaxChunkSize = 4 * 1024 * 1024; +using testing::HasSubstr; + +constexpr size_t kMaxChunkSize = 4ul * 1024 * 1024; TEST(TraceProcessorCustomConfigTest, SkipInternalMetricsMatchingMountPath) { auto config = Config(); @@ -97,12 +104,13 @@ class TraceProcessorIntegrationTest : public ::testing::Test { : processor_(TraceProcessor::CreateInstance(Config())) {} protected: - util::Status LoadTrace(const char* name, + base::Status LoadTrace(const char* name, size_t min_chunk_size = 512, size_t max_chunk_size = kMaxChunkSize) { EXPECT_LE(min_chunk_size, max_chunk_size); - base::ScopedFstream f(fopen( - base::GetTestDataPath(std::string("test/data/") + name).c_str(), "rb")); + base::ScopedFstream f( + fopen(base::GetTestDataPath(std::string("test/data/") + name).c_str(), + "rbe")); std::minstd_rand0 rnd_engine(0); std::uniform_int_distribution dist(min_chunk_size, max_chunk_size); while (!feof(*f)) { @@ -118,7 +126,7 @@ class TraceProcessorIntegrationTest : public ::testing::Test { } Iterator Query(const std::string& query) { - return processor_->ExecuteQuery(query.c_str()); + return processor_->ExecuteQuery(query); } TraceProcessor* Processor() { return processor_.get(); } @@ -280,7 +288,7 @@ TEST_F(TraceProcessorIntegrationTest, SerializeMetricDescriptors) { TEST_F(TraceProcessorIntegrationTest, ComputeMetricsFormattedExtension) { std::string metric_output; - util::Status status = Processor()->ComputeMetricText( + base::Status status = Processor()->ComputeMetricText( std::vector{"test_chrome_metric"}, TraceProcessor::MetricResultFormat::kProtoText, &metric_output); ASSERT_TRUE(status.ok()); @@ -293,7 +301,7 @@ TEST_F(TraceProcessorIntegrationTest, ComputeMetricsFormattedExtension) { TEST_F(TraceProcessorIntegrationTest, ComputeMetricsFormattedNoExtension) { std::string metric_output; - util::Status status = Processor()->ComputeMetricText( + base::Status status = Processor()->ComputeMetricText( std::vector{"trace_metadata"}, TraceProcessor::MetricResultFormat::kProtoText, &metric_output); ASSERT_TRUE(status.ok()); @@ -321,21 +329,21 @@ TEST_F(TraceProcessorIntegrationTest, Clusterfuzz14753) { } TEST_F(TraceProcessorIntegrationTest, Clusterfuzz14762) { - ASSERT_TRUE(LoadTrace("clusterfuzz_14762", 4096 * 1024).ok()); + ASSERT_TRUE(LoadTrace("clusterfuzz_14762", 4096ul * 1024).ok()); auto it = Query("select sum(value) from stats where severity = 'error';"); ASSERT_TRUE(it.Next()); ASSERT_GT(it.Get(0).long_value, 0); } TEST_F(TraceProcessorIntegrationTest, Clusterfuzz14767) { - ASSERT_TRUE(LoadTrace("clusterfuzz_14767", 4096 * 1024).ok()); + ASSERT_TRUE(LoadTrace("clusterfuzz_14767", 4096ul * 1024).ok()); auto it = Query("select sum(value) from stats where severity = 'error';"); ASSERT_TRUE(it.Next()); ASSERT_GT(it.Get(0).long_value, 0); } TEST_F(TraceProcessorIntegrationTest, Clusterfuzz14799) { - ASSERT_TRUE(LoadTrace("clusterfuzz_14799", 4096 * 1024).ok()); + ASSERT_TRUE(LoadTrace("clusterfuzz_14799", 4096ul * 1024).ok()); auto it = Query("select sum(value) from stats where severity = 'error';"); ASSERT_TRUE(it.Next()); ASSERT_GT(it.Get(0).long_value, 0); @@ -793,6 +801,15 @@ TEST_F(TraceProcessorIntegrationTest, FunctionRegistrationError) { ASSERT_TRUE(it.Status().ok()); } +TEST_F(TraceProcessorIntegrationTest, CreateTableDuplicateNames) { + auto it = Query( + "create perfetto table foo select 1 as duplicate_a, 2 as duplicate_a, 3 " + "as duplicate_b, 4 as duplicate_b"); + ASSERT_FALSE(it.Next()); + ASSERT_FALSE(it.Status().ok()); + ASSERT_THAT(it.Status().message(), HasSubstr("duplicate_a")); + ASSERT_THAT(it.Status().message(), HasSubstr("duplicate_b")); +} + } // namespace -} // namespace trace_processor -} // namespace perfetto +} // namespace perfetto::trace_processor diff --git a/src/trace_processor/trace_processor_context.cc b/src/trace_processor/trace_processor_context.cc index 2299a1b799..0da469324f 100644 --- a/src/trace_processor/trace_processor_context.cc +++ b/src/trace_processor/trace_processor_context.cc @@ -15,6 +15,7 @@ */ #include "src/trace_processor/types/trace_processor_context.h" +#include #include #include "src/trace_processor/forwarding_trace_parser.h" @@ -24,6 +25,7 @@ #include "src/trace_processor/importers/common/chunked_trace_reader.h" #include "src/trace_processor/importers/common/clock_converter.h" #include "src/trace_processor/importers/common/clock_tracker.h" +#include "src/trace_processor/importers/common/cpu_tracker.h" #include "src/trace_processor/importers/common/deobfuscation_mapping_table.h" #include "src/trace_processor/importers/common/event_tracker.h" #include "src/trace_processor/importers/common/flow_tracker.h" @@ -31,13 +33,16 @@ #include "src/trace_processor/importers/common/machine_tracker.h" #include "src/trace_processor/importers/common/mapping_tracker.h" #include "src/trace_processor/importers/common/metadata_tracker.h" +#include "src/trace_processor/importers/common/process_track_translation_table.h" #include "src/trace_processor/importers/common/process_tracker.h" #include "src/trace_processor/importers/common/sched_event_tracker.h" #include "src/trace_processor/importers/common/slice_tracker.h" #include "src/trace_processor/importers/common/slice_translation_table.h" #include "src/trace_processor/importers/common/stack_profile_tracker.h" +#include "src/trace_processor/importers/common/trace_file_tracker.h" #include "src/trace_processor/importers/common/track_tracker.h" #include "src/trace_processor/importers/ftrace/ftrace_module.h" +#include "src/trace_processor/importers/proto/android_track_event.descriptor.h" #include "src/trace_processor/importers/proto/chrome_track_event.descriptor.h" #include "src/trace_processor/importers/proto/multi_machine_trace_manager.h" #include "src/trace_processor/importers/proto/perf_sample_tracker.h" @@ -45,6 +50,7 @@ #include "src/trace_processor/importers/proto/track_event.descriptor.h" #include "src/trace_processor/importers/proto/track_event_module.h" #include "src/trace_processor/sorter/trace_sorter.h" +#include "src/trace_processor/trace_reader_registry.h" #include "src/trace_processor/types/destructible.h" namespace perfetto { @@ -52,6 +58,7 @@ namespace trace_processor { TraceProcessorContext::TraceProcessorContext(const InitArgs& args) : config(args.config), storage(args.storage) { + reader_registry = std::make_unique(this); // Init the trackers. machine_tracker.reset(new MachineTracker(this, args.raw_machine_id)); if (!machine_id()) { @@ -67,12 +74,15 @@ TraceProcessorContext::TraceProcessorContext(const InitArgs& args) event_tracker.reset(new EventTracker(this)); sched_event_tracker.reset(new SchedEventTracker(this)); process_tracker.reset(new ProcessTracker(this)); + process_track_translation_table.reset( + new ProcessTrackTranslationTable(storage.get())); clock_tracker.reset(new ClockTracker(this)); clock_converter.reset(new ClockConverter(this)); mapping_tracker.reset(new MappingTracker(this)); perf_sample_tracker.reset(new PerfSampleTracker(this)); stack_profile_tracker.reset(new StackProfileTracker(this)); metadata_tracker.reset(new MetadataTracker(storage.get())); + cpu_tracker.reset(new CpuTracker(this)); global_args_tracker.reset(new GlobalArgsTracker(storage.get())); { descriptor_pool_.reset(new DescriptorPool()); @@ -85,12 +95,20 @@ TraceProcessorContext::TraceProcessorContext(const InitArgs& args) kChromeTrackEventDescriptor.data(), kChromeTrackEventDescriptor.size()); PERFETTO_DCHECK(status.ok()); + + status = descriptor_pool_->AddFromFileDescriptorSet( + kAndroidTrackEventDescriptor.data(), + kAndroidTrackEventDescriptor.size()); + + PERFETTO_DCHECK(status.ok()); } slice_tracker->SetOnSliceBeginCallback( [this](TrackId track_id, SliceId slice_id) { flow_tracker->ClosePendingEventsOnTrack(track_id, slice_id); }); + + trace_file_tracker = std::make_unique(this); } TraceProcessorContext::TraceProcessorContext() = default; diff --git a/src/trace_processor/trace_processor_impl.cc b/src/trace_processor/trace_processor_impl.cc index 7bba0e9fe8..7973457342 100644 --- a/src/trace_processor/trace_processor_impl.cc +++ b/src/trace_processor/trace_processor_impl.cc @@ -43,9 +43,12 @@ #include "perfetto/trace_processor/iterator.h" #include "perfetto/trace_processor/trace_blob_view.h" #include "perfetto/trace_processor/trace_processor.h" -#include "src/trace_processor/importers/android_bugreport/android_bugreport_parser.h" +#include "src/trace_processor/importers/android_bugreport/android_log_event_parser_impl.h" +#include "src/trace_processor/importers/android_bugreport/android_log_reader.h" #include "src/trace_processor/importers/common/clock_tracker.h" #include "src/trace_processor/importers/common/metadata_tracker.h" +#include "src/trace_processor/importers/common/trace_file_tracker.h" +#include "src/trace_processor/importers/common/trace_parser.h" #include "src/trace_processor/importers/fuchsia/fuchsia_trace_parser.h" #include "src/trace_processor/importers/fuchsia/fuchsia_trace_tokenizer.h" #include "src/trace_processor/importers/gzip/gzip_trace_parser.h" @@ -53,11 +56,12 @@ #include "src/trace_processor/importers/json/json_trace_tokenizer.h" #include "src/trace_processor/importers/json/json_utils.h" #include "src/trace_processor/importers/ninja/ninja_log_parser.h" -#include "src/trace_processor/importers/perf/perf_data_parser.h" #include "src/trace_processor/importers/perf/perf_data_tokenizer.h" +#include "src/trace_processor/importers/perf/record_parser.h" #include "src/trace_processor/importers/proto/additional_modules.h" #include "src/trace_processor/importers/proto/content_analyzer.h" #include "src/trace_processor/importers/systrace/systrace_trace_parser.h" +#include "src/trace_processor/importers/zip/zip_trace_reader.h" #include "src/trace_processor/iterator_impl.h" #include "src/trace_processor/metrics/all_chrome_metrics.descriptor.h" #include "src/trace_processor/metrics/all_webview_metrics.descriptor.h" @@ -70,9 +74,11 @@ #include "src/trace_processor/perfetto_sql/intrinsics/functions/clock_functions.h" #include "src/trace_processor/perfetto_sql/intrinsics/functions/create_function.h" #include "src/trace_processor/perfetto_sql/intrinsics/functions/create_view_function.h" -#include "src/trace_processor/perfetto_sql/intrinsics/functions/dfs.h" #include "src/trace_processor/perfetto_sql/intrinsics/functions/dominator_tree.h" +#include "src/trace_processor/perfetto_sql/intrinsics/functions/graph_scan.h" +#include "src/trace_processor/perfetto_sql/intrinsics/functions/graph_traversal.h" #include "src/trace_processor/perfetto_sql/intrinsics/functions/import.h" +#include "src/trace_processor/perfetto_sql/intrinsics/functions/interval_intersect.h" #include "src/trace_processor/perfetto_sql/intrinsics/functions/layout_functions.h" #include "src/trace_processor/perfetto_sql/intrinsics/functions/math.h" #include "src/trace_processor/perfetto_sql/intrinsics/functions/pprof_functions.h" @@ -80,9 +86,11 @@ #include "src/trace_processor/perfetto_sql/intrinsics/functions/stack_functions.h" #include "src/trace_processor/perfetto_sql/intrinsics/functions/structural_tree_partition.h" #include "src/trace_processor/perfetto_sql/intrinsics/functions/to_ftrace.h" +#include "src/trace_processor/perfetto_sql/intrinsics/functions/type_builders.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/interval_intersect_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" @@ -96,7 +104,6 @@ #include "src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_flat_slice.h" #include "src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_sched_upid.h" #include "src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_slice_layout.h" -#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/interval_intersect.h" #include "src/trace_processor/perfetto_sql/intrinsics/table_functions/table_info.h" #include "src/trace_processor/perfetto_sql/prelude/tables_views.h" #include "src/trace_processor/perfetto_sql/stdlib/stdlib.h" @@ -110,6 +117,7 @@ #include "src/trace_processor/storage/trace_storage.h" #include "src/trace_processor/tp_metatrace.h" #include "src/trace_processor/trace_processor_storage_impl.h" +#include "src/trace_processor/trace_reader_registry.h" #include "src/trace_processor/types/trace_processor_context.h" #include "src/trace_processor/types/variadic.h" #include "src/trace_processor/util/descriptors.h" @@ -119,6 +127,7 @@ #include "src/trace_processor/util/regex.h" #include "src/trace_processor/util/sql_modules.h" #include "src/trace_processor/util/status_macros.h" +#include "src/trace_processor/util/trace_type.h" #include "protos/perfetto/common/builtin_clock.pbzero.h" #include "protos/perfetto/trace/clock_snapshot.pbzero.h" @@ -179,6 +188,8 @@ void BuildBoundsTable(sqlite3* db, std::pair bounds) { class ValueAtMaxTs : public SqliteAggregateFunction { public: + static constexpr char kName[] = "VALUE_AT_MAX_TS"; + static constexpr int kArgCount = 2; struct Context { bool initialized; int value_type; @@ -241,8 +252,7 @@ class ValueAtMaxTs : public SqliteAggregateFunction { } static void Final(sqlite3_context* ctx) { - Context* fn_ctx = - reinterpret_cast(sqlite3_aggregate_context(ctx, 0)); + auto* fn_ctx = static_cast(sqlite3_aggregate_context(ctx, 0)); if (!fn_ctx) { sqlite::result::Null(ctx); return; @@ -256,8 +266,8 @@ class ValueAtMaxTs : public SqliteAggregateFunction { }; void RegisterValueAtMaxTsFunction(PerfettoSqlEngine& engine) { - base::Status status = engine.RegisterSqliteAggregateFunction( - "VALUE_AT_MAX_TS", 2, nullptr); + base::Status status = + engine.RegisterSqliteAggregateFunction(nullptr); if (!status.ok()) { PERFETTO_ELOG("Error initializing VALUE_AT_MAX_TS"); } @@ -288,32 +298,6 @@ void InsertIntoTraceMetricsTable(sqlite3* db, const std::string& metric_name) { } } -const char* TraceTypeToString(TraceType trace_type) { - switch (trace_type) { - case kUnknownTraceType: - return "unknown"; - case kProtoTraceType: - return "proto"; - case kJsonTraceType: - return "json"; - case kFuchsiaTraceType: - return "fuchsia"; - case kSystraceTraceType: - return "systrace"; - case kGzipTraceType: - return "gzip"; - case kCtraceTraceType: - return "ctrace"; - case kNinjaLogTraceType: - return "ninja_log"; - case kAndroidBugreportTraceType: - return "android_bugreport"; - case kPerfDataTraceType: - return "perf_data"; - } - PERFETTO_FATAL("For GCC"); -} - sql_modules::NameToModule GetStdlibModules() { sql_modules::NameToModule modules; for (const auto& file_to_sql : stdlib::kFileToSql) { @@ -339,27 +323,38 @@ void InitializePreludeTablesViews(sqlite3* db) { TraceProcessorImpl::TraceProcessorImpl(const Config& cfg) : TraceProcessorStorageImpl(cfg), config_(cfg) { - context_.fuchsia_trace_tokenizer = - std::make_unique(&context_); + context_.reader_registry->RegisterTraceReader( + kAndroidLogcatTraceType); + context_.android_log_event_parser = + std::make_unique(&context_); + + context_.reader_registry->RegisterTraceReader( + kFuchsiaTraceType); context_.fuchsia_record_parser = std::make_unique(&context_); - context_.ninja_log_parser = std::make_unique(&context_); - context_.systrace_trace_parser = - std::make_unique(&context_); - context_.perf_data_trace_tokenizer = - std::make_unique(&context_); + + context_.reader_registry->RegisterTraceReader( + kSystraceTraceType); + context_.reader_registry->RegisterTraceReader( + kNinjaLogTraceType); + + context_.reader_registry + ->RegisterTraceReader( + kPerfDataTraceType); context_.perf_record_parser = - std::make_unique(&context_); + std::make_unique(&context_); if (util::IsGzipSupported()) { - context_.gzip_trace_parser = std::make_unique(&context_); - context_.android_bugreport_parser = - std::make_unique(&context_); + context_.reader_registry->RegisterTraceReader( + kGzipTraceType); + context_.reader_registry->RegisterTraceReader( + kCtraceTraceType); + context_.reader_registry->RegisterTraceReader(kZipFile); } if (json::IsJsonSupported()) { - context_.json_trace_tokenizer = - std::make_unique(&context_); + context_.reader_registry->RegisterTraceReader( + kJsonTraceType); context_.json_trace_parser = std::make_unique(&context_); } @@ -424,14 +419,6 @@ void TraceProcessorImpl::SetCurrentTraceName(const std::string& name) { void TraceProcessorImpl::Flush() { TraceProcessorStorageImpl::Flush(); - - context_.metadata_tracker->SetMetadata( - metadata::trace_size_bytes, - Variadic::Integer(static_cast(bytes_parsed_))); - const StringId trace_type_id = - context_.storage->InternString(TraceTypeToString(context_.trace_type)); - context_.metadata_tracker->SetMetadata(metadata::trace_type, - Variadic::String(trace_type_id)); BuildBoundsTable(engine_->sqlite_engine()->db(), context_.storage->GetTraceTimestampBoundsNs()); } @@ -708,32 +695,53 @@ void TraceProcessorImpl::InitPerfettoSqlEngine() { { base::Status status = RegisterLastNonNullFunction(*engine_); if (!status.ok()) - PERFETTO_ELOG("%s", status.c_message()); + PERFETTO_FATAL("%s", status.c_message()); } { base::Status status = RegisterStackFunctions(engine_.get(), &context_); if (!status.ok()) - PERFETTO_ELOG("%s", status.c_message()); + PERFETTO_FATAL("%s", status.c_message()); } { base::Status status = PprofFunctions::Register(*engine_, &context_); if (!status.ok()) - PERFETTO_ELOG("%s", status.c_message()); + PERFETTO_FATAL("%s", status.c_message()); } { base::Status status = RegisterLayoutFunctions(*engine_); if (!status.ok()) - PERFETTO_ELOG("%s", status.c_message()); + PERFETTO_FATAL("%s", status.c_message()); } { base::Status status = RegisterMathFunctions(*engine_); if (!status.ok()) - PERFETTO_ELOG("%s", status.c_message()); + PERFETTO_FATAL("%s", status.c_message()); } { base::Status status = RegisterBase64Functions(*engine_); if (!status.ok()) - PERFETTO_ELOG("%s", status.c_message()); + PERFETTO_FATAL("%s", status.c_message()); + } + { + base::Status status = RegisterTypeBuilderFunctions(*engine_); + if (!status.ok()) + PERFETTO_FATAL("%s", status.c_message()); + } + { + base::Status status = RegisterGraphScanFunctions( + *engine_, context_.storage->mutable_string_pool()); + if (!status.ok()) + PERFETTO_FATAL("%s", status.c_message()); + } + { + base::Status status = RegisterGraphTraversalFunctions( + *engine_, *context_.storage->mutable_string_pool()); + if (!status.ok()) + PERFETTO_FATAL("%s", status.c_message()); + } + { + base::Status status = perfetto_sql::RegisterIntervalIntersectFunctions( + *engine_, context_.storage->mutable_string_pool()); } TraceStorage* storage = context_.storage.get(); @@ -756,6 +764,10 @@ void TraceProcessorImpl::InitPerfettoSqlEngine() { engine_->sqlite_engine()->RegisterVirtualTableModule( "__intrinsic_slice_mipmap", std::make_unique(engine_.get())); + engine_->sqlite_engine() + ->RegisterVirtualTableModule( + "__intrinsic_ii_with_interval_tree", + std::make_unique(engine_.get())); // Initalize the tables and views in the prelude. InitializePreludeTablesViews(db); @@ -773,7 +785,7 @@ void TraceProcessorImpl::InitPerfettoSqlEngine() { { base::Status status = engine_->RegisterSqliteAggregateFunction( - "RepeatedField", 1, nullptr); + nullptr); if (!status.ok()) PERFETTO_ELOG("%s", status.c_message()); } @@ -798,109 +810,123 @@ void TraceProcessorImpl::InitPerfettoSqlEngine() { // Note: if adding a table here which might potentially contain many rows // (O(rows in sched/slice/counter)), then consider calling ShrinkToFit on // that table in TraceStorage::ShrinkToFitTables. - RegisterStaticTable(storage->arg_table()); - RegisterStaticTable(storage->raw_table()); - RegisterStaticTable(storage->ftrace_event_table()); - RegisterStaticTable(storage->thread_table()); - RegisterStaticTable(storage->process_table()); - RegisterStaticTable(storage->filedescriptor_table()); - - RegisterStaticTable(storage->slice_table()); - RegisterStaticTable(storage->flow_table()); - RegisterStaticTable(storage->sched_slice_table()); - RegisterStaticTable(storage->spurious_sched_wakeup_table()); - RegisterStaticTable(storage->thread_state_table()); - RegisterStaticTable(storage->gpu_slice_table()); - - RegisterStaticTable(storage->track_table()); - RegisterStaticTable(storage->thread_track_table()); - RegisterStaticTable(storage->process_track_table()); - RegisterStaticTable(storage->cpu_track_table()); - RegisterStaticTable(storage->gpu_track_table()); - RegisterStaticTable(storage->uid_track_table()); - RegisterStaticTable(storage->gpu_work_period_track_table()); - - RegisterStaticTable(storage->counter_table()); - - RegisterStaticTable(storage->counter_track_table()); - RegisterStaticTable(storage->process_counter_track_table()); - RegisterStaticTable(storage->thread_counter_track_table()); - RegisterStaticTable(storage->cpu_counter_track_table()); - RegisterStaticTable(storage->irq_counter_track_table()); - RegisterStaticTable(storage->softirq_counter_track_table()); - RegisterStaticTable(storage->gpu_counter_track_table()); - RegisterStaticTable(storage->gpu_counter_group_table()); - RegisterStaticTable(storage->perf_counter_track_table()); - RegisterStaticTable(storage->energy_counter_track_table()); - RegisterStaticTable(storage->linux_device_track_table()); - RegisterStaticTable(storage->uid_counter_track_table()); - RegisterStaticTable(storage->energy_per_uid_counter_track_table()); - - RegisterStaticTable(storage->heap_graph_object_table()); - RegisterStaticTable(storage->heap_graph_reference_table()); - RegisterStaticTable(storage->heap_graph_class_table()); - - RegisterStaticTable(storage->symbol_table()); - RegisterStaticTable(storage->heap_profile_allocation_table()); - RegisterStaticTable(storage->cpu_profile_stack_sample_table()); - RegisterStaticTable(storage->perf_sample_table()); - RegisterStaticTable(storage->stack_profile_callsite_table()); - RegisterStaticTable(storage->stack_profile_mapping_table()); - RegisterStaticTable(storage->stack_profile_frame_table()); - RegisterStaticTable(storage->package_list_table()); - RegisterStaticTable(storage->profiler_smaps_table()); - - RegisterStaticTable(storage->android_log_table()); - RegisterStaticTable(storage->android_dumpstate_table()); - RegisterStaticTable(storage->android_game_intervention_list_table()); - - RegisterStaticTable(storage->vulkan_memory_allocations_table()); - - RegisterStaticTable(storage->graphics_frame_slice_table()); - - RegisterStaticTable(storage->expected_frame_timeline_slice_table()); - RegisterStaticTable(storage->actual_frame_timeline_slice_table()); - - RegisterStaticTable(storage->v8_isolate_table()); - RegisterStaticTable(storage->v8_js_script_table()); - RegisterStaticTable(storage->v8_wasm_script_table()); - RegisterStaticTable(storage->v8_js_function_table()); - RegisterStaticTable(storage->v8_js_code_table()); - RegisterStaticTable(storage->v8_internal_code_table()); - RegisterStaticTable(storage->v8_wasm_code_table()); - RegisterStaticTable(storage->v8_regexp_code_table()); - - RegisterStaticTable(storage->jit_code_table()); - RegisterStaticTable(storage->jit_frame_table()); - - RegisterStaticTable(storage->inputmethod_clients_table()); - RegisterStaticTable(storage->inputmethod_manager_service_table()); - RegisterStaticTable(storage->inputmethod_service_table()); - - RegisterStaticTable(storage->surfaceflinger_layers_snapshot_table()); - RegisterStaticTable(storage->surfaceflinger_layer_table()); - RegisterStaticTable(storage->surfaceflinger_transactions_table()); - - RegisterStaticTable(storage->window_manager_shell_transitions_table()); + RegisterStaticTable(storage->mutable_machine_table()); + RegisterStaticTable(storage->mutable_arg_table()); + RegisterStaticTable(storage->mutable_raw_table()); + RegisterStaticTable(storage->mutable_ftrace_event_table()); + RegisterStaticTable(storage->mutable_thread_table()); + RegisterStaticTable(storage->mutable_process_table()); + RegisterStaticTable(storage->mutable_filedescriptor_table()); + RegisterStaticTable(storage->mutable_trace_file_table()); + + RegisterStaticTable(storage->mutable_slice_table()); + RegisterStaticTable(storage->mutable_flow_table()); + RegisterStaticTable(storage->mutable_sched_slice_table()); + RegisterStaticTable(storage->mutable_spurious_sched_wakeup_table()); + RegisterStaticTable(storage->mutable_thread_state_table()); + RegisterStaticTable(storage->mutable_gpu_slice_table()); + + RegisterStaticTable(storage->mutable_track_table()); + RegisterStaticTable(storage->mutable_thread_track_table()); + RegisterStaticTable(storage->mutable_process_track_table()); + RegisterStaticTable(storage->mutable_cpu_track_table()); + RegisterStaticTable(storage->mutable_gpu_track_table()); + RegisterStaticTable(storage->mutable_uid_track_table()); + RegisterStaticTable(storage->mutable_gpu_work_period_track_table()); + + RegisterStaticTable(storage->mutable_counter_table()); + + RegisterStaticTable(storage->mutable_counter_track_table()); + RegisterStaticTable(storage->mutable_process_counter_track_table()); + RegisterStaticTable(storage->mutable_thread_counter_track_table()); + RegisterStaticTable(storage->mutable_cpu_counter_track_table()); + RegisterStaticTable(storage->mutable_irq_counter_track_table()); + RegisterStaticTable(storage->mutable_softirq_counter_track_table()); + RegisterStaticTable(storage->mutable_gpu_counter_track_table()); + RegisterStaticTable(storage->mutable_gpu_counter_group_table()); + RegisterStaticTable(storage->mutable_perf_counter_track_table()); + RegisterStaticTable(storage->mutable_energy_counter_track_table()); + RegisterStaticTable(storage->mutable_linux_device_track_table()); + RegisterStaticTable(storage->mutable_uid_counter_track_table()); + RegisterStaticTable(storage->mutable_energy_per_uid_counter_track_table()); + + RegisterStaticTable(storage->mutable_heap_graph_object_table()); + RegisterStaticTable(storage->mutable_heap_graph_reference_table()); + RegisterStaticTable(storage->mutable_heap_graph_class_table()); + + RegisterStaticTable(storage->mutable_symbol_table()); + RegisterStaticTable(storage->mutable_heap_profile_allocation_table()); + RegisterStaticTable(storage->mutable_cpu_profile_stack_sample_table()); + RegisterStaticTable(storage->mutable_perf_session_table()); + RegisterStaticTable(storage->mutable_perf_sample_table()); + RegisterStaticTable(storage->mutable_stack_profile_callsite_table()); + RegisterStaticTable(storage->mutable_stack_profile_mapping_table()); + RegisterStaticTable(storage->mutable_stack_profile_frame_table()); + RegisterStaticTable(storage->mutable_package_list_table()); + RegisterStaticTable(storage->mutable_profiler_smaps_table()); + + RegisterStaticTable(storage->mutable_android_log_table()); + RegisterStaticTable(storage->mutable_android_dumpstate_table()); + RegisterStaticTable(storage->mutable_android_game_intervenion_list_table()); + RegisterStaticTable(storage->mutable_android_key_events_table()); + RegisterStaticTable(storage->mutable_android_motion_events_table()); + RegisterStaticTable(storage->mutable_android_input_event_dispatch_table()); + + RegisterStaticTable(storage->mutable_vulkan_memory_allocations_table()); + + RegisterStaticTable(storage->mutable_graphics_frame_slice_table()); + + RegisterStaticTable(storage->mutable_expected_frame_timeline_slice_table()); + RegisterStaticTable(storage->mutable_actual_frame_timeline_slice_table()); + + RegisterStaticTable(storage->mutable_android_network_packets_table()); + + RegisterStaticTable(storage->mutable_v8_isolate_table()); + RegisterStaticTable(storage->mutable_v8_js_script_table()); + RegisterStaticTable(storage->mutable_v8_wasm_script_table()); + RegisterStaticTable(storage->mutable_v8_js_function_table()); + RegisterStaticTable(storage->mutable_v8_js_code_table()); + RegisterStaticTable(storage->mutable_v8_internal_code_table()); + RegisterStaticTable(storage->mutable_v8_wasm_code_table()); + RegisterStaticTable(storage->mutable_v8_regexp_code_table()); + + RegisterStaticTable(storage->mutable_jit_code_table()); + RegisterStaticTable(storage->mutable_jit_frame_table()); + + RegisterStaticTable(storage->mutable_inputmethod_clients_table()); + RegisterStaticTable(storage->mutable_inputmethod_manager_service_table()); + RegisterStaticTable(storage->mutable_inputmethod_service_table()); + + RegisterStaticTable(storage->mutable_surfaceflinger_layers_snapshot_table()); + RegisterStaticTable(storage->mutable_surfaceflinger_layer_table()); + RegisterStaticTable(storage->mutable_surfaceflinger_transactions_table()); + + RegisterStaticTable(storage->mutable_viewcapture_table()); + + RegisterStaticTable(storage->mutable_windowmanager_table()); + RegisterStaticTable( - storage->window_manager_shell_transition_handlers_table()); + storage->mutable_window_manager_shell_transitions_table()); + RegisterStaticTable( + storage->mutable_window_manager_shell_transition_handlers_table()); - RegisterStaticTable(storage->protolog_table()); + RegisterStaticTable(storage->mutable_protolog_table()); - RegisterStaticTable(storage->metadata_table()); - RegisterStaticTable(storage->cpu_table()); - RegisterStaticTable(storage->cpu_freq_table()); - RegisterStaticTable(storage->clock_snapshot_table()); + RegisterStaticTable(storage->mutable_metadata_table()); + RegisterStaticTable(storage->mutable_cpu_table()); + RegisterStaticTable(storage->mutable_cpu_freq_table()); + RegisterStaticTable(storage->mutable_clock_snapshot_table()); - RegisterStaticTable(storage->memory_snapshot_table()); - RegisterStaticTable(storage->process_memory_snapshot_table()); - RegisterStaticTable(storage->memory_snapshot_node_table()); - RegisterStaticTable(storage->memory_snapshot_edge_table()); + RegisterStaticTable(storage->mutable_memory_snapshot_table()); + RegisterStaticTable(storage->mutable_process_memory_snapshot_table()); + RegisterStaticTable(storage->mutable_memory_snapshot_node_table()); + RegisterStaticTable(storage->mutable_memory_snapshot_edge_table()); - RegisterStaticTable(storage->experimental_proto_path_table()); - RegisterStaticTable(storage->experimental_proto_content_table()); + RegisterStaticTable(storage->mutable_experimental_proto_path_table()); + RegisterStaticTable(storage->mutable_experimental_proto_content_table()); - RegisterStaticTable(storage->experimental_missing_chrome_processes_table()); + RegisterStaticTable( + storage->mutable_experimental_missing_chrome_processes_table()); // Tables dynamically generated at query time. engine_->RegisterStaticTableFunction( @@ -934,19 +960,13 @@ void TraceProcessorImpl::InitPerfettoSqlEngine() { std::make_unique(&context_)); engine_->RegisterStaticTableFunction( std::make_unique(&context_)); - engine_->RegisterStaticTableFunction(std::make_unique( - context_.storage->mutable_string_pool())); engine_->RegisterStaticTableFunction(std::make_unique( context_.storage->mutable_string_pool())); // Value table aggregate functions. - engine_->RegisterSqliteAggregateFunction( - Dfs::kName, Dfs::kArgCount, context_.storage->mutable_string_pool()); engine_->RegisterSqliteAggregateFunction( - DominatorTree::kName, DominatorTree::kArgCount, context_.storage->mutable_string_pool()); engine_->RegisterSqliteAggregateFunction( - StructuralTreePartition::kName, StructuralTreePartition::kArgCount, context_.storage->mutable_string_pool()); // Metrics. diff --git a/src/trace_processor/trace_processor_impl.h b/src/trace_processor/trace_processor_impl.h index 249ceb9bb8..74fc96105b 100644 --- a/src/trace_processor/trace_processor_impl.h +++ b/src/trace_processor/trace_processor_impl.h @@ -102,7 +102,7 @@ class TraceProcessorImpl : public TraceProcessor, friend class IteratorImpl; template - void RegisterStaticTable(const Table& table) { + void RegisterStaticTable(Table* table) { engine_->RegisterStaticTable(table, Table::Name(), Table::ComputeStaticSchema()); } diff --git a/src/trace_processor/trace_processor_shell.cc b/src/trace_processor/trace_processor_shell.cc index faabf219be..90e45e1a00 100644 --- a/src/trace_processor/trace_processor_shell.cc +++ b/src/trace_processor/trace_processor_shell.cc @@ -273,9 +273,7 @@ base::Status ExportTraceToDatabase(const std::string& output_name) { return base::ErrStatus("%s", status.c_message()); // Export real and virtual tables. - auto tables_it = g_tp->ExecuteQuery( - "SELECT name FROM perfetto_tables UNION " - "SELECT name FROM sqlite_master WHERE type='table'"); + auto tables_it = g_tp->ExecuteQuery("SELECT name FROM perfetto_tables"); while (tables_it.Next()) { std::string table_name = tables_it.Get(0).string_value; PERFETTO_CHECK(!base::Contains(table_name, '\'')); @@ -666,6 +664,7 @@ metatrace::MetatraceCategories ParseMetatraceCategories(std::string s) { struct CommandLineOptions { std::string perf_file_path; std::string query_file_path; + std::string query_string; std::string pre_metrics_path; std::string sqlite_file_path; std::string sql_module_path; @@ -714,6 +713,10 @@ Usage: %s [FLAGS] trace_file.pb If used with --run-metrics, the query is executed after the selected metrics and the metrics output is suppressed. + -Q, --query-string QUERY Execute the SQL query QUERY. + If used with --run-metrics, the query is + executed after the selected metrics and + the metrics output is suppressed. -D, --httpd Enables the HTTP RPC server. --http-port PORT Specify what port to run HTTP RPC server. --stdiod Enables the stdio RPC server. @@ -816,6 +819,7 @@ CommandLineOptions ParseCommandLineOptions(int argc, char** argv) { {"wide", no_argument, nullptr, 'W'}, {"perf-file", required_argument, nullptr, 'p'}, {"query-file", required_argument, nullptr, 'q'}, + {"query-string", required_argument, nullptr, 'Q'}, {"httpd", no_argument, nullptr, 'D'}, {"http-port", required_argument, nullptr, OPT_HTTP_PORT}, {"stdiod", no_argument, nullptr, OPT_STDIOD}, @@ -846,7 +850,7 @@ CommandLineOptions ParseCommandLineOptions(int argc, char** argv) { bool explicit_interactive = false; for (;;) { int option = - getopt_long(argc, argv, "hvWiDdm:p:q:e:", long_options, nullptr); + getopt_long(argc, argv, "hvWiDdm:p:q:Q:e:", long_options, nullptr); if (option == -1) break; // EOF. @@ -873,6 +877,11 @@ CommandLineOptions ParseCommandLineOptions(int argc, char** argv) { continue; } + if (option == 'Q') { + command_line_options.query_string = optarg; + continue; + } + if (option == 'D') { #if PERFETTO_BUILDFLAG(PERFETTO_TP_HTTPD) command_line_options.enable_httpd = true; @@ -992,6 +1001,7 @@ CommandLineOptions ParseCommandLineOptions(int argc, char** argv) { explicit_interactive || (command_line_options.pre_metrics_path.empty() && command_line_options.metric_names.empty() && command_line_options.query_file_path.empty() && + command_line_options.query_string.empty() && command_line_options.sqlite_file_path.empty()); // Only allow non-interactive queries to emit perf data. @@ -1078,13 +1088,7 @@ base::Status LoadTrace(const std::string& trace_file_path, double* size_mb) { return base::OkStatus(); } -base::Status RunQueries(const std::string& query_file_path, - bool expect_output) { - std::string queries; - if (!base::ReadFile(query_file_path.c_str(), &queries)) { - return base::ErrStatus("Unable to read file %s", query_file_path.c_str()); - } - +base::Status RunQueries(const std::string& queries, bool expect_output) { base::Status status; if (expect_output) { status = RunQueriesAndPrintResult(queries, stdout); @@ -1097,6 +1101,16 @@ base::Status RunQueries(const std::string& query_file_path, return base::OkStatus(); } +base::Status RunQueriesFromFile(const std::string& query_file_path, + bool expect_output) { + std::string queries; + if (!base::ReadFile(query_file_path.c_str(), &queries)) { + return base::ErrStatus("Unable to read file %s", query_file_path.c_str()); + } + + return RunQueries(queries, expect_output); +} + base::Status ParseSingleMetricExtensionPath(bool dev, const std::string& raw_extension, MetricExtension& parsed_extension) { @@ -1460,7 +1474,7 @@ base::Status StartInteractiveShell(const InteractiveOptions& options) { } else if (strcmp(command, "reset") == 0) { g_tp->RestoreInitialTables(); } else if (strcmp(command, "read") == 0 && strlen(arg)) { - base::Status status = RunQueries(arg, true); + base::Status status = RunQueriesFromFile(arg, true); if (!status.ok()) { PERFETTO_ELOG("%s", status.c_message()); } @@ -1639,7 +1653,7 @@ base::Status TraceProcessorMain(int argc, char** argv) { base::TimeNanos t_query_start = base::GetWallTimeNs(); if (!options.pre_metrics_path.empty()) { - RETURN_IF_ERROR(RunQueries(options.pre_metrics_path, false)); + RETURN_IF_ERROR(RunQueriesFromFile(options.pre_metrics_path, false)); } std::vector metrics; @@ -1653,13 +1667,23 @@ base::Status TraceProcessorMain(int argc, char** argv) { } if (!options.query_file_path.empty()) { - base::Status status = RunQueries(options.query_file_path, true); + base::Status status = RunQueriesFromFile(options.query_file_path, true); if (!status.ok()) { // Write metatrace if needed before exiting. RETURN_IF_ERROR(MaybeWriteMetatrace(options.metatrace_path)); return status; } } + + if (!options.query_string.empty()) { + base::Status status = RunQueries(options.query_string, true); + if (!status.ok()) { + // Write metatrace if needed before exiting. + RETURN_IF_ERROR(MaybeWriteMetatrace(options.metatrace_path)); + return status; + } + } + base::TimeNanos t_query = base::GetWallTimeNs() - t_query_start; if (!options.sqlite_file_path.empty()) { diff --git a/src/trace_processor/trace_processor_storage_impl.cc b/src/trace_processor/trace_processor_storage_impl.cc index 3a686e889c..90db87f246 100644 --- a/src/trace_processor/trace_processor_storage_impl.cc +++ b/src/trace_processor/trace_processor_storage_impl.cc @@ -16,6 +16,7 @@ #include "src/trace_processor/trace_processor_storage_impl.h" #include +#include #include "perfetto/base/logging.h" #include "perfetto/ext/base/uuid.h" @@ -30,12 +31,15 @@ #include "src/trace_processor/importers/common/machine_tracker.h" #include "src/trace_processor/importers/common/mapping_tracker.h" #include "src/trace_processor/importers/common/metadata_tracker.h" +#include "src/trace_processor/importers/common/process_track_translation_table.h" #include "src/trace_processor/importers/common/process_tracker.h" #include "src/trace_processor/importers/common/sched_event_tracker.h" #include "src/trace_processor/importers/common/slice_tracker.h" #include "src/trace_processor/importers/common/slice_translation_table.h" #include "src/trace_processor/importers/common/stack_profile_tracker.h" +#include "src/trace_processor/importers/common/trace_file_tracker.h" #include "src/trace_processor/importers/common/track_tracker.h" +#include "src/trace_processor/importers/perf/dso_tracker.h" #include "src/trace_processor/importers/proto/chrome_track_event.descriptor.h" #include "src/trace_processor/importers/proto/default_modules.h" #include "src/trace_processor/importers/proto/packet_analyzer.h" @@ -45,13 +49,19 @@ #include "src/trace_processor/importers/proto/proto_trace_reader.h" #include "src/trace_processor/importers/proto/track_event.descriptor.h" #include "src/trace_processor/sorter/trace_sorter.h" +#include "src/trace_processor/trace_reader_registry.h" #include "src/trace_processor/util/descriptors.h" +#include "src/trace_processor/util/trace_type.h" namespace perfetto { namespace trace_processor { TraceProcessorStorageImpl::TraceProcessorStorageImpl(const Config& cfg) : context_({cfg, std::make_shared(cfg)}) { + context_.reader_registry->RegisterTraceReader( + kProtoTraceType); + context_.reader_registry->RegisterTraceReader( + kSymbolsTraceType); context_.proto_trace_parser = std::make_unique(&context_); RegisterDefaultModules(&context_); @@ -65,8 +75,12 @@ util::Status TraceProcessorStorageImpl::Parse(TraceBlobView blob) { if (unrecoverable_parse_error_) return util::ErrStatus( "Failed unrecoverably while parsing in a previous Parse call"); - if (!context_.chunk_reader) - context_.chunk_reader.reset(new ForwardingTraceParser(&context_)); + if (!parser_) { + active_file_ = context_.trace_file_tracker->StartNewFile(); + auto parser = std::make_unique(&context_); + parser_ = parser.get(); + context_.chunk_readers.push_back(std::move(parser)); + } auto scoped_trace = context_.storage->TraceExecutionTimeIntoStats( stats::parse_trace_duration_ns); @@ -83,7 +97,8 @@ util::Status TraceProcessorStorageImpl::Parse(TraceBlobView blob) { Variadic::String(id_for_uuid)); } - util::Status status = context_.chunk_reader->Parse(std::move(blob)); + active_file_->AddSize(blob.size()); + util::Status status = parser_->Parse(std::move(blob)); unrecoverable_parse_error_ |= !status.ok(); return status; } @@ -98,20 +113,30 @@ void TraceProcessorStorageImpl::Flush() { } void TraceProcessorStorageImpl::NotifyEndOfFile() { - if (unrecoverable_parse_error_ || !context_.chunk_reader) + if (unrecoverable_parse_error_ || !parser_) return; Flush(); - context_.chunk_reader->NotifyEndOfFile(); + parser_->NotifyEndOfFile(); + PERFETTO_CHECK(active_file_.has_value()); + active_file_->SetTraceType(parser_->trace_type()); + active_file_.reset(); + + // NotifyEndOfFile might have pushed packets to the sorter. + Flush(); for (std::unique_ptr& module : context_.modules) { module->NotifyEndOfFile(); } if (context_.content_analyzer) { PacketAnalyzer::Get(&context_)->NotifyEndOfFile(); } + context_.event_tracker->FlushPendingEvents(); context_.slice_tracker->FlushPendingSlices(); context_.args_tracker->Flush(); context_.process_tracker->NotifyEndOfFile(); + if (context_.perf_dso_tracker) { + perf_importer::DsoTracker::GetOrCreate(&context_).SymbolizeFrames(); + } } void TraceProcessorStorageImpl::DestroyContext() { diff --git a/src/trace_processor/trace_processor_storage_impl.h b/src/trace_processor/trace_processor_storage_impl.h index 83d9400cfe..4fd4f1f290 100644 --- a/src/trace_processor/trace_processor_storage_impl.h +++ b/src/trace_processor/trace_processor_storage_impl.h @@ -18,16 +18,20 @@ #define SRC_TRACE_PROCESSOR_TRACE_PROCESSOR_STORAGE_IMPL_H_ #include +#include #include "perfetto/ext/base/hash.h" #include "perfetto/trace_processor/basic_types.h" #include "perfetto/trace_processor/status.h" #include "perfetto/trace_processor/trace_processor_storage.h" +#include "src/trace_processor/importers/common/trace_file_tracker.h" #include "src/trace_processor/types/trace_processor_context.h" namespace perfetto { namespace trace_processor { +class ForwardingTraceParser; + class TraceProcessorStorageImpl : public TraceProcessorStorage { public: explicit TraceProcessorStorageImpl(const Config&); @@ -46,6 +50,8 @@ class TraceProcessorStorageImpl : public TraceProcessorStorage { TraceProcessorContext context_; bool unrecoverable_parse_error_ = false; size_t hash_input_size_remaining_ = 4096; + ForwardingTraceParser* parser_ = nullptr; + std::optional active_file_; }; } // namespace trace_processor diff --git a/src/trace_processor/trace_reader_registry.cc b/src/trace_processor/trace_reader_registry.cc new file mode 100644 index 0000000000..b0712957e2 --- /dev/null +++ b/src/trace_processor/trace_reader_registry.cc @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "src/trace_processor/trace_reader_registry.h" +#include "perfetto/base/logging.h" +#include "src/trace_processor/importers/common/chunked_trace_reader.h" +#include "src/trace_processor/types/trace_processor_context.h" +#include "src/trace_processor/util/gzip_utils.h" +#include "src/trace_processor/util/trace_type.h" + +namespace perfetto::trace_processor { +namespace { +const char kNoZlibErr[] = + "Cannot open compressed trace. zlib not enabled in the build config"; + +bool RequiresZlibSupport(TraceType type) { + switch (type) { + case kGzipTraceType: + case kAndroidBugreportTraceType: + case kCtraceTraceType: + case kZipFile: + return true; + + case kNinjaLogTraceType: + case kSystraceTraceType: + case kPerfDataTraceType: + case kUnknownTraceType: + case kJsonTraceType: + case kFuchsiaTraceType: + case kProtoTraceType: + case kSymbolsTraceType: + case kAndroidLogcatTraceType: + case kAndroidDumpstateTraceType: + return false; + } + PERFETTO_FATAL("For GCC"); +} +} // namespace + +void TraceReaderRegistry::RegisterFactory(TraceType trace_type, + Factory factory) { + PERFETTO_CHECK(factories_.Insert(trace_type, std::move(factory)).second); +} + +base::StatusOr> +TraceReaderRegistry::CreateTraceReader(TraceType type) { + if (auto it = factories_.Find(type); it) { + return (*it)(context_); + } + + if (RequiresZlibSupport(type) && !util::IsGzipSupported()) { + return base::ErrStatus("%s support is disabled. %s", + TraceTypeToString(type), kNoZlibErr); + } + + return base::ErrStatus("%s support is disabled", TraceTypeToString(type)); +} + +} // namespace perfetto::trace_processor diff --git a/src/trace_processor/trace_reader_registry.h b/src/trace_processor/trace_reader_registry.h new file mode 100644 index 0000000000..8e9b0397a1 --- /dev/null +++ b/src/trace_processor/trace_reader_registry.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SRC_TRACE_PROCESSOR_TRACE_READER_REGISTRY_H_ +#define SRC_TRACE_PROCESSOR_TRACE_READER_REGISTRY_H_ + +#include +#include +#include + +#include "perfetto/ext/base/flat_hash_map.h" + +#include "perfetto/ext/base/status_or.h" +#include "src/trace_processor/importers/common/trace_parser.h" +#include "src/trace_processor/sorter/trace_sorter.h" +#include "src/trace_processor/util/trace_type.h" + +namespace perfetto { +namespace trace_processor { + +class ChunkedTraceReader; +class TraceProcessorContext; + +// Maps `TraceType` values to `ChunkedTraceReader` subclasses. +// This class is used to create `ChunkedTraceReader` instances for a given +// `TraceType`. +class TraceReaderRegistry { + public: + explicit TraceReaderRegistry(TraceProcessorContext* context) + : context_(context) {} + + // Registers a mapping from `TraceType` value to `ChunkedTraceReader` + // subclass. Only one such mapping can be registered per `TraceType` value. + template + void RegisterTraceReader(TraceType trace_type) { + RegisterFactory(trace_type, [](TraceProcessorContext* ctxt) { + return std::make_unique(ctxt); + }); + } + + // Creates a new `ChunkedTraceReader` instance for the given `type`. Returns + // an error if no mapping has been previously registered. + base::StatusOr> CreateTraceReader( + TraceType type); + + private: + using Factory = std::function( + TraceProcessorContext*)>; + void RegisterFactory(TraceType trace_type, Factory factory); + + TraceProcessorContext* const context_; + base::FlatHashMap( + TraceProcessorContext*)>> + factories_; +}; + +} // namespace trace_processor +} // namespace perfetto + +#endif // SRC_TRACE_PROCESSOR_TRACE_READER_REGISTRY_H_ diff --git a/src/trace_processor/types/BUILD.gn b/src/trace_processor/types/BUILD.gn index 6b66c53861..9fbff7a779 100644 --- a/src/trace_processor/types/BUILD.gn +++ b/src/trace_processor/types/BUILD.gn @@ -32,6 +32,7 @@ source_set("types") { "../../../include/perfetto/trace_processor", "../containers", "../tables:tables_python", + "../util:trace_type", ] } diff --git a/src/trace_processor/types/trace_processor_context.h b/src/trace_processor/types/trace_processor_context.h index 5a91d78b40..909959c3c6 100644 --- a/src/trace_processor/types/trace_processor_context.h +++ b/src/trace_processor/types/trace_processor_context.h @@ -18,35 +18,25 @@ #define SRC_TRACE_PROCESSOR_TYPES_TRACE_PROCESSOR_CONTEXT_H_ #include +#include #include #include "perfetto/trace_processor/basic_types.h" #include "src/trace_processor/tables/metadata_tables_py.h" #include "src/trace_processor/types/destructible.h" +#include "src/trace_processor/util/trace_type.h" namespace perfetto { namespace trace_processor { -enum TraceType { - kUnknownTraceType, - kProtoTraceType, - kJsonTraceType, - kFuchsiaTraceType, - kSystraceTraceType, - kGzipTraceType, - kCtraceTraceType, - kNinjaLogTraceType, - kAndroidBugreportTraceType, - kPerfDataTraceType, -}; - -class AndroidProbesTracker; +class AndroidLogEventParser; class ArgsTracker; class ArgsTranslationTable; class AsyncTrackSetTracker; class ChunkedTraceReader; class ClockConverter; class ClockTracker; +class CpuTracker; class DeobfuscationMappingTable; class DescriptorPool; class EtwModule; @@ -66,12 +56,15 @@ class PacketAnalyzer; class PerfRecordParser; class PerfSampleTracker; class ProcessTracker; +class ProcessTrackTranslationTable; class ProtoImporterModule; class ProtoTraceParser; class SchedEventTracker; class SliceTracker; class SliceTranslationTable; class StackProfileTracker; +class TraceFileTracker; +class TraceReaderRegistry; class TraceSorter; class TraceStorage; class TrackEventModule; @@ -99,7 +92,12 @@ class TraceProcessorContext { // |storage| is shared among multiple contexts in multi-machine tracing. std::shared_ptr storage; - std::unique_ptr chunk_reader; + std::unique_ptr reader_registry; + + // We might create multiple `ChunkedTraceReader` instances (e.g. one for each + // file in a ZIP ). The instances are kept around here as some tokenizers + // might keep state that is later needed after sorting. + std::vector> chunk_readers; // The sorter is used to sort trace data by timestamp and is shared among // multiple machines. @@ -118,6 +116,7 @@ class TraceProcessorContext { std::unique_ptr slice_translation_table; std::unique_ptr flow_tracker; std::unique_ptr process_tracker; + std::unique_ptr process_track_translation_table; std::unique_ptr event_tracker; std::unique_ptr sched_event_tracker; std::unique_ptr clock_tracker; @@ -127,6 +126,8 @@ class TraceProcessorContext { std::unique_ptr perf_sample_tracker; std::unique_ptr stack_profile_tracker; std::unique_ptr metadata_tracker; + std::unique_ptr cpu_tracker; + std::unique_ptr trace_file_tracker; // These fields are stored as pointers to Destructible objects rather than // their actual type (a subclass of Destructible), as the concrete subclass @@ -151,19 +152,10 @@ class TraceProcessorContext { std::unique_ptr ftrace_sched_tracker; // FtraceSchedEventTracker std::unique_ptr v8_tracker; // V8Tracker std::unique_ptr jit_tracker; // JitTracker + std::unique_ptr perf_dso_tracker; // DsoTracker + std::unique_ptr protolog_message_decoder; // ProtoLogMessageDecoder // clang-format on - // These fields are trace readers which will be called by |forwarding_parser| - // once the format of the trace is discovered. They are placed here as they - // are only available in the lib target. - std::unique_ptr json_trace_tokenizer; - std::unique_ptr fuchsia_trace_tokenizer; - std::unique_ptr ninja_log_parser; - std::unique_ptr android_bugreport_parser; - std::unique_ptr systrace_trace_parser; - std::unique_ptr gzip_trace_parser; - std::unique_ptr perf_data_trace_tokenizer; - std::unique_ptr proto_trace_parser; // These fields are trace parsers which will be called by |forwarding_parser| @@ -172,6 +164,7 @@ class TraceProcessorContext { std::unique_ptr json_trace_parser; std::unique_ptr fuchsia_record_parser; std::unique_ptr perf_record_parser; + std::unique_ptr android_log_event_parser; // This field contains the list of proto descriptors that can be used by // reflection-based parsers. @@ -193,8 +186,6 @@ class TraceProcessorContext { // 4KB of the trace. bool uuid_found_in_trace = false; - TraceType trace_type = kUnknownTraceType; - std::optional machine_id() const; // Manages the contexts for reading trace data emitted from remote machines. diff --git a/src/trace_processor/util/BUILD.gn b/src/trace_processor/util/BUILD.gn index 5ca39a1d0b..b857bd03fd 100644 --- a/src/trace_processor/util/BUILD.gn +++ b/src/trace_processor/util/BUILD.gn @@ -177,7 +177,10 @@ source_set("zip_reader") { ] deps = [ ":gzip", + ":trace_blob_view_reader", + ":util", "../../../gn:default_deps", + "../../../include/perfetto/trace_processor:storage", "../../base", ] if (enable_perfetto_zlib) { @@ -254,10 +257,10 @@ source_set("profile_builder") { ] } -source_set("file_buffer") { +source_set("trace_blob_view_reader") { sources = [ - "file_buffer.cc", - "file_buffer.h", + "trace_blob_view_reader.cc", + "trace_blob_view_reader.h", ] deps = [ "../../../gn:default_deps", @@ -266,11 +269,23 @@ source_set("file_buffer") { ] } +source_set("trace_type") { + sources = [ + "trace_type.cc", + "trace_type.h", + ] + deps = [ + "../../../gn:default_deps", + "../../../include/perfetto/ext/base", + "../../../protos/perfetto/trace:non_minimal_zero", + "../importers/android_bugreport:android_log_event", + ] +} + source_set("unittests") { sources = [ "bump_allocator_unittest.cc", "debug_annotation_parser_unittest.cc", - "file_buffer_unittest.cc", "glob_unittest.cc", "proto_profiler_unittest.cc", "proto_to_args_parser_unittest.cc", @@ -278,13 +293,13 @@ source_set("unittests") { "protozero_to_text_unittests.cc", "sql_argument_unittest.cc", "streaming_line_reader_unittest.cc", + "trace_blob_view_reader_unittest.cc", "zip_reader_unittest.cc", ] testonly = true deps = [ ":bump_allocator", ":descriptors", - ":file_buffer", ":glob", ":gzip", ":proto_profiler", @@ -292,6 +307,7 @@ source_set("unittests") { ":protozero_to_json", ":protozero_to_text", ":sql_argument", + ":trace_blob_view_reader", ":zip_reader", "..:gen_cc_test_messages_descriptor", "../../../gn:default_deps", @@ -301,6 +317,7 @@ source_set("unittests") { "../../../protos/perfetto/trace/interned_data:zero", "../../../protos/perfetto/trace/profiling:zero", "../../../protos/perfetto/trace/track_event:zero", + "../../base:test_support", "../../protozero", "../../protozero:testing_messages_zero", "../importers/proto:gen_cc_track_event_descriptor", diff --git a/src/trace_processor/util/file_buffer.cc b/src/trace_processor/util/file_buffer.cc deleted file mode 100644 index aa9234814e..0000000000 --- a/src/trace_processor/util/file_buffer.cc +++ /dev/null @@ -1,119 +0,0 @@ - -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/trace_processor/util/file_buffer.h" - -#include -#include -#include -#include -#include -#include - -#include "perfetto/base/logging.h" -#include "perfetto/trace_processor/trace_blob.h" -#include "perfetto/trace_processor/trace_blob_view.h" - -namespace perfetto::trace_processor::util { - -void FileBuffer::PushBack(TraceBlobView data) { - if (data.size() == 0) { - return; - } - const size_t size = data.size(); - data_.emplace_back(Entry{end_offset_, std::move(data)}); - end_offset_ += size; -} - -bool FileBuffer::PopFrontBytesUntil(const size_t target_offset) { - while (!data_.empty()) { - Entry& entry = data_.front(); - if (target_offset <= entry.file_offset) { - return true; - } - const size_t bytes_to_pop = target_offset - entry.file_offset; - if (entry.data.size() > bytes_to_pop) { - entry.data = - entry.data.slice_off(bytes_to_pop, entry.data.size() - bytes_to_pop); - entry.file_offset += bytes_to_pop; - return true; - } - data_.pop_front(); - } - - return target_offset == end_offset_; -} - -std::optional FileBuffer::SliceOff(size_t start_offset, - size_t length) const { - if (length == 0) { - return TraceBlobView(); - } - - if (start_offset + length > end_offset_) { - return std::nullopt; - } - - Iterator it = FindEntryWithOffset(start_offset); - if (it == end()) { - return std::nullopt; - } - - const size_t offset_from_entry_start = start_offset - it->file_offset; - const size_t bytes_in_entry = it->data.size() - offset_from_entry_start; - TraceBlobView first_blob = it->data.slice_off( - offset_from_entry_start, std::min(bytes_in_entry, length)); - - if (first_blob.size() == length) { - return std::move(first_blob); - } - - auto buffer = TraceBlob::Allocate(length); - uint8_t* ptr = buffer.data(); - - memcpy(ptr, first_blob.data(), first_blob.size()); - ptr += first_blob.size(); - length -= first_blob.size(); - ++it; - - while (length != 0) { - PERFETTO_DCHECK(it != end()); - const size_t bytes_to_copy = std::min(length, it->data.size()); - memcpy(ptr, it->data.data(), bytes_to_copy); - ptr += bytes_to_copy; - length -= bytes_to_copy; - ++it; - } - - return TraceBlobView(std::move(buffer)); -} - -FileBuffer::Iterator FileBuffer::FindEntryWithOffset(size_t offset) const { - if (offset >= end_offset_) { - return end(); - } - - auto it = std::upper_bound( - data_.begin(), data_.end(), offset, - [](size_t offset, const Entry& rhs) { return offset < rhs.file_offset; }); - if (it == data_.begin()) { - return end(); - } - return std::prev(it); -} - -} // namespace perfetto::trace_processor::util diff --git a/src/trace_processor/util/file_buffer.h b/src/trace_processor/util/file_buffer.h deleted file mode 100644 index 07de20fbaf..0000000000 --- a/src/trace_processor/util/file_buffer.h +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef SRC_TRACE_PROCESSOR_UTIL_FILE_BUFFER_H_ -#define SRC_TRACE_PROCESSOR_UTIL_FILE_BUFFER_H_ - -#include -#include - -#include "perfetto/ext/base/circular_queue.h" -#include "perfetto/trace_processor/trace_blob_view.h" - -namespace perfetto::trace_processor::util { - -// Helper class that exposes a window into the contents of a file. Data can be -// appended to the end of the buffer (increasing the size of the window) or -// removed from the front (decreasing the size of the window). -// -// TraceProcessor reads trace files in chunks and streams those to the -// `ChunkedTraceReader` instance. But sometimes the reader needs to look into -// the future (i.e. some data that has not yet arrived) before being able to -// process the current data. In such a case the reader would have to buffer data -// until the "future" data arrives. This class encapsulates that functionality. -class FileBuffer { - public: - // Trivial empty ctor. - FileBuffer() = default; - - // Returns the offset to the start of the buffered window of data. - size_t file_offset() const { - return data_.empty() ? end_offset_ : data_.front().file_offset; - } - - // Adds a `TraceBlobView` at the back. - void PushBack(TraceBlobView view); - - // Shrinks the buffer by dropping bytes from the front of the buffer until the - // given offset is reached. If not enough data is present as much data as - // possible will be dropped and `false` will be returned. - bool PopFrontBytesUntil(size_t offset); - - // Similar to `TraceBlobView::slice_off`, creates a slice with data starting - // at `offset` and of the given `length`. This method might need to allocate a - // new buffer and copy data into it (if the requested data spans multiple - // TraceBlobView instances). If not enough data is present `std::nullopt` is - // returned. - // - // ATTENTION: If `offset` < 'file_offset()' this method will never return a - // value. - std::optional SliceOff(size_t offset, size_t length) const; - - private: - struct Entry { - // File offset of the first byte in `data`. - size_t file_offset; - TraceBlobView data; - }; - using Iterator = base::CircularQueue::Iterator; - // Finds the `TraceBlobView` at `offset` and returns a slice starting at that - // offset and spanning the rest of the `TraceBlobView`. It also returns an - // iterator to the next `TraceBlobView` instance (which might be `end()`). - Iterator FindEntryWithOffset(size_t offset) const; - - Iterator end() const { return data_.end(); } - - // CircularQueue has no const_iterator, so mutable is needed to access it from - // const methods. - // CircularQueue has no const_iterator, so mutable is needed to access it from - // const methods. - mutable base::CircularQueue data_; - size_t end_offset_ = 0; -}; - -} // namespace perfetto::trace_processor::util - -#endif // SRC_TRACE_PROCESSOR_UTIL_FILE_BUFFER_H_ diff --git a/src/trace_processor/util/gzip_utils.cc b/src/trace_processor/util/gzip_utils.cc index cbb9cc0723..3d76c33818 100644 --- a/src/trace_processor/util/gzip_utils.cc +++ b/src/trace_processor/util/gzip_utils.cc @@ -16,19 +16,21 @@ #include "src/trace_processor/util/gzip_utils.h" -// For bazel build. +#include +#include +#include +#include + #include "perfetto/base/build_config.h" -#include "perfetto/base/compiler.h" #if PERFETTO_BUILDFLAG(PERFETTO_ZLIB) +#include #include #else struct z_stream_s {}; #endif -namespace perfetto { -namespace trace_processor { -namespace util { +namespace perfetto::trace_processor::util { bool IsGzipSupported() { #if PERFETTO_BUILDFLAG(PERFETTO_ZLIB) @@ -40,7 +42,8 @@ bool IsGzipSupported() { #if PERFETTO_BUILDFLAG(PERFETTO_ZLIB) // Real Implementation -GzipDecompressor::GzipDecompressor(InputMode mode) : z_stream_(new z_stream()) { +GzipDecompressor::GzipDecompressor(InputMode mode) + : z_stream_(new z_stream_s()) { z_stream_->zalloc = nullptr; z_stream_->zfree = nullptr; z_stream_->opaque = nullptr; @@ -50,10 +53,6 @@ GzipDecompressor::GzipDecompressor(InputMode mode) : z_stream_(new z_stream()) { inflateInit2(z_stream_.get(), wbits); } -GzipDecompressor::~GzipDecompressor() { - inflateEnd(z_stream_.get()); -} - void GzipDecompressor::Reset() { inflateReset(z_stream_.get()); } @@ -91,15 +90,27 @@ GzipDecompressor::Result GzipDecompressor::ExtractOutput(uint8_t* out, } } +size_t GzipDecompressor::AvailIn() const { + return z_stream_->avail_in; +} + +void GzipDecompressor::Deleter::operator()(z_stream_s* stream) const { + inflateEnd(stream); + delete stream; +} + #else // Dummy Implementation GzipDecompressor::GzipDecompressor(InputMode) {} -GzipDecompressor::~GzipDecompressor() = default; void GzipDecompressor::Reset() {} void GzipDecompressor::Feed(const uint8_t*, size_t) {} GzipDecompressor::Result GzipDecompressor::ExtractOutput(uint8_t*, size_t) { return Result{ResultCode::kError, 0}; } +size_t GzipDecompressor::AvailIn() const { + return 0; +} +void GzipDecompressor::Deleter::operator()(z_stream_s*) const {} #endif // PERFETTO_BUILDFLAG(PERFETTO_ZLIB) @@ -115,6 +126,4 @@ std::vector GzipDecompressor::DecompressFully(const uint8_t* data, return whole_data; } -} // namespace util -} // namespace trace_processor -} // namespace perfetto +} // namespace perfetto::trace_processor::util diff --git a/src/trace_processor/util/gzip_utils.h b/src/trace_processor/util/gzip_utils.h index ef7237b864..c9c9c32e96 100644 --- a/src/trace_processor/util/gzip_utils.h +++ b/src/trace_processor/util/gzip_utils.h @@ -17,6 +17,7 @@ #ifndef SRC_TRACE_PROCESSOR_UTIL_GZIP_UTILS_H_ #define SRC_TRACE_PROCESSOR_UTIL_GZIP_UTILS_H_ +#include #include #include #include @@ -79,9 +80,6 @@ class GzipDecompressor { }; explicit GzipDecompressor(InputMode = InputMode::kGzip); - ~GzipDecompressor(); - GzipDecompressor(const GzipDecompressor&) = delete; - GzipDecompressor& operator=(const GzipDecompressor&) = delete; // Feed the next mem-block. void Feed(const uint8_t* data, size_t size); @@ -89,6 +87,8 @@ class GzipDecompressor { // Feed the next mem-block and extract output in the callback consumer. // callback can get invoked multiple times if there are multiple // mem-blocks to output. + // + // Note the output of this function is guaranteed *not* to be kOk. template ResultCode FeedAndExtract(const uint8_t* data, size_t size, @@ -120,8 +120,14 @@ class GzipDecompressor { // which doesn't require streaming decompression. static std::vector DecompressFully(const uint8_t* data, size_t len); + // Returns the amount of input bytes left unprocessed. + size_t AvailIn() const; + private: - std::unique_ptr z_stream_; + struct Deleter { + void operator()(z_stream_s*) const; + }; + std::unique_ptr z_stream_; }; } // namespace util diff --git a/src/trace_processor/util/profile_builder.cc b/src/trace_processor/util/profile_builder.cc index 6948a8e8e7..2e02a845e3 100644 --- a/src/trace_processor/util/profile_builder.cc +++ b/src/trace_processor/util/profile_builder.cc @@ -480,7 +480,7 @@ std::vector GProfileBuilder::GetLinesForSymbolSetId( if (uint64_t function_id = WriteFunctionIfNeeded(symbol, annotation, mapping_id); function_id != kNullFunctionId) { - lines.push_back({function_id, symbol.line_number()}); + lines.push_back({function_id, symbol.line_number().value_or(0)}); } } @@ -502,7 +502,9 @@ uint64_t GProfileBuilder::WriteFunctionIfNeeded( CallsiteAnnotation annotation, uint64_t mapping_id) { int64_t name = string_table_.GetAnnotatedString(symbol.name(), annotation); - int64_t filename = string_table_.InternString(symbol.source_file()); + int64_t filename = symbol.source_file().has_value() + ? string_table_.InternString(*symbol.source_file()) + : kEmptyStringIndex; auto ins = functions_.insert( {Function{name, kEmptyStringIndex, filename}, functions_.size() + 1}); diff --git a/src/trace_processor/util/sql_argument.cc b/src/trace_processor/util/sql_argument.cc index f9ac83c529..14384c65ad 100644 --- a/src/trace_processor/util/sql_argument.cc +++ b/src/trace_processor/util/sql_argument.cc @@ -31,31 +31,31 @@ bool IsValidName(base::StringView str) { } std::optional ParseType(base::StringView str) { - if (str == "BOOL") { + if (str.CaseInsensitiveEq("bool")) { return Type::kBool; } - if (str == "INT") { + if (str.CaseInsensitiveEq("int")) { return Type::kInt; } - if (str == "UINT") { + if (str.CaseInsensitiveEq("uint")) { return Type::kUint; } - if (str == "LONG") { + if (str.CaseInsensitiveEq("long")) { return Type::kLong; } - if (str == "FLOAT") { + if (str.CaseInsensitiveEq("float")) { return Type::kFloat; } - if (str == "DOUBLE") { + if (str.CaseInsensitiveEq("double")) { return Type::kDouble; } - if (str == "STRING") { + if (str.CaseInsensitiveEq("string")) { return Type::kString; } - if (str == "PROTO") { + if (str.CaseInsensitiveEq("proto")) { return Type::kProto; } - if (str == "BYTES") { + if (str.CaseInsensitiveEq("bytes")) { return Type::kBytes; } return std::nullopt; diff --git a/src/trace_processor/util/trace_blob_view_reader.cc b/src/trace_processor/util/trace_blob_view_reader.cc new file mode 100644 index 0000000000..4ba5b13f11 --- /dev/null +++ b/src/trace_processor/util/trace_blob_view_reader.cc @@ -0,0 +1,122 @@ + +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "src/trace_processor/util/trace_blob_view_reader.h" + +#include +#include +#include +#include +#include +#include + +#include "perfetto/base/logging.h" +#include "perfetto/public/compiler.h" +#include "perfetto/trace_processor/trace_blob.h" +#include "perfetto/trace_processor/trace_blob_view.h" + +namespace perfetto::trace_processor::util { + +void TraceBlobViewReader::PushBack(TraceBlobView data) { + if (data.size() == 0) { + return; + } + const size_t size = data.size(); + data_.emplace_back(Entry{end_offset_, std::move(data)}); + end_offset_ += size; +} + +bool TraceBlobViewReader::PopFrontUntil(const size_t target_offset) { + PERFETTO_CHECK(start_offset() <= target_offset); + while (!data_.empty()) { + Entry& entry = data_.front(); + if (target_offset == entry.start_offset) { + return true; + } + const size_t bytes_to_pop = target_offset - entry.start_offset; + if (entry.data.size() > bytes_to_pop) { + entry.data = + entry.data.slice_off(bytes_to_pop, entry.data.size() - bytes_to_pop); + entry.start_offset += bytes_to_pop; + return true; + } + data_.pop_front(); + } + return target_offset == end_offset_; +} + +std::optional TraceBlobViewReader::SliceOff( + size_t offset, + size_t length) const { + PERFETTO_DCHECK(offset >= start_offset()); + + // Fast path: the slice fits entirely inside the first TBV, we can just slice + // that directly without doing any searching. This will happen most of the + // time when this class is used so optimize for it. + bool is_fast_path = + !data_.empty() && + offset + length <= data_.front().start_offset + data_.front().data.size(); + if (PERFETTO_LIKELY(is_fast_path)) { + return data_.front().data.slice_off(offset - data_.front().start_offset, + length); + } + + // If the length is zero, then a zero-sized blob view is always approrpriate. + if (PERFETTO_UNLIKELY(length == 0)) { + return TraceBlobView(); + } + + // If we don't have any TBVs or the end of the slice does not fit, then we + // cannot possibly return a full slice. + if (PERFETTO_UNLIKELY(data_.empty() || offset + length > end_offset_)) { + return std::nullopt; + } + + // Find the first block finishes *after* start_offset i.e. there is at least + // one byte in that block which will end up in the slice. We know this *must* + // exist because of the above check. + auto rit = std::upper_bound( + data_.begin(), data_.end(), offset, [](size_t offset, const Entry& rhs) { + return offset < rhs.start_offset + rhs.data.size(); + }); + PERFETTO_CHECK(rit != data_.end()); + + // If the slice fits entirely in the block we found, then just slice that + // block avoiding any copies. + size_t rel_off = offset - rit->start_offset; + if (rel_off + length <= rit->data.size()) { + return rit->data.slice_off(rel_off, length); + } + + // Otherwise, allocate some memory and make a copy. + auto buffer = TraceBlob::Allocate(length); + uint8_t* ptr = buffer.data(); + uint8_t* end = buffer.data() + buffer.size(); + + // Copy all bytes in this block which overlap with the slice. + memcpy(ptr, rit->data.data() + rel_off, rit->data.length() - rel_off); + ptr += rit->data.length() - rel_off; + + for (auto it = rit + 1; ptr != end; ++it) { + auto len = std::min(static_cast(end - ptr), it->data.size()); + memcpy(ptr, it->data.data(), len); + ptr += len; + } + return TraceBlobView(std::move(buffer)); +} + +} // namespace perfetto::trace_processor::util diff --git a/src/trace_processor/util/trace_blob_view_reader.h b/src/trace_processor/util/trace_blob_view_reader.h new file mode 100644 index 0000000000..c39ffaba7c --- /dev/null +++ b/src/trace_processor/util/trace_blob_view_reader.h @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SRC_TRACE_PROCESSOR_UTIL_TRACE_BLOB_VIEW_READER_H_ +#define SRC_TRACE_PROCESSOR_UTIL_TRACE_BLOB_VIEW_READER_H_ + +#include +#include + +#include "perfetto/ext/base/circular_queue.h" +#include "perfetto/trace_processor/trace_blob_view.h" + +namespace perfetto::trace_processor::util { + +// Helper class which handles all the complexity of reading pieces of data which +// span across multiple TraceBlobView chunks. It takes care of: +// 1) Buffering data until it can be read. +// 2) Stitching together the cross-chunk spanning pieces. +// 3) Dropping data when it is no longer necessary to be buffered. +class TraceBlobViewReader { + public: + // Adds a `TraceBlobView` at the back. + void PushBack(TraceBlobView); + + // Shrinks the buffer by dropping data from the front of the buffer until the + // given offset is reached. If not enough data is present as much data as + // possible will be dropped and `false` will be returned. + // + // NOTE: If `offset` < 'file_offset()' this method will CHECK fail. + bool PopFrontUntil(size_t offset); + + // Shrinks the buffer by dropping `bytes` from the front of the buffer. If not + // enough data is present as much data as possible will be dropped and `false` + // will be returned. + bool PopFrontBytes(size_t bytes) { + return PopFrontUntil(start_offset() + bytes); + } + + // Creates a TraceBlobView by slicing this reader starting at |offset| and + // spanning |length| bytes. + // + // If possible, this method will try to avoid copies and simply slice an + // input TraceBlobView. However, that may not be possible and it so, it will + // allocate a new chunk of memory and copy over the data instead. + // + // NOTE: If `offset` < 'file_offset()' this method will CHECK fail. + std::optional SliceOff(size_t offset, size_t length) const; + + // Returns the offset to the start of the available data. + size_t start_offset() const { + return data_.empty() ? end_offset_ : data_.front().start_offset; + } + + // Returns the offset to the end of the available data. + size_t end_offset() const { return end_offset_; } + + // Returns the number of bytes of buffered data. + size_t avail() const { return end_offset() - start_offset(); } + + bool empty() const { return data_.empty(); } + + private: + struct Entry { + // File offset of the first byte in `data`. + size_t start_offset; + TraceBlobView data; + }; + using Iterator = base::CircularQueue::Iterator; + + // CircularQueue has no const_iterator, so mutable is needed to access it from + // const methods. + mutable base::CircularQueue data_; + size_t end_offset_ = 0; +}; + +} // namespace perfetto::trace_processor::util + +#endif // SRC_TRACE_PROCESSOR_UTIL_TRACE_BLOB_VIEW_READER_H_ diff --git a/src/trace_processor/util/file_buffer_unittest.cc b/src/trace_processor/util/trace_blob_view_reader_unittest.cc similarity index 80% rename from src/trace_processor/util/file_buffer_unittest.cc rename to src/trace_processor/util/trace_blob_view_reader_unittest.cc index 418cf76f83..583648f1b5 100644 --- a/src/trace_processor/util/file_buffer_unittest.cc +++ b/src/trace_processor/util/trace_blob_view_reader_unittest.cc @@ -14,11 +14,14 @@ * limitations under the License. */ -#include "src/trace_processor/util/file_buffer.h" +#include "src/trace_processor/util/trace_blob_view_reader.h" +#include #include #include #include +#include +#include #include #include "perfetto/trace_processor/trace_blob.h" @@ -43,11 +46,9 @@ class SameDataAsMatcher { : expected_data_(expected_data) {} bool MatchAndExplain(const ArgType& arg, ::testing ::MatchResultListener*) const override { - if (expected_data_.size() != arg.size()) { - return false; - } - return memcmp(expected_data_.data(), arg.data(), expected_data_.size()) == - 0; + return std::equal(expected_data_.data(), + expected_data_.data() + expected_data_.size(), + arg.data(), arg.data() + arg.size()); } void DescribeTo(::std ::ostream*) const override {} void DescribeNegationTo(::std ::ostream*) const override {} @@ -93,22 +94,24 @@ std::vector Slice(const TraceBlobView& blob, size_t chunk_size) { return chunks; } -FileBuffer CreateFileBuffer(const std::vector& chunks) { - FileBuffer chunked_buffer; +TraceBlobViewReader CreateTraceBlobViewReader( + const std::vector& chunks) { + TraceBlobViewReader chunked_buffer; for (const auto& chunk : chunks) { chunked_buffer.PushBack(chunk.copy()); } return chunked_buffer; } -TEST(FileBuffer, ContiguousAccessAtOffset) { +TEST(TraceBlobViewReader, ContiguousAccessAtOffset) { constexpr size_t kExpectedSize = 256; constexpr size_t kChunkSize = kExpectedSize / 4; TraceBlobView expected_data = CreateExpectedData(kExpectedSize); - FileBuffer buffer = CreateFileBuffer(Slice(expected_data, kChunkSize)); + TraceBlobViewReader buffer = + CreateTraceBlobViewReader(Slice(expected_data, kChunkSize)); for (size_t file_offset = 0; file_offset <= kExpectedSize; ++file_offset) { - EXPECT_TRUE(buffer.PopFrontBytesUntil(file_offset)); + EXPECT_TRUE(buffer.PopFrontUntil(file_offset)); for (size_t off = file_offset; off <= kExpectedSize; ++off) { auto expected = expected_data.slice_off(off, kExpectedSize - off); std::optional tbv = buffer.SliceOff(off, expected.size()); @@ -117,12 +120,12 @@ TEST(FileBuffer, ContiguousAccessAtOffset) { } } -TEST(FileBuffer, NoCopyIfDataIsContiguous) { +TEST(TraceBlobViewReader, NoCopyIfDataIsContiguous) { constexpr size_t kExpectedSize = 256; constexpr size_t kChunkSize = kExpectedSize / 4; std::vector chunks = Slice(CreateExpectedData(kExpectedSize), kChunkSize); - FileBuffer buffer = CreateFileBuffer(chunks); + TraceBlobViewReader buffer = CreateTraceBlobViewReader(chunks); for (size_t i = 0; i < chunks.size(); ++i) { for (size_t off = 0; off < kChunkSize; ++off) { @@ -134,27 +137,26 @@ TEST(FileBuffer, NoCopyIfDataIsContiguous) { } } -TEST(FileBuffer, PopRemovesData) { +TEST(TraceBlobViewReader, PopRemovesData) { size_t expected_size = 256; size_t expected_file_offset = 0; const size_t kChunkSize = expected_size / 4; TraceBlobView expected_data = CreateExpectedData(expected_size); - FileBuffer buffer = CreateFileBuffer(Slice(expected_data, kChunkSize)); + TraceBlobViewReader buffer = + CreateTraceBlobViewReader(Slice(expected_data, kChunkSize)); --expected_size; ++expected_file_offset; - buffer.PopFrontBytesUntil(expected_file_offset); - EXPECT_THAT(buffer.file_offset(), Eq(expected_file_offset)); - EXPECT_THAT(buffer.SliceOff(expected_file_offset - 1, 1), Eq(std::nullopt)); + buffer.PopFrontUntil(expected_file_offset); + EXPECT_THAT(buffer.start_offset(), Eq(expected_file_offset)); EXPECT_THAT(buffer.SliceOff(expected_file_offset, expected_size), Optional(SameDataAs(expected_data.slice_off( expected_data.size() - expected_size, expected_size)))); expected_size -= kChunkSize; expected_file_offset += kChunkSize; - buffer.PopFrontBytesUntil(expected_file_offset); - EXPECT_THAT(buffer.file_offset(), Eq(expected_file_offset)); - EXPECT_THAT(buffer.SliceOff(expected_file_offset - 1, 1), Eq(std::nullopt)); + buffer.PopFrontUntil(expected_file_offset); + EXPECT_THAT(buffer.start_offset(), Eq(expected_file_offset)); EXPECT_THAT(buffer.SliceOff(expected_file_offset, expected_size), Optional(SameDataAs(expected_data.slice_off( expected_data.size() - expected_size, expected_size)))); diff --git a/src/trace_processor/util/trace_type.cc b/src/trace_processor/util/trace_type.cc new file mode 100644 index 0000000000..c4a5762a87 --- /dev/null +++ b/src/trace_processor/util/trace_type.cc @@ -0,0 +1,215 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "src/trace_processor/util/trace_type.h" + +#include +#include +#include +#include + +#include "perfetto/base/logging.h" +#include "perfetto/ext/base/string_utils.h" +#include "perfetto/ext/base/string_view.h" +#include "src/trace_processor/importers/android_bugreport/android_log_event.h" + +#include "protos/perfetto/trace/trace.pbzero.h" +#include "protos/perfetto/trace/trace_packet.pbzero.h" + +namespace perfetto::trace_processor { +namespace { +// Fuchsia traces have a magic number as documented here: +// https://fuchsia.googlesource.com/fuchsia/+/HEAD/docs/development/tracing/trace-format/README.md#magic-number-record-trace-info-type-0 +constexpr char kFuchsiaMagic[] = {'\x10', '\x00', '\x04', '\x46', + '\x78', '\x54', '\x16', '\x00'}; +constexpr char kPerfMagic[] = {'P', 'E', 'R', 'F', 'I', 'L', 'E', '2'}; + +constexpr char kZipMagic[] = {'P', 'K', '\x03', '\x04'}; + +constexpr char kGzipMagic[] = {'\x1f', '\x8b'}; + +constexpr uint8_t kTracePacketTag = + protozero::proto_utils::MakeTagLengthDelimited( + protos::pbzero::Trace::kPacketFieldNumber); +constexpr uint16_t kModuleSymbolsTag = + protozero::proto_utils::MakeTagLengthDelimited( + protos::pbzero::TracePacket::kModuleSymbolsFieldNumber); + +inline bool isspace(unsigned char c) { + return ::isspace(c); +} + +std::string RemoveWhitespace(std::string str) { + str.erase(std::remove_if(str.begin(), str.end(), isspace), str.end()); + return str; +} + +template +bool MatchesMagic(const uint8_t* data, size_t size, const char (&magic)[N]) { + if (size < N) { + return false; + } + return memcmp(data, magic, N) == 0; +} + +base::StringView FindLine(const uint8_t* data, size_t size) { + for (size_t i = 0; i < size; ++i) { + if (data[i] == '\n') { + return base::StringView(reinterpret_cast(data), i); + } + } + return base::StringView(); +} + +bool IsProtoTraceWithSymbols(const uint8_t* ptr, size_t size) { + const uint8_t* const end = ptr + size; + + uint64_t tag; + const uint8_t* next = protozero::proto_utils::ParseVarInt(ptr, end, &tag); + + if (next == ptr || tag != kTracePacketTag) { + return false; + } + + ptr = next; + uint64_t field_length; + next = protozero::proto_utils::ParseVarInt(ptr, end, &field_length); + if (next == ptr) { + return false; + } + ptr = next; + + if (field_length == 0) { + return false; + } + + next = protozero::proto_utils::ParseVarInt(ptr, end, &tag); + if (next == ptr) { + return false; + } + + return tag == kModuleSymbolsTag; +} + +} // namespace + +const char* TraceTypeToString(TraceType trace_type) { + switch (trace_type) { + case kJsonTraceType: + return "json"; + case kProtoTraceType: + return "proto"; + case kSymbolsTraceType: + return "symbols"; + case kNinjaLogTraceType: + return "ninja_log"; + case kFuchsiaTraceType: + return "fuchsia"; + case kSystraceTraceType: + return "systrace"; + case kGzipTraceType: + return "gzip"; + case kCtraceTraceType: + return "ctrace"; + case kZipFile: + return "zip"; + case kPerfDataTraceType: + return "perf"; + case kAndroidLogcatTraceType: + return "android_logcat"; + case kAndroidDumpstateTraceType: + return "android_dumpstate"; + case kAndroidBugreportTraceType: + return "android_bugreport"; + case kUnknownTraceType: + return "unknown"; + } + PERFETTO_FATAL("For GCC"); +} + +TraceType GuessTraceType(const uint8_t* data, size_t size) { + if (size == 0) { + return kUnknownTraceType; + } + + if (MatchesMagic(data, size, kFuchsiaMagic)) { + return kFuchsiaTraceType; + } + + if (MatchesMagic(data, size, kPerfMagic)) { + return kPerfDataTraceType; + } + + if (MatchesMagic(data, size, kZipMagic)) { + return kZipFile; + } + + if (MatchesMagic(data, size, kGzipMagic)) { + return kGzipTraceType; + } + + std::string start(reinterpret_cast(data), + std::min(size, kGuessTraceMaxLookahead)); + + std::string start_minus_white_space = RemoveWhitespace(start); + if (base::StartsWith(start_minus_white_space, "{\"")) + return kJsonTraceType; + if (base::StartsWith(start_minus_white_space, "[{\"")) + return kJsonTraceType; + + // Systrace with header but no leading HTML. + if (base::Contains(start, "# tracer")) + return kSystraceTraceType; + + // Systrace with leading HTML. + // Both: and have been observed. + std::string lower_start = base::ToLower(start); + if (base::StartsWith(lower_start, "") || + base::StartsWith(lower_start, "")) + return kSystraceTraceType; + + // Traces obtained from atrace -z (compress). + // They all have the string "TRACE:" followed by 78 9C which is a zlib header + // for "deflate, default compression, window size=32K" (see b/208691037) + if (base::Contains(start, "TRACE:\n\x78\x9c")) + return kCtraceTraceType; + + // Traces obtained from atrace without -z (no compression). + if (base::Contains(start, "TRACE:\n")) + return kSystraceTraceType; + + // Ninja's build log (.ninja_log). + if (base::StartsWith(start, "# ninja log")) + return kNinjaLogTraceType; + + if (AndroidLogEvent::IsAndroidLogEvent(FindLine(data, size))) { + return kAndroidLogcatTraceType; + } + + // Systrace with no header or leading HTML. + if (base::StartsWith(start, " ")) + return kSystraceTraceType; + + if (IsProtoTraceWithSymbols(data, size)) + return kSymbolsTraceType; + + if (base::StartsWith(start, "\x0a")) + return kProtoTraceType; + + return kUnknownTraceType; +} + +} // namespace perfetto::trace_processor diff --git a/src/trace_processor/util/trace_type.h b/src/trace_processor/util/trace_type.h new file mode 100644 index 0000000000..7e730fc4d9 --- /dev/null +++ b/src/trace_processor/util/trace_type.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SRC_TRACE_PROCESSOR_UTIL_TRACE_TYPE_H_ +#define SRC_TRACE_PROCESSOR_UTIL_TRACE_TYPE_H_ + +#include +#include + +namespace perfetto::trace_processor { + +enum TraceType { + kAndroidBugreportTraceType, + kAndroidDumpstateTraceType, + kAndroidLogcatTraceType, + kCtraceTraceType, + kFuchsiaTraceType, + kGzipTraceType, + kJsonTraceType, + kNinjaLogTraceType, + kPerfDataTraceType, + kProtoTraceType, + kSymbolsTraceType, + kSystraceTraceType, + kUnknownTraceType, + kZipFile, +}; + +constexpr size_t kGuessTraceMaxLookahead = 64; +TraceType GuessTraceType(const uint8_t* data, size_t size); +const char* TraceTypeToString(TraceType type); + +} // namespace perfetto::trace_processor + +#endif // SRC_TRACE_PROCESSOR_UTIL_TRACE_TYPE_H_ diff --git a/src/trace_processor/util/zip_reader.cc b/src/trace_processor/util/zip_reader.cc index d1651f2a26..1ec0cf50db 100644 --- a/src/trace_processor/util/zip_reader.cc +++ b/src/trace_processor/util/zip_reader.cc @@ -16,28 +16,52 @@ #include "src/trace_processor/util/zip_reader.h" -#include +#include +#include +#include +#include +#include +#include +#include +#include #include "perfetto/base/build_config.h" #include "perfetto/base/logging.h" +#include "perfetto/base/status.h" #include "perfetto/base/time.h" +#include "perfetto/ext/base/status_or.h" +#include "perfetto/ext/base/string_view.h" #include "perfetto/ext/base/utils.h" +#include "perfetto/trace_processor/trace_blob_view.h" #include "src/trace_processor/util/gzip_utils.h" +#include "src/trace_processor/util/status_macros.h" #include "src/trace_processor/util/streaming_line_reader.h" #if PERFETTO_BUILDFLAG(PERFETTO_ZLIB) -#include // For crc32(). +#include +#include #endif -namespace perfetto { -namespace trace_processor { -namespace util { +namespace perfetto::trace_processor::util { namespace { // Entry signatures. -const uint32_t kFileHeaderSig = 0x04034b50; -const uint32_t kCentralDirectorySig = 0x02014b50; +constexpr uint32_t kFileHeaderSig = 0x04034b50; +constexpr uint32_t kCentralDirectorySig = 0x02014b50; +constexpr uint32_t kDataDescriptorSig = 0x08074b50; + +// 4 bytes each of: 1) signature, 2) crc, 3) compressed size 4) uncompressed +// size. +constexpr uint32_t kDataDescriptorSize = 4 * 4; + +enum GeneralPurposeBitFlag : uint32_t { + kEncrypted = 1 << 0, + k8kSlidingDictionary = 1u << 1, + kShannonFaro = 1u << 2, + kDataDescriptor = 1u << 3, + kUnknown = ~((1u << 4) - 1), +}; // Compression flags. const uint16_t kNoCompression = 0; @@ -56,136 +80,249 @@ T ReadAndAdvance(const uint8_t** ptr) { ZipReader::ZipReader() = default; ZipReader::~ZipReader() = default; -base::Status ZipReader::Parse(const void* data, size_t len) { - const uint8_t* input = static_cast(data); - const uint8_t* const input_begin = input; - const uint8_t* const input_end = input + len; - auto input_avail = [&] { return static_cast(input_end - input); }; +base::Status ZipReader::Parse(TraceBlobView tbv) { + reader_.PushBack(std::move(tbv)); // .zip file sequence: // [ File 1 header (30 bytes) ] // [ File 1 name ] // [ File 1 extra fields (optional) ] // [ File 1 compressed payload ] + // [ File 1 data descriptor (optional) ] // // [ File 2 header (30 bytes) ] // [ File 2 name ] // [ File 2 extra fields (optional) ] // [ File 2 compressed payload ] + // [ File 2 data descriptor (optional) ] // // [ Central directory (ignored) ] - while (input < input_end) { - // Initial state, we are building up the file header. - if (cur_.raw_hdr_size < kZipFileHdrSize) { - size_t copy_size = - std::min(input_avail(), kZipFileHdrSize - cur_.raw_hdr_size); - memcpy(&cur_.raw_hdr[cur_.raw_hdr_size], input, copy_size); - cur_.raw_hdr_size += copy_size; - input += copy_size; - - // If we got all the kZipFileHdrSize bytes, parse the zip file header now. - if (cur_.raw_hdr_size == kZipFileHdrSize) { - const uint8_t* hdr_it = &cur_.raw_hdr[0]; - cur_.hdr.signature = ReadAndAdvance(&hdr_it); - if (cur_.hdr.signature == kCentralDirectorySig) { - // We reached the central directory at the end of file. - // We don't make any use here of the central directory, so we just - // ignore everything else after this point. - // Here we abuse the ZipFile class a bit. The Central Directory header - // has a different layout. The first 4 bytes (signature) match, the - // rest don't but the sizeof(central dir) is >> sizeof(file header) so - // we are fine. - // We do this rather than retuning because we could have further - // Parse() calls (imagine parsing bytes one by one), and we need a way - // to keep track of the "keep eating input without doing anything". - cur_.ignore_bytes_after_fname = std::numeric_limits::max(); - input = input_end; - break; - } - if (cur_.hdr.signature != kFileHeaderSig) { - return base::ErrStatus( - "Invalid signature found at offset 0x%zx. Actual=%x, expected=%x", - static_cast(input - input_begin) - kZipFileHdrSize, - cur_.hdr.signature, kFileHeaderSig); - } - - cur_.hdr.version = ReadAndAdvance(&hdr_it); - cur_.hdr.flags = ReadAndAdvance(&hdr_it); - cur_.hdr.compression = ReadAndAdvance(&hdr_it); - cur_.hdr.mtime = ReadAndAdvance(&hdr_it); - cur_.hdr.mdate = ReadAndAdvance(&hdr_it); - cur_.hdr.checksum = ReadAndAdvance(&hdr_it); - cur_.hdr.compressed_size = ReadAndAdvance(&hdr_it); - cur_.hdr.uncompressed_size = ReadAndAdvance(&hdr_it); - cur_.hdr.fname_len = ReadAndAdvance(&hdr_it); - cur_.hdr.extra_field_len = ReadAndAdvance(&hdr_it); - PERFETTO_DCHECK(static_cast(hdr_it - cur_.raw_hdr) == - kZipFileHdrSize); - - // We support only up to version 2.0 (20). Higher versions define - // more advanced features that we don't support (zip64 extensions, - // encryption). - // Flag bits 1,2 define the compression strength for deflating (which - // zlib supports transparently). Other bits define other compression - // methods that we don't support. - if ((cur_.hdr.version > 20) || (cur_.hdr.flags & ~3) != 0) { - return base::ErrStatus( - "Unsupported zip features at offset 0x%zx. version=%x, flags=%x", - static_cast(input - input_begin) - kZipFileHdrSize, - cur_.hdr.version, cur_.hdr.flags); - } - cur_.compressed_data.reset(new uint8_t[cur_.hdr.compressed_size]); - cur_.ignore_bytes_after_fname = cur_.hdr.extra_field_len; - } - continue; - } - // Build up the file name. - if (cur_.hdr.fname.size() < cur_.hdr.fname_len) { - size_t name_left = cur_.hdr.fname_len - cur_.hdr.fname.size(); - size_t copy_size = std::min(name_left, input_avail()); - cur_.hdr.fname.append(reinterpret_cast(input), copy_size); - input += copy_size; - continue; + for (;;) { + auto state = cur_.parse_state; + switch (state) { + case FileParseState::kHeader: + RETURN_IF_ERROR(TryParseHeader()); + break; + case FileParseState::kFilename: + RETURN_IF_ERROR(TryParseFilename()); + break; + case FileParseState::kSkipBytes: + RETURN_IF_ERROR(TrySkipBytes()); + break; + case FileParseState::kCompressedData: + RETURN_IF_ERROR(TryParseCompressedData()); + break; } - - // Skip any bytes if extra fields were present. - if (cur_.ignore_bytes_after_fname > 0) { - size_t skip_size = std::min(input_avail(), cur_.ignore_bytes_after_fname); - cur_.ignore_bytes_after_fname -= skip_size; - input += skip_size; - continue; + if (state == cur_.parse_state) { + return base::OkStatus(); } + } +} - // Build up the compressed payload - if (cur_.compressed_data_written < cur_.hdr.compressed_size) { - size_t needed = cur_.hdr.compressed_size - cur_.compressed_data_written; - size_t copy_size = std::min(needed, input_avail()); - memcpy(&cur_.compressed_data[cur_.compressed_data_written], input, - copy_size); - cur_.compressed_data_written += copy_size; - input += copy_size; - continue; - } +base::Status ZipReader::TryParseHeader() { + PERFETTO_CHECK(cur_.hdr.signature == 0); - // We have accumulated the whole header, file name and compressed payload. - PERFETTO_DCHECK(cur_.raw_hdr_size == kZipFileHdrSize); - PERFETTO_DCHECK(cur_.hdr.fname.size() == cur_.hdr.fname_len); - PERFETTO_DCHECK(cur_.compressed_data_written == cur_.hdr.compressed_size); - PERFETTO_DCHECK(cur_.ignore_bytes_after_fname == 0); + std::optional hdr = + reader_.SliceOff(reader_.start_offset(), kZipFileHdrSize); + if (!hdr) { + return base::OkStatus(); + } + PERFETTO_CHECK(reader_.PopFrontBytes(kZipFileHdrSize)); + + const uint8_t* hdr_it = hdr->data(); + cur_.hdr.signature = ReadAndAdvance(&hdr_it); + if (cur_.hdr.signature == kCentralDirectorySig) { + // We reached the central directory at the end of file. + // We don't make any use here of the central directory, so we just + // ignore everything else after this point. + // Here we abuse the ZipFile class a bit. The Central Directory header + // has a different layout. The first 4 bytes (signature) match, the + // rest don't but the sizeof(central dir) is >> sizeof(file header) so + // we are fine. + // We do this rather than retuning because we could have further + // Parse() calls (imagine parsing bytes one by one), and we need a way + // to keep track of the "keep eating input without doing anything". + cur_.ignore_bytes_after_fname = std::numeric_limits::max(); + cur_.parse_state = FileParseState::kSkipBytes; + return base::OkStatus(); + } + if (cur_.hdr.signature != kFileHeaderSig) { + return base::ErrStatus( + "Invalid signature found at offset 0x%zx. Actual=0x%x, " + "expected=0x%x", + reader_.start_offset(), cur_.hdr.signature, kFileHeaderSig); + } - files_.emplace_back(); - files_.back().hdr_ = std::move(cur_.hdr); - files_.back().compressed_data_ = std::move(cur_.compressed_data); - cur_ = FileParseState(); // Reset the parsing state for the next file. + cur_.hdr.version = ReadAndAdvance(&hdr_it); + cur_.hdr.flags = ReadAndAdvance(&hdr_it); + cur_.hdr.compression = ReadAndAdvance(&hdr_it); + cur_.hdr.mtime = ReadAndAdvance(&hdr_it); + cur_.hdr.mdate = ReadAndAdvance(&hdr_it); + cur_.hdr.checksum = ReadAndAdvance(&hdr_it); + cur_.hdr.compressed_size = ReadAndAdvance(&hdr_it); + cur_.hdr.uncompressed_size = ReadAndAdvance(&hdr_it); + cur_.hdr.fname_len = ReadAndAdvance(&hdr_it); + cur_.hdr.extra_field_len = ReadAndAdvance(&hdr_it); + PERFETTO_DCHECK(static_cast(hdr_it - hdr->data()) == kZipFileHdrSize); + + // We support only up to version 2.0 (20). Higher versions define + // more advanced features that we don't support (zip64 extensions, + // encryption). + // Disallow encryption or any flags we don't know how to handle. + if ((cur_.hdr.version > 20) || (cur_.hdr.flags & kEncrypted) || + (cur_.hdr.flags & kUnknown)) { + return base::ErrStatus( + "Unsupported zip features at offset 0x%zx. version=%x, flags=%x", + reader_.start_offset(), cur_.hdr.version, cur_.hdr.flags); + } + if (cur_.hdr.compression != kNoCompression && + cur_.hdr.compression != kDeflate) { + return base::ErrStatus( + "Unsupported compression type at offset 0x%zx. type=%x. Only " + "deflate and no compression are supported.", + reader_.start_offset(), cur_.hdr.compression); + } + if (cur_.hdr.flags & kDataDescriptor && cur_.hdr.compression != kDeflate) { + return base::ErrStatus( + "Unsupported compression type at offset 0x%zx. type=%x. Only " + "deflate supported for ZIPs compressed in a streaming fashion.", + reader_.start_offset(), cur_.hdr.compression); + } + cur_.ignore_bytes_after_fname = cur_.hdr.extra_field_len; + cur_.parse_state = FileParseState::kFilename; + return base::OkStatus(); +} - } // while (input < input_end) +base::Status ZipReader::TryParseFilename() { + if (cur_.hdr.fname_len == 0) { + cur_.parse_state = FileParseState::kSkipBytes; + return base::OkStatus(); + } + PERFETTO_CHECK(cur_.hdr.fname.empty()); - // At this point we must have consumed all input. - PERFETTO_DCHECK(input_avail() == 0); + std::optional fname_tbv = + reader_.SliceOff(reader_.start_offset(), cur_.hdr.fname_len); + if (!fname_tbv) { + return base::OkStatus(); + } + PERFETTO_CHECK(reader_.PopFrontBytes(cur_.hdr.fname_len)); + cur_.hdr.fname = std::string(reinterpret_cast(fname_tbv->data()), + cur_.hdr.fname_len); + cur_.parse_state = FileParseState::kSkipBytes; return base::OkStatus(); } +base::Status ZipReader::TrySkipBytes() { + if (cur_.ignore_bytes_after_fname == 0) { + cur_.parse_state = FileParseState::kCompressedData; + return base::OkStatus(); + } + + size_t avail = reader_.avail(); + if (avail < cur_.ignore_bytes_after_fname) { + PERFETTO_CHECK(reader_.PopFrontBytes(avail)); + cur_.ignore_bytes_after_fname -= avail; + return base::OkStatus(); + } + PERFETTO_CHECK(reader_.PopFrontBytes(cur_.ignore_bytes_after_fname)); + cur_.ignore_bytes_after_fname = 0; + cur_.parse_state = FileParseState::kCompressedData; + return base::OkStatus(); +} + +base::Status ZipReader::TryParseCompressedData() { + // Build up the compressed payload + if (cur_.hdr.flags & kDataDescriptor) { + if (!cur_.compressed) { + ASSIGN_OR_RETURN(auto compressed, TryParseUnsizedCompressedData()); + if (!compressed) { + return base::OkStatus(); + } + cur_.compressed = std::move(compressed); + } + + std::optional data_descriptor = + reader_.SliceOff(reader_.start_offset(), kDataDescriptorSize); + if (!data_descriptor) { + return base::OkStatus(); + } + PERFETTO_CHECK(reader_.PopFrontBytes(kDataDescriptorSize)); + + const auto* desc_it = data_descriptor->data(); + auto desc_sig = ReadAndAdvance(&desc_it); + if (desc_sig != kDataDescriptorSig) { + return base::ErrStatus( + "Invalid signature found at offset 0x%zx. Actual=0x%x, " + "expected=0x%x", + reader_.start_offset(), desc_sig, kDataDescriptorSig); + } + cur_.hdr.checksum = ReadAndAdvance(&desc_it); + cur_.hdr.compressed_size = ReadAndAdvance(&desc_it); + cur_.hdr.uncompressed_size = ReadAndAdvance(&desc_it); + } else { + PERFETTO_CHECK(!cur_.compressed); + std::optional raw_compressed = + reader_.SliceOff(reader_.start_offset(), cur_.hdr.compressed_size); + if (!raw_compressed) { + return base::OkStatus(); + } + cur_.compressed = *std::move(raw_compressed); + PERFETTO_CHECK(reader_.PopFrontBytes(cur_.hdr.compressed_size)); + } + + // We have accumulated the whole header, file name and compressed payload. + PERFETTO_CHECK(cur_.compressed); + PERFETTO_CHECK(cur_.hdr.fname.size() == cur_.hdr.fname_len); + PERFETTO_CHECK(cur_.compressed->size() == cur_.hdr.compressed_size); + PERFETTO_CHECK(cur_.ignore_bytes_after_fname == 0); + + files_.emplace_back(); + files_.back().hdr_ = std::move(cur_.hdr); + files_.back().compressed_data_ = *std::move(cur_.compressed); + cur_ = FileParseState(); // Reset the parsing state for the next file. + return base::OkStatus(); +} // namespace perfetto::trace_processor::util + +base::StatusOr> +ZipReader::TryParseUnsizedCompressedData() { + PERFETTO_CHECK(cur_.hdr.compression == kDeflate); + + auto start = reader_.start_offset() + cur_.decompressor_bytes_fed; + auto end = reader_.end_offset(); + auto slice = reader_.SliceOff(start, end - start); + PERFETTO_CHECK(slice); + auto res_code = cur_.decompressor.FeedAndExtract(slice->data(), slice->size(), + [](const uint8_t*, size_t) { + // Intentionally do + // nothing: we are only + // looking for the bounds + // of the deflate stream, + // we are not actually + // interested in the + // output. + }); + switch (res_code) { + case GzipDecompressor::ResultCode::kNeedsMoreInput: + cur_.decompressor_bytes_fed += slice->size(); + return {std::nullopt}; + case GzipDecompressor::ResultCode::kError: + return base::ErrStatus( + "Failed decompressing stream in ZIP file at offset 0x%zx", + reader_.start_offset()); + case GzipDecompressor::ResultCode::kOk: + PERFETTO_FATAL("Unexpected result code"); + case GzipDecompressor::ResultCode::kEof: + break; + } + cur_.decompressor_bytes_fed += slice->size() - cur_.decompressor.AvailIn(); + auto raw_compressed = + reader_.SliceOff(reader_.start_offset(), cur_.decompressor_bytes_fed); + PERFETTO_CHECK(raw_compressed); + PERFETTO_CHECK(reader_.PopFrontBytes(cur_.decompressor_bytes_fed)); + return {std::move(raw_compressed)}; +} + ZipFile* ZipReader::Find(const std::string& path) { for (ZipFile& zf : files_) { if (zf.name() == path) @@ -201,23 +338,21 @@ ZipFile& ZipFile::operator=(ZipFile&& other) noexcept = default; base::Status ZipFile::Decompress(std::vector* out_data) const { out_data->clear(); - - auto res = DoDecompressionChecks(); - if (!res.ok()) - return res; + RETURN_IF_ERROR(DoDecompressionChecks()); if (hdr_.compression == kNoCompression) { - const uint8_t* data = compressed_data_.get(); + const uint8_t* data = compressed_data_.data(); out_data->insert(out_data->end(), data, data + hdr_.compressed_size); return base::OkStatus(); } - if (hdr_.uncompressed_size == 0) + if (hdr_.uncompressed_size == 0) { return base::OkStatus(); + } PERFETTO_DCHECK(hdr_.compression == kDeflate); GzipDecompressor dec(GzipDecompressor::InputMode::kRawDeflate); - dec.Feed(compressed_data_.get(), hdr_.compressed_size); + dec.Feed(compressed_data_.data(), hdr_.compressed_size); out_data->resize(hdr_.uncompressed_size); auto dec_res = dec.ExtractOutput(out_data->data(), out_data->size()); @@ -243,23 +378,20 @@ base::Status ZipFile::Decompress(std::vector* out_data) const { base::Status ZipFile::DecompressLines(LinesCallback callback) const { using ResultCode = GzipDecompressor::ResultCode; + RETURN_IF_ERROR(DoDecompressionChecks()); - auto res = DoDecompressionChecks(); - if (!res.ok()) - return res; - - StreamingLineReader line_reader(callback); + StreamingLineReader line_reader(std::move(callback)); if (hdr_.compression == kNoCompression) { line_reader.Tokenize( - base::StringView(reinterpret_cast(compressed_data_.get()), + base::StringView(reinterpret_cast(compressed_data_.data()), hdr_.compressed_size)); return base::OkStatus(); } PERFETTO_DCHECK(hdr_.compression == kDeflate); GzipDecompressor dec(GzipDecompressor::InputMode::kRawDeflate); - dec.Feed(compressed_data_.get(), hdr_.compressed_size); + dec.Feed(compressed_data_.data(), hdr_.compressed_size); static constexpr size_t kChunkSize = 32768; GzipDecompressor::Result dec_res; @@ -267,9 +399,10 @@ base::Status ZipFile::DecompressLines(LinesCallback callback) const { auto* wptr = reinterpret_cast(line_reader.BeginWrite(kChunkSize)); dec_res = dec.ExtractOutput(wptr, kChunkSize); if (dec_res.ret == ResultCode::kError || - dec_res.ret == ResultCode::kNeedsMoreInput) + dec_res.ret == ResultCode::kNeedsMoreInput) { return base::ErrStatus("zlib decompression error on %s (%d)", name().c_str(), static_cast(dec_res.ret)); + } PERFETTO_DCHECK(dec_res.bytes_written <= kChunkSize); line_reader.EndWrite(dec_res.bytes_written); } while (dec_res.ret == ResultCode::kOk); @@ -278,28 +411,24 @@ base::Status ZipFile::DecompressLines(LinesCallback callback) const { // Common logic for both Decompress() and DecompressLines(). base::Status ZipFile::DoDecompressionChecks() const { - PERFETTO_DCHECK(compressed_data_); - if (hdr_.compression == kNoCompression) { PERFETTO_CHECK(hdr_.compressed_size == hdr_.uncompressed_size); return base::OkStatus(); } - if (hdr_.compression != kDeflate) { return base::ErrStatus("Zip compression mode not supported (%u)", hdr_.compression); } - if (!IsGzipSupported()) { return base::ErrStatus( "Cannot open zip file. Gzip is not enabled in the current build. " "Rebuild with enable_perfetto_zlib=true"); } - return base::OkStatus(); } -// Returns a 64-bit version of time_t, that is, the num seconds since the Epoch. +// Returns a 64-bit version of time_t, that is, the num seconds since the +// Epoch. int64_t ZipFile::GetDatetime() const { // Date: 7 bits year, 4 bits month, 5 bits day. // Time: 5 bits hour, 6 bits minute, 5 bits second. @@ -330,6 +459,4 @@ std::string ZipFile::GetDatetimeStr() const { return buf; } -} // namespace util -} // namespace trace_processor -} // namespace perfetto +} // namespace perfetto::trace_processor::util diff --git a/src/trace_processor/util/zip_reader.h b/src/trace_processor/util/zip_reader.h index 2f34d457ab..f325947428 100644 --- a/src/trace_processor/util/zip_reader.h +++ b/src/trace_processor/util/zip_reader.h @@ -17,15 +17,20 @@ #ifndef SRC_TRACE_PROCESSOR_UTIL_ZIP_READER_H_ #define SRC_TRACE_PROCESSOR_UTIL_ZIP_READER_H_ -#include - +#include +#include #include -#include +#include #include +#include #include #include "perfetto/base/status.h" +#include "perfetto/ext/base/status_or.h" #include "perfetto/ext/base/string_view.h" +#include "perfetto/trace_processor/trace_blob_view.h" +#include "src/trace_processor/util/gzip_utils.h" +#include "src/trace_processor/util/trace_blob_view_reader.h" // ZipReader allows to read Zip files in a streaming fashion. // Key features: @@ -48,9 +53,8 @@ // interesting files (e.g. *.txt) and skip the appending of the other entries. // This would avoid completely the cost of keeping in memory the compressed // payload of unwanted files (e.g. dumpstate.bin in BRs). -namespace perfetto { -namespace trace_processor { -namespace util { + +namespace perfetto::trace_processor::util { class ZipReader; @@ -123,7 +127,7 @@ class ZipFile { }; Header hdr_{}; - std::unique_ptr compressed_data_; + TraceBlobView compressed_data_; // If adding new fields here, remember to update the move operators. }; @@ -144,7 +148,7 @@ class ZipReader { // has been processed. You don't need to get to the end of the zip file to // see all files. The final "central directory" at the end of the file is // actually ignored. - base::Status Parse(const void* data, size_t len); + base::Status Parse(TraceBlobView); // Returns a list of all the files discovered so far. const std::vector& files() const { return files_; } @@ -161,19 +165,32 @@ class ZipReader { // When a compressed file is completely parsed, a ZipFile instance is // constructed and appended to `files_`. struct FileParseState { - uint8_t raw_hdr[kZipFileHdrSize]{}; - size_t raw_hdr_size = 0; // Actual bytes seen for `hdr_`. - std::unique_ptr compressed_data; - size_t compressed_data_written = 0; + enum { + kHeader, + kFilename, + kSkipBytes, + kCompressedData, + } parse_state = kHeader; size_t ignore_bytes_after_fname = 0; + // Used to track the number of bytes fed into the decompressor when we don't + // know the compressed size upfront. + size_t decompressor_bytes_fed = 0; + GzipDecompressor decompressor{GzipDecompressor::InputMode::kRawDeflate}; + std::optional compressed; ZipFile::Header hdr{}; }; + + base::Status TryParseHeader(); + base::Status TryParseFilename(); + base::Status TrySkipBytes(); + base::Status TryParseCompressedData(); + base::StatusOr> TryParseUnsizedCompressedData(); + FileParseState cur_; std::vector files_; + util::TraceBlobViewReader reader_; }; -} // namespace util -} // namespace trace_processor -} // namespace perfetto +} // namespace perfetto::trace_processor::util #endif // SRC_TRACE_PROCESSOR_UTIL_ZIP_READER_H_ diff --git a/src/trace_processor/util/zip_reader_unittest.cc b/src/trace_processor/util/zip_reader_unittest.cc index a73051a598..73c75cc0f0 100644 --- a/src/trace_processor/util/zip_reader_unittest.cc +++ b/src/trace_processor/util/zip_reader_unittest.cc @@ -16,18 +16,23 @@ #include "src/trace_processor/util/zip_reader.h" -#include +#include +#include +#include +#include +#include #include "perfetto/base/build_config.h" -#include "perfetto/ext/base/file_utils.h" -#include "perfetto/ext/base/string_utils.h" - +#include "perfetto/base/status.h" +#include "perfetto/ext/base/string_view.h" +#include "perfetto/trace_processor/trace_blob.h" +#include "perfetto/trace_processor/trace_blob_view.h" +#include "src/base/test/status_matchers.h" #include "test/gtest_and_gmock.h" -namespace perfetto { -namespace trace_processor { -namespace util { +namespace perfetto::trace_processor::util { namespace { +using base::gtest_matchers::IsError; // This zip file contains the following: // Zip file size: 386 bytes, number of entries: 2 @@ -73,7 +78,7 @@ const uint8_t kTestZip[] = { 0xc8, 0x00, 0x00, 0x00, 0x00, 0x00}; std::string vec2str(const std::vector& vec) { - return std::string(reinterpret_cast(vec.data()), vec.size()); + return {reinterpret_cast(vec.data()), vec.size()}; } void ValidateTestZip(ZipReader& zr) { @@ -105,16 +110,15 @@ void ValidateTestZip(ZipReader& zr) { TEST(ZipReaderTest, ValidZip_OneShotParse) { ZipReader zr; - base::Status res = zr.Parse(kTestZip, sizeof(kTestZip)); - ASSERT_TRUE(res.ok()) << res.message(); + ASSERT_OK( + zr.Parse(TraceBlobView(TraceBlob::CopyFrom(kTestZip, sizeof(kTestZip))))); ValidateTestZip(zr); } TEST(ZipReaderTest, ValidZip_OneByteChunks) { ZipReader zr; - for (size_t i = 0; i < sizeof(kTestZip); i++) { - base::Status res = zr.Parse(&kTestZip[i], 1); - ASSERT_TRUE(res.ok()) << res.message(); + for (auto i : kTestZip) { + ASSERT_OK(zr.Parse(TraceBlobView(TraceBlob::CopyFrom(&i, 1)))); } ValidateTestZip(zr); } @@ -124,8 +128,9 @@ TEST(ZipReaderTest, MalformedZip_InvalidSignature) { uint8_t content[sizeof(kTestZip)]; memcpy(content, kTestZip, sizeof(kTestZip)); content[0] = 0xff; // Invalid signature - base::Status res = zr.Parse(content, sizeof(kTestZip)); - ASSERT_FALSE(res.ok()); + ASSERT_THAT( + zr.Parse(TraceBlobView(TraceBlob::CopyFrom(content, sizeof(kTestZip)))), + IsError()); ASSERT_EQ(zr.files().size(), 0u); } @@ -134,21 +139,22 @@ TEST(ZipReaderTest, MalformedZip_VersionTooHigh) { uint8_t content[sizeof(kTestZip)]; memcpy(content, kTestZip, sizeof(kTestZip)); content[5] = 9; // Version: 9.0 - base::Status res = zr.Parse(content, sizeof(kTestZip)); - ASSERT_FALSE(res.ok()); + ASSERT_THAT( + zr.Parse(TraceBlobView(TraceBlob::CopyFrom(content, sizeof(kTestZip)))), + IsError()); ASSERT_EQ(zr.files().size(), 0u); } TEST(ZipReaderTest, TruncatedZip) { ZipReader zr; - base::Status res = zr.Parse(kTestZip, 40); + ASSERT_OK(zr.Parse(TraceBlobView(TraceBlob::CopyFrom(kTestZip, 40)))); ASSERT_EQ(zr.files().size(), 0u); } TEST(ZipReaderTest, Find) { ZipReader zr; - base::Status res = zr.Parse(kTestZip, sizeof(kTestZip)); - ASSERT_TRUE(res.ok()) << res.message(); + ASSERT_OK( + zr.Parse(TraceBlobView(TraceBlob::CopyFrom(kTestZip, sizeof(kTestZip))))); ASSERT_EQ(zr.Find("stored_file")->name(), "stored_file"); ASSERT_EQ(zr.Find("dir/deflated_file")->name(), "dir/deflated_file"); ASSERT_EQ(nullptr, zr.Find("stored_f")); @@ -161,8 +167,8 @@ TEST(ZipReaderTest, Find) { TEST(ZipReaderTest, ValidZip_DecompressLines) { ZipReader zr; - base::Status res = zr.Parse(kTestZip, sizeof(kTestZip)); - ASSERT_TRUE(res.ok()) << res.message(); + ASSERT_OK( + zr.Parse(TraceBlobView(TraceBlob::CopyFrom(kTestZip, sizeof(kTestZip))))); ValidateTestZip(zr); int num_callbacks = 0; zr.files()[1].DecompressLines( @@ -187,17 +193,15 @@ TEST(ZipReaderTest, MalformedZip_DecomprError) { // bytes later. We start clobbering at offset=150, so the header is intanct // but decompression fails. memset(&content[150], 0, 40); - base::Status res = zr.Parse(content, sizeof(kTestZip)); - ASSERT_TRUE(res.ok()); + ASSERT_OK( + zr.Parse(TraceBlobView(TraceBlob::CopyFrom(content, sizeof(kTestZip))))); ASSERT_EQ(zr.files().size(), 2u); std::vector ignored; - ASSERT_TRUE(zr.files()[0].Decompress(&ignored).ok()); - ASSERT_FALSE(zr.files()[1].Decompress(&ignored).ok()); + ASSERT_OK(zr.files()[0].Decompress(&ignored)); + ASSERT_THAT(zr.files()[1].Decompress(&ignored), IsError()); } #endif // PERFETTO_BUILDFLAG(PERFETTO_ZLIB) } // namespace -} // namespace util -} // namespace trace_processor -} // namespace perfetto +} // namespace perfetto::trace_processor::util diff --git a/src/trace_redaction/BUILD.gn b/src/trace_redaction/BUILD.gn index 45d3bdc877..e9e0737533 100644 --- a/src/trace_redaction/BUILD.gn +++ b/src/trace_redaction/BUILD.gn @@ -28,27 +28,23 @@ executable("trace_redactor") { source_set("trace_redaction") { sources = [ + "broadphase_packet_filter.cc", + "broadphase_packet_filter.h", "collect_frame_cookies.cc", "collect_frame_cookies.h", "collect_system_info.cc", "collect_system_info.h", "collect_timeline_events.cc", "collect_timeline_events.h", - "filter_ftrace_using_allowlist.cc", - "filter_ftrace_using_allowlist.h", - "filter_packet_using_allowlist.cc", - "filter_packet_using_allowlist.h", - "filter_print_events.cc", - "filter_print_events.h", - "filter_sched_waking_events.cc", - "filter_sched_waking_events.h", - "filter_task_rename.cc", - "filter_task_rename.h", + "filtering.cc", + "filtering.h", "find_package_uid.cc", "find_package_uid.h", "frame_cookie.h", - "modify_process_trees.cc", - "modify_process_trees.h", + "merge_threads.cc", + "merge_threads.h", + "modify.cc", + "modify.h", "populate_allow_lists.cc", "populate_allow_lists.h", "process_thread_timeline.cc", @@ -57,30 +53,22 @@ source_set("trace_redaction") { "proto_util.h", "prune_package_list.cc", "prune_package_list.h", - "redact_ftrace_event.cc", - "redact_ftrace_event.h", - "redact_process_free.cc", - "redact_process_free.h", - "redact_sched_switch.cc", - "redact_sched_switch.h", - "redact_task_newtask.cc", - "redact_task_newtask.h", - "remap_scheduling_events.cc", - "remap_scheduling_events.h", - "scrub_ftrace_events.cc", - "scrub_ftrace_events.h", + "redact_ftrace_events.cc", + "redact_ftrace_events.h", + "redact_process_events.cc", + "redact_process_events.h", + "redact_process_trees.cc", + "redact_process_trees.h", + "redact_sched_events.cc", + "redact_sched_events.h", "scrub_process_stats.cc", "scrub_process_stats.h", - "scrub_process_trees.cc", - "scrub_process_trees.h", - "scrub_trace_packet.cc", - "scrub_trace_packet.h", - "suspend_resume.cc", - "suspend_resume.h", "trace_redaction_framework.cc", "trace_redaction_framework.h", "trace_redactor.cc", "trace_redactor.h", + "verify_integrity.cc", + "verify_integrity.h", ] deps = [ "../../gn:default_deps", @@ -88,6 +76,7 @@ source_set("trace_redaction") { "../../include/perfetto/ext/base", "../../include/perfetto/protozero:protozero", "../../include/perfetto/trace_processor:storage", + "../../protos/perfetto/common:zero", "../../protos/perfetto/trace:non_minimal_zero", "../../protos/perfetto/trace/android:zero", "../../protos/perfetto/trace/ftrace:zero", @@ -100,15 +89,16 @@ source_set("trace_redaction") { source_set("integrationtests") { testonly = true sources = [ - "filter_ftrace_using_allowlist_integrationtest.cc", + "boardphase_packet_filter_integrationtest.cc", + "collect_frame_cookies_integrationtest.cc", "filter_sched_waking_events_integrationtest.cc", "filter_task_rename_integrationtest.cc", + "process_thread_timeline_integrationtest.cc", "prune_package_list_integrationtest.cc", - "redact_sched_switch_integrationtest.cc", - "remap_scheduling_events_integrationtest.cc", - "scrub_ftrace_events_integrationtest.cc", + "redact_process_trees_integrationtest.cc", + "redact_sched_events_integrationtest.cc", "scrub_process_stats_integrationtest.cc", - "scrub_process_trees_integrationtest.cc", + "trace_processor_integrationtest.cc", "trace_redaction_integration_fixture.cc", "trace_redaction_integration_fixture.h", ] @@ -117,6 +107,7 @@ source_set("integrationtests") { "../../gn:default_deps", "../../gn:gtest_and_gmock", "../../include/perfetto/ext/base", + "../../include/perfetto/trace_processor:trace_processor", "../../protos/perfetto/trace:non_minimal_cpp", "../../protos/perfetto/trace:non_minimal_zero", "../../protos/perfetto/trace/android:cpp", @@ -130,22 +121,18 @@ source_set("integrationtests") { perfetto_unittest_source_set("unittests") { testonly = true sources = [ + "broadphase_packet_filter_unittest.cc", "collect_frame_cookies_unittest.cc", "collect_system_info_unittest.cc", "collect_timeline_events_unittest.cc", - "filter_ftrace_using_allowlist_unittest.cc", - "filter_packet_using_allowlist_unittest.cc", "filter_sched_waking_events_unittest.cc", - "filter_task_rename_unittest.cc", "find_package_uid_unittest.cc", "process_thread_timeline_unittest.cc", "proto_util_unittest.cc", "prune_package_list_unittest.cc", - "redact_process_free_unittest.cc", - "redact_sched_switch_unittest.cc", - "redact_task_newtask_unittest.cc", - "remap_scheduling_events_unittest.cc", - "suspend_resume_unittest.cc", + "redact_process_events_unittest.cc", + "redact_sched_events_unittest.cc", + "verify_integrity_unittest.cc", ] deps = [ ":trace_redaction", @@ -153,6 +140,7 @@ perfetto_unittest_source_set("unittests") { "../../gn:gtest_and_gmock", "../../include/perfetto/ext/base:base", "../../include/perfetto/protozero:protozero", + "../../protos/perfetto/common:cpp", "../../protos/perfetto/config:cpp", "../../protos/perfetto/config:zero", "../../protos/perfetto/trace:non_minimal_cpp", diff --git a/src/trace_redaction/boardphase_packet_filter_integrationtest.cc b/src/trace_redaction/boardphase_packet_filter_integrationtest.cc new file mode 100644 index 0000000000..b72c84df1d --- /dev/null +++ b/src/trace_redaction/boardphase_packet_filter_integrationtest.cc @@ -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. + */ + +#include + +#include "src/base/test/status_matchers.h" +#include "src/trace_redaction/broadphase_packet_filter.h" +#include "src/trace_redaction/populate_allow_lists.h" +#include "src/trace_redaction/trace_redaction_integration_fixture.h" +#include "test/gtest_and_gmock.h" + +#include "protos/perfetto/trace/trace.pbzero.h" +#include "protos/perfetto/trace/trace_packet.pbzero.h" + +namespace perfetto::trace_redaction { + +class BroadphasePacketFilterIntegrationTest + : public testing::Test, + protected TraceRedactionIntegrationFixure { + protected: + void SetUp() override { + trace_redactor_.emplace_build(); + trace_redactor_.emplace_transform(); + } + + Context::TracePacketMask ScanPacketFields(const std::string& trace) { + protos::pbzero::Trace::Decoder trace_decoder(trace); + + Context::TracePacketMask mask; + + for (auto packet = trace_decoder.packet(); packet; ++packet) { + protozero::ProtoDecoder decoder(*packet); + + for (auto field = decoder.ReadField(); field.valid(); + field = decoder.ReadField()) { + PERFETTO_DCHECK(field.id() < mask.size()); + mask.set(field.id()); + } + } + + return mask; + } + + Context::FtraceEventMask ScanFtraceEventFields(const std::string& buffer) { + protos::pbzero::Trace::Decoder trace(buffer); + + Context::FtraceEventMask mask; + + for (auto packet = trace.packet(); packet; ++packet) { + protos::pbzero::TracePacket::Decoder decoder(*packet); + + if (decoder.has_ftrace_events()) { + mask |= CopyEventFields(decoder.ftrace_events()); + } + } + + return mask; + } + + Context context_; + TraceRedactor trace_redactor_; + + private: + Context::FtraceEventMask CopyEventFields(protozero::ConstBytes bytes) { + protozero::ProtoDecoder decoder(bytes); + + Context::FtraceEventMask mask; + + for (auto field = decoder.ReadField(); field.valid(); + field = decoder.ReadField()) { + PERFETTO_DCHECK(field.id() < mask.size()); + mask.set(field.id()); + } + + return mask; + } +}; + +// To avoid being fragile, this test checks that some included fields passed +// through redaction and checks that no excluded fields passed through +// redaction. +TEST_F(BroadphasePacketFilterIntegrationTest, OnlyKeepsIncludedPacketFields) { + ASSERT_OK(Redact(trace_redactor_, &context_)); + + auto trace = LoadRedacted(); + ASSERT_OK(trace); + + auto include_mask = context_.packet_mask; + auto exclude_mask = ~include_mask; + + auto fields = ScanPacketFields(*trace); + + ASSERT_TRUE(fields.any()); + ASSERT_TRUE(include_mask.any()); + + ASSERT_TRUE((fields & include_mask).any()); + ASSERT_FALSE((fields & exclude_mask).any()); +} + +// To avoid being fragile, this test checks that some included fields passed +// through redaction and checks that no excluded fields passed through +// redaction. +TEST_F(BroadphasePacketFilterIntegrationTest, + OnlyKeepsIncludedFtraceEventFields) { + ASSERT_OK(Redact(trace_redactor_, &context_)); + + auto trace = LoadRedacted(); + ASSERT_OK(trace); + + auto include_mask = context_.ftrace_mask; + auto exclude_mask = ~include_mask; + + auto fields = ScanFtraceEventFields(*trace); + + ASSERT_TRUE(fields.any()); + ASSERT_TRUE(include_mask.any()); + + ASSERT_TRUE((fields & include_mask).any()); + ASSERT_FALSE((fields & exclude_mask).any()); +} + +} // namespace perfetto::trace_redaction diff --git a/src/trace_redaction/broadphase_packet_filter.cc b/src/trace_redaction/broadphase_packet_filter.cc new file mode 100644 index 0000000000..7b88a40300 --- /dev/null +++ b/src/trace_redaction/broadphase_packet_filter.cc @@ -0,0 +1,104 @@ +/* + * 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/broadphase_packet_filter.h" + +#include "perfetto/base/status.h" +#include "perfetto/protozero/scattered_heap_buffer.h" +#include "src/trace_redaction/proto_util.h" +#include "src/trace_redaction/trace_redaction_framework.h" + +#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h" +#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h" + +namespace perfetto::trace_redaction { + +base::Status BroadphasePacketFilter::Transform(const Context& context, + std::string* packet) const { + if (context.packet_mask.none()) { + return base::ErrStatus("FilterTracePacketFields: empty packet mask."); + } + + if (context.ftrace_mask.none()) { + return base::ErrStatus("FilterTracePacketFields: empty ftrace mask."); + } + + if (!packet || packet->empty()) { + return base::ErrStatus("FilterTracePacketFields: missing packet."); + } + + protozero::HeapBuffered message; + + protozero::ProtoDecoder decoder(*packet); + + const auto& mask = context.packet_mask; + + for (auto field = decoder.ReadField(); field.valid(); + field = decoder.ReadField()) { + // Make sure the id can be references. If it is out of bounds, it is by + // definition "no set". + if (field.id() < mask.size() && mask.test(field.id())) { + if (field.id() == protos::pbzero::TracePacket::kFtraceEventsFieldNumber) { + OnFtraceEvents(context, field.as_bytes(), message->set_ftrace_events()); + } else { + proto_util::AppendField(field, message.get()); + } + } + } + + packet->assign(message.SerializeAsString()); + return base::OkStatus(); +} + +void BroadphasePacketFilter::OnFtraceEvents( + const Context& context, + protozero::ConstBytes bytes, + protos::pbzero::FtraceEventBundle* message) const { + PERFETTO_DCHECK(message); + + protozero::ProtoDecoder decoder(bytes); + + for (auto field = decoder.ReadField(); field.valid(); + field = decoder.ReadField()) { + if (field.id() == protos::pbzero::FtraceEventBundle::kEventFieldNumber) { + OnFtraceEvent(context, field.as_bytes(), message->add_event()); + } else { + proto_util::AppendField(field, message); + } + } +} + +void BroadphasePacketFilter::OnFtraceEvent( + const Context& context, + protozero::ConstBytes bytes, + protos::pbzero::FtraceEvent* message) const { + PERFETTO_DCHECK(message); + + protozero::ProtoDecoder decoder(bytes); + + const auto& mask = context.ftrace_mask; + + for (auto field = decoder.ReadField(); field.valid(); + field = decoder.ReadField()) { + // Make sure the id can be references. If it is out of bounds, it is by + // definition "no set". + if (field.id() < mask.size() && mask.test(field.id())) { + proto_util::AppendField(field, message); + } + } +} + +} // namespace perfetto::trace_redaction diff --git a/src/trace_redaction/broadphase_packet_filter.h b/src/trace_redaction/broadphase_packet_filter.h new file mode 100644 index 0000000000..68f18e35f3 --- /dev/null +++ b/src/trace_redaction/broadphase_packet_filter.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SRC_TRACE_REDACTION_BROADPHASE_PACKET_FILTER_H_ +#define SRC_TRACE_REDACTION_BROADPHASE_PACKET_FILTER_H_ + +#include "src/trace_redaction/trace_redaction_framework.h" + +#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h" +#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h" + +namespace perfetto::trace_redaction { + +// Execute a broad-phase filter here and defer a narrow-phase filter via other +// primitives. +// +// The concepts of broad-phase and narrow-phase are borrowed from the graphics +// space where a cheap operations removes large chunks of information +// (broad-phase) so that less information goes through the more operations +// (narrow-phase). +// +// Here, the broad-phase operation is a filter that removes high-level fields +// from trace packets so that other primitives (narrow-phase operations) have +// fewer fields to read and write. +class BroadphasePacketFilter : public TransformPrimitive { + public: + base::Status Transform(const Context& context, + std::string* packet) const override; + + private: + void OnFtraceEvents(const Context& context, + protozero::ConstBytes bytes, + protos::pbzero::FtraceEventBundle* message) const; + + void OnFtraceEvent(const Context& context, + protozero::ConstBytes bytes, + protos::pbzero::FtraceEvent* message) const; +}; + +} // namespace perfetto::trace_redaction + +#endif // SRC_TRACE_REDACTION_BROADPHASE_PACKET_FILTER_H_ diff --git a/src/trace_redaction/broadphase_packet_filter_unittest.cc b/src/trace_redaction/broadphase_packet_filter_unittest.cc new file mode 100644 index 0000000000..7dfb7a770a --- /dev/null +++ b/src/trace_redaction/broadphase_packet_filter_unittest.cc @@ -0,0 +1,207 @@ +/* + * 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/broadphase_packet_filter.h" +#include "protos/perfetto/trace/ftrace/ftrace.gen.h" +#include "protos/perfetto/trace/ftrace/ftrace_event.gen.h" +#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.gen.h" +#include "protos/perfetto/trace/trace_packet.gen.h" +#include "src/base/test/status_matchers.h" +#include "src/trace_redaction/trace_redaction_framework.h" +#include "test/gtest_and_gmock.h" + +#include "protos/perfetto/trace/trace_packet.pbzero.h" + +namespace perfetto::trace_redaction { + +class BroadphasePacketFilterTest : public testing::Test { + protected: + BroadphasePacketFilter transform_; + Context context_; + protos::gen::TracePacket builder_; +}; + +TEST_F(BroadphasePacketFilterTest, ReturnErrorForEmptyMasks) { + auto buffer = builder_.SerializeAsString(); + ASSERT_FALSE(transform_.Transform(context_, &buffer).ok()); +} + +TEST_F(BroadphasePacketFilterTest, ReturnErrorForEmptyPacketMask) { + // Set the ftrace mask to ensure the error is from the packet mask. + context_.ftrace_mask.set(0); + + auto buffer = builder_.SerializeAsString(); + ASSERT_FALSE(transform_.Transform(context_, &buffer).ok()); +} + +TEST_F(BroadphasePacketFilterTest, ReturnErrorForEmptyFtraceMask) { + // Set the ftrace mask to ensure the error is from the ftrace mask. + context_.packet_mask.set(0); + + auto buffer = builder_.SerializeAsString(); + ASSERT_FALSE(transform_.Transform(context_, &buffer).ok()); +} + +TEST_F(BroadphasePacketFilterTest, ReturnErrorForNullPacket) { + // Set the masks to ensure the error is from the ftrace mask. + context_.ftrace_mask.set(0); + context_.packet_mask.set(0); + + ASSERT_FALSE(transform_.Transform(context_, nullptr).ok()); +} + +TEST_F(BroadphasePacketFilterTest, ReturnErrorForEmptyPacket) { + // Set the masks to ensure the error is from the ftrace mask. + context_.ftrace_mask.set(0); + context_.packet_mask.set(0); + + std::string buffer; + ASSERT_FALSE(transform_.Transform(context_, &buffer).ok()); +} + +TEST_F(BroadphasePacketFilterTest, DropsPacketField) { + constexpr uint64_t kTime = 1000; + + builder_.set_timestamp(kTime); + auto buffer = builder_.SerializeAsString(); + + // Both masks need some bit set. + context_.ftrace_mask.set(0); + context_.packet_mask.set(0); + + ASSERT_OK(transform_.Transform(context_, &buffer)); + + protos::gen::TracePacket packet; + ASSERT_TRUE(packet.ParseFromString(buffer)); + + // The timestamp field should have been dropped. + ASSERT_FALSE(packet.has_timestamp()); +} + +TEST_F(BroadphasePacketFilterTest, KeepsPacketField) { + constexpr uint64_t kTime = 1000; + + builder_.set_timestamp(kTime); + auto buffer = builder_.SerializeAsString(); + + // Both masks need some bit set. + context_.ftrace_mask.set(0); + context_.packet_mask.set(protos::pbzero::TracePacket::kTimestampFieldNumber); + + ASSERT_OK(transform_.Transform(context_, &buffer)); + + protos::gen::TracePacket packet; + ASSERT_TRUE(packet.ParseFromString(buffer)); + + ASSERT_TRUE(packet.has_timestamp()); + ASSERT_EQ(packet.timestamp(), kTime); +} + +TEST_F(BroadphasePacketFilterTest, DropsAllFtraceEvents) { + constexpr uint64_t kTime = 1000; + + builder_.mutable_ftrace_events()->add_event()->set_timestamp(kTime); + auto buffer = builder_.SerializeAsString(); + + // Both masks need some bit set. + context_.ftrace_mask.set(0); + context_.packet_mask.set(0); + + ASSERT_OK(transform_.Transform(context_, &buffer)); + + protos::gen::TracePacket packet; + ASSERT_TRUE(packet.ParseFromString(buffer)); + + // Because kEvents was not set, all ftrace events will be dropped. + ASSERT_FALSE(packet.has_ftrace_events()); +} + +TEST_F(BroadphasePacketFilterTest, KeepFtraceEvents) { + constexpr uint64_t kTime = 1000; + constexpr int32_t kCpu = 3; + + builder_.mutable_ftrace_events()->add_event()->set_timestamp(kTime); + builder_.mutable_ftrace_events()->set_cpu(kCpu); + + auto buffer = builder_.SerializeAsString(); + + // Both masks need some bit set. + context_.ftrace_mask.set(0); + context_.packet_mask.set( + protos::pbzero::TracePacket::kFtraceEventsFieldNumber); + + ASSERT_OK(transform_.Transform(context_, &buffer)); + + protos::gen::TracePacket packet; + ASSERT_TRUE(packet.ParseFromString(buffer)); + + // The bundle will be kept. Ignoring the events, the other fields should be + // copied over. To be simple, we're only checking one field (CPU). + ASSERT_TRUE(packet.has_ftrace_events()); + ASSERT_TRUE(packet.ftrace_events().has_cpu()); +} + +TEST_F(BroadphasePacketFilterTest, KeepsFtraceEvent) { + constexpr uint64_t kTime = 1000; + + auto* event = builder_.mutable_ftrace_events()->add_event(); + event->set_timestamp(kTime); + event->mutable_print()->set_buf("hello world"); + auto buffer = builder_.SerializeAsString(); + + // Both masks need some bit set. + context_.ftrace_mask.set(protos::pbzero::FtraceEvent::kPrintFieldNumber); + context_.packet_mask.set( + protos::pbzero::TracePacket::kFtraceEventsFieldNumber); + + ASSERT_OK(transform_.Transform(context_, &buffer)); + + protos::gen::TracePacket packet; + ASSERT_TRUE(packet.ParseFromString(buffer)); + + // kFtraceEvents must be in the packet mask in order for the ftrace events to + // be searched. + ASSERT_TRUE(packet.has_ftrace_events()); + ASSERT_EQ(packet.ftrace_events().event().size(), 1u); + ASSERT_TRUE(packet.ftrace_events().event().at(0).has_print()); +} + +TEST_F(BroadphasePacketFilterTest, DropsFtraceEvent) { + constexpr uint64_t kTime = 1000; + + auto* event = builder_.mutable_ftrace_events()->add_event(); + event->set_timestamp(kTime); + event->mutable_print()->set_buf("hello world"); + auto buffer = builder_.SerializeAsString(); + + // Both masks need some bit set. + context_.ftrace_mask.set(0); + context_.packet_mask.set( + protos::pbzero::TracePacket::kFtraceEventsFieldNumber); + + ASSERT_OK(transform_.Transform(context_, &buffer)); + + protos::gen::TracePacket packet; + ASSERT_TRUE(packet.ParseFromString(buffer)); + + // The ftrace events bundle will be copied. All the ftrace events will be + // copied, but the tasks in the events (e.g. print) will be removed. + ASSERT_TRUE(packet.has_ftrace_events()); + ASSERT_EQ(packet.ftrace_events().event().size(), 1u); + ASSERT_FALSE(packet.ftrace_events().event().at(0).has_print()); +} + +} // namespace perfetto::trace_redaction diff --git a/src/trace_redaction/collect_frame_cookies.cc b/src/trace_redaction/collect_frame_cookies.cc index 04c5eb3c56..aa642a0c9f 100644 --- a/src/trace_redaction/collect_frame_cookies.cc +++ b/src/trace_redaction/collect_frame_cookies.cc @@ -19,6 +19,8 @@ #include "perfetto/base/status.h" #include "perfetto/protozero/field.h" #include "perfetto/protozero/proto_decoder.h" +#include "perfetto/protozero/scattered_heap_buffer.h" +#include "src/trace_redaction/proto_util.h" #include "src/trace_redaction/trace_redaction_framework.h" #include "protos/perfetto/trace/android/frame_timeline_event.pbzero.h" @@ -160,17 +162,44 @@ base::Status ReduceFrameCookies::Build(Context* context) const { return base::OkStatus(); } +base::Status FilterFrameEvents::Transform(const Context& context, + std::string* packet) const { + if (packet == nullptr || packet->empty()) { + return base::ErrStatus("FilterFrameEvents: null or empty packet."); + } + + protozero::ProtoDecoder decoder(*packet); + + if (!decoder + .FindField( + protos::pbzero::TracePacket::kFrameTimelineEventFieldNumber) + .valid()) { + return base::OkStatus(); + } + + protozero::HeapBuffered message; + + for (auto field = decoder.ReadField(); field.valid(); + field = decoder.ReadField()) { + if (field.id() == + protos::pbzero::TracePacket::kFrameTimelineEventFieldNumber) { + if (KeepField(context, field)) { + proto_util::AppendField(field, message.get()); + } + + } else { + proto_util::AppendField(field, message.get()); + } + } + + packet->assign(message.SerializeAsString()); + 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; - } + PERFETTO_DCHECK(field.id() == + protos::pbzero::TracePacket::kFrameTimelineEventFieldNumber); protozero::ProtoDecoder timeline_event_decoder(field.as_bytes()); diff --git a/src/trace_redaction/collect_frame_cookies.h b/src/trace_redaction/collect_frame_cookies.h index 7463ff61cb..ec13b93f6f 100644 --- a/src/trace_redaction/collect_frame_cookies.h +++ b/src/trace_redaction/collect_frame_cookies.h @@ -18,7 +18,6 @@ #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" @@ -47,12 +46,13 @@ class ReduceFrameCookies : public BuildPrimitive { 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 { +class FilterFrameEvents : public TransformPrimitive { public: - bool KeepField(const Context& context, - const protozero::Field& field) const override; + base::Status Transform(const Context& context, + std::string* packet) const override; + + private: + bool KeepField(const Context& context, const protozero::Field& field) const; }; } // namespace perfetto::trace_redaction diff --git a/src/trace_redaction/collect_frame_cookies_integrationtest.cc b/src/trace_redaction/collect_frame_cookies_integrationtest.cc new file mode 100644 index 0000000000..577ecc70c3 --- /dev/null +++ b/src/trace_redaction/collect_frame_cookies_integrationtest.cc @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "perfetto/trace_processor/trace_processor.h" +#include "src/base/test/status_matchers.h" +#include "src/trace_redaction/trace_redaction_integration_fixture.h" +#include "test/gtest_and_gmock.h" + +namespace perfetto::trace_redaction { +namespace { +constexpr auto kTrace = "test/data/trace-redaction-api-capture.pftrace"; + +constexpr auto kPackageName = "com.prefabulated.touchlatency"; +constexpr auto kPid = 4524; +} // namespace + +class CollectFrameCookiesIntegrationTest + : public testing::Test, + protected TraceRedactionIntegrationFixure { + protected: + void SetUp() override { + SetSourceTrace(kTrace); + + trace_processor::Config tp_config; + trace_processor_ = + trace_processor::TraceProcessor::CreateInstance(tp_config); + + TraceRedactor::Config tr_config; + auto trace_redactor = TraceRedactor::CreateInstance(tr_config); + + Context context; + context.package_name = kPackageName; + + ASSERT_OK(Redact(*trace_redactor, &context)); + + auto raw = LoadRedacted(); + ASSERT_OK(raw); + + auto read_buffer = std::make_unique(raw->size()); + memcpy(read_buffer.get(), raw->data(), raw->size()); + + ASSERT_OK(trace_processor_->Parse(std::move(read_buffer), raw->size())); + trace_processor_->NotifyEndOfFile(); + } + + std::unique_ptr trace_processor_; +}; + +TEST_F(CollectFrameCookiesIntegrationTest, OnlyRetainsTargetActualFrames) { + auto query = + " SELECT pid" + " FROM process" + " WHERE upid IN (" + " SELECT DISTINCT upid FROM actual_frame_timeline_slice)"; + + auto rows = trace_processor_->ExecuteQuery(query); + + ASSERT_TRUE(rows.Next()); + ASSERT_EQ(rows.Get(0).AsLong(), kPid); + + ASSERT_FALSE(rows.Next()); + ASSERT_OK(rows.Status()); +} + +TEST_F(CollectFrameCookiesIntegrationTest, OnlyRetainsTargetExpectedFrames) { + auto query = + " SELECT pid" + " FROM process" + " WHERE upid IN (" + " SELECT DISTINCT upid FROM expected_frame_timeline_slice)"; + + auto row = trace_processor_->ExecuteQuery(query); + + ASSERT_TRUE(row.Next()); + ASSERT_EQ(row.Get(0).AsLong(), kPid); + + ASSERT_FALSE(row.Next()); + ASSERT_OK(row.Status()); +} + +// The target package has two overlapping timelines. So both tracks should exist +// under one pid. +TEST_F(CollectFrameCookiesIntegrationTest, + RetainsOverlappingExpectedFrameEvents) { + auto query = + " SELECT DISTINCT track_id, pid" + " FROM expected_frame_timeline_slice" + " JOIN process USING (upid)"; + + auto rows = trace_processor_->ExecuteQuery(query); + + ASSERT_TRUE(rows.Next()); + ASSERT_EQ(rows.Get(1).AsLong(), kPid); + + ASSERT_TRUE(rows.Next()); + ASSERT_EQ(rows.Get(1).AsLong(), kPid); + + ASSERT_FALSE(rows.Next()); + ASSERT_OK(rows.Status()); +} + +} // namespace perfetto::trace_redaction diff --git a/src/trace_redaction/collect_frame_cookies_unittest.cc b/src/trace_redaction/collect_frame_cookies_unittest.cc index 6e77231de1..dc6d8fd4f9 100644 --- a/src/trace_redaction/collect_frame_cookies_unittest.cc +++ b/src/trace_redaction/collect_frame_cookies_unittest.cc @@ -16,25 +16,27 @@ */ #include "src/trace_redaction/collect_frame_cookies.h" +#include "perfetto/ext/base/status_or.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 kTimeStep = 1000; + 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 uint64_t kTimestampB = kTimeStep; +constexpr uint64_t kTimestampC = kTimeStep * 2; +constexpr uint64_t kTimestampD = kTimeStep * 3; +constexpr uint64_t kTimestampE = kTimeStep * 4; constexpr int64_t kCookieA = 1234; +constexpr int64_t kCookieB = 2345; // Start at 1, amd not zero, because zero hnas special meaning (system uid). constexpr uint64_t kUidA = 1; @@ -42,169 +44,173 @@ constexpr uint64_t kUidA = 1; constexpr int32_t kPidNone = 10; constexpr int32_t kPidA = 11; -} // namespace +enum class FrameCookieType { + ExpectedSurface, + ExpectedDisplay, + ActualSurface, + ActualDisplay, +}; -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; - } +protos::gen::TracePacket CreateExpectedSurfaceFrameStart(uint64_t ts, + int32_t pid, + int64_t cookie) { + protos::gen::TracePacket packet; + packet.set_timestamp(ts); + auto* start = packet.mutable_frame_timeline_event() + ->mutable_expected_surface_frame_start(); + start->set_cookie(cookie); + start->set_pid(pid); + return packet; +} - return packet.SerializeAsString(); - } +protos::gen::TracePacket CreateActualSurfaceFrameStart(uint64_t ts, + int32_t pid, + int64_t cookie) { + protos::gen::TracePacket packet; + packet.set_timestamp(ts); + auto* start = packet.mutable_frame_timeline_event() + ->mutable_actual_surface_frame_start(); + start->set_cookie(cookie); + start->set_pid(pid); + + return packet; +} - std::string CreateFrameEnd(uint64_t ts, int64_t cookie) const { - protos::gen::TracePacket packet; - packet.set_timestamp(ts); +protos::gen::TracePacket CreateExpectedDisplayFrameStart(uint64_t ts, + int32_t pid, + int64_t cookie) { + protos::gen::TracePacket packet; + packet.set_timestamp(ts); + auto* start = packet.mutable_frame_timeline_event() + ->mutable_expected_display_frame_start(); + start->set_cookie(cookie); + start->set_pid(pid); + + return packet; +} - auto* start = packet.mutable_frame_timeline_event()->mutable_frame_end(); - start->set_cookie(cookie); +protos::gen::TracePacket CreateActualDisplayFrameStart(uint64_t ts, + int32_t pid, + int64_t cookie) { + protos::gen::TracePacket packet; + packet.set_timestamp(ts); + auto* start = packet.mutable_frame_timeline_event() + ->mutable_actual_display_frame_start(); + start->set_cookie(cookie); + start->set_pid(pid); + + return packet; +} - return packet.SerializeAsString(); - } +protos::gen::TracePacket CreateFrameEnd(uint64_t ts, int64_t cookie) { + protos::gen::TracePacket packet; + packet.set_timestamp(ts); - void CollectEvents(std::initializer_list events, - Context* context) const { - CollectTimelineEvents collect; - ASSERT_OK(collect.Begin(context)); + auto* start = packet.mutable_frame_timeline_event()->mutable_frame_end(); + start->set_cookie(cookie); - for (const auto& event : events) { - context->timeline->Append(event); - } + return packet; +} - ASSERT_OK(collect.End(context)); +protos::gen::TracePacket CreateStartPacket(FrameCookieType type, + uint64_t ts, + int32_t pid, + int64_t cookie) { + switch (type) { + case FrameCookieType::ExpectedSurface: + return CreateExpectedSurfaceFrameStart(ts, pid, cookie); + case FrameCookieType::ExpectedDisplay: + return CreateExpectedDisplayFrameStart(ts, pid, cookie); + case FrameCookieType::ActualSurface: + return CreateActualSurfaceFrameStart(ts, pid, cookie); + case FrameCookieType::ActualDisplay: + return CreateActualDisplayFrameStart(ts, pid, cookie); } - void CollectCookies(std::initializer_list packets, - Context* context) const { - CollectFrameCookies collect; - ASSERT_OK(collect.Begin(context)); + PERFETTO_FATAL("Unhandled case. This should never happen."); +} - for (const auto& packet : packets) { - protos::pbzero::TracePacket::Decoder decoder(packet); - ASSERT_OK(collect.Collect(decoder, context)); - } +void CollectCookies(const std::string& packet, Context* context) { + protos::pbzero::TracePacket::Decoder decoder(packet); - ASSERT_OK(collect.End(context)); - } + CollectFrameCookies collect_; + ASSERT_OK(collect_.Begin(context)); + 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); - } +class FrameCookieTest : public testing::Test { + protected: + CollectFrameCookies collect_; + Context context_; +}; - 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); - } +TEST_F(FrameCookieTest, ExtractsExpectedSurfaceFrameStart) { + auto packet = CreateExpectedSurfaceFrameStart(kTimestampA, kPidA, kCookieA); + auto bytes = packet.SerializeAsString(); + CollectCookies(bytes, &context_); - 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); - } + ASSERT_EQ(context_.global_frame_cookies.size(), 1u); - 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); - } -}; + const auto& cookie = context_.global_frame_cookies.back(); + ASSERT_EQ(cookie.cookie, kCookieA); + ASSERT_EQ(cookie.pid, kPidA); + ASSERT_EQ(cookie.ts, kTimestampA); +} -class CollectFrameCookiesTest : public testing::Test, - protected FrameCookieFixture, - public testing::WithParamInterface { - protected: - Context context_; -}; +TEST_F(FrameCookieTest, ExtractsActualSurfaceFrameStart) { + auto packet = CreateActualSurfaceFrameStart(kTimestampA, kPidA, kCookieA); + auto bytes = packet.SerializeAsString(); + CollectCookies(bytes, &context_); -TEST_P(CollectFrameCookiesTest, ExtractsExpectedSurfaceFrameStart) { - auto field_id = GetParam(); + ASSERT_EQ(context_.global_frame_cookies.size(), 1u); - auto packet = CreateStartEvent(field_id, kTimestampA, kPidA, kCookieA); + const auto& cookie = context_.global_frame_cookies.back(); + ASSERT_EQ(cookie.cookie, kCookieA); + ASSERT_EQ(cookie.pid, kPidA); + ASSERT_EQ(cookie.ts, kTimestampA); +} - CollectCookies({packet}, &context_); +TEST_F(FrameCookieTest, ExtractsExpectedDisplayFrameStart) { + auto packet = CreateExpectedDisplayFrameStart(kTimestampA, kPidA, kCookieA); + auto bytes = packet.SerializeAsString(); + CollectCookies(bytes, &context_); ASSERT_EQ(context_.global_frame_cookies.size(), 1u); - auto& cookie = context_.global_frame_cookies.back(); + const 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)); +TEST_F(FrameCookieTest, ExtractsActualDisplayFrameStart) { + auto packet = CreateActualDisplayFrameStart(kTimestampA, kPidA, kCookieA); + auto bytes = packet.SerializeAsString(); + CollectCookies(bytes, &context_); + + ASSERT_EQ(context_.global_frame_cookies.size(), 1u); + + const auto& cookie = context_.global_frame_cookies.back(); + ASSERT_EQ(cookie.cookie, kCookieA); + ASSERT_EQ(cookie.pid, kPidA); + ASSERT_EQ(cookie.ts, kTimestampA); +} // 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_); +TEST_F(FrameCookieTest, IgnoresFrameEnd) { + auto packet = CreateFrameEnd(kTimestampA, kCookieA); + auto bytes = packet.SerializeAsString(); + CollectCookies(bytes, &context_); ASSERT_TRUE(context_.global_frame_cookies.empty()); } -class ReduceFrameCookiesTest : public testing::Test, - protected FrameCookieFixture, - public testing::WithParamInterface { +class ReduceFrameCookiesTest + : public testing::Test, + public testing::WithParamInterface { protected: void SetUp() { context_.package_uid = kUidA; @@ -216,131 +222,196 @@ class ReduceFrameCookiesTest : public testing::Test, // 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_); + context_.timeline = std::make_unique(); + context_.timeline->Append(ProcessThreadTimeline::Event::Open( + kTimestampB, kPidA, kPidNone, kUidA)); + context_.timeline->Append( + ProcessThreadTimeline::Event::Close(kTimestampD, kPidA)); + context_.timeline->Sort(); } 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_); +TEST_P(ReduceFrameCookiesTest, RejectBeforeStart) { + auto packet = CreateStartPacket(GetParam(), kTimestampA, kPidA, kCookieA); + auto bytes = packet.SerializeAsString(); + CollectCookies(bytes, &context_); ASSERT_OK(reduce_.Build(&context_)); ASSERT_FALSE(context_.package_frame_cookies.count(kCookieA)); } -TEST_P(ReduceFrameCookiesTest, AcceptDuringActive) { - auto field_id = GetParam(); +TEST_P(ReduceFrameCookiesTest, AcceptAtStart) { + auto packet = CreateStartPacket(GetParam(), kTimestampB, kPidA, kCookieA); + auto bytes = packet.SerializeAsString(); + CollectCookies(bytes, &context_); - // kTimestampC is between pid starts and ends. - auto packet = CreateStartEvent(field_id, kTimestampC, kPidA, kCookieA); + ASSERT_OK(reduce_.Build(&context_)); + ASSERT_TRUE(context_.package_frame_cookies.count(kCookieA)); +} - CollectCookies({packet}, &context_); +TEST_P(ReduceFrameCookiesTest, AcceptBetweenStartAndEnd) { + auto packet = CreateStartPacket(GetParam(), kTimestampC, kPidA, kCookieA); + auto bytes = packet.SerializeAsString(); + CollectCookies(bytes, &context_); ASSERT_OK(reduce_.Build(&context_)); ASSERT_TRUE(context_.package_frame_cookies.count(kCookieA)); } -TEST_P(ReduceFrameCookiesTest, RejectAfterActive) { - auto field_id = GetParam(); +TEST_P(ReduceFrameCookiesTest, AcceptAtEnd) { + auto packet = CreateStartPacket(GetParam(), kTimestampD, kPidA, kCookieA); + auto bytes = packet.SerializeAsString(); + CollectCookies(bytes, &context_); - // kTimestampE is after pid ends. - auto packet = CreateStartEvent(field_id, kTimestampE, kPidA, kCookieA); + ASSERT_OK(reduce_.Build(&context_)); + ASSERT_TRUE(context_.package_frame_cookies.count(kCookieA)); +} - CollectCookies({packet}, &context_); +TEST_P(ReduceFrameCookiesTest, RejectAfterEnd) { + auto packet = CreateStartPacket(GetParam(), kTimestampE, kPidA, kCookieA); + auto bytes = packet.SerializeAsString(); + CollectCookies(bytes, &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 { - protected: - protozero::Field ExtractTimelineEvent(const std::string& packet) const { - protozero::ProtoDecoder packet_decoder(packet); +INSTANTIATE_TEST_SUITE_P(Default, + ReduceFrameCookiesTest, + testing::Values(FrameCookieType::ExpectedSurface, + FrameCookieType::ExpectedDisplay, + FrameCookieType::ActualSurface, + FrameCookieType::ActualDisplay)); - // 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); - } +class TransformStartCookiesTest + : public testing::Test, + public testing::WithParamInterface { + protected: + void SetUp() { context_.package_frame_cookies.insert(kCookieA); } 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); +TEST_P(TransformStartCookiesTest, RetainStartEvent) { + auto packet = CreateStartPacket(GetParam(), kTimestampE, kPidA, kCookieA); + auto bytes = packet.SerializeAsString(); + + ASSERT_OK(filter_.Transform(context_, &bytes)); + + protos::gen::TracePacket redacted; + ASSERT_TRUE(redacted.ParseFromString(bytes)); + + ASSERT_TRUE(redacted.has_frame_timeline_event()); + const auto& timeline = redacted.frame_timeline_event(); + + int64_t cookie; + + // Find the cookie from the packet. + switch (GetParam()) { + case FrameCookieType::ExpectedSurface: { + ASSERT_TRUE(timeline.has_expected_surface_frame_start()); + const auto& start = timeline.expected_surface_frame_start(); + ASSERT_TRUE(start.has_cookie()); + cookie = start.cookie(); + + break; + } + + case FrameCookieType::ExpectedDisplay: { + ASSERT_TRUE(timeline.has_expected_display_frame_start()); + const auto& start = timeline.expected_display_frame_start(); + ASSERT_TRUE(start.has_cookie()); + cookie = start.cookie(); + + break; + } + + case FrameCookieType::ActualSurface: { + ASSERT_TRUE(timeline.has_actual_surface_frame_start()); + const auto& start = timeline.actual_surface_frame_start(); + ASSERT_TRUE(start.has_cookie()); + cookie = start.cookie(); + + break; + } + case FrameCookieType::ActualDisplay: { + ASSERT_TRUE(timeline.has_actual_display_frame_start()); + const auto& start = timeline.actual_display_frame_start(); + ASSERT_TRUE(start.has_cookie()); + cookie = start.cookie(); - auto field_id = GetParam(); - auto packet = CreateStartEvent(field_id, kTimestampA, kPidA, kCookieA); - auto timeline_field = ExtractTimelineEvent(packet); + break; + } + } - ASSERT_TRUE(filter_.KeepField(context_, timeline_field)); + ASSERT_EQ(cookie, kCookieA); } -// 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); +TEST_P(TransformStartCookiesTest, DropStartEvent) { + // Even those this packet is using PidA, because CookieA is not in the package + // coookie pool, the event should be dropped. + auto packet = CreateStartPacket(GetParam(), kTimestampE, kPidA, kCookieB); + auto bytes = packet.SerializeAsString(); - ASSERT_FALSE(filter_.KeepField(context_, timeline_field)); + ASSERT_OK(filter_.Transform(context_, &bytes)); + + protos::gen::TracePacket redacted; + ASSERT_TRUE(redacted.ParseFromString(bytes)); + + ASSERT_FALSE(redacted.has_frame_timeline_event()); } -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); +INSTANTIATE_TEST_SUITE_P(Default, + TransformStartCookiesTest, + testing::Values(FrameCookieType::ExpectedSurface, + FrameCookieType::ExpectedDisplay, + FrameCookieType::ActualSurface, + FrameCookieType::ActualDisplay)); + +class TransformEndFrameCookiesTest : public testing::Test { + protected: + void SetUp() { context_.package_frame_cookies.insert(kCookieA); } + + FilterFrameEvents filter_; + Context context_; +}; +// An end event has no pid. The cookie connects it to a pid. If the start event +// cookie moved from the global pool into the package pool, then end the end +// event should be retained. +TEST_F(TransformStartCookiesTest, Retain) { auto packet = CreateFrameEnd(kTimestampA, kCookieA); - auto timeline_field = ExtractTimelineEvent(packet); + auto bytes = packet.SerializeAsString(); + + ASSERT_OK(filter_.Transform(context_, &bytes)); + + protos::gen::TracePacket redacted; + ASSERT_TRUE(redacted.ParseFromString(bytes)); + + ASSERT_TRUE(redacted.has_frame_timeline_event()); + const auto& timeline = redacted.frame_timeline_event(); - ASSERT_TRUE(filter_.KeepField(context_, timeline_field)); + ASSERT_TRUE(timeline.has_frame_end()); + const auto& end = timeline.frame_end(); + + ASSERT_EQ(end.cookie(), kCookieA); } -TEST_F(FilterCookiesFieldsTest, ExcludeMissingEndCookies) { - auto packet = CreateFrameEnd(kTimestampA, kCookieA); - auto timeline_field = ExtractTimelineEvent(packet); +TEST_F(TransformStartCookiesTest, Drop) { + auto packet = CreateFrameEnd(kTimestampA, kCookieB); + auto bytes = packet.SerializeAsString(); + + ASSERT_OK(filter_.Transform(context_, &bytes)); - ASSERT_FALSE(filter_.KeepField(context_, timeline_field)); + protos::gen::TracePacket redacted; + ASSERT_TRUE(redacted.ParseFromString(bytes)); + + ASSERT_FALSE(redacted.has_frame_timeline_event()); } +} // namespace } // namespace perfetto::trace_redaction diff --git a/src/trace_redaction/collect_system_info.cc b/src/trace_redaction/collect_system_info.cc index 374f510c2e..48f83c62a6 100644 --- a/src/trace_redaction/collect_system_info.cc +++ b/src/trace_redaction/collect_system_info.cc @@ -19,6 +19,7 @@ #include "perfetto/protozero/field.h" #include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h" +#include "protos/perfetto/trace/ps/process_tree.pbzero.h" namespace perfetto::trace_redaction { @@ -37,17 +38,29 @@ base::Status CollectSystemInfo::Collect( Context* context) const { auto* system_info = &context->system_info.value(); - if (!packet.has_ftrace_events()) { - return base::OkStatus(); + PERFETTO_DCHECK(system_info); // See Begin() + + if (packet.has_ftrace_events()) { + return OnFtraceEvents(packet.ftrace_events(), context); } - protozero::ProtoDecoder decoder(packet.ftrace_events()); + return base::OkStatus(); +} - auto field = +base::Status CollectSystemInfo::OnFtraceEvents(protozero::ConstBytes bytes, + Context* context) const { + protozero::ProtoDecoder decoder(bytes); + + auto cpu = decoder.FindField(protos::pbzero::FtraceEventBundle::kCpuFieldNumber); - if (field.valid()) { - system_info->ReserveCpu(field.as_uint32()); + if (!cpu.valid()) { + return base::ErrStatus( + "BuildSyntheticThreads: missing FtraceEventBundle::kCpu."); + } + + if (cpu.valid()) { + context->system_info->ReserveCpu(cpu.as_uint32()); } return base::OkStatus(); @@ -55,26 +68,25 @@ base::Status CollectSystemInfo::Collect( base::Status BuildSyntheticThreads::Build(Context* context) const { if (!context->system_info.has_value()) { - return base::ErrStatus("BuildThreadMap: missing system info."); + return base::ErrStatus("BuildSyntheticThreads: missing system info."); } - if (context->synthetic_threads.has_value()) { + if (context->synthetic_process) { return base::ErrStatus( - "BuildThreadMap: synthetic threads were already initialized."); + "BuildSyntheticThreads: synthetic threads were already initialized."); } auto& system_info = context->system_info.value(); - auto& synthetic_threads = context->synthetic_threads.emplace(); - auto cpu_count = system_info.last_cpu() + 1; + // Add an extra tid for the main thread. + std::vector tids(system_info.cpu_count() + 1); - synthetic_threads.tgid = system_info.AllocateSynthThread(); - synthetic_threads.tids.resize(cpu_count); - - for (uint32_t cpu = 0; cpu < cpu_count; ++cpu) { - synthetic_threads.tids[cpu] = system_info.AllocateSynthThread(); + for (uint32_t i = 0; i < tids.size(); ++i) { + tids[i] = system_info.AllocateSynthThread(); } + context->synthetic_process = std::make_unique(tids); + return base::OkStatus(); } diff --git a/src/trace_redaction/collect_system_info.h b/src/trace_redaction/collect_system_info.h index 1dec21c3dd..73cc41eeb0 100644 --- a/src/trace_redaction/collect_system_info.h +++ b/src/trace_redaction/collect_system_info.h @@ -29,6 +29,10 @@ class CollectSystemInfo : public CollectPrimitive { base::Status Collect(const protos::pbzero::TracePacket::Decoder&, Context*) const override; + + private: + base::Status OnFtraceEvents(protozero::ConstBytes bytes, + Context* context) const; }; // Condenses system info into a query-focuesed structure, making it possible to diff --git a/src/trace_redaction/collect_system_info_unittest.cc b/src/trace_redaction/collect_system_info_unittest.cc index bf0ced26f0..a64da3107b 100644 --- a/src/trace_redaction/collect_system_info_unittest.cc +++ b/src/trace_redaction/collect_system_info_unittest.cc @@ -63,13 +63,13 @@ TEST_F(CollectSystemInfoTest, UpdatesCpuCountUsingFtraceEvents) { AppendSchedSwitch(9); ASSERT_OK(Collect()); - ASSERT_EQ(context_.system_info->last_cpu(), 7u); + ASSERT_EQ(context_.system_info->cpu_count(), 8u); AppendFtraceEvent(11, 8); AppendSchedSwitch(9); ASSERT_OK(Collect()); - ASSERT_EQ(context_.system_info->last_cpu(), 11u); + ASSERT_EQ(context_.system_info->cpu_count(), 12u); } // The first synth thread pid should be beyond the range of valid pids. @@ -80,7 +80,7 @@ TEST(SystemInfoTest, FirstSynthThreadPidIsNotAValidPid) { ASSERT_GT(pid, 1 << 22); } -TEST(BuildSyntheticThreadsTest, CreatesThreadsPerCpu) { +TEST(BuildSyntheticProcessTest, CreatesThreadsPerCpu) { Context context; context.system_info.emplace(); @@ -90,8 +90,10 @@ TEST(BuildSyntheticThreadsTest, CreatesThreadsPerCpu) { BuildSyntheticThreads build; ASSERT_OK(build.Build(&context)); - ASSERT_NE(context.synthetic_threads->tgid, 0); - ASSERT_EQ(context.synthetic_threads->tids.size(), 8u); + ASSERT_NE(context.synthetic_process->tgid(), 0); + + // One main thread and 1 thread per CPU. + ASSERT_EQ(context.synthetic_process->tids().size(), 9u); } } // namespace perfetto::trace_redaction diff --git a/src/trace_redaction/collect_timeline_events.cc b/src/trace_redaction/collect_timeline_events.cc index 70f92f5878..9c15cc67fc 100644 --- a/src/trace_redaction/collect_timeline_events.cc +++ b/src/trace_redaction/collect_timeline_events.cc @@ -39,11 +39,11 @@ using TaskNewtaskFtraceEvent = protos::pbzero::TaskNewtaskFtraceEvent; void MarkOpen(uint64_t ts, const ProcessTree::Process::Decoder& process, ProcessThreadTimeline* timeline) { - // The uid in the process tree is a int32_t, but in the package list, the uid - // is a uint64_t. auto uid = static_cast(process.uid()); + + // See "trace_redaction_framework.h" for why uid must be normalized. auto e = ProcessThreadTimeline::Event::Open(ts, process.pid(), process.ppid(), - uid); + NormalizeUid(uid)); timeline->Append(e); } diff --git a/src/trace_redaction/collect_timeline_events_unittest.cc b/src/trace_redaction/collect_timeline_events_unittest.cc index 96282ba6be..9b8790b322 100644 --- a/src/trace_redaction/collect_timeline_events_unittest.cc +++ b/src/trace_redaction/collect_timeline_events_unittest.cc @@ -15,13 +15,14 @@ * limitations under the License. */ -#include "src/base/test/status_matchers.h" #include "src/trace_redaction/collect_timeline_events.h" +#include "protos/perfetto/trace/ftrace/sched.gen.h" +#include "src/base/test/status_matchers.h" #include "src/trace_redaction/trace_redaction_framework.h" #include "protos/perfetto/trace/ftrace/ftrace_event.gen.h" #include "protos/perfetto/trace/ftrace/ftrace_event_bundle.gen.h" -#include "protos/perfetto/trace/ftrace/sched.gen.h" +#include "protos/perfetto/trace/ftrace/task.gen.h" #include "protos/perfetto/trace/ps/process_tree.gen.h" #include "protos/perfetto/trace/trace_packet.gen.h" @@ -29,23 +30,13 @@ namespace perfetto::trace_redaction { namespace { -constexpr uint64_t kSystemPackage = 0; -constexpr uint64_t kUnityUid = 10252; - -constexpr int32_t kZygotePid = 1093; -constexpr int32_t kUnityPid = 7105; -constexpr int32_t kUnityTid = 7127; - -// TODO(vaage): Need a better name and documentation. -constexpr int32_t kPidWithNoOpen = 32; - -constexpr uint64_t kProcessTreeTimestamp = 6702093635419927; +constexpr uint64_t kPackage = 0; +constexpr int32_t kPid = 1093; -// These two timestamps are used to separate the packet and event times. A -// packet can have time X, but the time can have time Y. Time Y should be used -// for the events. -constexpr uint64_t kThreadFreePacketTimestamp = 6702094703928940; -constexpr uint64_t kThreadFreeOffset = 100; +constexpr uint64_t kFullStep = 1000; +constexpr uint64_t kTimeA = 0; +constexpr uint64_t kTimeB = kFullStep; +constexpr uint64_t kTimeC = kFullStep * 2; } // namespace @@ -53,167 +44,138 @@ constexpr uint64_t kThreadFreeOffset = 100; // contains trace elements that should create timeline events. class CollectTimelineEventsTest : public testing::Test { protected: - void SetUp() { - CollectTimelineEvents collector; + void SetUp() { ASSERT_OK(collector_.Begin(&context_)); } - ASSERT_OK(collector.Begin(&context_)); - - // Minimum ProcessTree information. - { - auto timestamp = kProcessTreeTimestamp; + Context context_; + CollectTimelineEvents collector_; +}; - protos::gen::TracePacket packet; - packet.set_timestamp(timestamp); +TEST_F(CollectTimelineEventsTest, OpenEventForProcessTreeProcess) { + { + protos::gen::TracePacket packet; + packet.set_timestamp(kTimeA); - auto* process_tree = packet.mutable_process_tree(); + auto* process_tree = packet.mutable_process_tree(); - auto* zygote = process_tree->add_processes(); - zygote->set_pid(kZygotePid); - zygote->set_ppid(1); - zygote->set_uid(kSystemPackage); + auto* process = process_tree->add_processes(); + process->set_pid(kPid); + process->set_ppid(1); + process->set_uid(kPackage); - auto* unity = process_tree->add_processes(); - unity->set_pid(kUnityPid); - unity->set_ppid(1093); - unity->set_uid(kUnityUid); + auto buffer = packet.SerializeAsString(); - auto* thread = process_tree->add_threads(); - thread->set_tid(kUnityTid); - thread->set_tgid(kUnityPid); + protos::pbzero::TracePacket::Decoder decoder(buffer); - process_tree->set_collection_end_timestamp(timestamp); + ASSERT_OK(collector_.Collect(decoder, &context_)); + } - auto buffer = packet.SerializeAsString(); + ASSERT_OK(collector_.End(&context_)); - protos::pbzero::TracePacket::Decoder decoder(buffer); - ASSERT_OK(collector.Collect(decoder, &context_)); - } + const auto* event = context_.timeline->GetOpeningEvent(kTimeA, kPid); + ASSERT_TRUE(event); + ASSERT_TRUE(event->valid()); +} - // Minimum proc free informations. - { - auto timestamp = kThreadFreePacketTimestamp; +TEST_F(CollectTimelineEventsTest, OpenEventForProcessTreeThread) { + { + protos::gen::TracePacket packet; + packet.set_timestamp(kTimeA); - protos::gen::TracePacket packet; - packet.set_timestamp(timestamp); + auto* process_tree = packet.mutable_process_tree(); - auto* ftrace_event = packet.mutable_ftrace_events()->add_event(); - ftrace_event->set_timestamp(timestamp + kThreadFreeOffset); - ftrace_event->set_pid(10); // kernel thread - e.g. "rcuop/0" + auto* process = process_tree->add_threads(); + process->set_tid(kPid); + process->set_tgid(1); - auto* process_free = ftrace_event->mutable_sched_process_free(); - process_free->set_pid(kUnityTid); + auto buffer = packet.SerializeAsString(); - auto buffer = packet.SerializeAsString(); + protos::pbzero::TracePacket::Decoder decoder(buffer); - protos::pbzero::TracePacket::Decoder decoder(buffer); - ASSERT_OK(collector.Collect(decoder, &context_)); - } + ASSERT_OK(collector_.Collect(decoder, &context_)); + } - // Free a pid that neve started. - { - auto timestamp = kThreadFreePacketTimestamp; + ASSERT_OK(collector_.End(&context_)); - protos::gen::TracePacket packet; - packet.set_timestamp(timestamp); + const auto* event = context_.timeline->GetOpeningEvent(kTimeA, kPid); + ASSERT_TRUE(event); + ASSERT_TRUE(event->valid()); +} - auto* ftrace_event = packet.mutable_ftrace_events()->add_event(); - ftrace_event->set_timestamp(timestamp + kThreadFreeOffset); - ftrace_event->set_pid(10); // kernel thread - e.g. "rcuop/0" +TEST_F(CollectTimelineEventsTest, OpenEventForNewTask) { + { + protos::gen::TracePacket packet; + auto* event = packet.mutable_ftrace_events()->add_event(); + event->set_timestamp(kTimeA); - auto* process_free = ftrace_event->mutable_sched_process_free(); - process_free->set_pid(kPidWithNoOpen); + auto* new_task = event->mutable_task_newtask(); + new_task->set_clone_flags(0); + new_task->set_comm(""); + new_task->set_oom_score_adj(0); + new_task->set_pid(kPid); - auto buffer = packet.SerializeAsString(); + auto buffer = packet.SerializeAsString(); - protos::pbzero::TracePacket::Decoder decoder(buffer); - ASSERT_OK(collector.Collect(decoder, &context_)); - } + protos::pbzero::TracePacket::Decoder decoder(buffer); - ASSERT_OK(collector.End(&context_)); + ASSERT_OK(collector_.Collect(decoder, &context_)); } - Context context_; -}; - -class CollectTimelineFindsOpenEventTest - : public CollectTimelineEventsTest, - public testing::WithParamInterface {}; + ASSERT_OK(collector_.End(&context_)); -TEST_P(CollectTimelineFindsOpenEventTest, NoOpenEventBeforeProcessTree) { - auto pid = GetParam(); - - auto event = - context_.timeline->FindPreviousEvent(kProcessTreeTimestamp - 1, pid); - ASSERT_EQ(event.type, ProcessThreadTimeline::Event::Type::kInvalid); + const auto* open_event = context_.timeline->GetOpeningEvent(kTimeA, kPid); + ASSERT_TRUE(open_event); + ASSERT_TRUE(open_event->valid()); } -TEST_P(CollectTimelineFindsOpenEventTest, OpenEventOnProcessTree) { - auto pid = GetParam(); +TEST_F(CollectTimelineEventsTest, ProcFreeEndsThread) { + { + protos::gen::TracePacket packet; - auto event = context_.timeline->FindPreviousEvent(kProcessTreeTimestamp, pid); - ASSERT_EQ(event.type, ProcessThreadTimeline::Event::Type::kOpen); - ASSERT_EQ(event.pid, pid); -} + auto* event = packet.mutable_ftrace_events()->add_event(); + event->set_timestamp(kTimeA); -TEST_P(CollectTimelineFindsOpenEventTest, OpenEventAfterProcessTree) { - auto pid = GetParam(); + auto* new_task = event->mutable_task_newtask(); + new_task->set_clone_flags(0); + new_task->set_comm(""); + new_task->set_oom_score_adj(0); + new_task->set_pid(kPid); - auto event = context_.timeline->FindPreviousEvent(kProcessTreeTimestamp, pid); - ASSERT_EQ(event.type, ProcessThreadTimeline::Event::Type::kOpen); - ASSERT_EQ(event.pid, pid); -} + auto buffer = packet.SerializeAsString(); -INSTANTIATE_TEST_SUITE_P( - SystemProcess, - CollectTimelineFindsOpenEventTest, - testing::Values(kZygotePid, // System-level process/thread - kUnityPid, // Process - kUnityTid // Child thread. kUnityPid is the parent. - )); - -class CollectTimelineFindsFreeEventTest : public CollectTimelineEventsTest {}; + protos::pbzero::TracePacket::Decoder decoder(buffer); + ASSERT_OK(collector_.Collect(decoder, &context_)); + } -TEST_F(CollectTimelineFindsFreeEventTest, UsesFtraceEventTime) { - auto pid = kUnityTid; + { + protos::gen::TracePacket packet; - // While this will be a valid event (type != invalid), it won't be the close - // event. - auto incorrect = - context_.timeline->FindPreviousEvent(kThreadFreePacketTimestamp, pid); - ASSERT_EQ(incorrect.type, ProcessThreadTimeline::Event::Type::kOpen); + auto* event = packet.mutable_ftrace_events()->add_event(); + event->set_timestamp(kTimeB); - auto correct = context_.timeline->FindPreviousEvent( - kThreadFreePacketTimestamp + kThreadFreeOffset, pid); - ASSERT_EQ(correct.type, ProcessThreadTimeline::Event::Type::kClose); -} + auto* process_free = event->mutable_sched_process_free(); + process_free->set_comm(""); + process_free->set_pid(kPid); + process_free->set_prio(0); -TEST_F(CollectTimelineFindsFreeEventTest, NoCloseEventBeforeFree) { - auto pid = kUnityTid; + auto buffer = packet.SerializeAsString(); - auto event = - context_.timeline->FindPreviousEvent(kThreadFreePacketTimestamp - 1, pid); - ASSERT_EQ(event.type, ProcessThreadTimeline::Event::Type::kOpen); - ASSERT_EQ(event.pid, pid); -} + protos::pbzero::TracePacket::Decoder decoder(buffer); + ASSERT_OK(collector_.Collect(decoder, &context_)); + } -// Whether or not AddsCloseOnFree and AddsCloseAfterFree are the same close -// event is an implementation detail. -TEST_F(CollectTimelineFindsFreeEventTest, AddsCloseOnFree) { - auto pid = kUnityTid; + ASSERT_OK(collector_.End(&context_)); - auto event = context_.timeline->FindPreviousEvent( - kThreadFreePacketTimestamp + kThreadFreeOffset, pid); - ASSERT_EQ(event.type, ProcessThreadTimeline::Event::Type::kClose); - ASSERT_EQ(event.pid, pid); -} + const auto* start = context_.timeline->GetOpeningEvent(kTimeA, kPid); + ASSERT_TRUE(start); + ASSERT_TRUE(start->valid()); -TEST_F(CollectTimelineFindsFreeEventTest, AddsCloseAfterFree) { - auto pid = kUnityTid; + // The end event was correctly set so that the free event is inclusive. + const auto* end = context_.timeline->GetOpeningEvent(kTimeB, kPid); + ASSERT_TRUE(end); + ASSERT_TRUE(end->valid()); - auto event = context_.timeline->FindPreviousEvent( - kThreadFreePacketTimestamp + kThreadFreeOffset + 1, pid); - ASSERT_EQ(event.type, ProcessThreadTimeline::Event::Type::kClose); - ASSERT_EQ(event.pid, pid); + const auto* after = context_.timeline->GetOpeningEvent(kTimeC, kPid); + ASSERT_FALSE(after); } } // namespace perfetto::trace_redaction diff --git a/src/trace_redaction/filter_ftrace_using_allowlist.cc b/src/trace_redaction/filter_ftrace_using_allowlist.cc deleted file mode 100644 index f32ddeb15b..0000000000 --- a/src/trace_redaction/filter_ftrace_using_allowlist.cc +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/trace_redaction/filter_ftrace_using_allowlist.h" - -#include "perfetto/base/status.h" -#include "perfetto/protozero/field.h" -#include "perfetto/protozero/proto_decoder.h" - -namespace perfetto::trace_redaction { - -base::Status FilterFtraceUsingAllowlist::VerifyContext( - const Context& context) const { - if (context.ftrace_packet_allow_list.empty()) { - return base::ErrStatus( - "FilterFtraceUsingAllowlist: missing ftrace allowlist."); - } - - return base::OkStatus(); -} - -bool FilterFtraceUsingAllowlist::KeepEvent(const Context& context, - protozero::ConstBytes bytes) const { - PERFETTO_DCHECK(!context.ftrace_packet_allow_list.empty()); - - protozero::ProtoDecoder event(bytes); - - for (auto field = event.ReadField(); field.valid(); - field = event.ReadField()) { - if (context.ftrace_packet_allow_list.count(field.id()) != 0) { - return true; - } - } - - return false; -} - -} // namespace perfetto::trace_redaction diff --git a/src/trace_redaction/filter_ftrace_using_allowlist.h b/src/trace_redaction/filter_ftrace_using_allowlist.h deleted file mode 100644 index 661da68ea7..0000000000 --- a/src/trace_redaction/filter_ftrace_using_allowlist.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef SRC_TRACE_REDACTION_FILTER_FTRACE_USING_ALLOWLIST_H_ -#define SRC_TRACE_REDACTION_FILTER_FTRACE_USING_ALLOWLIST_H_ - -#include "perfetto/protozero/field.h" -#include "src/trace_redaction/scrub_ftrace_events.h" -#include "src/trace_redaction/trace_redaction_framework.h" - -namespace perfetto::trace_redaction { - -// event { -// timestamp: 6702094168934980 -// pid: 7127 -// sched_waking { <-- event type -// comm: "Job.worker 1" -// pid: 7143 -// prio: 120 -// success: 1 -// target_cpu: 7 -// } -// } -// -// Check if the event type appears in the ftrace allow-list. If it doesn't -// appear there, then mark the event as "don't keep". -class FilterFtraceUsingAllowlist : public FtraceEventFilter { - public: - base::Status VerifyContext(const Context& context) const override; - bool KeepEvent(const Context& context, - protozero::ConstBytes bytes) const override; -}; - -} // namespace perfetto::trace_redaction - -#endif // SRC_TRACE_REDACTION_FILTER_FTRACE_USING_ALLOWLIST_H_ diff --git a/src/trace_redaction/filter_ftrace_using_allowlist_integrationtest.cc b/src/trace_redaction/filter_ftrace_using_allowlist_integrationtest.cc deleted file mode 100644 index e53770c642..0000000000 --- a/src/trace_redaction/filter_ftrace_using_allowlist_integrationtest.cc +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include - -#include "perfetto/base/status.h" -#include "src/base/test/status_matchers.h" -#include "src/trace_redaction/filter_ftrace_using_allowlist.h" -#include "src/trace_redaction/populate_allow_lists.h" -#include "src/trace_redaction/scrub_ftrace_events.h" -#include "src/trace_redaction/trace_redaction_integration_fixture.h" -#include "src/trace_redaction/trace_redactor.h" -#include "test/gtest_and_gmock.h" - -#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h" -#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h" -#include "protos/perfetto/trace/trace.pbzero.h" -#include "protos/perfetto/trace/trace_packet.pbzero.h" - -namespace perfetto::trace_redaction { - -class FilterFtraceUsingAllowlistTest - : public testing::Test, - protected TraceRedactionIntegrationFixure { - protected: - void SetUp() override { - trace_redactor()->emplace_build(); - trace_redactor() - ->emplace_transform() - ->emplace_back(); - } - - // Parse the given buffer and gather field ids from across all events. This - // will also include fields like timestamp. - base::FlatSet ParseEvents(std::string trace_buffer) { - base::FlatSet event_ids; - - protos::pbzero::Trace::Decoder trace_decoder(trace_buffer); - - for (auto packet = trace_decoder.packet(); packet; ++packet) { - protos::pbzero::TracePacket::Decoder packet_decoder(*packet); - - if (!packet_decoder.has_ftrace_events()) { - continue; - } - - protos::pbzero::FtraceEventBundle::Decoder bundle_decoder( - packet_decoder.ftrace_events()); - - for (auto event = bundle_decoder.event(); event; ++event) { - protozero::ProtoDecoder event_decoder(*event); - - for (auto field = event_decoder.ReadField(); field.valid(); - field = event_decoder.ReadField()) { - event_ids.insert(field.id()); - } - } - } - - return event_ids; - } -}; - -// This is not a test of FilterFtraceUsingAllowlist, but instead verifies of the -// sample trace used in the test. -TEST_F(FilterFtraceUsingAllowlistTest, TraceHasAllEvents) { - auto trace = LoadOriginal(); - ASSERT_OK(trace) << trace->c_str(); - - auto events = ParseEvents(std::move(trace.value())); - ASSERT_EQ(events.size(), 14u); - - ASSERT_TRUE( - events.count(protos::pbzero::FtraceEvent::kCpuFrequencyFieldNumber)); - ASSERT_TRUE(events.count(protos::pbzero::FtraceEvent::kCpuIdleFieldNumber)); - ASSERT_TRUE( - events.count(protos::pbzero::FtraceEvent::kOomScoreAdjUpdateFieldNumber)); - ASSERT_TRUE(events.count(protos::pbzero::FtraceEvent::kPidFieldNumber)); - ASSERT_TRUE(events.count(protos::pbzero::FtraceEvent::kPrintFieldNumber)); - ASSERT_TRUE( - events.count(protos::pbzero::FtraceEvent::kSchedProcessExitFieldNumber)); - ASSERT_TRUE( - events.count(protos::pbzero::FtraceEvent::kSchedProcessFreeFieldNumber)); - ASSERT_TRUE( - events.count(protos::pbzero::FtraceEvent::kSchedSwitchFieldNumber)); - ASSERT_TRUE( - events.count(protos::pbzero::FtraceEvent::kSchedWakeupFieldNumber)); - ASSERT_TRUE( - events.count(protos::pbzero::FtraceEvent::kSchedWakeupNewFieldNumber)); - ASSERT_TRUE( - events.count(protos::pbzero::FtraceEvent::kSchedWakingFieldNumber)); - ASSERT_TRUE( - events.count(protos::pbzero::FtraceEvent::kTaskNewtaskFieldNumber)); - ASSERT_TRUE( - events.count(protos::pbzero::FtraceEvent::kTaskRenameFieldNumber)); - ASSERT_TRUE(events.count(protos::pbzero::FtraceEvent::kTimestampFieldNumber)); -} - -TEST_F(FilterFtraceUsingAllowlistTest, RetainsAllowedEvents) { - auto redacted = Redact(); - ASSERT_OK(redacted) << redacted.c_message(); - - auto trace = LoadRedacted(); - ASSERT_OK(trace) << trace.status().c_message(); - - auto events = ParseEvents(std::move(trace.value())); - - // These are not events, they are fields that exist alongside the event. - ASSERT_TRUE(events.count(protos::pbzero::FtraceEvent::kPidFieldNumber)); - ASSERT_TRUE(events.count(protos::pbzero::FtraceEvent::kTimestampFieldNumber)); - - // These are events. - ASSERT_TRUE(events.count(protos::pbzero::FtraceEvent::kPrintFieldNumber)); - ASSERT_TRUE( - events.count(protos::pbzero::FtraceEvent::kCpuFrequencyFieldNumber)); - ASSERT_TRUE(events.count(protos::pbzero::FtraceEvent::kCpuIdleFieldNumber)); - ASSERT_TRUE( - events.count(protos::pbzero::FtraceEvent::kSchedProcessFreeFieldNumber)); - ASSERT_TRUE( - events.count(protos::pbzero::FtraceEvent::kSchedSwitchFieldNumber)); - ASSERT_TRUE( - events.count(protos::pbzero::FtraceEvent::kSchedWakingFieldNumber)); - ASSERT_TRUE( - events.count(protos::pbzero::FtraceEvent::kTaskNewtaskFieldNumber)); - ASSERT_TRUE( - events.count(protos::pbzero::FtraceEvent::kTaskRenameFieldNumber)); -} - -TEST_F(FilterFtraceUsingAllowlistTest, RemovesNotAllowedEvents) { - auto redacted = Redact(); - ASSERT_OK(redacted) << redacted.c_message(); - - auto trace = LoadRedacted(); - ASSERT_OK(trace) << trace.status().c_message(); - - auto events = ParseEvents(std::move(trace.value())); - - // These are events. - ASSERT_FALSE( - events.count(protos::pbzero::FtraceEvent::kOomScoreAdjUpdateFieldNumber)); - ASSERT_FALSE( - events.count(protos::pbzero::FtraceEvent::kSchedProcessExitFieldNumber)); - ASSERT_FALSE( - events.count(protos::pbzero::FtraceEvent::kSchedWakeupFieldNumber)); - ASSERT_FALSE( - events.count(protos::pbzero::FtraceEvent::kSchedWakeupNewFieldNumber)); -} - -} // namespace perfetto::trace_redaction diff --git a/src/trace_redaction/filter_ftrace_using_allowlist_unittest.cc b/src/trace_redaction/filter_ftrace_using_allowlist_unittest.cc deleted file mode 100644 index 2b45043035..0000000000 --- a/src/trace_redaction/filter_ftrace_using_allowlist_unittest.cc +++ /dev/null @@ -1,245 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/trace_redaction/filter_ftrace_using_allowlist.h" -#include "src/base/test/status_matchers.h" -#include "src/trace_redaction/scrub_ftrace_events.h" -#include "test/gtest_and_gmock.h" - -#include "protos/perfetto/trace/ftrace/ftrace_event.gen.h" -#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h" -#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.gen.h" -#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h" -#include "protos/perfetto/trace/ftrace/power.gen.h" -#include "protos/perfetto/trace/ftrace/task.gen.h" -#include "protos/perfetto/trace/ps/process_tree.gen.h" -#include "protos/perfetto/trace/trace_packet.gen.h" - -namespace perfetto::trace_redaction { - -// Tests which nested messages and fields are removed. -class FilterFtraceUsingAllowlistTest : public testing::Test { - protected: - void SetUp() override { - transform_.emplace_back(); - } - - // task_rename should be in the allow-list. - static void AddTaskRename(protos::gen::FtraceEventBundle* bundle, - int32_t pid, - const std::string& old_comm, - const std::string& new_comm) { - auto* e = bundle->add_event(); - e->mutable_task_rename()->set_pid(pid); - e->mutable_task_rename()->set_oldcomm(old_comm); - e->mutable_task_rename()->set_newcomm(new_comm); - } - - static void AddClockSetRate(protos::gen::FtraceEventBundle* bundle, - uint64_t cpu, - const std::string& name, - uint64_t state) { - auto* e = bundle->add_event(); - e->mutable_clock_set_rate()->set_cpu_id(cpu); - e->mutable_clock_set_rate()->set_name(name); - e->mutable_clock_set_rate()->set_state(state); - } - - ScrubFtraceEvents transform_; -}; - -TEST_F(FilterFtraceUsingAllowlistTest, ReturnErrorForNullPacket) { - // Have something in the allow-list to avoid that error. - Context context; - context.ftrace_packet_allow_list = { - protos::pbzero::FtraceEvent::kTaskRenameFieldNumber}; - - ASSERT_FALSE(transform_.Transform(context, nullptr).ok()); -} - -TEST_F(FilterFtraceUsingAllowlistTest, ReturnErrorForEmptyPacket) { - // Have something in the allow-list to avoid that error. - Context context; - context.ftrace_packet_allow_list = { - protos::pbzero::FtraceEvent::kTaskRenameFieldNumber}; - - std::string packet_str = ""; - - ASSERT_FALSE(transform_.Transform(context, &packet_str).ok()); -} - -TEST_F(FilterFtraceUsingAllowlistTest, ReturnErrorForEmptyAllowList) { - // The context will have no allow-list entries. ScrubFtraceEvents should fail. - Context context; - - protos::gen::TracePacket packet; - std::string packet_str = packet.SerializeAsString(); - - ASSERT_FALSE(transform_.Transform(context, &packet_str).ok()); -} - -TEST_F(FilterFtraceUsingAllowlistTest, IgnorePacketWithNoFtraceEvents) { - protos::gen::TracePacket trace_packet; - auto* tree = trace_packet.mutable_process_tree(); - - auto& process = tree->mutable_processes()->emplace_back(); - process.set_pid(1); - process.set_ppid(2); - process.set_uid(3); - - auto& thread = tree->mutable_threads()->emplace_back(); - thread.set_name("hello world"); - thread.set_tgid(1); - thread.set_tid(135); - - auto original_packet = trace_packet.SerializeAsString(); - auto packet = original_packet; - - Context context; - context.ftrace_packet_allow_list = { - protos::pbzero::FtraceEvent::kTaskRenameFieldNumber}; - - auto transform_status = transform_.Transform(context, &packet); - ASSERT_OK(transform_status) << transform_status.c_message(); - - // The packet doesn't have any ftrace events. It should not be affected by - // this transform. - ASSERT_EQ(original_packet, packet); -} - -// There are some values in a ftrace event that sits behind the ftrace bundle. -// These values should be retained. -TEST_F(FilterFtraceUsingAllowlistTest, KeepsFtraceBundleSiblingValues) { - protos::gen::TracePacket trace_packet; - auto* ftrace_events = trace_packet.mutable_ftrace_events(); - - ftrace_events->set_cpu(7); - AddTaskRename(ftrace_events, 7, "old_comm", "new_comm_7"); - AddClockSetRate(ftrace_events, 7, "cool cpu name", 1); - - auto original_packet = trace_packet.SerializeAsString(); - auto packet = original_packet; - - Context context; - context.ftrace_packet_allow_list = { - protos::pbzero::FtraceEvent::kTaskRenameFieldNumber}; - - ASSERT_OK(transform_.Transform(context, &packet)); - - protos::gen::TracePacket gen_packet; - gen_packet.ParseFromString(packet); - - ASSERT_TRUE(gen_packet.has_ftrace_events()); - const auto& gen_events = gen_packet.ftrace_events(); - - // Because the CPU sits beside the event list, and not inside the event list, - // the CPU value should be retained. - ASSERT_TRUE(gen_events.has_cpu()); - ASSERT_EQ(gen_events.cpu(), 7u); - - // ClockSetRate should be dropped. Only TaskRename should remain. - ASSERT_EQ(gen_events.event_size(), 1); - ASSERT_FALSE(gen_events.event().front().has_clock_set_rate()); - ASSERT_TRUE(gen_events.event().front().has_task_rename()); -} - -TEST_F(FilterFtraceUsingAllowlistTest, KeepsAllowedEvents) { - Context context; - context.ftrace_packet_allow_list = { - protos::pbzero::FtraceEvent::kTaskRenameFieldNumber, - }; - - protos::gen::TracePacket before; - AddTaskRename(before.mutable_ftrace_events(), 7, "old_comm", "new_comm_7"); - AddTaskRename(before.mutable_ftrace_events(), 8, "old_comm", "new_comm_8"); - AddTaskRename(before.mutable_ftrace_events(), 9, "old_comm", "new_comm_9"); - - auto before_str = before.SerializeAsString(); - auto after_str = before_str; - - ASSERT_OK(transform_.Transform(context, &after_str)); - - protos::gen::TracePacket after; - after.ParseFromString(after_str); - - // Implementation detail: ScrubFtraceEvents may change entry order. The diff - // must be order independent. Sort the events by pid, this will make it easier - // to assert values. - auto events = after.ftrace_events().event(); - std::sort(events.begin(), events.end(), - [](const auto& l, const auto& r) { return l.pid() < r.pid(); }); - - ASSERT_EQ(events.size(), 3u); - - ASSERT_TRUE(events[0].has_task_rename()); - ASSERT_EQ(events[0].task_rename().pid(), 7); - ASSERT_EQ(events[0].task_rename().oldcomm(), "old_comm"); - ASSERT_EQ(events[0].task_rename().newcomm(), "new_comm_7"); - - ASSERT_TRUE(events[1].has_task_rename()); - ASSERT_EQ(events[1].task_rename().pid(), 8); - ASSERT_EQ(events[1].task_rename().oldcomm(), "old_comm"); - ASSERT_EQ(events[1].task_rename().newcomm(), "new_comm_8"); - - ASSERT_TRUE(events[2].has_task_rename()); - ASSERT_EQ(events[2].task_rename().pid(), 9); - ASSERT_EQ(events[2].task_rename().oldcomm(), "old_comm"); - ASSERT_EQ(events[2].task_rename().newcomm(), "new_comm_9"); -} - -// Only the specific non-allowed events should be removed from the event list. -TEST_F(FilterFtraceUsingAllowlistTest, OnlyDropsNotAllowedEvents) { - // AddTaskRename >> Keep - // AddClockSetRate >> Drop - protos::gen::TracePacket original_packet; - AddTaskRename(original_packet.mutable_ftrace_events(), 7, "old_comm", - "new_comm_7"); - AddClockSetRate(original_packet.mutable_ftrace_events(), 0, "cool cpu name", - 1); - AddTaskRename(original_packet.mutable_ftrace_events(), 8, "old_comm", - "new_comm_8"); - AddTaskRename(original_packet.mutable_ftrace_events(), 9, "old_comm", - "new_comm_9"); - auto packet = original_packet.SerializeAsString(); - - Context context; - context.ftrace_packet_allow_list = { - protos::pbzero::FtraceEvent::kTaskRenameFieldNumber}; - - ASSERT_OK(transform_.Transform(context, &packet)); - - protos::gen::TracePacket modified_packet; - ASSERT_TRUE(modified_packet.ParseFromString(packet)); - - // Only the clock set rate event should have been removed (drop 1 of the 4 - // events). - ASSERT_TRUE(modified_packet.has_ftrace_events()); - ASSERT_EQ(modified_packet.ftrace_events().event_size(), 3); - - // All ftrace events should be rename events. - const auto& events = modified_packet.ftrace_events().event(); - - ASSERT_TRUE(events.at(0).has_task_rename()); - ASSERT_EQ(events.at(0).task_rename().pid(), 7); - - ASSERT_TRUE(events.at(1).has_task_rename()); - ASSERT_EQ(events.at(1).task_rename().pid(), 8); - - ASSERT_TRUE(events.at(2).has_task_rename()); - ASSERT_EQ(events.at(2).task_rename().pid(), 9); -} - -} // namespace perfetto::trace_redaction diff --git a/src/trace_redaction/filter_packet_using_allowlist.h b/src/trace_redaction/filter_packet_using_allowlist.h deleted file mode 100644 index 9205e946ce..0000000000 --- a/src/trace_redaction/filter_packet_using_allowlist.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef SRC_TRACE_REDACTION_FILTER_PACKET_USING_ALLOWLIST_H_ -#define SRC_TRACE_REDACTION_FILTER_PACKET_USING_ALLOWLIST_H_ - -#include "src/trace_redaction/scrub_trace_packet.h" -#include "src/trace_redaction/trace_redaction_framework.h" - -namespace perfetto::trace_redaction { - -// Since the number of allow-listed message types, and the allow-list is -// small, the look-up can be considered constant time. -// -// There is a constant max number of fields in a packet. Given this limit and -// the constant allow-list look-up, this primitive can be considered linear. -class FilterPacketUsingAllowlist : public TracePacketFilter { - public: - base::Status VerifyContext(const Context& context) const override; - - bool KeepField(const Context& context, - const protozero::Field& field) const override; -}; - -} // namespace perfetto::trace_redaction - -#endif // SRC_TRACE_REDACTION_FILTER_PACKET_USING_ALLOWLIST_H_ diff --git a/src/trace_redaction/filter_packet_using_allowlist_unittest.cc b/src/trace_redaction/filter_packet_using_allowlist_unittest.cc deleted file mode 100644 index fad6879a32..0000000000 --- a/src/trace_redaction/filter_packet_using_allowlist_unittest.cc +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/trace_redaction/filter_packet_using_allowlist.h" -#include "perfetto/protozero/scattered_heap_buffer.h" -#include "test/gtest_and_gmock.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 { - -namespace { - -constexpr auto kJustSomeFieldId = - protos::pbzero::TracePacket::kProcessTreeFieldNumber; - -} // namespace - -TEST(FilterPacketUsingAllowlistParamErrorTest, ReturnsErrorForEmptyAllowlist) { - Context context; - - FilterPacketUsingAllowlist filter; - auto status = filter.VerifyContext(context); - - ASSERT_FALSE(status.ok()) << status.message(); -} - -TEST(FilterPacketUsingAllowlistParamErrorTest, ReturnsFalseForInvalidField) { - // Have something in the allow-list to avoid an error. - Context context; - context.trace_packet_allow_list.insert(kJustSomeFieldId); - - protozero::Field invalid = {}; - ASSERT_FALSE(invalid.valid()); - - FilterPacketUsingAllowlist filter; - ASSERT_FALSE(filter.KeepField(context, invalid)); -} - -TEST(FilterPacketUsingAllowlistParamErrorTest, ReturnsFalseForExcludedField) { - Context context; - context.trace_packet_allow_list.insert(kJustSomeFieldId); - - protozero::HeapBuffered packet; - packet->set_timestamp(123456789); - - auto buffer = packet.SerializeAsString(); - - protozero::ProtoDecoder decoder(buffer); - protozero::Field field = decoder.FindField(kJustSomeFieldId); - - FilterPacketUsingAllowlist filter; - ASSERT_FALSE(filter.KeepField(context, field)); -} - -} // namespace perfetto::trace_redaction diff --git a/src/trace_redaction/filter_print_events.cc b/src/trace_redaction/filter_print_events.cc deleted file mode 100644 index 869c7a0fe7..0000000000 --- a/src/trace_redaction/filter_print_events.cc +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/trace_redaction/filter_print_events.h" - -#include "perfetto/base/logging.h" -#include "perfetto/base/status.h" - -#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h" -#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h" - -namespace perfetto::trace_redaction { - -base::Status FilterPrintEvents::VerifyContext(const Context& context) const { - if (!context.package_uid.has_value()) { - return base::ErrStatus("FilterPrintEvents: missing packet uid."); - } - - if (!context.timeline) { - return base::ErrStatus("FilterPrintEvents: missing timeline."); - } - - return base::OkStatus(); -} - -bool FilterPrintEvents::KeepEvent(const Context& context, - protozero::ConstBytes bytes) const { - PERFETTO_DCHECK(context.timeline); - PERFETTO_DCHECK(context.package_uid.has_value()); - - protozero::ProtoDecoder event(bytes); - - // This is not a print packet. Keep the packet. - if (!event.FindField(protos::pbzero::FtraceEvent::kPrintFieldNumber) - .valid()) { - return true; - } - - auto time = - event.FindField(protos::pbzero::FtraceEvent::kTimestampFieldNumber); - auto pid = event.FindField(protos::pbzero::FtraceEvent::kPidFieldNumber); - - return pid.valid() && time.valid() && - context.timeline->PidConnectsToUid(time.as_uint64(), pid.as_int32(), - *context.package_uid); -} - -} // namespace perfetto::trace_redaction diff --git a/src/trace_redaction/filter_print_events.h b/src/trace_redaction/filter_print_events.h deleted file mode 100644 index 36ef92be58..0000000000 --- a/src/trace_redaction/filter_print_events.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef SRC_TRACE_REDACTION_FILTER_PRINT_EVENTS_H_ -#define SRC_TRACE_REDACTION_FILTER_PRINT_EVENTS_H_ - -#include "perfetto/protozero/field.h" -#include "src/trace_redaction/scrub_ftrace_events.h" -#include "src/trace_redaction/trace_redaction_framework.h" - -namespace perfetto::trace_redaction { - -// event { -// timestamp: 6702093749982230 -// pid: 7947 <-- target -// print { -// buf: "B|7105|virtual void -// swappy::ChoreographerThread::onChoreographer()\n" -// } -// } -// -// If the target pid doesn't belong to the target package (context.package_uid), -// then the event will be marked as "don't keep". -class FilterPrintEvents : public FtraceEventFilter { - public: - base::Status VerifyContext(const Context& context) const override; - bool KeepEvent(const Context& context, - protozero::ConstBytes bytes) const override; -}; - -} // namespace perfetto::trace_redaction - -#endif // SRC_TRACE_REDACTION_FILTER_PRINT_EVENTS_H_ diff --git a/src/trace_redaction/filter_sched_waking_events.cc b/src/trace_redaction/filter_sched_waking_events.cc deleted file mode 100644 index 287090dad0..0000000000 --- a/src/trace_redaction/filter_sched_waking_events.cc +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/trace_redaction/filter_sched_waking_events.h" - -#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h" -#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h" -#include "protos/perfetto/trace/ftrace/sched.pbzero.h" - -namespace perfetto::trace_redaction { - -base::Status FilterSchedWakingEvents::VerifyContext( - const Context& context) const { - if (!context.package_uid.has_value()) { - return base::ErrStatus("FilterSchedWakingEvents: missing packet uid."); - } - - if (!context.timeline) { - return base::ErrStatus("FilterSchedWakingEvents: missing timeline."); - } - - return base::OkStatus(); -} - -bool FilterSchedWakingEvents::KeepEvent(const Context& context, - protozero::ConstBytes bytes) const { - PERFETTO_DCHECK(context.package_uid.has_value()); - PERFETTO_DCHECK(context.timeline); - - protozero::ProtoDecoder event_decoder(bytes); - - auto sched_waking = event_decoder.FindField( - protos::pbzero::FtraceEvent::kSchedWakingFieldNumber); - - if (!sched_waking.valid()) { - return true; // Keep - } - - auto timestamp = event_decoder.FindField( - protos::pbzero::FtraceEvent::kTimestampFieldNumber); - - if (!timestamp.valid()) { - return false; // Remove - } - - auto outer_pid = - event_decoder.FindField(protos::pbzero::FtraceEvent::kPidFieldNumber); - - if (!outer_pid.valid() || - !context.timeline->PidConnectsToUid( - timestamp.as_uint64(), outer_pid.as_int32(), *context.package_uid)) { - return false; // Remove - } - - protozero::ProtoDecoder waking_decoder(sched_waking.as_bytes()); - - auto inner_pid = waking_decoder.FindField( - protos::pbzero::SchedWakingFtraceEvent::kPidFieldNumber); - - if (!inner_pid.valid() || - !context.timeline->PidConnectsToUid( - timestamp.as_uint64(), inner_pid.as_int32(), *context.package_uid)) { - return false; // Remove - } - - return true; -} - -} // namespace perfetto::trace_redaction diff --git a/src/trace_redaction/filter_sched_waking_events.h b/src/trace_redaction/filter_sched_waking_events.h deleted file mode 100644 index 5d33ed6c17..0000000000 --- a/src/trace_redaction/filter_sched_waking_events.h +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef SRC_TRACE_REDACTION_FILTER_SCHED_WAKING_EVENTS_H_ -#define SRC_TRACE_REDACTION_FILTER_SCHED_WAKING_EVENTS_H_ - -#include "perfetto/protozero/field.h" -#include "src/trace_redaction/scrub_ftrace_events.h" -#include "src/trace_redaction/trace_redaction_framework.h" - -namespace perfetto::trace_redaction { - -// Redact sched waking trace events in a ftrace event bundle: -// -// event { -// timestamp: 6702093787823849 -// pid: 814 <-- waker -// sched_waking { -// comm: "surfaceflinger" -// pid: 756 <-- target -// prio: 97 -// success: 1 -// target_cpu: 2 -// } -// } -// -// The three values needed are: -// -// 1. event.pid -// 2. event.timestamp -// 3. event.sched_waking.pid -// -// The two checks that are executed are: -// -// 1. package(event.pid).at(event.timestamp).is(target) -// 2. package(event.sched_waking.pid).at(event.timestamp).is(target) -// -// Both must be true in order to keep an event. -class FilterSchedWakingEvents : public FtraceEventFilter { - public: - base::Status VerifyContext(const Context& context) const override; - bool KeepEvent(const Context& context, - protozero::ConstBytes bytes) const override; -}; - -} // namespace perfetto::trace_redaction - -#endif // SRC_TRACE_REDACTION_FILTER_SCHED_WAKING_EVENTS_H_ diff --git a/src/trace_redaction/filter_sched_waking_events_integrationtest.cc b/src/trace_redaction/filter_sched_waking_events_integrationtest.cc index 2993ac0a56..efd91c4d38 100644 --- a/src/trace_redaction/filter_sched_waking_events_integrationtest.cc +++ b/src/trace_redaction/filter_sched_waking_events_integrationtest.cc @@ -22,9 +22,8 @@ #include "perfetto/ext/base/flat_hash_map.h" #include "src/base/test/status_matchers.h" #include "src/trace_redaction/collect_timeline_events.h" -#include "src/trace_redaction/filter_sched_waking_events.h" #include "src/trace_redaction/find_package_uid.h" -#include "src/trace_redaction/scrub_ftrace_events.h" +#include "src/trace_redaction/redact_sched_events.h" #include "src/trace_redaction/trace_redaction_framework.h" #include "src/trace_redaction/trace_redaction_integration_fixture.h" #include "src/trace_redaction/trace_redactor.h" @@ -47,15 +46,19 @@ class RedactSchedWakingIntegrationTest protected TraceRedactionIntegrationFixure { protected: void SetUp() override { - trace_redactor()->emplace_collect(); - trace_redactor()->emplace_collect(); + trace_redactor_.emplace_collect(); + trace_redactor_.emplace_collect(); - auto* ftrace_filter = - trace_redactor()->emplace_transform(); - ftrace_filter->emplace_back(); + auto* redact_sched_events = + trace_redactor_.emplace_transform(); + redact_sched_events->emplace_modifier(); + redact_sched_events->emplace_waking_filter(); - context()->package_name = kPackageName; + context_.package_name = kPackageName; } + + Context context_; + TraceRedactor trace_redactor_; }; // >>> SELECT uid @@ -110,7 +113,7 @@ class RedactSchedWakingIntegrationTest // +------+-----------------+ TEST_F(RedactSchedWakingIntegrationTest, OnlyKeepsPackageEvents) { - auto result = Redact(); + auto result = Redact(trace_redactor_, &context_); ASSERT_OK(result) << result.c_message(); auto original = LoadOriginal(); @@ -158,22 +161,16 @@ TEST_F(RedactSchedWakingIntegrationTest, OnlyKeepsPackageEvents) { protos::pbzero::FtraceEventBundle::Decoder ftrace_events_decoder( packet_decoder.ftrace_events()); - for (auto event = ftrace_events_decoder.event(); event; ++event) { - protos::pbzero::FtraceEvent::Decoder event_decoder(*event); - - if (!event_decoder.has_sched_waking()) { - continue; - } + for (auto it = ftrace_events_decoder.event(); it; ++it) { + protos::pbzero::FtraceEvent::Decoder event(*it); - ASSERT_TRUE(event_decoder.has_pid()); - ASSERT_TRUE( - expected_names.Find(static_cast(event_decoder.pid()))); + if (event.has_sched_waking()) { + protos::pbzero::SchedWakingFtraceEvent::Decoder waking( + event.sched_waking()); - protos::pbzero::SchedWakingFtraceEvent::Decoder waking_decoder( - event_decoder.sched_waking()); - - ASSERT_TRUE(waking_decoder.has_pid()); - ASSERT_TRUE(expected_names.Find(waking_decoder.pid())); + ASSERT_TRUE(waking.has_pid()); + ASSERT_TRUE(expected_names.Find(waking.pid())); + } } } } diff --git a/src/trace_redaction/filter_sched_waking_events_unittest.cc b/src/trace_redaction/filter_sched_waking_events_unittest.cc index 9f71113c34..b264acb2cb 100644 --- a/src/trace_redaction/filter_sched_waking_events_unittest.cc +++ b/src/trace_redaction/filter_sched_waking_events_unittest.cc @@ -14,8 +14,7 @@ * limitations under the License. */ -#include "src/trace_redaction/filter_sched_waking_events.h" -#include "src/trace_redaction/scrub_ftrace_events.h" +#include "src/trace_redaction/redact_sched_events.h" #include "test/gtest_and_gmock.h" #include "protos/perfetto/trace/ftrace/ftrace_event.gen.h" @@ -31,7 +30,10 @@ constexpr int32_t kPackageUid = 1; class FilterSchedWakingEventsTest : public testing::Test { protected: - void SetUp() override { transform_.emplace_back(); } + void SetUp() override { + redact_.emplace_modifier(); + redact_.emplace_waking_filter(); + } void BeginBundle() { ftrace_bundle_ = trace_packet_.mutable_ftrace_events(); } @@ -46,8 +48,6 @@ class FilterSchedWakingEventsTest : public testing::Test { sched_waking->set_comm(std::string(comm)); } - const ScrubFtraceEvents& transform() const { return transform_; } - // event { // timestamp: 6702093757720043 // pid: 0 @@ -104,11 +104,11 @@ class FilterSchedWakingEventsTest : public testing::Test { return event; } + RedactSchedEvents redact_; + private: protos::gen::TracePacket trace_packet_; protos::gen::FtraceEventBundle* ftrace_bundle_; - - ScrubFtraceEvents transform_; }; TEST_F(FilterSchedWakingEventsTest, ReturnsErrorForNullPacket) { @@ -117,7 +117,7 @@ TEST_F(FilterSchedWakingEventsTest, ReturnsErrorForNullPacket) { context.package_uid = kPackageUid; context.timeline = std::make_unique(); - ASSERT_FALSE(transform().Transform(context, nullptr).ok()); + ASSERT_FALSE(redact_.Transform(context, nullptr).ok()); } TEST_F(FilterSchedWakingEventsTest, ReturnsErrorForEmptyPacket) { @@ -128,7 +128,7 @@ TEST_F(FilterSchedWakingEventsTest, ReturnsErrorForEmptyPacket) { std::string packet_str = ""; - ASSERT_FALSE(transform().Transform(context, &packet_str).ok()); + ASSERT_FALSE(redact_.Transform(context, &packet_str).ok()); } TEST_F(FilterSchedWakingEventsTest, ReturnsErrorForNoTimeline) { @@ -139,7 +139,7 @@ TEST_F(FilterSchedWakingEventsTest, ReturnsErrorForNoTimeline) { protos::gen::TracePacket packet; std::string packet_str = packet.SerializeAsString(); - ASSERT_FALSE(transform().Transform(context, &packet_str).ok()); + ASSERT_FALSE(redact_.Transform(context, &packet_str).ok()); } TEST_F(FilterSchedWakingEventsTest, ReturnsErrorForMissingPackage) { @@ -150,7 +150,7 @@ TEST_F(FilterSchedWakingEventsTest, ReturnsErrorForMissingPackage) { protos::gen::TracePacket packet; std::string packet_str = packet.SerializeAsString(); - ASSERT_FALSE(transform().Transform(context, &packet_str).ok()); + ASSERT_FALSE(redact_.Transform(context, &packet_str).ok()); } // Assume that the traces has a series of events like the events below. All @@ -210,7 +210,7 @@ TEST_F(FilterSchedWakingEventsTest, RetainsNonWakingEvents) { 6702093757720043, 7148, 0, kPackageUid)); context.timeline->Sort(); - ASSERT_TRUE(transform().Transform(context, &packet_str).ok()); + ASSERT_TRUE(redact_.Transform(context, &packet_str).ok()); { protos::gen::TracePacket packet; @@ -241,192 +241,110 @@ TEST_F(FilterSchedWakingEventsTest, RetainsNonWakingEvents) { } } -// Assume that the traces has a series of events like the events below. All -// constants will come from these packets: -// -// event { -// timestamp: 6702093757727075 -// pid: 7147 <- This pid woke up... -// sched_waking { -// comm: "Job.worker 6" -// pid: 7148 <- ... this pid -// prio: 120 -// success: 1 -// target_cpu: 6 -// } -// } -// -// Because the sched waking event pid's appears in the timeline and is connected -// to the target package (kPackageUid), the waking even should remain. -TEST_F(FilterSchedWakingEventsTest, KeepsWhenBothPidsConnectToPackage) { - std::string packet_str; +// When the wake target's pid is connected to the target package, the event +// should be present after redaction. +TEST_F(FilterSchedWakingEventsTest, KeepsIncludedWakeEvents) { + constexpr int32_t kUidA = 11; + constexpr int32_t kPidA = 1; - { - protos::gen::TracePacket packet; - auto* events = packet.mutable_ftrace_events(); - events->set_cpu(0); + constexpr int32_t kUidB = 20; + constexpr int32_t kPidB = 2; - CreateSchedWakingEvent(events->add_event()); + // Build a packet where Pid A wakes Pid B. + protos::gen::TracePacket packet_builder; - packet_str = packet.SerializeAsString(); - } + auto* events_builder = packet_builder.mutable_ftrace_events(); + events_builder->set_cpu(0); - // Create a timeline where the wake-target (7147 & 7148) is connected to the - // target package. - Context context; - context.timeline = std::make_unique(); - context.package_uid = kPackageUid; - context.timeline->Append(ProcessThreadTimeline::Event::Open( - 6702093757720043, 7147, 0, kPackageUid)); - context.timeline->Append(ProcessThreadTimeline::Event::Open( - 6702093757720043, 7148, 0, kPackageUid)); - context.timeline->Sort(); - - ASSERT_TRUE(transform().Transform(context, &packet_str).ok()); + auto* event_builder = events_builder->add_event(); + event_builder->set_timestamp(10); + event_builder->set_pid(kPidA); - { - protos::gen::TracePacket packet; - packet.ParseFromString(packet_str); - - ASSERT_TRUE(packet.has_ftrace_events()); - - const protos::gen::FtraceEvent* waking_it = nullptr; + auto* sched_waking_builder = event_builder->mutable_sched_waking(); + sched_waking_builder->set_comm("Job.worker 6"); + sched_waking_builder->set_pid(kPidB); + sched_waking_builder->set_prio(120); + sched_waking_builder->set_success(1); + sched_waking_builder->set_target_cpu(6); - for (const auto& event : packet.ftrace_events().event()) { - if (event.has_sched_waking()) { - waking_it = &event; - } - } + auto packet_str = packet_builder.SerializeAsString(); - ASSERT_TRUE(waking_it); + Context context; - const auto& waking = waking_it->sched_waking(); + // Keep the packet (wake target is Uid B). + context.package_uid = kUidB; - ASSERT_EQ(waking.comm(), "Job.worker 6"); - ASSERT_EQ(waking.pid(), 7148); - ASSERT_EQ(waking.prio(), 120); - ASSERT_EQ(waking.success(), 1); - ASSERT_EQ(waking.target_cpu(), 6); - } -} - -// Assume that the traces has a series of events like the events below. All -// constants will come from these packets: -// -// event { -// timestamp: 6702093757727075 -// pid: 7147 <- This pid woke up... -// sched_waking { -// comm: "Job.worker 6" -// pid: 7148 <- ... this pid -// prio: 120 -// success: 1 -// target_cpu: 6 -// } -// } -// -// Because only one of the sched waking events pid's appears in the -// timeline and is connected to the target package (kPackageUid), the waking -// even should be removed. -TEST_F(FilterSchedWakingEventsTest, DropWhenOnlyWakerConnectsToPackage) { - std::string packet_str; + context.timeline = std::make_unique(); + context.timeline->Append( + ProcessThreadTimeline::Event::Open(0, kPidA, 0, kUidA)); + context.timeline->Append( + ProcessThreadTimeline::Event::Open(0, kPidB, 0, kUidB)); + context.timeline->Sort(); - { - protos::gen::TracePacket packet; - auto* events = packet.mutable_ftrace_events(); - events->set_cpu(0); + ASSERT_TRUE(redact_.Transform(context, &packet_str).ok()); - CreateSchedWakingEvent(events->add_event()); + protos::gen::TracePacket packet; + ASSERT_TRUE(packet.ParseFromString(packet_str)); - packet_str = packet.SerializeAsString(); - } + ASSERT_TRUE(packet.has_ftrace_events()); - // Because 7147 is not added to the timeline, the waking event should not be - // retained. - Context context; - context.timeline = std::make_unique(); - context.package_uid = kPackageUid; - context.timeline->Append(ProcessThreadTimeline::Event::Open( - 6702093757720043, 7148, 0, kPackageUid)); - context.timeline->Sort(); + const auto& events = packet.ftrace_events().event(); - ASSERT_TRUE(transform().Transform(context, &packet_str).ok()); + ASSERT_EQ(events.size(), 1u); + ASSERT_TRUE(events.at(0).has_sched_waking()); +} - { - protos::gen::TracePacket packet; - packet.ParseFromString(packet_str); +// When the wake target's pid is NOT connected to the target package, the event +// should NOT be present after redaction. +TEST_F(FilterSchedWakingEventsTest, DropsExcludedWakeEvents) { + constexpr int32_t kUidA = 11; + constexpr int32_t kPidA = 1; - ASSERT_TRUE(packet.has_ftrace_events()); + constexpr int32_t kUidB = 20; + constexpr int32_t kPidB = 2; - const protos::gen::FtraceEvent* waking_it = nullptr; + // Build a packet where Pid A wakes Pid B. + protos::gen::TracePacket packet_builder; - for (const auto& event : packet.ftrace_events().event()) { - if (event.has_sched_waking()) { - waking_it = &event; - } - } + auto* events_builder = packet_builder.mutable_ftrace_events(); + events_builder->set_cpu(0); - ASSERT_FALSE(waking_it); - } -} + auto* event_builder = events_builder->add_event(); + event_builder->set_timestamp(10); + event_builder->set_pid(kPidA); -// Assume that the traces has a series of events like the events below. All -// constants will come from these packets: -// -// event { -// timestamp: 6702093757727075 -// pid: 7147 <- This pid woke up... -// sched_waking { -// comm: "Job.worker 6" -// pid: 7148 <- ... this pid -// prio: 120 -// success: 1 -// target_cpu: 6 -// } -// } -// -// Because the only one of the sched waking events pid's appears in the -// timeline and is connected to the target package (kPackageUid), the waking -// even should remain. -TEST_F(FilterSchedWakingEventsTest, DropWhenOnlyTargetConnectsToPackage) { - std::string packet_str; + auto* sched_waking_builder = event_builder->mutable_sched_waking(); + sched_waking_builder->set_comm("Job.worker 6"); + sched_waking_builder->set_pid(kPidB); + sched_waking_builder->set_prio(120); + sched_waking_builder->set_success(1); + sched_waking_builder->set_target_cpu(6); - { - protos::gen::TracePacket packet; - auto* events = packet.mutable_ftrace_events(); - events->set_cpu(0); + auto packet_str = packet_builder.SerializeAsString(); - CreateSchedWakingEvent(events->add_event()); + Context context; - packet_str = packet.SerializeAsString(); - } + // Do not keep the packet (wake target is Pid B, but Pid B is connected to Uid + // B). + context.package_uid = kUidA; - // Because 7147 is not added to the timeline, the waking event should not be - // retained. - Context context; context.timeline = std::make_unique(); - context.package_uid = kPackageUid; - context.timeline->Append(ProcessThreadTimeline::Event::Open( - 6702093757720043, 7147, 0, kPackageUid)); + context.timeline->Append( + ProcessThreadTimeline::Event::Open(0, kPidA, 0, kUidA)); + context.timeline->Append( + ProcessThreadTimeline::Event::Open(0, kPidB, 0, kUidB)); context.timeline->Sort(); - ASSERT_TRUE(transform().Transform(context, &packet_str).ok()); + ASSERT_TRUE(redact_.Transform(context, &packet_str).ok()); - { - protos::gen::TracePacket packet; - packet.ParseFromString(packet_str); - - ASSERT_TRUE(packet.has_ftrace_events()); + protos::gen::TracePacket packet; + ASSERT_TRUE(packet.ParseFromString(packet_str)); - const protos::gen::FtraceEvent* waking_it = nullptr; + ASSERT_TRUE(packet.has_ftrace_events()); - for (const auto& event : packet.ftrace_events().event()) { - if (event.has_sched_waking()) { - waking_it = &event; - } - } + const auto& events = packet.ftrace_events().event(); - ASSERT_FALSE(waking_it); - } + ASSERT_EQ(events.size(), 1u); + ASSERT_FALSE(events.at(0).has_sched_waking()); } } // namespace perfetto::trace_redaction diff --git a/src/trace_redaction/filter_task_rename.cc b/src/trace_redaction/filter_task_rename.cc deleted file mode 100644 index 467e5b5bb6..0000000000 --- a/src/trace_redaction/filter_task_rename.cc +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/trace_redaction/filter_task_rename.h" - -#include "perfetto/base/status.h" -#include "src/trace_redaction/trace_redaction_framework.h" - -#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h" -#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h" - -namespace perfetto::trace_redaction { - -base::Status FilterTaskRename::VerifyContext(const Context& context) const { - if (!context.package_uid.has_value()) { - return base::ErrStatus("FilterTaskRename: missing package uid."); - } - - if (!context.timeline) { - return base::ErrStatus("FilterTaskRename: missing timeline."); - } - - return base::OkStatus(); -} - -bool FilterTaskRename::KeepEvent(const Context& context, - protozero::ConstBytes bytes) const { - PERFETTO_DCHECK(context.package_uid.has_value()); - PERFETTO_DCHECK(context.timeline); - - protozero::ProtoDecoder event_decoder(bytes); - - auto rename = event_decoder.FindField( - protos::pbzero::FtraceEvent::kTaskRenameFieldNumber); - - // Likely - most events will not have a rename event (that's okay). - if (!rename.valid()) { - return true; - } - - auto pid = - event_decoder.FindField(protos::pbzero::FtraceEvent::kPidFieldNumber); - - // Unlikely - all events should have a pid. - if (!pid.valid()) { - return false; - } - - auto timestamp = event_decoder.FindField( - protos::pbzero::FtraceEvent::kTimestampFieldNumber); - - // Unlikely - all events should have a timestamp. - if (!timestamp.valid()) { - return false; - } - - return context.timeline->PidConnectsToUid( - timestamp.as_uint64(), pid.as_int32(), *context.package_uid); -} - -} // namespace perfetto::trace_redaction diff --git a/src/trace_redaction/filter_task_rename_integrationtest.cc b/src/trace_redaction/filter_task_rename_integrationtest.cc index f080fe7e00..332bb2ed68 100644 --- a/src/trace_redaction/filter_task_rename_integrationtest.cc +++ b/src/trace_redaction/filter_task_rename_integrationtest.cc @@ -16,14 +16,12 @@ #include #include -#include #include -#include "perfetto/base/status.h" #include "src/base/test/status_matchers.h" #include "src/trace_redaction/collect_timeline_events.h" -#include "src/trace_redaction/filter_task_rename.h" #include "src/trace_redaction/find_package_uid.h" +#include "src/trace_redaction/redact_process_events.h" #include "src/trace_redaction/trace_redaction_framework.h" #include "src/trace_redaction/trace_redaction_integration_fixture.h" #include "src/trace_redaction/trace_redactor.h" @@ -36,17 +34,6 @@ namespace perfetto::trace_redaction { -namespace { - -using FtraceEvent = protos::pbzero::FtraceEvent; - -// Set the package name to "just some package name". If a specific package name -// is needed, the test it should overwrite this value. -constexpr std::string_view kPackageName = - "com.Unity.com.unity.multiplayer.samples.coop"; - -} // namespace - class RenameEventsTraceRedactorIntegrationTest : public testing::Test, protected TraceRedactionIntegrationFixure { @@ -54,68 +41,72 @@ class RenameEventsTraceRedactorIntegrationTest void SetUp() override { // In order for ScrubTaskRename to work, it needs the timeline. All // registered primitives are there to generate the timeline. - trace_redactor()->emplace_collect(); - trace_redactor()->emplace_collect(); + trace_redactor_.emplace_collect(); + trace_redactor_.emplace_collect(); - auto scrub_ftrace_events = - trace_redactor()->emplace_transform(); - scrub_ftrace_events->emplace_back(); + // Configure the system to drop every rename event not connected to the + // package. + auto* redact = trace_redactor_.emplace_transform(); + redact->emplace_filter(); + redact->emplace_modifier(); - context()->package_name = kPackageName; + context_.package_name = "com.Unity.com.unity.multiplayer.samples.coop"; } - std::vector GetAllRenamedPids( - protos::pbzero::Trace::Decoder trace) const { - std::vector renamed_pids; + void GetRenamedPids( + const protos::pbzero::FtraceEventBundle::Decoder& ftrace_events, + std::vector* pids) const { + for (auto event_it = ftrace_events.event(); event_it; ++event_it) { + protos::pbzero::FtraceEvent::Decoder event(*event_it); - for (auto packet_it = trace.packet(); packet_it; ++packet_it) { - protos::pbzero::TracePacket::Decoder packet_decoder(*packet_it); - - if (!packet_decoder.has_ftrace_events()) { - continue; + if (event.has_task_rename()) { + pids->push_back(event.pid()); } + } + } + + std::vector GetRenamedPids(const std::string bytes) const { + std::vector renamed_pids; - protos::pbzero::FtraceEventBundle::Decoder bundle_decoder( - packet_decoder.ftrace_events()); + protos::pbzero::Trace::Decoder trace(bytes); - for (auto event_it = bundle_decoder.event(); event_it; ++event_it) { - protos::pbzero::FtraceEvent::Decoder event(*event_it); + for (auto packet_it = trace.packet(); packet_it; ++packet_it) { + protos::pbzero::TracePacket::Decoder packet_decoder(*packet_it); - if (event.has_task_rename()) { - renamed_pids.push_back(event.pid()); - } + if (packet_decoder.has_ftrace_events()) { + protos::pbzero::FtraceEventBundle::Decoder ftrace_events( + packet_decoder.ftrace_events()); + GetRenamedPids(ftrace_events, &renamed_pids); } } return renamed_pids; } + + Context context_; + TraceRedactor trace_redactor_; }; TEST_F(RenameEventsTraceRedactorIntegrationTest, RemovesUnwantedRenameTasks) { - auto result = Redact(); - ASSERT_OK(result) << result.c_message(); + ASSERT_OK(Redact(trace_redactor_, &context_)); auto original = LoadOriginal(); - ASSERT_OK(original) << original.status().c_message(); + ASSERT_OK(original); auto redacted = LoadRedacted(); - ASSERT_OK(redacted) << redacted.status().c_message(); - - auto original_rename_pids = - GetAllRenamedPids(protos::pbzero::Trace::Decoder(*original)); - std::sort(original_rename_pids.begin(), original_rename_pids.end()); - - // The test trace has found rename events. This assert is just to document - // theme. - ASSERT_EQ(original_rename_pids.size(), 4u); - ASSERT_EQ(original_rename_pids[0], 7971u); - ASSERT_EQ(original_rename_pids[1], 7972u); - ASSERT_EQ(original_rename_pids[2], 7973u); - ASSERT_EQ(original_rename_pids[3], 7974u); - - auto redacted_rename_pids = - GetAllRenamedPids(protos::pbzero::Trace::Decoder(*redacted)); - ASSERT_TRUE(redacted_rename_pids.empty()); + ASSERT_OK(redacted); + + auto pids_before = GetRenamedPids(*original); + std::sort(pids_before.begin(), pids_before.end()); + + ASSERT_EQ(pids_before.size(), 4u); + ASSERT_EQ(pids_before[0], 7971u); + ASSERT_EQ(pids_before[1], 7972u); + ASSERT_EQ(pids_before[2], 7973u); + ASSERT_EQ(pids_before[3], 7974u); + + auto pids_after = GetRenamedPids(*redacted); + ASSERT_TRUE(pids_after.empty()); } } // namespace perfetto::trace_redaction diff --git a/src/trace_redaction/filter_task_rename_unittest.cc b/src/trace_redaction/filter_task_rename_unittest.cc deleted file mode 100644 index c11111f029..0000000000 --- a/src/trace_redaction/filter_task_rename_unittest.cc +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include - -#include "src/trace_redaction/filter_task_rename.h" -#include "src/trace_redaction/process_thread_timeline.h" -#include "test/gtest_and_gmock.h" - -#include "protos/perfetto/trace/ftrace/ftrace_event.gen.h" -#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.gen.h" -#include "protos/perfetto/trace/ftrace/sched.gen.h" -#include "protos/perfetto/trace/ftrace/task.gen.h" -#include "protos/perfetto/trace/trace_packet.gen.h" - -namespace perfetto::trace_redaction { - -namespace { - -// Used when a single pid is needed. -constexpr uint32_t kPid = 7971; - -constexpr uint64_t kUid = 27; - -constexpr uint64_t kJustSomeTime = 6702094131629195; - -} // namespace - -class ScrubRenameTaskTest : public testing::Test { - protected: - // event { - // timestamp: 6702094131629195 - // pid: 7971 - // task_rename { - // pid: 7971 - // oldcomm: "adbd" - // newcomm: "sh" - // oom_score_adj: -950 - // } - // } - protos::gen::FtraceEvent CreateRenameEvent(uint64_t ts, uint32_t pid) { - protos::gen::FtraceEvent event; - event.set_timestamp(ts); - event.set_pid(pid); - - auto* rename = event.mutable_task_rename(); - rename->set_pid(static_cast(pid)); - rename->set_oldcomm("adbd"); - rename->set_newcomm("sh"); - rename->set_oom_score_adj(-950); - - return event; - } - - // event { - // timestamp: 6702094034179654 - // pid: 7145 - // sched_switch { - // prev_comm: "Job.worker 3" - // prev_pid: 7145 - // prev_prio: 120 - // prev_state: 1 - // next_comm: "swapper/1" - // next_pid: 0 - // next_prio: 120 - // } - // } - protos::gen::FtraceEvent CreateSomeEvent(uint64_t ts, uint32_t pid) { - protos::gen::FtraceEvent event; - event.set_timestamp(ts); - event.set_pid(pid); - - auto* sched = event.mutable_sched_switch(); - sched->set_prev_comm("Job.worker 3"); - sched->set_prev_pid(static_cast(pid)); - sched->set_prev_prio(120); - sched->set_prev_state(1); - sched->set_next_comm("swapper/1"); - sched->set_next_pid(0); - sched->set_next_prio(120); - - return event; - } - - const FilterTaskRename& filter() const { return filter_; } - - Context* context() { return &context_; } - - private: - Context context_; - FilterTaskRename filter_; -}; - -TEST_F(ScrubRenameTaskTest, ReturnErrorForNoPackage) { - context()->timeline.reset(new ProcessThreadTimeline()); - - ASSERT_FALSE(filter().VerifyContext(*context()).ok()); -} - -TEST_F(ScrubRenameTaskTest, ReturnErrorForNoTimeline) { - context()->package_name = "package name"; - context()->package_uid = kUid; - - ASSERT_FALSE(filter().VerifyContext(*context()).ok()); -} - -TEST_F(ScrubRenameTaskTest, KeepsNonRenameEvent) { - context()->package_name = "package name"; - context()->package_uid = kUid; - - context()->timeline.reset(new ProcessThreadTimeline()); - - auto event = CreateSomeEvent(kJustSomeTime, kPid).SerializeAsArray(); - ASSERT_TRUE(filter().KeepEvent(*context(), {event.data(), event.size()})); -} - -TEST_F(ScrubRenameTaskTest, RejectsRenameEventOutsidePackage) { - context()->package_name = "package name"; - context()->package_uid = kUid; - - // There's no connection between kPid and kUid. This means the rename packet - // should be dropped. - context()->timeline.reset(new ProcessThreadTimeline()); - - auto event = CreateRenameEvent(kJustSomeTime, kPid).SerializeAsArray(); - ASSERT_FALSE(filter().KeepEvent(*context(), {event.data(), event.size()})); -} - -TEST_F(ScrubRenameTaskTest, AcceptsRenameEventInPackage) { - context()->package_name = "package name"; - context()->package_uid = kUid; - - context()->timeline.reset(new ProcessThreadTimeline()); - context()->timeline->Append( - ProcessThreadTimeline::Event::Open(0, kPid, 0, kUid)); - context()->timeline->Sort(); - - auto bytes = CreateRenameEvent(kJustSomeTime, kPid).SerializeAsArray(); - ASSERT_TRUE(filter().KeepEvent(*context(), {bytes.data(), bytes.size()})); -} - -} // namespace perfetto::trace_redaction diff --git a/src/trace_redaction/filter_packet_using_allowlist.cc b/src/trace_redaction/filtering.cc similarity index 50% rename from src/trace_redaction/filter_packet_using_allowlist.cc rename to src/trace_redaction/filtering.cc index b1a4dc42c7..15bb8259de 100644 --- a/src/trace_redaction/filter_packet_using_allowlist.cc +++ b/src/trace_redaction/filtering.cc @@ -14,27 +14,26 @@ * limitations under the License. */ -#include "perfetto/base/status.h" -#include "perfetto/protozero/field.h" -#include "src/trace_redaction/filter_packet_using_allowlist.h" -#include "src/trace_redaction/trace_redaction_framework.h" +#include "src/trace_redaction/filtering.h" namespace perfetto::trace_redaction { -base::Status FilterPacketUsingAllowlist::VerifyContext( - const Context& context) const { - if (context.trace_packet_allow_list.empty()) { - return base::ErrStatus("FilterPacketUsingAllowlist: missing allow-list."); - } +PidFilter::~PidFilter() = default; - return base::OkStatus(); +bool ConnectedToPackage::Includes(const Context& context, + uint64_t ts, + int32_t pid) const { + PERFETTO_DCHECK(context.timeline); + PERFETTO_DCHECK(context.package_uid.has_value()); + return context.timeline->PidConnectsToUid(ts, pid, *context.package_uid); } -bool FilterPacketUsingAllowlist::KeepField( - const Context& context, - const protozero::Field& field) const { - PERFETTO_DCHECK(!context.trace_packet_allow_list.empty()); - return field.valid() && context.trace_packet_allow_list.count(field.id()); +bool AllowAll::Includes(const Context&, uint64_t, int32_t) const { + return true; +} + +bool AllowAll::Includes(const Context&, protozero::Field) const { + return true; } } // namespace perfetto::trace_redaction diff --git a/src/trace_redaction/filtering.h b/src/trace_redaction/filtering.h new file mode 100644 index 0000000000..7cb8c52150 --- /dev/null +++ b/src/trace_redaction/filtering.h @@ -0,0 +1,58 @@ +/* + * 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 + +#include "src/trace_redaction/trace_redaction_framework.h" + +#ifndef SRC_TRACE_REDACTION_FILTERING_H_ +#define SRC_TRACE_REDACTION_FILTERING_H_ + +namespace perfetto::trace_redaction { + +class PidFilter { + public: + virtual ~PidFilter(); + + virtual bool Includes(const Context& context, + uint64_t ts, + int32_t pid) const = 0; +}; + +class FtraceEventFilter { + public: + virtual ~FtraceEventFilter(); + virtual bool Includes(const Context& context, + protozero::Field event) const = 0; +}; + +class ConnectedToPackage : public PidFilter { + public: + bool Includes(const Context& context, + uint64_t ts, + int32_t pid) const override; +}; + +class AllowAll : public PidFilter, public FtraceEventFilter { + public: + bool Includes(const Context&, uint64_t, int32_t) const override; + + bool Includes(const Context& context, protozero::Field event) const override; +}; + +} // namespace perfetto::trace_redaction + +#endif // SRC_TRACE_REDACTION_FILTERING_H_ diff --git a/src/trace_redaction/find_package_uid.cc b/src/trace_redaction/find_package_uid.cc index 7b5f20071b..6b7f3a585e 100644 --- a/src/trace_redaction/find_package_uid.cc +++ b/src/trace_redaction/find_package_uid.cc @@ -43,7 +43,6 @@ base::Status FindPackageUid::Collect( return base::OkStatus(); } - // Skip package and move onto the next packet. if (!packet.has_packages_list()) { return base::OkStatus(); } @@ -52,24 +51,28 @@ base::Status FindPackageUid::Collect( packet.packages_list()); for (auto package = packages_list_decoder.packages(); package; ++package) { - protozero::ProtoDecoder package_decoder(*package); - - auto name = package_decoder.FindField( - protos::pbzero::PackagesList::PackageInfo::kNameFieldNumber); - auto uid = package_decoder.FindField( - protos::pbzero::PackagesList::PackageInfo::kUidFieldNumber); - - if (name.valid() && uid.valid()) { - // Package names should be lowercase, but this check is meant to be more - // forgiving. - if (base::StringView(context->package_name) - .CaseInsensitiveEq(name.as_string())) { - context->package_uid = NormalizeUid(uid.as_uint64()); - return base::OkStatus(); - } + protos::pbzero::PackagesList::PackageInfo::Decoder info(*package); + + if (!info.has_name() || !info.uid()) { + continue; + } + + // Package names should be lowercase, but this check is meant to be more + // forgiving. + base::StringView expected_name(context->package_name.data(), + context->package_name.size()); + base::StringView actual_name(info.name().data, info.name().size); + if (!actual_name.CaseInsensitiveEq(expected_name)) { + continue; } + + // See "trace_redaction_framework.cc" for info.uid() must be normalized. + context->package_uid = NormalizeUid(info.uid()); + return base::OkStatus(); } + // Nothing was found. There should only be one package list, but we keep + // looking just incase. The error case will be handled in End(). return base::OkStatus(); } diff --git a/src/trace_redaction/find_package_uid_unittest.cc b/src/trace_redaction/find_package_uid_unittest.cc index dd8e44f8e6..e772ef26f3 100644 --- a/src/trace_redaction/find_package_uid_unittest.cc +++ b/src/trace_redaction/find_package_uid_unittest.cc @@ -196,7 +196,9 @@ TEST(FindPackageUidTest, FindsUidInPackageList) { ASSERT_OK(status) << status.message(); ASSERT_TRUE(context.package_uid.has_value()); - ASSERT_EQ(NormalizeUid(context.package_uid.value()), NormalizeUid(10205)); + + // context.package_uid should have been normalized already. + ASSERT_EQ(context.package_uid.value(), NormalizeUid(10205)); } TEST(FindPackageUidTest, ContinuesOverNonPackageList) { diff --git a/src/trace_redaction/main.cc b/src/trace_redaction/main.cc index eec75fd6b3..bc34340eee 100644 --- a/src/trace_redaction/main.cc +++ b/src/trace_redaction/main.cc @@ -16,29 +16,9 @@ #include "perfetto/base/logging.h" #include "perfetto/base/status.h" -#include "src/trace_redaction/collect_frame_cookies.h" -#include "src/trace_redaction/collect_system_info.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" -#include "src/trace_redaction/filter_print_events.h" -#include "src/trace_redaction/filter_sched_waking_events.h" -#include "src/trace_redaction/filter_task_rename.h" -#include "src/trace_redaction/find_package_uid.h" -#include "src/trace_redaction/populate_allow_lists.h" -#include "src/trace_redaction/prune_package_list.h" -#include "src/trace_redaction/redact_ftrace_event.h" -#include "src/trace_redaction/redact_process_free.h" -#include "src/trace_redaction/redact_sched_switch.h" -#include "src/trace_redaction/redact_task_newtask.h" -#include "src/trace_redaction/remap_scheduling_events.h" -#include "src/trace_redaction/scrub_ftrace_events.h" -#include "src/trace_redaction/scrub_process_stats.h" -#include "src/trace_redaction/scrub_process_trees.h" -#include "src/trace_redaction/scrub_trace_packet.h" -#include "src/trace_redaction/suspend_resume.h" #include "src/trace_redaction/trace_redaction_framework.h" #include "src/trace_redaction/trace_redactor.h" +#include "src/trace_redaction/verify_integrity.h" namespace perfetto::trace_redaction { @@ -46,68 +26,13 @@ namespace perfetto::trace_redaction { static base::Status Main(std::string_view input, std::string_view output, std::string_view package_name) { - TraceRedactor redactor; - - // Add all collectors. - redactor.emplace_collect(); - redactor.emplace_collect(); - redactor.emplace_collect(); - redactor.emplace_collect(); - - // Add all builders. - redactor.emplace_build(); - redactor.emplace_build(); - redactor.emplace_build(); - redactor.emplace_build(); - - // Add all transforms. - auto* scrub_packet = redactor.emplace_transform(); - scrub_packet->emplace_back(); - scrub_packet->emplace_back(); - - auto* scrub_ftrace_events = redactor.emplace_transform(); - scrub_ftrace_events->emplace_back(); - scrub_ftrace_events->emplace_back(); - scrub_ftrace_events->emplace_back(); - scrub_ftrace_events->emplace_back(); - scrub_ftrace_events->emplace_back(); - - // Scrub packets and ftrace events first as they will remove the largest - // chucks of data from the trace. This will reduce the amount of data that the - // other primitives need to operate on. - redactor.emplace_transform(); - redactor.emplace_transform(); - redactor.emplace_transform(); - - auto* redact_ftrace_events = redactor.emplace_transform(); - redact_ftrace_events - ->emplace_back(); - redact_ftrace_events - ->emplace_back(); - redact_ftrace_events - ->emplace_back(); - - // This set of transformations will change pids. This will break the - // connections between pids and the timeline (the synth threads are not in the - // timeline). If a transformation uses the timeline, it must be before this - // transformation. - auto* redact_sched_events = redactor.emplace_transform(); - redact_sched_events->emplace_back(); - redact_sched_events->emplace_back(); - redact_sched_events->emplace_back(); - redact_sched_events->emplace_back< - ThreadMergeDropField::kTaskNewtaskFieldNumber, ThreadMergeDropField>(); - redact_sched_events - ->emplace_back(); + TraceRedactor::Config config; + auto redactor = TraceRedactor::CreateInstance(config); Context context; context.package_name = package_name; - return redactor.Redact(input, output, &context); + return redactor->Redact(input, output, &context); } } // namespace perfetto::trace_redaction diff --git a/src/trace_redaction/merge_threads.cc b/src/trace_redaction/merge_threads.cc new file mode 100644 index 0000000000..48cca0cee6 --- /dev/null +++ b/src/trace_redaction/merge_threads.cc @@ -0,0 +1,48 @@ +/* + * 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/merge_threads.h" + +namespace perfetto::trace_redaction { + +void MergeThreadsPids::Modify(const Context& context, + uint64_t ts, + int32_t cpu, + int32_t* pid, + std::string* comm) const { + // When Modify() is used with RedactFtraceEvents, comm will be null. + PERFETTO_DCHECK(pid); + + // Avoid re-mapping system threads (pid 0). These pids have special uses (e.g. + // cpu_idle) and if re-mapped, important structures break (e.g. remapping + // cpu_idle's pid breaks scheduling). + + if (*pid == 0) { + return; + } + + if (context.timeline->PidConnectsToUid(ts, *pid, *context.package_uid)) { + return; + } + + *pid = context.synthetic_process->RunningOn(cpu); + + if (comm) { + comm->clear(); + } +} + +} // namespace perfetto::trace_redaction diff --git a/src/trace_redaction/filter_task_rename.h b/src/trace_redaction/merge_threads.h similarity index 58% rename from src/trace_redaction/filter_task_rename.h rename to src/trace_redaction/merge_threads.h index 20b119a3eb..ed0a814759 100644 --- a/src/trace_redaction/filter_task_rename.h +++ b/src/trace_redaction/merge_threads.h @@ -14,24 +14,24 @@ * limitations under the License. */ -#ifndef SRC_TRACE_REDACTION_FILTER_TASK_RENAME_H_ -#define SRC_TRACE_REDACTION_FILTER_TASK_RENAME_H_ +#ifndef SRC_TRACE_REDACTION_MERGE_THREADS_H_ +#define SRC_TRACE_REDACTION_MERGE_THREADS_H_ -#include "perfetto/base/status.h" -#include "src/trace_redaction/scrub_ftrace_events.h" +#include "src/trace_redaction/redact_sched_events.h" #include "src/trace_redaction/trace_redaction_framework.h" namespace perfetto::trace_redaction { -// Reject rename events that don't belong to the target package. -class FilterTaskRename final : public FtraceEventFilter { - public: - base::Status VerifyContext(const Context& context) const override; - - bool KeepEvent( - const Context& context, protozero::ConstBytes bytes) const override; +// If 'pid' is not connected to the target package, replace it with a synthetic +// pid. +class MergeThreadsPids : public PidCommModifier { + void Modify(const Context& context, + uint64_t ts, + int32_t cpu, + int32_t* pid, + std::string* comm) const override; }; } // namespace perfetto::trace_redaction -#endif // SRC_TRACE_REDACTION_FILTER_TASK_RENAME_H_ +#endif // SRC_TRACE_REDACTION_MERGE_THREADS_H_ diff --git a/src/trace_redaction/modify.cc b/src/trace_redaction/modify.cc new file mode 100644 index 0000000000..c832645b33 --- /dev/null +++ b/src/trace_redaction/modify.cc @@ -0,0 +1,58 @@ +/* + * 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/modify.h" +#include "src/trace_redaction/proto_util.h" + +namespace perfetto::trace_redaction { + +PidCommModifier::~PidCommModifier() = default; + +FtraceEventModifier::~FtraceEventModifier() = default; + +void ClearComms::Modify(const Context& context, + uint64_t ts, + int32_t, + int32_t* pid, + std::string* comm) const { + PERFETTO_DCHECK(context.timeline); + PERFETTO_DCHECK(context.package_uid.has_value()); + PERFETTO_DCHECK(pid); + PERFETTO_DCHECK(comm); + + if (!context.timeline->PidConnectsToUid(ts, *pid, *context.package_uid)) { + comm->clear(); + } +} + +void DoNothing::Modify(const Context&, + uint64_t, + int32_t, + int32_t*, + std::string*) const {} + +// Because FtraceEventModifier is responsible for modifying and writing +// (compared to PidCommModifier), it needs to pass the value through to the +// message. +void DoNothing::Modify( + const Context&, + const protos::pbzero::FtraceEventBundle::Decoder&, + protozero::Field event, + protos::pbzero::FtraceEventBundle* parent_message) const { + proto_util::AppendField(event, parent_message); +} + +} // namespace perfetto::trace_redaction diff --git a/src/trace_redaction/modify.h b/src/trace_redaction/modify.h new file mode 100644 index 0000000000..75a7be63c7 --- /dev/null +++ b/src/trace_redaction/modify.h @@ -0,0 +1,73 @@ +/* + * 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 + +#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h" +#include "src/trace_redaction/trace_redaction_framework.h" + +#ifndef SRC_TRACE_REDACTION_MODIFY_H_ +#define SRC_TRACE_REDACTION_MODIFY_H_ + +namespace perfetto::trace_redaction { + +class PidCommModifier { + public: + virtual ~PidCommModifier(); + virtual void Modify(const Context& context, + uint64_t ts, + int32_t cpu, + int32_t* pid, + std::string* comm) const = 0; +}; + +class FtraceEventModifier { + public: + virtual ~FtraceEventModifier(); + virtual void Modify(const Context& context, + const protos::pbzero::FtraceEventBundle::Decoder& bundle, + protozero::Field event, + protos::pbzero::FtraceEventBundle* message) const = 0; +}; + +class ClearComms : public PidCommModifier { + public: + void Modify(const Context& context, + uint64_t ts, + int32_t cpu, + int32_t* pid, + std::string* comm) const override; +}; + +// Implementation of every type of modifier, allow any modifier to be assigned +// "Do Nothing" as if it was nullptr. +class DoNothing : public PidCommModifier, public FtraceEventModifier { + public: + void Modify(const Context& context, + uint64_t ts, + int32_t cpu, + int32_t* pid, + std::string* comm) const override; + + void Modify(const Context& context, + const protos::pbzero::FtraceEventBundle::Decoder& bundle, + protozero::Field event, + protos::pbzero::FtraceEventBundle* message) const override; +}; + +} // namespace perfetto::trace_redaction + +#endif // SRC_TRACE_REDACTION_MODIFY_H_ diff --git a/src/trace_redaction/modify_process_trees.cc b/src/trace_redaction/modify_process_trees.cc deleted file mode 100644 index 099890bae0..0000000000 --- a/src/trace_redaction/modify_process_trees.cc +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/trace_redaction/modify_process_trees.h" - -#include - -#include "perfetto/base/status.h" -#include "perfetto/protozero/field.h" -#include "perfetto/protozero/scattered_heap_buffer.h" -#include "src/trace_redaction/proto_util.h" -#include "src/trace_redaction/trace_redaction_framework.h" - -#include "protos/perfetto/trace/ps/process_tree.pbzero.h" -#include "protos/perfetto/trace/trace_packet.pbzero.h" - -namespace perfetto::trace_redaction { - -base::Status ModifyProcessTree::VerifyContext(const Context&) const { - return base::OkStatus(); -} - -base::Status ModifyProcessTree::Transform(const Context& context, - std::string* packet) const { - protozero::ProtoDecoder decoder(*packet); - - auto process_tree = - decoder.FindField(protos::pbzero::TracePacket::kProcessTreeFieldNumber); - - if (!process_tree.valid()) { - return base::OkStatus(); - } - - auto timestamp = - decoder.FindField(protos::pbzero::TracePacket::kTimestampFieldNumber); - - protozero::HeapBuffered packet_message; - - for (auto field = decoder.ReadField(); field.valid(); - field = decoder.ReadField()) { - if (field.id() == protos::pbzero::TracePacket::kProcessTreeFieldNumber) { - TransformProcessTree(context, timestamp, field, - packet_message->set_process_tree()); - } else { - proto_util::AppendField(field, packet_message.get()); - } - } - - packet->assign(packet_message.SerializeAsString()); - - return base::OkStatus(); -} - -void ModifyProcessTree::TransformProcess( - const Context&, - const protozero::Field&, - const protozero::Field& process, - protos::pbzero::ProcessTree* process_tree) const { - PERFETTO_DCHECK(process.id() == - protos::pbzero::ProcessTree::kProcessesFieldNumber); - proto_util::AppendField(process, process_tree); -} - -void ModifyProcessTree::TransformThread( - const Context&, - const protozero::Field&, - const protozero::Field& thread, - protos::pbzero::ProcessTree* process_tree) const { - PERFETTO_DCHECK(thread.id() == - protos::pbzero::ProcessTree::kThreadsFieldNumber); - proto_util::AppendField(thread, process_tree); -} - -void ModifyProcessTree::TransformProcessTree( - const Context& context, - const protozero::Field& timestamp, - const protozero::Field& process_tree, - protos::pbzero::ProcessTree* message) const { - protozero::ProtoDecoder decoder(process_tree.as_bytes()); - - for (auto field = decoder.ReadField(); field.valid(); - field = decoder.ReadField()) { - switch (field.id()) { - case protos::pbzero::ProcessTree::kProcessesFieldNumber: - TransformProcess(context, timestamp, field, message); - break; - - case protos::pbzero::ProcessTree::kThreadsFieldNumber: - TransformThread(context, timestamp, field, message); - break; - - default: - proto_util::AppendField(field, message); - break; - } - } - - // TODO(vaage): Call the handler to add extra fields to the process tree. -} - -} // namespace perfetto::trace_redaction diff --git a/src/trace_redaction/modify_process_trees.h b/src/trace_redaction/modify_process_trees.h deleted file mode 100644 index e36223e5fb..0000000000 --- a/src/trace_redaction/modify_process_trees.h +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef SRC_TRACE_REDACTION_MODIFY_PROCESS_TREES_H_ -#define SRC_TRACE_REDACTION_MODIFY_PROCESS_TREES_H_ - -#include - -#include "perfetto/base/status.h" -#include "src/trace_redaction/trace_redaction_framework.h" - -#include "protos/perfetto/trace/ps/process_tree.pbzero.h" - -namespace perfetto::trace_redaction { - -// Walk through process trees, calling process and thread handlers to add new -// process and threads messages to the process tree. If the default handler is -// not replaced, the thread/process will be added to the parent. -class ModifyProcessTree : public TransformPrimitive { - public: - base::Status Transform(const Context& context, - std::string* packet) const override; - - protected: - // Verifies that the context contains required values. No-op by default. - virtual base::Status VerifyContext(const Context& context) const; - - // Modifies a process before adding it back to the process tree. Appends the - // field to the process tree without modification by default. - virtual void TransformProcess( - const Context& context, - const protozero::Field& timestamp, - const protozero::Field& process, - protos::pbzero::ProcessTree* process_tree) const; - - // Modifies a thread before adding it back to the process tree. Appends the - // field to the process tree without modification by default. - virtual void TransformThread( - const Context& context, - const protozero::Field& timestamp, - const protozero::Field& thread, - protos::pbzero::ProcessTree* process_trees) const; - - // TODO(vaage): Add a handler that is called the process tree is populated so - // that fields can be added to process tree (e.g. creating new threads - - // needed for thread merging). - - private: - void TransformProcessTree(const Context& context, - const protozero::Field& timestamp, - const protozero::Field& process_tree, - protos::pbzero::ProcessTree* message) const; -}; - -} // namespace perfetto::trace_redaction - -#endif // SRC_TRACE_REDACTION_MODIFY_PROCESS_TREES_H_ diff --git a/src/trace_redaction/populate_allow_lists.cc b/src/trace_redaction/populate_allow_lists.cc index 11d895b392..2b8207d829 100644 --- a/src/trace_redaction/populate_allow_lists.cc +++ b/src/trace_redaction/populate_allow_lists.cc @@ -25,64 +25,44 @@ 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 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 - // - // AndroidSystemProperty exposes a key-value pair structure with no - // 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 trace_packets = { - protos::pbzero::TracePacket::kProcessTreeFieldNumber, - protos::pbzero::TracePacket::kProcessStatsFieldNumber, - protos::pbzero::TracePacket::kClockSnapshotFieldNumber, - protos::pbzero::TracePacket::kSysStatsFieldNumber, - protos::pbzero::TracePacket::kTraceConfigFieldNumber, - protos::pbzero::TracePacket::kTraceStatsFieldNumber, - protos::pbzero::TracePacket::kSystemInfoFieldNumber, - protos::pbzero::TracePacket::kTriggerFieldNumber, - protos::pbzero::TracePacket::kCpuInfoFieldNumber, - protos::pbzero::TracePacket::kServiceEventFieldNumber, - protos::pbzero::TracePacket::kInitialDisplayStateFieldNumber, - protos::pbzero::TracePacket::kFrameTimelineEventFieldNumber, - protos::pbzero::TracePacket::kSynchronizationMarkerFieldNumber, - protos::pbzero::TracePacket::kFtraceEventsFieldNumber, - - // Keep the package list. There are some metrics and stdlib queries that - // depend on the package list. - protos::pbzero::TracePacket::kPackagesListFieldNumber, - }; - - for (auto item : trace_packets) { - context->trace_packet_allow_list.insert(item); - } + auto& packet_mask = context->packet_mask; + + // Top-level fields - fields outside of the "oneof data" field. + packet_mask.set( + protos::pbzero::TracePacket::kFirstPacketOnSequenceFieldNumber); + packet_mask.set( + protos::pbzero::TracePacket::kIncrementalStateClearedFieldNumber); + packet_mask.set(protos::pbzero::TracePacket::kInternedDataFieldNumber); + packet_mask.set(protos::pbzero::TracePacket::kMachineIdFieldNumber); + packet_mask.set( + protos::pbzero::TracePacket::kPreviousPacketDroppedFieldNumber); + packet_mask.set(protos::pbzero::TracePacket::kSequenceFlagsFieldNumber); + packet_mask.set(protos::pbzero::TracePacket::kTimestampClockIdFieldNumber); + packet_mask.set(protos::pbzero::TracePacket::kTimestampFieldNumber); + packet_mask.set(protos::pbzero::TracePacket::kTracePacketDefaultsFieldNumber); + packet_mask.set( + protos::pbzero::TracePacket::kTrustedPacketSequenceIdFieldNumber); + packet_mask.set(protos::pbzero::TracePacket::kTrustedPidFieldNumber); + packet_mask.set(protos::pbzero::TracePacket::kTrustedUidFieldNumber); + + // Trace packet data (one-of field) - Every field here should also be modified + // by message-focused transform. + packet_mask.set(protos::pbzero::TracePacket::kClockSnapshotFieldNumber); + packet_mask.set(protos::pbzero::TracePacket::kCpuInfoFieldNumber); + packet_mask.set(protos::pbzero::TracePacket::kFrameTimelineEventFieldNumber); + packet_mask.set(protos::pbzero::TracePacket::kFtraceEventsFieldNumber); + packet_mask.set(protos::pbzero::TracePacket::kInitialDisplayStateFieldNumber); + packet_mask.set(protos::pbzero::TracePacket::kPackagesListFieldNumber); + packet_mask.set(protos::pbzero::TracePacket::kProcessStatsFieldNumber); + packet_mask.set(protos::pbzero::TracePacket::kProcessTreeFieldNumber); + packet_mask.set(protos::pbzero::TracePacket::kServiceEventFieldNumber); + packet_mask.set( + protos::pbzero::TracePacket::kSynchronizationMarkerFieldNumber); + packet_mask.set(protos::pbzero::TracePacket::kSysStatsFieldNumber); + packet_mask.set(protos::pbzero::TracePacket::kSystemInfoFieldNumber); + packet_mask.set(protos::pbzero::TracePacket::kTraceConfigFieldNumber); + packet_mask.set(protos::pbzero::TracePacket::kTraceStatsFieldNumber); + packet_mask.set(protos::pbzero::TracePacket::kTriggerFieldNumber); // FTRACE EVENT NOTES // @@ -96,31 +76,25 @@ base::Status PopulateAllowlists::Build(Context* context) const { // are centrally allocated by the HAL process). We drop them for now as we // don't have the required attribution info in the trace. // - // TODO(vaage): The allowed rss stat events (i.e. kRssStatFieldNumber, - // kRssStatThrottledFieldNumber) are process-scoped. It is non-trivial to - // merge events, so all events outside of the target package should be - // dropped. - // // TODO(vaage): kSchedBlockedReasonFieldNumber contains two pids, an outer // and inner pid. A primitive is needed to further redact these events. - std::initializer_list ftrace_events = { - protos::pbzero::FtraceEvent::kCpuFrequencyFieldNumber, - protos::pbzero::FtraceEvent::kCpuIdleFieldNumber, - protos::pbzero::FtraceEvent::kPrintFieldNumber, - protos::pbzero::FtraceEvent::kRssStatFieldNumber, - protos::pbzero::FtraceEvent::kRssStatThrottledFieldNumber, - protos::pbzero::FtraceEvent::kSchedBlockedReasonFieldNumber, - protos::pbzero::FtraceEvent::kSchedProcessFreeFieldNumber, - protos::pbzero::FtraceEvent::kSchedSwitchFieldNumber, - protos::pbzero::FtraceEvent::kSchedWakingFieldNumber, - protos::pbzero::FtraceEvent::kTaskNewtaskFieldNumber, - protos::pbzero::FtraceEvent::kTaskRenameFieldNumber, - }; - - for (auto item : ftrace_events) { - context->ftrace_packet_allow_list.insert(item); - } + auto& ftrace_masks = context->ftrace_mask; + + ftrace_masks.set(protos::pbzero::FtraceEvent::kCommonFlagsFieldNumber); + ftrace_masks.set(protos::pbzero::FtraceEvent::kCpuFrequencyFieldNumber); + ftrace_masks.set(protos::pbzero::FtraceEvent::kCpuIdleFieldNumber); + ftrace_masks.set(protos::pbzero::FtraceEvent::kPidFieldNumber); + ftrace_masks.set(protos::pbzero::FtraceEvent::kPrintFieldNumber); + ftrace_masks.set(protos::pbzero::FtraceEvent::kRssStatFieldNumber); + ftrace_masks.set(protos::pbzero::FtraceEvent::kRssStatThrottledFieldNumber); + ftrace_masks.set(protos::pbzero::FtraceEvent::kSchedBlockedReasonFieldNumber); + ftrace_masks.set(protos::pbzero::FtraceEvent::kSchedProcessFreeFieldNumber); + ftrace_masks.set(protos::pbzero::FtraceEvent::kSchedSwitchFieldNumber); + ftrace_masks.set(protos::pbzero::FtraceEvent::kSchedWakingFieldNumber); + ftrace_masks.set(protos::pbzero::FtraceEvent::kTaskNewtaskFieldNumber); + ftrace_masks.set(protos::pbzero::FtraceEvent::kTaskRenameFieldNumber); + ftrace_masks.set(protos::pbzero::FtraceEvent::kTimestampFieldNumber); return base::OkStatus(); } diff --git a/src/trace_redaction/process_thread_timeline.cc b/src/trace_redaction/process_thread_timeline.cc index 43d36372e9..a4280739c9 100644 --- a/src/trace_redaction/process_thread_timeline.cc +++ b/src/trace_redaction/process_thread_timeline.cc @@ -45,95 +45,116 @@ void ProcessThreadTimeline::Sort() { mode_ = Mode::kRead; } +const ProcessThreadTimeline::Event* ProcessThreadTimeline::GetOpeningEvent( + uint64_t ts, + int32_t pid) const { + PERFETTO_DCHECK(mode_ == Mode::kRead); + + auto prev_open = QueryLeftMax(ts, pid, Event::Type::kOpen); + auto prev_close = QueryLeftMax(ts, pid, Event::Type::kClose); + + // If there is no open event before ts, it means pid never started. + if (!prev_open) { + return nullptr; + } + + // There is a close event that is strictly between the open event and ts, then + // the pid is considered free. + // + // |---------| ^ : pid is free + // ^ |---------| ^ : pid is free + // ^---------| : pid is active + // |---------^ : pid is active + // |----^----| : pid is active + + // Both open and close are less than or equal to ts (QueryLeftMax). + uint64_t close = prev_close ? prev_close->ts : 0; + uint64_t open = prev_open->ts; + + return close > open && close < ts ? nullptr : prev_open; +} + bool ProcessThreadTimeline::PidConnectsToUid(uint64_t ts, int32_t pid, uint64_t uid) const { PERFETTO_DCHECK(mode_ == Mode::kRead); - auto event = FindPreviousEvent(ts, pid); + const auto* prev_open = QueryLeftMax(ts, pid, Event::Type::kOpen); + const auto* prev_close = QueryLeftMax(ts, pid, Event::Type::kClose); for (size_t d = 0; d < kMaxSearchDepth; ++d) { - // The thread/process was freed. It won't exist until a new open event. - if (event.type != Event::Type::kOpen) { + // If there's no previous open event, it means this pid was never created. + // This should not happen. + if (!prev_open) { return false; } + // This get tricky here. If done wrong, proc_free events will fail because + // they'll report as disconnected when they could be connected to the + // package. Inclusive bounds are used here. In context, if a task_newtask + // event happens at time T, the pid exists at time T. If a proc_free event + // happens at time T, the pid is "shutting down" at time T but still exists. + // + // B E : B = begin + // . . E = end + // . . + // |---------| ^ : pid is free + // ^ |---------| : pid is free + // ^---------| : pid is active + // |---------^ : pid is active + // |----^----| : pid is active + + // By definition, both open and close are less than or equal to ts + // (QueryLeftMax), so problem space is reduces. + auto close = prev_close ? prev_close->ts : 0; + auto open = prev_open->ts; + + if (close > open && close < ts) { + return false; // Close is sitting between open and ts. + } + // TODO(vaage): Normalize the uid values. - if (event.uid == uid) { + if (prev_open->uid == uid) { return true; } - // If there is no parent, there is no way to keep searching. - if (event.ppid == Event::kUnknownPid) { - return false; + if (prev_open->ppid == Event::kUnknownPid) { + return false; // If there is no parent, there is no way to keep + // searching. } - event = FindPreviousEvent(ts, event.ppid); + auto ppid = prev_open->ppid; + + prev_open = QueryLeftMax(ts, ppid, Event::Type::kOpen); + prev_close = QueryLeftMax(ts, ppid, Event::Type::kClose); } return false; } -ProcessThreadTimeline::Event ProcessThreadTimeline::FindPreviousEvent( +const ProcessThreadTimeline::Event* ProcessThreadTimeline::QueryLeftMax( uint64_t ts, - int32_t pid) const { - PERFETTO_DCHECK(mode_ == Mode::kRead); - - Event fake = Event::Close(ts, pid); + int32_t pid, + Event::Type type) const { + auto fake = Event::Close(0, pid); // Events are sorted by pid, creating islands of data. This search is to put // the cursor at the start of pid's island. Each island will be small (a // couple of items), so searching within the islands should be cheap. - auto at = std::lower_bound(events_.begin(), events_.end(), fake, OrderByPid); + auto it = std::lower_bound(events_.begin(), events_.end(), fake, OrderByPid); + auto end = std::upper_bound(events_.begin(), events_.end(), fake, OrderByPid); - // `pid` was not found in `events_`. - if (at == events_.end()) { - return {}; - } - - // "no best option". - Event best = {}; + const Event* best = nullptr; - // Run through all events (related to this pid) and find the last event that - // comes before ts. If the events were in order by time, the search could be - // more efficient, but the gains are margin because: - // - // 1. The number of edge cases go up. - // - // 2. The code is harder to read. - // - // 3. The performance gains are minimal or non-existant because of the small - // number of events. - for (; at != events_.end() && at->pid == pid; ++at) { - // This event is after "now" and can safely be ignored. - if (at->ts > ts) { - continue; - } - - // `at` is know to be before now. So it is always safe to accept an event. - // - // All ts values are positive. However, ts_at and ts_best are both less than - // ts (see early condition), meaning they can be considered negative values. - // - // at best ts - // <---+-----------+-------------+----> - // 31 64 93 - // - // at best ts - // <---+-----------+-------------+----> - // -62 -29 0 - // - // This means that the latest ts value under ts is the closest to ts. + for (; it != end; ++it) { + bool replace = false; - if (best.type == Event::Type::kInvalid || at->ts > best.ts) { - best = *at; + if (it->type == type && it->ts <= ts) { + replace = !best || it->ts > best->ts; } - // This handles the rare edge case where an open and close event occur at - // the same time. The close event must get priority. This is done by - // allowing close events to use ">=" where as other events can only use ">". - if (at->type == Event::Type::kClose && at->ts == best.ts) { - best = *at; + if (replace) { + best = &(*it); } } diff --git a/src/trace_redaction/process_thread_timeline.h b/src/trace_redaction/process_thread_timeline.h index fdb70ba2dc..6352ebef49 100644 --- a/src/trace_redaction/process_thread_timeline.h +++ b/src/trace_redaction/process_thread_timeline.h @@ -99,8 +99,14 @@ class ProcessThreadTimeline { // Returns true if a process/thread is connected to a package. bool PidConnectsToUid(uint64_t ts, int32_t pid, uint64_t uid) const; - // Finds the pid's last event before ts. - Event FindPreviousEvent(uint64_t ts, int32_t pid) const; + // Get the opening event for a pid. If pid ends at ts, an opening event will + // still be returned. + const Event* GetOpeningEvent(uint64_t ts, int32_t pid) const; + + // SELECT MAX(ts), * FROM events WHERE pid=@pid AND type=@type AND ts<=@ts + const Event* QueryLeftMax(uint64_t ts, + int32_t pid, + ProcessThreadTimeline::Event::Type type) const; private: enum class Mode { diff --git a/src/trace_redaction/process_thread_timeline_integrationtest.cc b/src/trace_redaction/process_thread_timeline_integrationtest.cc new file mode 100644 index 0000000000..d88e2c3b6e --- /dev/null +++ b/src/trace_redaction/process_thread_timeline_integrationtest.cc @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include "src/base/test/status_matchers.h" +#include "src/trace_redaction/collect_timeline_events.h" +#include "src/trace_redaction/find_package_uid.h" +#include "src/trace_redaction/trace_redaction_framework.h" +#include "src/trace_redaction/trace_redaction_integration_fixture.h" +#include "src/trace_redaction/trace_redactor.h" +#include "test/gtest_and_gmock.h" + +namespace perfetto::trace_redaction { +namespace { +// Every thread in the package stars before the trace and ends after the +// trace, allowing any time to be used for the query. This time is the +// start time of a slice in the trace. +constexpr uint64_t kTime = 6702094223167642; +} // namespace + +class ProcessThreadTimelineIntegrationTest + : public testing::Test, + protected TraceRedactionIntegrationFixure { + protected: + void SetUp() override { + context_.package_name = "com.Unity.com.unity.multiplayer.samples.coop"; + + trace_redactor_.emplace_collect(); + trace_redactor_.emplace_collect(); + + ASSERT_OK(Redact(trace_redactor_, &context_)); + } + + Context context_; + TraceRedactor trace_redactor_; +}; + +TEST_F(ProcessThreadTimelineIntegrationTest, PackageThreadsAreConnected) { + // select * from thread where upid in ( + // select upid from process where uid in ( + // select uid from package_list where + // package_name='com.Unity.com.unity.multiplayer.samples.coop')) + + auto threads = { + 7105, 7111, 7112, 7113, 7114, 7115, 7116, 7117, 7118, 7119, 7120, + 7124, 7125, 7127, 7129, 7130, 7131, 7132, 7133, 7134, 7135, 7136, + 7137, 7139, 7141, 7142, 7143, 7144, 7145, 7146, 7147, 7148, 7149, + 7150, 7151, 7152, 7153, 7154, 7155, 7156, 7157, 7158, 7159, 7160, + 7161, 7162, 7163, 7164, 7165, 7166, 7167, 7171, 7172, 7174, 7178, + 7180, 7184, 7200, 7945, 7946, 7947, 7948, 7950, 7969, + }; + + for (auto pid : threads) { + // Use EXPECT instead of ASSERT to test all values. + EXPECT_TRUE( + context_.timeline->PidConnectsToUid(kTime, pid, *context_.package_uid)); + } +} + +TEST_F(ProcessThreadTimelineIntegrationTest, MainThreadIsConnected) { + // select * from process where uid in ( + // select uid from package_list where + // package_name='com.Unity.com.unity.multiplayer.samples.coop') + + ASSERT_TRUE( + context_.timeline->PidConnectsToUid(kTime, 7105, *context_.package_uid)); +} + +TEST_F(ProcessThreadTimelineIntegrationTest, + DoesNotConnectDisconnectedMainThread) { + // /vendor/bin/hw/android.hardware.audio.service + // + // select * from thread where upid in ( + // select upid from process where pid=1104) + // + // The audio server, like the targe threads, have no start or end time, so + // using the "whatever" time is okay. Because the audio service is not + // directly or indirectly connected to the target package, no thread should + // test as connected. + + auto threads = { + 1104, 1135, 1142, 1169, 1176, 1602, 1609, 1610, + 1617, 1689, 1690, 1692, 2190, 29650, 23020, + }; + + for (auto pid : threads) { + // Use EXPECT instead of ASSERT to test all values. + EXPECT_FALSE( + context_.timeline->PidConnectsToUid(kTime, pid, *context_.package_uid)); + } +} + +} // namespace perfetto::trace_redaction diff --git a/src/trace_redaction/process_thread_timeline_unittest.cc b/src/trace_redaction/process_thread_timeline_unittest.cc index 4e86a8ab8a..1dc773f1dc 100644 --- a/src/trace_redaction/process_thread_timeline_unittest.cc +++ b/src/trace_redaction/process_thread_timeline_unittest.cc @@ -14,6 +14,7 @@ * limitations under the License. */ +#include #include #include "src/trace_redaction/process_thread_timeline.h" @@ -23,21 +24,6 @@ namespace perfetto::trace_redaction { namespace { -class SliceTestParams { - public: - SliceTestParams(uint64_t ts, int32_t pid, uint64_t uid) - : ts_(ts), pid_(pid), uid_(uid) {} - - uint64_t ts() const { return ts_; } - int32_t pid() const { return pid_; } - uint64_t uid() const { return uid_; } - - private: - uint64_t ts_; - int32_t pid_; - uint64_t uid_; -}; - constexpr uint64_t kTimeA = 0; constexpr uint64_t kTimeB = 10; constexpr uint64_t kTimeC = 20; @@ -103,72 +89,122 @@ class ProcessThreadTimelineTest : public testing::Test { ProcessThreadTimeline timeline_; }; -TEST_F(ProcessThreadTimelineTest, NoEventBeforeFirstSpan) { - auto event = timeline_.FindPreviousEvent(kTimeA, kPidB); - ASSERT_EQ(event, invalid_); -} +TEST_F(ProcessThreadTimelineTest, BeforeSpan) { + auto prev_open = timeline_.QueryLeftMax( + kTimeA, kPidB, ProcessThreadTimeline::Event::Type::kOpen); + ASSERT_FALSE(prev_open); -TEST_F(ProcessThreadTimelineTest, OpenEventAtStartOfFirstSpan) { - auto event = timeline_.FindPreviousEvent(kTimeB, kPidB); - ASSERT_EQ(event, pid_b_events_[0]); + auto prev_close = timeline_.QueryLeftMax( + kTimeA, kPidB, ProcessThreadTimeline::Event::Type::kClose); + ASSERT_FALSE(prev_close); } -TEST_F(ProcessThreadTimelineTest, OpenEventWithinFirstSpan) { - auto event = timeline_.FindPreviousEvent(kTimeC, kPidB); - ASSERT_EQ(event, pid_b_events_[0]); +TEST_F(ProcessThreadTimelineTest, StartOfSpan) { + auto prev_open = timeline_.QueryLeftMax( + kTimeB, kPidB, ProcessThreadTimeline::Event::Type::kOpen); + ASSERT_TRUE(prev_open); + ASSERT_EQ(*prev_open, pid_b_events_[0]); + + auto prev_close = timeline_.QueryLeftMax( + kTimeB, kPidB, ProcessThreadTimeline::Event::Type::kClose); + ASSERT_FALSE(prev_close); } -TEST_F(ProcessThreadTimelineTest, CloseEventAtEndOfFirstSpan) { - auto event = timeline_.FindPreviousEvent(kTimeD, kPidB); - ASSERT_EQ(event, pid_b_events_[1]); +TEST_F(ProcessThreadTimelineTest, DuringSpan) { + auto prev_open = timeline_.QueryLeftMax( + kTimeC, kPidB, ProcessThreadTimeline::Event::Type::kOpen); + ASSERT_TRUE(prev_open); + ASSERT_EQ(*prev_open, pid_b_events_[0]); + + auto prev_close = timeline_.QueryLeftMax( + kTimeC, kPidB, ProcessThreadTimeline::Event::Type::kClose); + ASSERT_FALSE(prev_close); } -TEST_F(ProcessThreadTimelineTest, CloseEventBetweenSpans) { - auto event = timeline_.FindPreviousEvent(kTimeE, kPidB); - ASSERT_EQ(event, pid_b_events_[1]); +TEST_F(ProcessThreadTimelineTest, EndOfSpan) { + auto prev_open = timeline_.QueryLeftMax( + kTimeD, kPidB, ProcessThreadTimeline::Event::Type::kOpen); + ASSERT_TRUE(prev_open); + ASSERT_EQ(*prev_open, pid_b_events_[0]); + + auto prev_close = timeline_.QueryLeftMax( + kTimeD, kPidB, ProcessThreadTimeline::Event::Type::kClose); + ASSERT_TRUE(prev_close); + ASSERT_EQ(*prev_close, pid_b_events_[1]); } -TEST_F(ProcessThreadTimelineTest, OpenEventAtStartOfSecondSpan) { - auto event = timeline_.FindPreviousEvent(kTimeF, kPidB); - ASSERT_EQ(event, pid_b_events_[2]); +// Even through its after a span, the previous open and close events should be +// openned. +TEST_F(ProcessThreadTimelineTest, AfterSpan) { + auto prev_open = timeline_.QueryLeftMax( + kTimeE, kPidB, ProcessThreadTimeline::Event::Type::kOpen); + ASSERT_TRUE(prev_open); + ASSERT_EQ(*prev_open, pid_b_events_[0]); + + auto prev_close = timeline_.QueryLeftMax( + kTimeE, kPidB, ProcessThreadTimeline::Event::Type::kClose); + ASSERT_TRUE(prev_close); + ASSERT_EQ(*prev_close, pid_b_events_[1]); } -TEST_F(ProcessThreadTimelineTest, OpenEventWithinSecondSpan) { - auto event = timeline_.FindPreviousEvent(kTimeG, kPidB); - ASSERT_EQ(event, pid_b_events_[2]); +// When a pid is reused, the new open event (for the reused pid) should be +// returned, but the close from the previous span should be returned. +TEST_F(ProcessThreadTimelineTest, StartOfSecondSpan) { + auto prev_open = timeline_.QueryLeftMax( + kTimeF, kPidB, ProcessThreadTimeline::Event::Type::kOpen); + ASSERT_TRUE(prev_open); + ASSERT_EQ(*prev_open, pid_b_events_[2]); + + auto prev_close = timeline_.QueryLeftMax( + kTimeF, kPidB, ProcessThreadTimeline::Event::Type::kClose); + ASSERT_TRUE(prev_close); + ASSERT_EQ(*prev_close, pid_b_events_[1]); } -TEST_F(ProcessThreadTimelineTest, CloseEventAtEndOfSecondSpan) { - auto event = timeline_.FindPreviousEvent(kTimeH, kPidB); - ASSERT_EQ(event, pid_b_events_[3]); +// Now that there is a second close event, both open and close events should +// come from the same span. +TEST_F(ProcessThreadTimelineTest, CloseOfSecondSpan) { + auto prev_open = timeline_.QueryLeftMax( + kTimeH, kPidB, ProcessThreadTimeline::Event::Type::kOpen); + ASSERT_TRUE(prev_open); + ASSERT_EQ(*prev_open, pid_b_events_[2]); + + auto prev_close = timeline_.QueryLeftMax( + kTimeH, kPidB, ProcessThreadTimeline::Event::Type::kClose); + ASSERT_TRUE(prev_close); + ASSERT_EQ(*prev_close, pid_b_events_[3]); } -// Pid B is active. But Pid C is not active. At this point, Pid C should report -// as invalid event though another pid is active. -TEST_F(ProcessThreadTimelineTest, InvalidEventWhenAnotherSpanIsActive) { - ASSERT_EQ(timeline_.FindPreviousEvent(kTimeB, kPidB), pid_b_events_[0]); - ASSERT_EQ(timeline_.FindPreviousEvent(kTimeB, kPidC), invalid_); +TEST_F(ProcessThreadTimelineTest, BeforeSpanWithZeroDuration) { + auto prev_open = timeline_.QueryLeftMax( + kTimeA, kPidD, ProcessThreadTimeline::Event::Type::kOpen); + ASSERT_FALSE(prev_open); + + auto prev_close = timeline_.QueryLeftMax( + kTimeA, kPidD, ProcessThreadTimeline::Event::Type::kClose); + ASSERT_FALSE(prev_close); } -// When both pids are active, they should both report as active (using their -// open events). -TEST_F(ProcessThreadTimelineTest, ConcurrentSpansBothReportAsActive) { - ASSERT_EQ(timeline_.FindPreviousEvent(kTimeC, kPidB), pid_b_events_[0]); - ASSERT_EQ(timeline_.FindPreviousEvent(kTimeC, kPidC), pid_c_events_[0]); +TEST_F(ProcessThreadTimelineTest, SpanWithZeroDuration) { + auto prev_open = timeline_.QueryLeftMax( + kTimeC, kPidD, ProcessThreadTimeline::Event::Type::kOpen); + ASSERT_TRUE(prev_open); + ASSERT_EQ(*prev_open, pid_d_events_[0]); + + auto prev_close = timeline_.QueryLeftMax( + kTimeC, kPidD, ProcessThreadTimeline::Event::Type::kClose); + ASSERT_TRUE(prev_close); + ASSERT_EQ(*prev_close, pid_d_events_[1]); } -// There are three test cases here: -// -// 1. Before open/close -// 2. At open/close -// 3. After open/close -// -// Normally these would be tree different test cases, but the naming gets -// complicated, so it is easier to do it in one case. -TEST_F(ProcessThreadTimelineTest, ZeroDuration) { - ASSERT_EQ(timeline_.FindPreviousEvent(kTimeB, kPidD), invalid_); - ASSERT_EQ(timeline_.FindPreviousEvent(kTimeC, kPidD), pid_d_events_[1]); - ASSERT_EQ(timeline_.FindPreviousEvent(kTimeD, kPidD), pid_d_events_[1]); +TEST_F(ProcessThreadTimelineTest, AfterSpanWithZeroDuration) { + auto prev_open = timeline_.QueryLeftMax( + kTimeE, kPidD, ProcessThreadTimeline::Event::Type::kOpen); + ASSERT_TRUE(prev_open); + + auto prev_close = timeline_.QueryLeftMax( + kTimeE, kPidD, ProcessThreadTimeline::Event::Type::kClose); + ASSERT_TRUE(prev_close); } // |----- UID A -----| |----- UID C -----| diff --git a/src/trace_redaction/prune_package_list.cc b/src/trace_redaction/prune_package_list.cc index 2dab1fce8a..478d7739e9 100644 --- a/src/trace_redaction/prune_package_list.cc +++ b/src/trace_redaction/prune_package_list.cc @@ -26,21 +26,6 @@ #include "protos/perfetto/trace/android/packages_list.pbzero.h" namespace perfetto::trace_redaction { -namespace { - -bool ShouldKeepPackageInfo(protozero::Field package_info, uint64_t uid) { - PERFETTO_DCHECK(package_info.id() == - protos::pbzero::PackagesList::kPackagesFieldNumber); - - protozero::ProtoDecoder decoder(package_info.as_bytes()); - auto uid_field = decoder.FindField( - protos::pbzero::PackagesList::PackageInfo::kUidFieldNumber); - - return uid_field.valid() && - NormalizeUid(uid_field.as_uint64()) == NormalizeUid(uid); -} - -} // namespace base::Status PrunePackageList::Transform(const Context& context, std::string* packet) const { @@ -48,49 +33,60 @@ base::Status PrunePackageList::Transform(const Context& context, return base::ErrStatus("PrunePackageList: missing package uid."); } - protozero::ProtoDecoder packet_decoder(*packet); + protozero::ProtoDecoder decoder(*packet); protos::pbzero::TracePacket::Decoder trace_packet_decoder(*packet); - auto package_list = packet_decoder.FindField( - protos::pbzero::TracePacket::kPackagesListFieldNumber); + auto package_list = + decoder.FindField(protos::pbzero::TracePacket::kPackagesListFieldNumber); if (!package_list.valid()) { return base::OkStatus(); } - auto uid = context.package_uid.value(); - protozero::HeapBuffered packet_message; - for (auto packet_field = packet_decoder.ReadField(); packet_field.valid(); - packet_field = packet_decoder.ReadField()) { - if (packet_field.id() != - protos::pbzero::TracePacket::kPackagesListFieldNumber) { - proto_util::AppendField(packet_field, packet_message.get()); - continue; + for (auto field = decoder.ReadField(); field.valid(); + field = decoder.ReadField()) { + if (field.id() == protos::pbzero::TracePacket::kPackagesListFieldNumber) { + OnPackageList(context, field.as_bytes(), + packet_message->set_packages_list()); + } else { + proto_util::AppendField(field, packet_message.get()); } + } - auto* package_list_message = packet_message->set_packages_list(); + packet->assign(packet_message.SerializeAsString()); - protozero::ProtoDecoder package_list_decoder(packet_field.as_bytes()); + return base::OkStatus(); +} - for (auto package_field = package_list_decoder.ReadField(); - package_field.valid(); - package_field = package_list_decoder.ReadField()) { - // If not packages, keep. - // If packages and uid matches, keep. - if (package_field.id() != - protos::pbzero::PackagesList::kPackagesFieldNumber || - ShouldKeepPackageInfo(package_field, uid)) { - proto_util::AppendField(package_field, package_list_message); +void PrunePackageList::OnPackageList( + const Context& context, + protozero::ConstBytes bytes, + protos::pbzero::PackagesList* message) const { + PERFETTO_DCHECK(message); + + protozero::ProtoDecoder decoder(bytes); + + for (auto field = decoder.ReadField(); field.valid(); + field = decoder.ReadField()) { + if (field.id() == protos::pbzero::PackagesList::kPackagesFieldNumber) { + // The package uid should already be normalized (see + // find_package_info.cc). + // + // If there are more than one package entry (see + // trace_redaction_framework.h for more details), we need to match all + // instances here because retained processes will reference them. + protos::pbzero::PackagesList::PackageInfo::Decoder info(field.as_bytes()); + + if (info.has_uid() && NormalizeUid(info.uid()) == context.package_uid) { + proto_util::AppendField(field, message); } + } else { + proto_util::AppendField(field, message); } } - - packet->assign(packet_message.SerializeAsString()); - - return base::OkStatus(); } } // namespace perfetto::trace_redaction diff --git a/src/trace_redaction/prune_package_list.h b/src/trace_redaction/prune_package_list.h index ff5f060b81..8a9effe8e0 100644 --- a/src/trace_redaction/prune_package_list.h +++ b/src/trace_redaction/prune_package_list.h @@ -30,6 +30,11 @@ class PrunePackageList final : public TransformPrimitive { public: base::Status Transform(const Context& context, std::string* packet) const override; + + private: + void OnPackageList(const Context& context, + protozero::ConstBytes bytes, + protos::pbzero::PackagesList* message) const; }; } // namespace perfetto::trace_redaction diff --git a/src/trace_redaction/prune_package_list_integrationtest.cc b/src/trace_redaction/prune_package_list_integrationtest.cc index 8fd056aa8c..9f98c295a7 100644 --- a/src/trace_redaction/prune_package_list_integrationtest.cc +++ b/src/trace_redaction/prune_package_list_integrationtest.cc @@ -51,10 +51,10 @@ class PrunePackageListIntegrationTest protected TraceRedactionIntegrationFixure { protected: void SetUp() override { - context()->package_name = kPackageName; + context_.package_name = kPackageName; - trace_redactor()->emplace_collect(); - trace_redactor()->emplace_transform(); + trace_redactor_.emplace_collect(); + trace_redactor_.emplace_transform(); } std::vector GetPackageInfo( @@ -91,6 +91,9 @@ class PrunePackageListIntegrationTest return names; } + + Context context_; + TraceRedactor trace_redactor_; }; // It is possible for two packages_list to appear in the trace. The @@ -99,15 +102,14 @@ class PrunePackageListIntegrationTest // packages_list to contain copies of each other - for example // "com.Unity.com.unity.multiplayer.samples.coop" appears in both packages_list. TEST_F(PrunePackageListIntegrationTest, FindsPackageAndFiltersPackageList) { - auto result = Redact(); + auto result = Redact(trace_redactor_, &context_); ASSERT_OK(result) << result.message(); auto after_raw_trace = LoadRedacted(); ASSERT_OK(after_raw_trace) << after_raw_trace.status().message(); - ASSERT_TRUE(context()->package_uid.has_value()); - ASSERT_EQ(NormalizeUid(context()->package_uid.value()), - NormalizeUid(kPackageUid)); + ASSERT_TRUE(context_.package_uid.has_value()); + ASSERT_EQ(*context_.package_uid, kPackageUid); protos::pbzero::Trace::Decoder redacted_trace(after_raw_trace.value()); auto packages = GetPackageInfo(redacted_trace); @@ -119,7 +121,7 @@ TEST_F(PrunePackageListIntegrationTest, FindsPackageAndFiltersPackageList) { ASSERT_EQ(package.name(), kPackageName); ASSERT_TRUE(package.has_uid()); - ASSERT_EQ(NormalizeUid(package.uid()), NormalizeUid(kPackageUid)); + ASSERT_EQ(NormalizeUid(package.uid()), kPackageUid); } } @@ -128,9 +130,9 @@ TEST_F(PrunePackageListIntegrationTest, FindsPackageAndFiltersPackageList) { // the package list, so there is no way to differentiate these packages (only // the uid is used later), so each entry should remain. TEST_F(PrunePackageListIntegrationTest, RetainsAllInstancesOfUid) { - context()->package_name = "com.google.android.networkstack.tethering"; + context_.package_name = "com.google.android.networkstack.tethering"; - auto result = Redact(); + auto result = Redact(trace_redactor_, &context_); ASSERT_OK(result) << result.message(); auto after_raw_trace = LoadRedacted(); diff --git a/src/trace_redaction/redact_ftrace_event.cc b/src/trace_redaction/redact_ftrace_event.cc deleted file mode 100644 index a16a90d2f4..0000000000 --- a/src/trace_redaction/redact_ftrace_event.cc +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include - -#include "perfetto/base/status.h" -#include "perfetto/protozero/scattered_heap_buffer.h" -#include "src/trace_redaction/proto_util.h" -#include "src/trace_redaction/redact_ftrace_event.h" -#include "src/trace_redaction/trace_redaction_framework.h" - -#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h" - -namespace perfetto::trace_redaction { - -FtraceEventRedaction::~FtraceEventRedaction() = default; - -base::Status RedactFtraceEvent::Transform(const Context& context, - std::string* packet) const { - protozero::HeapBuffered message; - - protozero::ProtoDecoder decoder(*packet); - - // Treat FtraceEvents (bundle) as a special case. - for (auto f = decoder.ReadField(); f.valid(); f = decoder.ReadField()) { - if (f.id() == protos::pbzero::TracePacket::kFtraceEventsFieldNumber) { - RedactEvents(context, f, message->set_ftrace_events()); - } else { - proto_util::AppendField(f, message.get()); - } - } - - packet->assign(message.SerializeAsString()); - - return base::OkStatus(); -} - -// Iterate over every field in FtraceEvents (bundle), treating FtraceEvent as a -// special case (calls the correct redaction). -void RedactFtraceEvent::RedactEvents( - const Context& context, - protozero::Field bundle, - protos::pbzero::FtraceEventBundle* message) const { - PERFETTO_DCHECK(bundle.id() == - protos::pbzero::TracePacket::kFtraceEventsFieldNumber); - - // There is only one bundle per packet, so creating the bundle decoder is an - // "okay" expense. - protos::pbzero::FtraceEventBundle::Decoder bundle_decoder(bundle.as_bytes()); - - // Even through we have `bundle_decoder` create a simpler decoder to iterate - // over every field. - protozero::ProtoDecoder decoder(bundle.as_bytes()); - - // Treat FtraceEvent as a special case. - for (auto f = decoder.ReadField(); f.valid(); f = decoder.ReadField()) { - if (f.id() == protos::pbzero::FtraceEventBundle::kEventFieldNumber) { - RedactEvent(context, bundle_decoder, f, message->add_event()); - } else { - proto_util::AppendField(f, message); - } - } -} - -void RedactFtraceEvent::RedactEvent( - const Context& context, - const protos::pbzero::FtraceEventBundle::Decoder& bundle, - protozero::Field event, - protos::pbzero::FtraceEvent* message) const { - PERFETTO_DCHECK(event.id() == - protos::pbzero::FtraceEventBundle::kEventFieldNumber); - - // A modifier can/will change the decoder by calling ReadField(). To avoid a - // modifier from interfering with the this function's loop, a reusable decoder - // is used for each modifier call. - protozero::ProtoDecoder outer_decoder(event.as_bytes()); - protozero::ProtoDecoder inner_decoder(event.as_bytes()); - - // If there is a handler for a field, treat it as a special case. - for (auto f = outer_decoder.ReadField(); f.valid(); - f = outer_decoder.ReadField()) { - auto* mod = redactions_.Find(f.id()); - if (mod && mod->get()) { - // Reset the decoder so that it appears like a "new" decoder to the - // modifier. - inner_decoder.Reset(); - mod->get()->Redact(context, bundle, inner_decoder, message); - } else { - proto_util::AppendField(f, message); - } - } -} -} // namespace perfetto::trace_redaction diff --git a/src/trace_redaction/redact_ftrace_event.h b/src/trace_redaction/redact_ftrace_event.h deleted file mode 100644 index 9de6cc683d..0000000000 --- a/src/trace_redaction/redact_ftrace_event.h +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef SRC_TRACE_REDACTION_REDACT_FTRACE_EVENT_H_ -#define SRC_TRACE_REDACTION_REDACT_FTRACE_EVENT_H_ - -#include -#include -#include - -#include "perfetto/base/status.h" -#include "perfetto/ext/base/flat_hash_map.h" -#include "perfetto/protozero/field.h" -#include "src/trace_redaction/trace_redaction_framework.h" - -#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h" -#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h" - -namespace perfetto::trace_redaction { - -// Redaction and "scrubing" are two different operations. Scrubbing removes the -// whole event. Redaction removes fields from with-in the event, but keeps the -// event in the trace. -class FtraceEventRedaction { - public: - virtual ~FtraceEventRedaction(); - - // Write at most one field from `event` to `event_message`. This relies on the - // honor system; other redactions may be registered on other values. - // - // - event: effectively "protos::pbzero::FtraceEvent::Decoder" - virtual base::Status Redact( - const Context& context, - const protos::pbzero::FtraceEventBundle::Decoder& bundle, - protozero::ProtoDecoder& event, - protos::pbzero::FtraceEvent* event_message) const = 0; -}; - -class RedactFtraceEvent : public TransformPrimitive { - public: - base::Status Transform(const Context& context, - std::string* packet) const override; - - // Add a new redaction. T must extend FtraceEventRedaction. This relies on the - // honor system; no more than one redaction can be mapped to a field. - template - void emplace_back() { - redactions_.Insert(field_id, std::make_unique()); - } - - private: - void RedactEvents(const Context& context, - protozero::Field bundle, - protos::pbzero::FtraceEventBundle* message) const; - - void RedactEvent(const Context& context, - const protos::pbzero::FtraceEventBundle::Decoder& bundle, - protozero::Field event, - protos::pbzero::FtraceEvent* message) const; - - base::FlatHashMap> - redactions_; -}; - -} // namespace perfetto::trace_redaction - -#endif // SRC_TRACE_REDACTION_REDACT_FTRACE_EVENT_H_ diff --git a/src/trace_redaction/redact_ftrace_events.cc b/src/trace_redaction/redact_ftrace_events.cc new file mode 100644 index 0000000000..123e122b76 --- /dev/null +++ b/src/trace_redaction/redact_ftrace_events.cc @@ -0,0 +1,184 @@ +/* + * 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/redact_ftrace_events.h" + +#include + +#include "perfetto/protozero/scattered_heap_buffer.h" +#include "src/trace_processor/util/status_macros.h" +#include "src/trace_redaction/proto_util.h" + +#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h" +#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h" +#include "protos/perfetto/trace/ftrace/power.pbzero.h" +#include "protos/perfetto/trace/trace.pbzero.h" + +namespace perfetto::trace_redaction { + +FtraceEventFilter::~FtraceEventFilter() = default; + +bool FilterFtraceUsingSuspendResume::Includes(const Context&, + protozero::Field event) const { + // Values are taken from "suspend_period.textproto". These values would + // ideally be provided via the context, but until there are multiple sources, + // they can be here. + constexpr std::string_view kSyscoreSuspend = "syscore_suspend"; + constexpr std::string_view kSyscoreResume = "syscore_resume"; + constexpr std::string_view kTimekeepingFreeze = "timekeeping_freeze"; + + protozero::ProtoDecoder event_decoder(event.as_bytes()); + + // It's not a suspend-resume event, defer the decision to another filter. + auto suspend_resume = event_decoder.FindField( + protos::pbzero::FtraceEvent::kSuspendResumeFieldNumber); + + if (!suspend_resume.valid()) { + return true; + } + + protozero::ProtoDecoder suspend_resume_decoder(suspend_resume.as_bytes()); + + auto action = suspend_resume_decoder.FindField( + protos::pbzero::SuspendResumeFtraceEvent::kActionFieldNumber); + + // If a suspend-resume has no action, there is nothing to redact, so it is + // safe to passthrough. + if (!action.valid()) { + return true; + } + + std::string_view action_str(action.as_string().data, action.size()); + + return kSyscoreSuspend == action_str || kSyscoreResume == action_str || + kTimekeepingFreeze == action_str; +} + +bool FilterRss::Includes(const Context& context, protozero::Field event) const { + protos::pbzero::FtraceEvent::Decoder event_decoder(event.as_bytes()); + + if (event_decoder.has_rss_stat_throttled() || event_decoder.has_rss_stat()) { + // The event's pid is unsigned, but tids are always signed. + auto pid = static_cast(event_decoder.pid()); + return context.timeline->PidConnectsToUid(event_decoder.timestamp(), pid, + *context.package_uid); + } + + return true; +} + +base::Status RedactFtraceEvents::Transform(const Context& context, + std::string* packet) const { + if (packet == nullptr || packet->empty()) { + return base::ErrStatus("RedactFtraceEvents: null or empty packet."); + } + + protozero::ProtoDecoder packet_decoder(*packet); + auto ftrace_events = packet_decoder.FindField( + protos::pbzero::TracePacket::kFtraceEventsFieldNumber); + + if (!ftrace_events.valid()) { + return base::OkStatus(); + } + + protozero::ProtoDecoder decoder(*packet); + + protozero::HeapBuffered message; + + for (auto it = decoder.ReadField(); it.valid(); it = decoder.ReadField()) { + if (it.id() == protos::pbzero::TracePacket::kFtraceEventsFieldNumber) { + RETURN_IF_ERROR( + OnFtraceEvents(context, it, message->set_ftrace_events())); + } else { + proto_util::AppendField(it, message.get()); + } + } + + packet->assign(message.SerializeAsString()); + + return base::OkStatus(); +} + +base::Status RedactFtraceEvents::OnFtraceEvents( + const Context& context, + protozero::Field ftrace_events, + protos::pbzero::FtraceEventBundle* message) const { + // If there are N ftrace events, and all N events are passed to the modifier, + // it is far better to have the bundle fully decoded ahead of time. + protos::pbzero::FtraceEventBundle::Decoder bundle(ftrace_events.as_bytes()); + + if (!bundle.has_cpu()) { + return base::ErrStatus( + "RedactFtraceEvents: missing field FtraceEventBundle::kCpu."); + } + + protozero::ProtoDecoder decoder(ftrace_events.as_bytes()); + + for (auto it = decoder.ReadField(); it.valid(); it = decoder.ReadField()) { + if (it.id() == protos::pbzero::FtraceEventBundle::kEventFieldNumber) { + OnFtraceEvent(context, bundle, it, message); + } else { + proto_util::AppendField(it, message); + } + } + + return base::OkStatus(); +} + +void RedactFtraceEvents::OnFtraceEvent( + const Context& context, + const protos::pbzero::FtraceEventBundle::Decoder& bundle, + protozero::Field event, + protos::pbzero::FtraceEventBundle* parent_message) const { + PERFETTO_DCHECK(filter_); + PERFETTO_DCHECK(modifier_); + + if (event.id() != protos::pbzero::FtraceEventBundle::kEventFieldNumber) { + proto_util::AppendField(event, parent_message); + return; + } + + protozero::ProtoDecoder decoder(event.as_bytes()); + + auto ts_field = + decoder.FindField(protos::pbzero::FtraceEvent::kTimestampFieldNumber); + PERFETTO_DCHECK(ts_field.valid()); + + auto pid_field = + decoder.FindField(protos::pbzero::FtraceEvent::kPidFieldNumber); + PERFETTO_DCHECK(pid_field.valid()); + + if (!filter_->Includes(context, event)) { + return; + } + + auto cpu = static_cast(bundle.cpu()); + auto pid = pid_field.as_int32(); + + modifier_->Modify(context, ts_field.as_uint64(), cpu, &pid, nullptr); + + auto* message = parent_message->add_event(); + + for (auto it = decoder.ReadField(); it.valid(); it = decoder.ReadField()) { + if (it.id() == protos::pbzero::FtraceEvent::kPidFieldNumber) { + message->set_pid(static_cast(pid)); + } else { + proto_util::AppendField(it, message); + } + } +} + +} // namespace perfetto::trace_redaction diff --git a/src/trace_redaction/redact_ftrace_events.h b/src/trace_redaction/redact_ftrace_events.h new file mode 100644 index 0000000000..f4006e9aee --- /dev/null +++ b/src/trace_redaction/redact_ftrace_events.h @@ -0,0 +1,79 @@ +/* + * 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_REDACT_FTRACE_EVENTS_H_ +#define SRC_TRACE_REDACTION_REDACT_FTRACE_EVENTS_H_ + +#include + +#include "perfetto/protozero/field.h" +#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h" +#include "src/trace_redaction/redact_sched_events.h" +#include "src/trace_redaction/trace_redaction_framework.h" + +namespace perfetto::trace_redaction { + +class FilterFtraceUsingSuspendResume : public FtraceEventFilter { + public: + bool Includes(const Context& context, protozero::Field event) const override; +}; + +// Discard all rss events not belonging to the target package. +class FilterRss : public FtraceEventFilter { + public: + bool Includes(const Context& context, protozero::Field event) const override; +}; + +// Filters ftrace events and modifies remaining events before writing them to +// the packet. Only one filter and/or writer can be assigned to provide finer +// grain control. +class RedactFtraceEvents : public TransformPrimitive { + public: + base::Status Transform(const Context& context, + std::string* packet) const override; + + // Selects which ftrace events should be redacted. All non-ftrace events are + // appended to the new packet. + template + void emplace_ftrace_filter() { + filter_ = std::make_unique(); + } + + // For ftrace events that pass the filter, they go through this modifier which + // will optionally modify the event before adding it to the event bundle (or + // even drop it). + template + void emplace_post_filter_modifier() { + modifier_ = std::make_unique(); + } + + private: + base::Status OnFtraceEvents(const Context& context, + protozero::Field ftrace_events, + protos::pbzero::FtraceEventBundle* message) const; + + void OnFtraceEvent(const Context& context, + const protos::pbzero::FtraceEventBundle::Decoder& bundle, + protozero::Field event, + protos::pbzero::FtraceEventBundle* parent_message) const; + + std::unique_ptr filter_; + std::unique_ptr modifier_; +}; + +} // namespace perfetto::trace_redaction + +#endif // SRC_TRACE_REDACTION_REDACT_FTRACE_EVENTS_H_ diff --git a/src/trace_redaction/redact_process_events.cc b/src/trace_redaction/redact_process_events.cc new file mode 100644 index 0000000000..47013ef636 --- /dev/null +++ b/src/trace_redaction/redact_process_events.cc @@ -0,0 +1,452 @@ +/* + * 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/redact_process_events.h" + +#include + +#include "perfetto/protozero/scattered_heap_buffer.h" +#include "src/trace_processor/util/status_macros.h" +#include "src/trace_redaction/proto_util.h" + +#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h" +#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h" +#include "protos/perfetto/trace/ftrace/power.pbzero.h" +#include "protos/perfetto/trace/ftrace/sched.pbzero.h" +#include "protos/perfetto/trace/ftrace/task.pbzero.h" + +namespace perfetto::trace_redaction { + +base::Status RedactProcessEvents::Transform(const Context& context, + std::string* packet) const { + PERFETTO_DCHECK(modifier_); + PERFETTO_DCHECK(filter_); + + if (!context.timeline) { + return base::ErrStatus("RedactProcessEvents: missing timeline."); + } + + if (!context.package_uid.has_value()) { + return base::ErrStatus("RedactProcessEvents: missing package uid."); + } + + if (!packet || packet->empty()) { + return base::ErrStatus("RedactProcessEvents: null or empty packet."); + } + + protozero::ProtoDecoder packet_decoder(*packet); + + protozero::HeapBuffered message; + + for (auto it = packet_decoder.ReadField(); it.valid(); + it = packet_decoder.ReadField()) { + if (it.id() == protos::pbzero::TracePacket::kFtraceEventsFieldNumber) { + RETURN_IF_ERROR( + OnFtraceEvents(context, it.as_bytes(), message->set_ftrace_events())); + } else { + proto_util::AppendField(it, message.get()); + } + } + + packet->assign(message.SerializeAsString()); + return base::OkStatus(); +} + +base::Status RedactProcessEvents::OnFtraceEvents( + const Context& context, + protozero::ConstBytes bytes, + protos::pbzero::FtraceEventBundle* message) const { + protozero::ProtoDecoder decoder(bytes); + + auto cpu = + decoder.FindField(protos::pbzero::FtraceEventBundle::kCpuFieldNumber); + + std::string shared_comm; + + for (auto it = decoder.ReadField(); it.valid(); it = decoder.ReadField()) { + if (it.id() == protos::pbzero::FtraceEventBundle::kEventFieldNumber) { + RETURN_IF_ERROR(OnFtraceEvent(context, cpu.as_int32(), it.as_bytes(), + &shared_comm, message->add_event())); + } else { + proto_util::AppendField(it, message); + } + } + + return base::OkStatus(); +} + +base::Status RedactProcessEvents::OnFtraceEvent( + const Context& context, + int32_t cpu, + protozero::ConstBytes bytes, + std::string* shared_comm, + protos::pbzero::FtraceEvent* message) const { + PERFETTO_DCHECK(shared_comm); + PERFETTO_DCHECK(message); + + protozero::ProtoDecoder decoder(bytes); + + auto ts = + decoder.FindField(protos::pbzero::FtraceEvent::kTimestampFieldNumber); + + if (!ts.valid()) { + return base::ErrStatus("RedactProcessEvents: missing FtraceEvent %d", + protos::pbzero::FtraceEvent::kTimestampFieldNumber); + } + + for (auto it = decoder.ReadField(); it.valid(); it = decoder.ReadField()) { + switch (it.id()) { + case protos::pbzero::FtraceEvent::kSchedProcessFreeFieldNumber: + RETURN_IF_ERROR(OnProcessFree(context, ts.as_uint64(), cpu, + it.as_bytes(), shared_comm, message)); + break; + case protos::pbzero::FtraceEvent::kTaskNewtaskFieldNumber: + RETURN_IF_ERROR(OnNewTask(context, ts.as_uint64(), cpu, it.as_bytes(), + shared_comm, message)); + break; + case protos::pbzero::FtraceEvent::kTaskRenameFieldNumber: + RETURN_IF_ERROR(OnProcessRename(context, ts.as_uint64(), cpu, + it.as_bytes(), shared_comm, message)); + break; + case protos::pbzero::FtraceEvent::kPrintFieldNumber: + RETURN_IF_ERROR(OnPrint(context, ts.as_uint64(), bytes, message)); + break; + case protos::pbzero::FtraceEvent::kSuspendResumeFieldNumber: + RETURN_IF_ERROR( + OnSuspendResume(context, ts.as_uint64(), bytes, message)); + break; + case protos::pbzero::FtraceEvent::kSchedBlockedReasonFieldNumber: + RETURN_IF_ERROR( + OnSchedBlockedReason(context, ts.as_uint64(), bytes, message)); + break; + default: + proto_util::AppendField(it, message); + break; + } + } + + return base::OkStatus(); +} + +base::Status RedactProcessEvents::OnProcessFree( + const Context& context, + uint64_t ts, + int32_t cpu, + protozero::ConstBytes bytes, + std::string* shared_comm, + protos::pbzero::FtraceEvent* parent_message) const { + PERFETTO_DCHECK(shared_comm); + PERFETTO_DCHECK(parent_message); + + protos::pbzero::SchedProcessFreeFtraceEvent::Decoder decoder(bytes); + + if (!decoder.has_pid()) { + return base::ErrStatus( + "RedactProcessEvents: missing SchedProcessFreeFtraceEvent %d", + protos::pbzero::SchedProcessFreeFtraceEvent::kPidFieldNumber); + } + + if (!decoder.has_comm()) { + return base::ErrStatus( + "RedactProcessEvents: missing SchedProcessFreeFtraceEvent %d", + protos::pbzero::SchedProcessFreeFtraceEvent::kCommFieldNumber); + } + + if (!decoder.has_prio()) { + return base::ErrStatus( + "RedactProcessEvents: missing SchedProcessFreeFtraceEvent %d", + protos::pbzero::SchedProcessFreeFtraceEvent::kPrioFieldNumber); + } + + auto pid = decoder.pid(); + auto comm = decoder.comm(); + auto prio = decoder.prio(); + + PERFETTO_DCHECK(filter_); + if (!filter_->Includes(context, ts, pid)) { + return base::OkStatus(); + } + + shared_comm->assign(comm.data, comm.size); + + PERFETTO_DCHECK(modifier_); + modifier_->Modify(context, ts, cpu, &pid, shared_comm); + + auto* message = parent_message->set_sched_process_free(); + message->set_pid(pid); + message->set_comm(*shared_comm); + message->set_prio(prio); + + return base::OkStatus(); +} + +base::Status RedactProcessEvents::OnNewTask( + const Context& context, + uint64_t ts, + int32_t cpu, + protozero::ConstBytes bytes, + std::string* shared_comm, + protos::pbzero::FtraceEvent* parent_message) const { + PERFETTO_DCHECK(shared_comm); + PERFETTO_DCHECK(parent_message); + + protos::pbzero::TaskNewtaskFtraceEvent::Decoder decoder(bytes); + + if (!decoder.has_clone_flags()) { + return base::ErrStatus( + "RedactProcessEvents: missing TaskNewtaskFtraceEvent %d", + protos::pbzero::TaskNewtaskFtraceEvent::kCloneFlagsFieldNumber); + } + + if (!decoder.has_comm()) { + return base::ErrStatus( + "RedactProcessEvents: missing TaskNewtaskFtraceEvent %d", + protos::pbzero::TaskNewtaskFtraceEvent::kCommFieldNumber); + } + + if (!decoder.has_oom_score_adj()) { + return base::ErrStatus( + "RedactProcessEvents: missing TaskNewtaskFtraceEvent %d", + protos::pbzero::TaskNewtaskFtraceEvent::kOomScoreAdjFieldNumber); + } + + if (!decoder.has_pid()) { + return base::ErrStatus( + "RedactProcessEvents: missing TaskNewtaskFtraceEvent %d", + protos::pbzero::TaskNewtaskFtraceEvent::kPidFieldNumber); + } + + auto clone_flags = decoder.clone_flags(); + auto comm = decoder.comm(); + auto omm_score_adj = decoder.oom_score_adj(); + auto pid = decoder.pid(); + + PERFETTO_DCHECK(filter_); + if (!filter_->Includes(context, ts, pid)) { + return base::OkStatus(); + } + + shared_comm->assign(comm.data, comm.size); + + PERFETTO_DCHECK(modifier_); + modifier_->Modify(context, ts, cpu, &pid, shared_comm); + + auto* message = parent_message->set_task_newtask(); + message->set_clone_flags(clone_flags); + message->set_comm(*shared_comm); + message->set_oom_score_adj(omm_score_adj); + message->set_pid(pid); + + return base::OkStatus(); +} + +base::Status RedactProcessEvents::OnProcessRename( + const Context& context, + uint64_t ts, + int32_t cpu, + protozero::ConstBytes bytes, + std::string* shared_comm, + protos::pbzero::FtraceEvent* parent_message) const { + PERFETTO_DCHECK(shared_comm); + PERFETTO_DCHECK(parent_message); + + protos::pbzero::TaskRenameFtraceEvent::Decoder decoder(bytes); + + if (!decoder.has_pid()) { + return base::ErrStatus( + "RedactProcessEvents: missing TaskRenameFtraceEvent %d", + protos::pbzero::TaskRenameFtraceEvent::kPidFieldNumber); + } + + if (!decoder.has_newcomm()) { + return base::ErrStatus( + "RedactProcessEvents: missing TaskRenameFtraceEvent %d", + protos::pbzero::TaskRenameFtraceEvent::kNewcommFieldNumber); + } + + if (!decoder.has_oldcomm()) { + return base::ErrStatus( + "RedactProcessEvents: missing TaskRenameFtraceEvent %d", + protos::pbzero::TaskRenameFtraceEvent::kOldcommFieldNumber); + } + + if (!decoder.has_oom_score_adj()) { + return base::ErrStatus( + "RedactProcessEvents: missing TaskRenameFtraceEvent %d", + protos::pbzero::TaskRenameFtraceEvent::kOomScoreAdjFieldNumber); + } + + auto pid = decoder.pid(); + auto new_comm = decoder.newcomm(); + auto old_comm = decoder.oldcomm(); + auto oom_score_adj = decoder.oom_score_adj(); + + PERFETTO_DCHECK(filter_); + if (!filter_->Includes(context, ts, pid)) { + return base::OkStatus(); + } + + auto* message = parent_message->set_task_rename(); + + auto noop_pid = pid; + + shared_comm->assign(old_comm.data, old_comm.size); + + PERFETTO_DCHECK(modifier_); + modifier_->Modify(context, ts, cpu, &noop_pid, shared_comm); + + // Write the old-comm now so shared_comm can be used new-comm. + message->set_oldcomm(*shared_comm); + + shared_comm->assign(new_comm.data, new_comm.size); + + PERFETTO_DCHECK(modifier_); + modifier_->Modify(context, ts, cpu, &pid, shared_comm); + + message->set_newcomm(*shared_comm); + + // Because the same modification is used for each comm, the resulting pids + // should be the same. + PERFETTO_DCHECK(noop_pid == pid); + + message->set_pid(pid); + message->set_oom_score_adj(oom_score_adj); + + return base::OkStatus(); +} + +base::Status RedactProcessEvents::OnPrint( + const Context& context, + uint64_t ts, + protozero::ConstBytes event_bytes, + protos::pbzero::FtraceEvent* parent_message) const { + PERFETTO_DCHECK(parent_message); + + protozero::ProtoDecoder decoder(event_bytes); + + auto pid = decoder.FindField(protos::pbzero::FtraceEvent::kPidFieldNumber); + if (!pid.valid()) { + return base::ErrStatus("RedactProcessEvents: missing FtraceEvent %u", + pid.id()); + } + + auto print = + decoder.FindField(protos::pbzero::FtraceEvent::kPrintFieldNumber); + if (!print.valid()) { + return base::ErrStatus("RedactProcessEvents: missing FtraceEvent %u", + print.id()); + } + + if (filter_->Includes(context, ts, pid.as_int32())) { + proto_util::AppendField(print, parent_message); + } + + return base::OkStatus(); +} + +base::Status RedactProcessEvents::OnSuspendResume( + const Context& context, + uint64_t ts, + protozero::ConstBytes event_bytes, + protos::pbzero::FtraceEvent* parent_message) const { + PERFETTO_DCHECK(parent_message); + + // Values are taken from "suspend_period.textproto". These values would + // ideally be provided via the context, but until there are multiple sources, + // they can be here. + constexpr std::array kValidActions = { + "syscore_suspend", "syscore_resume", "timekeeping_freeze"}; + + protozero::ProtoDecoder decoder(event_bytes); + + auto pid = decoder.FindField(protos::pbzero::FtraceEvent::kPidFieldNumber); + if (!pid.valid()) { + return base::ErrStatus("RedactProcessEvents: missing FtraceEvent::kPid"); + } + + auto suspend_resume_field = + decoder.FindField(protos::pbzero::FtraceEvent::kSuspendResumeFieldNumber); + if (!suspend_resume_field.valid()) { + return base::ErrStatus( + "RedactProcessEvents: missing FtraceEvent::kSuspendResume"); + } + + protos::pbzero::SuspendResumeFtraceEvent::Decoder suspend_resume( + suspend_resume_field.as_bytes()); + + auto action = suspend_resume.action(); + std::string_view action_str(action.data, action.size); + + // Do the allow list first because it should be cheaper (e.g. array look-up vs + // timeline query). + if (std::find(kValidActions.begin(), kValidActions.end(), action_str) != + kValidActions.end()) { + if (filter_->Includes(context, ts, pid.as_int32())) { + proto_util::AppendField(suspend_resume_field, parent_message); + } + } + + return base::OkStatus(); +} + +base::Status RedactProcessEvents::OnSchedBlockedReason( + const Context& context, + uint64_t ts, + protozero::ConstBytes event_bytes, + protos::pbzero::FtraceEvent* parent_message) const { + PERFETTO_DCHECK(parent_message); + + protos::pbzero::FtraceEvent::Decoder decoder(event_bytes); + + auto pid = decoder.FindField(protos::pbzero::FtraceEvent::kPidFieldNumber); + if (!pid.valid()) { + return base::ErrStatus("RedactProcessEvents: missing FtraceEvent::kPid"); + } + + auto blocked_reason_field = decoder.FindField( + protos::pbzero::FtraceEvent::kSchedBlockedReasonFieldNumber); + if (!blocked_reason_field.valid()) { + return base::ErrStatus( + "RedactProcessEvents: missing FtraceEvent::kSchedBlockedReason"); + } + + protos::pbzero::SchedBlockedReasonFtraceEvent::Decoder blocking_reason( + blocked_reason_field.as_bytes()); + + auto has_fields = { + blocking_reason.has_caller(), + blocking_reason.has_io_wait(), + blocking_reason.has_pid(), + }; + + if (std::find(has_fields.begin(), has_fields.end(), false) != + has_fields.end()) { + return base::ErrStatus( + "RedactProcessEvents: missing SchedBlockedReasonFtraceEvent::*"); + } + + // The semantics here is similar to waking events (i.e. event.pid is the + // blocker, and sched_blocked_reason.pid is the blockee). + // sched_blocked_reason.pid only has meaning when the pid is not merged. If + // pid was merged, it could have conflicting blocking events. + if (filter_->Includes(context, ts, blocking_reason.pid())) { + proto_util::AppendField(blocked_reason_field, parent_message); + } + + return base::OkStatus(); +} + +} // namespace perfetto::trace_redaction diff --git a/src/trace_redaction/redact_process_events.h b/src/trace_redaction/redact_process_events.h new file mode 100644 index 0000000000..8445790281 --- /dev/null +++ b/src/trace_redaction/redact_process_events.h @@ -0,0 +1,120 @@ +/* + * 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_REDACT_PROCESS_EVENTS_H_ +#define SRC_TRACE_REDACTION_REDACT_PROCESS_EVENTS_H_ + +#include + +#include "src/trace_redaction/redact_sched_events.h" +#include "src/trace_redaction/trace_redaction_framework.h" + +#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h" + +namespace perfetto::trace_redaction { + +// Goes through a trace packet and filters: +// +// - task_rename +// - task_newtask +// - sched_process_free +// - print +// +// Goes through a trace packet and modifies pid and comm: +// +// - task_newtask +// - sched_process_free +// - task_rename +// +// 'print' does not support modification. +// +// These operations are separate from the scheduling events in an effort to make +// the code easier to understand, however they use the same filter and modifier +// types and should have the same values when used together. +class RedactProcessEvents : public TransformPrimitive { + public: + base::Status Transform(const Context& context, + std::string* packet) const override; + + template + void emplace_modifier() { + modifier_ = std::make_unique(); + } + + template + void emplace_filter() { + filter_ = std::make_unique(); + } + + private: + base::Status OnFtraceEvents(const Context& context, + protozero::ConstBytes bytes, + protos::pbzero::FtraceEventBundle* message) const; + + base::Status OnFtraceEvent(const Context& context, + int32_t cpu, + protozero::ConstBytes bytes, + std::string* shared_comm, + protos::pbzero::FtraceEvent* message) const; + + base::Status OnProcessFree(const Context& context, + uint64_t ts, + int32_t cpu, + protozero::ConstBytes bytes, + std::string* shared_comm, + protos::pbzero::FtraceEvent* parent_message) const; + + base::Status OnNewTask(const Context& context, + uint64_t ts, + int32_t cpu, + protozero::ConstBytes bytes, + std::string* shared_comm, + protos::pbzero::FtraceEvent* parent_message) const; + + base::Status OnProcessRename( + const Context& context, + uint64_t ts, + int32_t cpu, + protozero::ConstBytes bytes, + std::string* shared_comm, + protos::pbzero::FtraceEvent* parent_message) const; + + // Unlike the other On* functions, this one required the event's byte buffer + // because it needs the pid from it. + base::Status OnPrint(const Context& context, + uint64_t ts, + protozero::ConstBytes event_bytes, + protos::pbzero::FtraceEvent* parent_message) const; + + base::Status OnSuspendResume( + const Context& context, + uint64_t ts, + protozero::ConstBytes event_bytes, + protos::pbzero::FtraceEvent* parent_message) const; + + base::Status OnSchedBlockedReason( + const Context& context, + uint64_t ts, + protozero::ConstBytes event_bytes, + protos::pbzero::FtraceEvent* parent_message) const; + + std::unique_ptr modifier_; + std::unique_ptr filter_; +}; + +} // namespace perfetto::trace_redaction + +#endif // SRC_TRACE_REDACTION_REDACT_PROCESS_EVENTS_H_ diff --git a/src/trace_redaction/redact_process_events_unittest.cc b/src/trace_redaction/redact_process_events_unittest.cc new file mode 100644 index 0000000000..90086bc308 --- /dev/null +++ b/src/trace_redaction/redact_process_events_unittest.cc @@ -0,0 +1,821 @@ +/* + * 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/redact_process_events.h" +#include "src/base/test/status_matchers.h" +#include "test/gtest_and_gmock.h" + +#include "protos/perfetto/trace/ftrace/ftrace.gen.h" +#include "protos/perfetto/trace/ftrace/ftrace_event.gen.h" +#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.gen.h" +#include "protos/perfetto/trace/ftrace/power.gen.h" +#include "protos/perfetto/trace/ftrace/sched.gen.h" +#include "protos/perfetto/trace/ftrace/task.gen.h" +#include "protos/perfetto/trace/trace.gen.h" +#include "protos/perfetto/trace/trace_packet.gen.h" + +namespace perfetto::trace_redaction { + +namespace { +constexpr uint64_t kCpu = 1; + +constexpr uint64_t kUidA = 1; +constexpr uint64_t kUidB = 2; + +constexpr int32_t kNoParent = 10; +constexpr int32_t kPidA = 11; +constexpr int32_t kPidB = 12; + +// Used as a child of kPidA. +constexpr int32_t kPidAA = kPidA * 10; + +constexpr uint64_t kTimeA = 0; +constexpr uint64_t kTimeB = 1000; + +constexpr std::string_view kCommA = "comm-a"; +constexpr std::string_view kCommB = "comm-b"; +} // namespace + +class RedactProcessEventsTest : public testing::Test { + protected: + void SetUp() { + redact_.emplace_modifier(); + redact_.emplace_filter(); + } + + RedactProcessEvents redact_; +}; + +TEST_F(RedactProcessEventsTest, RejectMissingPackageUid) { + Context context; + context.timeline = std::make_unique(); + + protos::gen::TracePacket packet; + auto packet_str = packet.SerializeAsString(); + + ASSERT_FALSE(redact_.Transform(context, &packet_str).ok()); +} + +TEST_F(RedactProcessEventsTest, RejectMissingTimeline) { + Context context; + context.package_uid = kUidA; + + protos::gen::TracePacket packet; + auto packet_str = packet.SerializeAsString(); + + ASSERT_FALSE(redact_.Transform(context, &packet_str).ok()); +} + +TEST_F(RedactProcessEventsTest, RejectMissingPacket) { + Context context; + context.package_uid = kUidA; + context.timeline = std::make_unique(); + + ASSERT_FALSE(redact_.Transform(context, nullptr).ok()); +} + +TEST_F(RedactProcessEventsTest, EmptyMissingPacket) { + Context context; + context.package_uid = kUidA; + context.timeline = std::make_unique(); + + std::string packet_str; + + ASSERT_FALSE(redact_.Transform(context, &packet_str).ok()); +} + +// Tests which nested messages and fields are removed. +class RedactNewTaskTest : public testing::Test { + protected: + void SetUp() override { + auto* events = packet_.mutable_ftrace_events(); + events->set_cpu(kCpu); + + auto* event = events->add_event(); + event->set_timestamp(kTimeB); + event->set_pid(kPidA); + + auto* new_task = event->mutable_task_newtask(); + new_task->set_clone_flags(0); + new_task->set_comm(std::string(kCommA)); + new_task->set_oom_score_adj(0); + new_task->set_pid(kPidA); + + // This test breaks the rules for task_newtask and the timeline. The + // timeline will report the task existing before the new task event. This + // should not happen in the field, but it makes the test more robust. + + context_.timeline = std::make_unique(); + + context_.timeline->Append( + ProcessThreadTimeline::Event::Open(kTimeA, kPidA, kNoParent, kUidA)); + context_.timeline->Append( + ProcessThreadTimeline::Event::Open(kTimeA, kPidB, kNoParent, kUidB)); + context_.timeline->Sort(); + + redact_.emplace_modifier(); + redact_.emplace_filter(); + } + + RedactProcessEvents redact_; + protos::gen::TracePacket packet_; + Context context_; +}; + +TEST_F(RedactNewTaskTest, KeepCommInPackage) { + redact_.emplace_modifier(); + + // The new task is for Pid A. Pid A is part of Uid A. Keep Uid A; keep new + // task. + context_.package_uid = kUidA; + + auto packet_str = packet_.SerializeAsString(); + ASSERT_OK(redact_.Transform(context_, &packet_str)); + + protos::gen::TracePacket packet; + ASSERT_TRUE(packet.ParseFromString(packet_str)); + + ASSERT_TRUE(packet.has_ftrace_events()); + ASSERT_EQ(packet.ftrace_events().event().size(), 1u); + + const auto& event = packet.ftrace_events().event().at(0); + ASSERT_TRUE(event.has_task_newtask()); + + const auto& new_task = event.task_newtask(); + + ASSERT_TRUE(new_task.has_pid()); + ASSERT_EQ(new_task.pid(), kPidA); + + ASSERT_TRUE(new_task.has_comm()); + ASSERT_EQ(new_task.comm(), kCommA); +} + +TEST_F(RedactNewTaskTest, ClearCommOutsidePackage) { + redact_.emplace_modifier(); + + // The new task is for Pid A. Pid A is part of Uid A. Keep Uid B; clear the + // comm. + context_.package_uid = kUidB; + + auto packet_str = packet_.SerializeAsString(); + ASSERT_OK(redact_.Transform(context_, &packet_str)); + + protos::gen::TracePacket packet; + ASSERT_TRUE(packet.ParseFromString(packet_str)); + + ASSERT_TRUE(packet.has_ftrace_events()); + ASSERT_EQ(packet.ftrace_events().event().size(), 1u); + + const auto& event = packet.ftrace_events().event().at(0); + ASSERT_TRUE(event.has_task_newtask()); + + const auto& new_task = event.task_newtask(); + + ASSERT_TRUE(new_task.has_pid()); + ASSERT_EQ(new_task.pid(), kPidA); + + ASSERT_TRUE(new_task.has_comm()); + ASSERT_TRUE(new_task.comm().empty()); +} + +TEST_F(RedactNewTaskTest, KeepTaskInPackage) { + redact_.emplace_filter(); + + // The new task is for Pid A. Pid A is part of Uid A. Keep Uid A; keep new + // task. + context_.package_uid = kUidA; + + auto packet_str = packet_.SerializeAsString(); + ASSERT_OK(redact_.Transform(context_, &packet_str)); + + protos::gen::TracePacket packet; + ASSERT_TRUE(packet.ParseFromString(packet_str)); + + ASSERT_TRUE(packet.has_ftrace_events()); + ASSERT_EQ(packet.ftrace_events().event().size(), 1u); + + const auto& event = packet.ftrace_events().event().at(0); + ASSERT_TRUE(event.has_task_newtask()); + + const auto& new_task = event.task_newtask(); + + ASSERT_TRUE(new_task.has_pid()); + ASSERT_EQ(new_task.pid(), kPidA); +} + +TEST_F(RedactNewTaskTest, DropTaskOutsidePackage) { + redact_.emplace_filter(); + + // The new task is for Pid A. Pid A is part of Uid A. Keep Uid B; drop new + // task event. + context_.package_uid = kUidB; + + auto packet_str = packet_.SerializeAsString(); + ASSERT_OK(redact_.Transform(context_, &packet_str)); + + protos::gen::TracePacket packet; + ASSERT_TRUE(packet.ParseFromString(packet_str)); + + ASSERT_TRUE(packet.has_ftrace_events()); + ASSERT_EQ(packet.ftrace_events().event().size(), 1u); + + // The task should have been removed, but the event will still remain. + ASSERT_FALSE(packet.ftrace_events().event().at(0).has_task_newtask()); +} + +class RedactProcessFree : public testing::Test { + protected: + void SetUp() override { + auto* events = packet_.mutable_ftrace_events(); + events->set_cpu(kCpu); + + auto* event = events->add_event(); + event->set_timestamp(kTimeB); + event->set_pid(kPidA); + + auto* process_free = event->mutable_sched_process_free(); + process_free->set_comm(std::string(kCommA)); + process_free->set_pid(kPidA); + process_free->set_prio(0); + + // By default, this timeline is invalid. sched_process_free would insert + // close events. If sched_process_free appended at time B a close event + // would be created at time B. + // + // Timeline spans are inclusive-start but exclusive-end, so a + // sched_process_free will never pass a "connected to package" test. The + // timeline is created to make testing easier. + // + // If a test wants a "valid" timeline, it should add a close event at + // sched_process_free. + + context_.timeline = std::make_unique(); + + context_.timeline->Append( + ProcessThreadTimeline::Event::Open(kTimeA, kPidA, kNoParent, kUidA)); + context_.timeline->Append( + ProcessThreadTimeline::Event::Open(kTimeA, kPidB, kNoParent, kUidB)); + context_.timeline->Sort(); + + redact_.emplace_modifier(); + redact_.emplace_filter(); + } + + RedactProcessEvents redact_; + protos::gen::TracePacket packet_; + Context context_; +}; + +TEST_F(RedactProcessFree, KeepsCommInPackage) { + redact_.emplace_modifier(); + + // The new task is for Pid A. Pid A is part of Uid A. Keep Uid A; keep comm. + context_.package_uid = kUidA; + + auto packet_str = packet_.SerializeAsString(); + ASSERT_OK(redact_.Transform(context_, &packet_str)); + + protos::gen::TracePacket packet; + ASSERT_TRUE(packet.ParseFromString(packet_str)); + + ASSERT_TRUE(packet.has_ftrace_events()); + ASSERT_EQ(packet.ftrace_events().event().size(), 1u); + + const auto& event = packet.ftrace_events().event().at(0); + ASSERT_TRUE(event.has_sched_process_free()); + + const auto& process_free = event.sched_process_free(); + + ASSERT_TRUE(process_free.has_pid()); + ASSERT_EQ(process_free.pid(), kPidA); + + ASSERT_TRUE(process_free.has_comm()); + ASSERT_EQ(process_free.comm(), kCommA); +} + +TEST_F(RedactProcessFree, DropsCommOutsidePackage) { + redact_.emplace_modifier(); + + // The new task is for Pid A. Pid A is part of Uid A. Keep Uid B; drop comm. + context_.package_uid = kUidB; + + auto packet_str = packet_.SerializeAsString(); + ASSERT_OK(redact_.Transform(context_, &packet_str)); + + protos::gen::TracePacket packet; + ASSERT_TRUE(packet.ParseFromString(packet_str)); + + ASSERT_TRUE(packet.has_ftrace_events()); + ASSERT_EQ(packet.ftrace_events().event().size(), 1u); + + const auto& event = packet.ftrace_events().event().at(0); + ASSERT_TRUE(event.has_sched_process_free()); + + const auto& process_free = event.sched_process_free(); + + ASSERT_TRUE(process_free.has_pid()); + ASSERT_EQ(process_free.pid(), kPidA); + + ASSERT_TRUE(process_free.has_comm()); + ASSERT_TRUE(process_free.comm().empty()); +} + +TEST_F(RedactProcessFree, KeepsCommAtProcessFree) { + redact_.emplace_modifier(); + + // The new task is for Pid A. Pid A is part of Uid A. Keep Uid A; Process free + // marks the end of Pid A, but process free is inclusive and Pid A is only + // free after the event. + context_.package_uid = kUidA; + + context_.timeline->Append(ProcessThreadTimeline::Event::Close(kTimeB, kPidA)); + context_.timeline->Sort(); + + auto packet_str = packet_.SerializeAsString(); + ASSERT_OK(redact_.Transform(context_, &packet_str)); + + protos::gen::TracePacket packet; + ASSERT_TRUE(packet.ParseFromString(packet_str)); + + ASSERT_TRUE(packet.has_ftrace_events()); + ASSERT_EQ(packet.ftrace_events().event().size(), 1u); + + const auto& event = packet.ftrace_events().event().at(0); + ASSERT_TRUE(event.has_sched_process_free()); + + const auto& process_free = event.sched_process_free(); + + ASSERT_TRUE(process_free.has_pid()); + ASSERT_EQ(process_free.pid(), kPidA); + + ASSERT_TRUE(process_free.has_comm()); + ASSERT_EQ(process_free.comm(), kCommA); +} + +TEST_F(RedactProcessFree, KeepTaskInPackage) { + redact_.emplace_filter(); + + // The new task is for Pid A. Pid A is part of Uid A. Keep Uid A; keep new + // task. + context_.package_uid = kUidA; + + auto packet_str = packet_.SerializeAsString(); + ASSERT_OK(redact_.Transform(context_, &packet_str)); + + protos::gen::TracePacket packet; + ASSERT_TRUE(packet.ParseFromString(packet_str)); + + ASSERT_TRUE(packet.has_ftrace_events()); + ASSERT_EQ(packet.ftrace_events().event().size(), 1u); + + const auto& event = packet.ftrace_events().event().at(0); + ASSERT_TRUE(event.has_sched_process_free()); + + const auto& process_free = event.sched_process_free(); + + ASSERT_TRUE(process_free.has_pid()); + ASSERT_EQ(process_free.pid(), kPidA); +} + +TEST_F(RedactProcessFree, DropTaskOutsidePackage) { + redact_.emplace_filter(); + + // The new task is for Pid A. Pid A is part of Uid A. Keep Uid B; drop new + // task event. + context_.package_uid = kUidB; + + auto packet_str = packet_.SerializeAsString(); + ASSERT_OK(redact_.Transform(context_, &packet_str)); + + protos::gen::TracePacket packet; + ASSERT_TRUE(packet.ParseFromString(packet_str)); + + ASSERT_TRUE(packet.has_ftrace_events()); + ASSERT_EQ(packet.ftrace_events().event().size(), 1u); + + // The task should have been removed, but the event will still remain. + ASSERT_FALSE(packet.ftrace_events().event().at(0).has_sched_process_free()); +} + +class RedactRenameTest : public testing::Test { + protected: + void SetUp() { + auto* events = packet_.mutable_ftrace_events(); + events->set_cpu(kCpu); + + auto* event = events->add_event(); + event->set_timestamp(kTimeB); + event->set_pid(kPidA); + + // The rename event pid will match the ftrace event pid. + auto* rename = event->mutable_task_rename(); + rename->set_newcomm(std::string(kCommB)); + rename->set_oldcomm(std::string(kCommA)); + rename->set_pid(kPidA); + rename->set_oom_score_adj(0); + + context_.timeline = std::make_unique(); + + context_.timeline->Append( + ProcessThreadTimeline::Event::Open(kTimeA, kPidA, kNoParent, kUidA)); + context_.timeline->Append( + ProcessThreadTimeline::Event::Open(kTimeA, kPidB, kNoParent, kUidB)); + context_.timeline->Sort(); + + redact_.emplace_modifier(); + redact_.emplace_filter(); + } + + RedactProcessEvents redact_; + protos::gen::TracePacket packet_; + Context context_; +}; + +TEST_F(RedactRenameTest, KeepCommInsidePackage) { + redact_.emplace_modifier(); + + // The rename task is for Pid A. Pid A is part of Uid A. Keep Uid A; keep + // comm. + context_.package_uid = kUidA; + + auto packet_str = packet_.SerializeAsString(); + ASSERT_OK(redact_.Transform(context_, &packet_str)); + + protos::gen::TracePacket packet; + ASSERT_TRUE(packet.ParseFromString(packet_str)); + + ASSERT_TRUE(packet.has_ftrace_events()); + ASSERT_EQ(packet.ftrace_events().event().size(), 1u); + + const auto& event = packet.ftrace_events().event().at(0); + ASSERT_TRUE(event.has_task_rename()); + + const auto& task_rename = event.task_rename(); + + ASSERT_TRUE(task_rename.has_pid()); + ASSERT_EQ(task_rename.pid(), kPidA); + + ASSERT_TRUE(task_rename.has_oldcomm()); + ASSERT_EQ(task_rename.oldcomm(), kCommA); + + ASSERT_TRUE(task_rename.has_newcomm()); + ASSERT_EQ(task_rename.newcomm(), kCommB); +} + +TEST_F(RedactRenameTest, ClearCommOutsidePackage) { + redact_.emplace_modifier(); + + // The rename task is for Pid A. Pid A is part of Uid A. Keep Uid B; drop + // comm. + context_.package_uid = kUidB; + + auto packet_str = packet_.SerializeAsString(); + ASSERT_OK(redact_.Transform(context_, &packet_str)); + + protos::gen::TracePacket packet; + ASSERT_TRUE(packet.ParseFromString(packet_str)); + + ASSERT_TRUE(packet.has_ftrace_events()); + ASSERT_EQ(packet.ftrace_events().event().size(), 1u); + + const auto& event = packet.ftrace_events().event().at(0); + ASSERT_TRUE(event.has_task_rename()); + + const auto& task_rename = event.task_rename(); + + ASSERT_TRUE(task_rename.has_pid()); + ASSERT_EQ(task_rename.pid(), kPidA); + + ASSERT_TRUE(task_rename.has_oldcomm()); + ASSERT_TRUE(task_rename.oldcomm().empty()); + + ASSERT_TRUE(task_rename.has_newcomm()); + ASSERT_TRUE(task_rename.newcomm().empty()); +} + +TEST_F(RedactRenameTest, KeepTaskInsidePackage) { + redact_.emplace_filter(); + + // The rename task is for Pid A. Pid A is part of Uid A. Keep Uid A; keep + // comm. + context_.package_uid = kUidA; + + auto packet_str = packet_.SerializeAsString(); + ASSERT_OK(redact_.Transform(context_, &packet_str)); + + protos::gen::TracePacket packet; + ASSERT_TRUE(packet.ParseFromString(packet_str)); + + ASSERT_TRUE(packet.has_ftrace_events()); + ASSERT_EQ(packet.ftrace_events().event().size(), 1u); + + const auto& event = packet.ftrace_events().event().at(0); + ASSERT_TRUE(event.has_task_rename()); +} + +TEST_F(RedactRenameTest, DropTaskOutsidePackage) { + redact_.emplace_filter(); + + // The rename task is for Pid A. Pid A is part of Uid A. Keep Uid B; drop + // task. + context_.package_uid = kUidB; + + auto packet_str = packet_.SerializeAsString(); + ASSERT_OK(redact_.Transform(context_, &packet_str)); + + protos::gen::TracePacket packet; + ASSERT_TRUE(packet.ParseFromString(packet_str)); + + ASSERT_TRUE(packet.has_ftrace_events()); + ASSERT_EQ(packet.ftrace_events().event().size(), 1u); + + // The task should have been removed, but the event will still remain. + const auto& event = packet.ftrace_events().event().at(0); + ASSERT_FALSE(event.has_task_rename()); +} + +class RedactPrintTest : public testing::Test { + protected: + void SetUp() { + auto* events = packet_.mutable_ftrace_events(); + events->set_cpu(kCpu); + + auto* event = events->add_event(); + event->set_timestamp(kTimeB); + event->set_pid(kPidA); + + // The rename event pid will match the ftrace event pid. + auto* print = event->mutable_print(); + print->set_buf(std::string(kCommA)); + print->set_ip(0); + + context_.timeline = std::make_unique(); + + context_.timeline->Append( + ProcessThreadTimeline::Event::Open(kTimeA, kPidA, kNoParent, kUidA)); + context_.timeline->Append( + ProcessThreadTimeline::Event::Open(kTimeA, kPidB, kNoParent, kUidB)); + context_.timeline->Sort(); + + redact_.emplace_modifier(); + redact_.emplace_filter(); + } + + RedactProcessEvents redact_; + protos::gen::TracePacket packet_; + Context context_; +}; + +TEST_F(RedactPrintTest, KeepTaskInsidePackage) { + redact_.emplace_filter(); + + // The rename task is for Pid A. Pid A is part of Uid A. Keep Uid A; keep + // comm. + context_.package_uid = kUidA; + + auto packet_str = packet_.SerializeAsString(); + ASSERT_OK(redact_.Transform(context_, &packet_str)); + + protos::gen::TracePacket packet; + ASSERT_TRUE(packet.ParseFromString(packet_str)); + + ASSERT_TRUE(packet.has_ftrace_events()); + ASSERT_EQ(packet.ftrace_events().event().size(), 1u); + + const auto& event = packet.ftrace_events().event().at(0); + ASSERT_TRUE(event.has_print()); +} + +TEST_F(RedactPrintTest, DropTaskOutsidePackage) { + redact_.emplace_filter(); + + // The rename task is for Pid A. Pid A is part of Uid A. Keep Uid B; drop + // task. + context_.package_uid = kUidB; + + auto packet_str = packet_.SerializeAsString(); + ASSERT_OK(redact_.Transform(context_, &packet_str)); + + protos::gen::TracePacket packet; + ASSERT_TRUE(packet.ParseFromString(packet_str)); + + ASSERT_TRUE(packet.has_ftrace_events()); + ASSERT_EQ(packet.ftrace_events().event().size(), 1u); + + // The task should have been removed, but the event will still remain. + const auto& event = packet.ftrace_events().event().at(0); + ASSERT_FALSE(event.has_print()); +} + +class RedactSuspendResumeTest : public testing::Test { + protected: + void SetUp() { + auto* events = packet_.mutable_ftrace_events(); + events->set_cpu(kCpu); + + for (const auto& action : {"syscore_suspend", "syscore_resume", + "timekeeping_freeze", "not-allowed"}) { + auto* event = events->add_event(); + event->set_timestamp(kTimeB); + event->set_pid(kPidA); + + auto* suspend_resume = event->mutable_suspend_resume(); + suspend_resume->set_action(action); + suspend_resume->set_start(0); + suspend_resume->set_val(3); + } + + context_.timeline = std::make_unique(); + + context_.timeline->Append( + ProcessThreadTimeline::Event::Open(kTimeA, kPidA, kNoParent, kUidA)); + context_.timeline->Append( + ProcessThreadTimeline::Event::Open(kTimeA, kPidB, kNoParent, kUidB)); + context_.timeline->Sort(); + + redact_.emplace_modifier(); + redact_.emplace_filter(); + } + + RedactProcessEvents redact_; + protos::gen::TracePacket packet_; + Context context_; +}; + +TEST_F(RedactSuspendResumeTest, KeepTaskInsidePackage) { + redact_.emplace_filter(); + + context_.package_uid = kUidA; + + auto packet_str = packet_.SerializeAsString(); + ASSERT_OK(redact_.Transform(context_, &packet_str)); + + protos::gen::TracePacket packet; + ASSERT_TRUE(packet.ParseFromString(packet_str)); + + ASSERT_TRUE(packet.has_ftrace_events()); + ASSERT_EQ(packet.ftrace_events().event().size(), 4u); +} + +// Only actions in the allowlist should be allowed. +// +// TODO(vaage): The allowlist is not configurable right now. If moved into the +// context, it could be configured. +TEST_F(RedactSuspendResumeTest, FiltersByAllowlist) { + redact_.emplace_filter(); + + context_.package_uid = kUidA; + + auto packet_str = packet_.SerializeAsString(); + ASSERT_OK(redact_.Transform(context_, &packet_str)); + + protos::gen::TracePacket packet; + ASSERT_TRUE(packet.ParseFromString(packet_str)); + + ASSERT_TRUE(packet.has_ftrace_events()); + ASSERT_EQ(packet.ftrace_events().event().size(), 4u); + + { + const auto& event = packet.ftrace_events().event().at(0); + ASSERT_TRUE(event.has_suspend_resume()); + ASSERT_EQ(event.suspend_resume().action(), "syscore_suspend"); + } + + { + const auto& event = packet.ftrace_events().event().at(1); + ASSERT_TRUE(event.has_suspend_resume()); + ASSERT_EQ(event.suspend_resume().action(), "syscore_resume"); + } + + { + const auto& event = packet.ftrace_events().event().at(2); + ASSERT_TRUE(event.has_suspend_resume()); + ASSERT_EQ(event.suspend_resume().action(), "timekeeping_freeze"); + } + + // The fourth entry is an invalid action. While the other entries are valid + // and are retained, this one should be dropped. + ASSERT_FALSE(packet.ftrace_events().event().at(3).has_suspend_resume()); +} + +TEST_F(RedactSuspendResumeTest, DropTaskOutsidePackage) { + redact_.emplace_filter(); + + context_.package_uid = kUidB; + + auto packet_str = packet_.SerializeAsString(); + ASSERT_OK(redact_.Transform(context_, &packet_str)); + + protos::gen::TracePacket packet; + ASSERT_TRUE(packet.ParseFromString(packet_str)); + + ASSERT_TRUE(packet.has_ftrace_events()); + ASSERT_EQ(packet.ftrace_events().event().size(), 4u); + + // The task should have been removed, but the event will still remain. + ASSERT_FALSE(packet.ftrace_events().event().at(0).has_suspend_resume()); + ASSERT_FALSE(packet.ftrace_events().event().at(1).has_suspend_resume()); + ASSERT_FALSE(packet.ftrace_events().event().at(2).has_suspend_resume()); + ASSERT_FALSE(packet.ftrace_events().event().at(3).has_suspend_resume()); +} + +class RedactSchedBlockReasonTest : public testing::Test { + protected: + void SetUp() { + auto* events = packet_.mutable_ftrace_events(); + events->set_cpu(kCpu); + + { + auto* event = events->add_event(); + event->set_timestamp(kTimeB); + event->set_pid(kPidB); + + auto* reason = event->mutable_sched_blocked_reason(); + reason->set_caller(3); + reason->set_io_wait(7); + reason->set_pid(kPidAA); + } + + context_.timeline = std::make_unique(); + + context_.timeline->Append( + ProcessThreadTimeline::Event::Open(kTimeA, kPidA, kNoParent, kUidA)); + + context_.timeline->Append( + ProcessThreadTimeline::Event::Open(kTimeA, kPidAA, kPidA)); + + context_.timeline->Append( + ProcessThreadTimeline::Event::Open(kTimeA, kPidB, kNoParent, kUidB)); + + context_.timeline->Sort(); + + redact_.emplace_modifier(); + redact_.emplace_filter(); + } + + RedactProcessEvents redact_; + protos::gen::TracePacket packet_; + Context context_; +}; + +// Implementation detail: No events are removed, only inner messages. +TEST_F(RedactSchedBlockReasonTest, KeepTaskInsidePackage) { + redact_.emplace_filter(); + + // The blocking events target kPidA is connected to kUidA. Since the target is + // kUidA, the blocking events should be retained. + context_.package_uid = kUidA; + + auto packet_str = packet_.SerializeAsString(); + ASSERT_OK(redact_.Transform(context_, &packet_str)); + + protos::gen::TracePacket packet; + ASSERT_TRUE(packet.ParseFromString(packet_str)); + + ASSERT_TRUE(packet.has_ftrace_events()); + ASSERT_EQ(packet.ftrace_events().event().size(), 1u); + + // Assumption, event order does not change. The first event was connected to + // kUidA. + const auto& event = packet.ftrace_events().event().at(0); + ASSERT_TRUE(event.has_sched_blocked_reason()); + ASSERT_EQ(event.sched_blocked_reason().pid(), kPidAA); +} + +// Implementation detail: No events are removed, only inner messages. +TEST_F(RedactSchedBlockReasonTest, DropTaskOutsidePackage) { + redact_.emplace_filter(); + + // The blocking events target kPidA is connected to kUidA. Since the target is + // kUidB, the blocking events should be dropped. + context_.package_uid = kUidB; + + auto packet_str = packet_.SerializeAsString(); + ASSERT_OK(redact_.Transform(context_, &packet_str)); + + protos::gen::TracePacket packet; + ASSERT_TRUE(packet.ParseFromString(packet_str)); + + ASSERT_TRUE(packet.has_ftrace_events()); + ASSERT_EQ(packet.ftrace_events().event().size(), 1u); + + // Assumption, event order does not change. The first event was connected to + // kUidA. + const auto& event = packet.ftrace_events().event().at(0); + ASSERT_FALSE(event.has_sched_blocked_reason()); +} + +} // namespace perfetto::trace_redaction diff --git a/src/trace_redaction/redact_process_free.cc b/src/trace_redaction/redact_process_free.cc deleted file mode 100644 index 8f5a83e0fb..0000000000 --- a/src/trace_redaction/redact_process_free.cc +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/trace_redaction/redact_process_free.h" - -#include "src/trace_redaction/proto_util.h" - -#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h" -#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h" -#include "protos/perfetto/trace/ftrace/sched.pbzero.h" - -namespace perfetto::trace_redaction { - -// Redact sched_process_free events. -// -// event { -// timestamp: 6702094703928940 -// pid: 10 -// sched_process_free { -// comm: "sh" -// pid: 7973 -// prio: 120 -// } -// } -// -// In the above message, it should be noted that "event.pid" will not be -// equal to "event.sched_process_free.pid". -// -// The timeline treats "start" as inclusive and "end" as exclusive. This means -// no pid will connect to the target package at a process free event. Because -// of this, the timeline is not needed. -base::Status RedactProcessFree::Redact( - const Context&, - const protos::pbzero::FtraceEventBundle::Decoder&, - protozero::ProtoDecoder& event, - protos::pbzero::FtraceEvent* event_message) const { - auto sched_process_free = event.FindField( - protos::pbzero::FtraceEvent::kSchedProcessFreeFieldNumber); - if (!sched_process_free.valid()) { - return base::ErrStatus( - "RedactProcessFree: was used for unsupported field type"); - } - - // SchedProcessFreeFtraceEvent - protozero::ProtoDecoder process_free_decoder(sched_process_free.as_bytes()); - - auto* process_free_message = event_message->set_sched_process_free(); - - // Replace the comm with an empty string instead of dropping the comm field. - // The perfetto UI doesn't render things correctly if comm values are missing. - for (auto field = process_free_decoder.ReadField(); field.valid(); - field = process_free_decoder.ReadField()) { - if (field.id() == - protos::pbzero::SchedProcessFreeFtraceEvent::kCommFieldNumber) { - process_free_message->set_comm(""); - } else { - proto_util::AppendField(field, process_free_message); - } - } - - return base::OkStatus(); -} - -} // namespace perfetto::trace_redaction diff --git a/src/trace_redaction/redact_process_free.h b/src/trace_redaction/redact_process_free.h deleted file mode 100644 index 19e954c836..0000000000 --- a/src/trace_redaction/redact_process_free.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef SRC_TRACE_REDACTION_REDACT_PROCESS_FREE_H_ -#define SRC_TRACE_REDACTION_REDACT_PROCESS_FREE_H_ - -#include "src/trace_redaction/redact_ftrace_event.h" -#include "src/trace_redaction/trace_redaction_framework.h" - -namespace perfetto::trace_redaction { - -// Goes through ftrace events and conditonally removes the comm values from -// process free events. -class RedactProcessFree : public FtraceEventRedaction { - public: - static constexpr auto kFieldId = - protos::pbzero::FtraceEvent::kSchedProcessFreeFieldNumber; - - base::Status Redact( - const Context& context, - const protos::pbzero::FtraceEventBundle::Decoder& bundle, - protozero::ProtoDecoder& event, - protos::pbzero::FtraceEvent* event_message) const override; -}; - -} // namespace perfetto::trace_redaction - -#endif // SRC_TRACE_REDACTION_REDACT_PROCESS_FREE_H_ diff --git a/src/trace_redaction/redact_process_free_unittest.cc b/src/trace_redaction/redact_process_free_unittest.cc deleted file mode 100644 index 04064ae091..0000000000 --- a/src/trace_redaction/redact_process_free_unittest.cc +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/trace_redaction/redact_process_free.h" -#include "perfetto/protozero/scattered_heap_buffer.h" -#include "src/base/test/status_matchers.h" -#include "src/trace_redaction/trace_redaction_framework.h" -#include "test/gtest_and_gmock.h" - -#include "protos/perfetto/trace/ftrace/ftrace_event.gen.h" -#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h" -#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.gen.h" -#include "protos/perfetto/trace/ftrace/sched.gen.h" -#include "protos/perfetto/trace/trace.gen.h" -#include "protos/perfetto/trace/trace_packet.gen.h" - -namespace perfetto::trace_redaction { - -class RedactProcessFreeTest : public testing::Test { - protected: - void SetUp() override { - auto* source_event = bundle.add_event(); - source_event->set_timestamp(123456789); - source_event->set_pid(10); - } - - base::Status Redact(protos::pbzero::FtraceEvent* event_message) { - RedactProcessFree redact; - Context context; - - auto bundle_str = bundle.SerializeAsString(); - protos::pbzero::FtraceEventBundle::Decoder bundle_decoder(bundle_str); - - auto event_str = bundle.event().back().SerializeAsString(); - protos::pbzero::FtraceEvent::Decoder event_decoder(event_str); - - return redact.Redact(context, bundle_decoder, event_decoder, event_message); - } - - protos::gen::FtraceEventBundle bundle; -}; - -// A free event will always test as "not active". So the comm value should -// always be replaced with an empty string. -TEST_F(RedactProcessFreeTest, ClearsCommValue) { - auto* process_free = - bundle.mutable_event()->back().mutable_sched_process_free(); - process_free->set_comm("comm-a"); - process_free->set_pid(11); - - protozero::HeapBuffered event_message; - - auto result = Redact(event_message.get()); - ASSERT_OK(result) << result.c_message(); - - protos::gen::FtraceEvent redacted_event; - redacted_event.ParseFromString(event_message.SerializeAsString()); - - // No process free event should have been added to the ftrace event. - ASSERT_TRUE(redacted_event.has_sched_process_free()); - ASSERT_TRUE(redacted_event.sched_process_free().has_comm()); - ASSERT_TRUE(redacted_event.sched_process_free().comm().empty()); -} - -// Even if there is no pid in the process free event, the comm value should be -// replaced with an empty string. -TEST_F(RedactProcessFreeTest, NoPidClearsEvent) { - // Don't add a pid. This should have no change in behaviour. - auto* process_free = - bundle.mutable_event()->back().mutable_sched_process_free(); - process_free->set_comm("comm-a"); - - protozero::HeapBuffered event_message; - - auto result = Redact(event_message.get()); - ASSERT_OK(result) << result.c_message(); - - protos::gen::FtraceEvent redacted_event; - redacted_event.ParseFromString(event_message.SerializeAsString()); - - // No process free event should have been added to the ftrace event. - ASSERT_TRUE(redacted_event.has_sched_process_free()); - ASSERT_TRUE(redacted_event.sched_process_free().has_comm()); - ASSERT_TRUE(redacted_event.sched_process_free().comm().empty()); -} - -} // namespace perfetto::trace_redaction diff --git a/src/trace_redaction/redact_process_trees.cc b/src/trace_redaction/redact_process_trees.cc new file mode 100644 index 0000000000..b7010c0d04 --- /dev/null +++ b/src/trace_redaction/redact_process_trees.cc @@ -0,0 +1,197 @@ +/* + * 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/redact_process_trees.h" + +#include + +#include "perfetto/base/status.h" +#include "perfetto/protozero/field.h" +#include "perfetto/protozero/scattered_heap_buffer.h" +#include "src/trace_processor/util/status_macros.h" +#include "src/trace_redaction/proto_util.h" +#include "src/trace_redaction/trace_redaction_framework.h" + +#include "protos/perfetto/trace/ps/process_tree.pbzero.h" +#include "protos/perfetto/trace/trace_packet.pbzero.h" + +namespace perfetto::trace_redaction { + +ProcessTreeModifier::~ProcessTreeModifier() = default; + +base::Status ProcessTreeDoNothing::Modify(const Context&, + protos::pbzero::ProcessTree*) const { + return base::OkStatus(); +} + +base::Status ProcessTreeCreateSynthThreads::Modify( + const Context& context, + protos::pbzero::ProcessTree* message) const { + PERFETTO_DCHECK(message); + + if (!context.synthetic_process) { + return base::ErrStatus( + "ProcessTreeCreateSynthThreads: missing synthetic thread group"); + } + + const auto& tids = context.synthetic_process->tids(); + + // At the very least there needs to be a main thread and one CPU thread. If + // not, something is wrong. + if (tids.size() < 2) { + return base::ErrStatus( + "ProcessTreeCreateSynthThreads: missing synthetic threads"); + } + + auto it = tids.begin(); + + auto* process = message->add_processes(); + process->set_uid(context.synthetic_process->uid()); + process->set_ppid(context.synthetic_process->ppid()); + process->set_pid(*it); + process->add_cmdline("Other-Processes"); + + ++it; + + for (; it != tids.end(); ++it) { + auto name = std::to_string(*it); + name.insert(0, "cpu-"); + + auto* thread = message->add_threads(); + thread->set_tgid(context.synthetic_process->tgid()); + thread->set_tid(*it); + thread->set_name(name); + } + + return base::OkStatus(); +} + +base::Status RedactProcessTrees::Transform(const Context& context, + std::string* packet) const { + PERFETTO_DCHECK(packet); + + if (!context.package_uid.has_value()) { + return base::ErrStatus("RedactProcessTrees: missing package uid."); + } + + if (!context.timeline) { + return base::ErrStatus("RedactProcessTrees: missing timeline."); + } + + if (!context.synthetic_process) { + return base::ErrStatus("RedactProcessTrees: missing synthentic threads."); + } + + protozero::ProtoDecoder decoder(*packet); + + auto tree = + decoder.FindField(protos::pbzero::TracePacket::kProcessTreeFieldNumber); + + if (!tree.valid()) { + return base::OkStatus(); + } + + // This has been verified by the verify primitive. + auto timestamp = + decoder.FindField(protos::pbzero::TracePacket::kTimestampFieldNumber); + + protozero::HeapBuffered message; + + for (auto it = decoder.ReadField(); it.valid(); it = decoder.ReadField()) { + if (it.id() == tree.id()) { + RETURN_IF_ERROR(OnProcessTree(context, timestamp.as_uint64(), + it.as_bytes(), + message->set_process_tree())); + } else { + proto_util::AppendField(it, message.get()); + } + } + + packet->assign(message.SerializeAsString()); + + return base::OkStatus(); +} + +base::Status RedactProcessTrees::OnProcessTree( + const Context& context, + uint64_t ts, + protozero::ConstBytes bytes, + protos::pbzero::ProcessTree* message) const { + protozero::ProtoDecoder decoder(bytes); + + for (auto it = decoder.ReadField(); it.valid(); it = decoder.ReadField()) { + switch (it.id()) { + case protos::pbzero::ProcessTree::kProcessesFieldNumber: + RETURN_IF_ERROR(OnProcess(context, ts, it, message)); + break; + case protos::pbzero::ProcessTree::kThreadsFieldNumber: + RETURN_IF_ERROR(OnThread(context, ts, it, message)); + break; + default: + proto_util::AppendField(it, message); + break; + } + } + + PERFETTO_DCHECK(modifier_); + return modifier_->Modify(context, message); +} + +base::Status RedactProcessTrees::OnProcess( + const Context& context, + uint64_t ts, + protozero::Field field, + protos::pbzero::ProcessTree* message) const { + protozero::ProtoDecoder decoder(field.as_bytes()); + + auto pid = + decoder.FindField(protos::pbzero::ProcessTree::Process::kPidFieldNumber); + if (!pid.valid()) { + return base::ErrStatus("RedactProcessTrees: process with no pid"); + } + + PERFETTO_DCHECK(filter_); + + if (filter_->Includes(context, ts, pid.as_int32())) { + proto_util::AppendField(field, message); + } + + return base::OkStatus(); +} + +base::Status RedactProcessTrees::OnThread( + const Context& context, + uint64_t ts, + protozero::Field field, + protos::pbzero::ProcessTree* message) const { + protozero::ProtoDecoder decoder(field.as_bytes()); + + auto tid = + decoder.FindField(protos::pbzero::ProcessTree::Thread::kTidFieldNumber); + if (!tid.valid()) { + return base::ErrStatus("RedactProcessTrees: thread with no tid"); + } + + PERFETTO_DCHECK(filter_); + + if (filter_->Includes(context, ts, tid.as_int32())) { + proto_util::AppendField(field, message); + } + + return base::OkStatus(); +} + +} // namespace perfetto::trace_redaction diff --git a/src/trace_redaction/redact_process_trees.h b/src/trace_redaction/redact_process_trees.h new file mode 100644 index 0000000000..5c9e507893 --- /dev/null +++ b/src/trace_redaction/redact_process_trees.h @@ -0,0 +1,90 @@ +/* + * 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_REDACT_PROCESS_TREES_H_ +#define SRC_TRACE_REDACTION_REDACT_PROCESS_TREES_H_ + +#include "perfetto/base/status.h" +#include "perfetto/protozero/field.h" +#include "src/trace_redaction/redact_sched_events.h" +#include "src/trace_redaction/trace_redaction_framework.h" + +#include "protos/perfetto/trace/ps/process_tree.pbzero.h" + +namespace perfetto::trace_redaction { + +class ProcessTreeModifier { + public: + virtual ~ProcessTreeModifier(); + virtual base::Status Modify(const Context& context, + protos::pbzero::ProcessTree* message) const = 0; +}; + +class ProcessTreeDoNothing : public ProcessTreeModifier { + public: + base::Status Modify(const Context& context, + protos::pbzero::ProcessTree* message) const override; +}; + +class ProcessTreeCreateSynthThreads : public ProcessTreeModifier { + public: + base::Status Modify(const Context& context, + protos::pbzero::ProcessTree* message) const override; +}; + +// Removes threads and processes from the process tree based on whether or not +// they are connected to the target package. +class RedactProcessTrees : public TransformPrimitive { + public: + base::Status Transform(const Context& context, + std::string* packet) const override; + + template + void emplace_filter() { + filter_ = std::make_unique(); + } + + template + void emplace_modifier() { + modifier_ = std::make_unique(); + } + + private: + base::Status OnProcessTree(const Context& context, + uint64_t ts, + protozero::ConstBytes bytes, + protos::pbzero::ProcessTree* message) const; + + base::Status OnProcess(const Context& context, + uint64_t ts, + protozero::Field field, + protos::pbzero::ProcessTree* message) const; + + base::Status OnThread(const Context& context, + uint64_t ts, + protozero::Field field, + protos::pbzero::ProcessTree* message) const; + + base::Status AppendSynthThreads(const Context& context, + protos::pbzero::ProcessTree* message) const; + + std::unique_ptr filter_; + std::unique_ptr modifier_; +}; + +} // namespace perfetto::trace_redaction + +#endif // SRC_TRACE_REDACTION_REDACT_PROCESS_TREES_H_ diff --git a/src/trace_redaction/redact_process_trees_integrationtest.cc b/src/trace_redaction/redact_process_trees_integrationtest.cc new file mode 100644 index 0000000000..716cabc044 --- /dev/null +++ b/src/trace_redaction/redact_process_trees_integrationtest.cc @@ -0,0 +1,245 @@ +/* + * 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 +#include +#include + +#include "src/base/test/status_matchers.h" +#include "src/trace_redaction/collect_system_info.h" +#include "src/trace_redaction/collect_timeline_events.h" +#include "src/trace_redaction/find_package_uid.h" +#include "src/trace_redaction/redact_process_trees.h" +#include "src/trace_redaction/trace_redaction_framework.h" +#include "src/trace_redaction/trace_redaction_integration_fixture.h" +#include "src/trace_redaction/trace_redactor.h" +#include "test/gtest_and_gmock.h" + +#include "protos/perfetto/trace/ps/process_tree.pbzero.h" +#include "protos/perfetto/trace/trace.pbzero.h" + +namespace perfetto::trace_redaction { + +namespace { + +constexpr std::string_view kProcessName = + "com.Unity.com.unity.multiplayer.samples.coop"; + +} // namespace + +class RedactProcessTreesIntegrationTest + : public testing::Test, + protected TraceRedactionIntegrationFixure { + protected: + void SetUp() override { + trace_redactor_.emplace_collect(); + trace_redactor_.emplace_build(); + + trace_redactor_.emplace_collect(); + trace_redactor_.emplace_collect(); + + // Filter the process tree based on whether or not a process is part of the + // target package. + auto* process_tree = + trace_redactor_.emplace_transform(); + process_tree->emplace_modifier(); + process_tree->emplace_filter(); + + // In this case, the process and package have the same name. + context_.package_name = kProcessName; + } + + std::unordered_set GetPids(const std::string& bytes) const { + std::unordered_set pids; + + protos::pbzero::Trace::Decoder decoder(bytes); + + for (auto it = decoder.packet(); it; ++it) { + protos::pbzero::TracePacket::Decoder packet(*it); + + if (packet.has_process_tree()) { + GetPids(packet.process_tree(), &pids); + } + } + + return pids; + } + + std::unordered_set GetTids(const std::string& bytes) const { + std::unordered_set tids; + + protos::pbzero::Trace::Decoder decoder(bytes); + + for (auto it = decoder.packet(); it; ++it) { + protos::pbzero::TracePacket::Decoder packet(*it); + + if (packet.has_process_tree()) { + GetTids(packet.process_tree(), &tids); + } + } + + return tids; + } + + Context context_; + TraceRedactor trace_redactor_; + + private: + void GetPids(protozero::ConstBytes bytes, + std::unordered_set* pids) const { + protos::pbzero::ProcessTree::Decoder process_tree(bytes); + + for (auto it = process_tree.processes(); it; ++it) { + protos::pbzero::ProcessTree::Process::Decoder process(*it); + pids->insert(process.ppid()); + pids->insert(process.pid()); + } + } + + void GetTids(protozero::ConstBytes bytes, + std::unordered_set* tids) const { + protos::pbzero::ProcessTree::Decoder process_tree(bytes); + + for (auto it = process_tree.threads(); it; ++it) { + protos::pbzero::ProcessTree::Thread::Decoder thread(*it); + tids->insert(thread.tgid()); + tids->insert(thread.tid()); + } + } +}; + +TEST_F(RedactProcessTreesIntegrationTest, FilterProcesses) { + ASSERT_OK(Redact(trace_redactor_, &context_)); + + auto original_trace_str = LoadOriginal(); + ASSERT_OK(original_trace_str); + + auto redacted_trace_str = LoadRedacted(); + ASSERT_OK(redacted_trace_str); + + auto original_pids = GetPids(*original_trace_str); + auto redacted_pids = GetPids(*redacted_trace_str); + + // There are 902 unique pids across all process trees: + // grep 'processes {' -A 1 src.pftrace.txt | grep 'pid: ' | grep -Po "\d+" + // | sort | uniq | wc -l + // + // But if ppids are included, there are 903 pids in the process tree: + // grep 'processes {' -A 2 src.pftrace.txt | grep 'pid: ' | grep -Po "\d+" + // | sort | uniq | wc -l + // + // The above grep statements use a stringified version of the trace. Using "-A + // 1" will return the pid line. Using "-A 2" will include both pid and ppid. + // + // The original process count aligns with trace processor. However, the + // redacted count does not. The final tree has one process but trace processor + // reports 4 processes. + ASSERT_EQ(original_pids.size(), 903u); + ASSERT_EQ(redacted_pids.size(), 2u); + + ASSERT_TRUE(redacted_pids.count(7105)); +} + +TEST_F(RedactProcessTreesIntegrationTest, FilterThreads) { + ASSERT_OK(Redact(trace_redactor_, &context_)); + + auto original_trace_str = LoadOriginal(); + ASSERT_OK(original_trace_str); + + auto redacted_trace_str = LoadRedacted(); + ASSERT_OK(redacted_trace_str); + + auto original_tids = GetTids(*original_trace_str); + auto redacted_tids = GetTids(*redacted_trace_str); + + // There are 2761 unique tids across all process trees: + // grep 'threads {' -A 1 src.pftrace.txt | grep 'tid: ' | grep -Po "\d+" | + // sort | uniq | wc -l + // + // There are 2896 unique tids/tgis across all process trees: + // grep 'threads {' -A 2 src.pftrace.txt | grep -P '(tid|tgid): ' | grep + // -Po '\d+' | sort | uniq | wc -l + // + // The original tid count does NOT align with what trace processor returns. + // Trace processor reports 3666 threads. The assumption is trace processor is + // fulling thread information from additional. + // + // The redacted tid+tgid count does NOT align with what trace processor + // returns. Trace processor reports 199 tids where are there are only 63 tids + // found in process tree. This suggests that trace processor is pulling tid + // data from other locations. + ASSERT_EQ(original_tids.size(), 2896u); + ASSERT_EQ(redacted_tids.size(), 64u); +} + +TEST_F(RedactProcessTreesIntegrationTest, AddSynthProcess) { + // Append another primitive that won't filter, but will add new threads. This + // will be compatible with the other instanced in SetUp(). + auto* process_tree = trace_redactor_.emplace_transform(); + process_tree->emplace_modifier(); + process_tree->emplace_filter(); + + ASSERT_OK(Redact(trace_redactor_, &context_)); + + auto redacted_trace_str = LoadRedacted(); + ASSERT_OK(redacted_trace_str); + + auto redacted_pids = GetPids(*redacted_trace_str); + + const auto* synth_process = context_.synthetic_process.get(); + ASSERT_TRUE(synth_process); + + ASSERT_NE(std::find(redacted_pids.begin(), redacted_pids.end(), + synth_process->tgid()), + redacted_pids.end()); +} + +TEST_F(RedactProcessTreesIntegrationTest, AddSynthThreads) { + // Append another primitive that won't filter, but will add new threads. This + // will be compatible with the other instanced in SetUp(). + auto* process_tree = trace_redactor_.emplace_transform(); + process_tree->emplace_modifier(); + process_tree->emplace_filter(); + + ASSERT_OK(Redact(trace_redactor_, &context_)); + + const auto* synth_process = context_.synthetic_process.get(); + ASSERT_TRUE(synth_process); + + ASSERT_FALSE(synth_process->tids().empty()); + + auto original_trace_str = LoadOriginal(); + ASSERT_OK(original_trace_str); + + auto original_tids = GetTids(*original_trace_str); + + // The synth threads should not be found in the original trace. + for (auto tid : synth_process->tids()) { + ASSERT_FALSE(original_tids.count(tid)); + } + + auto redacted_trace_str = LoadRedacted(); + ASSERT_OK(redacted_trace_str); + + auto redacted_tids = GetTids(*redacted_trace_str); + + // The synth threads should be found in the redacted trace. + for (auto tid : synth_process->tids()) { + ASSERT_TRUE(redacted_tids.count(tid)); + } +} + +} // namespace perfetto::trace_redaction diff --git a/src/trace_redaction/redact_sched_events.cc b/src/trace_redaction/redact_sched_events.cc new file mode 100644 index 0000000000..0d7ffbea7a --- /dev/null +++ b/src/trace_redaction/redact_sched_events.cc @@ -0,0 +1,626 @@ +/* + * 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/redact_sched_events.h" + +#include "perfetto/protozero/scattered_heap_buffer.h" +#include "src/trace_processor/util/status_macros.h" +#include "src/trace_redaction/proto_util.h" + +#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h" +#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h" +#include "protos/perfetto/trace/ftrace/sched.pbzero.h" + +namespace perfetto::trace_redaction { + +namespace { +bool IsTrue(bool value) { + return value; +} + +// Copy a field from 'decoder' to 'message' if the field can be found. Returns +// false if the field cannot be found. +bool Passthrough(protozero::ProtoDecoder& decoder, + uint32_t field_id, + protozero::Message* message) { + auto field = decoder.FindField(field_id); + + if (field.valid()) { + proto_util::AppendField(field, message); + return true; + } + + return false; +} +} // namespace + +int64_t InternTable::Push(const char* data, size_t size) { + std::string_view outer(data, size); + + for (size_t i = 0; i < interned_comms_.size(); ++i) { + auto view = interned_comms_[i]; + + if (view == outer) { + return static_cast(i); + } + } + + // No room for the new string, reject the request. + if (comms_length_ + size > comms_.size()) { + return -1; + } + + auto* head = comms_.data() + comms_length_; + + // Important note, the null byte is not copied. + memcpy(head, data, size); + comms_length_ += size; + + size_t id = interned_comms_.size(); + interned_comms_.emplace_back(head, size); + + return static_cast(id); +} + +std::string_view InternTable::Find(size_t index) const { + if (index < interned_comms_.size()) { + return interned_comms_[index]; + } + + return {}; +} + +// Redact sched switch trace events in an ftrace event bundle: +// +// event { +// timestamp: 6702093744772646 +// pid: 0 +// sched_switch { +// prev_comm: "swapper/0" +// prev_pid: 0 +// prev_prio: 120 +// prev_state: 0 +// next_comm: "writer" +// next_pid: 23020 +// next_prio: 96 +// } +// } +// +// In the above message, it should be noted that "event.pid" will always be +// equal to "event.sched_switch.prev_pid". +// +// "ftrace_event_bundle_message" is the ftrace event bundle (contains a +// collection of ftrace event messages) because data in a sched_switch message +// is needed in order to know if the event should be added to the bundle. + +base::Status RedactSchedEvents::Transform(const Context& context, + std::string* packet) const { + PERFETTO_DCHECK(modifier_); + PERFETTO_DCHECK(waking_filter_); + + if (!context.timeline) { + return base::ErrStatus("RedactSchedEvents: missing timeline."); + } + + if (!context.package_uid.has_value()) { + return base::ErrStatus("RedactSchedEvents: missing package uid."); + } + + if (!packet || packet->empty()) { + return base::ErrStatus("RedactSchedEvents: null or empty packet."); + } + + protozero::HeapBuffered message; + protozero::ProtoDecoder decoder(*packet); + + for (auto field = decoder.ReadField(); field.valid(); + field = decoder.ReadField()) { + if (field.id() == protos::pbzero::TracePacket::kFtraceEventsFieldNumber) { + RETURN_IF_ERROR( + OnFtraceEvents(context, field, message->set_ftrace_events())); + } else { + proto_util::AppendField(field, message.get()); + } + } + + packet->assign(message.SerializeAsString()); + + return base::OkStatus(); +} + +base::Status RedactSchedEvents::OnFtraceEvents( + const Context& context, + protozero::Field ftrace_events, + protos::pbzero::FtraceEventBundle* message) const { + PERFETTO_DCHECK(ftrace_events.id() == + protos::pbzero::TracePacket::kFtraceEventsFieldNumber); + + protozero::ProtoDecoder decoder(ftrace_events.as_bytes()); + + auto cpu = + decoder.FindField(protos::pbzero::FtraceEventBundle::kCpuFieldNumber); + if (!cpu.valid()) { + return base::ErrStatus( + "RedactSchedEvents: missing cpu in ftrace event bundle."); + } + + for (auto field = decoder.ReadField(); field.valid(); + field = decoder.ReadField()) { + if (field.id() == protos::pbzero::FtraceEventBundle::kEventFieldNumber) { + RETURN_IF_ERROR( + OnFtraceEvent(context, cpu.as_int32(), field, message->add_event())); + continue; + } + + if (field.id() == + protos::pbzero::FtraceEventBundle::kCompactSchedFieldNumber) { + protos::pbzero::FtraceEventBundle::CompactSched::Decoder comp_sched( + field.as_bytes()); + RETURN_IF_ERROR(OnCompSched(context, cpu.as_int32(), comp_sched, + message->set_compact_sched())); + continue; + } + + proto_util::AppendField(field, message); + } + + return base::OkStatus(); +} + +base::Status RedactSchedEvents::OnFtraceEvent( + const Context& context, + int32_t cpu, + protozero::Field ftrace_event, + protos::pbzero::FtraceEvent* message) const { + PERFETTO_DCHECK(ftrace_event.id() == + protos::pbzero::FtraceEventBundle::kEventFieldNumber); + + protozero::ProtoDecoder decoder(ftrace_event.as_bytes()); + + auto ts = + decoder.FindField(protos::pbzero::FtraceEvent::kTimestampFieldNumber); + if (!ts.valid()) { + return base::ErrStatus( + "RedactSchedEvents: missing timestamp in ftrace event."); + } + + std::string scratch_str; + + for (auto field = decoder.ReadField(); field.valid(); + field = decoder.ReadField()) { + switch (field.id()) { + case protos::pbzero::FtraceEvent::kSchedSwitchFieldNumber: { + protos::pbzero::SchedSwitchFtraceEvent::Decoder sched_switch( + field.as_bytes()); + RETURN_IF_ERROR(OnFtraceEventSwitch(context, ts.as_uint64(), cpu, + sched_switch, &scratch_str, + message->set_sched_switch())); + break; + } + + case protos::pbzero::FtraceEvent::kSchedWakingFieldNumber: { + protos::pbzero::SchedWakingFtraceEvent::Decoder sched_waking( + field.as_bytes()); + RETURN_IF_ERROR(OnFtraceEventWaking( + context, ts.as_uint64(), cpu, sched_waking, &scratch_str, message)); + break; + } + + default: { + proto_util::AppendField(field, message); + break; + } + } + } + + return base::OkStatus(); +} + +base::Status RedactSchedEvents::OnFtraceEventSwitch( + const Context& context, + uint64_t ts, + int32_t cpu, + protos::pbzero::SchedSwitchFtraceEvent::Decoder& sched_switch, + std::string* scratch_str, + protos::pbzero::SchedSwitchFtraceEvent* message) const { + PERFETTO_DCHECK(modifier_); + PERFETTO_DCHECK(scratch_str); + PERFETTO_DCHECK(message); + + std::array has_fields = { + sched_switch.has_prev_comm(), sched_switch.has_prev_pid(), + sched_switch.has_prev_prio(), sched_switch.has_prev_state(), + sched_switch.has_next_comm(), sched_switch.has_next_pid(), + sched_switch.has_next_prio()}; + + if (!std::all_of(has_fields.begin(), has_fields.end(), IsTrue)) { + return base::ErrStatus( + "RedactSchedEvents: missing required SchedSwitchFtraceEvent " + "field."); + } + + auto prev_pid = sched_switch.prev_pid(); + auto prev_comm = sched_switch.prev_comm(); + + auto next_pid = sched_switch.next_pid(); + auto next_comm = sched_switch.next_comm(); + + // There are 7 values in a sched switch message. Since 4 of the 7 can be + // replaced, it is easier/cleaner to go value-by-value. Go in proto-defined + // order. + + scratch_str->assign(prev_comm.data, prev_comm.size); + + modifier_->Modify(context, ts, cpu, &prev_pid, scratch_str); + + message->set_prev_comm(*scratch_str); // FieldNumber = 1 + message->set_prev_pid(prev_pid); // FieldNumber = 2 + message->set_prev_prio(sched_switch.prev_prio()); // FieldNumber = 3 + message->set_prev_state(sched_switch.prev_state()); // FieldNumber = 4 + + scratch_str->assign(next_comm.data, next_comm.size); + + modifier_->Modify(context, ts, cpu, &next_pid, scratch_str); + + message->set_next_comm(*scratch_str); // FieldNumber = 5 + message->set_next_pid(next_pid); // FieldNumber = 6 + message->set_next_prio(sched_switch.next_prio()); // FieldNumber = 7 + + return base::OkStatus(); +} + +// Redact sched waking trace events in a ftrace event bundle: +// +// event { +// timestamp: 6702093787823849 +// pid: 814 <-- waker +// sched_waking { +// comm: "surfaceflinger" +// pid: 756 <-- target +// prio: 97 +// success: 1 +// target_cpu: 2 +// } +// } +base::Status RedactSchedEvents::OnFtraceEventWaking( + const Context& context, + uint64_t ts, + int32_t cpu, + protos::pbzero::SchedWakingFtraceEvent::Decoder& sched_waking, + std::string* scratch_str, + protos::pbzero::FtraceEvent* parent_message) const { + PERFETTO_DCHECK(modifier_); + PERFETTO_DCHECK(scratch_str); + PERFETTO_DCHECK(parent_message); + + std::array has_fields = { + sched_waking.has_comm(), sched_waking.has_pid(), sched_waking.has_prio(), + sched_waking.has_success(), sched_waking.has_target_cpu()}; + + if (!std::all_of(has_fields.begin(), has_fields.end(), IsTrue)) { + return base::ErrStatus( + "RedactSchedEvents: missing required SchedWakingFtraceEvent " + "field."); + } + + auto pid = sched_waking.pid(); + + if (!waking_filter_->Includes(context, ts, pid)) { + return base::OkStatus(); + } + + auto comm = sched_waking.comm(); + + // There are 5 values in a sched switch message. Since 2 of the 5 can be + // replaced, it is easier/cleaner to go value-by-value. Go in proto-defined + // order. + + scratch_str->assign(comm.data, comm.size); + + modifier_->Modify(context, ts, cpu, &pid, scratch_str); + + auto message = parent_message->set_sched_waking(); + message->set_comm(*scratch_str); // FieldNumber = 1 + message->set_pid(pid); // FieldNumber = 2 + message->set_prio(sched_waking.prio()); // FieldNumber = 3 + message->set_success(sched_waking.success()); // FieldNumber = 4 + message->set_target_cpu(sched_waking.target_cpu()); // FieldNumber = 5 + + return base::OkStatus(); +} + +base::Status RedactSchedEvents::OnCompSched( + const Context& context, + int32_t cpu, + protos::pbzero::FtraceEventBundle::CompactSched::Decoder& comp_sched, + protos::pbzero::FtraceEventBundle::CompactSched* message) const { + // Populate the intern table once; it will be used by both sched and waking. + InternTable intern_table; + + for (auto it = comp_sched.intern_table(); it; ++it) { + auto chars = it->as_string(); + auto index = intern_table.Push(chars.data, chars.size); + + if (index < 0) { + return base::ErrStatus( + "RedactSchedEvents: failed to insert string into intern " + "table."); + } + } + + std::array has_switch_fields = { + comp_sched.has_switch_timestamp(), + comp_sched.has_switch_prev_state(), + comp_sched.has_switch_next_pid(), + comp_sched.has_switch_next_prio(), + comp_sched.has_switch_next_comm_index(), + }; + + if (std::any_of(has_switch_fields.begin(), has_switch_fields.end(), IsTrue)) { + RETURN_IF_ERROR( + OnCompSchedSwitch(context, cpu, comp_sched, &intern_table, message)); + } + + std::array has_waking_fields = { + comp_sched.has_waking_timestamp(), comp_sched.has_waking_pid(), + comp_sched.has_waking_target_cpu(), comp_sched.has_waking_prio(), + comp_sched.has_waking_comm_index(), comp_sched.has_waking_common_flags(), + }; + + if (std::any_of(has_waking_fields.begin(), has_waking_fields.end(), IsTrue)) { + RETURN_IF_ERROR( + OnCompactSchedWaking(context, comp_sched, &intern_table, message)); + } + + // IMPORTANT: The intern table can only be added after switch and waking + // because switch and/or waking can/will modify the intern table. + for (auto view : intern_table.values()) { + message->add_intern_table(view.data(), view.size()); + } + + return base::OkStatus(); +} + +base::Status RedactSchedEvents::OnCompSchedSwitch( + const Context& context, + int32_t cpu, + protos::pbzero::FtraceEventBundle::CompactSched::Decoder& comp_sched, + InternTable* intern_table, + protos::pbzero::FtraceEventBundle::CompactSched* message) const { + PERFETTO_DCHECK(modifier_); + PERFETTO_DCHECK(message); + + std::array has_fields = { + comp_sched.has_intern_table(), + comp_sched.has_switch_timestamp(), + comp_sched.has_switch_prev_state(), + comp_sched.has_switch_next_pid(), + comp_sched.has_switch_next_prio(), + comp_sched.has_switch_next_comm_index(), + }; + + if (!std::all_of(has_fields.begin(), has_fields.end(), IsTrue)) { + return base::ErrStatus( + "RedactSchedEvents: missing required FtraceEventBundle::CompactSched " + "switch field."); + } + + std::string scratch_str; + + protozero::PackedVarInt packed_comm; + protozero::PackedVarInt packed_pid; + + // The first it_ts value is an absolute value, all other values are delta + // values. + uint64_t ts = 0; + + std::array parse_errors = {false, false, false}; + + auto it_ts = comp_sched.switch_timestamp(&parse_errors.at(0)); + auto it_pid = comp_sched.switch_next_pid(&parse_errors.at(1)); + auto it_comm = comp_sched.switch_next_comm_index(&parse_errors.at(2)); + + while (it_ts && it_pid && it_comm) { + ts += *it_ts; + + auto pid = *it_pid; + + auto comm_index = *it_comm; + auto comm = intern_table->Find(comm_index); + + scratch_str.assign(comm); + + modifier_->Modify(context, ts, cpu, &pid, &scratch_str); + + auto found = intern_table->Push(scratch_str.data(), scratch_str.size()); + + if (found < 0) { + return base::ErrStatus( + "RedactSchedEvents: failed to insert string into intern table."); + } + + packed_comm.Append(found); + packed_pid.Append(pid); + + ++it_ts; + ++it_pid; + ++it_comm; + } + + if (std::any_of(parse_errors.begin(), parse_errors.end(), IsTrue)) { + return base::ErrStatus( + "RedactSchedEvents: error reading FtraceEventBundle::CompactSched."); + } + + if (it_ts || it_pid || it_comm) { + return base::ErrStatus( + "RedactSchedEvents: uneven associative arrays in " + "FtraceEventBundle::CompactSched (switch)."); + } + + message->set_switch_next_pid(packed_pid); + message->set_switch_next_comm_index(packed_comm); + + // There's a lot of data in a compact sched message. Most of it is packed data + // and most of the data is not going to change. To avoid unpacking, doing + // nothing, and then packing... cheat. Find the fields and pass them as opaque + // blobs. + // + // kInternTableFieldNumber: The intern table will be modified by both + // switch events and waking events. It will + // be written elsewhere. + // + // kSwitchNextPidFieldNumber: The switch pid will change during thread + // merging. + // + // kSwitchNextCommIndexFieldNumber: The switch comm value will change when + // clearing thread names and replaced + // during thread merging. + + auto passed_through = { + Passthrough(comp_sched, + protos::pbzero::FtraceEventBundle::CompactSched:: + kSwitchTimestampFieldNumber, + message), + Passthrough(comp_sched, + protos::pbzero::FtraceEventBundle::CompactSched:: + kSwitchPrevStateFieldNumber, + message), + Passthrough(comp_sched, + protos::pbzero::FtraceEventBundle::CompactSched:: + kSwitchNextPrioFieldNumber, + message)}; + + if (!std::all_of(passed_through.begin(), passed_through.end(), IsTrue)) { + return base::ErrStatus( + "RedactSchedEvents: missing required " + "FtraceEventBundle::CompactSched switch field."); + } + + return base::OkStatus(); +} + +base::Status RedactSchedEvents::OnCompactSchedWaking( + const Context& context, + protos::pbzero::FtraceEventBundle::CompactSched::Decoder& compact_sched, + InternTable* intern_table, + protos::pbzero::FtraceEventBundle::CompactSched* compact_sched_message) + const { + protozero::PackedVarInt var_comm_index; + protozero::PackedVarInt var_common_flags; + protozero::PackedVarInt var_pid; + protozero::PackedVarInt var_prio; + protozero::PackedVarInt var_target_cpu; + protozero::PackedVarInt var_timestamp; + + // Time is expressed as delta time, for example: + // + // Event: A B C D + // Absolute Time: 20 30 35 41 + // | | | | + // Delta Time: 20 10 5 6 + // + // When an event is removed, for example, event B, delta times are off: + // + // Event: A * C D + // Absolute Time: 20 30 35 41 + // | | | | + // Delta Time: 20 * 5 6 + // | | | + // Effective Abs. Time: 20 25 31 + // Error: 0 10 10 + // + // To address this issue, delta times are added into a bucket. The bucket is + // drained each time an event is retained. If an event is dropped, its time + // is added to the bucket, but the bucket won't be drained until a retained + // event drains it. + uint64_t ts_bucket = 0; + uint64_t ts_absolute = 0; + + std::string comm; + + std::array parse_errors = {!compact_sched.has_intern_table(), + false, + false, + false, + false, + false, + false}; + + // A note on readability, because the waking iterators are the primary focus, + // they won't have a "waking" prefix. + auto it_comm_index = compact_sched.waking_comm_index(&parse_errors.at(1)); + auto it_common_flags = compact_sched.waking_common_flags(&parse_errors.at(2)); + auto it_pid = compact_sched.waking_pid(&parse_errors.at(3)); + auto it_prio = compact_sched.waking_prio(&parse_errors.at(4)); + auto it_target_cpu = compact_sched.waking_target_cpu(&parse_errors.at(5)); + auto it_timestamp = compact_sched.waking_timestamp(&parse_errors.at(6)); + + while (it_comm_index && it_common_flags && it_pid && it_prio && + it_target_cpu && it_timestamp) { + ts_bucket += *it_timestamp; // add time to the bucket + ts_absolute += *it_timestamp; + + if (waking_filter_->Includes(context, ts_absolute, *it_pid)) { + // Now that the waking event will be kept, it can be modified using the + // same rules as switch events. + auto pid = *it_pid; + comm.assign(intern_table->Find(*it_comm_index)); + modifier_->Modify(context, ts_absolute, *it_target_cpu, &pid, &comm); + + auto comm_it = intern_table->Push(comm.data(), comm.size()); + + var_comm_index.Append(comm_it); + var_common_flags.Append(*it_common_flags); + var_pid.Append(pid); + var_prio.Append(*it_prio); + var_target_cpu.Append(*it_target_cpu); + var_timestamp.Append(ts_bucket); + + ts_bucket = 0; // drain the whole bucket. + } + + ++it_comm_index; + ++it_common_flags; + ++it_pid; + ++it_prio; + ++it_target_cpu; + ++it_timestamp; + } + + if (std::any_of(parse_errors.begin(), parse_errors.end(), IsTrue)) { + return base::ErrStatus( + "RedactSchedEvents: failed to parse FtraceEventBundle::CompactSched."); + } + + if (it_comm_index || it_common_flags || it_pid || it_prio || it_target_cpu || + it_timestamp) { + return base::ErrStatus( + "RedactSchedEvents: uneven associative arrays in " + "FtraceEventBundle::CompactSched (waking)."); + } + + compact_sched_message->set_waking_comm_index(var_comm_index); + compact_sched_message->set_waking_common_flags(var_common_flags); + compact_sched_message->set_waking_pid(var_pid); + compact_sched_message->set_waking_prio(var_prio); + compact_sched_message->set_waking_target_cpu(var_target_cpu); + compact_sched_message->set_waking_timestamp(var_timestamp); + + return base::OkStatus(); +} + +} // namespace perfetto::trace_redaction diff --git a/src/trace_redaction/redact_sched_events.h b/src/trace_redaction/redact_sched_events.h new file mode 100644 index 0000000000..f48fea2479 --- /dev/null +++ b/src/trace_redaction/redact_sched_events.h @@ -0,0 +1,121 @@ +/* + * 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_REDACT_SCHED_EVENTS_H_ +#define SRC_TRACE_REDACTION_REDACT_SCHED_EVENTS_H_ + +#include "src/trace_redaction/filtering.h" +#include "src/trace_redaction/modify.h" +#include "src/trace_redaction/trace_redaction_framework.h" + +#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h" +#include "protos/perfetto/trace/ftrace/sched.pbzero.h" + +namespace perfetto::trace_redaction { + +class InternTable { + public: + int64_t Push(const char* data, size_t size); + + std::string_view Find(size_t index) const; + + const std::vector& values() const { + return interned_comms_; + } + + private: + constexpr static size_t kExpectedCommLength = 16; + constexpr static size_t kMaxElements = 4096; + + std::array comms_; + size_t comms_length_ = 0; + + std::vector interned_comms_; +}; + +class RedactSchedEvents : public TransformPrimitive { + public: + base::Status Transform(const Context& context, + std::string* packet) const override; + + template + void emplace_modifier() { + modifier_ = std::make_unique(); + } + + template + void emplace_waking_filter() { + waking_filter_ = std::make_unique(); + } + + private: + base::Status OnFtraceEvents(const Context& context, + protozero::Field ftrace_events, + protos::pbzero::FtraceEventBundle* message) const; + + base::Status OnFtraceEvent(const Context& context, + int32_t cpu, + protozero::Field ftrace_event, + protos::pbzero::FtraceEvent* message) const; + + // scratch_str is a reusable string, allowing comm modifications to be done in + // a shared buffer, avoiding allocations when processing ftrace events. + base::Status OnFtraceEventSwitch( + const Context& context, + uint64_t ts, + int32_t cpu, + protos::pbzero::SchedSwitchFtraceEvent::Decoder& sched_switch, + std::string* scratch_str, + protos::pbzero::SchedSwitchFtraceEvent* message) const; + + // Unlike other On* functions, this one takes the parent message, allowing it + // to optionally add the body. This is what allows the waking event to be + // removed. + base::Status OnFtraceEventWaking( + const Context& context, + uint64_t ts, + int32_t cpu, + protos::pbzero::SchedWakingFtraceEvent::Decoder& sched_waking, + std::string* scratch_str, + protos::pbzero::FtraceEvent* parent_message) const; + + base::Status OnCompSched( + const Context& context, + int32_t cpu, + protos::pbzero::FtraceEventBundle::CompactSched::Decoder& comp_sched, + protos::pbzero::FtraceEventBundle::CompactSched* message) const; + + base::Status OnCompSchedSwitch( + const Context& context, + int32_t cpu, + protos::pbzero::FtraceEventBundle::CompactSched::Decoder& comp_sched, + InternTable* intern_table, + protos::pbzero::FtraceEventBundle::CompactSched* message) const; + + base::Status OnCompactSchedWaking( + const Context& context, + protos::pbzero::FtraceEventBundle::CompactSched::Decoder& compact_sched, + InternTable* intern_table, + protos::pbzero::FtraceEventBundle::CompactSched* compact_sched_message) + const; + + std::unique_ptr modifier_; + std::unique_ptr waking_filter_; +}; + +} // namespace perfetto::trace_redaction + +#endif // SRC_TRACE_REDACTION_REDACT_SCHED_EVENTS_H_ diff --git a/src/trace_redaction/redact_sched_switch_integrationtest.cc b/src/trace_redaction/redact_sched_events_integrationtest.cc similarity index 68% rename from src/trace_redaction/redact_sched_switch_integrationtest.cc rename to src/trace_redaction/redact_sched_events_integrationtest.cc index f14d6e9906..bf47328402 100644 --- a/src/trace_redaction/redact_sched_switch_integrationtest.cc +++ b/src/trace_redaction/redact_sched_events_integrationtest.cc @@ -16,13 +16,13 @@ #include #include +#include #include "perfetto/base/status.h" -#include "perfetto/ext/base/flat_hash_map.h" #include "src/base/test/status_matchers.h" #include "src/trace_redaction/collect_timeline_events.h" #include "src/trace_redaction/find_package_uid.h" -#include "src/trace_redaction/redact_sched_switch.h" +#include "src/trace_redaction/redact_sched_events.h" #include "src/trace_redaction/trace_redaction_framework.h" #include "src/trace_redaction/trace_redaction_integration_fixture.h" #include "src/trace_redaction/trace_redactor.h" @@ -36,23 +36,6 @@ namespace perfetto::trace_redaction { -class RedactSchedSwitchIntegrationTest - : public testing::Test, - protected TraceRedactionIntegrationFixure { - protected: - void SetUp() override { - trace_redactor()->emplace_collect(); - trace_redactor()->emplace_collect(); - - auto* ftrace_event_redactions = - trace_redactor()->emplace_transform(); - ftrace_event_redactions - ->emplace_back(); - - context()->package_name = "com.Unity.com.unity.multiplayer.samples.coop"; - } -}; - // >>> SELECT uid // >>> FROM package_list // >>> WHERE package_name='com.Unity.com.unity.multiplayer.samples.coop' @@ -103,9 +86,42 @@ class RedactSchedSwitchIntegrationTest // | 7950 | UnityGfxDeviceW | // | 7969 | UnityGfxDeviceW | // +------+-----------------+ +class RedactSchedSwitchIntegrationTest + : public testing::Test, + protected TraceRedactionIntegrationFixure { + protected: + void SetUp() override { + trace_redactor_.emplace_collect(); + trace_redactor_.emplace_collect(); + + auto* redact_sched_events = + trace_redactor_.emplace_transform(); + redact_sched_events->emplace_modifier(); + redact_sched_events->emplace_waking_filter(); + + context_.package_name = "com.Unity.com.unity.multiplayer.samples.coop"; + } + + std::unordered_map expected_names_ = { + {7120, "Binder:7105_2"}, {7127, "UnityMain"}, + {7142, "Job.worker 0"}, {7143, "Job.worker 1"}, + {7144, "Job.worker 2"}, {7145, "Job.worker 3"}, + {7146, "Job.worker 4"}, {7147, "Job.worker 5"}, + {7148, "Job.worker 6"}, {7150, "Background Job."}, + {7151, "Background Job."}, {7167, "UnityGfxDeviceW"}, + {7172, "AudioTrack"}, {7174, "FMOD stream thr"}, + {7180, "Binder:7105_3"}, {7184, "UnityChoreograp"}, + {7945, "Filter0"}, {7946, "Filter1"}, + {7947, "Thread-7"}, {7948, "FMOD mixer thre"}, + {7950, "UnityGfxDeviceW"}, {7969, "UnityGfxDeviceW"}, + }; + + Context context_; + TraceRedactor trace_redactor_; +}; TEST_F(RedactSchedSwitchIntegrationTest, ClearsNonTargetSwitchComms) { - auto result = Redact(); + auto result = Redact(trace_redactor_, &context_); ASSERT_OK(result) << result.c_message(); auto original = LoadOriginal(); @@ -114,30 +130,6 @@ TEST_F(RedactSchedSwitchIntegrationTest, ClearsNonTargetSwitchComms) { auto redacted = LoadRedacted(); ASSERT_OK(redacted) << redacted.status().c_message(); - base::FlatHashMap expected_names; - expected_names.Insert(7120, "Binder:7105_2"); - expected_names.Insert(7127, "UnityMain"); - expected_names.Insert(7142, "Job.worker 0"); - expected_names.Insert(7143, "Job.worker 1"); - expected_names.Insert(7144, "Job.worker 2"); - expected_names.Insert(7145, "Job.worker 3"); - expected_names.Insert(7146, "Job.worker 4"); - expected_names.Insert(7147, "Job.worker 5"); - expected_names.Insert(7148, "Job.worker 6"); - expected_names.Insert(7150, "Background Job."); - expected_names.Insert(7151, "Background Job."); - expected_names.Insert(7167, "UnityGfxDeviceW"); - expected_names.Insert(7172, "AudioTrack"); - expected_names.Insert(7174, "FMOD stream thr"); - expected_names.Insert(7180, "Binder:7105_3"); - expected_names.Insert(7184, "UnityChoreograp"); - expected_names.Insert(7945, "Filter0"); - expected_names.Insert(7946, "Filter1"); - expected_names.Insert(7947, "Thread-7"); - expected_names.Insert(7948, "FMOD mixer thre"); - expected_names.Insert(7950, "UnityGfxDeviceW"); - expected_names.Insert(7969, "UnityGfxDeviceW"); - auto redacted_trace_data = LoadRedacted(); ASSERT_OK(redacted_trace_data) << redacted.status().c_message(); @@ -164,29 +156,29 @@ TEST_F(RedactSchedSwitchIntegrationTest, ClearsNonTargetSwitchComms) { event_decoder.sched_switch()); ASSERT_TRUE(sched_decoder.has_next_pid()); - ASSERT_TRUE(sched_decoder.has_prev_pid()); - - auto next_pid = sched_decoder.next_pid(); - auto prev_pid = sched_decoder.prev_pid(); + ASSERT_TRUE(sched_decoder.has_next_comm()); // If the pid is expected, make sure it has the right now. If it is not // expected, it should be missing. - const auto* next_comm = expected_names.Find(next_pid); - const auto* prev_comm = expected_names.Find(prev_pid); - - EXPECT_TRUE(sched_decoder.has_next_comm()); - EXPECT_TRUE(sched_decoder.has_prev_comm()); + auto next_pid = sched_decoder.next_pid(); + auto next_comm = expected_names_.find(next_pid); - if (next_comm) { - EXPECT_EQ(sched_decoder.next_comm().ToStdString(), *next_comm); + if (next_comm == expected_names_.end()) { + ASSERT_EQ(sched_decoder.next_comm().size, 0u); } else { - EXPECT_EQ(sched_decoder.next_comm().size, 0u); + ASSERT_EQ(sched_decoder.next_comm().ToStdString(), next_comm->second); } - if (prev_comm) { - EXPECT_EQ(sched_decoder.prev_comm().ToStdString(), *prev_comm); + ASSERT_TRUE(sched_decoder.has_prev_pid()); + ASSERT_TRUE(sched_decoder.has_prev_comm()); + + auto prev_pid = sched_decoder.prev_pid(); + auto prev_comm = expected_names_.find(prev_pid); + + if (prev_comm == expected_names_.end()) { + ASSERT_EQ(sched_decoder.prev_comm().size, 0u); } else { - EXPECT_EQ(sched_decoder.prev_comm().size, 0u); + ASSERT_EQ(sched_decoder.prev_comm().ToStdString(), prev_comm->second); } } } diff --git a/src/trace_redaction/redact_sched_events_unittest.cc b/src/trace_redaction/redact_sched_events_unittest.cc new file mode 100644 index 0000000000..e3ca9625f9 --- /dev/null +++ b/src/trace_redaction/redact_sched_events_unittest.cc @@ -0,0 +1,801 @@ +/* + * 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/redact_sched_events.h" +#include "src/base/test/status_matchers.h" +#include "test/gtest_and_gmock.h" + +#include "protos/perfetto/trace/ftrace/ftrace_event.gen.h" +#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.gen.h" +#include "protos/perfetto/trace/ftrace/sched.gen.h" +#include "protos/perfetto/trace/trace.gen.h" +#include "protos/perfetto/trace/trace_packet.gen.h" + +namespace perfetto::trace_redaction { + +namespace { +constexpr uint64_t kUidA = 1; +constexpr uint64_t kUidB = 2; +constexpr uint64_t kUidC = 3; + +constexpr int32_t kNoParent = 10; +constexpr int32_t kPidA = 11; +constexpr int32_t kPidB = 12; +constexpr int32_t kPidC = 13; +constexpr int32_t kPidD = 14; + +constexpr int32_t kCpuA = 0; +constexpr int32_t kCpuB = 1; +constexpr int32_t kCpuC = 2; + +constexpr uint64_t kHalfStep = 500; +constexpr uint64_t kFullStep = kHalfStep * 2; + +constexpr uint64_t kTimeA = 0; +constexpr uint64_t kTimeB = kFullStep; +constexpr uint64_t kTimeC = kFullStep * 2; + +constexpr auto kCommA = "comm-a"; +constexpr auto kCommB = "comm-b"; +constexpr auto kCommC = "comm-c"; +constexpr auto kCommNone = ""; + +template +class ChangePidTo : public PidCommModifier { + public: + void Modify(const Context& context, + uint64_t ts, + int32_t, + int32_t* pid, + std::string*) const override { + PERFETTO_DCHECK(context.timeline); + PERFETTO_DCHECK(context.package_uid.has_value()); + PERFETTO_DCHECK(pid); + if (!context.timeline->PidConnectsToUid(ts, *pid, *context.package_uid)) { + *pid = new_pid; + } + } +}; +} // namespace + +class RedactSchedSwitchFtraceEventTest : public testing::Test { + protected: + void SetUp() override { + // Create a packet where two pids are swapping back-and-forth. + auto* bundle = packet_.mutable_ftrace_events(); + bundle->set_cpu(kCpuA); + + { + auto* event = bundle->add_event(); + + event->set_timestamp(kTimeA); + event->set_pid(kPidA); + + auto* sched_switch = event->mutable_sched_switch(); + sched_switch->set_prev_comm(kCommA); + sched_switch->set_prev_pid(kPidA); + sched_switch->set_prev_prio(0); + sched_switch->set_prev_state(0); + sched_switch->set_next_comm(kCommB); + sched_switch->set_next_pid(kPidB); + sched_switch->set_next_prio(0); + } + + { + auto* event = bundle->add_event(); + + event->set_timestamp(kTimeB); + event->set_pid(kPidB); + + auto* sched_switch = event->mutable_sched_switch(); + sched_switch->set_prev_comm(kCommB); + sched_switch->set_prev_pid(kPidB); + sched_switch->set_prev_prio(0); + sched_switch->set_prev_state(0); + sched_switch->set_next_comm(kCommA); + sched_switch->set_next_pid(kPidA); + sched_switch->set_next_prio(0); + } + + // PID A and PID B need to be attached to different packages (UID) so that + // its possible to include one but not the other. + context_.timeline = std::make_unique(); + context_.timeline->Append( + ProcessThreadTimeline::Event::Open(kTimeA, kPidA, kNoParent, kUidA)); + context_.timeline->Append( + ProcessThreadTimeline::Event::Open(kTimeA, kPidB, kNoParent, kUidB)); + context_.timeline->Sort(); + + redact_.emplace_modifier(); + redact_.emplace_waking_filter(); + } + + protos::gen::TracePacket packet_; + Context context_; + RedactSchedEvents redact_; +}; + +// In this case, the target uid will be UID A. That means the comm values for +// PID B should be removed, and the comm values for PID A should remain. +TEST_F(RedactSchedSwitchFtraceEventTest, KeepsTargetCommValues) { + context_.package_uid = kUidA; + + auto packet_buffer = packet_.SerializeAsString(); + + ASSERT_OK(redact_.Transform(context_, &packet_buffer)); + + protos::gen::TracePacket packet; + ASSERT_TRUE(packet.ParseFromString(packet_buffer)); + + const auto& bundle = packet.ftrace_events(); + const auto& events = bundle.event(); + + ASSERT_EQ(events.size(), 2u); + + ASSERT_EQ(events[0].sched_switch().prev_pid(), kPidA); + ASSERT_EQ(events[0].sched_switch().prev_comm(), kCommA); + + ASSERT_EQ(events[0].sched_switch().next_pid(), kPidB); + ASSERT_EQ(events[0].sched_switch().next_comm(), kCommNone); + + ASSERT_EQ(events[1].sched_switch().prev_pid(), kPidB); + ASSERT_EQ(events[1].sched_switch().prev_comm(), kCommNone); + + ASSERT_EQ(events[1].sched_switch().next_pid(), kPidA); + ASSERT_EQ(events[1].sched_switch().next_comm(), kCommA); +} + +// This case is very similar to the "some are connected", expect that it +// verifies all comm values will be removed when testing against an unused +// uid. +TEST_F(RedactSchedSwitchFtraceEventTest, RemovesAllCommsIfPackageDoesntExist) { + context_.package_uid = kUidC; + + auto packet_buffer = packet_.SerializeAsString(); + + ASSERT_OK(redact_.Transform(context_, &packet_buffer)); + + protos::gen::TracePacket packet; + ASSERT_TRUE(packet.ParseFromString(packet_buffer)); + + const auto& bundle = packet.ftrace_events(); + const auto& events = bundle.event(); + + ASSERT_EQ(events.size(), 2u); + + ASSERT_EQ(events[0].sched_switch().prev_comm(), kCommNone); + ASSERT_EQ(events[0].sched_switch().next_comm(), kCommNone); + + ASSERT_EQ(events[1].sched_switch().prev_comm(), kCommNone); + ASSERT_EQ(events[1].sched_switch().next_comm(), kCommNone); +} + +class RedactCompactSchedSwitchTest : public testing::Test { + protected: + void SetUp() override { + // PID A and PID B need to be attached to different packages (UID) so that + // its possible to include one but not the other. + context_.timeline = std::make_unique(); + context_.timeline->Append( + ProcessThreadTimeline::Event::Open(kTimeA, kPidA, kNoParent, kUidA)); + context_.timeline->Append( + ProcessThreadTimeline::Event::Open(kTimeA, kPidB, kNoParent, kUidB)); + context_.timeline->Sort(); + + auto* bundle = packet_.mutable_ftrace_events(); + bundle->set_cpu(kCpuA); // All switch events occur on this CPU + + compact_sched = bundle->mutable_compact_sched(); + + compact_sched->add_intern_table(kCommA); + compact_sched->add_intern_table(kCommB); + + redact_.emplace_modifier(); + redact_.emplace_waking_filter(); + } + + void AddSwitchEvent(uint64_t ts, + int32_t next_pid, + int32_t prev_state, + int32_t prio, + uint32_t comm) { + compact_sched->add_switch_timestamp(ts); + compact_sched->add_switch_next_pid(next_pid); + compact_sched->add_switch_prev_state(prev_state); + compact_sched->add_switch_next_prio(prio); + compact_sched->add_switch_next_comm_index(comm); + } + + protos::gen::TracePacket packet_; + protos::gen::FtraceEventBundle::CompactSched* compact_sched; + + Context context_; + RedactSchedEvents redact_; +}; + +TEST_F(RedactCompactSchedSwitchTest, KeepsTargetCommValues) { + uint32_t kCommIndexA = 0; + uint32_t kCommIndexB = 1; + + // The new entry will be appended to the table. Another primitive can be used + // to reduce the intern string table. + uint32_t kCommIndexNone = 2; + + AddSwitchEvent(kTimeA, kPidA, 0, 0, kCommIndexA); + AddSwitchEvent(kTimeB, kPidB, 0, 0, kCommIndexB); + + context_.package_uid = kUidA; + + auto packet_buffer = packet_.SerializeAsString(); + + ASSERT_OK(redact_.Transform(context_, &packet_buffer)); + + protos::gen::TracePacket packet; + ASSERT_TRUE(packet.ParseFromString(packet_buffer)); + + const auto& bundle = packet.ftrace_events(); + ASSERT_TRUE(bundle.has_compact_sched()); + + const auto& compact_sched = bundle.compact_sched(); + + // A new entry (empty string) should have been added to the table. + ASSERT_EQ(compact_sched.intern_table_size(), 3); + ASSERT_EQ(compact_sched.intern_table().back(), kCommNone); + + ASSERT_EQ(compact_sched.switch_next_comm_index_size(), 2); + ASSERT_EQ(compact_sched.switch_next_comm_index().at(0), kCommIndexA); + ASSERT_EQ(compact_sched.switch_next_comm_index().at(1), kCommIndexNone); +} + +// If two pids use the same comm, but one pid changes, the shared comm should +// still be available. +TEST_F(RedactCompactSchedSwitchTest, ChangingSharedCommonRetainsComm) { + uint32_t kCommIndexA = 0; + + AddSwitchEvent(kTimeA, kPidA, 0, 0, kCommIndexA); + AddSwitchEvent(kTimeB, kPidB, 0, 0, kCommIndexA); + + context_.package_uid = kUidA; + + auto packet_buffer = packet_.SerializeAsString(); + + ASSERT_OK(redact_.Transform(context_, &packet_buffer)); + + protos::gen::TracePacket packet; + ASSERT_TRUE(packet.ParseFromString(packet_buffer)); + + const auto& bundle = packet.ftrace_events(); + ASSERT_TRUE(bundle.has_compact_sched()); + + const auto& compact_sched = bundle.compact_sched(); + + // A new entry should have been appended, but comm A (previously shared) + // should still exist in the table. + ASSERT_EQ(compact_sched.intern_table_size(), 3); + ASSERT_EQ(compact_sched.intern_table().front(), kCommA); + ASSERT_EQ(compact_sched.intern_table().back(), kCommNone); +} + +TEST_F(RedactCompactSchedSwitchTest, RemovesAllCommsIfPackageDoesntExist) { + uint32_t kCommIndexA = 0; + uint32_t kCommIndexB = 1; + + // The new entry will be appended to the table. Another primitive can be used + // to reduce the intern string table. + uint32_t kCommIndexNone = 2; + + AddSwitchEvent(kTimeA, kPidA, 0, 0, kCommIndexA); + AddSwitchEvent(kTimeB, kPidB, 0, 0, kCommIndexB); + + context_.package_uid = kUidC; + + auto packet_buffer = packet_.SerializeAsString(); + + ASSERT_OK(redact_.Transform(context_, &packet_buffer)); + + protos::gen::TracePacket packet; + ASSERT_TRUE(packet.ParseFromString(packet_buffer)); + + const auto& bundle = packet.ftrace_events(); + ASSERT_TRUE(bundle.has_compact_sched()); + + const auto& compact_sched = bundle.compact_sched(); + + // A new entry (empty string) should have been added to the table. + ASSERT_EQ(compact_sched.intern_table_size(), 3); + ASSERT_EQ(compact_sched.intern_table().back(), kCommNone); + + ASSERT_EQ(compact_sched.switch_next_comm_index_size(), 2); + ASSERT_EQ(compact_sched.switch_next_comm_index().at(0), kCommIndexNone); + ASSERT_EQ(compact_sched.switch_next_comm_index().at(1), kCommIndexNone); +} + +TEST_F(RedactCompactSchedSwitchTest, CanChangePid) { + uint32_t kCommIndexA = 0; + uint32_t kCommIndexB = 1; + + AddSwitchEvent(kTimeA, kPidA, 0, 0, kCommIndexA); + AddSwitchEvent(kTimeB, kPidB, 0, 0, kCommIndexB); + + // Because the target is package A, PidA should be remain. PidB should change. + context_.package_uid = kUidA; + + auto packet_buffer = packet_.SerializeAsString(); + + redact_.emplace_modifier>(); + + ASSERT_OK(redact_.Transform(context_, &packet_buffer)); + + protos::gen::TracePacket packet; + ASSERT_TRUE(packet.ParseFromString(packet_buffer)); + + const auto& bundle = packet.ftrace_events(); + ASSERT_TRUE(bundle.has_compact_sched()); + + const auto& compact_sched = bundle.compact_sched(); + + // The intern table should not change. + ASSERT_EQ(compact_sched.intern_table_size(), 2); + + ASSERT_EQ(compact_sched.switch_next_pid_size(), 2); + ASSERT_EQ(compact_sched.switch_next_pid().at(0), kPidA); + + // Because Pid B was not connected to Uid A, it should have its pid changed. + ASSERT_EQ(compact_sched.switch_next_pid().at(1), kPidC); +} + +class RedactSchedWakingFtraceEventTest : public testing::Test { + protected: + void SetUp() override { + // Create a packet where two pids are swapping back-and-forth. + auto* bundle = packet_.mutable_ftrace_events(); + bundle->set_cpu(kCpuA); + + // Pid A wakes up Pid B at time Time B + { + auto* event = bundle->add_event(); + + event->set_timestamp(kTimeB); + event->set_pid(kPidA); + + auto* sched_waking = event->mutable_sched_waking(); + sched_waking->set_comm(kCommB); + sched_waking->set_pid(kPidB); + sched_waking->set_prio(0); + sched_waking->set_success(true); + sched_waking->set_target_cpu(kCpuB); + } + + // Pid A wakes up Pid C at time Time C. + { + auto* event = bundle->add_event(); + + event->set_timestamp(kTimeC); + event->set_pid(kPidA); + + auto* sched_waking = event->mutable_sched_waking(); + sched_waking->set_comm(kCommC); + sched_waking->set_pid(kPidC); + sched_waking->set_prio(0); + sched_waking->set_success(true); + sched_waking->set_target_cpu(kCpuC); + } + + // PID A and PID B need to be attached to different packages (UID) so that + // its possible to include one but not the other. + context_.timeline = std::make_unique(); + context_.timeline->Append( + ProcessThreadTimeline::Event::Open(kTimeA, kPidA, kNoParent, kUidA)); + context_.timeline->Append( + ProcessThreadTimeline::Event::Open(kTimeA, kPidB, kNoParent, kUidB)); + context_.timeline->Append( + ProcessThreadTimeline::Event::Open(kTimeA, kPidC, kNoParent, kUidC)); + context_.timeline->Sort(); + + redact.emplace_modifier(); + redact.emplace_waking_filter(); + } + + protos::gen::TracePacket packet_; + Context context_; + + RedactSchedEvents redact; +}; + +TEST_F(RedactSchedWakingFtraceEventTest, WakeeKeepsCommWhenConnectedToPackage) { + context_.package_uid = kUidB; + + auto packet_buffer = packet_.SerializeAsString(); + + ASSERT_OK(redact.Transform(context_, &packet_buffer)); + + protos::gen::TracePacket packet; + ASSERT_TRUE(packet.ParseFromString(packet_buffer)); + + const auto& bundle = packet.ftrace_events(); + const auto& events = bundle.event(); + + ASSERT_EQ(events.size(), 2u); + + ASSERT_EQ(events.front().sched_waking().comm(), kCommB); + ASSERT_EQ(events.back().sched_waking().comm(), kCommNone); +} + +TEST_F(RedactSchedWakingFtraceEventTest, + WakeeLosesCommWhenNotConnectedToPackage) { + context_.package_uid = kUidA; + + auto packet_buffer = packet_.SerializeAsString(); + + ASSERT_OK(redact.Transform(context_, &packet_buffer)); + + protos::gen::TracePacket packet; + ASSERT_TRUE(packet.ParseFromString(packet_buffer)); + + const auto& bundle = packet.ftrace_events(); + const auto& events = bundle.event(); + + ASSERT_EQ(events.size(), 2u); + + ASSERT_EQ(events.front().sched_waking().comm(), kCommNone); + ASSERT_EQ(events.back().sched_waking().comm(), kCommNone); +} + +TEST_F(RedactSchedWakingFtraceEventTest, WakeeKeepsPidWhenConnectedToPackage) { + redact.emplace_modifier>(); + + context_.package_uid = kUidB; + + auto packet_buffer = packet_.SerializeAsString(); + + ASSERT_OK(redact.Transform(context_, &packet_buffer)); + + protos::gen::TracePacket packet; + ASSERT_TRUE(packet.ParseFromString(packet_buffer)); + + const auto& bundle = packet.ftrace_events(); + const auto& events = bundle.event(); + + ASSERT_EQ(events.size(), 2u); + + ASSERT_EQ(events.front().sched_waking().pid(), kPidB); + + // Because Pid C was not connected to Uid B, it should have its pid changed. + ASSERT_EQ(events.back().sched_waking().pid(), kPidD); +} + +TEST_F(RedactSchedWakingFtraceEventTest, + WakeeLosesPidWhenNotConnectedToPackage) { + redact.emplace_modifier>(); + + context_.package_uid = kUidA; + + auto packet_buffer = packet_.SerializeAsString(); + + ASSERT_OK(redact.Transform(context_, &packet_buffer)); + + protos::gen::TracePacket packet; + ASSERT_TRUE(packet.ParseFromString(packet_buffer)); + + const auto& bundle = packet.ftrace_events(); + const auto& events = bundle.event(); + + ASSERT_EQ(events.size(), 2u); + + // Both pids should have changed. + ASSERT_EQ(events.at(0).sched_waking().pid(), kPidD); + ASSERT_EQ(events.at(1).sched_waking().pid(), kPidD); +} + +TEST_F(RedactSchedWakingFtraceEventTest, WakerPidIsLeftUnaffected) { + redact.emplace_modifier>(); + + context_.package_uid = kUidB; + + auto packet_buffer = packet_.SerializeAsString(); + + ASSERT_OK(redact.Transform(context_, &packet_buffer)); + + protos::gen::TracePacket packet; + ASSERT_TRUE(packet.ParseFromString(packet_buffer)); + + const auto& bundle = packet.ftrace_events(); + const auto& events = bundle.event(); + + ASSERT_EQ(events.size(), 2u); + + // The waker in the ftrace event waking event should change, but by another + // primitive. This case only appears in the ftrace events because the waker is + // inferred in the comp sched case. + ASSERT_EQ(events.at(0).pid(), static_cast(kPidA)); + ASSERT_EQ(events.at(1).pid(), static_cast(kPidA)); +} + +class FilterCompactSchedWakingEventsTest : public testing::Test { + protected: + void SetUp() { + // Uid B is used instead of Uid A because Pid A, belonging to Uid A, is the + // waker. Pid B and Pid C are the wakees. + context_.package_uid = kUidB; + + // FilterSchedWakingEvents expects a timeline because most + // FilterSchedWakingEvents::Filter filters will need one. However, the + // filter used in this test doesn't require one. + context_.timeline = std::make_unique(); + + context_.timeline->Append( + ProcessThreadTimeline::Event::Open(kTimeA, kPidA, kNoParent, kUidA)); + context_.timeline->Append( + ProcessThreadTimeline::Event::Open(kTimeA, kPidB, kNoParent, kUidB)); + context_.timeline->Append( + ProcessThreadTimeline::Event::Open(kTimeA, kPidC, kNoParent, kUidC)); + context_.timeline->Sort(); + + // Default to "allow all" and "change nothing" so a test only needs to + // override what they need. + redact_.emplace_waking_filter(); + redact_.emplace_modifier(); + } + + Context context_; + RedactSchedEvents redact_; +}; + +// Builds a simple ftrace bundle that contains two ftrace events: +// +// - Pid A wakes up pid B +// - Pid A wakes up pid C +// +// Because compact sched uses associative arrays, the data will look like: +// +// - Time | PID | CPU | * +// -----+-------+-------+--- +// 0.5 | kPidB | kCpuB | +// 1.5 | kPidC | kCpuB | +// +// Because the filter will only keep events where pid is being waked, only the +// first of the two events should remain. +TEST_F(FilterCompactSchedWakingEventsTest, FilterCompactSched) { + redact_.emplace_waking_filter(); + + protos::gen::TracePacket packet_builder; + packet_builder.mutable_ftrace_events()->set_cpu(kCpuA); + + auto* compact_sched = + packet_builder.mutable_ftrace_events()->mutable_compact_sched(); + + compact_sched->add_intern_table(kCommA); + + // Implementation detail: The timestamp, target cpu, and pid matter. The other + // values are copied to the output, but have no influence over the internal + // logic. + compact_sched->add_waking_comm_index(0); + compact_sched->add_waking_common_flags(0); + compact_sched->add_waking_prio(0); + compact_sched->add_waking_timestamp(kHalfStep); + compact_sched->add_waking_target_cpu(kCpuB); + compact_sched->add_waking_pid(kPidB); + + compact_sched->add_waking_comm_index(0); + compact_sched->add_waking_common_flags(0); + compact_sched->add_waking_prio(0); + compact_sched->add_waking_timestamp(kFullStep + kHalfStep); + compact_sched->add_waking_target_cpu(kCpuB); + compact_sched->add_waking_pid(kPidC); + + auto bytes = packet_builder.SerializeAsString(); + ASSERT_OK(redact_.Transform(context_, &bytes)); + + protos::gen::TracePacket packet; + packet.ParseFromString(bytes); + + ASSERT_TRUE(packet.has_ftrace_events()); + + const auto& events = packet.ftrace_events(); + ASSERT_TRUE(events.has_compact_sched()); + + // All events not from Pid B should be removed. In this case, that means the + // event from Pid C should be dropped. + ASSERT_EQ(events.compact_sched().waking_pid_size(), 1); + ASSERT_EQ(events.compact_sched().waking_pid().at(0), kPidB); +} + +// Timing information is based off delta-time values. When a row is removed +// from the compact sched arrays, downstream timing data is corrupted. The +// delta value of removed rows should be rolled into the next row. +TEST_F(FilterCompactSchedWakingEventsTest, + CorrectsTimeWhenRemovingWakingEvents) { + // All the times are delta times. The commented times are the absolute times. + std::array before = { + 0, + kFullStep, // 1 + kFullStep, // 2 + kHalfStep, // 2.5 + kHalfStep, // 3 + kFullStep, // 4 + kFullStep, // 5 + }; + + // These are the times that should be drop + std::array drop_times = { + kFullStep, // 6 + kFullStep, // 7 + kHalfStep, // 7.5 + }; + + // When the times are dropped, the times removed from drop_times should be + // rolling into the first time. So it should got from 1 unit to 3.5 units. + std::array after = { + kFullStep, // 8 + kFullStep, // 9 + }; + + protos::gen::TracePacket packet_builder; + packet_builder.mutable_ftrace_events()->set_cpu(kCpuA); + + auto* compact_sched = + packet_builder.mutable_ftrace_events()->mutable_compact_sched(); + + compact_sched->add_intern_table(kCommA); + + // Before and after, this events should not be affected. + for (auto time : before) { + compact_sched->add_waking_comm_index(0); + compact_sched->add_waking_common_flags(0); + compact_sched->add_waking_prio(0); + compact_sched->add_waking_timestamp(time); + compact_sched->add_waking_target_cpu(kCpuB); + compact_sched->add_waking_pid(kPidB); + } + + // Use pid B so that these times will be dropped. + for (auto time : drop_times) { + compact_sched->add_waking_comm_index(0); + compact_sched->add_waking_common_flags(0); + compact_sched->add_waking_prio(0); + compact_sched->add_waking_timestamp(time); + compact_sched->add_waking_target_cpu(kCpuB); + compact_sched->add_waking_pid(kPidC); + } + + // After redaction, these events should still exist, but the first event in + // this series, the timestamp should be larger (before of the dropped events). + for (auto time : after) { + compact_sched->add_waking_comm_index(0); + compact_sched->add_waking_common_flags(0); + compact_sched->add_waking_prio(0); + compact_sched->add_waking_timestamp(time); + compact_sched->add_waking_target_cpu(kCpuB); + compact_sched->add_waking_pid(kPidB); + } + + auto bytes = packet_builder.SerializeAsString(); + + redact_.emplace_waking_filter(); + ASSERT_OK(redact_.Transform(context_, &bytes)); + + protos::gen::TracePacket packet; + ASSERT_TRUE(packet.ParseFromString(bytes)); + + ASSERT_TRUE(packet.has_ftrace_events()); + const auto& events = packet.ftrace_events(); + + ASSERT_TRUE(events.has_compact_sched()); + const auto& times = packet.ftrace_events().compact_sched().waking_timestamp(); + + ASSERT_EQ(times.size(), 9u); // i.e. before + after + + // Nothing in the before should have changed. + for (size_t i = 0; i < before.size(); ++i) { + ASSERT_EQ(times[i], before[i]); + } + + // Sum of all dropped event time. + ASSERT_EQ(drop_times.size(), 3u); + auto lost_time = drop_times[0] + drop_times[1] + drop_times[2]; + + // Only the first of the two "after" events should have changed. + ASSERT_EQ(times[before.size()], after[0] + lost_time); + ASSERT_EQ(times[before.size() + 1], after[1]); +} + +// This is an implementation detail. When an event is removed, the gap is +// collapsed into the next event by tracking the error created by removing the +// event. If implemented incorrectly, flipping between keep and remove will +// break as the error will not be reset correctly. +TEST_F(FilterCompactSchedWakingEventsTest, RemovingWakingEventsThrashing) { + // X : Drop this event + // [ ] : This is an event + // = : Number of time units + // + // X X X + // [==][==][=][==][==][=][==][==][=] + // + // Events are going to follow a "keep, keep, drop" pattern. All keep events + // will be full time units. All drop events will be half time units. + // + // It is key to notice that the series ends on a removed event. This creates a + // special: remove an event without an event to accept the error. + std::array before = { + 0, // abs time 0 + kFullStep, // abs time 1 + kHalfStep, // abs time 1.5 + + kFullStep, // abs time 2.5 + kFullStep, // abs time 3.5 + kHalfStep, // abs time 4 + + kFullStep, // abs time 5 + kFullStep, // abs time 6 + kHalfStep, // abs time 6.5 + }; + + std::array after = { + 0, // abs time 0 + kFullStep, // abs time 1 + kFullStep + kHalfStep, // abs time 2.5 + kFullStep, // abs time 3.5 + kFullStep + kHalfStep, // abs time 5 + kFullStep, // abs time 6 + }; + + protos::gen::TracePacket packet_builder; + packet_builder.mutable_ftrace_events()->set_cpu(kCpuA); + + auto* compact_sched = + packet_builder.mutable_ftrace_events()->mutable_compact_sched(); + + compact_sched->add_intern_table(kCommA); + + for (size_t i = 0; i < before.size(); ++i) { + auto time = before[i]; + + compact_sched->add_waking_comm_index(0); + compact_sched->add_waking_common_flags(0); + compact_sched->add_waking_prio(0); + compact_sched->add_waking_timestamp(time); + compact_sched->add_waking_target_cpu(kCpuB); + + // The pattern is "keep, keep, drop", therefore, PID B > B > C ... + if (i % 3 == 2) { + compact_sched->add_waking_pid(kPidC); + } else { + compact_sched->add_waking_pid(kPidB); + } + } + + auto bytes = packet_builder.SerializeAsString(); + + redact_.emplace_waking_filter(); + ASSERT_OK(redact_.Transform(context_, &bytes)); + + protos::gen::TracePacket packet; + ASSERT_TRUE(packet.ParseFromString(bytes)); + + ASSERT_TRUE(packet.has_ftrace_events()); + const auto& events = packet.ftrace_events(); + + ASSERT_TRUE(events.has_compact_sched()); + const auto& times = packet.ftrace_events().compact_sched().waking_timestamp(); + + ASSERT_EQ(times.size(), after.size()); + + for (size_t i = 0; i < after.size(); ++i) { + ASSERT_EQ(times[i], after[i]); + } +} + +} // namespace perfetto::trace_redaction diff --git a/src/trace_redaction/redact_sched_switch.cc b/src/trace_redaction/redact_sched_switch.cc deleted file mode 100644 index 83cb36f79e..0000000000 --- a/src/trace_redaction/redact_sched_switch.cc +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/trace_redaction/redact_sched_switch.h" - -#include "src/trace_redaction/proto_util.h" - -#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h" -#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h" -#include "protos/perfetto/trace/ftrace/sched.pbzero.h" - -namespace perfetto::trace_redaction { - -namespace { - -// TODO(vaage): Merge with RedactComm in redact_task_newtask.cc. -protozero::ConstChars RedactComm(const Context& context, - uint64_t ts, - int32_t pid, - protozero::ConstChars comm) { - if (context.timeline->PidConnectsToUid(ts, pid, *context.package_uid)) { - return comm; - } - - return {}; -} - -} // namespace - -// Redact sched switch trace events in an ftrace event bundle: -// -// event { -// timestamp: 6702093744772646 -// pid: 0 -// sched_switch { -// prev_comm: "swapper/0" -// prev_pid: 0 -// prev_prio: 120 -// prev_state: 0 -// next_comm: "writer" -// next_pid: 23020 -// next_prio: 96 -// } -// } -// -// In the above message, it should be noted that "event.pid" will always be -// equal to "event.sched_switch.prev_pid". -// -// "ftrace_event_bundle_message" is the ftrace event bundle (contains a -// collection of ftrace event messages) because data in a sched_switch message -// is needed in order to know if the event should be added to the bundle. - -base::Status RedactSchedSwitch::Redact( - const Context& context, - const protos::pbzero::FtraceEventBundle::Decoder&, - protozero::ProtoDecoder& event, - protos::pbzero::FtraceEvent* event_message) const { - if (!context.package_uid.has_value()) { - return base::ErrStatus("RedactSchedSwitch: missing package uid"); - } - - if (!context.timeline) { - return base::ErrStatus("RedactSchedSwitch: missing timeline"); - } - - // The timestamp is needed to do the timeline look-up. If the packet has no - // timestamp, don't add the sched switch event. This is the safest option. - auto timestamp = - event.FindField(protos::pbzero::FtraceEvent::kTimestampFieldNumber); - if (!timestamp.valid()) { - return base::OkStatus(); - } - - auto sched_switch = - event.FindField(protos::pbzero::FtraceEvent::kSchedSwitchFieldNumber); - if (!sched_switch.valid()) { - return base::ErrStatus( - "RedactSchedSwitch: was used for unsupported field type"); - } - - protozero::ProtoDecoder sched_switch_decoder(sched_switch.as_bytes()); - - auto prev_pid = sched_switch_decoder.FindField( - protos::pbzero::SchedSwitchFtraceEvent::kPrevPidFieldNumber); - auto next_pid = sched_switch_decoder.FindField( - protos::pbzero::SchedSwitchFtraceEvent::kNextPidFieldNumber); - - // There must be a prev pid and a next pid. Otherwise, the event is invalid. - // Dropping the event is the safest option. - if (!prev_pid.valid() || !next_pid.valid()) { - return base::OkStatus(); - } - - // Avoid making the message until we know that we have prev and next pids. - auto sched_switch_message = event_message->set_sched_switch(); - - for (auto field = sched_switch_decoder.ReadField(); field.valid(); - field = sched_switch_decoder.ReadField()) { - switch (field.id()) { - case protos::pbzero::SchedSwitchFtraceEvent::kNextCommFieldNumber: - sched_switch_message->set_next_comm( - RedactComm(context, timestamp.as_uint64(), next_pid.as_int32(), - field.as_string())); - break; - - case protos::pbzero::SchedSwitchFtraceEvent::kPrevCommFieldNumber: - sched_switch_message->set_prev_comm( - RedactComm(context, timestamp.as_uint64(), prev_pid.as_int32(), - field.as_string())); - break; - - default: - proto_util::AppendField(field, sched_switch_message); - break; - } - } - - return base::OkStatus(); -} - -} // namespace perfetto::trace_redaction diff --git a/src/trace_redaction/redact_sched_switch.h b/src/trace_redaction/redact_sched_switch.h deleted file mode 100644 index bcfa30ddb5..0000000000 --- a/src/trace_redaction/redact_sched_switch.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef SRC_TRACE_REDACTION_REDACT_SCHED_SWITCH_H_ -#define SRC_TRACE_REDACTION_REDACT_SCHED_SWITCH_H_ - -#include "src/trace_redaction/redact_ftrace_event.h" -#include "src/trace_redaction/trace_redaction_framework.h" - -namespace perfetto::trace_redaction { - -// Goes through ftrace events and conditonally removes the comm values from -// sched switch events. -class RedactSchedSwitch : public FtraceEventRedaction { - public: - static constexpr auto kFieldId = - protos::pbzero::FtraceEvent::kSchedSwitchFieldNumber; - - base::Status Redact( - const Context& context, - const protos::pbzero::FtraceEventBundle::Decoder& bundle, - protozero::ProtoDecoder& event, - protos::pbzero::FtraceEvent* event_message) const override; -}; - -} // namespace perfetto::trace_redaction - -#endif // SRC_TRACE_REDACTION_REDACT_SCHED_SWITCH_H_ diff --git a/src/trace_redaction/redact_sched_switch_unittest.cc b/src/trace_redaction/redact_sched_switch_unittest.cc deleted file mode 100644 index 72b65f4a56..0000000000 --- a/src/trace_redaction/redact_sched_switch_unittest.cc +++ /dev/null @@ -1,202 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/trace_redaction/redact_sched_switch.h" -#include "perfetto/protozero/scattered_heap_buffer.h" -#include "src/base/test/status_matchers.h" -#include "test/gtest_and_gmock.h" - -#include "protos/perfetto/trace/ftrace/ftrace_event.gen.h" -#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.gen.h" -#include "protos/perfetto/trace/ftrace/sched.gen.h" -#include "protos/perfetto/trace/trace.gen.h" -#include "protos/perfetto/trace/trace_packet.gen.h" - -namespace perfetto::trace_redaction { - -namespace { -constexpr uint64_t kUidA = 1; -constexpr uint64_t kUidB = 2; -constexpr uint64_t kUidC = 3; - -constexpr int32_t kNoParent = 10; -constexpr int32_t kPidA = 11; -constexpr int32_t kPidB = 12; - -constexpr std::string_view kCommA = "comm-a"; -constexpr std::string_view kCommB = "comm-b"; - -} // namespace - -// Tests which nested messages and fields are removed. -class RedactSchedSwitchTest : public testing::Test { - protected: - void SetUp() override { - auto* event = bundle_.add_event(); - - event->set_timestamp(123456789); - event->set_pid(kPidA); - - auto* sched_switch = event->mutable_sched_switch(); - sched_switch->set_prev_comm(std::string(kCommA)); - sched_switch->set_prev_pid(kPidA); - sched_switch->set_next_comm(std::string(kCommB)); - sched_switch->set_next_pid(kPidB); - } - - base::Status Redact(const Context& context, - protos::pbzero::FtraceEvent* event_message) { - RedactSchedSwitch redact; - - auto bundle_str = bundle_.SerializeAsString(); - protos::pbzero::FtraceEventBundle::Decoder bundle_decoder(bundle_str); - - auto event_str = bundle_.event().back().SerializeAsString(); - protos::pbzero::FtraceEvent::Decoder event_decoder(event_str); - - return redact.Redact(context, bundle_decoder, event_decoder, event_message); - } - - const std::string& event_string() const { return event_string_; } - - // This test breaks the rules for task_newtask and the timeline. The - // timeline will report the task existing before the new task event. This - // should not happen in the field, but it makes the test more robust. - std::unique_ptr CreatePopulatedTimeline() { - auto timeline = std::make_unique(); - - timeline->Append( - ProcessThreadTimeline::Event::Open(0, kPidA, kNoParent, kUidA)); - timeline->Append( - ProcessThreadTimeline::Event::Open(0, kPidB, kNoParent, kUidB)); - timeline->Sort(); - - return timeline; - } - - private: - std::string event_string_; - - std::unique_ptr timeline_; - - protos::gen::FtraceEventBundle bundle_; -}; - -TEST_F(RedactSchedSwitchTest, RejectMissingPackageUid) { - RedactSchedSwitch redact; - - Context context; - context.timeline = std::make_unique(); - - protozero::HeapBuffered event_message; - auto result = Redact(context, event_message.get()); - ASSERT_FALSE(result.ok()); -} - -TEST_F(RedactSchedSwitchTest, RejectMissingTimeline) { - RedactSchedSwitch redact; - - Context context; - context.package_uid = kUidA; - - protozero::HeapBuffered event_message; - auto result = Redact(context, event_message.get()); - ASSERT_FALSE(result.ok()); -} - -TEST_F(RedactSchedSwitchTest, ReplacePrevAndNextWithEmptyStrings) { - RedactSchedSwitch redact; - - Context context; - context.timeline = CreatePopulatedTimeline(); - - // Neither pid is connected to the target package (see timeline - // initialization). - context.package_uid = kUidC; - - protozero::HeapBuffered event_message; - auto result = Redact(context, event_message.get()); - ASSERT_OK(result) << result.c_message(); - - protos::gen::FtraceEvent event; - event.ParseFromString(event_message.SerializeAsString()); - - ASSERT_TRUE(event.has_sched_switch()); - - // Cleared prev and next comm. - ASSERT_TRUE(event.sched_switch().has_prev_comm()); - ASSERT_TRUE(event.sched_switch().prev_comm().empty()); - - ASSERT_TRUE(event.sched_switch().has_next_comm()); - ASSERT_TRUE(event.sched_switch().next_comm().empty()); -} - -TEST_F(RedactSchedSwitchTest, ReplacePrevWithEmptyStrings) { - RedactSchedSwitch redact; - - Context context; - context.timeline = CreatePopulatedTimeline(); - - // Only next pid is connected to the target package (see timeline - // initialization). - context.package_uid = kUidB; - - protozero::HeapBuffered event_message; - auto result = Redact(context, event_message.get()); - - ASSERT_OK(result) << result.c_message(); - - protos::gen::FtraceEvent event; - event.ParseFromString(event_message.SerializeAsString()); - - ASSERT_TRUE(event.has_sched_switch()); - - // Only cleared the prev comm. - ASSERT_TRUE(event.sched_switch().has_prev_comm()); - ASSERT_TRUE(event.sched_switch().prev_comm().empty()); - - ASSERT_TRUE(event.sched_switch().has_next_comm()); - ASSERT_FALSE(event.sched_switch().next_comm().empty()); -} - -TEST_F(RedactSchedSwitchTest, ReplaceNextWithEmptyStrings) { - RedactSchedSwitch redact; - - Context context; - context.timeline = CreatePopulatedTimeline(); - - // Only prev pid is connected to the target package (see timeline - // initialization). - context.package_uid = kUidA; - - protozero::HeapBuffered event_message; - auto result = Redact(context, event_message.get()); - ASSERT_OK(result) << result.c_message(); - - protos::gen::FtraceEvent event; - event.ParseFromString(event_message.SerializeAsString()); - - ASSERT_TRUE(event.has_sched_switch()); - - ASSERT_TRUE(event.sched_switch().has_prev_comm()); - ASSERT_FALSE(event.sched_switch().prev_comm().empty()); - - // Only cleared the next comm. - ASSERT_TRUE(event.sched_switch().has_next_comm()); - ASSERT_TRUE(event.sched_switch().next_comm().empty()); -} - -} // namespace perfetto::trace_redaction diff --git a/src/trace_redaction/redact_task_newtask.cc b/src/trace_redaction/redact_task_newtask.cc deleted file mode 100644 index fb0cfe8bac..0000000000 --- a/src/trace_redaction/redact_task_newtask.cc +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/trace_redaction/redact_task_newtask.h" - -#include "src/trace_redaction/proto_util.h" - -#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h" -#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h" -#include "protos/perfetto/trace/ftrace/task.pbzero.h" - -namespace perfetto::trace_redaction { - -namespace { - -// TODO(vaage): Merge with RedactComm in redact_sched_switch.cc. -protozero::ConstChars RedactComm(const Context& context, - uint64_t ts, - int32_t pid, - protozero::ConstChars comm) { - if (context.timeline->PidConnectsToUid(ts, pid, *context.package_uid)) { - return comm; - } - - return {}; -} - -} // namespace -// Redact sched switch trace events in an ftrace event bundle: -// -// event { -// timestamp: 6702094133317685 -// pid: 6167 -// task_newtask { -// pid: 7972 -// comm: "adbd" -// clone_flags: 4001536 -// oom_score_adj: -1000 -// } -// } -// -// In the above message, it should be noted that "event.pid" will never be -// equal to "event.task_newtask.pid" (a thread cannot start itself). -base::Status RedactTaskNewTask::Redact( - const Context& context, - const protos::pbzero::FtraceEventBundle::Decoder&, - protozero::ProtoDecoder& event, - protos::pbzero::FtraceEvent* event_message) const { - if (!context.package_uid.has_value()) { - return base::ErrStatus("RedactTaskNewTask: missing package uid"); - } - - if (!context.timeline) { - return base::ErrStatus("RedactTaskNewTask: missing timeline"); - } - - // The timestamp is needed to do the timeline look-up. If the packet has no - // timestamp, don't add the sched switch event. This is the safest option. - auto timestamp = - event.FindField(protos::pbzero::FtraceEvent::kTimestampFieldNumber); - if (!timestamp.valid()) { - return base::OkStatus(); - } - - auto new_task = - event.FindField(protos::pbzero::FtraceEvent::kTaskNewtaskFieldNumber); - if (!new_task.valid()) { - return base::ErrStatus( - "RedactTaskNewTask: was used for unsupported field type"); - } - - protozero::ProtoDecoder new_task_decoder(new_task.as_bytes()); - - auto pid = new_task_decoder.FindField( - protos::pbzero::TaskNewtaskFtraceEvent::kPidFieldNumber); - - if (!pid.valid()) { - return base::OkStatus(); - } - - // Avoid making the message until we know that we have prev and next pids. - auto* new_task_message = event_message->set_task_newtask(); - - for (auto field = new_task_decoder.ReadField(); field.valid(); - field = new_task_decoder.ReadField()) { - // Perfetto view (ui.perfetto.dev) crashes if the comm value is missing. - // To work around this, the comm value is replaced with an empty string. - // This appears to work. - if (field.id() == - protos::pbzero::TaskNewtaskFtraceEvent::kCommFieldNumber) { - new_task_message->set_comm(RedactComm(context, timestamp.as_uint64(), - pid.as_int32(), field.as_string())); - } else { - proto_util::AppendField(field, new_task_message); - } - } - - return base::OkStatus(); -} - -} // namespace perfetto::trace_redaction diff --git a/src/trace_redaction/redact_task_newtask.h b/src/trace_redaction/redact_task_newtask.h deleted file mode 100644 index 51c11243d2..0000000000 --- a/src/trace_redaction/redact_task_newtask.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef SRC_TRACE_REDACTION_REDACT_TASK_NEWTASK_H_ -#define SRC_TRACE_REDACTION_REDACT_TASK_NEWTASK_H_ - -#include "src/trace_redaction/redact_ftrace_event.h" -#include "src/trace_redaction/trace_redaction_framework.h" - -namespace perfetto::trace_redaction { - -// Goes through ftrace events and conditonally removes the comm values from -// task_newtask events. -class RedactTaskNewTask : public FtraceEventRedaction { - public: - static constexpr auto kFieldId = - protos::pbzero::FtraceEvent::kTaskNewtaskFieldNumber; - - base::Status Redact( - const Context& context, - const protos::pbzero::FtraceEventBundle::Decoder& bundle, - protozero::ProtoDecoder& event, - protos::pbzero::FtraceEvent* event_message) const override; -}; - -} // namespace perfetto::trace_redaction - -#endif // SRC_TRACE_REDACTION_REDACT_TASK_NEWTASK_H_ diff --git a/src/trace_redaction/redact_task_newtask_unittest.cc b/src/trace_redaction/redact_task_newtask_unittest.cc deleted file mode 100644 index 33886f621a..0000000000 --- a/src/trace_redaction/redact_task_newtask_unittest.cc +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/trace_redaction/redact_task_newtask.h" -#include "perfetto/protozero/scattered_heap_buffer.h" -#include "protos/perfetto/trace/ftrace/task.gen.h" -#include "test/gtest_and_gmock.h" - -#include "protos/perfetto/trace/ftrace/ftrace_event.gen.h" -#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h" -#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.gen.h" -#include "protos/perfetto/trace/ftrace/sched.gen.h" -#include "protos/perfetto/trace/trace.gen.h" -#include "protos/perfetto/trace/trace_packet.gen.h" - -namespace perfetto::trace_redaction { - -namespace { -constexpr uint64_t kUidA = 1; -constexpr uint64_t kUidB = 2; - -constexpr int32_t kNoParent = 10; -constexpr int32_t kPidA = 11; -constexpr int32_t kPidB = 12; - -constexpr std::string_view kCommA = "comm-a"; - -} // namespace - -// Tests which nested messages and fields are removed. -class RedactTaskNewTaskTest : public testing::Test { - protected: - void SetUp() override { - auto* event = bundle_.add_event(); - - event->set_timestamp(123456789); - event->set_pid(kPidA); - - auto* new_task = event->mutable_task_newtask(); - new_task->set_comm(std::string(kCommA)); - new_task->set_pid(kPidA); - } - - base::Status Redact(const Context& context, - protos::pbzero::FtraceEvent* event_message) { - RedactTaskNewTask redact; - - auto bundle_str = bundle_.SerializeAsString(); - protos::pbzero::FtraceEventBundle::Decoder bundle_decoder(bundle_str); - - auto event_str = bundle_.event().back().SerializeAsString(); - protos::pbzero::FtraceEvent::Decoder event_decoder(event_str); - - return redact.Redact(context, bundle_decoder, event_decoder, event_message); - } - - const std::string& event_string() const { return event_string_; } - - // This test breaks the rules for task_newtask and the timeline. The - // timeline will report the task existing before the new task event. This - // should not happen in the field, but it makes the test more robust. - std::unique_ptr CreatePopulatedTimeline() { - auto timeline = std::make_unique(); - - timeline->Append( - ProcessThreadTimeline::Event::Open(0, kPidA, kNoParent, kUidA)); - timeline->Append( - ProcessThreadTimeline::Event::Open(0, kPidB, kNoParent, kUidB)); - timeline->Sort(); - - return timeline; - } - - private: - std::string event_string_; - - std::unique_ptr timeline_; - - protos::gen::FtraceEventBundle bundle_; -}; - -TEST_F(RedactTaskNewTaskTest, RejectMissingPackageUid) { - RedactTaskNewTask redact; - - Context context; - context.timeline = std::make_unique(); - - protos::pbzero::FtraceEvent::Decoder event_decoder(event_string()); - protozero::HeapBuffered event_message; - - auto result = Redact(context, event_message.get()); - ASSERT_FALSE(result.ok()); -} - -TEST_F(RedactTaskNewTaskTest, RejectMissingTimeline) { - RedactTaskNewTask redact; - - Context context; - context.package_uid = kUidA; - - protos::pbzero::FtraceEvent::Decoder event_decoder(event_string()); - protozero::HeapBuffered event_message; - - auto result = Redact(context, event_message.get()); - ASSERT_FALSE(result.ok()); -} - -TEST_F(RedactTaskNewTaskTest, PidInPackageKeepsComm) { - RedactTaskNewTask redact; - - // Because Uid A is the target, when Pid A starts (new task event), it should - // keep its comm value. - Context context; - context.package_uid = kUidA; - context.timeline = CreatePopulatedTimeline(); - - protos::pbzero::FtraceEvent::Decoder event_decoder(event_string()); - protozero::HeapBuffered event_message; - - auto result = Redact(context, event_message.get()); - ASSERT_TRUE(result.ok()); - - protos::gen::FtraceEvent redacted_event; - redacted_event.ParseFromString(event_message.SerializeAsString()); - - ASSERT_TRUE(redacted_event.has_task_newtask()); - ASSERT_TRUE(redacted_event.task_newtask().has_comm()); - ASSERT_EQ(redacted_event.task_newtask().comm(), kCommA); -} - -TEST_F(RedactTaskNewTaskTest, PidOutsidePackageLosesComm) { - RedactTaskNewTask redact; - - // Because Uid B is the target, when Pid A starts (new task event), it should - // lose its comm value. - Context context; - context.package_uid = kUidB; - context.timeline = CreatePopulatedTimeline(); - - protos::pbzero::FtraceEvent::Decoder event_decoder(event_string()); - protozero::HeapBuffered event_message; - - auto result = Redact(context, event_message.get()); - ASSERT_TRUE(result.ok()); - - protos::gen::FtraceEvent redacted_event; - redacted_event.ParseFromString(event_message.SerializeAsString()); - - ASSERT_TRUE(redacted_event.has_task_newtask()); - ASSERT_TRUE(redacted_event.task_newtask().has_comm()); - ASSERT_TRUE(redacted_event.task_newtask().comm().empty()); -} - -} // namespace perfetto::trace_redaction diff --git a/src/trace_redaction/remap_scheduling_events.cc b/src/trace_redaction/remap_scheduling_events.cc deleted file mode 100644 index be0973e129..0000000000 --- a/src/trace_redaction/remap_scheduling_events.cc +++ /dev/null @@ -1,265 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/trace_redaction/remap_scheduling_events.h" - -#include "src/trace_redaction/proto_util.h" - -#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h" -#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h" -#include "protos/perfetto/trace/ftrace/sched.pbzero.h" - -namespace perfetto::trace_redaction { - -namespace { -int32_t RemapPid(const Context& context, - uint64_t timestamp, - uint32_t cpu, - int32_t pid) { - PERFETTO_DCHECK(context.package_uid.value()); - PERFETTO_DCHECK(cpu < context.synthetic_threads->tids.size()); - - // PID 0 is used for CPU idle. If it was to get re-mapped, threading - // information get corrupted. - if (pid == 0) { - return 0; - } - - if (context.timeline->PidConnectsToUid(timestamp, pid, - *context.package_uid)) { - return pid; - } - - return context.synthetic_threads->tids[cpu]; -} -} // namespace - -base::Status ThreadMergeRemapFtraceEventPid::Redact( - const Context& context, - const protos::pbzero::FtraceEventBundle::Decoder& bundle, - protozero::ProtoDecoder& event, - protos::pbzero::FtraceEvent* event_message) const { - if (!context.package_uid.has_value()) { - return base::ErrStatus( - "ThreadMergeRemapFtraceEventPid: missing package uid"); - } - - if (!context.synthetic_threads.has_value()) { - return base::ErrStatus( - "ThreadMergeRemapFtraceEventPid: missing synthetic threads"); - } - - // This should never happen. A bundle should have a cpu. - if (!bundle.has_cpu()) { - return base::ErrStatus( - "ThreadMergeRemapFtraceEventPid: Invalid ftrace event, missing cpu."); - } - - if (bundle.cpu() >= context.synthetic_threads->tids.size()) { - return base::ErrStatus( - "ThreadMergeRemapFtraceEventPid: synthetic thread count"); - } - - auto timestamp = - event.FindField(protos::pbzero::FtraceEvent::kTimestampFieldNumber); - - // This should never happen. An event should have a timestamp. - if (!timestamp.valid()) { - return base::ErrStatus( - "ThreadMergeRemapFtraceEventPid: Invalid ftrace event, missing " - "timestamp."); - } - - // This handler should only be called for the pid field. - auto pid = event.FindField(protos::pbzero::FtraceEvent::kPidFieldNumber); - PERFETTO_DCHECK(pid.valid()); - - // The event's pid is technically a uint, but we need it as a int. - auto new_pid = - RemapPid(context, timestamp.as_uint64(), bundle.cpu(), pid.as_int32()); - event_message->set_pid(static_cast(new_pid)); - - return base::OkStatus(); -} - -base::Status ThreadMergeRemapSchedSwitchPid::Redact( - const Context& context, - const protos::pbzero::FtraceEventBundle::Decoder& bundle, - protozero::ProtoDecoder& event, - protos::pbzero::FtraceEvent* event_message) const { - if (!context.package_uid.has_value()) { - return base::ErrStatus( - "ThreadMergeRemapSchedSwitchPid: missing package uid"); - } - - if (!context.synthetic_threads.has_value()) { - return base::ErrStatus( - "ThreadMergeRemapSchedSwitchPid: missing synthetic threads"); - } - - // This should never happen. A bundle should have a cpu. - if (!bundle.has_cpu()) { - return base::ErrStatus( - "ThreadMergeRemapSchedSwitchPid: Invalid ftrace event, missing cpu."); - } - - if (bundle.cpu() >= context.synthetic_threads->tids.size()) { - return base::ErrStatus( - "ThreadMergeRemapSchedSwitchPid: synthetic thread count"); - } - - auto timestamp = - event.FindField(protos::pbzero::FtraceEvent::kTimestampFieldNumber); - - // This should never happen. An event should have a timestamp. - if (!timestamp.valid()) { - return base::ErrStatus( - "ThreadMergeRemapSchedSwitchPid: Invalid ftrace event, missing " - "timestamp."); - } - - // This handler should only be called for the sched switch field. - auto sched_switch = - event.FindField(protos::pbzero::FtraceEvent::kSchedSwitchFieldNumber); - PERFETTO_DCHECK(sched_switch.valid()); - - protozero::ProtoDecoder sched_switch_decoder(sched_switch.as_bytes()); - - auto old_prev_pid_field = sched_switch_decoder.FindField( - protos::pbzero::SchedSwitchFtraceEvent::kPrevPidFieldNumber); - auto old_next_pid_field = sched_switch_decoder.FindField( - protos::pbzero::SchedSwitchFtraceEvent::kNextPidFieldNumber); - - if (!old_prev_pid_field.valid()) { - return base::ErrStatus( - "ThreadMergeRemapSchedSwitchPid: Invalid sched switch event, missing " - "prev pid"); - } - - if (!old_next_pid_field.valid()) { - return base::ErrStatus( - "ThreadMergeRemapSchedSwitchPid: Invalid sched switch event, missing " - "next pid"); - } - - auto new_prev_pid_field = - RemapPid(context, timestamp.as_uint64(), bundle.cpu(), - old_prev_pid_field.as_int32()); - auto new_next_pid_field = - RemapPid(context, timestamp.as_uint64(), bundle.cpu(), - old_next_pid_field.as_int32()); - - auto* sched_switch_message = event_message->set_sched_switch(); - - for (auto f = sched_switch_decoder.ReadField(); f.valid(); - f = sched_switch_decoder.ReadField()) { - switch (f.id()) { - case protos::pbzero::SchedSwitchFtraceEvent::kPrevPidFieldNumber: - sched_switch_message->set_prev_pid(new_prev_pid_field); - break; - - case protos::pbzero::SchedSwitchFtraceEvent::kNextPidFieldNumber: - sched_switch_message->set_next_pid(new_next_pid_field); - break; - - default: - proto_util::AppendField(f, sched_switch_message); - break; - } - } - - return base::OkStatus(); -} - -base::Status ThreadMergeRemapSchedWakingPid::Redact( - const Context& context, - const protos::pbzero::FtraceEventBundle::Decoder& bundle, - protozero::ProtoDecoder& event, - protos::pbzero::FtraceEvent* event_message) const { - if (!context.package_uid.has_value()) { - return base::ErrStatus( - "ThreadMergeRemapSchedWakingPid: missing package uid"); - } - - if (!context.synthetic_threads.has_value()) { - return base::ErrStatus( - "ThreadMergeRemapSchedWakingPid: missing synthetic threads"); - } - - // This should never happen. A bundle should have a cpu. - if (!bundle.has_cpu()) { - return base::ErrStatus( - "ThreadMergeRemapSchedWakingPid: Invalid ftrace event, missing cpu."); - } - - if (bundle.cpu() >= context.synthetic_threads->tids.size()) { - return base::ErrStatus( - "ThreadMergeRemapSchedWakingPid: synthetic thread count"); - } - - auto timestamp = - event.FindField(protos::pbzero::FtraceEvent::kTimestampFieldNumber); - - // This should never happen. An event should have a timestamp. - if (!timestamp.valid()) { - return base::ErrStatus( - "ThreadMergeRemapSchedWakingPid: Invalid ftrace event, missing " - "timestamp."); - } - - // This handler should only be called for the sched waking field. - auto sched_waking = - event.FindField(protos::pbzero::FtraceEvent::kSchedWakingFieldNumber); - PERFETTO_DCHECK(sched_waking.valid()); - - protozero::ProtoDecoder sched_waking_decoder(sched_waking.as_bytes()); - - auto old_pid = sched_waking_decoder.FindField( - protos::pbzero::SchedWakingFtraceEvent::kPidFieldNumber); - - if (!old_pid.valid()) { - return base::ErrStatus( - "ThreadMergeRemapSchedWakingPid: Invalid sched waking event, missing " - "pid"); - } - - auto new_pid_field = RemapPid(context, timestamp.as_uint64(), bundle.cpu(), - old_pid.as_int32()); - - auto* sched_waking_message = event_message->set_sched_waking(); - - for (auto f = sched_waking_decoder.ReadField(); f.valid(); - f = sched_waking_decoder.ReadField()) { - if (f.id() == protos::pbzero::SchedWakingFtraceEvent::kPidFieldNumber) { - sched_waking_message->set_pid(new_pid_field); - } else { - proto_util::AppendField(f, sched_waking_message); - } - } - - return base::OkStatus(); -} - -// By doing nothing, the field gets dropped. -base::Status ThreadMergeDropField::Redact( - const Context&, - const protos::pbzero::FtraceEventBundle::Decoder&, - protozero::ProtoDecoder&, - protos::pbzero::FtraceEvent*) const { - return base::OkStatus(); -} - -} // namespace perfetto::trace_redaction diff --git a/src/trace_redaction/remap_scheduling_events.h b/src/trace_redaction/remap_scheduling_events.h deleted file mode 100644 index 0160534891..0000000000 --- a/src/trace_redaction/remap_scheduling_events.h +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef SRC_TRACE_REDACTION_REMAP_SCHEDULING_EVENTS_H_ -#define SRC_TRACE_REDACTION_REMAP_SCHEDULING_EVENTS_H_ - -#include "perfetto/protozero/proto_decoder.h" -#include "src/trace_redaction/redact_ftrace_event.h" -#include "src/trace_redaction/trace_redaction_framework.h" - -#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h" - -namespace perfetto::trace_redaction { - -// Reads the Ftrace event's pid and replaces it with a synthetic thread id (if -// necessary). -class ThreadMergeRemapFtraceEventPid : public FtraceEventRedaction { - public: - static constexpr auto kFieldId = protos::pbzero::FtraceEvent::kPidFieldNumber; - - base::Status Redact( - const Context& context, - const protos::pbzero::FtraceEventBundle::Decoder& bundle, - protozero::ProtoDecoder& event, - protos::pbzero::FtraceEvent* event_message) const override; -}; - -// Reads the sched switch pid and replaces it with a synthetic thread id (if -// necessary). -// -// event { -// timestamp: 6702093743539938 -// pid: 0 -// sched_switch { -// prev_comm: "swapper/7" -// prev_pid: 0 -// prev_prio: 120 -// prev_state: 0 -// next_comm: "FMOD stream thr" -// next_pid: 7174 -// next_prio: 104 -// } -// } -class ThreadMergeRemapSchedSwitchPid : public FtraceEventRedaction { - public: - static constexpr auto kFieldId = - protos::pbzero::FtraceEvent::kSchedSwitchFieldNumber; - - base::Status Redact( - const Context& context, - const protos::pbzero::FtraceEventBundle::Decoder& bundle, - protozero::ProtoDecoder& event, - protos::pbzero::FtraceEvent* event_message) const override; -}; - -// Reads the sched waking pid and replaces it with a synthetic thread id (if -// necessary). -// -// event { -// timestamp: 6702093743527386 -// pid: 0 -// sched_waking { -// comm: "FMOD stream thr" -// pid: 7174 -// prio: 104 -// success: 1 -// target_cpu: 7 -// } -// } -class ThreadMergeRemapSchedWakingPid : public FtraceEventRedaction { - public: - static constexpr auto kFieldId = - protos::pbzero::FtraceEvent::kSchedWakingFieldNumber; - - base::Status Redact( - const Context& context, - const protos::pbzero::FtraceEventBundle::Decoder& bundle, - protozero::ProtoDecoder& event, - protos::pbzero::FtraceEvent* event_message) const override; -}; - -// Drop "new task" events because it's safe to assume that the threads always -// exist. -// -// event { -// timestamp: 6702094133317685 -// pid: 6167 -// task_newtask { -// pid: 7972 <-- Pid being started -// comm: "adbd" -// clone_flags: 4001536 -// oom_score_adj: -1000 -// } -// } -// -// Drop "process free" events because it's safe to assume that the threads -// always exist. -// -// event { -// timestamp: 6702094703942898 -// pid: 10 -// sched_process_free { -// comm: "shell svc 7973" -// pid: 7974 <-- Pid being freed -// prio: 120 -// } -// } -class ThreadMergeDropField : public FtraceEventRedaction { - public: - static constexpr auto kTaskNewtaskFieldNumber = - protos::pbzero::FtraceEvent::kTaskNewtaskFieldNumber; - static constexpr auto kSchedProcessFreeFieldNumber = - protos::pbzero::FtraceEvent::kSchedProcessFreeFieldNumber; - - base::Status Redact( - const Context& context, - const protos::pbzero::FtraceEventBundle::Decoder& bundle, - protozero::ProtoDecoder& event, - protos::pbzero::FtraceEvent* event_message) const override; -}; - -} // namespace perfetto::trace_redaction - -#endif // SRC_TRACE_REDACTION_REMAP_SCHEDULING_EVENTS_H_ diff --git a/src/trace_redaction/remap_scheduling_events_integrationtest.cc b/src/trace_redaction/remap_scheduling_events_integrationtest.cc deleted file mode 100644 index 2f68c95bf9..0000000000 --- a/src/trace_redaction/remap_scheduling_events_integrationtest.cc +++ /dev/null @@ -1,292 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/base/test/status_matchers.h" -#include "src/trace_redaction/collect_system_info.h" -#include "src/trace_redaction/collect_timeline_events.h" -#include "src/trace_redaction/find_package_uid.h" -#include "src/trace_redaction/redact_ftrace_event.h" -#include "src/trace_redaction/remap_scheduling_events.h" -#include "src/trace_redaction/trace_redaction_integration_fixture.h" -#include "test/gtest_and_gmock.h" - -#include "protos/perfetto/trace/ftrace/sched.pbzero.h" -#include "protos/perfetto/trace/ftrace/task.pbzero.h" - -namespace perfetto::trace_redaction { - -// Runs ThreadMergeRemapFtraceEventPid, ThreadMergeRemapSchedSwitchPid, -// ThreadMergeRemapSchedWakingPid, and ThreadMergeDropField to replace pids with -// synthetic pids (for all threads outside of the target package); -class RemapSchedulingEventsIntegrationTest - : public testing::Test, - protected TraceRedactionIntegrationFixure { - public: - static constexpr auto kPackageName = - "com.Unity.com.unity.multiplayer.samples.coop"; - static constexpr uint64_t kPackageId = 10252; - static constexpr int32_t kPid = 7105; - - // Threads belonging to pid 7105. Collected using trace processors. - static constexpr auto kTids = { - 0, // pid 0 will always be included because CPU idle uses it. - 7105, 7111, 7112, 7113, 7114, 7115, 7116, 7117, 7118, 7119, 7120, - 7124, 7125, 7127, 7129, 7130, 7131, 7132, 7133, 7134, 7135, 7136, - 7137, 7139, 7141, 7142, 7143, 7144, 7145, 7146, 7147, 7148, 7149, - 7150, 7151, 7152, 7153, 7154, 7155, 7156, 7157, 7158, 7159, 7160, - 7161, 7162, 7163, 7164, 7165, 7166, 7167, 7171, 7172, 7174, 7178, - 7180, 7184, 7200, 7945, 7946, 7947, 7948, 7950, 7969, - }; - - protected: - void SetUp() override { - trace_redactor()->emplace_collect(); - - // In order to remap threads, we need to have synth threads. - trace_redactor()->emplace_collect(); - trace_redactor()->emplace_build(); - - // Timeline information is needed to know if a pid belongs to a package. - trace_redactor()->emplace_collect(); - - auto* redactions = trace_redactor()->emplace_transform(); - redactions->emplace_back(); - redactions->emplace_back(); - redactions->emplace_back(); - redactions->emplace_back(); - redactions->emplace_back(); - - context()->package_name = kPackageName; - } - - struct Index { - // List of FtraceEvent - std::vector events; - - // List of SchedSwitchFtraceEvent - std::vector events_sched_switch; - - // List of SchedWakingFtraceEvent - std::vector events_sched_waking; - - // List of SchedProcessFreeFtraceEvent - std::vector events_sched_process_free; - - // List of TaskNewtaskFtraceEvent - std::vector events_task_newtask; - }; - - void UpdateFtraceIndex(protozero::ConstBytes bytes, Index* index) { - protos::pbzero::FtraceEventBundle::Decoder bundle(bytes); - - for (auto event = bundle.event(); event; ++event) { - index->events.push_back(event->as_bytes()); - - // protos::pbzero::FtraceEvent - protozero::ProtoDecoder ftrace_event(event->as_bytes()); - - auto sched_switch = ftrace_event.FindField( - protos::pbzero::FtraceEvent::kSchedSwitchFieldNumber); - if (sched_switch.valid()) { - index->events_sched_switch.push_back(sched_switch.as_bytes()); - } - - auto sched_waking = ftrace_event.FindField( - protos::pbzero::FtraceEvent::kSchedWakingFieldNumber); - if (sched_waking.valid()) { - index->events_sched_waking.push_back(sched_waking.as_bytes()); - } - - auto sched_process_free = ftrace_event.FindField( - protos::pbzero::FtraceEvent::kSchedProcessFreeFieldNumber); - if (sched_process_free.valid()) { - index->events_sched_process_free.push_back( - sched_process_free.as_bytes()); - } - - auto task_newtask = ftrace_event.FindField( - protos::pbzero::FtraceEvent::kTaskNewtaskFieldNumber); - if (task_newtask.valid()) { - index->events_task_newtask.push_back(task_newtask.as_bytes()); - } - } - } - - // Bytes should be TracePacket - Index CreateFtraceIndex(const std::string& bytes) { - Index index; - - protozero::ProtoDecoder packet_decoder(bytes); - - for (auto packet = packet_decoder.ReadField(); packet.valid(); - packet = packet_decoder.ReadField()) { - auto events = packet_decoder.FindField( - protos::pbzero::TracePacket::kFtraceEventsFieldNumber); - - if (events.valid()) { - UpdateFtraceIndex(events.as_bytes(), &index); - } - } - - return index; - } - - base::StatusOr LoadAndRedactTrace() { - auto source = LoadOriginal(); - - if (!source.ok()) { - return source.status(); - } - - auto redact = Redact(); - - if (!redact.ok()) { - return redact; - } - - // Double-check the package id with the one from trace processor. If this - // was wrong and this check was missing, finding the problem would be much - // harder. - if (!context()->package_uid.has_value()) { - return base::ErrStatus("Missing package uid."); - } - - if (context()->package_uid.value() != kPackageId) { - return base::ErrStatus("Unexpected package uid found."); - } - - auto redacted = LoadRedacted(); - - if (redacted.ok()) { - return redacted; - } - - // System info is used to initialize the synth threads. If these are wrong, - // then the synth threads will be wrong. - if (!context()->system_info.has_value()) { - return base::ErrStatus("Missing system info."); - } - - if (context()->system_info->last_cpu() != 7u) { - return base::ErrStatus("Unexpected cpu count."); - } - - // The synth threads should have been initialized. They will be used here to - // verify which threads exist in the redacted trace. - if (!context()->synthetic_threads.has_value()) { - return base::ErrStatus("Missing synthetic threads."); - } - - if (context()->synthetic_threads->tids.size() != 8u) { - return base::ErrStatus("Unexpected synthentic thread count."); - } - - return redacted; - } - - // Should be called after redaction since it requires data from the context. - std::unordered_set CopyAllowedTids(const Context& context) const { - std::unordered_set tids(kTids.begin(), kTids.end()); - - tids.insert(context.synthetic_threads->tgid); - tids.insert(context.synthetic_threads->tids.begin(), - context.synthetic_threads->tids.end()); - - return tids; - } - - private: - std::unordered_set allowed_tids_; -}; - -TEST_F(RemapSchedulingEventsIntegrationTest, FilterFtraceEventPid) { - auto redacted = LoadAndRedactTrace(); - ASSERT_OK(redacted); - - auto allowlist = CopyAllowedTids(*context()); - - auto index = CreateFtraceIndex(*redacted); - - for (const auto& event : index.events) { - protos::pbzero::FtraceEvent::Decoder decoder(event); - auto pid = static_cast(decoder.pid()); - ASSERT_TRUE(allowlist.count(pid)); - } -} - -TEST_F(RemapSchedulingEventsIntegrationTest, FiltersSchedSwitch) { - auto redacted = LoadAndRedactTrace(); - ASSERT_OK(redacted); - - auto allowlist = CopyAllowedTids(*context()); - - auto index = CreateFtraceIndex(*redacted); - - for (const auto& event : index.events_sched_switch) { - protos::pbzero::SchedSwitchFtraceEvent::Decoder decoder(event); - ASSERT_TRUE(allowlist.count(decoder.prev_pid())); - ASSERT_TRUE(allowlist.count(decoder.next_pid())); - } -} - -TEST_F(RemapSchedulingEventsIntegrationTest, FiltersSchedWaking) { - auto redacted = LoadAndRedactTrace(); - ASSERT_OK(redacted); - - auto allowlist = CopyAllowedTids(*context()); - - auto index = CreateFtraceIndex(*redacted); - - for (const auto& event : index.events_sched_waking) { - protos::pbzero::SchedWakingFtraceEvent::Decoder decoder(event); - ASSERT_TRUE(allowlist.count(decoder.pid())); - } -} - -TEST_F(RemapSchedulingEventsIntegrationTest, FiltersProcessFree) { - auto redacted = LoadAndRedactTrace(); - ASSERT_OK(redacted); - - auto allowlist = CopyAllowedTids(*context()); - - auto index = CreateFtraceIndex(*redacted); - - for (const auto& event : index.events_sched_process_free) { - protos::pbzero::SchedProcessFreeFtraceEvent::Decoder decoder(event); - ASSERT_TRUE(allowlist.count(decoder.pid())); - } -} - -TEST_F(RemapSchedulingEventsIntegrationTest, FiltersNewTask) { - auto redacted = LoadAndRedactTrace(); - ASSERT_OK(redacted); - - auto allowlist = CopyAllowedTids(*context()); - - auto index = CreateFtraceIndex(*redacted); - - for (const auto& event : index.events_task_newtask) { - protos::pbzero::TaskNewtaskFtraceEvent::Decoder decoder(event); - ASSERT_TRUE(allowlist.count(decoder.pid())); - } -} - -} // namespace perfetto::trace_redaction diff --git a/src/trace_redaction/remap_scheduling_events_unittest.cc b/src/trace_redaction/remap_scheduling_events_unittest.cc deleted file mode 100644 index 5cdc753736..0000000000 --- a/src/trace_redaction/remap_scheduling_events_unittest.cc +++ /dev/null @@ -1,441 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/trace_redaction/remap_scheduling_events.h" - -#include "perfetto/protozero/scattered_heap_buffer.h" -#include "src/base/test/status_matchers.h" -#include "src/trace_redaction/trace_redaction_framework.h" -#include "test/gtest_and_gmock.h" - -#include "protos/perfetto/trace/ftrace/ftrace_event.gen.h" -#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.gen.h" -#include "protos/perfetto/trace/ftrace/sched.gen.h" - -namespace perfetto::trace_redaction { - -template -class ThreadMergeTest { - protected: - struct Process { - uint64_t uid; - int32_t ppid; - int32_t pid; - }; - - base::Status Redact(protos::pbzero::FtraceEvent* event_message) { - T redact; - - auto bundle_str = bundle_.SerializeAsString(); - protos::pbzero::FtraceEventBundle::Decoder bundle_decoder(bundle_str); - - auto event_str = bundle_.event().back().SerializeAsString(); - protos::pbzero::FtraceEvent::Decoder event_decoder(event_str); - - return redact.Redact(context_, bundle_decoder, event_decoder, - event_message); - } - - Context context_; - protos::gen::FtraceEventBundle bundle_; -}; - -// All ftrace events have a timestamp and a pid. This test focuses on the -// event's pid value. When that pid doesn't belong to the target package, it -// should be replaced with a synthetic thread id. -// -// event { -// timestamp: 6702093743539938 -// pid: 0 -// sched_switch { ... } -// } -class ThreadMergeRemapFtraceEventPidTest - : public testing::Test, - protected ThreadMergeTest { - protected: - static constexpr uint32_t kCpu = 3; - - static constexpr auto kTimestamp = 123456789; - - // This process will be connected to the target package. - static constexpr Process kProcess = {12, 5, 7}; - - // This process will not be connected to the target package. - static constexpr Process kOtherProcess = {120, 50, 70}; - - void SetUp() override { - bundle_.add_event(); - - context_.package_uid = kProcess.uid; - - context_.timeline = std::make_unique(); - context_.timeline->Append(ProcessThreadTimeline::Event::Open( - 0, kProcess.pid, kProcess.ppid, kProcess.uid)); - context_.timeline->Append(ProcessThreadTimeline::Event::Open( - 0, kOtherProcess.pid, kOtherProcess.ppid, kOtherProcess.uid)); - context_.timeline->Sort(); - - // Because kCpu is 3, it means that there are four CPUs (id 0, id 1, ...). - context_.synthetic_threads.emplace(); - context_.synthetic_threads->tids.assign({100, 101, 102, 103}); - } -}; - -// This should never happen, a bundle should always have a cpu. If it doesn't -// have a CPU, the event field should be dropped (safest option). -// -// TODO(vaage): This will create an invalid trace. It can also leak information -// if other primitives don't strip the remaining information. To be safe, these -// cases should be replaced with errors. -TEST_F(ThreadMergeRemapFtraceEventPidTest, MissingCpuReturnsError) { - // Do not call set_cpu(uint32_t value). There should be no cpu for this case. - bundle_.mutable_event()->back().set_timestamp(kTimestamp); - bundle_.mutable_event()->back().set_pid(kProcess.pid); - - protozero::HeapBuffered event_message; - ASSERT_FALSE(Redact(event_message.get()).ok()); -} - -// This should never happen, an event should always have a timestamp. If it -// doesn't have a timestamp, the event field should be dropped (safest option). -// -// TODO(vaage): This will create an invalid trace. It can also leak information -// if other primitives don't strip the remaining information. To be safe, these -// cases should be replaced with errors. -TEST_F(ThreadMergeRemapFtraceEventPidTest, MissingTimestampReturnsError) { - bundle_.set_cpu(kCpu); - // Do not call set_timestamp(uint64_t value). There should be no timestamp for - // this case. - bundle_.mutable_event()->back().set_pid(kProcess.pid); - - protozero::HeapBuffered event_message; - ASSERT_FALSE(Redact(event_message.get()).ok()); -} - -TEST_F(ThreadMergeRemapFtraceEventPidTest, NoopWhenPidIsInPackage) { - bundle_.set_cpu(kCpu); - bundle_.mutable_event()->back().set_timestamp(kTimestamp); - bundle_.mutable_event()->back().set_pid(kProcess.pid); - - protozero::HeapBuffered event_message; - ASSERT_OK(Redact(event_message.get())); - - protos::gen::FtraceEvent event; - event.ParseFromString(event_message.SerializeAsString()); - - ASSERT_TRUE(event.has_pid()); - ASSERT_EQ(static_cast(event.pid()), kProcess.pid); -} - -TEST_F(ThreadMergeRemapFtraceEventPidTest, ChangesPidWhenPidIsOutsidePackage) { - bundle_.set_cpu(kCpu); // The CPU is used to select the pid. - bundle_.mutable_event()->back().set_timestamp(kTimestamp); - bundle_.mutable_event()->back().set_pid(kOtherProcess.pid); - - protozero::HeapBuffered event_message; - ASSERT_OK(Redact(event_message.get())); - - protos::gen::FtraceEvent event; - event.ParseFromString(event_message.SerializeAsString()); - - ASSERT_TRUE(event.has_pid()); - ASSERT_EQ(static_cast(event.pid()), - context_.synthetic_threads->tids[kCpu]); -} - -// When creating a sched_switch event, the event pid and the previous pid should -// be the same pid. -// -// event { -// timestamp: 6702093743539938 -// pid: 0 -// sched_switch { -// prev_comm: "swapper/7" -// prev_pid: 0 -// prev_prio: 120 -// prev_state: 0 -// next_comm: "FMOD stream thr" -// next_pid: 7174 -// next_prio: 104 -// } -// } -class ThreadMergeRemapSchedSwitchPidTest - : public testing::Test, - protected ThreadMergeTest { - protected: - static constexpr uint32_t kCpu = 3; - - static constexpr auto kTimestamp = 123456789; - - // This process will be connected to the target package. - static constexpr Process kPrevProcess = {12, 5, 7}; - static constexpr Process kNextProcess = {12, 5, 8}; - - // This process will not be connected to the target package. - static constexpr Process kOtherProcess = {120, 50, 70}; - - void SetUp() override { - bundle_.add_event(); - - context_.package_uid = kPrevProcess.uid; - - context_.timeline = std::make_unique(); - context_.timeline->Append(ProcessThreadTimeline::Event::Open( - 0, kPrevProcess.pid, kPrevProcess.ppid, kPrevProcess.uid)); - context_.timeline->Append(ProcessThreadTimeline::Event::Open( - 0, kNextProcess.pid, kNextProcess.ppid, kNextProcess.uid)); - context_.timeline->Append(ProcessThreadTimeline::Event::Open( - 0, kOtherProcess.pid, kOtherProcess.ppid, kOtherProcess.uid)); - - context_.timeline->Sort(); - - // Because kCpu is 3, it means that there are four CPUs (id 0, id 1, ...). - context_.synthetic_threads.emplace(); - context_.synthetic_threads->tids.assign({100, 101, 102, 103}); - } -}; - -// This should never happen, a bundle should always have a cpu. If it doesn't -// have a CPU, the event field should be dropped (safest option). -// -// TODO(vaage): This will create an invalid trace. It can also leak information -// if other primitives don't strip the remaining information. To be safe, these -// cases should be replaced with errors. -TEST_F(ThreadMergeRemapSchedSwitchPidTest, MissingCpuReturnsError) { - // Do not call set_cpu(uint32_t value). There should be no cpu for this case. - bundle_.mutable_event()->back().set_timestamp(kTimestamp); - bundle_.mutable_event()->back().set_pid(kPrevProcess.pid); - - protozero::HeapBuffered event_message; - ASSERT_FALSE(Redact(event_message.get()).ok()); -} - -// This should never happen, an event should always have a timestamp. If it -// doesn't have a timestamp, the event field should be dropped (safest option). -// -// TODO(vaage): This will create an invalid trace. It can also leak information -// if other primitives don't strip the remaining information. To be safe, these -// cases should be replaced with errors. -TEST_F(ThreadMergeRemapSchedSwitchPidTest, MissingTimestampReturnsError) { - bundle_.set_cpu(kCpu); - // Do not call set_timestamp(uint64_t value). There should be no timestamp for - // this case. - bundle_.mutable_event()->back().set_pid(kPrevProcess.pid); - - protozero::HeapBuffered event_message; - ASSERT_FALSE(Redact(event_message.get()).ok()); -} - -TEST_F(ThreadMergeRemapSchedSwitchPidTest, NoopWhenPidIsInPackage) { - bundle_.set_cpu(kCpu); - bundle_.mutable_event()->back().set_timestamp(kTimestamp); - bundle_.mutable_event()->back().set_pid(kPrevProcess.pid); - - auto* sched_switch = bundle_.mutable_event()->back().mutable_sched_switch(); - sched_switch->set_prev_pid(kPrevProcess.pid); - sched_switch->set_next_pid(kNextProcess.pid); - - protozero::HeapBuffered event_message; - ASSERT_OK(Redact(event_message.get())); - - protos::gen::FtraceEvent event; - event.ParseFromString(event_message.SerializeAsString()); - - ASSERT_TRUE(event.has_sched_switch()); - - ASSERT_TRUE(event.sched_switch().has_prev_pid()); - ASSERT_EQ(static_cast(event.sched_switch().prev_pid()), - kPrevProcess.pid); - - ASSERT_TRUE(event.sched_switch().has_next_pid()); - ASSERT_EQ(static_cast(event.sched_switch().next_pid()), - kNextProcess.pid); -} - -TEST_F(ThreadMergeRemapSchedSwitchPidTest, - ChangesPrevPidWhenPidIsOutsidePackage) { - bundle_.set_cpu(kCpu); - bundle_.mutable_event()->back().set_timestamp(kTimestamp); - bundle_.mutable_event()->back().set_pid(kPrevProcess.pid); - - auto* sched_switch = bundle_.mutable_event()->back().mutable_sched_switch(); - sched_switch->set_prev_pid(kOtherProcess.pid); - sched_switch->set_next_pid(kNextProcess.pid); - - protozero::HeapBuffered event_message; - ASSERT_OK(Redact(event_message.get())); - - protos::gen::FtraceEvent event; - event.ParseFromString(event_message.SerializeAsString()); - - ASSERT_TRUE(event.has_sched_switch()); - - ASSERT_TRUE(event.sched_switch().has_prev_pid()); - ASSERT_EQ(static_cast(event.sched_switch().prev_pid()), - context_.synthetic_threads->tids[kCpu]); - - ASSERT_TRUE(event.sched_switch().has_next_pid()); - ASSERT_EQ(static_cast(event.sched_switch().next_pid()), - kNextProcess.pid); -} - -TEST_F(ThreadMergeRemapSchedSwitchPidTest, - ChangesNextPidWhenPidIsOutsidePackage) { - bundle_.set_cpu(kCpu); - bundle_.mutable_event()->back().set_timestamp(kTimestamp); - bundle_.mutable_event()->back().set_pid(kPrevProcess.pid); - - auto* sched_switch = bundle_.mutable_event()->back().mutable_sched_switch(); - sched_switch->set_prev_pid(kPrevProcess.pid); - sched_switch->set_next_pid(kOtherProcess.pid); - - protozero::HeapBuffered event_message; - ASSERT_OK(Redact(event_message.get())); - - protos::gen::FtraceEvent event; - event.ParseFromString(event_message.SerializeAsString()); - - ASSERT_TRUE(event.has_sched_switch()); - - ASSERT_TRUE(event.sched_switch().has_prev_pid()); - ASSERT_EQ(static_cast(event.sched_switch().prev_pid()), - kPrevProcess.pid); - - ASSERT_TRUE(event.sched_switch().has_next_pid()); - ASSERT_EQ(static_cast(event.sched_switch().next_pid()), - context_.synthetic_threads->tids[kCpu]); -} - -// event { -// timestamp: 6702093743527386 -// pid: 0 -// sched_waking { -// comm: "FMOD stream thr" -// pid: 7174 -// prio: 104 -// success: 1 -// target_cpu: 7 -// } -// } -class ThreadMergeRemapSchedWakingPidTest - : public testing::Test, - protected ThreadMergeTest { - protected: - static constexpr uint32_t kCpu = 3; - - static constexpr auto kTimestamp = 123456789; - - // This process will be connected to the target package. - static constexpr Process kWakerProcess = {12, 5, 7}; - static constexpr Process kWakeTarget = {12, 5, 8}; - - // This process will not be connected to the target package. - static constexpr Process kOtherProcess = {120, 50, 70}; - - void SetUp() override { - bundle_.add_event(); - - context_.package_uid = kWakerProcess.uid; - - context_.timeline = std::make_unique(); - context_.timeline->Append(ProcessThreadTimeline::Event::Open( - 0, kWakerProcess.pid, kWakerProcess.ppid, kWakerProcess.uid)); - context_.timeline->Append(ProcessThreadTimeline::Event::Open( - 0, kWakeTarget.pid, kWakeTarget.ppid, kWakeTarget.uid)); - context_.timeline->Append(ProcessThreadTimeline::Event::Open( - 0, kOtherProcess.pid, kOtherProcess.ppid, kOtherProcess.uid)); - - context_.timeline->Sort(); - - // Because kCpu is 3, it means that there are four CPUs (id 0, id 1, ...). - context_.synthetic_threads.emplace(); - context_.synthetic_threads->tids.assign({100, 101, 102, 103}); - } -}; - -// This should never happen, a bundle should always have a cpu. If it doesn't -// have a CPU, the event field should be dropped (safest option). -// -// TODO(vaage): This will create an invalid trace. It can also leak information -// if other primitives don't strip the remaining information. To be safe, these -// cases should be replaced with errors. -TEST_F(ThreadMergeRemapSchedWakingPidTest, MissingCpuReturnsError) { - // Do not call set_cpu(uint32_t value). There should be no cpu for this case. - bundle_.mutable_event()->back().set_timestamp(kTimestamp); - bundle_.mutable_event()->back().set_pid(kWakerProcess.pid); - - protozero::HeapBuffered event_message; - ASSERT_FALSE(Redact(event_message.get()).ok()); -} - -// This should never happen, an event should always have a timestamp. If it -// doesn't have a timestamp, the event field should be dropped (safest option). -// -// TODO(vaage): This will create an invalid trace. It can also leak information -// if other primitives don't strip the remaining information. To be safe, these -// cases should be replaced with errors. -TEST_F(ThreadMergeRemapSchedWakingPidTest, MissingTimestampReturnsError) { - bundle_.set_cpu(kCpu); - // Do not call set_timestamp(uint64_t value). There should be no timestamp for - // this case. - bundle_.mutable_event()->back().set_pid(kWakerProcess.pid); - - protozero::HeapBuffered event_message; - ASSERT_FALSE(Redact(event_message.get()).ok()); -} - -TEST_F(ThreadMergeRemapSchedWakingPidTest, NoopWhenPidIsInPackage) { - bundle_.set_cpu(kCpu); - bundle_.mutable_event()->back().set_timestamp(kTimestamp); - bundle_.mutable_event()->back().set_pid(kWakerProcess.pid); - - auto* sched_waking = bundle_.mutable_event()->back().mutable_sched_waking(); - sched_waking->set_pid(kWakeTarget.pid); - - protozero::HeapBuffered event_message; - ASSERT_OK(Redact(event_message.get())); - - protos::gen::FtraceEvent event; - event.ParseFromString(event_message.SerializeAsString()); - - ASSERT_TRUE(event.has_sched_waking()); - - ASSERT_TRUE(event.sched_waking().has_pid()); - ASSERT_EQ(static_cast(event.sched_waking().pid()), kWakeTarget.pid); -} - -TEST_F(ThreadMergeRemapSchedWakingPidTest, ChangesPidWhenPidIsOutsidePackage) { - bundle_.set_cpu(kCpu); - bundle_.mutable_event()->back().set_timestamp(kTimestamp); - bundle_.mutable_event()->back().set_pid(kWakerProcess.pid); - - auto* sched_switch = bundle_.mutable_event()->back().mutable_sched_waking(); - sched_switch->set_pid(kOtherProcess.pid); - - protozero::HeapBuffered event_message; - ASSERT_OK(Redact(event_message.get())); - - protos::gen::FtraceEvent event; - event.ParseFromString(event_message.SerializeAsString()); - - ASSERT_TRUE(event.has_sched_waking()); - - ASSERT_TRUE(event.sched_waking().has_pid()); - ASSERT_EQ(static_cast(event.sched_waking().pid()), - context_.synthetic_threads->tids[kCpu]); -} - -} // namespace perfetto::trace_redaction diff --git a/src/trace_redaction/scrub_ftrace_events.cc b/src/trace_redaction/scrub_ftrace_events.cc deleted file mode 100644 index 89d0b8a990..0000000000 --- a/src/trace_redaction/scrub_ftrace_events.cc +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/trace_redaction/scrub_ftrace_events.h" - -#include - -#include "perfetto/protozero/scattered_heap_buffer.h" -#include "src/trace_redaction/proto_util.h" - -#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h" -#include "protos/perfetto/trace/trace.pbzero.h" - -namespace perfetto::trace_redaction { - -FtraceEventFilter::~FtraceEventFilter() = default; - -// packet { -// ftrace_events { -// event { <-- This is where we test the allow-list -// timestamp: 6702095044299807 -// pid: 0 -// cpu_idle { <-- This is the event type -// state: 4294967295 -// cpu_id: 2 -// } -// } -// } -// } -base::Status ScrubFtraceEvents::Transform(const Context& context, - std::string* packet) const { - if (packet == nullptr || packet->empty()) { - return base::ErrStatus("ScrubFtraceEvents: null or empty packet."); - } - - for (const auto& filter : filters_) { - auto status = filter->VerifyContext(context); - - if (!status.ok()) { - return status; - } - } - - protozero::ProtoDecoder packet_decoder(*packet); - - if (!packet_decoder - .FindField(protos::pbzero::TracePacket::kFtraceEventsFieldNumber) - .valid()) { - return base::OkStatus(); - } - - protozero::HeapBuffered packet_message; - - for (auto field = packet_decoder.ReadField(); field.valid(); - field = packet_decoder.ReadField()) { - if (field.id() != protos::pbzero::TracePacket::kFtraceEventsFieldNumber) { - proto_util::AppendField(field, packet_message.get()); - continue; - } - - auto* bundle_message = packet_message->set_ftrace_events(); - - protozero::ProtoDecoder bundle(field.as_bytes()); - - for (auto event_it = bundle.ReadField(); event_it.valid(); - event_it = bundle.ReadField()) { - if (event_it.id() != - protos::pbzero::FtraceEventBundle::kEventFieldNumber || - KeepEvent(context, event_it.as_bytes())) { - proto_util::AppendField(event_it, bundle_message); - } - } - } - - packet->assign(packet_message.SerializeAsString()); - - return base::OkStatus(); -} - -// Logical AND of all filters. -bool ScrubFtraceEvents::KeepEvent(const Context& context, - protozero::ConstBytes bytes) const { - for (const auto& filter : filters_) { - auto keep = filter->KeepEvent(context, bytes); - - if (!keep) { - return false; - } - } - - return true; -} - -} // namespace perfetto::trace_redaction diff --git a/src/trace_redaction/scrub_ftrace_events.h b/src/trace_redaction/scrub_ftrace_events.h deleted file mode 100644 index 35c12bf5c9..0000000000 --- a/src/trace_redaction/scrub_ftrace_events.h +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef SRC_TRACE_REDACTION_SCRUB_FTRACE_EVENTS_H_ -#define SRC_TRACE_REDACTION_SCRUB_FTRACE_EVENTS_H_ - -#include - -#include "perfetto/protozero/field.h" -#include "src/trace_redaction/trace_redaction_framework.h" - -namespace perfetto::trace_redaction { - -class FtraceEventFilter { - public: - virtual ~FtraceEventFilter(); - - // Checks if the context contains all neccessary parameters. - virtual base::Status VerifyContext(const Context& context) const = 0; - - virtual bool KeepEvent(const Context& context, - protozero::ConstBytes bytes) const = 0; -}; - -// Assumptions: -// 1. This is a hot path (a lot of ftrace packets) -// 2. Allocations are slower than CPU cycles. -// -// Overview: -// To limit allocations pbzero protos are used to build a new packet. These -// protos are append-only, so data is not removed from the packet. Instead, -// data is optionally added to a new packet. -// -// To limit allocations, the goal is to add data as large chucks rather than -// small fragments. To do this, a reactive strategy is used. All operations -// follow a probe-than-act pattern. Before any action can be taken, the -// input data must be queries to determine the scope. For example: -// -// [------A------][---B---][------C------] -// [---][-D-][---] -// -// Assume that A and B don't need any work, they can be appended to the -// output as two large blocks. -// -// Block C is different, there is a block D that falls within block C. -// Block D contains sensitive information and should be dropped. When C -// is probed, it will come back saying that C needs additional redaction. -class ScrubFtraceEvents : public TransformPrimitive { - public: - base::Status Transform(const Context& context, - std::string* packet) const override; - - // Add a new filter. T must extend FtraceEventFilter. - template - void emplace_back() { - filters_.push_back(std::make_unique()); - } - - private: - bool KeepEvent(const Context& context, protozero::ConstBytes bytes) const; - - std::vector> filters_; -}; - -} // namespace perfetto::trace_redaction - -#endif // SRC_TRACE_REDACTION_SCRUB_FTRACE_EVENTS_H_ diff --git a/src/trace_redaction/scrub_ftrace_events_integrationtest.cc b/src/trace_redaction/scrub_ftrace_events_integrationtest.cc deleted file mode 100644 index 50853c68d1..0000000000 --- a/src/trace_redaction/scrub_ftrace_events_integrationtest.cc +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include - -#include "perfetto/base/status.h" -#include "src/base/test/status_matchers.h" -#include "src/trace_redaction/scrub_ftrace_events.h" -#include "src/trace_redaction/trace_redaction_framework.h" -#include "src/trace_redaction/trace_redaction_integration_fixture.h" -#include "test/gtest_and_gmock.h" - -#include "protos/perfetto/trace//ftrace/ftrace_event.pbzero.h" -#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h" -#include "protos/perfetto/trace/trace.pbzero.h" -#include "protos/perfetto/trace/trace_packet.pbzero.h" - -namespace perfetto::trace_redaction { - -// Runs ScrubFtraceEvents over an actual trace, verifying packet integrity when -// fields are removed. -class ScrubFtraceEventsIntegrationTest - : public testing::Test, - protected TraceRedactionIntegrationFixure { - public: - ScrubFtraceEventsIntegrationTest() = default; - ~ScrubFtraceEventsIntegrationTest() override = default; - - protected: - void SetUp() override { - context()->ftrace_packet_allow_list.insert( - protos::pbzero::FtraceEvent::kSchedSwitchFieldNumber); - - trace_redactor()->emplace_transform(); - } - - // Gets spans for `event` messages that contain `sched_switch` messages. - static std::vector GetEventsWithSchedSwitch( - protos::pbzero::TracePacket::Decoder packet) { - std::vector ranges; - - if (!packet.has_ftrace_events()) { - return ranges; - } - - protos::pbzero::FtraceEventBundle::Decoder bundle(packet.ftrace_events()); - - if (!bundle.has_event()) { - return ranges; - } - - for (auto event_it = bundle.event(); event_it; ++event_it) { - protos::pbzero::FtraceEvent::Decoder event(*event_it); - - if (event.has_sched_switch()) { - ranges.push_back(*event_it); - } - } - - return ranges; - } - - // Instead of using the allow-list created by PopulateAllowlist, use a simpler - // allowlist; an allowlist that contains most value types. - // - // uint64....FtraceEvent...............timestamp - // uint32....FtraceEvent...............pid - // - // int32.....SchedSwitchFtraceEvent....prev_pid - // int64.....SchedSwitchFtraceEvent....prev_state - // string....SchedSwitchFtraceEvent....next_comm - // - // Compare all switch events in each trace. The comparison is only on the - // switch packets, not on the data leading up to or around them. - static void ComparePackets(protos::pbzero::TracePacket::Decoder left, - protos::pbzero::TracePacket::Decoder right) { - auto left_switches = GetEventsWithSchedSwitch(std::move(left)); - auto right_switches = GetEventsWithSchedSwitch(std::move(right)); - - ASSERT_EQ(left_switches.size(), right_switches.size()); - - auto left_switch_it = left_switches.begin(); - auto right_switch_it = right_switches.begin(); - - while (left_switch_it != left_switches.end() && - right_switch_it != right_switches.end()) { - auto left_switch_str = left_switch_it->ToStdString(); - auto right_switch_str = right_switch_it->ToStdString(); - - ASSERT_EQ(left_switch_str, right_switch_str); - - ++left_switch_it; - ++right_switch_it; - } - - ASSERT_EQ(left_switches.size(), right_switches.size()); - } -}; - -TEST_F(ScrubFtraceEventsIntegrationTest, FindsPackageAndFiltersPackageList) { - auto redacted = Redact(); - ASSERT_OK(redacted) << redacted.message(); - - // Load source. - auto before_raw_trace = LoadOriginal(); - ASSERT_OK(before_raw_trace) << before_raw_trace.status().message(); - protos::pbzero::Trace::Decoder before_trace(before_raw_trace.value()); - auto before_it = before_trace.packet(); - - // Load redacted. - auto after_raw_trace = LoadRedacted(); - ASSERT_OK(after_raw_trace) << after_raw_trace.status().message(); - protos::pbzero::Trace::Decoder after_trace(after_raw_trace.value()); - auto after_it = after_trace.packet(); - - while (before_it && after_it) { - protos::pbzero::TracePacket::Decoder before_packet(*before_it); - protos::pbzero::TracePacket::Decoder after_packet(*after_it); - - ComparePackets(std::move(before_packet), std::move(after_packet)); - - ++before_it; - ++after_it; - } - - // Both should be at the end. - ASSERT_FALSE(before_it); - ASSERT_FALSE(after_it); -} - -} // namespace perfetto::trace_redaction diff --git a/src/trace_redaction/scrub_process_stats.cc b/src/trace_redaction/scrub_process_stats.cc index 78c983ae18..d7492b742b 100644 --- a/src/trace_redaction/scrub_process_stats.cc +++ b/src/trace_redaction/scrub_process_stats.cc @@ -21,6 +21,7 @@ #include "perfetto/base/status.h" #include "perfetto/protozero/field.h" #include "perfetto/protozero/scattered_heap_buffer.h" +#include "src/trace_processor/util/status_macros.h" #include "src/trace_redaction/proto_util.h" #include "src/trace_redaction/trace_redaction_framework.h" @@ -50,50 +51,65 @@ base::Status ScrubProcessStats::Transform(const Context& context, protozero::HeapBuffered message; - // TODO(vaage): Add primitive to drop all packets that don't have a - // timestamp, allowing all other packets assume there are timestamps. + // Not all packets will have a top-level timestamp, but for process stats, the + // timestamp is located at the trace packet. auto time_field = packet_decoder.FindField( protos::pbzero::TracePacket::kTimestampFieldNumber); PERFETTO_DCHECK(time_field.valid()); - auto time = time_field.as_uint64(); - - for (auto packet_field = packet_decoder.ReadField(); packet_field.valid(); - packet_field = packet_decoder.ReadField()) { - if (packet_field.id() != - protos::pbzero::TracePacket::kProcessStatsFieldNumber) { - proto_util::AppendField(packet_field, message.get()); - continue; - } - - auto process_stats = std::move(packet_field); - protozero::ProtoDecoder process_stats_decoder(process_stats.as_bytes()); - auto* process_stats_message = message->set_process_stats(); + auto ts = time_field.as_uint64(); - for (auto process_stats_field = process_stats_decoder.ReadField(); - process_stats_field.valid(); - process_stats_field = process_stats_decoder.ReadField()) { - bool keep_field; + for (auto field = packet_decoder.ReadField(); field.valid(); + field = packet_decoder.ReadField()) { + if (field.id() == protos::pbzero::TracePacket::kProcessStatsFieldNumber) { + RETURN_IF_ERROR(OnProcessStats(context, ts, field.as_bytes(), + message->set_process_stats())); + } else { + proto_util::AppendField(field, message.get()); + } + } - if (process_stats_field.id() == - protos::pbzero::ProcessStats::kProcessesFieldNumber) { - protozero::ProtoDecoder process_decoder(process_stats_field.as_bytes()); - auto pid = process_decoder.FindField( - protos::pbzero::ProcessStats::Process::kPidFieldNumber); + packet->assign(message.SerializeAsString()); - keep_field = context.timeline->PidConnectsToUid(time, pid.as_int32(), - *context.package_uid); - } else { - keep_field = true; - } + return base::OkStatus(); +} - if (keep_field) { - proto_util::AppendField(process_stats_field, process_stats_message); - } +base::Status ScrubProcessStats::OnProcessStats( + const Context& context, + uint64_t ts, + protozero::ConstBytes bytes, + protos::pbzero::ProcessStats* message) const { + protozero::ProtoDecoder decoder(bytes); + + for (auto field = decoder.ReadField(); field.valid(); + field = decoder.ReadField()) { + if (field.id() == protos::pbzero::ProcessStats::kProcessesFieldNumber) { + RETURN_IF_ERROR(OnProcess(context, ts, field, message)); + } else { + proto_util::AppendField(field, message); } } - packet->assign(message.SerializeAsString()); + return base::OkStatus(); +} + +base::Status ScrubProcessStats::OnProcess( + const Context& context, + uint64_t ts, + protozero::Field field, + protos::pbzero::ProcessStats* message) const { + PERFETTO_DCHECK(field.id() == + protos::pbzero::ProcessStats::kProcessesFieldNumber); + + protozero::ProtoDecoder decoder(field.as_bytes()); + auto pid = + decoder.FindField(protos::pbzero::ProcessStats::Process::kPidFieldNumber); + PERFETTO_DCHECK(pid.valid()); + + PERFETTO_DCHECK(filter_); + if (filter_->Includes(context, ts, pid.as_int32())) { + proto_util::AppendField(field, message); + } return base::OkStatus(); } diff --git a/src/trace_redaction/scrub_process_stats.h b/src/trace_redaction/scrub_process_stats.h index 99b66978b3..fab98da2f3 100644 --- a/src/trace_redaction/scrub_process_stats.h +++ b/src/trace_redaction/scrub_process_stats.h @@ -17,6 +17,9 @@ #ifndef SRC_TRACE_REDACTION_SCRUB_PROCESS_STATS_H_ #define SRC_TRACE_REDACTION_SCRUB_PROCESS_STATS_H_ +#include + +#include "src/trace_redaction/redact_sched_events.h" #include "src/trace_redaction/trace_redaction_framework.h" namespace perfetto::trace_redaction { @@ -25,6 +28,24 @@ class ScrubProcessStats : public TransformPrimitive { public: base::Status Transform(const Context& context, std::string* packet) const override; + + template + void emplace_filter() { + filter_ = std::make_unique(); + } + + private: + base::Status OnProcessStats(const Context& context, + uint64_t ts, + protozero::ConstBytes bytes, + protos::pbzero::ProcessStats* message) const; + + base::Status OnProcess(const Context& context, + uint64_t ts, + protozero::Field field, + protos::pbzero::ProcessStats* message) const; + + std::unique_ptr filter_; }; } // namespace perfetto::trace_redaction diff --git a/src/trace_redaction/scrub_process_stats_integrationtest.cc b/src/trace_redaction/scrub_process_stats_integrationtest.cc index cce2b6b383..7f3d3d9877 100644 --- a/src/trace_redaction/scrub_process_stats_integrationtest.cc +++ b/src/trace_redaction/scrub_process_stats_integrationtest.cc @@ -36,11 +36,13 @@ class ScrubProcessStatsTest : public testing::Test, protected TraceRedactionIntegrationFixure { protected: void SetUp() override { - trace_redactor()->emplace_collect(); - trace_redactor()->emplace_transform(); + trace_redactor_.emplace_collect(); + + auto* scrub = trace_redactor_.emplace_transform(); + scrub->emplace_filter(); // Package "com.Unity.com.unity.multiplayer.samples.coop"; - context()->package_uid = 10252; + context_.package_uid = 10252; } // Gets pids from all process_stats messages in the trace (bytes). @@ -68,6 +70,9 @@ class ScrubProcessStatsTest : public testing::Test, return pids; } + + Context context_; + TraceRedactor trace_redactor_; }; // This test is a canary for changes to the test data. If the test data was to @@ -126,7 +131,7 @@ TEST_F(ScrubProcessStatsTest, VerifyTraceStats) { // Package name: "com.Unity.com.unity.multiplayer.samples.coop" // Package pid: 7105 TEST_F(ScrubProcessStatsTest, OnlyKeepsStatsForPackage) { - auto result = Redact(); + auto result = Redact(trace_redactor_, &context_); ASSERT_OK(result) << result.c_message(); auto redacted = LoadRedacted(); diff --git a/src/trace_redaction/scrub_process_trees.cc b/src/trace_redaction/scrub_process_trees.cc deleted file mode 100644 index a3f1f3b46c..0000000000 --- a/src/trace_redaction/scrub_process_trees.cc +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/trace_redaction/scrub_process_trees.h" - -#include "perfetto/base/status.h" -#include "perfetto/protozero/field.h" -#include "perfetto/protozero/scattered_heap_buffer.h" -#include "src/trace_redaction/proto_util.h" -#include "src/trace_redaction/trace_redaction_framework.h" - -#include "protos/perfetto/trace/ps/process_tree.pbzero.h" -#include "protos/perfetto/trace/trace_packet.pbzero.h" - -namespace perfetto::trace_redaction { - -namespace { - -// Appends a value to the message if (and only if) the pid belongs to the target -// package. -void TryAppendPid(const Context& context, - const protozero::Field& timestamp, - const protozero::Field& pid, - const protozero::Field& value, - protozero::Message* message) { - // All valid processes with have a time and pid/tid values. However, if - // they're missing values, the trace is corrupt. To avoid making this work by - // dropping too much data, drop the cmdline for all processes. - if (!timestamp.valid() || !pid.valid()) { - return; - } - - if (context.timeline->PidConnectsToUid(timestamp.as_uint64(), pid.as_int32(), - *context.package_uid)) { - proto_util::AppendField(value, message); - } -} - -} // namespace - -base::Status ScrubProcessTrees::VerifyContext(const Context& context) const { - if (!context.package_uid.has_value()) { - return base::ErrStatus("ScrubProcessTrees: missing package uid."); - } - - if (!context.timeline) { - return base::ErrStatus("ScrubProcessTrees: missing timeline."); - } - - return base::OkStatus(); -} - -void ScrubProcessTrees::TransformProcess( - const Context& context, - const protozero::Field& timestamp, - const protozero::Field& process, - protos::pbzero::ProcessTree* process_tree) const { - protozero::ProtoDecoder decoder(process.as_bytes()); - - auto pid = - decoder.FindField(protos::pbzero::ProcessTree::Process::kPidFieldNumber); - - auto* process_message = process_tree->add_processes(); - - for (auto field = decoder.ReadField(); field.valid(); - field = decoder.ReadField()) { - if (field.id() == - protos::pbzero::ProcessTree::Process::kCmdlineFieldNumber) { - TryAppendPid(context, timestamp, pid, field, process_message); - } else { - proto_util::AppendField(field, process_message); - } - } -} - -void ScrubProcessTrees::TransformThread( - const Context& context, - const protozero::Field& timestamp, - const protozero::Field& thread, - protos::pbzero::ProcessTree* process_tree) const { - protozero::ProtoDecoder decoder(thread.as_bytes()); - - auto tid = - decoder.FindField(protos::pbzero::ProcessTree::Thread::kTidFieldNumber); - - auto* thread_message = process_tree->add_threads(); - - for (auto field = decoder.ReadField(); field.valid(); - field = decoder.ReadField()) { - if (field.id() == protos::pbzero::ProcessTree::Thread::kNameFieldNumber) { - TryAppendPid(context, timestamp, tid, field, thread_message); - } else { - proto_util::AppendField(field, thread_message); - } - } -} - -} // namespace perfetto::trace_redaction diff --git a/src/trace_redaction/scrub_process_trees.h b/src/trace_redaction/scrub_process_trees.h deleted file mode 100644 index 7c2b07a20b..0000000000 --- a/src/trace_redaction/scrub_process_trees.h +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef SRC_TRACE_REDACTION_SCRUB_PROCESS_TREES_H_ -#define SRC_TRACE_REDACTION_SCRUB_PROCESS_TREES_H_ - -#include "perfetto/base/status.h" -#include "perfetto/protozero/field.h" -#include "src/trace_redaction/modify_process_trees.h" -#include "src/trace_redaction/trace_redaction_framework.h" - -#include "protos/perfetto/trace/ps/process_tree.pbzero.h" - -namespace perfetto::trace_redaction { - -// Removes process names and thread names from process_trees if their pids/tids -// are not connected to the target package. -class ScrubProcessTrees : public ModifyProcessTree { - protected: - base::Status VerifyContext(const Context& context) const override; - - void TransformProcess( - const Context& context, - const protozero::Field& timestamp, - const protozero::Field& process, - protos::pbzero::ProcessTree* process_trees) const override; - - void TransformThread( - const Context& context, - const protozero::Field& timestamp, - const protozero::Field& thread, - protos::pbzero::ProcessTree* process_tree) const override; -}; - -} // namespace perfetto::trace_redaction - -#endif // SRC_TRACE_REDACTION_SCRUB_PROCESS_TREES_H_ diff --git a/src/trace_redaction/scrub_process_trees_integrationtest.cc b/src/trace_redaction/scrub_process_trees_integrationtest.cc deleted file mode 100644 index a001a36cf5..0000000000 --- a/src/trace_redaction/scrub_process_trees_integrationtest.cc +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include - -#include "src/base/test/status_matchers.h" -#include "src/trace_redaction/collect_timeline_events.h" -#include "src/trace_redaction/find_package_uid.h" -#include "src/trace_redaction/scrub_process_trees.h" -#include "src/trace_redaction/trace_redaction_framework.h" -#include "src/trace_redaction/trace_redaction_integration_fixture.h" -#include "src/trace_redaction/trace_redactor.h" -#include "test/gtest_and_gmock.h" - -#include "protos/perfetto/trace/ps/process_tree.pbzero.h" -#include "protos/perfetto/trace/trace.pbzero.h" - -namespace perfetto::trace_redaction { - -namespace { - -constexpr std::string_view kProcessName = - "com.Unity.com.unity.multiplayer.samples.coop"; - -} // namespace - -class ScrubProcessTreesIntegrationTest - : public testing::Test, - protected TraceRedactionIntegrationFixure { - protected: - void SetUp() override { - trace_redactor()->emplace_collect(); - trace_redactor()->emplace_collect(); - trace_redactor()->emplace_transform(); - - // In this case, the process and package have the same name. - context()->package_name = kProcessName; - } - - std::vector CollectProcessNames( - protos::pbzero::Trace::Decoder trace) const { - std::vector names; - - for (auto packet_it = trace.packet(); packet_it; ++packet_it) { - protos::pbzero::TracePacket::Decoder packet(*packet_it); - - if (!packet.has_process_tree()) { - continue; - } - - protos::pbzero::ProcessTree::Decoder process_tree(packet.process_tree()); - - for (auto process_it = process_tree.processes(); process_it; - ++process_it) { - protos::pbzero::ProcessTree::Process::Decoder process(*process_it); - - if (process.has_cmdline()) { - names.push_back(process.cmdline()->as_std_string()); - } - } - } - - return names; - } -}; - -TEST_F(ScrubProcessTreesIntegrationTest, RemovesProcessNamesFromProcessTrees) { - ASSERT_OK(Redact()); - - auto original_trace_str = LoadOriginal(); - ASSERT_OK(original_trace_str); - - auto redacted_trace_str = LoadRedacted(); - ASSERT_OK(redacted_trace_str); - - protos::pbzero::Trace::Decoder original_trace(original_trace_str.value()); - auto original_processes = CollectProcessNames(std::move(original_trace)); - - ASSERT_GT(original_processes.size(), 1u); - - protos::pbzero::Trace::Decoder redacted_trace(redacted_trace_str.value()); - auto redacted_processes = CollectProcessNames(std::move(redacted_trace)); - - ASSERT_EQ(redacted_processes.size(), 1u); - ASSERT_EQ(redacted_processes.at(0), kProcessName); -} - -} // namespace perfetto::trace_redaction diff --git a/src/trace_redaction/scrub_trace_packet.cc b/src/trace_redaction/scrub_trace_packet.cc deleted file mode 100644 index fd29885eab..0000000000 --- a/src/trace_redaction/scrub_trace_packet.cc +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include - -#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()) { - return base::ErrStatus("ScrubTracePacket: null or empty packet."); - } - - for (const auto& filter : filters_) { - RETURN_IF_ERROR(filter->VerifyContext(context)); - } - - protozero::HeapBuffered 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->assign(new_packet.SerializeAsString()); - return base::OkStatus(); -} - -// Logical AND all filters. -bool ScrubTracePacket::KeepEvent(const Context& context, - const protozero::Field& field) const { - for (const auto& filter : filters_) { - if (!filter->KeepField(context, field)) { - return false; - } - } - - return true; -} - -} // namespace perfetto::trace_redaction diff --git a/src/trace_redaction/scrub_trace_packet.h b/src/trace_redaction/scrub_trace_packet.h deleted file mode 100644 index 22e506bdcd..0000000000 --- a/src/trace_redaction/scrub_trace_packet.h +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef SRC_TRACE_REDACTION_SCRUB_TRACE_PACKET_H_ -#define SRC_TRACE_REDACTION_SCRUB_TRACE_PACKET_H_ - -#include "src/trace_redaction/trace_redaction_framework.h" - -namespace perfetto::trace_redaction { - -class TracePacketFilter { - public: - virtual ~TracePacketFilter(); - - // Checks if the context contains all neccessary parameters. - virtual base::Status VerifyContext(const Context& context) const; - - // 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 { - public: - base::Status Transform(const Context& context, - std::string* packet) const override; - - template - void emplace_back() { - filters_.emplace_back(new T()); - } - - private: - bool KeepEvent(const Context& context, const protozero::Field& field) const; - - std::vector> filters_; -}; - -} // namespace perfetto::trace_redaction - -#endif // SRC_TRACE_REDACTION_SCRUB_TRACE_PACKET_H_ diff --git a/src/trace_redaction/suspend_resume.cc b/src/trace_redaction/suspend_resume.cc deleted file mode 100644 index 243446f40f..0000000000 --- a/src/trace_redaction/suspend_resume.cc +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/trace_redaction/suspend_resume.h" -#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h" -#include "protos/perfetto/trace/ftrace/power.pbzero.h" - -namespace perfetto::trace_redaction { - -base::Status AllowSuspendResume::Build(Context* context) const { - context->ftrace_packet_allow_list.insert( - protos::pbzero::FtraceEvent::kSuspendResumeFieldNumber); - - // Values are taken from "suspend_period.textproto". - context->suspend_result_allow_list.insert("syscore_suspend"); - context->suspend_result_allow_list.insert("syscore_resume"); - context->suspend_result_allow_list.insert("timekeeping_freeze"); - - return base::OkStatus(); -} - -base::Status FilterSuspendResume::VerifyContext(const Context&) const { - // FilterSuspendResume could check if kSuspendResumeFieldNumber is present in - // ftrace_packet_allow_list and there are values in the - // suspend_result_allow_list, but would make it hard to enable/disable - // suspend-resume redaction. - return base::OkStatus(); -} - -// The ftrace event is passed in. -bool FilterSuspendResume::KeepEvent(const Context& context, - protozero::ConstBytes bytes) const { - protozero::ProtoDecoder event_decoder(bytes); - - auto suspend_resume = event_decoder.FindField( - protos::pbzero::FtraceEvent::kSuspendResumeFieldNumber); - - // It's not a suspend-resume event, defer the decision to another filter. - if (!suspend_resume.valid()) { - return true; - } - - protozero::ProtoDecoder suspend_resume_decoder(suspend_resume.as_bytes()); - - auto action = suspend_resume_decoder.FindField( - protos::pbzero::SuspendResumeFtraceEvent::kActionFieldNumber); - - return !action.valid() || - context.suspend_result_allow_list.count(action.as_std_string()); -} - -} // namespace perfetto::trace_redaction diff --git a/src/trace_redaction/suspend_resume.h b/src/trace_redaction/suspend_resume.h deleted file mode 100644 index 09f7bd8851..0000000000 --- a/src/trace_redaction/suspend_resume.h +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef SRC_TRACE_REDACTION_SUSPEND_RESUME_H_ -#define SRC_TRACE_REDACTION_SUSPEND_RESUME_H_ - -#include "src/trace_redaction/scrub_ftrace_events.h" -#include "src/trace_redaction/trace_redaction_framework.h" - -namespace perfetto::trace_redaction { - -// Updates allowlists to include suspend-resume events and which events to allow -// through. -class AllowSuspendResume : public BuildPrimitive { - public: - base::Status Build(Context* context) const override; -}; - -// Filters ftrace events based on the suspend-resume event. -class FilterSuspendResume : public FtraceEventFilter { - public: - base::Status VerifyContext(const Context& context) const override; - - bool KeepEvent(const Context& context, - protozero::ConstBytes bytes) const override; -}; - -} // namespace perfetto::trace_redaction - -#endif // SRC_TRACE_REDACTION_SUSPEND_RESUME_H_ diff --git a/src/trace_redaction/suspend_resume_unittest.cc b/src/trace_redaction/suspend_resume_unittest.cc deleted file mode 100644 index 7cf498a8d4..0000000000 --- a/src/trace_redaction/suspend_resume_unittest.cc +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/trace_redaction/suspend_resume.h" -#include "src/base/test/status_matchers.h" -#include "test/gtest_and_gmock.h" - -#include "protos/perfetto/trace/ftrace/ftrace.gen.h" -#include "protos/perfetto/trace/ftrace/ftrace_event.gen.h" -#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h" -#include "protos/perfetto/trace/ftrace/power.gen.h" - -namespace perfetto::trace_redaction { - -TEST(AllowSuspendResumeTest, UpdatesTracePacketAllowlist) { - Context context; - - // Start with a non-empty allow-list item. - context.ftrace_packet_allow_list.insert( - protos::pbzero::FtraceEvent::kPrintFieldNumber); - - ASSERT_EQ(context.ftrace_packet_allow_list.size(), 1u); - - AllowSuspendResume allow; - auto status = allow.Build(&context); - ASSERT_OK(status) << status.message(); - - // Print should still be present. The allowlist should have been updated, not - // replaced. - ASSERT_EQ(context.ftrace_packet_allow_list.count( - protos::pbzero::FtraceEvent::kPrintFieldNumber), - 1u); - - ASSERT_EQ(context.ftrace_packet_allow_list.count( - protos::pbzero::FtraceEvent::kSuspendResumeFieldNumber), - 1u); -} - -TEST(AllowSuspendResumeTest, UpdatesSuspendResumeAllowlist) { - Context context; - - ASSERT_TRUE(context.suspend_result_allow_list.empty()); - - AllowSuspendResume allow; - auto status = allow.Build(&context); - ASSERT_OK(status) << status.message(); - - ASSERT_FALSE(context.suspend_result_allow_list.empty()); -} - -class SuspendResumeTest : public testing::Test { - protected: - void SetUp() { - AllowSuspendResume allow; - ASSERT_OK(allow.Build(&context_)); - } - - protos::gen::FtraceEvent CreateSuspendResumeEvent( - const std::string* action) const { - protos::gen::FtraceEvent event; - event.set_timestamp(1234); - event.set_pid(0); - - auto* suspend_resume = event.mutable_suspend_resume(); - - if (action) { - suspend_resume->set_action(*action); - } - - return event; - } - - protos::gen::FtraceEvent CreateOtherEvent() const { - protos::gen::FtraceEvent event; - event.set_timestamp(1234); - event.set_pid(0); - - auto* print = event.mutable_print(); - print->set_buf("This is a message"); - - return event; - } - - const Context& context() const { return context_; } - - private: - Context context_; -}; - -// The suspend-resume filter is not responsible for non-suspend-resume events. -// It should assume that another filter will handle it and it should just allow -// those events through -TEST_F(SuspendResumeTest, AcceptsOtherEvents) { - auto event = CreateOtherEvent(); - auto event_array = event.SerializeAsArray(); - protozero::ConstBytes event_bytes{event_array.data(), event_array.size()}; - - FilterSuspendResume filter; - ASSERT_TRUE(filter.KeepEvent(context(), event_bytes)); -} - -TEST_F(SuspendResumeTest, AcceptsEventsWithNoName) { - auto event = CreateSuspendResumeEvent(nullptr); - auto event_array = event.SerializeAsArray(); - protozero::ConstBytes event_bytes{event_array.data(), event_array.size()}; - - Context context; - - FilterSuspendResume filter; - ASSERT_TRUE(filter.KeepEvent(context, event_bytes)); -} - -TEST_F(SuspendResumeTest, AcceptsEventsWithValidName) { - // This value is from "src/trace_redaction/suspend_resume.cc". - std::string name = "syscore_suspend"; - - auto event = CreateSuspendResumeEvent(&name); - auto event_array = event.SerializeAsArray(); - protozero::ConstBytes event_bytes{event_array.data(), event_array.size()}; - - FilterSuspendResume filter; - ASSERT_TRUE(filter.KeepEvent(context(), event_bytes)); -} - -TEST_F(SuspendResumeTest, RejectsEventsWithInvalidName) { - std::string name = "hello world"; - - auto event = CreateSuspendResumeEvent(&name); - auto event_array = event.SerializeAsArray(); - protozero::ConstBytes event_bytes{event_array.data(), event_array.size()}; - - FilterSuspendResume filter; - ASSERT_FALSE(filter.KeepEvent(context(), event_bytes)); -} - -} // namespace perfetto::trace_redaction diff --git a/src/trace_redaction/trace_processor_integrationtest.cc b/src/trace_redaction/trace_processor_integrationtest.cc new file mode 100644 index 0000000000..2f82d54a60 --- /dev/null +++ b/src/trace_redaction/trace_processor_integrationtest.cc @@ -0,0 +1,305 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "perfetto/trace_processor/trace_processor.h" +#include "src/base/test/status_matchers.h" +#include "src/trace_redaction/trace_redaction_integration_fixture.h" +#include "test/gtest_and_gmock.h" + +namespace perfetto::trace_redaction { +namespace { +constexpr auto kTrace = "test/data/trace-redaction-api-capture.pftrace"; + +constexpr auto kPackageName = "com.prefabulated.touchlatency"; +constexpr auto kPackageUid = 10020; +} // namespace + +class AfterRedactionIntegrationTest + : public testing::Test, + protected TraceRedactionIntegrationFixure { + protected: + void SetUp() override { + SetSourceTrace(kTrace); + + trace_processor::Config tp_config; + trace_processor_ = + trace_processor::TraceProcessor::CreateInstance(tp_config); + + TraceRedactor::Config tr_config; + auto trace_redactor = TraceRedactor::CreateInstance(tr_config); + + Context context; + context.package_name = kPackageName; + + ASSERT_OK(Redact(*trace_redactor, &context)); + + auto raw = LoadRedacted(); + ASSERT_OK(raw); + + auto read_buffer = std::make_unique(raw->size()); + memcpy(read_buffer.get(), raw->data(), raw->size()); + + ASSERT_OK(trace_processor_->Parse(std::move(read_buffer), raw->size())); + trace_processor_->NotifyEndOfFile(); + } + + std::unique_ptr trace_processor_; +}; + +// After redaction, the only package remaining in the package list should be the +// target package. +TEST_F(AfterRedactionIntegrationTest, FindsCorrectUid) { + auto rows = trace_processor_->ExecuteQuery( + "SELECT uid FROM package_list ORDER BY uid"); + + ASSERT_TRUE(rows.Next()); + ASSERT_EQ(rows.Get(0).AsLong(), kPackageUid); + + ASSERT_FALSE(rows.Next()); + ASSERT_OK(rows.Status()); +} + +TEST_F(AfterRedactionIntegrationTest, CreatesThreadForEachCPU) { + // There's a main thread, but it is not used (it's just there to create a + // thread group). Exclude it so we get N threads instead of N+1. + + // This should yield a collection of size 1. + std::string synth_process = + "SELECT upid FROM process WHERE name='Other-Processes'"; + + auto threads = trace_processor_->ExecuteQuery( + "SELECT COUNT(tid) FROM thread WHERE upid IN (" + synth_process + + ") AND NOT is_main_thread"); + + auto cpus = trace_processor_->ExecuteQuery( + "SELECT COUNT(DISTINCT cpu) FROM cpu_counter_track"); + + ASSERT_TRUE(threads.Next()); + ASSERT_TRUE(cpus.Next()); + + auto thread_count = threads.Get(0).AsLong(); + ASSERT_NE(thread_count, 0); + + auto cpu_count = threads.Get(0).AsLong(); + ASSERT_NE(cpu_count, 0); + + ASSERT_EQ(thread_count, cpu_count); + + ASSERT_FALSE(threads.Next()); + ASSERT_FALSE(cpus.Next()); + + ASSERT_OK(threads.Status()); + ASSERT_OK(cpus.Status()); +} + +TEST_F(AfterRedactionIntegrationTest, ReducesProcesses) { + auto processes = trace_processor_->ExecuteQuery( + "SELECT pid, name FROM process ORDER BY pid"); + + // PID NAME + // ====================================================== + // 0 NULL + // 1 NULL + // 863 NULL <--- Zygote + // 4524 com.prefabulated.touchlatency + // 4194305 Other-Processes + + ASSERT_TRUE(processes.Next()); + ASSERT_EQ(processes.Get(0).AsLong(), 0); + ASSERT_TRUE(processes.Get(1).is_null()); + + ASSERT_TRUE(processes.Next()); + ASSERT_EQ(processes.Get(0).AsLong(), 1); + ASSERT_TRUE(processes.Get(1).is_null()); + + // Zygote + ASSERT_TRUE(processes.Next()); + ASSERT_EQ(processes.Get(0).AsLong(), 863); + ASSERT_TRUE(processes.Get(1).is_null()); + + ASSERT_TRUE(processes.Next()); + ASSERT_EQ(processes.Get(0).AsLong(), 4524); + ASSERT_STREQ(processes.Get(1).AsString(), kPackageName); + + ASSERT_TRUE(processes.Next()); + ASSERT_EQ(processes.Get(0).AsLong(), 4194305); + ASSERT_STREQ(processes.Get(1).AsString(), "Other-Processes"); +} + +// Tests comparing the trace before and after redaction. +class BeforeAndAfterAfterIntegrationTest + : public testing::Test, + protected TraceRedactionIntegrationFixure { + protected: + void SetUp() override { + SetSourceTrace(kTrace); + + trace_processor::Config config; + + auto raw_before = LoadOriginal(); + ASSERT_OK(raw_before); + trace_processor_before_ = CreateTraceProcessor(raw_before.value()); + + TraceRedactor::Config tr_config; + auto trace_redactor = TraceRedactor::CreateInstance(tr_config); + + Context context; + context.package_name = kPackageName; + + Redact(*trace_redactor, &context); + + auto raw_after = LoadRedacted(); + ASSERT_OK(raw_after); + trace_processor_after_ = CreateTraceProcessor(raw_after.value()); + } + + static std::unique_ptr CreateTraceProcessor( + std::string_view raw) { + auto read_buffer = std::make_unique(raw.size()); + memcpy(read_buffer.get(), raw.data(), raw.size()); + + trace_processor::Config config; + auto trace_processor = + trace_processor::TraceProcessor::CreateInstance(config); + + auto parsed = trace_processor->Parse(std::move(read_buffer), raw.size()); + + if (!parsed.ok()) { + return nullptr; + } + + trace_processor->NotifyEndOfFile(); + return trace_processor; + } + + std::unique_ptr trace_processor_before_; + std::unique_ptr trace_processor_after_; +}; + +TEST_F(BeforeAndAfterAfterIntegrationTest, KeepsAllTargetPackageThreads) { + std::string package_name = kPackageName; + + // This should yield a collection of one. + std::string packages = + "SELECT uid FROM package_list WHERE package_name='" + package_name + "'"; + + // This should yield a collection of one. + std::string processes = + "SELECT upid FROM process WHERE uid IN (" + packages + ")"; + + // This should yield a collect of N where N is some non-zero integer. + const std::string tid_query = + "SELECT tid FROM thread WHERE upid IN (" + processes + ") ORDER BY tid"; + + auto it_before = trace_processor_before_->ExecuteQuery(tid_query); + auto it_after = trace_processor_after_->ExecuteQuery(tid_query); + + ASSERT_TRUE(it_before.Next()); + + do { + ASSERT_TRUE(it_after.Next()); + ASSERT_EQ(it_before.Get(0).AsLong(), it_after.Get(0).AsLong()); + } while (it_before.Next()); + + ASSERT_FALSE(it_after.Next()); + + ASSERT_OK(it_before.Status()); + ASSERT_OK(it_after.Status()); +} + +// There are two Zygotes on Android ('zygote', 'zygote64'). Modern device should +// have both, so we assume both are present in the unredacted trace. During +// redaction, all zygote information will be lost during the merge stage. +// However, since the target process references the zygote (ppid) a "ghost" +// process will appear in the process table. +class RedactedZygoteIntegrationTest + : public BeforeAndAfterAfterIntegrationTest { + protected: + void SetUp() { + BeforeAndAfterAfterIntegrationTest::SetUp(); + + auto it_before = trace_processor_before_->ExecuteQuery( + "SELECT pid FROM process WHERE name IN ('zygote', 'zygote64')"); + + ASSERT_TRUE(it_before.Next()); + zygotes_[0] = it_before.Get(0).AsLong(); + + ASSERT_TRUE(it_before.Next()); + zygotes_[1] = it_before.Get(0).AsLong(); + + ASSERT_FALSE(it_before.Next()); + ASSERT_OK(it_before.Status()); + } + + // Creates a SQL statement that can be used AFTER a "WHERE" clause to test if + // the process is a zygote processes. The caller is responsible for the + // prefix (e.g. WHERE, AND, OR, etc.). + std::string IsZygote() const { + auto p = std::to_string(zygotes_.at(0)); + auto q = std::to_string(zygotes_.at(1)); + + return "pid=" + p + " OR pid=" + q; + } + + std::array zygotes_; +}; + +TEST_F(RedactedZygoteIntegrationTest, KeepsOneZygote) { + auto count = trace_processor_after_->ExecuteQuery( + "SELECT COUNT(pid) FROM process WHERE " + IsZygote()); + + ASSERT_TRUE(count.Next()); + ASSERT_EQ(count.Get(0).AsLong(), 1); + ASSERT_FALSE(count.Next()); + ASSERT_OK(count.Status()); +} + +TEST_F(RedactedZygoteIntegrationTest, RemovesName) { + auto names = trace_processor_after_->ExecuteQuery( + "SELECT name FROM process WHERE " + IsZygote()); + + ASSERT_TRUE(names.Next()); + ASSERT_TRUE(names.Get(0).is_null()); + ASSERT_FALSE(names.Next()); + ASSERT_OK(names.Status()); +} + +// After redaction, the only application left should be the target package. +// While an application can have multiple processes, there should one top level +// process that was forked by the zygote. +// +// WARNING: This test relies on an assumption: there is only be one instance of +// the application running. We know this assumption to be faulty as multiple +// profiles allow for multiple instances of the same package to be running. +// In redaction, we treat them all as a single instance. The test trace does not +// use multiple profiles, so this assumption hold for this trace. +TEST_F(RedactedZygoteIntegrationTest, OnlyReferencedByTargetPackage) { + // To avoid collisions, trace processor quickly moves away from volatile + // values like tid and pid to use globally stable values like upid and utid. + // Because of this, we can't check if a process's parent is the zygote, we + // need to convert the pid to a upid first. + auto upids = "SELECT upid FROM process WHERE " + IsZygote(); + + auto ppids = trace_processor_after_->ExecuteQuery( + "SELECT COUNT(pid) FROM process WHERE parent_upid IN (" + upids + ")"); + + ASSERT_TRUE(ppids.Next()); + ASSERT_EQ(ppids.Get(0).AsLong(), 1); + ASSERT_FALSE(ppids.Next()); + ASSERT_OK(ppids.Status()); +} + +} // namespace perfetto::trace_redaction diff --git a/src/trace_redaction/trace_redaction_framework.h b/src/trace_redaction/trace_redaction_framework.h index 12f6eb2bf1..5cd62ecaea 100644 --- a/src/trace_redaction/trace_redaction_framework.h +++ b/src/trace_redaction/trace_redaction_framework.h @@ -17,6 +17,7 @@ #ifndef SRC_TRACE_REDACTION_TRACE_REDACTION_FRAMEWORK_H_ #define SRC_TRACE_REDACTION_TRACE_REDACTION_FRAMEWORK_H_ +#include #include #include #include @@ -42,16 +43,14 @@ constexpr uint64_t NormalizeUid(uint64_t uid) { class SystemInfo { public: - int32_t AllocateSynthThread() { - return (1 << kSynthShift) | (++next_synth_thread_); - } + int32_t AllocateSynthThread() { return ++next_synth_thread_; } uint32_t ReserveCpu(uint32_t cpu) { last_cpu_ = std::max(last_cpu_, cpu); return last_cpu_; } - uint32_t last_cpu() const { return last_cpu_; } + uint32_t cpu_count() const { return last_cpu_ + 1; } private: // This is the last allocated tid. Using a tid equal to or less than this tid @@ -79,18 +78,36 @@ class SystemInfo { // 2^22 (PID_MAX_LIMIT, approximately 4 million). // // SOURCE: https://man7.org/linux/man-pages/man5/proc.5.html - static constexpr auto kSynthShift = 22; - int32_t next_synth_thread_ = 0; + int32_t next_synth_thread_ = 1 << 22; // The last CPU index seen. If this value is 7, it means there are at least // 8 CPUs. uint32_t last_cpu_ = 0; }; -class SyntheticThreadGroup { +class SyntheticProcess { public: - int32_t tgid; - std::vector tids; + explicit SyntheticProcess(const std::vector& tids) : tids_(tids) {} + + // Use the SYSTEM_UID (i.e. 1000) because it best represents this "type" of + // process. + int32_t uid() const { return 1000; } + + // Use ppid == 1 which is normally considered to be init on Linux? + int32_t ppid() const { return 1; } + + int32_t tgid() const { return tids_.front(); } + + const std::vector& tids() const { return tids_; } + + int32_t RunningOn(uint32_t cpu) const { return tids_.at(1 + cpu); } + + int32_t RunningOn(int32_t cpu) const { + return tids_.at(1 + static_cast(cpu)); + } + + private: + std::vector tids_; }; // Primitives should be stateless. All state should be stored in the context. @@ -110,6 +127,11 @@ class SyntheticThreadGroup { // trace packets. class Context { public: + // Each packet will have a trusted uid. This is the package emitting the + // event. In production we only expect to see system uids. 9999 is the + // last allowed uid (allow all uids less than or equal to 9999). + static constexpr int32_t kMaxTrustedUid = 9999; + // The package that should not be redacted. This must be populated before // running any primitives. std::string package_name; @@ -124,17 +146,32 @@ class Context { // profileable_from_shell: false // version_code: 235013038 // } - // packages { - // name: "com.google.android.gsf" + // + // Processes reference their package using a uid: + // + // processes { + // pid: 18176 + // ppid: 904 + // cmdline: "com.google.android.gms.persistent" // uid: 10113 - // debuggable: false - // profileable_from_shell: false - // version_code: 34 // } // - // The process tree maps processes to packages via the uid value. However - // multiple processes can map to the same uid, only differed by some multiple - // of 100000, for example: + // An oddity within Android is that two or more processes can reference the + // same package using different uids: + // + // A = package(M * 100000 + X) + // B = package(N * 100000 + X) + // + // A and B map to the same package. This happens when there are two or more + // profiles on the device (e.g. a work profile and a personal profile). + // + // From the example above: + // + // uid = package_uid_for("com.google.android.gms") + // pid = main_thread_for(uid) + // ASSERT(pid == 18176) + // + // However, if there is another profile: // // processes { // pid: 18176 @@ -148,6 +185,41 @@ class Context { // cmdline: "com.google.android.gms.persistent" // uid: 1010113 // } + // + // The logic from before still hold, however, if the traced process was pid + // 21388, it will be merged with the other threads. + // + // To avoid this problem from happening, we normalize the uids and treat + // both instances as a single process: + // + // processes { + // pid: 18176 + // ppid: 904 + // cmdline: "com.google.android.gms.persistent" + // uid: 10113 + // } + // processes { + // pid: 21388 + // ppid: 904 + // cmdline: "com.google.android.gms.persistent" + // - uid: 1010113 + // + uid: 10113 + // } + // + // It sounds like there would be a privacy concern, but because both processes + // are from the same app and are being collected from the same user, there + // are no new privacy issues by doing this. + // + // But where should the uids be normalized? The dividing line is the timeline + // interface, specifically, should the timeline know anything about uids + // (other than "it's a number"). + // + // To avoid expanding the timeline's scope, the uid normalizations is done + // outside of the timeline. When a uid is passed into the timeline, it should + // be normalized (i.e. 5 != 100005). When the timeline is queried, the uid + // should be normalized. This increases the risk for error, but there are only + // two places where uids are set, writing the uid to the context and writing + // the uid to the timeline. std::optional package_uid; // Trace packets contain a "one of" entry called "data". This field can be @@ -171,9 +243,12 @@ class Context { // - protos::pbzero::TracePacket::kProcessStatsFieldNumber // - protos::pbzero::TracePacket::kClockSnapshotFieldNumber // - // Because "data" is a "one of", if no field in "trace_packet_allow_list" can - // be found, it packet should be removed. - base::FlatSet trace_packet_allow_list; + // If the mask is set to 0x00, all fields would be removed. This should not + // happen as some metadata provides context between packets. + // + // TracePacket has kForTestingFieldNumber which is set to 900. + using TracePacketMask = std::bitset<1024>; + TracePacketMask packet_mask; // Ftrace packets contain a "one of" entry called "event". Within the scope of // a ftrace event, the event can be considered the payload and other other @@ -212,7 +287,10 @@ class Context { // // 3. In this example, a cpu_idle event populates the one-of slot in the // ftrace event - base::FlatSet ftrace_packet_allow_list; + // + // Ftrace event has kMaliMaliPMMCURESETWAITFieldNumber which is set to 532. + using FtraceEventMask = std::bitset<1024>; + FtraceEventMask ftrace_mask; // message SuspendResumeFtraceEvent { // optional string action = 1 [(datapol.semantic_type) = ST_NOT_REQUIRED]; @@ -273,7 +351,7 @@ class Context { std::optional system_info; - std::optional synthetic_threads; + std::unique_ptr synthetic_process; }; // Extracts low-level data from the trace and writes it into the context. The diff --git a/src/trace_redaction/trace_redaction_integration_fixture.cc b/src/trace_redaction/trace_redaction_integration_fixture.cc index 85555f3209..8941d0d10a 100644 --- a/src/trace_redaction/trace_redaction_integration_fixture.cc +++ b/src/trace_redaction/trace_redaction_integration_fixture.cc @@ -22,13 +22,23 @@ namespace perfetto::trace_redaction { TraceRedactionIntegrationFixure::TraceRedactionIntegrationFixure() { - src_trace_ = - base::GetTestDataPath("test/data/trace-redaction-general.pftrace"); dest_trace_ = tmp_dir_.AbsolutePath("dst.pftrace"); + + // TODO: Most of the tests were written usng this trace. Those tests make a + // lot of assumption around using this trace. Those tests should be + // transitioned to the other constructor to remove this assumption. + SetSourceTrace("test/data/trace-redaction-general.pftrace"); +} + +void TraceRedactionIntegrationFixure::SetSourceTrace( + std::string_view source_file) { + src_trace_ = base::GetTestDataPath(std::string(source_file)); } -base::Status TraceRedactionIntegrationFixure::Redact() { - auto status = trace_redactor_.Redact(src_trace_, dest_trace_, &context_); +base::Status TraceRedactionIntegrationFixure::Redact( + const TraceRedactor& redactor, + Context* context) { + auto status = redactor.Redact(src_trace_, dest_trace_, context); if (status.ok()) { tmp_dir_.TrackFile("dst.pftrace"); diff --git a/src/trace_redaction/trace_redaction_integration_fixture.h b/src/trace_redaction/trace_redaction_integration_fixture.h index 55060f973d..489b5c9375 100644 --- a/src/trace_redaction/trace_redaction_integration_fixture.h +++ b/src/trace_redaction/trace_redaction_integration_fixture.h @@ -30,25 +30,19 @@ class TraceRedactionIntegrationFixure { protected: TraceRedactionIntegrationFixure(); + void SetSourceTrace(std::string_view source_file); + // Redact the source file and write it to the destination file. The contents // of each file can be read using LoadOriginal() and LoadRedacted(). - base::Status Redact(); + base::Status Redact(const TraceRedactor& redactor, Context* context); base::StatusOr LoadOriginal() const; base::StatusOr LoadRedacted() const; - Context* context() { return &context_; } - - TraceRedactor* trace_redactor() { return &trace_redactor_; } - private: base::StatusOr ReadRawTrace(const std::string& path) const; - Context context_; - - TraceRedactor trace_redactor_; - base::TmpDirTree tmp_dir_; std::string src_trace_; diff --git a/src/trace_redaction/trace_redactor.cc b/src/trace_redaction/trace_redactor.cc index 60e8188b3f..8f35739abb 100644 --- a/src/trace_redaction/trace_redactor.cc +++ b/src/trace_redaction/trace_redactor.cc @@ -28,7 +28,20 @@ #include "perfetto/trace_processor/trace_blob.h" #include "perfetto/trace_processor/trace_blob_view.h" #include "src/trace_processor/util/status_macros.h" +#include "src/trace_redaction/broadphase_packet_filter.h" +#include "src/trace_redaction/collect_frame_cookies.h" +#include "src/trace_redaction/collect_system_info.h" +#include "src/trace_redaction/collect_timeline_events.h" +#include "src/trace_redaction/find_package_uid.h" +#include "src/trace_redaction/merge_threads.h" +#include "src/trace_redaction/populate_allow_lists.h" +#include "src/trace_redaction/prune_package_list.h" +#include "src/trace_redaction/redact_ftrace_events.h" +#include "src/trace_redaction/redact_process_events.h" +#include "src/trace_redaction/redact_process_trees.h" +#include "src/trace_redaction/scrub_process_stats.h" #include "src/trace_redaction/trace_redaction_framework.h" +#include "src/trace_redaction/verify_integrity.h" #include "protos/perfetto/trace/trace.pbzero.h" @@ -136,4 +149,126 @@ base::Status TraceRedactor::Transform( return base::OkStatus(); } +std::unique_ptr TraceRedactor::CreateInstance( + const Config& config) { + auto redactor = std::make_unique(); + + // VerifyIntegrity breaks the CollectPrimitive pattern. Instead of writing to + // the context, its job is to read trace packets and return errors if any + // packet does not look "correct". This primitive is added first in an effort + // to detect and react to bad input before other collectors run. + if (config.verify) { + redactor->emplace_collect(); + } + + // Add all collectors. + redactor->emplace_collect(); + redactor->emplace_collect(); + redactor->emplace_collect(); + redactor->emplace_collect(); + + // Add all builders. + redactor->emplace_build(); + redactor->emplace_build(); + + { + // In order for BroadphasePacketFilter to work, something needs to populate + // the masks (i.e. PopulateAllowlists). + redactor->emplace_build(); + redactor->emplace_transform(); + } + + { + auto* primitive = redactor->emplace_transform(); + primitive->emplace_ftrace_filter(); + primitive->emplace_post_filter_modifier(); + } + + { + auto* primitive = redactor->emplace_transform(); + primitive->emplace_ftrace_filter(); + primitive->emplace_post_filter_modifier(); + } + + { + // Remove all frame timeline events that don't belong to the target package. + redactor->emplace_transform(); + } + + redactor->emplace_transform(); + + // Process stats includes per-process information, such as: + // + // processes { + // pid: 1 + // vm_size_kb: 11716992 + // vm_rss_kb: 5396 + // rss_anon_kb: 2896 + // rss_file_kb: 1728 + // rss_shmem_kb: 772 + // vm_swap_kb: 4236 + // vm_locked_kb: 0 + // vm_hwm_kb: 6720 + // oom_score_adj: -1000 + // } + // + // Use the ConnectedToPackage primitive to ensure only the target package has + // stats in the trace. + { + auto* primitive = redactor->emplace_transform(); + primitive->emplace_filter(); + } + + // Redacts all switch and waking events. This should use the same modifier and + // filter as the process events (see below). + { + auto* primitive = redactor->emplace_transform(); + primitive->emplace_modifier(); + primitive->emplace_waking_filter(); + } + + // Redacts all new task, rename task, process free events. This should use the + // same modifier and filter as the schedule events (see above). + { + auto* primitive = redactor->emplace_transform(); + primitive->emplace_modifier(); + primitive->emplace_filter(); + } + + // Merge Threads (part 1): Remove all waking events that connected to the + // target package. Change the pids not connected to the target package. + { + auto* primitive = redactor->emplace_transform(); + primitive->emplace_modifier(); + primitive->emplace_waking_filter(); + } + + // Merge Threads (part 2): Drop all process events not belonging to the + // target package. No modification is needed. + { + auto* primitive = redactor->emplace_transform(); + primitive->emplace_modifier(); + primitive->emplace_filter(); + } + + // Merge Threads (part 3): Replace ftrace event's pid (not the task's pid) + // for all pids not connected to the target package. + { + auto* primitive = redactor->emplace_transform(); + primitive->emplace_post_filter_modifier(); + primitive->emplace_ftrace_filter(); + } + + // Configure the primitive to remove processes and threads that don't belong + // to the target package and adds a process and threads for the synth thread + // group and threads. + { + auto* primitive = redactor->emplace_transform(); + primitive->emplace_modifier(); + primitive->emplace_filter(); + } + + return redactor; +} + } // namespace perfetto::trace_redaction diff --git a/src/trace_redaction/trace_redactor.h b/src/trace_redaction/trace_redactor.h index 7da6b3d414..d4982ca95c 100644 --- a/src/trace_redaction/trace_redactor.h +++ b/src/trace_redaction/trace_redactor.h @@ -72,6 +72,15 @@ class TraceRedactor { return ptr; } + struct Config { + // Controls whether or not the verify primitive is added to the pipeline. + // This should always be enabled unless you know that your test content + // fails verification. + bool verify = true; + }; + + static std::unique_ptr CreateInstance(const Config& config); + private: // Run all collectors on a packet because moving to the next package. // diff --git a/src/trace_redaction/verify_integrity.cc b/src/trace_redaction/verify_integrity.cc new file mode 100644 index 0000000000..2342967e92 --- /dev/null +++ b/src/trace_redaction/verify_integrity.cc @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2024 The Android Open Source Projectf + * + * 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/verify_integrity.h" + +#include "src/trace_processor/util/status_macros.h" + +#include "protos/perfetto/common/trace_stats.pbzero.h" +#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h" +#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h" +#include "protos/perfetto/trace/trace_packet.pbzero.h" + +namespace perfetto::trace_redaction { + +base::Status VerifyIntegrity::Collect( + const protos::pbzero::TracePacket::Decoder& packet, + Context*) const { + if (!packet.has_trusted_uid()) { + return base::ErrStatus( + "VerifyIntegrity: missing field (TracePacket::kTrustedUid)."); + } + + if (packet.trusted_uid() > Context::kMaxTrustedUid) { + return base::ErrStatus("VerifyIntegrity: untrusted uid found (uid = %d).", + packet.trusted_uid()); + } + + if (packet.has_ftrace_events()) { + RETURN_IF_ERROR(OnFtraceEvents(packet.ftrace_events())); + } + + // If there is a process tree, there should be a timestamp on the packet. This + // is the only way to know when the process tree was collected. + if (packet.has_process_tree() && !packet.has_timestamp()) { + return base::ErrStatus( + "VerifyIntegrity: missing fields (TracePacket::kProcessTree + " + "TracePacket::kTimestamp)."); + } + + // If there are a process stats, there should be a timestamp on the packet. + // This is the only way to know when the stats were collected. + if (packet.has_process_stats() && !packet.has_timestamp()) { + return base::ErrStatus( + "VerifyIntegrity: missing fields (TracePacket::kProcessStats + " + "TracePacket::kTimestamp)."); + } + + if (packet.has_trace_stats()) { + RETURN_IF_ERROR(OnTraceStats(packet.trace_stats())); + } + + return base::OkStatus(); +} + +base::Status VerifyIntegrity::OnFtraceEvents( + const protozero::ConstBytes bytes) const { + protos::pbzero::FtraceEventBundle::Decoder events(bytes); + + // Any ftrace lost events should cause the trace to be dropped: + // protos/perfetto/trace/ftrace/ftrace_event_bundle.proto + if (events.has_lost_events() && events.has_lost_events()) { + return base::ErrStatus( + "VerifyIntegrity: detected FtraceEventBundle error."); + } + + // The other clocks in ftrace are only used on very old kernel versions. No + // device with V should have such an old version. As a failsafe though, + // check that the ftrace_clock field is unset to ensure no invalid + // timestamps get by. + if (events.has_ftrace_clock()) { + return base::ErrStatus( + "VerifyIntegrity: unexpected field (FtraceEventBundle::kFtraceClock)."); + } + + // Every ftrace event bundle should have a CPU field. This is necessary for + // switch/waking redaction to work. + if (!events.has_cpu()) { + return base::ErrStatus( + "VerifyIntegrity: missing field (FtraceEventBundle::kCpu)."); + } + + // Any ftrace errors should cause the trace to be dropped: + // protos/perfetto/trace/ftrace/ftrace_event_bundle.proto + if (events.has_error()) { + return base::ErrStatus("VerifyIntegrity: detected FtraceEvent errors."); + } + + for (auto it = events.event(); it; ++it) { + RETURN_IF_ERROR(OnFtraceEvent(*it)); + } + + return base::OkStatus(); +} + +base::Status VerifyIntegrity::OnFtraceEvent( + const protozero::ConstBytes bytes) const { + protos::pbzero::FtraceEvent::Decoder event(bytes); + + if (!event.has_timestamp()) { + return base::ErrStatus( + "VerifyIntegrity: missing field (FtraceEvent::kTimestamp)."); + } + + if (!event.has_pid()) { + return base::ErrStatus( + "VerifyIntegrity: missing field (FtraceEvent::kPid)."); + } + + return base::OkStatus(); +} + +base::Status VerifyIntegrity::OnTraceStats( + const protozero::ConstBytes bytes) const { + protos::pbzero::TraceStats::Decoder trace_stats(bytes); + + if (trace_stats.has_flushes_failed() && trace_stats.flushes_failed()) { + return base::ErrStatus("VerifyIntegrity: detected TraceStats flush fails."); + } + + if (trace_stats.has_final_flush_outcome() && + trace_stats.final_flush_outcome() == + protos::pbzero::TraceStats::FINAL_FLUSH_FAILED) { + return base::ErrStatus( + "VerifyIntegrity: TraceStats final_flush_outcome is " + "FINAL_FLUSH_FAILED."); + } + + for (auto it = trace_stats.buffer_stats(); it; ++it) { + RETURN_IF_ERROR(OnBufferStats(*it)); + } + + return base::OkStatus(); +} + +base::Status VerifyIntegrity::OnBufferStats( + const protozero::ConstBytes bytes) const { + protos::pbzero::TraceStats::BufferStats::Decoder stats(bytes); + + if (stats.has_patches_failed() && stats.patches_failed()) { + return base::ErrStatus( + "VerifyIntegrity: detected BufferStats patch fails."); + } + + if (stats.has_abi_violations() && stats.abi_violations()) { + return base::ErrStatus( + "VerifyIntegrity: detected BufferStats abi violations."); + } + + auto has_loss = stats.has_trace_writer_packet_loss(); + auto value = stats.trace_writer_packet_loss(); + + if (has_loss && value) { + return base::ErrStatus( + "VerifyIntegrity: detected BufferStats writer packet loss."); + } + + return base::OkStatus(); +} + +} // namespace perfetto::trace_redaction diff --git a/src/trace_redaction/verify_integrity.h b/src/trace_redaction/verify_integrity.h new file mode 100644 index 0000000000..f8236290ed --- /dev/null +++ b/src/trace_redaction/verify_integrity.h @@ -0,0 +1,46 @@ +/* + * 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/trace_redaction_framework.h" + +#include "protos/perfetto/trace/trace_packet.pbzero.h" + +#ifndef SRC_TRACE_REDACTION_VERIFY_INTEGRITY_H_ +#define SRC_TRACE_REDACTION_VERIFY_INTEGRITY_H_ + +namespace perfetto::trace_redaction { + +// This breaks the normal collect primitive pattern. Rather than collecting +// information, it looks at packets and returns an error if the packet violates +// any requirements. +class VerifyIntegrity : public CollectPrimitive { + public: + base::Status Collect(const protos::pbzero::TracePacket::Decoder& packet, + Context* context) const override; + + private: + base::Status OnFtraceEvents(const protozero::ConstBytes bytes) const; + + base::Status OnFtraceEvent(const protozero::ConstBytes bytes) const; + + base::Status OnTraceStats(const protozero::ConstBytes bytes) const; + + base::Status OnBufferStats(const protozero::ConstBytes bytes) const; +}; + +} // namespace perfetto::trace_redaction + +#endif // SRC_TRACE_REDACTION_VERIFY_INTEGRITY_H_ diff --git a/src/trace_redaction/verify_integrity_unittest.cc b/src/trace_redaction/verify_integrity_unittest.cc new file mode 100644 index 0000000000..8912f67004 --- /dev/null +++ b/src/trace_redaction/verify_integrity_unittest.cc @@ -0,0 +1,347 @@ +/* + * 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/verify_integrity.h" +#include "src/base/test/status_matchers.h" +#include "test/gtest_and_gmock.h" + +#include "protos/perfetto/common/trace_stats.gen.h" +#include "protos/perfetto/trace/ftrace/ftrace_event.gen.h" +#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.gen.h" +#include "protos/perfetto/trace/trace_packet.gen.h" + +namespace perfetto::trace_redaction { + +namespace { +// The trace packet uid must be less than or equal to 9999 (nobody). If it is +// anything else, the packet is invalid. +int32_t kValid = 1000; +int32_t kLastValid = Context::kMaxTrustedUid; +int32_t kInvalidUid = 12000; + +uint64_t kSomeTime = 1234; +uint32_t kSomePid = 7; + +uint32_t kSomeCpu = 3; +} // namespace + +class VerifyIntegrityUnitTest : public testing::Test { + protected: + base::Status Verify(const protos::gen::TracePacket& packet) { + auto packet_buffer = packet.SerializeAsString(); + protos::pbzero::TracePacket::Decoder packer_decoder(packet_buffer); + + VerifyIntegrity verify; + Context context; + return verify.Collect(packer_decoder, &context); + } +}; + +TEST_F(VerifyIntegrityUnitTest, InvalidPacketNoUid) { + protos::gen::TracePacket packet; + ASSERT_FALSE(Verify(packet).ok()); +} + +TEST_F(VerifyIntegrityUnitTest, InvalidPacketInvalidUid) { + protos::gen::TracePacket packet; + + packet.set_trusted_uid(kInvalidUid); + + ASSERT_FALSE(Verify(packet).ok()); +} + +TEST_F(VerifyIntegrityUnitTest, ValidPacketSystemUid) { + protos::gen::TracePacket packet; + + packet.set_trusted_uid(kValid); + + ASSERT_OK(Verify(packet)); +} + +TEST_F(VerifyIntegrityUnitTest, InclusiveEnd) { + protos::gen::TracePacket packet; + + packet.set_trusted_uid(kLastValid); + + ASSERT_OK(Verify(packet)); +} + +TEST_F(VerifyIntegrityUnitTest, InvalidPacketFtraceBundleHasLostEvents) { + protos::gen::TracePacket packet; + + packet.set_trusted_uid(kValid); + + packet.mutable_ftrace_events()->set_lost_events(true); + + ASSERT_FALSE(Verify(packet).ok()); +} + +TEST_F(VerifyIntegrityUnitTest, ValidPacketFtraceBundleHasNoLostEvents) { + protos::gen::TracePacket packet; + + packet.set_trusted_uid(kValid); + + packet.mutable_ftrace_events()->set_lost_events(false); + + ASSERT_FALSE(Verify(packet).ok()); +} + +TEST_F(VerifyIntegrityUnitTest, InvalidPacketFtraceBundleMissingCpu) { + protos::gen::TracePacket packet; + + packet.set_trusted_uid(kValid); + + packet.mutable_ftrace_events(); + + ASSERT_FALSE(Verify(packet).ok()); +} + +TEST_F(VerifyIntegrityUnitTest, InvalidPacketFtraceBundleHasErrors) { + protos::gen::TracePacket packet; + + packet.set_trusted_uid(kValid); + + packet.mutable_ftrace_events()->add_error(); + + ASSERT_FALSE(Verify(packet).ok()); +} + +TEST_F(VerifyIntegrityUnitTest, ValidPacketFtraceBundle) { + protos::gen::TracePacket packet; + + packet.set_trusted_uid(kValid); + + // A bundle doesn't need to have anything in it (other than cpu). + auto* ftrace_events = packet.mutable_ftrace_events(); + ftrace_events->set_cpu(kSomeCpu); + + ASSERT_OK(Verify(packet)); +} + +TEST_F(VerifyIntegrityUnitTest, InvalidPacketFtraceEventMissingPid) { + protos::gen::TracePacket packet; + + packet.set_trusted_uid(kValid); + + auto* ftrace_events = packet.mutable_ftrace_events(); + ftrace_events->set_cpu(kSomeCpu); + + // A valid event has a pid and timestamp. Add the time (but not the pid) to + // ensure the pid caused the error. + auto* event = ftrace_events->add_event(); + event->set_timestamp(kSomeTime); + + ASSERT_FALSE(Verify(packet).ok()); +} + +TEST_F(VerifyIntegrityUnitTest, InvalidPacketFtraceEventMissingTime) { + protos::gen::TracePacket packet; + + packet.set_trusted_uid(kValid); + + auto* ftrace_events = packet.mutable_ftrace_events(); + ftrace_events->set_cpu(kSomeCpu); + + // A valid event has a pid and timestamp. Add the pid (but not the time) to + // ensure the time caused the error. + auto* event = ftrace_events->add_event(); + event->set_pid(kSomePid); + + ASSERT_FALSE(Verify(packet).ok()); +} + +TEST_F(VerifyIntegrityUnitTest, ValidPacketFtraceEvent) { + protos::gen::TracePacket packet; + + packet.set_trusted_uid(kValid); + + auto* ftrace_events = packet.mutable_ftrace_events(); + ftrace_events->set_cpu(kSomeCpu); + + auto* event = ftrace_events->add_event(); + event->set_pid(kSomePid); + event->set_timestamp(kSomeTime); + + ASSERT_OK(Verify(packet)); +} + +TEST_F(VerifyIntegrityUnitTest, InvalidPacketProcessTreeMissingTime) { + protos::gen::TracePacket packet; + + packet.set_trusted_uid(kValid); + + // When the packet has a process tree, the packet must have a timestamp. + packet.mutable_process_tree(); + + ASSERT_FALSE(Verify(packet).ok()); +} + +TEST_F(VerifyIntegrityUnitTest, ValidPacketProcessTree) { + protos::gen::TracePacket packet; + + packet.set_trusted_uid(kValid); + + // When the packet has a process tree, the packet must have a timestamp. + packet.mutable_process_tree(); + packet.set_timestamp(kSomeTime); + + ASSERT_OK(Verify(packet)); +} + +TEST_F(VerifyIntegrityUnitTest, InvalidPacketProcessStatsMissingTime) { + protos::gen::TracePacket packet; + + packet.set_trusted_uid(kValid); + + // When the packet has process stats, the packet must have a timestamp. + packet.mutable_process_stats(); + + ASSERT_FALSE(Verify(packet).ok()); +} + +TEST_F(VerifyIntegrityUnitTest, InvalidPacketTraceStatsFlushFailed) { + protos::gen::TracePacket packet; + + packet.set_trusted_uid(kValid); + + packet.mutable_trace_stats()->set_flushes_failed(true); + + ASSERT_FALSE(Verify(packet).ok()); +} + +TEST_F(VerifyIntegrityUnitTest, InvalidPacketTraceStatsNoFlushFailed) { + protos::gen::TracePacket packet; + + packet.set_trusted_uid(kValid); + + packet.mutable_trace_stats()->set_flushes_failed(false); + + ASSERT_OK(Verify(packet)); +} + +TEST_F(VerifyIntegrityUnitTest, ValidPacketFinalFlushSucceeded) { + protos::gen::TracePacket packet; + + packet.set_trusted_uid(kValid); + + packet.mutable_trace_stats()->set_final_flush_outcome( + protos::gen::TraceStats::FINAL_FLUSH_SUCCEEDED); + + ASSERT_OK(Verify(packet)); +} + +TEST_F(VerifyIntegrityUnitTest, ValidPacketFinalFlushUnspecified) { + protos::gen::TracePacket packet; + + packet.set_trusted_uid(kValid); + + packet.mutable_trace_stats()->set_final_flush_outcome( + protos::gen::TraceStats::FINAL_FLUSH_UNSPECIFIED); + + ASSERT_OK(Verify(packet)); +} + +TEST_F(VerifyIntegrityUnitTest, InvalidPacketFinalFlushFailed) { + protos::gen::TracePacket packet; + + packet.set_trusted_uid(kValid); + + packet.mutable_trace_stats()->set_final_flush_outcome( + protos::gen::TraceStats::FINAL_FLUSH_FAILED); + + ASSERT_FALSE(Verify(packet).ok()); +} + +TEST_F(VerifyIntegrityUnitTest, InvalidPacketBufferStatsPatchesFailed) { + protos::gen::TracePacket packet; + + packet.set_trusted_uid(kValid); + + packet.mutable_trace_stats()->add_buffer_stats()->set_patches_failed(3); + + ASSERT_FALSE(Verify(packet).ok()); +} + +TEST_F(VerifyIntegrityUnitTest, ValidPacketBufferStatsNoPatchesFailed) { + protos::gen::TracePacket packet; + + packet.set_trusted_uid(kValid); + + packet.mutable_trace_stats()->add_buffer_stats()->set_patches_failed(0); + + ASSERT_OK(Verify(packet)); +} + +TEST_F(VerifyIntegrityUnitTest, InvalidPacketBufferStatsAbiViolation) { + protos::gen::TracePacket packet; + + packet.set_trusted_uid(kValid); + + packet.mutable_trace_stats()->add_buffer_stats()->set_abi_violations(3); + + ASSERT_FALSE(Verify(packet).ok()); +} + +TEST_F(VerifyIntegrityUnitTest, InvalidPacketBufferStatsNoAbiViolation) { + protos::gen::TracePacket packet; + + packet.set_trusted_uid(kValid); + + packet.mutable_trace_stats()->add_buffer_stats()->set_abi_violations(0); + + ASSERT_OK(Verify(packet)); +} + +TEST_F(VerifyIntegrityUnitTest, InvalidPacketBufferStatsTraceWriterPacketLoss) { + protos::gen::TracePacket packet; + + packet.set_trusted_uid(kValid); + + packet.mutable_trace_stats() + ->add_buffer_stats() + ->set_trace_writer_packet_loss(3); + + ASSERT_EQ(packet.trace_stats().buffer_stats_size(), 1); + + ASSERT_FALSE(Verify(packet).ok()); +} + +TEST_F(VerifyIntegrityUnitTest, + InvalidPacketBufferStatsNoTraceWriterPacketLoss) { + protos::gen::TracePacket packet; + + packet.set_trusted_uid(kValid); + + packet.mutable_trace_stats() + ->add_buffer_stats() + ->set_trace_writer_packet_loss(0); + + ASSERT_OK(Verify(packet)); +} + +TEST_F(VerifyIntegrityUnitTest, ValidPacketProcessStats) { + protos::gen::TracePacket packet; + + packet.set_trusted_uid(kValid); + + // When the packet has a process tree, the packet must have a timestamp. + packet.mutable_process_stats(); + packet.set_timestamp(kSomeTime); + + ASSERT_OK(Verify(packet)); +} + +} // namespace perfetto::trace_redaction diff --git a/src/traceconv/BUILD.gn b/src/traceconv/BUILD.gn index 8cde4f465f..2984dca323 100644 --- a/src/traceconv/BUILD.gn +++ b/src/traceconv/BUILD.gn @@ -75,6 +75,7 @@ static_library("libpprofbuilder") { source_set("lib") { deps = [ ":gen_cc_trace_descriptor", + ":gen_cc_winscope_descriptor", ":pprofbuilder", ":utils", "../../gn:default_deps", @@ -91,12 +92,15 @@ source_set("lib") { "../../src/trace_processor/util:descriptors", "../../src/trace_processor/util:gzip", "../../src/trace_processor/util:protozero_to_text", + "../../src/trace_processor/util:trace_type", ] sources = [ "deobfuscate_profile.cc", "deobfuscate_profile.h", "symbolize_profile.cc", "symbolize_profile.h", + "trace_to_firefox.cc", + "trace_to_firefox.h", "trace_to_hprof.cc", "trace_to_hprof.h", "trace_to_json.cc", @@ -138,7 +142,12 @@ perfetto_cc_proto_descriptor("gen_cc_trace_descriptor") { descriptor_target = "../../protos/perfetto/trace:descriptor" } -perfetto_unittest_source_set("unittests") { +perfetto_cc_proto_descriptor("gen_cc_winscope_descriptor") { + descriptor_name = "winscope.descriptor" + descriptor_target = "../../protos/perfetto/trace/android:winscope_descriptor" +} + +source_set("integrationtests") { testonly = true deps = [ ":lib", @@ -146,6 +155,14 @@ perfetto_unittest_source_set("unittests") { "../../gn:gtest_and_gmock", "../../include/perfetto/base", "../../include/perfetto/ext/base:base", + "../../protos/third_party/pprof:cpp", + "../../protos/third_party/pprof:zero", + "../../src/base:test_support", + ] + sources = [ + "pprof_reader.cc", + "pprof_reader.h", + "trace_to_pprof_integrationtest.cc", + "trace_to_text_integrationtest.cc", ] - sources = [ "trace_to_text_unittest.cc" ] } diff --git a/src/traceconv/main.cc b/src/traceconv/main.cc index d7e6ac41ae..2dc64b2f86 100644 --- a/src/traceconv/main.cc +++ b/src/traceconv/main.cc @@ -26,6 +26,7 @@ #include "perfetto/ext/base/version.h" #include "src/traceconv/deobfuscate_profile.h" #include "src/traceconv/symbolize_profile.h" +#include "src/traceconv/trace_to_firefox.h" #include "src/traceconv/trace_to_hprof.h" #include "src/traceconv/trace_to_json.h" #include "src/traceconv/trace_to_profile.h" @@ -45,22 +46,23 @@ namespace trace_to_text { namespace { int Usage(const char* argv0) { - fprintf(stderr, - "Usage: %s MODE [OPTIONS] [input file] [output file]\n" - "modes:\n" - " systrace|json|ctrace|text|profile|hprof|symbolize|deobfuscate" - "|decompress_packets\n" - "options:\n" - " [--truncate start|end]\n" - " [--full-sort]\n" - "\"profile\" mode options:\n" - " [--perf] generate a perf profile instead of a heap profile\n" - " [--no-annotations] do not suffix frame names with derived " - "annotations\n" - " [--timestamps TIMESTAMP1,TIMESTAMP2,...] generate profiles " - "only for these *specific* timestamps\n" - " [--pid PID] generate profiles only for this process id\n", - argv0); + fprintf( + stderr, + "Usage: %s MODE [OPTIONS] [input file] [output file]\n" + "modes:\n" + " systrace|json|ctrace|text|profile|hprof|symbolize|deobfuscate|firefox" + "|java_heap_profile|decompress_packets\n" + "options:\n" + " [--truncate start|end]\n" + " [--full-sort]\n" + "\"profile\" mode options:\n" + " [--perf] generate a perf profile instead of a heap profile\n" + " [--no-annotations] do not suffix frame names with derived " + "annotations\n" + " [--timestamps TIMESTAMP1,TIMESTAMP2,...] generate profiles " + "only for these *specific* timestamps\n" + " [--pid PID] generate profiles only for this process id\n", + argv0); return 1; } @@ -212,6 +214,11 @@ int Main(int argc, char** argv) { timestamps, !profile_no_annotations); } + if (format == "java_heap_profile") { + return TraceToJavaHeapProfile(input_stream, output_stream, pid, timestamps, + !profile_no_annotations); + } + if (format == "hprof") return TraceToHprof(input_stream, output_stream, pid, timestamps); @@ -221,6 +228,9 @@ int Main(int argc, char** argv) { if (format == "deobfuscate") return DeobfuscateProfile(input_stream, output_stream); + if (format == "firefox") + return TraceToFirefoxProfile(input_stream, output_stream); + if (format == "decompress_packets") return UnpackCompressedPackets(input_stream, output_stream); diff --git a/src/traceconv/pprof_builder.cc b/src/traceconv/pprof_builder.cc index d8de2dc6ae..130399cf12 100644 --- a/src/traceconv/pprof_builder.cc +++ b/src/traceconv/pprof_builder.cc @@ -27,7 +27,6 @@ #include #include #include -#include #include #include "perfetto/base/logging.h" @@ -37,13 +36,9 @@ #include "perfetto/protozero/packed_repeated_fields.h" #include "perfetto/protozero/scattered_heap_buffer.h" #include "perfetto/trace_processor/trace_processor.h" -#include "src/profiling/symbolizer/symbolize_database.h" -#include "src/profiling/symbolizer/symbolizer.h" #include "src/trace_processor/containers/string_pool.h" #include "src/traceconv/utils.h" -#include "protos/perfetto/trace/trace.pbzero.h" -#include "protos/perfetto/trace/trace_packet.pbzero.h" #include "protos/third_party/pprof/profile.pbzero.h" // Quick hint on navigating the file: @@ -461,7 +456,8 @@ class GProfileBuilder { return true; } - std::string CompleteProfile(trace_processor::TraceProcessor* tp) { + std::string CompleteProfile(trace_processor::TraceProcessor* tp, + bool write_mappings = true) { std::set seen_mappings; std::set seen_functions; @@ -469,7 +465,7 @@ class GProfileBuilder { return {}; if (!WriteFunctions(seen_functions)) return {}; - if (!WriteMappings(tp, seen_mappings)) + if (write_mappings && !WriteMappings(tp, seen_mappings)) return {}; WriteStringTable(); @@ -831,6 +827,228 @@ static bool TraceToHeapPprof(trace_processor::TraceProcessor* tp, } } // namespace heap_profile +namespace java_heap_profile { +struct View { + const char* type; + const char* unit; + const char* query; +}; + +constexpr View kJavaAllocationViews[] = { + {"Total allocation count", "count", "count"}, + {"Total allocation size", "bytes", "size"}}; + +std::string CreateHeapDumpFlameGraphQuery(const std::string& columns, + const uint64_t upid, + const uint64_t ts) { + std::string query = "SELECT " + columns + " "; + query += "FROM experimental_flamegraph("; + + const std::vector query_params = { + // The type of the profile from which the flamegraph is being generated + // Always 'graph' for Java heap graphs. + "'graph'", + // Heapdump timestamp + std::to_string(ts), + // Timestamp constraints: not relevant and always null for Java heap + // graphs. + "NULL", + // The upid of the heap graph sample + std::to_string(upid), + // The upid group: not relevant and always null for Java heap graphs + "NULL", + // A regex for focusing on a particular node in the heapgraph + "NULL"}; + + query += base::Join(query_params, ", "); + query += ")"; + + return query; +} + +bool WriteAllocations( + GProfileBuilder* builder, + const std::unordered_map>& view_values) { + for (const auto& [id, values] : view_values) { + protozero::PackedVarInt sample_values; + for (const int64_t value : values) { + sample_values.Append(value); + } + if (!builder->AddSample(sample_values, id)) { + return false; + } + } + return true; +} + +// Extracts and interns the unique locations from the heap dump SQL tables. +// +// It uses experimental_flamegraph table to get normalized representation of +// the heap graph as a tree, which always takes the shortest path to the root. +// +// Approach: +// * First we iterate over all heap dump flamegraph rows and create a map +// of flamegraph item id -> flamegraph item parent_id, each flamechart +// item is converted to a Location where we populate Function name using +// the name of the class (as opposed to using actual call function as +// allocation call stack is not available for java heap dumps). +// Also populate view_values straightaway here to not iterate over the data +// again in the future. +// * For each location we iterate over all its parents until we find +// the root and use this list of locations as a 'callstack' (which is +// actually list of class names) +LocationTracker PreprocessLocationsForJavaHeap( + trace_processor::TraceProcessor* tp, + trace_processor::StringPool* interner, + const std::vector& views, + std::unordered_map>& view_values_out, + uint64_t upid, + uint64_t ts) { + LocationTracker tracker; + + std::string columns; + for (const auto& view : views) { + columns += std::string(view.query) + ", "; + } + + const auto data_columns_count = static_cast(views.size()); + columns += "id, parent_id, name"; + + const std::string query = CreateHeapDumpFlameGraphQuery(columns, upid, ts); + Iterator it = tp->ExecuteQuery(query); + + // flamegraph id -> flamegraph parent_id + std::unordered_map parents; + // flamegraph id -> interned location id + std::unordered_map interned_ids; + + // Create locations + while (it.Next()) { + const int64_t id = it.Get(data_columns_count).AsLong(); + + const int64_t parent_id = it.Get(data_columns_count + 1).is_null() + ? -1 + : it.Get(data_columns_count + 1).AsLong(); + + auto name = it.Get(data_columns_count + 2).is_null() + ? "" + : it.Get(data_columns_count + 2).AsString(); + + parents.emplace(id, parent_id); + + StringId func_name_id = interner->InternString(name); + Function func(func_name_id, StringId::Null(), StringId::Null()); + auto interned_function_id = tracker.InternFunction(func); + + Location loc(/*map=*/0, /*func=*/interned_function_id, /*inlines=*/{}); + auto interned_location_id = tracker.InternLocation(std::move(loc)); + + interned_ids.emplace(id, interned_location_id); + + std::vector view_values_vector; + for (uint32_t i = 0; i < views.size(); ++i) { + view_values_vector.push_back(it.Get(i).AsLong()); + } + + view_values_out.emplace(id, view_values_vector); + } + + if (!it.Status().ok()) { + PERFETTO_DFATAL_OR_ELOG("Invalid iterator: %s", + it.Status().message().c_str()); + return {}; + } + + // Iterate over all known locations again and build root-first paths + // for every location + for (auto& parent : parents) { + std::vector path; + + int64_t current_parent_id = parent.first; + while (current_parent_id != -1) { + auto id_it = interned_ids.find(current_parent_id); + PERFETTO_CHECK(id_it != interned_ids.end()); + + auto parent_location_id = id_it->second; + path.push_back(parent_location_id); + + // Find parent of the parent + auto parent_id_it = parents.find(current_parent_id); + PERFETTO_CHECK(parent_id_it != parents.end()); + + current_parent_id = parent_id_it->second; + } + + // Reverse to make it root-first list + std::reverse(path.begin(), path.end()); + + tracker.MaybeSetCallsiteLocations(parent.first, path); + } + + return tracker; +} + +bool TraceToHeapPprof(trace_processor::TraceProcessor* tp, + std::vector* output, + uint64_t target_pid, + const std::vector& target_timestamps) { + trace_processor::StringPool interner; + + // Find all heap graphs available in the trace and iterate over them + Iterator it = tp->ExecuteQuery( + "select distinct hgo.graph_sample_ts, hgo.upid, p.pid from " + "heap_graph_object hgo join process p using (upid)"); + + while (it.Next()) { + uint64_t ts = static_cast(it.Get(0).AsLong()); + uint64_t upid = static_cast(it.Get(1).AsLong()); + uint64_t profile_pid = static_cast(it.Get(2).AsLong()); + + if ((target_pid > 0 && profile_pid != target_pid) || + (!target_timestamps.empty() && + std::find(target_timestamps.begin(), target_timestamps.end(), ts) == + target_timestamps.end())) { + continue; + } + + // flamegraph id -> view values + std::unordered_map> view_values; + + std::vector views; + views.assign(std::begin(kJavaAllocationViews), + std::end(kJavaAllocationViews)); + + LocationTracker locations = PreprocessLocationsForJavaHeap( + tp, &interner, views, view_values, upid, ts); + + GProfileBuilder builder(locations, &interner); + + std::vector> sample_types; + for (const auto& view : views) { + sample_types.emplace_back(view.type, view.unit); + } + builder.WriteSampleTypes(sample_types); + + std::string profile_proto; + if (WriteAllocations(&builder, view_values)) { + profile_proto = builder.CompleteProfile(tp, /*write_mappings=*/false); + } + + output->emplace_back(SerializedProfile{ProfileType::kJavaHeapProfile, + profile_pid, + std::move(profile_proto), ""}); + } + + if (!it.Status().ok()) { + PERFETTO_DFATAL_OR_ELOG("Invalid iterator: %s", + it.Status().message().c_str()); + return false; + } + + return true; +} +} // namespace java_heap_profile + namespace perf_profile { struct ProcessInfo { uint64_t pid; @@ -971,6 +1189,8 @@ bool TraceToPprof(trace_processor::TraceProcessor* tp, timestamps); case (ConversionMode::kPerfProfile): return perf_profile::TraceToPerfPprof(tp, output, annotate_frames, pid); + case (ConversionMode::kJavaHeapProfile): + return java_heap_profile::TraceToHeapPprof(tp, output, pid, timestamps); } PERFETTO_FATAL("unknown conversion option"); // for gcc } diff --git a/src/traceconv/pprof_reader.cc b/src/traceconv/pprof_reader.cc new file mode 100644 index 0000000000..f14cca7377 --- /dev/null +++ b/src/traceconv/pprof_reader.cc @@ -0,0 +1,181 @@ +/* + * 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/traceconv/pprof_reader.h" + +#include +#include + +#include "perfetto/ext/base/file_utils.h" + +namespace perfetto::pprof { + +using namespace third_party::perftools::profiles::gen; + +PprofProfileReader::PprofProfileReader(const std::string& path) { + std::string pprof_contents; + base::ReadFile(path, &pprof_contents); + profile_.ParseFromString(pprof_contents); +} + +uint64_t PprofProfileReader::get_sample_count() const { + return static_cast(profile_.sample_size()); +} + +int64_t PprofProfileReader::get_string_index(const std::string& str) const { + const auto it = std::find(profile_.string_table().begin(), + profile_.string_table().end(), str); + + if (it == profile_.string_table().end()) { + PERFETTO_FATAL("String %s not found in string table", str.c_str()); + } + + return std::distance(profile_.string_table().begin(), it); +} + +std::string PprofProfileReader::get_string_by_index( + const uint64_t string_index) const { + if (string_index >= profile_.string_table().size()) { + PERFETTO_FATAL("String %" PRIu64 " is out of range in string table", + string_index); + } + + return profile_.string_table()[string_index]; +} + +uint64_t PprofProfileReader::find_location_id( + const std::string& function_name) const { + const int64_t function_string_id = get_string_index(function_name); + + // Find a function based on function_name + uint64_t function_id = 0; + bool found_function_id = false; + + for (const auto& function : profile_.function()) { + if (function.name() == function_string_id) { + function_id = function.id(); + found_function_id = true; + } + } + + if (!found_function_id) { + PERFETTO_FATAL("Function %s not found", function_name.c_str()); + } + + // Find a location for the function + for (const auto& location : profile_.location()) { + for (const auto& line : location.line()) { + if (line.function_id() == function_id) { + return location.id(); + } + } + } + + PERFETTO_FATAL("Location for function %s not found", function_name.c_str()); +} + +Location PprofProfileReader::find_location(const uint64_t location_id) const { + const auto it = std::find_if( + profile_.location().begin(), profile_.location().end(), + [location_id](const Location& loc) { return loc.id() == location_id; }); + + if (it != profile_.location().end()) { + return *it; + } + + PERFETTO_FATAL("Location with id %" PRIu64 " not found", location_id); +} + +Function PprofProfileReader::find_function(const uint64_t function_id) const { + const auto it = std::find_if( + profile_.function().begin(), profile_.function().end(), + [function_id](const Function& fun) { return fun.id() == function_id; }); + + if (it != profile_.function().end()) { + return *it; + } + + PERFETTO_FATAL("Function with id %" PRIu64 " not found", function_id); +} + +std::vector PprofProfileReader::get_sample_function_names( + const Sample& sample) const { + std::vector function_names; + for (const auto location_id : sample.location_id()) { + const auto location = find_location(location_id); + + for (const auto& line : location.line()) { + Function function = find_function(line.function_id()); + std::string function_name = + get_string_by_index(static_cast(function.name())); + function_names.push_back(function_name); + } + } + + return function_names; +} + +std::vector PprofProfileReader::get_samples( + const std::string& last_function_name) const { + const uint64_t location_id = find_location_id(last_function_name); + + std::vector samples; + for (const auto& sample : profile_.sample()) { + if (sample.location_id_size() == 0) { + continue; + } + + // Get the first location id from the iterator as they are stored inverted + const uint64_t last_location_id = sample.location_id()[0]; + + if (last_location_id == location_id) { + samples.push_back(sample); + } + } + + return samples; +} + +uint64_t PprofProfileReader::get_sample_value_index( + const std::string& value_name) const { + const int64_t value_name_string_index = get_string_index(value_name); + + const auto it = + std::find_if(profile_.sample_type().begin(), profile_.sample_type().end(), + [value_name_string_index](const auto& sample_type) { + return sample_type.type() == value_name_string_index; + }); + + if (it != profile_.sample_type().end()) { + return static_cast( + std::distance(profile_.sample_type().begin(), it)); + } + + PERFETTO_FATAL("Can't find value type with name \"%s\"", value_name.c_str()); +} + +int64_t PprofProfileReader::get_samples_value_sum( + const std::string& last_function_name, + const std::string& value_name) const { + int64_t total = 0; + const auto samples = get_samples(last_function_name); + const auto value_index = get_sample_value_index(value_name); + for (const auto& sample : samples) { + total += sample.value()[value_index]; + } + return total; +} +} // namespace perfetto::pprof diff --git a/src/traceconv/pprof_reader.h b/src/traceconv/pprof_reader.h new file mode 100644 index 0000000000..75f3cd4b7f --- /dev/null +++ b/src/traceconv/pprof_reader.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SRC_TRACECONV_PPROF_READER_H_ +#define SRC_TRACECONV_PPROF_READER_H_ + +#include + +#include "protos/third_party/pprof/profile.gen.h" + +namespace perfetto { +namespace pprof { + +class PprofProfileReader { + public: + explicit PprofProfileReader(const std::string& path); + + uint64_t get_sample_count() const; + + std::string get_string_by_index(uint64_t string_index) const; + + int64_t get_string_index(const std::string& str) const; + + uint64_t find_location_id(const std::string& function_name) const; + + std::vector get_sample_function_names( + const third_party::perftools::profiles::gen::Sample& sample) const; + + // Finds all samples from the profile where its location equals to the passed + // function name and returns them. It looks for the last (the most specific) + // function name to be equal to last_function_name + std::vector get_samples( + const std::string& last_function_name) const; + + third_party::perftools::profiles::gen::Location find_location( + const uint64_t location_id) const; + + third_party::perftools::profiles::gen::Function find_function( + const uint64_t function_id) const; + + uint64_t get_sample_value_index(const std::string& value_name) const; + + int64_t get_samples_value_sum(const std::string& last_function_name, + const std::string& value_name) const; + + private: + third_party::perftools::profiles::gen::Profile profile_; +}; + +} // namespace pprof +} // namespace perfetto + +#endif // SRC_TRACECONV_PPROF_READER_H_ diff --git a/src/traceconv/symbolize_profile.cc b/src/traceconv/symbolize_profile.cc index 00cc7b967e..4efaa107ea 100644 --- a/src/traceconv/symbolize_profile.cc +++ b/src/traceconv/symbolize_profile.cc @@ -55,12 +55,12 @@ int SymbolizeProfile(std::istream* input, std::ostream* output) { PERFETTO_FATAL("Failed to read trace."); tp->Flush(); + tp->NotifyEndOfFile(); SymbolizeDatabase( tp.get(), symbolizer.get(), [output](const std::string& trace_proto) { *output << trace_proto; }); - tp->NotifyEndOfFile(); return 0; } diff --git a/src/traceconv/trace_to_firefox.cc b/src/traceconv/trace_to_firefox.cc new file mode 100644 index 0000000000..976cf75432 --- /dev/null +++ b/src/traceconv/trace_to_firefox.cc @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "src/traceconv/trace_to_firefox.h" + +#include +#include +#include +#include + +#include "perfetto/base/logging.h" +#include "perfetto/trace_processor/basic_types.h" +#include "perfetto/trace_processor/trace_processor.h" +#include "src/traceconv/utils.h" + +namespace perfetto { +namespace trace_to_text { +namespace { + +void ExportFirefoxProfile(trace_processor::TraceProcessor& tp, + std::ostream* output) { + auto it = tp.ExecuteQuery(R"( + INCLUDE PERFETTO MODULE export.to_firefox_profile; + SELECT CAST(export_to_firefox_profile() AS BLOB); + )"); + PERFETTO_CHECK(it.Next()); + + it.Get(0).AsBytes(); + output->write(reinterpret_cast(it.Get(0).AsBytes()), + static_cast(it.Get(0).bytes_count)); + + PERFETTO_CHECK(!it.Next()); + PERFETTO_CHECK(it.Status().ok()); +} + +std::unique_ptr LoadTrace( + std::istream* input) { + trace_processor::Config config; + std::unique_ptr tp = + trace_processor::TraceProcessor::CreateInstance(config); + + if (!ReadTraceUnfinalized(tp.get(), input)) { + return nullptr; + } + tp->NotifyEndOfFile(); + return tp; +} + +} // namespace + +bool TraceToFirefoxProfile(std::istream* input, std::ostream* output) { + auto tp = LoadTrace(input); + if (!tp) { + return false; + } + ExportFirefoxProfile(*tp, output); + return true; +} + +} // namespace trace_to_text +} // namespace perfetto diff --git a/src/traceconv/trace_to_firefox.h b/src/traceconv/trace_to_firefox.h new file mode 100644 index 0000000000..13f419a501 --- /dev/null +++ b/src/traceconv/trace_to_firefox.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SRC_TRACECONV_TRACE_TO_FIREFOX_H_ +#define SRC_TRACECONV_TRACE_TO_FIREFOX_H_ + +#include + +namespace perfetto { +namespace trace_to_text { + +// Exports trace as as Firefox Profile. More details here: +// https://firefox-source-docs.mozilla.org/tools/profiler/code-overview.html +// https://github.com/firefox-devtools/profiler/blob/main/src/types/profile.js +bool TraceToFirefoxProfile(std::istream* input, std::ostream* output); + +} // namespace trace_to_text +} // namespace perfetto + +#endif // SRC_TRACECONV_TRACE_TO_FIREFOX_H_ diff --git a/src/traceconv/trace_to_pprof_integrationtest.cc b/src/traceconv/trace_to_pprof_integrationtest.cc new file mode 100644 index 0000000000..b85285461e --- /dev/null +++ b/src/traceconv/trace_to_pprof_integrationtest.cc @@ -0,0 +1,174 @@ +/* + * 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 "test/gtest_and_gmock.h" + +#include + +#include "perfetto/base/logging.h" +#include "perfetto/ext/base/file_utils.h" +#include "perfetto/ext/base/string_utils.h" +#include "src/base/test/utils.h" +#include "src/traceconv/pprof_reader.h" +#include "src/traceconv/trace_to_profile.h" + +namespace perfetto { +namespace { + +using testing::Contains; + +pprof::PprofProfileReader convert_trace_to_pprof( + const std::string& input_file_name) { + const std::string trace_file = base::GetTestDataPath(input_file_name); + std::ifstream file_istream; + file_istream.open(trace_file, std::ios_base::in | std::ios_base::binary); + PERFETTO_CHECK(file_istream.is_open()); + + std::stringstream ss; + std::ostream os(ss.rdbuf()); + trace_to_text::TraceToJavaHeapProfile(&file_istream, &os, /*pid=*/0, + /*timestamps=*/{}, + /*annotate_frames=*/false); + + auto conv_stdout = base::SplitString(ss.str(), " "); + PERFETTO_CHECK(!conv_stdout.empty()); + std::string out_dirname = base::TrimWhitespace(conv_stdout.back()); + std::vector filenames; + base::ListFilesRecursive(out_dirname, filenames); + // assumption: all test inputs contain exactly one profile + PERFETTO_CHECK(filenames.size() == 1); + std::string profile_path = out_dirname + "/" + filenames[0]; + + // read in the profile contents and then clean up the temp files + pprof::PprofProfileReader pprof_reader(profile_path); + unlink(profile_path.c_str()); + PERFETTO_CHECK(base::Rmdir(out_dirname)); + return pprof_reader; +} + +std::vector> get_samples_function_names( + const pprof::PprofProfileReader& pprof, + const std::string& last_function_name) { + const auto samples = pprof.get_samples(last_function_name); + std::vector> samples_function_names; + for (const auto& sample : samples) { + samples_function_names.push_back(pprof.get_sample_function_names(sample)); + } + return samples_function_names; +} + +class TraceToPprofTest : public ::testing::Test { + public: + pprof::PprofProfileReader* pprof = nullptr; + + void SetUp() override { +#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) + GTEST_SKIP() << "do not run traceconv tests on Android target"; +#endif + } + + void TearDown() override { delete pprof; } +}; + +TEST_F(TraceToPprofTest, SummaryValues) { + const auto pprof = + convert_trace_to_pprof("test/data/heap_graph/heap_graph.pb"); + + EXPECT_EQ(pprof.get_samples_value_sum("Foo", "Total allocation count"), 1); + EXPECT_EQ(pprof.get_samples_value_sum("Foo", "Total allocation size"), 32); + EXPECT_EQ(pprof.get_samples("Foo").size(), 1U); + EXPECT_EQ(pprof.get_sample_count(), 3U); + + const std::vector expected_function_names = { + "Foo", "FactoryProducerDelegateImplActor [ROOT_JAVA_FRAME]"}; + EXPECT_THAT(get_samples_function_names(pprof, "Foo"), + Contains(expected_function_names)); +} + +TEST_F(TraceToPprofTest, TreeLocationFunctionNames) { + const auto pprof = + convert_trace_to_pprof("test/data/heap_graph/heap_graph_branching.pb"); + + EXPECT_THAT(get_samples_function_names(pprof, "LeftChild0"), + Contains(std::vector{"LeftChild0", + "RootNode [ROOT_JAVA_FRAME]"})); + EXPECT_THAT(get_samples_function_names(pprof, "LeftChild1"), + Contains(std::vector{"LeftChild1", "LeftChild0", + "RootNode [ROOT_JAVA_FRAME]"})); + EXPECT_THAT(get_samples_function_names(pprof, "RightChild0"), + Contains(std::vector{"RightChild0", + "RootNode [ROOT_JAVA_FRAME]"})); + EXPECT_THAT(get_samples_function_names(pprof, "RightChild1"), + Contains(std::vector{"RightChild1", "RightChild0", + "RootNode [ROOT_JAVA_FRAME]"})); +} + +TEST_F(TraceToPprofTest, HugeSizes) { + const auto pprof = + convert_trace_to_pprof("test/data/heap_graph/heap_graph_huge_size.pb"); + EXPECT_EQ(pprof.get_samples_value_sum("dev.perfetto.BigStuff", + "Total allocation size"), + 3000000000); +} + +class TraceToPprofRealTraceTest : public ::testing::Test { + public: + void SetUp() override { +#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) + GTEST_SKIP() << "do not run traceconv tests on Android target"; +#endif +#if defined(LEAK_SANITIZER) + GTEST_SKIP() << "trace is too big to be tested in sanitizer builds"; +#endif + } +}; + +TEST_F(TraceToPprofRealTraceTest, AllocationCountForClass) { + const auto pprof = + convert_trace_to_pprof("test/data/system-server-heap-graph-new.pftrace"); + + EXPECT_EQ(pprof.get_samples_value_sum( + "android.content.pm.parsing.component.ParsedActivity", + "Total allocation count"), + 5108); + EXPECT_EQ(pprof.get_samples_value_sum( + "android.content.pm.parsing.component.ParsedActivity", + "Total allocation size"), + 817280); + EXPECT_EQ( + pprof.get_samples("android.content.pm.parsing.component.ParsedActivity") + .size(), + 5U); + EXPECT_EQ(pprof.get_sample_count(), 83256U); + + const std::vector expected_function_names = { + "android.content.pm.parsing.component.ParsedActivity", + "java.lang.Object[]", + "java.util.ArrayList", + "com.android.server.pm.parsing.pkg.PackageImpl", + "com.android.server.pm.PackageSetting", + "java.lang.Object[]", + "android.util.ArrayMap", + "com.android.server.pm.Settings", + "com.android.server.pm.PackageManagerService [ROOT_JNI_GLOBAL]"}; + + EXPECT_THAT(get_samples_function_names( + pprof, "android.content.pm.parsing.component.ParsedActivity"), + Contains(expected_function_names)); +} + +} // namespace +} // namespace perfetto diff --git a/src/traceconv/trace_to_profile.cc b/src/traceconv/trace_to_profile.cc index 6211f0259c..b18844bdc5 100644 --- a/src/traceconv/trace_to_profile.cc +++ b/src/traceconv/trace_to_profile.cc @@ -168,5 +168,20 @@ int TraceToPerfProfile(std::istream* input, ToConversionFlags(annotate_frames), "perf_profile-", filename_fn); } +int TraceToJavaHeapProfile(std::istream* input, + std::ostream* output, + const uint64_t pid, + const std::vector& timestamps, + const bool annotate_frames) { + int file_idx = 0; + auto filename_fn = [&file_idx](const SerializedProfile& profile) { + return "java_heap_dump." + std::to_string(++file_idx) + "." + + std::to_string(profile.pid) + ".pb"; + }; + + return TraceToProfile( + input, output, pid, timestamps, ConversionMode::kJavaHeapProfile, + ToConversionFlags(annotate_frames), "heap_profile-", filename_fn); +} } // namespace trace_to_text } // namespace perfetto diff --git a/src/traceconv/trace_to_profile.h b/src/traceconv/trace_to_profile.h index 0991945b76..a7e6081deb 100644 --- a/src/traceconv/trace_to_profile.h +++ b/src/traceconv/trace_to_profile.h @@ -38,6 +38,13 @@ int TraceToPerfProfile(std::istream* input, std::vector timestamps, bool annotate_frames); +// 0: success +int TraceToJavaHeapProfile(std::istream* input, + std::ostream* output, + uint64_t pid, + const std::vector& timestamps, + bool annotate_frames); + } // namespace trace_to_text } // namespace perfetto diff --git a/src/traceconv/trace_to_text.cc b/src/traceconv/trace_to_text.cc index ef7e03a28d..80c550d4e3 100644 --- a/src/traceconv/trace_to_text.cc +++ b/src/traceconv/trace_to_text.cc @@ -21,15 +21,16 @@ #include "perfetto/ext/base/scoped_file.h" #include "perfetto/ext/protozero/proto_ring_buffer.h" #include "src/traceconv/trace.descriptor.h" +#include "src/traceconv/winscope.descriptor.h" #include "src/traceconv/utils.h" #include "protos/perfetto/trace/trace.pbzero.h" #include "protos/perfetto/trace/trace_packet.pbzero.h" -#include "src/trace_processor/forwarding_trace_parser.h" #include "src/trace_processor/util/descriptors.h" #include "src/trace_processor/util/gzip_utils.h" #include "src/trace_processor/util/protozero_to_text.h" +#include "src/trace_processor/util/trace_type.h" namespace perfetto { namespace trace_to_text { @@ -53,6 +54,8 @@ class OnlineTraceToText { OnlineTraceToText(std::ostream* output) : output_(output) { pool_.AddFromFileDescriptorSet(kTraceDescriptor.data(), kTraceDescriptor.size()); + pool_.AddFromFileDescriptorSet(kWinscopeDescriptor.data(), + kWinscopeDescriptor.size()); } OnlineTraceToText(const OnlineTraceToText&) = delete; OnlineTraceToText& operator=(const OnlineTraceToText&) = delete; diff --git a/src/traceconv/trace_to_text_unittest.cc b/src/traceconv/trace_to_text_integrationtest.cc similarity index 100% rename from src/traceconv/trace_to_text_unittest.cc rename to src/traceconv/trace_to_text_integrationtest.cc diff --git a/src/traced/probes/ftrace/OWNERS b/src/traced/probes/ftrace/OWNERS index 52d5e506c2..015bd493cb 100644 --- a/src/traced/probes/ftrace/OWNERS +++ b/src/traced/probes/ftrace/OWNERS @@ -1,4 +1,3 @@ # People knowledgeable with traced_probes <> ftrace integration. primiano@google.com rsavitski@google.com -skyostil@google.com diff --git a/src/traced/probes/ftrace/atrace_wrapper.cc b/src/traced/probes/ftrace/atrace_wrapper.cc index 38f0200cc2..b4085f9524 100644 --- a/src/traced/probes/ftrace/atrace_wrapper.cc +++ b/src/traced/probes/ftrace/atrace_wrapper.cc @@ -169,6 +169,17 @@ bool ExecvAtrace(const std::vector& args, } #endif +#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) && \ + !PERFETTO_BUILDFLAG(PERFETTO_ANDROID_BUILD) +bool IsSdkGreaterOrEqualThan(uint32_t val) { + std::string str_value = base::GetAndroidProp("ro.build.version.sdk"); + if (str_value.empty()) + return true; + auto opt_value = base::CStringToUInt32(str_value.c_str()); + return !opt_value.has_value() || *opt_value >= val; +} +#endif + } // namespace AtraceWrapper::~AtraceWrapper() = default; @@ -191,11 +202,18 @@ bool AtraceWrapperImpl::SupportsUserspaceOnly() { #if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) && \ !PERFETTO_BUILDFLAG(PERFETTO_ANDROID_BUILD) // Sideloaded case. We could be sideloaded on a modern device or an older one. - std::string str_value = base::GetAndroidProp("ro.build.version.sdk"); - if (str_value.empty()) - return true; - auto opt_value = base::CStringToUInt32(str_value.c_str()); - return !opt_value.has_value() || *opt_value >= 28; // 28 == Android P. + return IsSdkGreaterOrEqualThan(28); // 28 == Android P. +#else + // In in-tree builds we know that atrace is current, no runtime checks needed. + return true; +#endif +} + +bool AtraceWrapperImpl::SupportsPreferSdk() { +#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) && \ + !PERFETTO_BUILDFLAG(PERFETTO_ANDROID_BUILD) + // Sideloaded case. We could be sideloaded on a modern device or an older one. + return IsSdkGreaterOrEqualThan(36); // 35 == Android V. #else // In in-tree builds we know that atrace is current, no runtime checks needed. return true; diff --git a/src/traced/probes/ftrace/atrace_wrapper.h b/src/traced/probes/ftrace/atrace_wrapper.h index 18c3d2d751..998b6c116d 100644 --- a/src/traced/probes/ftrace/atrace_wrapper.h +++ b/src/traced/probes/ftrace/atrace_wrapper.h @@ -31,6 +31,7 @@ class AtraceWrapper { // poke at ftrace. // - Suppress the checks for "somebody else enabled ftrace unexpectedly". virtual bool SupportsUserspaceOnly() = 0; + virtual bool SupportsPreferSdk() = 0; virtual bool RunAtrace(const std::vector& args, std::string* atrace_errors) = 0; }; @@ -39,6 +40,7 @@ class AtraceWrapperImpl : public AtraceWrapper { public: ~AtraceWrapperImpl() override; bool SupportsUserspaceOnly() override; + bool SupportsPreferSdk() override; bool RunAtrace(const std::vector& args, std::string* atrace_errors) override; }; diff --git a/src/traced/probes/ftrace/cpu_reader.cc b/src/traced/probes/ftrace/cpu_reader.cc index 66b9b1b431..34c407808b 100644 --- a/src/traced/probes/ftrace/cpu_reader.cc +++ b/src/traced/probes/ftrace/cpu_reader.cc @@ -697,9 +697,9 @@ protos::pbzero::FtraceParseStatus CpuReader::ParsePagePayload( } last_data_record_ts = timestamp; ptr = next; // jump to next event - } // default case - } // switch (event_header.type_or_length) - } // while (ptr < end) + } // default case + } // switch (event_header.type_or_length) + } // while (ptr < end) if (last_data_record_ts) *last_read_event_ts = last_data_record_ts; return FtraceParseStatus::FTRACE_STATUS_OK; @@ -869,6 +869,9 @@ bool CpuReader::ParseField(const Field& field, case kDevId64ToUint64: ReadDevId(field_start, field_id, message, metadata); return true; + case kFtraceSymAddr32ToUint64: + ReadSymbolAddr(field_start, field_id, message, metadata); + return true; case kFtraceSymAddr64ToUint64: ReadSymbolAddr(field_start, field_id, message, metadata); return true; diff --git a/src/traced/probes/ftrace/cpu_reader_benchmark.cc b/src/traced/probes/ftrace/cpu_reader_benchmark.cc index ae5de7d2ab..1797936574 100644 --- a/src/traced/probes/ftrace/cpu_reader_benchmark.cc +++ b/src/traced/probes/ftrace/cpu_reader_benchmark.cc @@ -851,6 +851,7 @@ void DoParse(const ExamplePage& test_case, std::nullopt, {}, {}, + {}, false /*symbolize_ksyms*/, false /*preserve_ftrace_buffer*/, {}}; @@ -954,6 +955,7 @@ void DoProcessPages(const ExamplePage& test_case, std::nullopt, {}, {}, + {}, false /*symbolize_ksyms*/, false /*preserve_ftrace_buffer*/, {}}; diff --git a/src/traced/probes/ftrace/cpu_reader_fuzzer.cc b/src/traced/probes/ftrace/cpu_reader_fuzzer.cc index f92aab9691..31181bcd24 100644 --- a/src/traced/probes/ftrace/cpu_reader_fuzzer.cc +++ b/src/traced/probes/ftrace/cpu_reader_fuzzer.cc @@ -56,6 +56,7 @@ void FuzzCpuReaderProcessPagesForDataSource(const uint8_t* data, size_t size) { /*print_filter=*/std::nullopt, /*atrace_apps=*/{}, /*atrace_categories=*/{}, + /*atrace_categories_prefer_track_event=*/{}, /*symbolize_ksyms=*/false, /*preserve_ftrace_buffer=*/false, /*syscalls_returning_fd=*/{}}; diff --git a/src/traced/probes/ftrace/cpu_reader_unittest.cc b/src/traced/probes/ftrace/cpu_reader_unittest.cc index 02d2962fd0..fb653cb073 100644 --- a/src/traced/probes/ftrace/cpu_reader_unittest.cc +++ b/src/traced/probes/ftrace/cpu_reader_unittest.cc @@ -80,6 +80,7 @@ FtraceDataSourceConfig EmptyConfig() { std::nullopt, {}, {}, + {}, false /*symbolize_ksyms*/, 50u, {}}; @@ -1048,6 +1049,7 @@ TEST_F(CpuReaderParsePagePayloadTest, ParseSixSchedSwitchCompactFormat) { std::nullopt, {}, {}, + {}, false /* symbolize_ksyms*/, false /*preserve_ftrace_buffer*/, {}}; @@ -1163,6 +1165,7 @@ TEST_F(CpuReaderParsePagePayloadTest, ParseCompactSchedSwitchAndWaking) { std::nullopt, {}, {}, + {}, false /* symbolize_ksyms*/, false /*preserve_ftrace_buffer*/, {}}; @@ -2459,7 +2462,7 @@ TEST_F(CpuReaderParsePagePayloadTest, ParseFullPageSchedSwitch) { // <...>-9290 [000] .... 1352.724574: suspend_resume: thaw_processes[0] begin // clang-format on -static ExamplePage g_suspend_resume { +static ExamplePage g_suspend_resume{ "synthetic", R"(00000000: edba 155a 3201 0000 7401 0000 0000 0000 ...Z2...t....... 00000010: 7e58 22cd 1201 0000 0600 0000 ac00 0000 ~X"............. diff --git a/src/traced/probes/ftrace/event_info.cc b/src/traced/probes/ftrace/event_info.cc index 61af03d8ad..7a5d79303d 100644 --- a/src/traced/probes/ftrace/event_info.cc +++ b/src/traced/probes/ftrace/event_info.cc @@ -157,6 +157,40 @@ std::vector GetStaticEventInfo() { kUnsetFtraceId, 436, kUnsetSize}, + {"bcl_irq_trigger", + "bcl_exynos", + { + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "id", 1, ProtoSchemaType::kInt32, + TranslationStrategy::kInvalidTranslationStrategy}, + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "throttle", 2, ProtoSchemaType::kInt32, + TranslationStrategy::kInvalidTranslationStrategy}, + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "cpu0_limit", 3, ProtoSchemaType::kInt32, + TranslationStrategy::kInvalidTranslationStrategy}, + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "cpu1_limit", 4, ProtoSchemaType::kInt32, + TranslationStrategy::kInvalidTranslationStrategy}, + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "cpu2_limit", 5, ProtoSchemaType::kInt32, + TranslationStrategy::kInvalidTranslationStrategy}, + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "tpu_limit", 6, ProtoSchemaType::kInt32, + TranslationStrategy::kInvalidTranslationStrategy}, + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "gpu_limit", 7, ProtoSchemaType::kInt32, + TranslationStrategy::kInvalidTranslationStrategy}, + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "voltage", 8, ProtoSchemaType::kInt32, + TranslationStrategy::kInvalidTranslationStrategy}, + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "capacity", 9, ProtoSchemaType::kInt32, + TranslationStrategy::kInvalidTranslationStrategy}, + }, + kUnsetFtraceId, + 533, + kUnsetSize}, {"binder_transaction", "binder", { @@ -1380,6 +1414,19 @@ std::vector GetStaticEventInfo() { kUnsetFtraceId, 364, kUnsetSize}, + {"dcvsh_freq", + "dcvsh", + { + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "cpu", 1, ProtoSchemaType::kUint64, + TranslationStrategy::kInvalidTranslationStrategy}, + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "freq", 2, ProtoSchemaType::kUint64, + TranslationStrategy::kInvalidTranslationStrategy}, + }, + kUnsetFtraceId, + 508, + kUnsetSize}, {"dma_fence_init", "dma_fence", { @@ -5463,6 +5510,19 @@ std::vector GetStaticEventInfo() { kUnsetFtraceId, 37, kUnsetSize}, + {"gpu_frequency", + "kgsl", + { + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "gpu_freq", 1, ProtoSchemaType::kUint32, + TranslationStrategy::kInvalidTranslationStrategy}, + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "gpu_id", 2, ProtoSchemaType::kUint32, + TranslationStrategy::kInvalidTranslationStrategy}, + }, + kUnsetFtraceId, + 509, + kUnsetSize}, {"alloc_pages_iommu_end", "kmem", { @@ -6961,6 +7021,374 @@ std::vector GetStaticEventInfo() { kUnsetFtraceId, 483, kUnsetSize}, + {"mali_PM_MCU_HCTL_CORES_DOWN_SCALE_NOTIFY_PEND", + "mali", + { + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "kctx_tgid", 1, ProtoSchemaType::kInt32, + TranslationStrategy::kInvalidTranslationStrategy}, + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "kctx_id", 2, ProtoSchemaType::kUint32, + TranslationStrategy::kInvalidTranslationStrategy}, + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "info_val", 3, ProtoSchemaType::kUint64, + TranslationStrategy::kInvalidTranslationStrategy}, + }, + kUnsetFtraceId, + 510, + kUnsetSize}, + {"mali_PM_MCU_HCTL_CORES_NOTIFY_PEND", + "mali", + { + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "kctx_tgid", 1, ProtoSchemaType::kInt32, + TranslationStrategy::kInvalidTranslationStrategy}, + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "kctx_id", 2, ProtoSchemaType::kUint32, + TranslationStrategy::kInvalidTranslationStrategy}, + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "info_val", 3, ProtoSchemaType::kUint64, + TranslationStrategy::kInvalidTranslationStrategy}, + }, + kUnsetFtraceId, + 511, + kUnsetSize}, + {"mali_PM_MCU_HCTL_CORE_INACTIVE_PEND", + "mali", + { + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "kctx_tgid", 1, ProtoSchemaType::kInt32, + TranslationStrategy::kInvalidTranslationStrategy}, + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "kctx_id", 2, ProtoSchemaType::kUint32, + TranslationStrategy::kInvalidTranslationStrategy}, + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "info_val", 3, ProtoSchemaType::kUint64, + TranslationStrategy::kInvalidTranslationStrategy}, + }, + kUnsetFtraceId, + 512, + kUnsetSize}, + {"mali_PM_MCU_HCTL_MCU_ON_RECHECK", + "mali", + { + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "kctx_tgid", 1, ProtoSchemaType::kInt32, + TranslationStrategy::kInvalidTranslationStrategy}, + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "kctx_id", 2, ProtoSchemaType::kUint32, + TranslationStrategy::kInvalidTranslationStrategy}, + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "info_val", 3, ProtoSchemaType::kUint64, + TranslationStrategy::kInvalidTranslationStrategy}, + }, + kUnsetFtraceId, + 513, + kUnsetSize}, + {"mali_PM_MCU_HCTL_SHADERS_CORE_OFF_PEND", + "mali", + { + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "kctx_tgid", 1, ProtoSchemaType::kInt32, + TranslationStrategy::kInvalidTranslationStrategy}, + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "kctx_id", 2, ProtoSchemaType::kUint32, + TranslationStrategy::kInvalidTranslationStrategy}, + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "info_val", 3, ProtoSchemaType::kUint64, + TranslationStrategy::kInvalidTranslationStrategy}, + }, + kUnsetFtraceId, + 514, + kUnsetSize}, + {"mali_PM_MCU_HCTL_SHADERS_PEND_OFF", + "mali", + { + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "kctx_tgid", 1, ProtoSchemaType::kInt32, + TranslationStrategy::kInvalidTranslationStrategy}, + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "kctx_id", 2, ProtoSchemaType::kUint32, + TranslationStrategy::kInvalidTranslationStrategy}, + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "info_val", 3, ProtoSchemaType::kUint64, + TranslationStrategy::kInvalidTranslationStrategy}, + }, + kUnsetFtraceId, + 515, + kUnsetSize}, + {"mali_PM_MCU_HCTL_SHADERS_PEND_ON", + "mali", + { + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "kctx_tgid", 1, ProtoSchemaType::kInt32, + TranslationStrategy::kInvalidTranslationStrategy}, + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "kctx_id", 2, ProtoSchemaType::kUint32, + TranslationStrategy::kInvalidTranslationStrategy}, + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "info_val", 3, ProtoSchemaType::kUint64, + TranslationStrategy::kInvalidTranslationStrategy}, + }, + kUnsetFtraceId, + 516, + kUnsetSize}, + {"mali_PM_MCU_HCTL_SHADERS_READY_OFF", + "mali", + { + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "kctx_tgid", 1, ProtoSchemaType::kInt32, + TranslationStrategy::kInvalidTranslationStrategy}, + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "kctx_id", 2, ProtoSchemaType::kUint32, + TranslationStrategy::kInvalidTranslationStrategy}, + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "info_val", 3, ProtoSchemaType::kUint64, + TranslationStrategy::kInvalidTranslationStrategy}, + }, + kUnsetFtraceId, + 517, + kUnsetSize}, + {"mali_PM_MCU_IN_SLEEP", + "mali", + { + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "kctx_tgid", 1, ProtoSchemaType::kInt32, + TranslationStrategy::kInvalidTranslationStrategy}, + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "kctx_id", 2, ProtoSchemaType::kUint32, + TranslationStrategy::kInvalidTranslationStrategy}, + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "info_val", 3, ProtoSchemaType::kUint64, + TranslationStrategy::kInvalidTranslationStrategy}, + }, + kUnsetFtraceId, + 518, + kUnsetSize}, + {"mali_PM_MCU_OFF", + "mali", + { + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "kctx_tgid", 1, ProtoSchemaType::kInt32, + TranslationStrategy::kInvalidTranslationStrategy}, + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "kctx_id", 2, ProtoSchemaType::kUint32, + TranslationStrategy::kInvalidTranslationStrategy}, + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "info_val", 3, ProtoSchemaType::kUint64, + TranslationStrategy::kInvalidTranslationStrategy}, + }, + kUnsetFtraceId, + 519, + kUnsetSize}, + {"mali_PM_MCU_ON", + "mali", + { + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "kctx_tgid", 1, ProtoSchemaType::kInt32, + TranslationStrategy::kInvalidTranslationStrategy}, + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "kctx_id", 2, ProtoSchemaType::kUint32, + TranslationStrategy::kInvalidTranslationStrategy}, + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "info_val", 3, ProtoSchemaType::kUint64, + TranslationStrategy::kInvalidTranslationStrategy}, + }, + kUnsetFtraceId, + 520, + kUnsetSize}, + {"mali_PM_MCU_ON_CORE_ATTR_UPDATE_PEND", + "mali", + { + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "kctx_tgid", 1, ProtoSchemaType::kInt32, + TranslationStrategy::kInvalidTranslationStrategy}, + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "kctx_id", 2, ProtoSchemaType::kUint32, + TranslationStrategy::kInvalidTranslationStrategy}, + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "info_val", 3, ProtoSchemaType::kUint64, + TranslationStrategy::kInvalidTranslationStrategy}, + }, + kUnsetFtraceId, + 521, + kUnsetSize}, + {"mali_PM_MCU_ON_GLB_REINIT_PEND", + "mali", + { + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "kctx_tgid", 1, ProtoSchemaType::kInt32, + TranslationStrategy::kInvalidTranslationStrategy}, + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "kctx_id", 2, ProtoSchemaType::kUint32, + TranslationStrategy::kInvalidTranslationStrategy}, + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "info_val", 3, ProtoSchemaType::kUint64, + TranslationStrategy::kInvalidTranslationStrategy}, + }, + kUnsetFtraceId, + 522, + kUnsetSize}, + {"mali_PM_MCU_ON_HALT", + "mali", + { + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "kctx_tgid", 1, ProtoSchemaType::kInt32, + TranslationStrategy::kInvalidTranslationStrategy}, + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "kctx_id", 2, ProtoSchemaType::kUint32, + TranslationStrategy::kInvalidTranslationStrategy}, + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "info_val", 3, ProtoSchemaType::kUint64, + TranslationStrategy::kInvalidTranslationStrategy}, + }, + kUnsetFtraceId, + 523, + kUnsetSize}, + {"mali_PM_MCU_ON_HWCNT_DISABLE", + "mali", + { + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "kctx_tgid", 1, ProtoSchemaType::kInt32, + TranslationStrategy::kInvalidTranslationStrategy}, + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "kctx_id", 2, ProtoSchemaType::kUint32, + TranslationStrategy::kInvalidTranslationStrategy}, + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "info_val", 3, ProtoSchemaType::kUint64, + TranslationStrategy::kInvalidTranslationStrategy}, + }, + kUnsetFtraceId, + 524, + kUnsetSize}, + {"mali_PM_MCU_ON_HWCNT_ENABLE", + "mali", + { + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "kctx_tgid", 1, ProtoSchemaType::kInt32, + TranslationStrategy::kInvalidTranslationStrategy}, + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "kctx_id", 2, ProtoSchemaType::kUint32, + TranslationStrategy::kInvalidTranslationStrategy}, + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "info_val", 3, ProtoSchemaType::kUint64, + TranslationStrategy::kInvalidTranslationStrategy}, + }, + kUnsetFtraceId, + 525, + kUnsetSize}, + {"mali_PM_MCU_ON_PEND_HALT", + "mali", + { + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "kctx_tgid", 1, ProtoSchemaType::kInt32, + TranslationStrategy::kInvalidTranslationStrategy}, + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "kctx_id", 2, ProtoSchemaType::kUint32, + TranslationStrategy::kInvalidTranslationStrategy}, + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "info_val", 3, ProtoSchemaType::kUint64, + TranslationStrategy::kInvalidTranslationStrategy}, + }, + kUnsetFtraceId, + 526, + kUnsetSize}, + {"mali_PM_MCU_ON_PEND_SLEEP", + "mali", + { + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "kctx_tgid", 1, ProtoSchemaType::kInt32, + TranslationStrategy::kInvalidTranslationStrategy}, + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "kctx_id", 2, ProtoSchemaType::kUint32, + TranslationStrategy::kInvalidTranslationStrategy}, + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "info_val", 3, ProtoSchemaType::kUint64, + TranslationStrategy::kInvalidTranslationStrategy}, + }, + kUnsetFtraceId, + 527, + kUnsetSize}, + {"mali_PM_MCU_ON_SLEEP_INITIATE", + "mali", + { + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "kctx_tgid", 1, ProtoSchemaType::kInt32, + TranslationStrategy::kInvalidTranslationStrategy}, + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "kctx_id", 2, ProtoSchemaType::kUint32, + TranslationStrategy::kInvalidTranslationStrategy}, + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "info_val", 3, ProtoSchemaType::kUint64, + TranslationStrategy::kInvalidTranslationStrategy}, + }, + kUnsetFtraceId, + 528, + kUnsetSize}, + {"mali_PM_MCU_PEND_OFF", + "mali", + { + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "kctx_tgid", 1, ProtoSchemaType::kInt32, + TranslationStrategy::kInvalidTranslationStrategy}, + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "kctx_id", 2, ProtoSchemaType::kUint32, + TranslationStrategy::kInvalidTranslationStrategy}, + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "info_val", 3, ProtoSchemaType::kUint64, + TranslationStrategy::kInvalidTranslationStrategy}, + }, + kUnsetFtraceId, + 529, + kUnsetSize}, + {"mali_PM_MCU_PEND_ON_RELOAD", + "mali", + { + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "kctx_tgid", 1, ProtoSchemaType::kInt32, + TranslationStrategy::kInvalidTranslationStrategy}, + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "kctx_id", 2, ProtoSchemaType::kUint32, + TranslationStrategy::kInvalidTranslationStrategy}, + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "info_val", 3, ProtoSchemaType::kUint64, + TranslationStrategy::kInvalidTranslationStrategy}, + }, + kUnsetFtraceId, + 530, + kUnsetSize}, + {"mali_PM_MCU_POWER_DOWN", + "mali", + { + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "kctx_tgid", 1, ProtoSchemaType::kInt32, + TranslationStrategy::kInvalidTranslationStrategy}, + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "kctx_id", 2, ProtoSchemaType::kUint32, + TranslationStrategy::kInvalidTranslationStrategy}, + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "info_val", 3, ProtoSchemaType::kUint64, + TranslationStrategy::kInvalidTranslationStrategy}, + }, + kUnsetFtraceId, + 531, + kUnsetSize}, + {"mali_PM_MCU_RESET_WAIT", + "mali", + { + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "kctx_tgid", 1, ProtoSchemaType::kInt32, + TranslationStrategy::kInvalidTranslationStrategy}, + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "kctx_id", 2, ProtoSchemaType::kUint32, + TranslationStrategy::kInvalidTranslationStrategy}, + {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType, + "info_val", 3, ProtoSchemaType::kUint64, + TranslationStrategy::kInvalidTranslationStrategy}, + }, + kUnsetFtraceId, + 532, + kUnsetSize}, {"mdp_cmd_kickoff", "mdss", { diff --git a/src/traced/probes/ftrace/event_info_constants.cc b/src/traced/probes/ftrace/event_info_constants.cc index 37f9c77f4e..4a0d08f20a 100644 --- a/src/traced/probes/ftrace/event_info_constants.cc +++ b/src/traced/probes/ftrace/event_info_constants.cc @@ -106,6 +106,8 @@ bool SetTranslationStrategy(FtraceFieldType ftrace, *out = kBoolToUint64; } else if (ftrace == kFtraceDataLoc && proto == ProtoSchemaType::kString) { *out = kDataLocToString; + } else if (ftrace == kFtraceSymAddr32 && proto == ProtoSchemaType::kUint64) { + *out = kFtraceSymAddr32ToUint64; } else if (ftrace == kFtraceSymAddr64 && proto == ProtoSchemaType::kUint64) { *out = kFtraceSymAddr64ToUint64; } else { diff --git a/src/traced/probes/ftrace/event_info_constants.h b/src/traced/probes/ftrace/event_info_constants.h index 0283f12484..cce144f6fb 100644 --- a/src/traced/probes/ftrace/event_info_constants.h +++ b/src/traced/probes/ftrace/event_info_constants.h @@ -48,6 +48,7 @@ enum FtraceFieldType { kFtraceDevId32, kFtraceDevId64, kFtraceDataLoc, + kFtraceSymAddr32, kFtraceSymAddr64, }; @@ -84,6 +85,7 @@ enum TranslationStrategy { kDevId32ToUint64, kDevId64ToUint64, kDataLocToString, + kFtraceSymAddr32ToUint64, kFtraceSymAddr64ToUint64, }; @@ -127,6 +129,7 @@ inline const char* ToString(FtraceFieldType v) { return "devid64"; case kFtraceDataLoc: return "__data_loc"; + case kFtraceSymAddr32: case kFtraceSymAddr64: return "void*"; case kInvalidFtraceFieldType: diff --git a/src/traced/probes/ftrace/ftrace_config_muxer.cc b/src/traced/probes/ftrace/ftrace_config_muxer.cc index f8a0eff5ae..ee9a3a2cdf 100644 --- a/src/traced/probes/ftrace/ftrace_config_muxer.cc +++ b/src/traced/probes/ftrace/ftrace_config_muxer.cc @@ -39,7 +39,6 @@ namespace { constexpr uint64_t kDefaultLowRamPerCpuBufferSizeKb = 2 * (1ULL << 10); // 2mb constexpr uint64_t kDefaultHighRamPerCpuBufferSizeKb = 8 * (1ULL << 10); // 8mb -constexpr uint64_t kMaxPerCpuBufferSizeKb = 64 * (1ULL << 10); // 64mb // Threshold for physical ram size used when deciding on default kernel buffer // sizes. We want to detect 8 GB, but the size reported through sysconf is @@ -113,6 +112,18 @@ void IntersectInPlace(const std::vector& unsorted_a, *out = std::move(v); } +std::vector Subtract(const std::vector& unsorted_a, + const std::vector& unsorted_b) { + std::vector a = unsorted_a; + std::sort(a.begin(), a.end()); + std::vector b = unsorted_b; + std::sort(b.begin(), b.end()); + std::vector v; + std::set_difference(a.begin(), a.end(), b.begin(), b.end(), + std::back_inserter(v)); + return v; +} + // This is just to reduce binary size and stack frame size of the insertions. // It effectively undoes STL's set::insert inlining. void PERFETTO_NO_INLINE InsertEvent(const char* group, @@ -587,9 +598,8 @@ FtraceConfigMuxer::FtraceConfigMuxer( : ftrace_(ftrace), atrace_wrapper_(atrace_wrapper), table_(table), - syscalls_(std::move(syscalls)), + syscalls_(syscalls), current_state_(), - ds_configs_(), vendor_events_(std::move(vendor_events)), secondary_instance_(secondary_instance) {} FtraceConfigMuxer::~FtraceConfigMuxer() = default; @@ -761,13 +771,16 @@ bool FtraceConfigMuxer::SetupConfig(FtraceConfigId id, std::vector apps(request.atrace_apps()); std::vector categories(request.atrace_categories()); + std::vector categories_sdk_optout = Subtract( + request.atrace_categories(), request.atrace_categories_prefer_sdk()); ds_configs_.emplace( std::piecewise_construct, std::forward_as_tuple(id), std::forward_as_tuple( std::move(filter), std::move(syscall_filter), compact_sched, std::move(ftrace_print_filter), std::move(apps), - std::move(categories), request.symbolize_ksyms(), - request.drain_buffer_percent(), GetSyscallsReturningFds(syscalls_))); + std::move(categories), std::move(categories_sdk_optout), + request.symbolize_ksyms(), request.drain_buffer_percent(), + GetSyscallsReturningFds(syscalls_))); return true; } @@ -805,12 +818,18 @@ bool FtraceConfigMuxer::RemoveConfig(FtraceConfigId config_id) { EventFilter expected_ftrace_events; std::vector expected_apps; std::vector expected_categories; + std::vector expected_categories_sdk_optout; for (const auto& id_config : ds_configs_) { const perfetto::FtraceDataSourceConfig& config = id_config.second; expected_ftrace_events.EnableEventsFrom(config.event_filter); UnionInPlace(config.atrace_apps, &expected_apps); UnionInPlace(config.atrace_categories, &expected_categories); + UnionInPlace(config.atrace_categories_sdk_optout, + &expected_categories_sdk_optout); } + std::vector expected_categories_prefer_sdk = + Subtract(expected_categories, expected_categories_sdk_optout); + // At this point expected_{apps,categories} contains the union of the // leftover configs (if any) that should be still on. However we did not // necessarily succeed in turning on atrace for each of those configs @@ -819,6 +838,7 @@ bool FtraceConfigMuxer::RemoveConfig(FtraceConfigId config_id) { // for: IntersectInPlace(current_state_.atrace_apps, &expected_apps); IntersectInPlace(current_state_.atrace_categories, &expected_categories); + // Work out if there is any difference between the current state and the // desired state: It's sufficient to compare sizes here (since we know from // above that expected_{apps,categories} is now a subset of @@ -827,6 +847,10 @@ bool FtraceConfigMuxer::RemoveConfig(FtraceConfigId config_id) { (current_state_.atrace_apps.size() != expected_apps.size()) || (current_state_.atrace_categories.size() != expected_categories.size()); + bool atrace_prefer_sdk_changed = + current_state_.atrace_categories_prefer_sdk != + expected_categories_prefer_sdk; + if (!SetSyscallEventFilter(/*extra_syscalls=*/{})) { PERFETTO_ELOG("Failed to set raw_syscall ftrace filter in RemoveConfig"); } @@ -887,6 +911,14 @@ bool FtraceConfigMuxer::RemoveConfig(FtraceConfigId config_id) { } } + if (atrace_prefer_sdk_changed) { + if (SetAtracePreferSdk(expected_categories_prefer_sdk, + /*atrace_errors=*/nullptr)) { + current_state_.atrace_categories_prefer_sdk = + expected_categories_prefer_sdk; + } + } + return true; } @@ -964,9 +996,8 @@ void FtraceConfigMuxer::SetupBufferSize(const FtraceConfig& request) { } // Post-conditions: -// 1. result >= 1 (should have at least one page per CPU) -// 2. result < kMaxTotalBufferSizeKb / (page_size / 1024) -// 3. If input is 0 output is a good default number +// * result >= 1 (should have at least one page per CPU) +// * If input is 0 output is a good default number size_t ComputeCpuBufferSizeInPages(size_t requested_buffer_size_kb, bool buffer_size_lower_bound, int64_t sysconf_phys_pages) { @@ -983,18 +1014,14 @@ size_t ComputeCpuBufferSizeInPages(size_t requested_buffer_size_kb, actual_size_kb = default_size_kb; } - if (actual_size_kb > kMaxPerCpuBufferSizeKb) { - PERFETTO_ELOG( - "The requested ftrace buf size (%zu KB) is too big, capping to %" PRIu64 - " KB", - actual_size_kb, kMaxPerCpuBufferSizeKb); - actual_size_kb = kMaxPerCpuBufferSizeKb; - } - size_t pages = actual_size_kb / (page_sz / 1024); return pages ? pages : 1; } +// TODO(rsavitski): stop caching the "input" value, as the kernel can and will +// choose a slightly different buffer size (especially on 6.x kernels). And even +// then the value might not be exactly page accurate due to scratch pages (more +// of a concern for the |FtraceController::FlushForInstance| caller). size_t FtraceConfigMuxer::GetPerCpuBufferSizePages() { return current_state_.cpu_buffer_size_pages; } @@ -1029,16 +1056,37 @@ void FtraceConfigMuxer::UpdateAtrace(const FtraceConfig& request, std::vector combined_apps = request.atrace_apps(); UnionInPlace(current_state_.atrace_apps, &combined_apps); - if (current_state_.atrace_on && - combined_apps.size() == current_state_.atrace_apps.size() && - combined_categories.size() == current_state_.atrace_categories.size()) { - return; + // Each data source can list some atrace categories for which the SDK is + // preferred (the rest of the categories are considered to opt out of the + // SDK). When merging multiple data sources, opting out wins. Therefore this + // code does a union of the opt outs for all data sources. + std::vector combined_categories_sdk_optout = Subtract( + request.atrace_categories(), request.atrace_categories_prefer_sdk()); + + std::vector current_categories_sdk_optout = + Subtract(current_state_.atrace_categories, + current_state_.atrace_categories_prefer_sdk); + UnionInPlace(current_categories_sdk_optout, &combined_categories_sdk_optout); + + std::vector combined_categories_prefer_sdk = + Subtract(combined_categories, combined_categories_sdk_optout); + + if (combined_categories_prefer_sdk != + current_state_.atrace_categories_prefer_sdk) { + if (SetAtracePreferSdk(combined_categories_prefer_sdk, atrace_errors)) { + current_state_.atrace_categories_prefer_sdk = + combined_categories_prefer_sdk; + } } - if (StartAtrace(combined_apps, combined_categories, atrace_errors)) { - current_state_.atrace_categories = combined_categories; - current_state_.atrace_apps = combined_apps; - current_state_.atrace_on = true; + if (!current_state_.atrace_on || + combined_apps.size() != current_state_.atrace_apps.size() || + combined_categories.size() != current_state_.atrace_categories.size()) { + if (StartAtrace(combined_apps, combined_categories, atrace_errors)) { + current_state_.atrace_categories = combined_categories; + current_state_.atrace_apps = combined_apps; + current_state_.atrace_on = true; + } } } @@ -1072,6 +1120,26 @@ bool FtraceConfigMuxer::StartAtrace(const std::vector& apps, return result; } +bool FtraceConfigMuxer::SetAtracePreferSdk( + const std::vector& prefer_sdk_categories, + std::string* atrace_errors) { + if (!atrace_wrapper_->SupportsPreferSdk()) { + return false; + } + PERFETTO_DLOG("Update atrace prefer sdk categories..."); + + std::vector args; + args.push_back("atrace"); // argv0 for exec() + args.push_back("--prefer_sdk"); + + for (const auto& category : prefer_sdk_categories) + args.push_back(category); + + bool result = atrace_wrapper_->RunAtrace(args, atrace_errors); + PERFETTO_DLOG("...done (%s)", result ? "success" : "fail"); + return result; +} + void FtraceConfigMuxer::DisableAtrace() { PERFETTO_DCHECK(current_state_.atrace_on); diff --git a/src/traced/probes/ftrace/ftrace_config_muxer.h b/src/traced/probes/ftrace/ftrace_config_muxer.h index 4c3f10bb6e..e654eed7f2 100644 --- a/src/traced/probes/ftrace/ftrace_config_muxer.h +++ b/src/traced/probes/ftrace/ftrace_config_muxer.h @@ -42,21 +42,25 @@ struct FtraceSetupErrors; // State held by the muxer per data source, used to parse ftrace according to // that data source's config. struct FtraceDataSourceConfig { - FtraceDataSourceConfig(EventFilter event_filter_in, - EventFilter syscall_filter_in, - CompactSchedConfig compact_sched_in, - std::optional print_filter_in, - std::vector atrace_apps_in, - std::vector atrace_categories_in, - bool symbolize_ksyms_in, - uint32_t buffer_percent_in, - base::FlatSet syscalls_returning_fd_in) + FtraceDataSourceConfig( + EventFilter event_filter_in, + EventFilter syscall_filter_in, + CompactSchedConfig compact_sched_in, + std::optional print_filter_in, + std::vector atrace_apps_in, + std::vector atrace_categories_in, + std::vector atrace_categories_sdk_optout_in, + bool symbolize_ksyms_in, + uint32_t buffer_percent_in, + base::FlatSet syscalls_returning_fd_in) : event_filter(std::move(event_filter_in)), syscall_filter(std::move(syscall_filter_in)), compact_sched(compact_sched_in), print_filter(std::move(print_filter_in)), atrace_apps(std::move(atrace_apps_in)), atrace_categories(std::move(atrace_categories_in)), + atrace_categories_sdk_optout( + std::move(atrace_categories_sdk_optout_in)), symbolize_ksyms(symbolize_ksyms_in), buffer_percent(buffer_percent_in), syscalls_returning_fd(std::move(syscalls_returning_fd_in)) {} @@ -78,6 +82,7 @@ struct FtraceDataSourceConfig { // Used only in Android for ATRACE_EVENT/os.Trace() userspace annotations. std::vector atrace_apps; std::vector atrace_categories; + std::vector atrace_categories_sdk_optout; // When enabled will turn on the kallsyms symbolizer in CpuReader. const bool symbolize_ksyms; @@ -184,8 +189,14 @@ class FtraceConfigMuxer { protos::pbzero::FtraceClock ftrace_clock{}; // Used only in Android for ATRACE_EVENT/os.Trace() userspace: bool atrace_on = false; + // Apps that should have the app tag enabled. This is a union of all the + // active configs. std::vector atrace_apps; + // Categories that should be enabled. This is a union of all the active + // configs. std::vector atrace_categories; + // Categories for which the perfetto SDK track_event should be enabled. + std::vector atrace_categories_prefer_sdk; bool saved_tracing_on; // Backup for the original tracing_on. }; @@ -199,6 +210,8 @@ class FtraceConfigMuxer { bool StartAtrace(const std::vector& apps, const std::vector& categories, std::string* atrace_errors); + bool SetAtracePreferSdk(const std::vector& prefer_sdk_categories, + std::string* atrace_errors); void DisableAtrace(); // This processes the config to get the exact events. diff --git a/src/traced/probes/ftrace/ftrace_config_muxer_unittest.cc b/src/traced/probes/ftrace/ftrace_config_muxer_unittest.cc index 9e9ca5e84e..53fa696802 100644 --- a/src/traced/probes/ftrace/ftrace_config_muxer_unittest.cc +++ b/src/traced/probes/ftrace/ftrace_config_muxer_unittest.cc @@ -97,6 +97,7 @@ class MockAtraceWrapper : public AtraceWrapper { public: MOCK_METHOD(bool, RunAtrace, (const std::vector&, std::string*)); MOCK_METHOD(bool, SupportsUserspaceOnly, ()); + MOCK_METHOD(bool, SupportsPreferSdk, ()); }; class MockProtoTranslationTable : public ProtoTranslationTable { @@ -127,7 +128,6 @@ TEST(ComputeCpuBufferSizeInPagesTest, DifferentCases) { auto KbToPages = [](uint64_t kb) { return kb * 1024 / base::GetSysPageSize(); }; - auto kMaxBufSizePages = KbToPages(64 * 1024); int64_t kNoRamInfo = 0; bool kExactSize = false; bool kLowerBoundSize = true; @@ -153,10 +153,6 @@ TEST(ComputeCpuBufferSizeInPagesTest, DifferentCases) { EXPECT_EQ(test(16384, kExactSize, kHighRamPages), KbToPages(16384)); EXPECT_EQ(test(16384, kLowerBoundSize, kHighRamPages), KbToPages(16384)); - // Buffer size given way too big: good default. - EXPECT_EQ(test(10 * (1ULL << 20), kExactSize, kNoRamInfo), kMaxBufSizePages); - EXPECT_EQ(test(512 * 1024, kExactSize, kNoRamInfo), kMaxBufSizePages); - // Your size ends up with less than 1 page per cpu -> 1 page. EXPECT_EQ(test(3, kExactSize, kNoRamInfo), 1u); // You picked a good size -> your size rounded to nearest page. @@ -174,6 +170,7 @@ class FtraceConfigMuxerTest : public ::testing::Test { FtraceConfigMuxerTest() { ON_CALL(atrace_wrapper_, RunAtrace).WillByDefault(Return(true)); ON_CALL(atrace_wrapper_, SupportsUserspaceOnly).WillByDefault(Return(true)); + ON_CALL(atrace_wrapper_, SupportsPreferSdk).WillByDefault(Return(true)); } std::unique_ptr GetMockTable() { @@ -843,6 +840,100 @@ TEST_F(FtraceConfigMuxerFakeTableTest, AtraceErrorsPropagated) { EXPECT_EQ(errors.atrace_errors, "foo\nbar\n"); } +TEST_F(FtraceConfigMuxerFakeTableTest, AtracePreferTrackEvent) { + const FtraceConfigId id_a = 3; + FtraceConfig config_a = CreateFtraceConfig({}); + *config_a.add_atrace_categories() = "cat_1"; + *config_a.add_atrace_categories() = "cat_2"; + *config_a.add_atrace_categories() = "cat_3"; + *config_a.add_atrace_categories_prefer_sdk() = "cat_1"; + *config_a.add_atrace_categories_prefer_sdk() = "cat_2"; + + ON_CALL(ftrace_, ReadFileIntoString("/root/current_tracer")) + .WillByDefault(Return("nop")); + ON_CALL(ftrace_, ReadFileIntoString("/root/events/enable")) + .WillByDefault(Return("0")); + EXPECT_CALL( + atrace_wrapper_, + RunAtrace(ElementsAreArray({"atrace", "--async_start", "--only_userspace", + "cat_1", "cat_2", "cat_3"}), + _)) + .WillOnce(Return(true)); + EXPECT_CALL( + atrace_wrapper_, + RunAtrace(ElementsAreArray({"atrace", "--prefer_sdk", "cat_1", "cat_2"}), + _)) + .WillOnce(Return(true)); + ASSERT_TRUE(model_.SetupConfig(id_a, config_a)); + + const FtraceConfigId id_b = 13; + FtraceConfig config_b = CreateFtraceConfig({}); + *config_b.add_atrace_categories() = "cat_1"; + *config_b.add_atrace_categories() = "cat_2"; + *config_b.add_atrace_categories() = "cat_3"; + *config_b.add_atrace_categories_prefer_sdk() = "cat_2"; + *config_b.add_atrace_categories_prefer_sdk() = "cat_3"; + + EXPECT_CALL( + atrace_wrapper_, + RunAtrace(ElementsAreArray({"atrace", "--prefer_sdk", "cat_2"}), _)) + .WillOnce(Return(true)); + ASSERT_TRUE(model_.SetupConfig(id_b, config_b)); + + EXPECT_CALL( + atrace_wrapper_, + RunAtrace(ElementsAreArray({"atrace", "--prefer_sdk", "cat_1", "cat_2"}), + _)) + .WillOnce(Return(true)); + ASSERT_TRUE(model_.RemoveConfig(id_b)); + + const FtraceConfigId id_c = 13; + FtraceConfig config_c = CreateFtraceConfig({}); + *config_c.add_atrace_categories() = "cat_1"; + *config_c.add_atrace_categories() = "cat_2"; + *config_c.add_atrace_categories() = "cat_3"; + *config_c.add_atrace_categories() = "cat_4"; + *config_c.add_atrace_categories_prefer_sdk() = "cat_1"; + *config_c.add_atrace_categories_prefer_sdk() = "cat_3"; + *config_c.add_atrace_categories_prefer_sdk() = "cat_4"; + + EXPECT_CALL( + atrace_wrapper_, + RunAtrace(ElementsAreArray({"atrace", "--async_start", "--only_userspace", + "cat_1", "cat_2", "cat_3", "cat_4"}), + _)) + .WillOnce(Return(true)); + EXPECT_CALL( + atrace_wrapper_, + RunAtrace(ElementsAreArray({"atrace", "--prefer_sdk", "cat_1", "cat_4"}), + _)) + .WillOnce(Return(true)); + ASSERT_TRUE(model_.SetupConfig(id_c, config_c)); + + EXPECT_CALL( + atrace_wrapper_, + RunAtrace(ElementsAreArray({"atrace", "--async_start", "--only_userspace", + "cat_1", "cat_2", "cat_3"}), + _)) + .WillOnce(Return(true)); + EXPECT_CALL( + atrace_wrapper_, + RunAtrace(ElementsAreArray({"atrace", "--prefer_sdk", "cat_1", "cat_2"}), + _)) + .WillOnce(Return(true)); + ASSERT_TRUE(model_.RemoveConfig(id_c)); + + EXPECT_CALL( + atrace_wrapper_, + RunAtrace( + ElementsAreArray({"atrace", "--async_stop", "--only_userspace"}), _)) + .WillOnce(Return(true)); + EXPECT_CALL(atrace_wrapper_, + RunAtrace(ElementsAreArray({"atrace", "--prefer_sdk"}), _)) + .WillOnce(Return(true)); + ASSERT_TRUE(model_.RemoveConfig(id_a)); +} + TEST_F(FtraceConfigMuxerFakeTableTest, SetupClockForTesting) { FtraceConfig config; diff --git a/src/traced/probes/ftrace/ftrace_controller_unittest.cc b/src/traced/probes/ftrace/ftrace_controller_unittest.cc index bfcb516f49..e0d4a1f656 100644 --- a/src/traced/probes/ftrace/ftrace_controller_unittest.cc +++ b/src/traced/probes/ftrace/ftrace_controller_unittest.cc @@ -206,6 +206,7 @@ class MockAtraceWrapper : public AtraceWrapper { public: MOCK_METHOD(bool, RunAtrace, (const std::vector&, std::string*)); MOCK_METHOD(bool, SupportsUserspaceOnly, ()); + MOCK_METHOD(bool, SupportsPreferSdk, ()); }; } // namespace @@ -298,7 +299,7 @@ std::unique_ptr CreateTestController( new MockFtraceProcfs("/root/", cpu_count)); } - std::unique_ptr atrace_wrapper; + auto atrace_wrapper = std::make_unique>(); auto table = FakeTable(ftrace_procfs.get()); @@ -501,27 +502,6 @@ TEST(FtraceControllerTest, BufferSize) { ASSERT_TRUE(controller->StartDataSource(data_source.get())); } - { - // Way too big buffer size -> max size. - EXPECT_CALL(*controller->procfs(), - WriteToFile("/root/buffer_size_kb", "65536")); - FtraceConfig config = CreateFtraceConfig({"group/foo"}); - config.set_buffer_size_kb(10 * 1024 * 1024); - auto data_source = controller->AddFakeDataSource(config); - ASSERT_TRUE(controller->StartDataSource(data_source.get())); - } - - { - // The limit is 64mb, 65mb is too much. - EXPECT_CALL(*controller->procfs(), - WriteToFile("/root/buffer_size_kb", "65536")); - FtraceConfig config = CreateFtraceConfig({"group/foo"}); - ON_CALL(*controller->procfs(), NumberOfCpus()).WillByDefault(Return(2)); - config.set_buffer_size_kb(65 * 1024); - auto data_source = controller->AddFakeDataSource(config); - ASSERT_TRUE(controller->StartDataSource(data_source.get())); - } - { // Your size ends up with less than 1 page per cpu -> 1 page (gmock already // covered by the cleanup expectation above). diff --git a/src/traced/probes/ftrace/ftrace_procfs.cc b/src/traced/probes/ftrace/ftrace_procfs.cc index 5346c0dfe6..b7adf48252 100644 --- a/src/traced/probes/ftrace/ftrace_procfs.cc +++ b/src/traced/probes/ftrace/ftrace_procfs.cc @@ -51,7 +51,7 @@ constexpr char kRssStatThrottledTrigger[] = constexpr char kSuspendResumeMinimalTrigger[] = "hist:keys=start:size=128:onmatch(power.suspend_resume)" ".trace(suspend_resume_minimal, start) if action == 'syscore_resume'"; -} +} // namespace void KernelLogWrite(const char* s) { PERFETTO_DCHECK(*s && s[strlen(s) - 1] == '\n'); @@ -285,7 +285,8 @@ bool FtraceProcfs::MaybeSetUpEventTriggers(const std::string& group, CreateEventTrigger("kmem", "rss_stat", kRssStatThrottledTrigger); } else if (name == "suspend_resume_minimal") { ret = RemoveAllEventTriggers("power", "suspend_resume") && - CreateEventTrigger("power", "suspend_resume", kSuspendResumeMinimalTrigger); + CreateEventTrigger("power", "suspend_resume", + kSuspendResumeMinimalTrigger); } } @@ -305,7 +306,8 @@ bool FtraceProcfs::MaybeTearDownEventTriggers(const std::string& group, if (name == "rss_stat_throttled") { ret = RemoveAllEventTriggers("kmem", "rss_stat"); } else if (name == "suspend_resume_minimal") { - ret = RemoveEventTrigger("power", "suspend_resume", kSuspendResumeMinimalTrigger); + ret = RemoveEventTrigger("power", "suspend_resume", + kSuspendResumeMinimalTrigger); } } diff --git a/src/traced/probes/ftrace/proto_translation_table.cc b/src/traced/probes/ftrace/proto_translation_table.cc index 9dcde14a6c..7e9092ea66 100644 --- a/src/traced/probes/ftrace/proto_translation_table.cc +++ b/src/traced/probes/ftrace/proto_translation_table.cc @@ -239,6 +239,7 @@ void SetProtoType(FtraceFieldType ftrace_type, case kFtraceUint64: case kFtraceInode32: case kFtraceInode64: + case kFtraceSymAddr32: case kFtraceSymAddr64: *proto_type = ProtoSchemaType::kUint64; *proto_field_id = GenericFtraceEvent::Field::kUintValueFieldNumber; @@ -310,13 +311,16 @@ bool InferFtraceType(const std::string& type_and_name, return true; } - // Kernel addresses that need symbolization via kallsyms. Only 64-bit kernels - // are supported for now. 32-bit kernels seems to be going away. - if ((base::StartsWith(type_and_name, "void*") || - base::StartsWith(type_and_name, "void *")) && - size == 8) { - *out = kFtraceSymAddr64; - return true; + // Kernel addresses that need symbolization via kallsyms. + if (base::StartsWith(type_and_name, "void*") || + base::StartsWith(type_and_name, "void *")) { + if (size == 4) { + *out = kFtraceSymAddr32; + return true; + } else if (size == 8) { + *out = kFtraceSymAddr64; + return true; + } } // Variable length strings: "char foo" + size: 0 (as in 'print'). diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/bcl_exynos/bcl_irq_trigger/format b/src/traced/probes/ftrace/test/data/synthetic/events/bcl_exynos/bcl_irq_trigger/format new file mode 100644 index 0000000000..4df557b34b --- /dev/null +++ b/src/traced/probes/ftrace/test/data/synthetic/events/bcl_exynos/bcl_irq_trigger/format @@ -0,0 +1,19 @@ +name: bcl_irq_trigger +ID: 1127 +format: + field:unsigned short common_type; offset:0; size:2; signed:0; + field:unsigned char common_flags; offset:2; size:1; signed:0; + field:unsigned char common_preempt_count; offset:3; size:1; signed:0; + field:int common_pid; offset:4; size:4; signed:1; + + field:int id; offset:8; size:4; signed:1; + field:int throttle; offset:12; size:4; signed:1; + field:int cpu0_limit; offset:16; size:4; signed:1; + field:int cpu1_limit; offset:20; size:4; signed:1; + field:int cpu2_limit; offset:24; size:4; signed:1; + field:int tpu_limit; offset:28; size:4; signed:1; + field:int gpu_limit; offset:32; size:4; signed:1; + field:int voltage; offset:36; size:4; signed:1; + field:int capacity; offset:40; size:4; signed:1; + +print fmt: "bcl irq %d trig %d: cpu0=%d, cpu1=%d, cpu2=%d, tpu=%d, gpu=%d, volt=%d cap=%d", REC->id, REC->throttle, REC->cpu0_limit, REC->cpu1_limit, REC->cpu2_limit, REC->tpu_limit, REC->gpu_limit, REC->voltage, REC->capacity \ No newline at end of file diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/dcvsh/dcvsh_freq/format b/src/traced/probes/ftrace/test/data/synthetic/events/dcvsh/dcvsh_freq/format new file mode 100644 index 0000000000..494c6fa293 --- /dev/null +++ b/src/traced/probes/ftrace/test/data/synthetic/events/dcvsh/dcvsh_freq/format @@ -0,0 +1,12 @@ +name: dcvsh_freq +ID: 1076 +format: + field:unsigned short common_type; offset:0; size:2; signed:0; + field:unsigned char common_flags; offset:2; size:1; signed:0; + field:unsigned char common_preempt_count; offset:3; size:1; signed:0; + field:int common_pid; offset:4; size:4; signed:1; + + field:unsigned long cpu; offset:8; size:8; signed:0; + field:unsigned long freq; offset:16; size:8; signed:0; + +print fmt: "cpu:%lu max frequency:%lu", REC->cpu, REC->freq diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/kgsl/gpu_frequency/format b/src/traced/probes/ftrace/test/data/synthetic/events/kgsl/gpu_frequency/format new file mode 100644 index 0000000000..2a8673469c --- /dev/null +++ b/src/traced/probes/ftrace/test/data/synthetic/events/kgsl/gpu_frequency/format @@ -0,0 +1,12 @@ +name: gpu_frequency +ID: 1136 +format: + field:unsigned short common_type; offset:0; size:2; signed:0; + field:unsigned char common_flags; offset:2; size:1; signed:0; + field:unsigned char common_preempt_count; offset:3; size:1; signed:0; + field:int common_pid; offset:4; size:4; signed:1; + + field:unsigned int gpu_freq; offset:8; size:4; signed:0; + field:unsigned int gpu_id; offset:12; size:4; signed:0; + +print fmt: "gpu_freq=%luKhz gpu_id=%lu", (unsigned long)REC->gpu_freq, (unsigned long)REC->gpu_id diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_HCTL_CORES_DOWN_SCALE_NOTIFY_PEND/format b/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_HCTL_CORES_DOWN_SCALE_NOTIFY_PEND/format new file mode 100644 index 0000000000..0d0220b0e4 --- /dev/null +++ b/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_HCTL_CORES_DOWN_SCALE_NOTIFY_PEND/format @@ -0,0 +1,13 @@ +name: mali_PM_MCU_HCTL_CORES_DOWN_SCALE_NOTIFY_PEND +ID: 1211 +format: + field:unsigned short common_type; offset:0; size:2; signed:0; + field:unsigned char common_flags; offset:2; size:1; signed:0; + field:unsigned char common_preempt_count; offset:3; size:1; signed:0; + field:int common_pid; offset:4; size:4; signed:1; + + field:pid_t kctx_tgid; offset:8; size:4; signed:1; + field:u32 kctx_id; offset:12; size:4; signed:0; + field:u64 info_val; offset:16; size:8; signed:0; + +print fmt: "kctx=%d_%u info=0x%llx", REC->kctx_tgid, REC->kctx_id, REC->info_val diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_HCTL_CORES_NOTIFY_PEND/format b/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_HCTL_CORES_NOTIFY_PEND/format new file mode 100644 index 0000000000..60c959ca21 --- /dev/null +++ b/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_HCTL_CORES_NOTIFY_PEND/format @@ -0,0 +1,13 @@ +name: mali_PM_MCU_HCTL_CORES_NOTIFY_PEND +ID: 1207 +format: + field:unsigned short common_type; offset:0; size:2; signed:0; + field:unsigned char common_flags; offset:2; size:1; signed:0; + field:unsigned char common_preempt_count; offset:3; size:1; signed:0; + field:int common_pid; offset:4; size:4; signed:1; + + field:pid_t kctx_tgid; offset:8; size:4; signed:1; + field:u32 kctx_id; offset:12; size:4; signed:0; + field:u64 info_val; offset:16; size:8; signed:0; + +print fmt: "kctx=%d_%u info=0x%llx", REC->kctx_tgid, REC->kctx_id, REC->info_val diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_HCTL_CORE_INACTIVE_PEND/format b/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_HCTL_CORE_INACTIVE_PEND/format new file mode 100644 index 0000000000..cbf8bb1826 --- /dev/null +++ b/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_HCTL_CORE_INACTIVE_PEND/format @@ -0,0 +1,13 @@ +name: mali_PM_MCU_HCTL_CORE_INACTIVE_PEND +ID: 1212 +format: + field:unsigned short common_type; offset:0; size:2; signed:0; + field:unsigned char common_flags; offset:2; size:1; signed:0; + field:unsigned char common_preempt_count; offset:3; size:1; signed:0; + field:int common_pid; offset:4; size:4; signed:1; + + field:pid_t kctx_tgid; offset:8; size:4; signed:1; + field:u32 kctx_id; offset:12; size:4; signed:0; + field:u64 info_val; offset:16; size:8; signed:0; + +print fmt: "kctx=%d_%u info=0x%llx", REC->kctx_tgid, REC->kctx_id, REC->info_val diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_HCTL_MCU_ON_RECHECK/format b/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_HCTL_MCU_ON_RECHECK/format new file mode 100644 index 0000000000..009ed969b2 --- /dev/null +++ b/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_HCTL_MCU_ON_RECHECK/format @@ -0,0 +1,13 @@ +name: mali_PM_MCU_HCTL_MCU_ON_RECHECK +ID: 1208 +format: + field:unsigned short common_type; offset:0; size:2; signed:0; + field:unsigned char common_flags; offset:2; size:1; signed:0; + field:unsigned char common_preempt_count; offset:3; size:1; signed:0; + field:int common_pid; offset:4; size:4; signed:1; + + field:pid_t kctx_tgid; offset:8; size:4; signed:1; + field:u32 kctx_id; offset:12; size:4; signed:0; + field:u64 info_val; offset:16; size:8; signed:0; + +print fmt: "kctx=%d_%u info=0x%llx", REC->kctx_tgid, REC->kctx_id, REC->info_val diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_HCTL_SHADERS_CORE_OFF_PEND/format b/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_HCTL_SHADERS_CORE_OFF_PEND/format new file mode 100644 index 0000000000..bffbeec827 --- /dev/null +++ b/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_HCTL_SHADERS_CORE_OFF_PEND/format @@ -0,0 +1,13 @@ +name: mali_PM_MCU_HCTL_SHADERS_CORE_OFF_PEND +ID: 1213 +format: + field:unsigned short common_type; offset:0; size:2; signed:0; + field:unsigned char common_flags; offset:2; size:1; signed:0; + field:unsigned char common_preempt_count; offset:3; size:1; signed:0; + field:int common_pid; offset:4; size:4; signed:1; + + field:pid_t kctx_tgid; offset:8; size:4; signed:1; + field:u32 kctx_id; offset:12; size:4; signed:0; + field:u64 info_val; offset:16; size:8; signed:0; + +print fmt: "kctx=%d_%u info=0x%llx", REC->kctx_tgid, REC->kctx_id, REC->info_val diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_HCTL_SHADERS_PEND_OFF/format b/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_HCTL_SHADERS_PEND_OFF/format new file mode 100644 index 0000000000..38acc95549 --- /dev/null +++ b/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_HCTL_SHADERS_PEND_OFF/format @@ -0,0 +1,13 @@ +name: mali_PM_MCU_HCTL_SHADERS_PEND_OFF +ID: 1210 +format: + field:unsigned short common_type; offset:0; size:2; signed:0; + field:unsigned char common_flags; offset:2; size:1; signed:0; + field:unsigned char common_preempt_count; offset:3; size:1; signed:0; + field:int common_pid; offset:4; size:4; signed:1; + + field:pid_t kctx_tgid; offset:8; size:4; signed:1; + field:u32 kctx_id; offset:12; size:4; signed:0; + field:u64 info_val; offset:16; size:8; signed:0; + +print fmt: "kctx=%d_%u info=0x%llx", REC->kctx_tgid, REC->kctx_id, REC->info_val diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_HCTL_SHADERS_PEND_ON/format b/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_HCTL_SHADERS_PEND_ON/format new file mode 100644 index 0000000000..f5fe135c90 --- /dev/null +++ b/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_HCTL_SHADERS_PEND_ON/format @@ -0,0 +1,13 @@ +name: mali_PM_MCU_HCTL_SHADERS_PEND_ON +ID: 1206 +format: + field:unsigned short common_type; offset:0; size:2; signed:0; + field:unsigned char common_flags; offset:2; size:1; signed:0; + field:unsigned char common_preempt_count; offset:3; size:1; signed:0; + field:int common_pid; offset:4; size:4; signed:1; + + field:pid_t kctx_tgid; offset:8; size:4; signed:1; + field:u32 kctx_id; offset:12; size:4; signed:0; + field:u64 info_val; offset:16; size:8; signed:0; + +print fmt: "kctx=%d_%u info=0x%llx", REC->kctx_tgid, REC->kctx_id, REC->info_val diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_HCTL_SHADERS_READY_OFF/format b/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_HCTL_SHADERS_READY_OFF/format new file mode 100644 index 0000000000..775fb83855 --- /dev/null +++ b/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_HCTL_SHADERS_READY_OFF/format @@ -0,0 +1,13 @@ +name: mali_PM_MCU_HCTL_SHADERS_READY_OFF +ID: 1209 +format: + field:unsigned short common_type; offset:0; size:2; signed:0; + field:unsigned char common_flags; offset:2; size:1; signed:0; + field:unsigned char common_preempt_count; offset:3; size:1; signed:0; + field:int common_pid; offset:4; size:4; signed:1; + + field:pid_t kctx_tgid; offset:8; size:4; signed:1; + field:u32 kctx_id; offset:12; size:4; signed:0; + field:u64 info_val; offset:16; size:8; signed:0; + +print fmt: "kctx=%d_%u info=0x%llx", REC->kctx_tgid, REC->kctx_id, REC->info_val diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_IN_SLEEP/format b/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_IN_SLEEP/format new file mode 100644 index 0000000000..67c5d04370 --- /dev/null +++ b/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_IN_SLEEP/format @@ -0,0 +1,13 @@ +name: mali_PM_MCU_IN_SLEEP +ID: 1216 +format: + field:unsigned short common_type; offset:0; size:2; signed:0; + field:unsigned char common_flags; offset:2; size:1; signed:0; + field:unsigned char common_preempt_count; offset:3; size:1; signed:0; + field:int common_pid; offset:4; size:4; signed:1; + + field:pid_t kctx_tgid; offset:8; size:4; signed:1; + field:u32 kctx_id; offset:12; size:4; signed:0; + field:u64 info_val; offset:16; size:8; signed:0; + +print fmt: "kctx=%d_%u info=0x%llx", REC->kctx_tgid, REC->kctx_id, REC->info_val diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_OFF/format b/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_OFF/format new file mode 100644 index 0000000000..0d32d12f3f --- /dev/null +++ b/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_OFF/format @@ -0,0 +1,13 @@ +name: mali_PM_MCU_OFF +ID: 1194 +format: + field:unsigned short common_type; offset:0; size:2; signed:0; + field:unsigned char common_flags; offset:2; size:1; signed:0; + field:unsigned char common_preempt_count; offset:3; size:1; signed:0; + field:int common_pid; offset:4; size:4; signed:1; + + field:pid_t kctx_tgid; offset:8; size:4; signed:1; + field:u32 kctx_id; offset:12; size:4; signed:0; + field:u64 info_val; offset:16; size:8; signed:0; + +print fmt: "kctx=%d_%u info=0x%llx", REC->kctx_tgid, REC->kctx_id, REC->info_val diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_ON/format b/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_ON/format new file mode 100644 index 0000000000..9a774cf13c --- /dev/null +++ b/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_ON/format @@ -0,0 +1,13 @@ +name: mali_PM_MCU_ON +ID: 1198 +format: + field:unsigned short common_type; offset:0; size:2; signed:0; + field:unsigned char common_flags; offset:2; size:1; signed:0; + field:unsigned char common_preempt_count; offset:3; size:1; signed:0; + field:int common_pid; offset:4; size:4; signed:1; + + field:pid_t kctx_tgid; offset:8; size:4; signed:1; + field:u32 kctx_id; offset:12; size:4; signed:0; + field:u64 info_val; offset:16; size:8; signed:0; + +print fmt: "kctx=%d_%u info=0x%llx", REC->kctx_tgid, REC->kctx_id, REC->info_val diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_ON_CORE_ATTR_UPDATE_PEND/format b/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_ON_CORE_ATTR_UPDATE_PEND/format new file mode 100644 index 0000000000..85b0b0bb6b --- /dev/null +++ b/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_ON_CORE_ATTR_UPDATE_PEND/format @@ -0,0 +1,13 @@ +name: mali_PM_MCU_ON_CORE_ATTR_UPDATE_PEND +ID: 1199 +format: + field:unsigned short common_type; offset:0; size:2; signed:0; + field:unsigned char common_flags; offset:2; size:1; signed:0; + field:unsigned char common_preempt_count; offset:3; size:1; signed:0; + field:int common_pid; offset:4; size:4; signed:1; + + field:pid_t kctx_tgid; offset:8; size:4; signed:1; + field:u32 kctx_id; offset:12; size:4; signed:0; + field:u64 info_val; offset:16; size:8; signed:0; + +print fmt: "kctx=%d_%u info=0x%llx", REC->kctx_tgid, REC->kctx_id, REC->info_val diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_ON_GLB_REINIT_PEND/format b/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_ON_GLB_REINIT_PEND/format new file mode 100644 index 0000000000..0e6f1b31ff --- /dev/null +++ b/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_ON_GLB_REINIT_PEND/format @@ -0,0 +1,13 @@ +name: mali_PM_MCU_ON_GLB_REINIT_PEND +ID: 1196 +format: + field:unsigned short common_type; offset:0; size:2; signed:0; + field:unsigned char common_flags; offset:2; size:1; signed:0; + field:unsigned char common_preempt_count; offset:3; size:1; signed:0; + field:int common_pid; offset:4; size:4; signed:1; + + field:pid_t kctx_tgid; offset:8; size:4; signed:1; + field:u32 kctx_id; offset:12; size:4; signed:0; + field:u64 info_val; offset:16; size:8; signed:0; + +print fmt: "kctx=%d_%u info=0x%llx", REC->kctx_tgid, REC->kctx_id, REC->info_val diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_ON_HALT/format b/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_ON_HALT/format new file mode 100644 index 0000000000..c99006b970 --- /dev/null +++ b/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_ON_HALT/format @@ -0,0 +1,13 @@ +name: mali_PM_MCU_ON_HALT +ID: 1201 +format: + field:unsigned short common_type; offset:0; size:2; signed:0; + field:unsigned char common_flags; offset:2; size:1; signed:0; + field:unsigned char common_preempt_count; offset:3; size:1; signed:0; + field:int common_pid; offset:4; size:4; signed:1; + + field:pid_t kctx_tgid; offset:8; size:4; signed:1; + field:u32 kctx_id; offset:12; size:4; signed:0; + field:u64 info_val; offset:16; size:8; signed:0; + +print fmt: "kctx=%d_%u info=0x%llx", REC->kctx_tgid, REC->kctx_id, REC->info_val diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_ON_HWCNT_DISABLE/format b/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_ON_HWCNT_DISABLE/format new file mode 100644 index 0000000000..f88998bf3d --- /dev/null +++ b/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_ON_HWCNT_DISABLE/format @@ -0,0 +1,13 @@ +name: mali_PM_MCU_ON_HWCNT_DISABLE +ID: 1200 +format: + field:unsigned short common_type; offset:0; size:2; signed:0; + field:unsigned char common_flags; offset:2; size:1; signed:0; + field:unsigned char common_preempt_count; offset:3; size:1; signed:0; + field:int common_pid; offset:4; size:4; signed:1; + + field:pid_t kctx_tgid; offset:8; size:4; signed:1; + field:u32 kctx_id; offset:12; size:4; signed:0; + field:u64 info_val; offset:16; size:8; signed:0; + +print fmt: "kctx=%d_%u info=0x%llx", REC->kctx_tgid, REC->kctx_id, REC->info_val diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_ON_HWCNT_ENABLE/format b/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_ON_HWCNT_ENABLE/format new file mode 100644 index 0000000000..2ec765a04e --- /dev/null +++ b/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_ON_HWCNT_ENABLE/format @@ -0,0 +1,13 @@ +name: mali_PM_MCU_ON_HWCNT_ENABLE +ID: 1197 +format: + field:unsigned short common_type; offset:0; size:2; signed:0; + field:unsigned char common_flags; offset:2; size:1; signed:0; + field:unsigned char common_preempt_count; offset:3; size:1; signed:0; + field:int common_pid; offset:4; size:4; signed:1; + + field:pid_t kctx_tgid; offset:8; size:4; signed:1; + field:u32 kctx_id; offset:12; size:4; signed:0; + field:u64 info_val; offset:16; size:8; signed:0; + +print fmt: "kctx=%d_%u info=0x%llx", REC->kctx_tgid, REC->kctx_id, REC->info_val diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_ON_PEND_HALT/format b/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_ON_PEND_HALT/format new file mode 100644 index 0000000000..f6eac93f4f --- /dev/null +++ b/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_ON_PEND_HALT/format @@ -0,0 +1,13 @@ +name: mali_PM_MCU_ON_PEND_HALT +ID: 1202 +format: + field:unsigned short common_type; offset:0; size:2; signed:0; + field:unsigned char common_flags; offset:2; size:1; signed:0; + field:unsigned char common_preempt_count; offset:3; size:1; signed:0; + field:int common_pid; offset:4; size:4; signed:1; + + field:pid_t kctx_tgid; offset:8; size:4; signed:1; + field:u32 kctx_id; offset:12; size:4; signed:0; + field:u64 info_val; offset:16; size:8; signed:0; + +print fmt: "kctx=%d_%u info=0x%llx", REC->kctx_tgid, REC->kctx_id, REC->info_val diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_ON_PEND_SLEEP/format b/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_ON_PEND_SLEEP/format new file mode 100644 index 0000000000..7ea24dec97 --- /dev/null +++ b/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_ON_PEND_SLEEP/format @@ -0,0 +1,13 @@ +name: mali_PM_MCU_ON_PEND_SLEEP +ID: 1215 +format: + field:unsigned short common_type; offset:0; size:2; signed:0; + field:unsigned char common_flags; offset:2; size:1; signed:0; + field:unsigned char common_preempt_count; offset:3; size:1; signed:0; + field:int common_pid; offset:4; size:4; signed:1; + + field:pid_t kctx_tgid; offset:8; size:4; signed:1; + field:u32 kctx_id; offset:12; size:4; signed:0; + field:u64 info_val; offset:16; size:8; signed:0; + +print fmt: "kctx=%d_%u info=0x%llx", REC->kctx_tgid, REC->kctx_id, REC->info_val diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_ON_SLEEP_INITIATE/format b/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_ON_SLEEP_INITIATE/format new file mode 100644 index 0000000000..0dd9675e01 --- /dev/null +++ b/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_ON_SLEEP_INITIATE/format @@ -0,0 +1,13 @@ +name: mali_PM_MCU_ON_SLEEP_INITIATE +ID: 1214 +format: + field:unsigned short common_type; offset:0; size:2; signed:0; + field:unsigned char common_flags; offset:2; size:1; signed:0; + field:unsigned char common_preempt_count; offset:3; size:1; signed:0; + field:int common_pid; offset:4; size:4; signed:1; + + field:pid_t kctx_tgid; offset:8; size:4; signed:1; + field:u32 kctx_id; offset:12; size:4; signed:0; + field:u64 info_val; offset:16; size:8; signed:0; + +print fmt: "kctx=%d_%u info=0x%llx", REC->kctx_tgid, REC->kctx_id, REC->info_val diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_PEND_OFF/format b/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_PEND_OFF/format new file mode 100644 index 0000000000..5319e3599f --- /dev/null +++ b/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_PEND_OFF/format @@ -0,0 +1,13 @@ +name: mali_PM_MCU_PEND_OFF +ID: 1204 +format: + field:unsigned short common_type; offset:0; size:2; signed:0; + field:unsigned char common_flags; offset:2; size:1; signed:0; + field:unsigned char common_preempt_count; offset:3; size:1; signed:0; + field:int common_pid; offset:4; size:4; signed:1; + + field:pid_t kctx_tgid; offset:8; size:4; signed:1; + field:u32 kctx_id; offset:12; size:4; signed:0; + field:u64 info_val; offset:16; size:8; signed:0; + +print fmt: "kctx=%d_%u info=0x%llx", REC->kctx_tgid, REC->kctx_id, REC->info_val diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_PEND_ON_RELOAD/format b/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_PEND_ON_RELOAD/format new file mode 100644 index 0000000000..5f015e209f --- /dev/null +++ b/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_PEND_ON_RELOAD/format @@ -0,0 +1,13 @@ +name: mali_PM_MCU_PEND_ON_RELOAD +ID: 1195 +format: + field:unsigned short common_type; offset:0; size:2; signed:0; + field:unsigned char common_flags; offset:2; size:1; signed:0; + field:unsigned char common_preempt_count; offset:3; size:1; signed:0; + field:int common_pid; offset:4; size:4; signed:1; + + field:pid_t kctx_tgid; offset:8; size:4; signed:1; + field:u32 kctx_id; offset:12; size:4; signed:0; + field:u64 info_val; offset:16; size:8; signed:0; + +print fmt: "kctx=%d_%u info=0x%llx", REC->kctx_tgid, REC->kctx_id, REC->info_val diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_POWER_DOWN/format b/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_POWER_DOWN/format new file mode 100644 index 0000000000..16551821a0 --- /dev/null +++ b/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_POWER_DOWN/format @@ -0,0 +1,13 @@ +name: mali_PM_MCU_POWER_DOWN +ID: 1203 +format: + field:unsigned short common_type; offset:0; size:2; signed:0; + field:unsigned char common_flags; offset:2; size:1; signed:0; + field:unsigned char common_preempt_count; offset:3; size:1; signed:0; + field:int common_pid; offset:4; size:4; signed:1; + + field:pid_t kctx_tgid; offset:8; size:4; signed:1; + field:u32 kctx_id; offset:12; size:4; signed:0; + field:u64 info_val; offset:16; size:8; signed:0; + +print fmt: "kctx=%d_%u info=0x%llx", REC->kctx_tgid, REC->kctx_id, REC->info_val diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_RESET_WAIT/format b/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_RESET_WAIT/format new file mode 100644 index 0000000000..2d92d59d36 --- /dev/null +++ b/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_PM_MCU_RESET_WAIT/format @@ -0,0 +1,13 @@ +name: mali_PM_MCU_RESET_WAIT +ID: 1205 +format: + field:unsigned short common_type; offset:0; size:2; signed:0; + field:unsigned char common_flags; offset:2; size:1; signed:0; + field:unsigned char common_preempt_count; offset:3; size:1; signed:0; + field:int common_pid; offset:4; size:4; signed:1; + + field:pid_t kctx_tgid; offset:8; size:4; signed:1; + field:u32 kctx_id; offset:12; size:4; signed:0; + field:u64 info_val; offset:16; size:8; signed:0; + +print fmt: "kctx=%d_%u info=0x%llx", REC->kctx_tgid, REC->kctx_id, REC->info_val diff --git a/src/traced/probes/ps/process_stats_data_source.cc b/src/traced/probes/ps/process_stats_data_source.cc index 9a27d29814..91c72a9d5b 100644 --- a/src/traced/probes/ps/process_stats_data_source.cc +++ b/src/traced/probes/ps/process_stats_data_source.cc @@ -720,6 +720,12 @@ bool ProcessStatsDataSource::WriteMemCounters(int32_t pid, GetOrCreateStatsProcess(pid)->set_smr_pss_shmem_kb(counter); cached.smr_pss_shmem_kb = counter; } + } else if (strcmp(key.data(), "SwapPss") == 0) { + auto counter = ToUInt32(value.data()); + if (counter != cached.smr_swap_pss_kb) { + GetOrCreateStatsProcess(pid)->set_smr_swap_pss_kb(counter); + cached.smr_swap_pss_kb = counter; + } } key.clear(); diff --git a/src/traced/probes/ps/process_stats_data_source.h b/src/traced/probes/ps/process_stats_data_source.h index 9ec28bc115..0224dd1756 100644 --- a/src/traced/probes/ps/process_stats_data_source.h +++ b/src/traced/probes/ps/process_stats_data_source.h @@ -90,6 +90,7 @@ class ProcessStatsDataSource : public ProbesDataSource { uint32_t smr_pss_anon_kb = std::numeric_limits::max(); uint32_t smr_pss_file_kb = std::numeric_limits::max(); uint32_t smr_pss_shmem_kb = std::numeric_limits::max(); + uint32_t smr_swap_pss_kb = std::numeric_limits::max(); uint64_t runtime_user_mode_ns = std::numeric_limits::max(); uint64_t runtime_kernel_mode_ns = std::numeric_limits::max(); // file descriptors diff --git a/src/traced/probes/sys_stats/sys_stats_data_source.cc b/src/traced/probes/sys_stats/sys_stats_data_source.cc index 09e4e98d7b..081b7cfc29 100644 --- a/src/traced/probes/sys_stats/sys_stats_data_source.cc +++ b/src/traced/probes/sys_stats/sys_stats_data_source.cc @@ -96,7 +96,6 @@ SysStatsDataSource::SysStatsDataSource( psi_cpu_fd_ = open_fn("/proc/pressure/cpu"); psi_io_fd_ = open_fn("/proc/pressure/io"); psi_memory_fd_ = open_fn("/proc/pressure/memory"); - read_buf_ = base::PagedMemory::Allocate(kReadBufSize); // Build a lookup map that allows to quickly translate strings like "MemTotal" @@ -148,8 +147,8 @@ SysStatsDataSource::SysStatsDataSource( stat_enabled_fields_ |= 1ul << static_cast(*counter); } - std::array periods_ms{}; - std::array ticks{}; + std::array periods_ms{}; + std::array ticks{}; static_assert(periods_ms.size() == ticks.size(), "must have same size"); periods_ms[0] = ClampTo10Ms(cfg.meminfo_period_ms(), "meminfo_period_ms"); @@ -160,6 +159,7 @@ SysStatsDataSource::SysStatsDataSource( periods_ms[5] = ClampTo10Ms(cfg.buddyinfo_period_ms(), "buddyinfo_period_ms"); periods_ms[6] = ClampTo10Ms(cfg.diskstat_period_ms(), "diskstat_period_ms"); periods_ms[7] = ClampTo10Ms(cfg.psi_period_ms(), "psi_period_ms"); + periods_ms[8] = ClampTo10Ms(cfg.thermal_period_ms(), "thermal_period_ms"); tick_period_ms_ = 0; for (uint32_t ms : periods_ms) { @@ -186,6 +186,7 @@ SysStatsDataSource::SysStatsDataSource( buddyinfo_ticks_ = ticks[5]; diskstat_ticks_ = ticks[6]; psi_ticks_ = ticks[7]; + thermal_ticks_ = ticks[8]; } void SysStatsDataSource::Start() { @@ -241,12 +242,84 @@ void SysStatsDataSource::ReadSysStats() { if (psi_ticks_ && tick_ % psi_ticks_ == 0) ReadPsi(sys_stats); + if (thermal_ticks_ && tick_ % thermal_ticks_ == 0) + ReadThermalZones(sys_stats); + sys_stats->set_collection_end_timestamp( static_cast(base::GetBootTimeNs().count())); tick_++; } +base::ScopedDir SysStatsDataSource::OpenDirAndLogOnErrorOnce( + const std::string& dir_path, + bool* already_logged) { + base::ScopedDir dir(opendir(dir_path.c_str())); + if (!dir && !(*already_logged)) { + PERFETTO_PLOG("Failed to open %s", dir_path.c_str()); + *already_logged = true; + } + return dir; +} + +std::optional SysStatsDataSource::ReadThermalZoneTemp( + const std::string& thermal_zone) { + base::StackString<256> thermal_zone_temp_path("/sys/class/thermal/%s/temp", + thermal_zone.c_str()); + base::ScopedFile fd = OpenReadOnly(thermal_zone_temp_path.c_str()); + if (!fd) { + return std::nullopt; + } + size_t rsize = ReadFile(&fd, thermal_zone_temp_path.c_str()); + if (!rsize) + return std::nullopt; + + return static_cast( + strtoll(static_cast(read_buf_.Get()), nullptr, 10)); +} + +std::optional SysStatsDataSource::ReadThermalZoneType( + const std::string& thermal_zone) { + base::StackString<256> thermal_zone_type_path("/sys/class/thermal/%s/type", + thermal_zone.c_str()); + base::ScopedFile fd = OpenReadOnly(thermal_zone_type_path.c_str()); + if (!fd) { + return std::nullopt; + } + size_t rsize = ReadFile(&fd, thermal_zone_type_path.c_str()); + if (!rsize) + return std::nullopt; + return base::StripSuffix(static_cast(read_buf_.Get()), "\n"); +} + +void SysStatsDataSource::ReadThermalZones(protos::pbzero::SysStats* sys_stats) { + std::string base_dir = "/sys/class/thermal/"; + base::ScopedDir thermal_dir = + OpenDirAndLogOnErrorOnce(base_dir, &thermal_error_logged_); + if (!thermal_dir) { + return; + } + while (struct dirent* dir_ent = readdir(*thermal_dir)) { + // Entries in /sys/class/thermal are symlinks to /devices/virtual + if (dir_ent->d_type != DT_LNK) + continue; + const char* name = dir_ent->d_name; + if (!base::StartsWith(name, "thermal_zone")) { + continue; + } + auto* thermal_zone = sys_stats->add_thermal_zone(); + thermal_zone->set_name(name); + auto temp = ReadThermalZoneTemp(name); + if (temp) { + thermal_zone->set_temp(*temp / 1000); + } + auto type = ReadThermalZoneType(name); + if (type) { + thermal_zone->set_type(*type); + } + } +} + void SysStatsDataSource::ReadDiskStat(protos::pbzero::SysStats* sys_stats) { size_t rsize = ReadFile(&diskstat_fd_, "/proc/diskstats"); if (!rsize) { @@ -380,19 +453,22 @@ void SysStatsDataSource::ReadBuddyInfo(protos::pbzero::SysStats* sys_stats) { } void SysStatsDataSource::ReadDevfreq(protos::pbzero::SysStats* sys_stats) { - base::ScopedDir devfreq_dir = OpenDevfreqDir(); - if (devfreq_dir) { - while (struct dirent* dir_ent = readdir(*devfreq_dir)) { - // Entries in /sys/class/devfreq are symlinks to /devices/platform - if (dir_ent->d_type != DT_LNK) - continue; - const char* name = dir_ent->d_name; - const char* file_content = ReadDevfreqCurFreq(name); - auto value = static_cast(strtoll(file_content, nullptr, 10)); - auto* devfreq = sys_stats->add_devfreq(); - devfreq->set_key(name); - devfreq->set_value(value); - } + std::string base_dir = "/sys/class/devfreq/"; + base::ScopedDir devfreq_dir = + OpenDirAndLogOnErrorOnce(base_dir, &devfreq_error_logged_); + if (!devfreq_dir) { + return; + } + while (struct dirent* dir_ent = readdir(*devfreq_dir)) { + // Entries in /sys/class/devfreq are symlinks to /devices/platform + if (dir_ent->d_type != DT_LNK) + continue; + const char* name = dir_ent->d_name; + const char* file_content = ReadDevfreqCurFreq(name); + auto value = static_cast(strtoll(file_content, nullptr, 10)); + auto* devfreq = sys_stats->add_devfreq(); + devfreq->set_key(name); + devfreq->set_value(value); } } @@ -403,16 +479,6 @@ void SysStatsDataSource::ReadCpufreq(protos::pbzero::SysStats* sys_stats) { sys_stats->add_cpufreq_khz(c); } -base::ScopedDir SysStatsDataSource::OpenDevfreqDir() { - const char* base_dir = "/sys/class/devfreq/"; - base::ScopedDir devfreq_dir(opendir(base_dir)); - if (!devfreq_dir && !devfreq_error_logged_) { - devfreq_error_logged_ = true; - PERFETTO_PLOG("failed to opendir(/sys/class/devfreq)"); - } - return devfreq_dir; -} - const char* SysStatsDataSource::ReadDevfreqCurFreq( const std::string& deviceName) { const char* devfreq_base_path = "/sys/class/devfreq"; diff --git a/src/traced/probes/sys_stats/sys_stats_data_source.h b/src/traced/probes/sys_stats/sys_stats_data_source.h index edeb67251e..c7e7e93935 100644 --- a/src/traced/probes/sys_stats/sys_stats_data_source.h +++ b/src/traced/probes/sys_stats/sys_stats_data_source.h @@ -67,8 +67,16 @@ class SysStatsDataSource : public ProbesDataSource { uint32_t tick_for_testing() const { return tick_; } // Virtual for testing - virtual base::ScopedDir OpenDevfreqDir(); + virtual base::ScopedDir OpenDirAndLogOnErrorOnce(const std::string& dir_path, + bool* already_logged); virtual const char* ReadDevfreqCurFreq(const std::string& name); + virtual std::optional ReadThermalZoneTemp(const std::string& name); + virtual std::optional ReadThermalZoneType( + const std::string& name); + + protected: + bool thermal_error_logged_ = false; + bool devfreq_error_logged_ = false; private: struct CStrCmp { @@ -90,6 +98,7 @@ class SysStatsDataSource : public ProbesDataSource { void ReadBuddyInfo(protos::pbzero::SysStats* sys_stats); void ReadDiskStat(protos::pbzero::SysStats* sys_stats); void ReadPsi(protos::pbzero::SysStats* sys_stats); + void ReadThermalZones(protos::pbzero::SysStats* sys_stats); size_t ReadFile(base::ScopedFile*, const char* path); base::TaskRunner* const task_runner_; @@ -118,7 +127,7 @@ class SysStatsDataSource : public ProbesDataSource { uint32_t buddyinfo_ticks_ = 0; uint32_t diskstat_ticks_ = 0; uint32_t psi_ticks_ = 0; - bool devfreq_error_logged_ = false; + uint32_t thermal_ticks_ = 0; std::unique_ptr cpu_freq_info_; diff --git a/src/traced/probes/sys_stats/sys_stats_data_source_unittest.cc b/src/traced/probes/sys_stats/sys_stats_data_source_unittest.cc index d5639f54f1..ea633330d5 100644 --- a/src/traced/probes/sys_stats/sys_stats_data_source_unittest.cc +++ b/src/traced/probes/sys_stats/sys_stats_data_source_unittest.cc @@ -208,6 +208,9 @@ const char kMockPsi[] = R"( some avg10=23.10 avg60=5.06 avg300=15.10 total=417963 full avg10=9.00 avg60=19.20 avg300=3.23 total=205933)"; +const uint64_t kMockThermalTemp = 25000; +const char kMockThermalType[] = "TSR0"; + class TestSysStatsDataSource : public SysStatsDataSource { public: TestSysStatsDataSource(base::TaskRunner* task_runner, @@ -223,11 +226,24 @@ class TestSysStatsDataSource : public SysStatsDataSource { std::move(cpu_freq_info), open_fn) {} - MOCK_METHOD(base::ScopedDir, OpenDevfreqDir, (), (override)); + MOCK_METHOD(base::ScopedDir, + OpenDirAndLogOnErrorOnce, + (const std::string& dir_path, bool* already_logged), + (override)); MOCK_METHOD(const char*, ReadDevfreqCurFreq, (const std::string& deviceName), (override)); + MOCK_METHOD(std::optional, + ReadThermalZoneTemp, + (const std::string& name), + (override)); + MOCK_METHOD(std::optional, + ReadThermalZoneType, + (const std::string& name), + (override)); + bool* GetDevfreqErrorLoggedAddress() { return &devfreq_error_logged_; } + bool* GetThermalErrorLoggedAddress() { return &thermal_error_logged_; } }; base::ScopedFile MockOpenReadOnly(const char* path) { @@ -438,6 +454,62 @@ TEST_F(SysStatsDataSourceTest, BuddyinfoAll) { EXPECT_EQ(sys_stats.buddy_info()[3].order_pages()[10], 3u); } +TEST_F(SysStatsDataSourceTest, ThermalZones) { + DataSourceConfig config; + protos::gen::SysStatsConfig sys_cfg; + sys_cfg.set_thermal_period_ms(10); + config.set_sys_stats_config_raw(sys_cfg.SerializeAsString()); + auto data_source = GetSysStatsDataSource(config); + + // Create dirs and symlinks, but only read the symlinks. + std::vector dirs_to_delete; + std::vector symlinks_to_delete; + auto make_thermal_paths = [&symlinks_to_delete, &dirs_to_delete]( + base::TempDir& temp_dir, base::TempDir& sym_dir, + const char* name) { + base::StackString<256> path("%s/%s", temp_dir.path().c_str(), name); + dirs_to_delete.push_back(path.ToStdString()); + mkdir(path.c_str(), 0755); + base::StackString<256> sym_path("%s/%s", sym_dir.path().c_str(), name); + symlinks_to_delete.push_back(sym_path.ToStdString()); + symlink(path.c_str(), sym_path.c_str()); + }; + auto fake_thermal = base::TempDir::Create(); + auto fake_thermal_symdir = base::TempDir::Create(); + static const char* const thermalzone_names[] = {"thermal_zone0"}; + for (auto dev : thermalzone_names) { + make_thermal_paths(fake_thermal, fake_thermal_symdir, dev); + } + + EXPECT_CALL(*data_source, OpenDirAndLogOnErrorOnce( + "/sys/class/thermal/", + data_source->GetThermalErrorLoggedAddress())) + .WillRepeatedly(Invoke([&fake_thermal_symdir] { + return base::ScopedDir(opendir(fake_thermal_symdir.path().c_str())); + })); + + EXPECT_CALL(*data_source, ReadThermalZoneTemp("thermal_zone0")) + .WillRepeatedly(Return(std::optional(kMockThermalTemp))); + EXPECT_CALL(*data_source, ReadThermalZoneType("thermal_zone0")) + .WillRepeatedly(Return(std::optional(kMockThermalType))); + + WaitTick(data_source.get()); + + protos::gen::TracePacket packet = writer_raw_->GetOnlyTracePacket(); + ASSERT_TRUE(packet.has_sys_stats()); + const auto& sys_stats = packet.sys_stats(); + + ASSERT_EQ(sys_stats.thermal_zone_size(), 1); + EXPECT_EQ(sys_stats.thermal_zone()[0].name(), "thermal_zone0"); + EXPECT_EQ(sys_stats.thermal_zone()[0].temp(), kMockThermalTemp / 1000); + EXPECT_EQ(sys_stats.thermal_zone()[0].type(), kMockThermalType); + + for (const std::string& path : dirs_to_delete) + base::Rmdir(path); + for (const std::string& path : symlinks_to_delete) + remove(path.c_str()); +} + TEST_F(SysStatsDataSourceTest, DevfreqAll) { DataSourceConfig config; protos::gen::SysStatsConfig sys_cfg; @@ -466,7 +538,9 @@ TEST_F(SysStatsDataSourceTest, DevfreqAll) { make_devfreq_paths(fake_devfreq, fake_devfreq_symdir, dev); } - EXPECT_CALL(*data_source, OpenDevfreqDir()) + EXPECT_CALL(*data_source, OpenDirAndLogOnErrorOnce( + "/sys/class/devfreq/", + data_source->GetDevfreqErrorLoggedAddress())) .WillRepeatedly(Invoke([&fake_devfreq_symdir] { return base::ScopedDir(opendir(fake_devfreq_symdir.path().c_str())); })); diff --git a/src/traced/probes/system_info/system_info_data_source.cc b/src/traced/probes/system_info/system_info_data_source.cc index 2178556cdc..eba4df891a 100644 --- a/src/traced/probes/system_info/system_info_data_source.cc +++ b/src/traced/probes/system_info/system_info_data_source.cc @@ -16,6 +16,8 @@ #include "src/traced/probes/system_info/system_info_data_source.h" +#include + #include "perfetto/base/time.h" #include "perfetto/ext/base/file_utils.h" #include "perfetto/ext/base/string_splitter.h" @@ -75,8 +77,19 @@ void SystemInfoDataSource::Start() { line_start = line_end + 1; if (line.empty() && !cpu_index.empty()) { PERFETTO_DCHECK(cpu_index == std::to_string(next_cpu_index)); + auto* cpu = cpu_info->add_cpus(); cpu->set_processor(default_processor); + + std::optional cpu_capacity = base::StringToUInt32( + base::StripSuffix(ReadFile("/sys/devices/system/cpu/cpu" + cpu_index + + "/cpu_capacity"), + "\n")); + + if (cpu_capacity.has_value()) { + cpu->set_capacity(cpu_capacity.value()); + } + auto freqs_range = cpu_freq_info_->GetFreqs(next_cpu_index); for (auto it = freqs_range.first; it != freqs_range.second; it++) { cpu->add_frequencies(*it); diff --git a/src/traced/probes/system_info/system_info_data_source_unittest.cc b/src/traced/probes/system_info/system_info_data_source_unittest.cc index 8c204ec960..83d5a723ca 100644 --- a/src/traced/probes/system_info/system_info_data_source_unittest.cc +++ b/src/traced/probes/system_info/system_info_data_source_unittest.cc @@ -28,6 +28,8 @@ using ::testing::Return; namespace perfetto { namespace { +static const uint32_t CPU_COUNT = 8; + const char kMockCpuInfoAndroid[] = R"( Processor : AArch64 Processor rev 13 (aarch64) processor : 0 @@ -106,6 +108,9 @@ Hardware : Qualcomm Technologies, Inc SDM670 )"; +const char* kMockCpuCapacityInfoAndroid[8] = { + "200\n", "200\n", "200\n", "600\n", "600\n", "600\n", "1024\n", "1024\n"}; + class TestSystemInfoDataSource : public SystemInfoDataSource { public: TestSystemInfoDataSource(std::unique_ptr writer, @@ -138,6 +143,14 @@ TEST_F(SystemInfoDataSourceTest, CpuInfoAndroid) { auto data_source = GetSystemInfoDataSource(); EXPECT_CALL(*data_source, ReadFile("/proc/cpuinfo")) .WillOnce(Return(kMockCpuInfoAndroid)); + + for (uint32_t cpu_index = 0; cpu_index < CPU_COUNT; cpu_index++) { + EXPECT_CALL(*data_source, + ReadFile("/sys/devices/system/cpu/cpu" + + std::to_string(cpu_index) + "/cpu_capacity")) + .WillOnce(Return(kMockCpuCapacityInfoAndroid[cpu_index])); + } + data_source->Start(); protos::gen::TracePacket packet = writer_raw_->GetOnlyTracePacket(); @@ -149,11 +162,14 @@ TEST_F(SystemInfoDataSourceTest, CpuInfoAndroid) { ASSERT_THAT(cpu.frequencies(), ElementsAre(300000, 576000, 748800, 998400, 1209600, 1324800, 1516800, 1612800, 1708800)); + ASSERT_EQ(cpu.capacity(), static_cast(200)); cpu = cpu_info.cpus()[1]; ASSERT_EQ(cpu.processor(), "AArch64 Processor rev 13 (aarch64)"); ASSERT_THAT(cpu.frequencies(), ElementsAre(300000, 652800, 825600, 979200, 1132800, 1363200, 1536000, 1747200, 1843200, 1996800, 2803200)); + cpu = cpu_info.cpus()[7]; + ASSERT_EQ(cpu.capacity(), static_cast(1024)); } } // namespace diff --git a/src/traced/service/builtin_producer.cc b/src/traced/service/builtin_producer.cc index 52fd468e79..a5999a2e91 100644 --- a/src/traced/service/builtin_producer.cc +++ b/src/traced/service/builtin_producer.cc @@ -53,7 +53,8 @@ constexpr char kJavaHprofOomDataSourceName[] = "android.java_hprof.oom"; constexpr char kTracedPerfDataSourceName[] = "linux.perf"; constexpr char kLazyHeapprofdPropertyName[] = "traced.lazy.heapprofd"; constexpr char kLazyTracedPerfPropertyName[] = "traced.lazy.traced_perf"; -constexpr char kJavaHprofOomActivePropertyName[] = "traced.oome_heap_session.count"; +constexpr char kJavaHprofOomActivePropertyName[] = + "traced.oome_heap_session.count"; constexpr char kAndroidSdkSyspropGuardDataSourceName[] = "android.sdk_sysprop_guard"; diff --git a/src/traced_relay/relay_service.cc b/src/traced_relay/relay_service.cc index e079b2857d..3eb42140b4 100644 --- a/src/traced_relay/relay_service.cc +++ b/src/traced_relay/relay_service.cc @@ -202,6 +202,26 @@ void RelayService::Start(const char* listening_socket_name, ConnectRelayClient(); } +void RelayService::Start(base::ScopedSocketHandle server_socket_handle, + const char* client_socket_name) { + // Called when the service is started by Android init, where + // |server_socket_handle| is a unix socket. + listening_socket_ = base::UnixSocket::Listen( + std::move(server_socket_handle), this, task_runner_, + base::SockFamily::kUnix, base::SockType::kStream); + bool producer_socket_listening = + listening_socket_ && listening_socket_->is_listening(); + if (!producer_socket_listening) { + PERFETTO_FATAL("Failed to listen to the server socket"); + } + + // Save |client_socket_name| for opening new client connection to remote + // service when a local producer connects. + client_socket_name_ = client_socket_name; + + ConnectRelayClient(); +} + void RelayService::OnNewIncomingConnection( base::UnixSocket* listen_socket, std::unique_ptr server_conn) { diff --git a/src/traced_relay/relay_service.h b/src/traced_relay/relay_service.h index 096961eb0e..789ed16fb0 100644 --- a/src/traced_relay/relay_service.h +++ b/src/traced_relay/relay_service.h @@ -110,6 +110,12 @@ class RelayService : public base::UnixSocket::EventListener { // |server_socket_name| and |client_socket_name| ports. void Start(const char* server_socket_name, const char* client_socket_name); + // Starts the service relay that forwards messages between the + // |server_socket_handle| and |client_socket_name| ports. Called when the + // service is started by Android init. + void Start(base::ScopedSocketHandle server_socket_handle, + const char* client_socket_name); + static std::string GetMachineIdHint( bool use_pseudo_boot_id_for_testing = false); diff --git a/src/traced_relay/relay_service_main.cc b/src/traced_relay/relay_service_main.cc index 50a6aa64db..89b1616cfe 100644 --- a/src/traced_relay/relay_service_main.cc +++ b/src/traced_relay/relay_service_main.cc @@ -115,20 +115,39 @@ static int RelayServiceMain(int argc, char** argv) { base::Daemonize([] { return 0; }); } - auto listen_socket = GetProducerSocket(); - remove(listen_socket); - if (!listen_socket_group.empty()) { - auto status = base::SetFilePermissions(listen_socket, listen_socket_group, - listen_socket_mode_bits); - if (!status.ok()) { - PERFETTO_ELOG("Failed to set socket permissions: %s", status.c_message()); - return 1; - } - } - base::UnixTaskRunner task_runner; auto svc = std::make_unique(&task_runner); - svc->Start(listen_socket, GetRelaySocket()); + + // traced_relay binds to the producer socket of the `traced` service. When + // built for Android, this socket is created and bound during init, and its + // file descriptor is passed through the environment variable. + const char* env_prod = getenv("ANDROID_SOCKET_traced_producer"); + base::ScopedFile producer_fd; + if (env_prod) { +#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) + PERFETTO_CHECK(false); +#else + auto opt_fd = base::CStringToInt32(env_prod); + if (opt_fd.has_value()) + producer_fd.reset(*opt_fd); + + svc->Start(std::move(producer_fd), GetRelaySocket()); +#endif + } else { + auto listen_socket = GetProducerSocket(); + remove(listen_socket); + if (!listen_socket_group.empty()) { + auto status = base::SetFilePermissions(listen_socket, listen_socket_group, + listen_socket_mode_bits); + if (!status.ok()) { + PERFETTO_ELOG("Failed to set socket permissions: %s", + status.c_message()); + return 1; + } + } + + svc->Start(listen_socket, GetRelaySocket()); + } // Set the CPU limit and start the watchdog running. The memory limit will // be set inside the service code as it relies on the size of buffers. diff --git a/src/tracing/OWNERS b/src/tracing/OWNERS index d3a35f887d..76c8b5cc30 100644 --- a/src/tracing/OWNERS +++ b/src/tracing/OWNERS @@ -1,5 +1,4 @@ # Knowledgeable people for C++ SDK & TRACE_EVENT headers. -skyostil@google.com eseckler@google.com nuskos@google.com diff --git a/src/tracing/internal/tracing_muxer_impl.cc b/src/tracing/internal/tracing_muxer_impl.cc index 8fd1d8a2b1..04832f1945 100644 --- a/src/tracing/internal/tracing_muxer_impl.cc +++ b/src/tracing/internal/tracing_muxer_impl.cc @@ -1333,6 +1333,7 @@ TracingMuxerImpl::FindDataSourceRes TracingMuxerImpl::SetupDataSourceImpl( internal_state->data_source = rds.factory(); internal_state->interceptor = nullptr; internal_state->interceptor_id = 0; + internal_state->will_notify_on_stop = rds.descriptor.will_notify_on_stop(); if (cfg.has_interceptor_config()) { for (size_t j = 0; j < interceptors_.size(); j++) { @@ -1545,6 +1546,7 @@ void TracingMuxerImpl::StopDataSource_AsyncEnd(TracingBackendId backend_id, const uint32_t mask = ~(1 << ds.instance_idx); ds.static_state->valid_instances.fetch_and(mask, std::memory_order_acq_rel); + bool will_notify_on_stop; // Take the mutex to prevent that the data source is in the middle of // a Trace() execution where it called GetDataSourceLocked() while we // destroy it. @@ -1558,6 +1560,7 @@ void TracingMuxerImpl::StopDataSource_AsyncEnd(TracingBackendId backend_id, ds.internal_state->interceptor.reset(); ds.internal_state->config.reset(); ds.internal_state->async_stop_in_progress = false; + will_notify_on_stop = ds.internal_state->will_notify_on_stop; startup_buffer_reservation = ds.internal_state->startup_target_buffer_reservation.load( std::memory_order_relaxed); @@ -1612,7 +1615,7 @@ void TracingMuxerImpl::StopDataSource_AsyncEnd(TracingBackendId backend_id, // Flush any commits that might have been batched by SharedMemoryArbiter. producer->service_->MaybeSharedMemoryArbiter() ->FlushPendingCommitDataRequests(); - if (instance_id) + if (instance_id && will_notify_on_stop) producer->service_->NotifyDataSourceStopped(instance_id); } producer->SweepDeadServices(); @@ -1856,7 +1859,7 @@ void TracingMuxerImpl::UpdateDataSourceOnAllBackends(RegisteredDataSource& rds, } rds.descriptor.set_will_notify_on_start(true); if (!rds.descriptor.has_will_notify_on_stop()) { - rds.descriptor.set_will_notify_on_stop(true); + rds.descriptor.set_will_notify_on_stop(true); } rds.descriptor.set_handles_incremental_state_clear(true); diff --git a/src/tracing/ipc/producer/producer_ipc_client_impl.cc b/src/tracing/ipc/producer/producer_ipc_client_impl.cc index ff0d1f142c..db1297c132 100644 --- a/src/tracing/ipc/producer/producer_ipc_client_impl.cc +++ b/src/tracing/ipc/producer/producer_ipc_client_impl.cc @@ -230,7 +230,7 @@ void ProducerIPCClientImpl::OnConnect() { std::move(on_cmd)); // If there are pending Sync() requests, send them now. - for (const auto& pending_sync : pending_sync_reqs_) + for (auto& pending_sync : pending_sync_reqs_) Sync(std::move(pending_sync)); pending_sync_reqs_.clear(); } diff --git a/src/tracing/service/tracing_service_impl.cc b/src/tracing/service/tracing_service_impl.cc index d66776730b..37dfc35ed6 100644 --- a/src/tracing/service/tracing_service_impl.cc +++ b/src/tracing/service/tracing_service_impl.cc @@ -24,6 +24,7 @@ #include #include #include +#include #include #include "perfetto/base/time.h" #include "perfetto/ext/tracing/core/client_identity.h" @@ -495,8 +496,6 @@ void TracingServiceImpl::DisconnectConsumer(ConsumerEndpointImpl* consumer) { PERFETTO_DLOG("Consumer %p disconnected", reinterpret_cast(consumer)); PERFETTO_DCHECK(consumers_.count(consumer)); - // TODO(primiano) : Check that this is safe (what happens if there are - // ReadBuffers() calls posted in the meantime? They need to become noop). if (consumer->tracing_session_id_) FreeBuffers(consumer->tracing_session_id_); // Will also DisableTracing(). consumers_.erase(consumer); @@ -778,6 +777,57 @@ base::Status TracingServiceImpl::EnableTracing(ConsumerEndpointImpl* consumer, } } + if (!cfg.session_semaphores().empty()) { + struct SemaphoreSessionsState { + uint64_t smallest_max_other_session_count = + std::numeric_limits::max(); + uint64_t session_count = 0; + }; + // For each semaphore, compute the number of active sessions and the + // MIN(limit). + std::unordered_map + sem_to_sessions_state; + for (const auto& id_and_session : tracing_sessions_) { + const auto& session = id_and_session.second; + if (session.state == TracingSession::CLONED_READ_ONLY || + session.state == TracingSession::DISABLED) { + // Don't consider cloned or disabled sessions in checks. + continue; + } + for (const auto& sem : session.config.session_semaphores()) { + auto& sessions_state = sem_to_sessions_state[sem.name()]; + sessions_state.smallest_max_other_session_count = + std::min(sessions_state.smallest_max_other_session_count, + sem.max_other_session_count()); + sessions_state.session_count++; + } + } + + // Check if any of the semaphores declared by the config clashes with any of + // the currently active semaphores. + for (const auto& semaphore : cfg.session_semaphores()) { + auto it = sem_to_sessions_state.find(semaphore.name()); + if (it == sem_to_sessions_state.end()) { + continue; + } + uint64_t max_other_session_count = + std::min(semaphore.max_other_session_count(), + it->second.smallest_max_other_session_count); + if (it->second.session_count > max_other_session_count) { + MaybeLogUploadEvent( + cfg, uuid, + PerfettoStatsdAtom:: + kTracedEnableTracingFailedSessionSemaphoreCheck); + return PERFETTO_SVC_ERR( + "Semaphore \"%s\" exceeds maximum allowed other session count " + "(%" PRIu64 " > min(%" PRIu64 ", %" PRIu64 "))", + semaphore.name().c_str(), it->second.session_count, + semaphore.max_other_session_count(), + it->second.smallest_max_other_session_count); + } + } + } + if (cfg.enable_extra_guardrails()) { // unique_session_name can be empty const std::string& name = cfg.unique_session_name(); @@ -1689,13 +1739,14 @@ void TracingServiceImpl::ActivateTriggers( tracing_session.config, tracing_session.trace_uuid, PerfettoStatsdAtom::kTracedTriggerCloneSnapshot, iter->name()); task_runner_->PostDelayedTask( - [weak_this, tsid] { + [weak_this, tsid, trigger_name = iter->name()] { if (!weak_this) return; auto* tsess = weak_this->GetTracingSession(tsid); if (!tsess || !tsess->consumer_maybe_null) return; - tsess->consumer_maybe_null->NotifyCloneSnapshotTrigger(); + tsess->consumer_maybe_null->NotifyCloneSnapshotTrigger( + trigger_name); }, iter->stop_delay_ms()); break; @@ -1880,7 +1931,7 @@ void TracingServiceImpl::NotifyFlushDoneForProducer( it++; } } // for (pending_flushes) - } // for (tracing_session) + } // for (tracing_session) } void TracingServiceImpl::OnFlushTimeout(TracingSessionID tsid, @@ -2427,7 +2478,7 @@ std::vector TracingServiceImpl::ReadBuffers( did_hit_threshold = packets_bytes >= threshold; packets.emplace_back(std::move(packet)); } // for(packets...) - } // for(buffers...) + } // for(buffers...) *has_more = did_hit_threshold; @@ -2642,10 +2693,23 @@ void TracingServiceImpl::FreeBuffers(TracingSessionID tsid) { bool is_long_trace = (tracing_session->config.write_into_file() && tracing_session->config.file_write_period_ms() < kMillisPerDay); + auto pending_clones = std::move(tracing_session->pending_clones); tracing_sessions_.erase(tsid); tracing_session = nullptr; UpdateMemoryGuardrail(); + for (const auto& id_to_clone_op : pending_clones) { + const PendingClone& clone_op = id_to_clone_op.second; + if (clone_op.weak_consumer) { + task_runner_->PostTask([weak_consumer = clone_op.weak_consumer] { + if (weak_consumer) { + weak_consumer->consumer_->OnSessionCloned( + {false, "Original session ended", {}}); + } + }); + } + } + PERFETTO_LOG("Tracing session %" PRIu64 " ended, total sessions:%zu", tsid, tracing_sessions_.size()); #if PERFETTO_BUILDFLAG(PERFETTO_ANDROID_BUILD) && \ @@ -3215,9 +3279,9 @@ void TracingServiceImpl::UpdateMemoryGuardrail() { // Sum up all the cloned traced buffers. for (const auto& id_to_ts : tracing_sessions_) { const TracingSession& ts = id_to_ts.second; - for (const auto& id_to_pending_clone : ts.pending_clones) { - const PendingClone& pending_clone = id_to_pending_clone.second; - for (const std::unique_ptr& buf : pending_clone.buffers) { + for (const auto& id_to_clone_op : ts.pending_clones) { + const PendingClone& clone_op = id_to_clone_op.second; + for (const std::unique_ptr& buf : clone_op.buffers) { if (buf) { total_buffer_bytes += buf->size(); } @@ -3490,8 +3554,8 @@ TraceStats TracingServiceImpl::GetTraceStats(TracingSession* tracing_session) { wri_stats->add_chunk_payload_histogram_sum(hist.GetBucketSum(i)); } } // for each sequence (writer). - } // for each buffer. - } // if (!disable_chunk_usage_histograms) + } // for each buffer. + } // if (!disable_chunk_usage_histograms) return trace_stats; } @@ -3556,6 +3620,21 @@ void TracingServiceImpl::EmitSystemInfo(std::vector* packets) { } else { PERFETTO_ELOG("Unable to read ro.build.version.sdk"); } + + std::string soc_model_value = base::GetAndroidProp("ro.soc.model"); + if (!soc_model_value.empty()) { + info->set_android_soc_model(soc_model_value); + } else { + PERFETTO_ELOG("Unable to read ro.soc.model"); + } + + std::string hw_rev_value = base::GetAndroidProp("ro.boot.hardware.revision"); + if (!hw_rev_value.empty()) { + info->set_android_hardware_revision(hw_rev_value); + } else { + PERFETTO_ELOG("Unable to read ro.boot.hardware.revision"); + } + #endif // PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) packet->set_trusted_uid(static_cast(uid_)); packet->set_trusted_packet_sequence_id(kServicePacketSequenceID); @@ -3707,10 +3786,11 @@ size_t TracingServiceImpl::PurgeExpiredAndCountTriggerInWindow( return trigger_count; } -void TracingServiceImpl::FlushAndCloneSession(ConsumerEndpointImpl* consumer, - TracingSessionID tsid, - bool skip_trace_filter, - bool for_bugreport) { +base::Status TracingServiceImpl::FlushAndCloneSession( + ConsumerEndpointImpl* consumer, + TracingSessionID tsid, + bool skip_trace_filter, + bool for_bugreport) { PERFETTO_DCHECK_THREAD(thread_checker_); auto clone_target = FlushFlags::CloneTarget::kUnknown; @@ -3723,9 +3803,8 @@ void TracingServiceImpl::FlushAndCloneSession(ConsumerEndpointImpl* consumer, PERFETTO_LOG("Looking for sessions for bugreport"); TracingSession* session = FindTracingSessionWithMaxBugreportScore(); if (!session) { - consumer->consumer_->OnSessionCloned( - {false, "No tracing sessions eligible for bugreport found", {}}); - return; + return base::ErrStatus( + "No tracing sessions eligible for bugreport found"); } tsid = session->id; clone_target = FlushFlags::CloneTarget::kBugreport; @@ -3737,9 +3816,14 @@ void TracingServiceImpl::FlushAndCloneSession(ConsumerEndpointImpl* consumer, TracingSession* session = GetTracingSession(tsid); if (!session) { - consumer->consumer_->OnSessionCloned( - {false, "Tracing session not found", {}}); - return; + return base::ErrStatus("Tracing session not found"); + } + + // Skip the UID check for sessions marked with a bugreport_score > 0. + // Those sessions, by design, can be stolen by any other consumer for the + // sake of creating snapshots for bugreports. + if (!session->IsCloneAllowed(consumer->uid_)) { + return PERFETTO_SVC_ERR("Not allowed to clone a session from another UID"); } // If any of the buffers are marked as clear_before_clone, reset them before @@ -3771,9 +3855,8 @@ void TracingServiceImpl::FlushAndCloneSession(ConsumerEndpointImpl* consumer, // We cannot leave the original tracing session buffer-less as it would // cause crashes when data sources commit new data. buf = std::move(old_buf); - consumer->consumer_->OnSessionCloned( - {false, "Buffer allocation failed while attempting to clone", {}}); - return; + return base::ErrStatus( + "Buffer allocation failed while attempting to clone"); } } @@ -3819,6 +3902,8 @@ void TracingServiceImpl::FlushAndCloneSession(ConsumerEndpointImpl* consumer, FlushFlags(FlushFlags::Initiator::kTraced, FlushFlags::Reason::kTraceClone, clone_target)); } + + return base::OkStatus(); } std::map> @@ -3956,13 +4041,6 @@ base::Status TracingServiceImpl::FinishCloneSession( "The consumer is already attached to another tracing session"); } - // Skip the UID check for sessions marked with a bugreport_score > 0. - // Those sessions, by design, can be stolen by any other consumer for the - // sake of creating snapshots for bugreports. - if (!src->IsCloneAllowed(consumer->uid_)) { - return PERFETTO_SVC_ERR("Not allowed to clone a session from another UID"); - } - std::vector buf_ids = buffer_ids_.AllocateMultiple(buf_snaps.size()); if (buf_ids.size() != buf_snaps.size()) { @@ -4265,13 +4343,15 @@ void TracingServiceImpl::ConsumerEndpointImpl::OnAllDataSourcesStarted() { observable_events->set_all_data_sources_started(true); } -void TracingServiceImpl::ConsumerEndpointImpl::NotifyCloneSnapshotTrigger() { +void TracingServiceImpl::ConsumerEndpointImpl::NotifyCloneSnapshotTrigger( + const std::string& trigger_name) { if (!(observable_events_mask_ & ObservableEvents::TYPE_CLONE_TRIGGER_HIT)) { return; } auto* observable_events = AddObservableEvents(); auto* clone_trig = observable_events->mutable_clone_trigger_hit(); clone_trig->set_tracing_session_id(static_cast(tracing_session_id_)); + clone_trig->set_trigger_name(trigger_name); } ObservableEvents* @@ -4401,8 +4481,12 @@ void TracingServiceImpl::ConsumerEndpointImpl::CloneSession( CloneSessionArgs args) { PERFETTO_DCHECK_THREAD(thread_checker_); // FlushAndCloneSession will call OnSessionCloned after the async flush. - service_->FlushAndCloneSession(this, tsid, args.skip_trace_filter, - args.for_bugreport); + base::Status result = service_->FlushAndCloneSession( + this, tsid, args.skip_trace_filter, args.for_bugreport); + + if (!result.ok()) { + consumer_->OnSessionCloned({false, result.message(), {}}); + } } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/tracing/service/tracing_service_impl.h b/src/tracing/service/tracing_service_impl.h index 9fa84fe5a7..64b1f506e6 100644 --- a/src/tracing/service/tracing_service_impl.h +++ b/src/tracing/service/tracing_service_impl.h @@ -32,6 +32,7 @@ #include "perfetto/base/time.h" #include "perfetto/ext/base/circular_queue.h" #include "perfetto/ext/base/periodic_task.h" +#include "perfetto/ext/base/string_view.h" #include "perfetto/ext/base/uuid.h" #include "perfetto/ext/base/weak_ptr.h" #include "perfetto/ext/tracing/core/basic_types.h" @@ -211,7 +212,7 @@ class TracingServiceImpl : public TracingService { ~ConsumerEndpointImpl() override; void NotifyOnTracingDisabled(const std::string& error); - void NotifyCloneSnapshotTrigger(); + void NotifyCloneSnapshotTrigger(const std::string& trigger_name); // TracingService::ConsumerEndpoint implementation. void EnableTracing(const TraceConfig&, base::ScopedFile) override; @@ -351,10 +352,10 @@ class TracingServiceImpl : public TracingService { ConsumerEndpoint::FlushCallback, FlushFlags); void FlushAndDisableTracing(TracingSessionID); - void FlushAndCloneSession(ConsumerEndpointImpl*, - TracingSessionID, - bool skip_filter, - bool for_bugreport); + base::Status FlushAndCloneSession(ConsumerEndpointImpl*, + TracingSessionID, + bool skip_filter, + bool for_bugreport); // Starts reading the internal tracing buffers from the tracing session `tsid` // and sends them to `*consumer` (which must be != nullptr). diff --git a/src/tracing/service/tracing_service_impl_unittest.cc b/src/tracing/service/tracing_service_impl_unittest.cc index 9e54d57296..776d56c771 100644 --- a/src/tracing/service/tracing_service_impl_unittest.cc +++ b/src/tracing/service/tracing_service_impl_unittest.cc @@ -16,22 +16,43 @@ #include "src/tracing/service/tracing_service_impl.h" -#include - +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "perfetto/base/build_config.h" +#include "perfetto/base/logging.h" +#include "perfetto/base/proc_utils.h" +#include "perfetto/base/time.h" #include "perfetto/ext/base/file_utils.h" #include "perfetto/ext/base/string_utils.h" #include "perfetto/ext/base/sys_types.h" #include "perfetto/ext/base/temp_file.h" #include "perfetto/ext/base/utils.h" +#include "perfetto/ext/base/uuid.h" +#include "perfetto/ext/tracing/core/basic_types.h" #include "perfetto/ext/tracing/core/client_identity.h" #include "perfetto/ext/tracing/core/consumer.h" -#include "perfetto/ext/tracing/core/observable_events.h" #include "perfetto/ext/tracing/core/producer.h" #include "perfetto/ext/tracing/core/shared_memory.h" -#include "perfetto/ext/tracing/core/trace_packet.h" +#include "perfetto/ext/tracing/core/shared_memory_abi.h" #include "perfetto/ext/tracing/core/trace_writer.h" -#include "perfetto/tracing/core/data_source_config.h" -#include "perfetto/tracing/core/data_source_descriptor.h" +#include "perfetto/ext/tracing/core/tracing_service.h" +#include "perfetto/protozero/contiguous_memory_range.h" +#include "perfetto/protozero/message_arena.h" +#include "perfetto/protozero/scattered_stream_writer.h" +#include "perfetto/tracing/buffer_exhausted_policy.h" +#include "perfetto/tracing/core/flush_flags.h" #include "perfetto/tracing/core/forward_decls.h" #include "protos/perfetto/common/builtin_clock.gen.h" #include "protos/perfetto/trace/clock_snapshot.gen.h" @@ -65,6 +86,7 @@ using ::testing::AssertionFailure; using ::testing::AssertionResult; using ::testing::AssertionSuccess; using ::testing::Contains; +using ::testing::ContainsRegex; using ::testing::DoAll; using ::testing::Each; using ::testing::ElementsAreArray; @@ -116,6 +138,12 @@ MATCHER_P(HasTriggerMode, mode, "") { return HasTriggerModeInternal(arg, mode); } +MATCHER_P(LowerCase, + m, + "Lower case " + testing::DescribeMatcher(m, negation)) { + return ExplainMatchResult(m, base::ToLower(arg), result_listener); +} + #if PERFETTO_BUILDFLAG(PERFETTO_ZLIB) std::string Decompress(const std::string& data) { uint8_t out[1024]; @@ -3202,10 +3230,6 @@ TEST_F(TracingServiceImplTest, AllowedBuffers) { // (data_source2.2). EXPECT_EQ(expected_buffers_producer2, GetAllowedTargetBuffers(producer2_id)); - // Calling StartTracing() should be a noop (% a DLOG statement) because the - // trace config didn't have the |deferred_start| flag set. - consumer->StartTracing(); - consumer->DisableTracing(); producer1->WaitForDataSourceStop("data_source1"); producer2->WaitForDataSourceStop("data_source2.1"); @@ -3245,10 +3269,6 @@ TEST_F(TracingServiceImplTest, CommitToForbiddenBufferIsDiscarded) { producer->WaitForDataSourceSetup("data_source"); producer->WaitForDataSourceStart("data_source"); - // Calling StartTracing() should be a noop (% a DLOG statement) because the - // trace config didn't have the |deferred_start| flag set. - consumer->StartTracing(); - // Try to write to the correct buffer. std::unique_ptr writer = producer->endpoint()->CreateTraceWriter( tracing_session()->buffers_index[0]); @@ -3313,10 +3333,6 @@ TEST_F(TracingServiceImplTest, RegisterAndUnregisterTraceWriter) { producer->WaitForDataSourceSetup("data_source"); producer->WaitForDataSourceStart("data_source"); - // Calling StartTracing() should be a noop (% a DLOG statement) because the - // trace config didn't have the |deferred_start| flag set. - consumer->StartTracing(); - // Creating the trace writer should register it with the service. std::unique_ptr writer = producer->endpoint()->CreateTraceWriter( tracing_session()->buffers_index[0]); @@ -3374,10 +3390,6 @@ TEST_F(TracingServiceImplTest, ScrapeBuffersOnFlush) { producer->WaitForDataSourceSetup("data_source"); producer->WaitForDataSourceStart("data_source"); - // Calling StartTracing() should be a noop (% a DLOG statement) because the - // trace config didn't have the |deferred_start| flag set. - consumer->StartTracing(); - std::unique_ptr writer = producer->endpoint()->CreateTraceWriter( tracing_session()->buffers_index[0]); WaitForTraceWritersChanged(producer_id); @@ -3464,36 +3476,40 @@ TEST_F(TracingServiceImplTest, ScrapeBuffersFromAnotherThread) { producer->WaitForTracingSetup(); producer->WaitForDataSourceSetup("data_source"); producer->WaitForDataSourceStart("data_source"); - consumer->StartTracing(); std::unique_ptr writer = producer->endpoint()->CreateTraceWriter( - tracing_session()->buffers_index[0]); + tracing_session()->buffers_index[0], BufferExhaustedPolicy::kDrop); WaitForTraceWritersChanged(producer_id); - constexpr int kPacketCount = 10; - std::atomic packets_written{}; + std::atomic packets_written = false; + std::atomic quit = false; std::thread writer_thread([&] { - for (int i = 0; i < kPacketCount; i++) { + while (!quit.load(std::memory_order_acquire)) { writer->NewTracePacket()->set_for_testing()->set_str("payload"); - packets_written.store(i, std::memory_order_relaxed); + packets_written.store(true, std::memory_order_release); + std::this_thread::yield(); } }); // Wait until the thread has had some time to write some packets. - while (packets_written.load(std::memory_order_relaxed) < kPacketCount / 2) - base::SleepMicroseconds(5000); + while (packets_written.load(std::memory_order_acquire) == false) + std::this_thread::yield(); // Disabling tracing will trigger scraping. consumer->DisableTracing(); + + producer->WaitForDataSourceStop("data_source"); + consumer->WaitForTracingDisabled(); + quit.store(true, std::memory_order_release); writer_thread.join(); // Because we don't synchronize with the producer thread, we can't make any // guarantees about the number of packets we will successfully read. We just // verify that no TSAN races are reported. - consumer->ReadBuffers(); - - producer->WaitForDataSourceStop("data_source"); - consumer->WaitForTracingDisabled(); + std::vector packets = consumer->ReadBuffers(); + EXPECT_THAT(packets, Contains(Property(&protos::gen::TracePacket::for_testing, + Property(&protos::gen::TestEvent::str, + Eq("payload"))))); } // Test scraping on producer disconnect. @@ -3519,10 +3535,6 @@ TEST_F(TracingServiceImplTest, ScrapeBuffersOnProducerDisconnect) { producer->WaitForDataSourceSetup("data_source"); producer->WaitForDataSourceStart("data_source"); - // Calling StartTracing() should be a noop (% a DLOG statement) because the - // trace config didn't have the |deferred_start| flag set. - consumer->StartTracing(); - std::unique_ptr writer = producer->endpoint()->CreateTraceWriter( tracing_session()->buffers_index[0]); WaitForTraceWritersChanged(producer_id); @@ -3582,10 +3594,6 @@ TEST_F(TracingServiceImplTest, ScrapeBuffersOnDisable) { producer->WaitForDataSourceSetup("data_source"); producer->WaitForDataSourceStart("data_source"); - // Calling StartTracing() should be a noop (% a DLOG statement) because the - // trace config didn't have the |deferred_start| flag set. - consumer->StartTracing(); - std::unique_ptr writer = producer->endpoint()->CreateTraceWriter( tracing_session()->buffers_index[0]); WaitForTraceWritersChanged(producer_id); @@ -5221,7 +5229,7 @@ TEST_F(TracingServiceImplTest, CloneMainSessionStopped) { } TEST_F(TracingServiceImplTest, CloneConsumerDisconnect) { - // The consumer the creates the initial tracing session. + // The consumer that creates the initial tracing session. std::unique_ptr consumer = CreateMockConsumer(); consumer->Connect(svc.get()); @@ -5276,6 +5284,76 @@ TEST_F(TracingServiceImplTest, CloneConsumerDisconnect) { consumer->WaitForTracingDisabled(); } +TEST_F(TracingServiceImplTest, CloneMainSessionGoesAwayDuringFlush) { + // The consumer that creates the initial tracing session. + std::unique_ptr consumer = CreateMockConsumer(); + consumer->Connect(svc.get()); + + std::unique_ptr producer1 = CreateMockProducer(); + producer1->Connect(svc.get(), "mock_producer1"); + producer1->RegisterDataSource("ds_1"); + + TraceConfig trace_config; + trace_config.add_buffers()->set_size_kb(1024); // Buf 0. + auto* ds_cfg = trace_config.add_data_sources()->mutable_config(); + ds_cfg->set_name("ds_1"); + ds_cfg->set_target_buffer(0); + + consumer->EnableTracing(trace_config); + producer1->WaitForTracingSetup(); + producer1->WaitForDataSourceSetup("ds_1"); + producer1->WaitForDataSourceStart("ds_1"); + + std::unique_ptr writer1 = producer1->CreateTraceWriter("ds_1"); + + { + auto tp = writer1->NewTracePacket(); + tp->set_for_testing()->set_str("buf1_beforeflush"); + } + writer1->Flush(); + + std::unique_ptr clone_consumer = CreateMockConsumer(); + clone_consumer->Connect(svc.get()); + + std::string clone_done_name = "consumer1_clone_done"; + auto clone_done = task_runner.CreateCheckpoint(clone_done_name); + EXPECT_CALL(*clone_consumer, OnSessionCloned) + .Times(1) + .WillOnce(Invoke([&](const Consumer::OnSessionClonedArgs& args) { + EXPECT_FALSE(args.success); + EXPECT_THAT(args.error, HasSubstr("Original session ended")); + clone_done(); + })); + clone_consumer->CloneSession(1); + + std::string producer1_flush_checkpoint_name = "producer1_flush_requested"; + auto flush1_requested = + task_runner.CreateCheckpoint(producer1_flush_checkpoint_name); + FlushRequestID flush1_req_id; + + // CloneSession() will issue a flush. + EXPECT_CALL(*producer1, Flush(_, _, _, _)) + .WillOnce([&](FlushRequestID flush_id, const DataSourceInstanceID*, + size_t, FlushFlags) { + flush1_req_id = flush_id; + flush1_requested(); + }); + + task_runner.RunUntilCheckpoint(producer1_flush_checkpoint_name); + + // The main session goes away. + consumer->DisableTracing(); + producer1->WaitForDataSourceStop("ds_1"); + consumer->WaitForTracingDisabled(); + consumer.reset(); + + task_runner.RunUntilCheckpoint(clone_done_name); + + // producer1 replies to flush much later. + producer1->endpoint()->NotifyFlushComplete(flush1_req_id); + task_runner.RunUntilIdle(); +} + TEST_F(TracingServiceImplTest, CloneTransferFlush) { // The consumer the creates the initial tracing session. std::unique_ptr consumer = CreateMockConsumer(); @@ -5757,4 +5835,205 @@ TEST_F(TracingServiceImplTest, RelayEndpointDisconnect) { ASSERT_FALSE(clock_sync_packet_seen); } +TEST_F(TracingServiceImplTest, SessionSemaphoreMutexSingleSession) { + TraceConfig trace_config; + trace_config.add_buffers()->set_size_kb(32); // Buf 0. + trace_config.add_session_semaphores()->set_name("mutex"); + + std::unique_ptr producer = CreateMockProducer(); + producer->Connect(svc.get(), "mock_producer"); + + std::unique_ptr consumer = CreateMockConsumer(); + consumer->Connect(svc.get()); + consumer->EnableTracing(trace_config); + consumer->DisableTracing(); + consumer->WaitForTracingDisabledWithError(IsEmpty()); +} + +TEST_F(TracingServiceImplTest, SessionSemaphoreMutexMultipleSession) { + TraceConfig trace_config; + trace_config.add_buffers()->set_size_kb(32); + trace_config.add_session_semaphores()->set_name("mutex"); + + std::unique_ptr consumer = CreateMockConsumer(); + consumer->Connect(svc.get()); + consumer->EnableTracing(trace_config); + + std::unique_ptr consumer2 = CreateMockConsumer(); + consumer2->Connect(svc.get()); + consumer2->EnableTracing(trace_config); + consumer2->WaitForTracingDisabledWithError(LowerCase(HasSubstr("semaphore"))); + + consumer->DisableTracing(); + consumer->WaitForTracingDisabledWithError(IsEmpty()); +} + +TEST_F(TracingServiceImplTest, SessionSemaphoreHigherCurrentFails) { + TraceConfig trace_config; + trace_config.add_buffers()->set_size_kb(32); + + auto* session_semaphore = trace_config.add_session_semaphores(); + session_semaphore->set_name("diff_value_semaphore"); + session_semaphore->set_max_other_session_count(0); + + std::unique_ptr consumer = CreateMockConsumer(); + consumer->Connect(svc.get()); + consumer->EnableTracing(trace_config); + + // The second consumer sets a higher count. + session_semaphore->set_max_other_session_count(1); + + std::unique_ptr consumer2 = CreateMockConsumer(); + consumer2->Connect(svc.get()); + consumer2->EnableTracing(trace_config); + consumer2->WaitForTracingDisabledWithError(LowerCase(HasSubstr("semaphore"))); + + consumer->DisableTracing(); + consumer->WaitForTracingDisabledWithError(IsEmpty()); +} + +TEST_F(TracingServiceImplTest, SessionSemaphoreHigherPreviousFails) { + TraceConfig trace_config; + trace_config.add_buffers()->set_size_kb(32); + + auto* session_semaphore = trace_config.add_session_semaphores(); + session_semaphore->set_name("diff_value_semaphore"); + session_semaphore->set_max_other_session_count(1); + + std::unique_ptr consumer = CreateMockConsumer(); + consumer->Connect(svc.get()); + consumer->EnableTracing(trace_config); + + // The second consumer sets a lower count. + session_semaphore->set_max_other_session_count(0); + + std::unique_ptr consumer2 = CreateMockConsumer(); + consumer2->Connect(svc.get()); + consumer2->EnableTracing(trace_config); + consumer2->WaitForTracingDisabledWithError(LowerCase(HasSubstr("semaphore"))); + + consumer->DisableTracing(); + consumer->WaitForTracingDisabledWithError(IsEmpty()); +} + +TEST_F(TracingServiceImplTest, SessionSemaphoreAllowedUpToLimit) { + TraceConfig trace_config; + trace_config.add_buffers()->set_size_kb(32); + + auto* session_semaphore = trace_config.add_session_semaphores(); + session_semaphore->set_name("multi_semaphore"); + session_semaphore->set_max_other_session_count(3); + + std::unique_ptr consumer = CreateMockConsumer(); + consumer->Connect(svc.get()); + consumer->EnableTracing(trace_config); + + std::unique_ptr consumer2 = CreateMockConsumer(); + consumer2->Connect(svc.get()); + consumer2->EnableTracing(trace_config); + + std::unique_ptr consumer3 = CreateMockConsumer(); + consumer3->Connect(svc.get()); + consumer3->EnableTracing(trace_config); + + std::unique_ptr consumer4 = CreateMockConsumer(); + consumer4->Connect(svc.get()); + consumer4->EnableTracing(trace_config); + + std::unique_ptr consumer5 = CreateMockConsumer(); + consumer5->Connect(svc.get()); + consumer5->EnableTracing(trace_config); + consumer5->WaitForTracingDisabledWithError(LowerCase(HasSubstr("semaphore"))); + + consumer4->DisableTracing(); + consumer4->WaitForTracingDisabledWithError(IsEmpty()); + + consumer3->DisableTracing(); + consumer3->WaitForTracingDisabledWithError(IsEmpty()); + + consumer2->DisableTracing(); + consumer2->WaitForTracingDisabledWithError(IsEmpty()); + + consumer->DisableTracing(); + consumer->WaitForTracingDisabledWithError(IsEmpty()); +} + +TEST_F(TracingServiceImplTest, DetachAttach) { + std::unique_ptr consumer = CreateMockConsumer(); + consumer->Connect(svc.get()); + + std::unique_ptr producer = CreateMockProducer(); + producer->Connect(svc.get(), "mock_producer"); + producer->RegisterDataSource("data_source"); + + TraceConfig trace_config; + trace_config.add_buffers()->set_size_kb(128); + auto* ds_config = trace_config.add_data_sources()->mutable_config(); + ds_config->set_name("data_source"); + ds_config->set_target_buffer(0); + consumer->EnableTracing(trace_config); + + producer->WaitForTracingSetup(); + producer->WaitForDataSourceSetup("data_source"); + producer->WaitForDataSourceStart("data_source"); + + std::string on_detach_name = "on_detach"; + auto on_detach = task_runner.CreateCheckpoint(on_detach_name); + EXPECT_CALL(*consumer, OnDetach(Eq(true))).WillOnce(Invoke(on_detach)); + + consumer->Detach("mykey"); + + task_runner.RunUntilCheckpoint(on_detach_name); + + consumer.reset(); + + std::unique_ptr writer = + producer->CreateTraceWriter("data_source"); + { + auto tp = writer->NewTracePacket(); + tp->set_for_testing()->set_str("payload-1"); + } + { + auto tp = writer->NewTracePacket(); + tp->set_for_testing()->set_str("payload-2"); + } + + writer->Flush(); + writer.reset(); + + consumer = CreateMockConsumer(); + consumer->Connect(svc.get()); + + TraceConfig attached_config; + std::string on_attach_name = "on_attach"; + auto on_attach = task_runner.CreateCheckpoint(on_attach_name); + EXPECT_CALL(*consumer, OnAttach(Eq(true), _)) + .WillOnce(Invoke([&](bool, const TraceConfig& cfg) { + attached_config = cfg; + on_attach(); + })); + + consumer->Attach("mykey"); + + task_runner.RunUntilCheckpoint(on_attach_name); + + EXPECT_EQ(attached_config, trace_config); + + consumer->DisableTracing(); + producer->WaitForDataSourceStop("data_source"); + consumer->WaitForTracingDisabled(); + + std::vector packets = consumer->ReadBuffers(); + EXPECT_THAT(packets, Not(IsEmpty())); + EXPECT_THAT( + packets, + Each(Property(&protos::gen::TracePacket::has_compressed_packets, false))); + EXPECT_THAT(packets, Contains(Property(&protos::gen::TracePacket::for_testing, + Property(&protos::gen::TestEvent::str, + Eq("payload-1"))))); + EXPECT_THAT(packets, Contains(Property(&protos::gen::TracePacket::for_testing, + Property(&protos::gen::TestEvent::str, + Eq("payload-2"))))); +} + } // namespace perfetto diff --git a/src/tracing/test/mock_consumer.cc b/src/tracing/test/mock_consumer.cc index 3482fbe361..0171bead81 100644 --- a/src/tracing/test/mock_consumer.cc +++ b/src/tracing/test/mock_consumer.cc @@ -82,11 +82,13 @@ void MockConsumer::CloneSession(TracingSessionID tsid) { service_endpoint_->CloneSession(tsid, {}); } -void MockConsumer::WaitForTracingDisabled(uint32_t timeout_ms) { +void MockConsumer::WaitForTracingDisabledWithError( + const testing::Matcher& error_matcher, + uint32_t timeout_ms) { static int i = 0; auto checkpoint_name = "on_tracing_disabled_consumer_" + std::to_string(i++); auto on_tracing_disabled = task_runner_->CreateCheckpoint(checkpoint_name); - EXPECT_CALL(*this, OnTracingDisabled(_)) + EXPECT_CALL(*this, OnTracingDisabled(error_matcher)) .WillOnce(testing::InvokeWithoutArgs(on_tracing_disabled)); task_runner_->RunUntilCheckpoint(checkpoint_name, timeout_ms); } @@ -198,4 +200,12 @@ TracingServiceState MockConsumer::QueryServiceState() { return res; } +void MockConsumer::Detach(std::string key) { + service_endpoint_->Detach(key); +} + +void MockConsumer::Attach(std::string key) { + service_endpoint_->Attach(key); +} + } // namespace perfetto diff --git a/src/tracing/test/mock_consumer.h b/src/tracing/test/mock_consumer.h index b7d8d0a4cb..1095e3e1f4 100644 --- a/src/tracing/test/mock_consumer.h +++ b/src/tracing/test/mock_consumer.h @@ -18,6 +18,7 @@ #define SRC_TRACING_TEST_MOCK_CONSUMER_H_ #include +#include #include "perfetto/ext/tracing/core/consumer.h" #include "perfetto/ext/tracing/core/trace_packet.h" @@ -52,10 +53,17 @@ class MockConsumer : public Consumer { void ForceDisconnect(); void EnableTracing(const TraceConfig&, base::ScopedFile = base::ScopedFile()); void StartTracing(); + void Detach(std::string key); + void Attach(std::string key); void ChangeTraceConfig(const TraceConfig&); void DisableTracing(); void FreeBuffers(); - void WaitForTracingDisabled(uint32_t timeout_ms = 3000); + void WaitForTracingDisabled(uint32_t timeout_ms = 3000) { + return WaitForTracingDisabledWithError(testing::_, timeout_ms); + } + void WaitForTracingDisabledWithError( + const testing::Matcher& error_matcher, + uint32_t timeout_ms = 3000); FlushRequest Flush( uint32_t timeout_ms = 10000, FlushFlags = FlushFlags(FlushFlags::Initiator::kConsumerSdk, diff --git a/src/websocket_bridge/websocket_bridge.cc b/src/websocket_bridge/websocket_bridge.cc index bf0708c52f..2462814d95 100644 --- a/src/websocket_bridge/websocket_bridge.cc +++ b/src/websocket_bridge/websocket_bridge.cc @@ -23,8 +23,8 @@ #include #include -#include "perfetto/ext/base/string_utils.h" #include "perfetto/ext/base/http/http_server.h" +#include "perfetto/ext/base/string_utils.h" #include "perfetto/ext/base/unix_socket.h" #include "perfetto/ext/base/unix_task_runner.h" #include "perfetto/tracing/default_socket.h" @@ -86,9 +86,11 @@ void WSBridge::Main(int, char**) { } else { adb_socket_endpoint = "127.0.0.1:5037"; } - PERFETTO_LOG("[WSBridge] adb server socket is:%s.", adb_socket_endpoint.c_str()); + PERFETTO_LOG("[WSBridge] adb server socket is:%s.", + adb_socket_endpoint.c_str()); endpoints_.push_back({"/traced", GetConsumerSocket(), kTracedFamily}); - endpoints_.push_back({"/adb", adb_socket_endpoint.c_str(), base::SockFamily::kInet}); + endpoints_.push_back( + {"/adb", adb_socket_endpoint.c_str(), base::SockFamily::kInet}); base::HttpServer srv(&task_runner_, this); srv.AddAllowedOrigin("http://localhost:10000"); diff --git a/test/.gitignore b/test/.gitignore index 8280df0bf7..5a1a1646ae 100644 --- a/test/.gitignore +++ b/test/.gitignore @@ -14,7 +14,19 @@ !/data/chrome/*.sha256 !/data/chrome/README.md +!/data/heap_graph +/data/heap_graph/* +!/data/heap_graph/*.sha256 + !/data/parser /data/parser/* !/data/parser/*.sha256 !/data/parser/README.md + +!/data/simpleperf +/data/simpleperf/* +!/data/simpleperf/*.sha256 + +!/data/zip +/data/zip/* +!/data/zip/*.sha256 \ No newline at end of file diff --git a/test/ci/android_tests.sh b/test/ci/android_tests.sh index da3d645903..2a626f299a 100755 --- a/test/ci/android_tests.sh +++ b/test/ci/android_tests.sh @@ -13,9 +13,15 @@ # See the License for the specific language governing permissions and # limitations under the License. -INSTALL_BUILD_DEPS_ARGS="--android" source $(dirname ${BASH_SOURCE[0]})/common.sh +# Save CI time by skipping runs on {UI,docs,infra}-only changes +if [[ $UI_DOCS_INFRA_ONLY_CHANGE == 1 ]]; then +echo "Detected non-code change, probably a UI-only change." +echo "skipping build + test runs" +exit 0 +fi + # Run the emulator earlier so by the time we build it's booted. # tools/run_android_test will perform a wait-for-device. This is just an # optimization to remove bubbles in the pipeline. diff --git a/test/ci/bazel_tests.sh b/test/ci/bazel_tests.sh index 012f0d072b..54f5c6e5ef 100755 --- a/test/ci/bazel_tests.sh +++ b/test/ci/bazel_tests.sh @@ -16,6 +16,13 @@ INSTALL_BUILD_DEPS_ARGS="" source $(dirname ${BASH_SOURCE[0]})/common.sh +# Save CI time by skipping runs on {UI,docs,infra}-only changes +if [[ $UI_DOCS_INFRA_ONLY_CHANGE == 1 ]]; then +echo "Detected non-code change, probably a UI-only change." +echo "skipping build + test runs" +exit 0 +fi + bazel build //:all --verbose_failures bazel build //python:all --verbose_failures diff --git a/test/ci/common.sh b/test/ci/common.sh index 5320ddcb9e..c28dff6360 100644 --- a/test/ci/common.sh +++ b/test/ci/common.sh @@ -20,7 +20,7 @@ OUT_PATH="out/dist" export PYTHONUNBUFFERED=1 -tools/install-build-deps $INSTALL_BUILD_DEPS_ARGS +tools/install-build-deps $PERFETTO_INSTALL_BUILD_DEPS_ARGS # Assumes Linux. Windows should use /win/clang instead. if [[ -e buildtools/linux64/clang/bin/llvm-symbolizer ]]; then @@ -35,4 +35,10 @@ rm -rf out/tmp.protoc # Performs checks on SQL files. tools/check_sql_modules.py -tools/check_sql_metrics.py \ No newline at end of file +tools/check_sql_metrics.py + +if !(git diff --name-only HEAD^1 HEAD | egrep -qv '^(ui|docs|infra)/'); then +export UI_DOCS_INFRA_ONLY_CHANGE=1 +else +export UI_DOCS_INFRA_ONLY_CHANGE=0 +fi diff --git a/test/ci/fuzzer_tests.sh b/test/ci/fuzzer_tests.sh index 9eaa6846c3..bb7889cb16 100755 --- a/test/ci/fuzzer_tests.sh +++ b/test/ci/fuzzer_tests.sh @@ -16,6 +16,13 @@ INSTALL_BUILD_DEPS_ARGS="" source $(dirname ${BASH_SOURCE[0]})/common.sh +# Save CI time by skipping runs on {UI,docs,infra}-only changes +if [[ $UI_DOCS_INFRA_ONLY_CHANGE == 1 ]]; then +echo "Detected non-code change, probably a UI-only change." +echo "skipping build + test runs" +exit 0 +fi + tools/gn gen ${OUT_PATH} --args="${PERFETTO_TEST_GN_ARGS}" --check tools/ninja -C ${OUT_PATH} ${PERFETTO_TEST_NINJA_ARGS} fuzzers diff --git a/test/ci/linux_tests.sh b/test/ci/linux_tests.sh index 1a68291870..0492ba1e99 100755 --- a/test/ci/linux_tests.sh +++ b/test/ci/linux_tests.sh @@ -13,9 +13,16 @@ # See the License for the specific language governing permissions and # limitations under the License. -INSTALL_BUILD_DEPS_ARGS="" source $(dirname ${BASH_SOURCE[0]})/common.sh + +# Save CI time by skipping runs on {UI,docs,infra}-only changes +if [[ $UI_DOCS_INFRA_ONLY_CHANGE == 1 ]]; then +echo "Detected non-code change, probably a UI-only change." +echo "skipping build + test runs" +exit 0 +fi + tools/gn gen ${OUT_PATH} --args="${PERFETTO_TEST_GN_ARGS}" --check tools/ninja -C ${OUT_PATH} ${PERFETTO_TEST_NINJA_ARGS} @@ -30,18 +37,18 @@ ${OUT_PATH}/trace_processor_minimal_smoke_tests # that is copied into the target directory (OUT_PATH) cannot run because depends # on libc++.so within the same folder (which is built using target bitness, # not host bitness). -TP_SHELL=${OUT_PATH}/gcc_like_host/trace_processor_shell -if [ ! -f ${TP_SHELL} ]; then - TP_SHELL=${OUT_PATH}/trace_processor_shell +HOST_OUT_PATH=${OUT_PATH}/gcc_like_host +if [ ! -f ${HOST_OUT_PATH}/trace_processor_shell ]; then + HOST_OUT_PATH=${OUT_PATH} fi mkdir -p /ci/artifacts/perf tools/diff_test_trace_processor.py \ --perf-file=/ci/artifacts/perf/tp-perf-all.json \ - ${TP_SHELL} + ${HOST_OUT_PATH}/trace_processor_shell -python/run_tests.py ${TP_SHELL} +python/run_tests.py ${HOST_OUT_PATH} # Don't run benchmarks under x86 (running out of address space because of 4GB) # limit or debug (too slow and pointless). diff --git a/test/ci/ui_tests.sh b/test/ci/ui_tests.sh index 75418f8129..50b0d7fa8d 100755 --- a/test/ci/ui_tests.sh +++ b/test/ci/ui_tests.sh @@ -13,7 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -INSTALL_BUILD_DEPS_ARGS="--ui" source $(dirname ${BASH_SOURCE[0]})/common.sh infra/perfetto.dev/build diff --git a/test/cts/heapprofd_java_test_cts.cc b/test/cts/heapprofd_java_test_cts.cc index 1178c3e278..ff0e7590c8 100644 --- a/test/cts/heapprofd_java_test_cts.cc +++ b/test/cts/heapprofd_java_test_cts.cc @@ -108,8 +108,9 @@ std::vector ProfileRuntime(std::string app_name) { return helper.trace(); } -std::vector TriggerOomHeapDump(std::string app_name, - std::string heap_dump_target) { +std::vector TriggerOomHeapDump( + std::string app_name, + std::string heap_dump_target) { base::TestTaskRunner task_runner; // (re)start the target app's main activity @@ -129,7 +130,8 @@ std::vector TriggerOomHeapDump(std::string app_name, trace_config.set_data_source_stop_timeout_ms(60000); auto* trigger_config = trace_config.mutable_trigger_config(); - trigger_config->set_trigger_mode(perfetto::protos::gen::TraceConfig::TriggerConfig::START_TRACING); + trigger_config->set_trigger_mode( + perfetto::protos::gen::TraceConfig::TriggerConfig::START_TRACING); trigger_config->set_trigger_timeout_ms(60000); auto* oom_trigger = trigger_config->add_triggers(); oom_trigger->set_name("com.android.telemetry.art-outofmemory"); @@ -145,7 +147,8 @@ std::vector TriggerOomHeapDump(std::string app_name, // start tracing helper.StartTracing(trace_config); - StartAppActivity(app_name, "JavaOomActivity", "target.app.running", &task_runner, + StartAppActivity(app_name, "JavaOomActivity", "target.app.running", + &task_runner, /*delay_ms=*/100); task_runner.RunUntilCheckpoint("target.app.running", 10000 /*ms*/); diff --git a/test/cts/reporter/Android.bp b/test/cts/reporter/Android.bp index 597426cdac..4338500900 100644 --- a/test/cts/reporter/Android.bp +++ b/test/cts/reporter/Android.bp @@ -9,6 +9,7 @@ package { cc_test { name: "CtsPerfettoReporterTestCases", + team: "trendy_team_perfetto", srcs: [ "reporter_test_cts.cc", ":perfetto_protos_perfetto_config_cpp_gen", diff --git a/test/data/android_calculator_startup.pb.sha256 b/test/data/android_calculator_startup.pb.sha256 new file mode 100644 index 0000000000..64aaa4667b --- /dev/null +++ b/test/data/android_calculator_startup.pb.sha256 @@ -0,0 +1 @@ +c71765f5ea1588fa2ba63db86d04652b5feb3647923f85bad951163157c95718 \ No newline at end of file diff --git a/test/data/chrome/chrome_input_with_frame_view_new.pftrace.sha256 b/test/data/chrome/chrome_input_with_frame_view_new.pftrace.sha256 new file mode 100644 index 0000000000..3f18a44361 --- /dev/null +++ b/test/data/chrome/chrome_input_with_frame_view_new.pftrace.sha256 @@ -0,0 +1 @@ +e901ad9577088e62c921dd8bfcb43d652ecf49fa69b5b57f81bb3d27dbe94e12 \ No newline at end of file diff --git a/test/data/chrome/cpu_powerups_1.pb.sha256 b/test/data/chrome/cpu_powerups_1.pb.sha256 index 04e0249f0a..879a39c8b3 100644 --- a/test/data/chrome/cpu_powerups_1.pb.sha256 +++ b/test/data/chrome/cpu_powerups_1.pb.sha256 @@ -1 +1 @@ -70f5511ba0cd6ce1359e3cadb4d1d9301fb6e26be85158e3384b06f41418d386 +70f5511ba0cd6ce1359e3cadb4d1d9301fb6e26be85158e3384b06f41418d386 \ No newline at end of file diff --git a/test/data/chrome/scroll_jank_with_pinch.pftrace.sha256 b/test/data/chrome/scroll_jank_with_pinch.pftrace.sha256 new file mode 100644 index 0000000000..bf43d24cb9 --- /dev/null +++ b/test/data/chrome/scroll_jank_with_pinch.pftrace.sha256 @@ -0,0 +1 @@ +8587d2016fdb5d39b5f852704b6e3925e9e6527af01696396be767bed04d4a45 \ No newline at end of file diff --git a/test/data/chrome/speedometer.perfetto_trace.gz.sha256 b/test/data/chrome/speedometer_21.perfetto_trace.gz.sha256 similarity index 100% rename from test/data/chrome/speedometer.perfetto_trace.gz.sha256 rename to test/data/chrome/speedometer_21.perfetto_trace.gz.sha256 diff --git a/test/data/chrome/speedometer_3.perfetto_trace.gz.sha256 b/test/data/chrome/speedometer_3.perfetto_trace.gz.sha256 new file mode 100644 index 0000000000..481be39039 --- /dev/null +++ b/test/data/chrome/speedometer_3.perfetto_trace.gz.sha256 @@ -0,0 +1 @@ +b2c77fbe2c17363432a1ad0c05c1c1c20d3ebc62bda92c041d39918011af6f02 \ No newline at end of file diff --git a/test/data/heap_graph/heap_graph.pb.sha256 b/test/data/heap_graph/heap_graph.pb.sha256 new file mode 100644 index 0000000000..e9f395f779 --- /dev/null +++ b/test/data/heap_graph/heap_graph.pb.sha256 @@ -0,0 +1 @@ +f6ef879ad1b0506c378426fe1178d1c7d413b4b7dd83ef5b9ba06b55ec0fbdf8 \ No newline at end of file diff --git a/test/data/heap_graph/heap_graph.textproto.sha256 b/test/data/heap_graph/heap_graph.textproto.sha256 new file mode 100644 index 0000000000..38282f28e4 --- /dev/null +++ b/test/data/heap_graph/heap_graph.textproto.sha256 @@ -0,0 +1 @@ +0f17b814c6b338295b99985204b0cd22971b7b8f93203ece22b2157a344f8873 \ No newline at end of file diff --git a/test/data/heap_graph/heap_graph_branching.pb.sha256 b/test/data/heap_graph/heap_graph_branching.pb.sha256 new file mode 100644 index 0000000000..e6d1812762 --- /dev/null +++ b/test/data/heap_graph/heap_graph_branching.pb.sha256 @@ -0,0 +1 @@ +98114c80ec97c179a39be699f4253e3f8b0a13af0901372e5a0f25233b5e06d1 \ No newline at end of file diff --git a/test/data/heap_graph/heap_graph_branching.textproto.sha256 b/test/data/heap_graph/heap_graph_branching.textproto.sha256 new file mode 100644 index 0000000000..f2b8465b6b --- /dev/null +++ b/test/data/heap_graph/heap_graph_branching.textproto.sha256 @@ -0,0 +1 @@ +eaa24454f5bee06b235679e775d5bac2d8c332296e495e460260f1ac7a366c60 \ No newline at end of file diff --git a/test/data/heap_graph/heap_graph_huge_size.pb.sha256 b/test/data/heap_graph/heap_graph_huge_size.pb.sha256 new file mode 100644 index 0000000000..ff31bc677a --- /dev/null +++ b/test/data/heap_graph/heap_graph_huge_size.pb.sha256 @@ -0,0 +1 @@ +dad614f622d36f4511025aa8e9f2df86b540e24c1c0a2eb7d23d9632149aa299 \ No newline at end of file diff --git a/test/data/heap_graph/heap_graph_huge_size.textproto.sha256 b/test/data/heap_graph/heap_graph_huge_size.textproto.sha256 new file mode 100644 index 0000000000..0c5a8e6441 --- /dev/null +++ b/test/data/heap_graph/heap_graph_huge_size.textproto.sha256 @@ -0,0 +1 @@ +6154874718fe8774bd9bf687a89730e102b5cd8225bd23c81cb181234f21d853 \ No newline at end of file diff --git a/test/data/multi_machine_trace.pb.sha256 b/test/data/multi_machine_trace.pb.sha256 new file mode 100644 index 0000000000..2e7c04ce8b --- /dev/null +++ b/test/data/multi_machine_trace.pb.sha256 @@ -0,0 +1 @@ +366c817527d84de98bac52943381965425fd959fa1c824dd856237a4cb3c9722 \ No newline at end of file diff --git a/test/data/simpleperf/linux_perf_with_symbols.zip.sha256 b/test/data/simpleperf/linux_perf_with_symbols.zip.sha256 new file mode 100644 index 0000000000..0f5a71fbae --- /dev/null +++ b/test/data/simpleperf/linux_perf_with_symbols.zip.sha256 @@ -0,0 +1 @@ +6b11de85a4e95c8f31e5cbc5f12f00ec885a19fd299e5531ea7708263602a1e8 \ No newline at end of file diff --git a/test/data/simpleperf/perf.data.sha256 b/test/data/simpleperf/perf.data.sha256 new file mode 100644 index 0000000000..8e90588bc3 --- /dev/null +++ b/test/data/simpleperf/perf.data.sha256 @@ -0,0 +1 @@ +cb3066f4050d84d3e204a37ca4c479113b7623b663c17a3ee8cae5a85b8238bf \ No newline at end of file diff --git a/test/data/simpleperf/perf_with_add_counter.data.sha256 b/test/data/simpleperf/perf_with_add_counter.data.sha256 new file mode 100644 index 0000000000..06c8a918f2 --- /dev/null +++ b/test/data/simpleperf/perf_with_add_counter.data.sha256 @@ -0,0 +1 @@ +f3f44439a4add389b13510d9bb9ed2a151dc6d0a1ee8a7f5c52a369d96c9f0eb \ No newline at end of file diff --git a/test/data/simpleperf/perf_with_synthetic_events.data.sha256 b/test/data/simpleperf/perf_with_synthetic_events.data.sha256 new file mode 100644 index 0000000000..18d23bc74b --- /dev/null +++ b/test/data/simpleperf/perf_with_synthetic_events.data.sha256 @@ -0,0 +1 @@ +6e299d6cd3a4d8364b437d80033dcba8ac7064487f58a20d4fef9ffc3fc53185 \ No newline at end of file diff --git a/test/data/trace-redaction-api-capture.pftrace.sha256 b/test/data/trace-redaction-api-capture.pftrace.sha256 new file mode 100644 index 0000000000..d1bf6171d7 --- /dev/null +++ b/test/data/trace-redaction-api-capture.pftrace.sha256 @@ -0,0 +1 @@ +7bc4e8693d87af89af84ce1d39b531c1375f1ce8a6ac523d0d524efca7ac544c \ 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 ae1eb866b0..6050173ff1 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 @@ -27883a40c19205b1de4f9b87dfc22f4039986330074a7955e4432a045b657314 \ No newline at end of file +1ebff9e4f873e4dc6da6dbefc43be67c6fd73773e3f21a2a266c0472b851ffd8 \ 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 1375a4a5c5..141fdb2ced 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 @@ -3194c35d4ec8eaf318fa2edcbf8ba04a9d29b0093fd60248ee0c73e982a1766e \ No newline at end of file +6868ac9fada2a3327700d4bc20798ff31b4059d42ca5473bf281d6738b2119d0 \ 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 ea62427a6d..4a13ea9152 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 @@ -8b878dd1d284fda4558de3f184d078293546c09a1438e14183161eb6040cee26 \ No newline at end of file +a7ad0191ca45da7b4835e834c57fedee443306ec6203e3049468c706d7f69a10 \ 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 e6a988a37d..13af8b12fe 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 @@ -d3e7cdfbb83dd4e1b674c5d7b90554fd915dab8747f3981a2b6dbb1fff955f61 \ No newline at end of file +af3450d975776d932de7eafada5132716256d16b0afae5a503d0d3ca387cd855 \ 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 1be3a540d5..73265ec94d 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 @@ -e1d34e03cd7540d476eb93e159a785ea18a80083b3410898a967a56f9d7af5f8 \ No newline at end of file +037ca463e7083b1514088c09706437adfa80117b3e2a7186c3ca97d5a3e2344f \ 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 2ced23b133..329530a0b5 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 @@ -0b5b8abecbbef18f25cbe0ddbfc7a8ff5e4aefb0c1ad07871cd72dc69d2647c9 \ No newline at end of file +4bff267db495815b079803cd9a92f86648939099774b040c7853798314d30173 \ No newline at end of file diff --git a/test/data/ui-screenshots/ui-features_track_debuggable_chip.png.sha256 b/test/data/ui-screenshots/ui-features_track_debuggable_chip.png.sha256 new file mode 100644 index 0000000000..61c05d82a0 --- /dev/null +++ b/test/data/ui-screenshots/ui-features_track_debuggable_chip.png.sha256 @@ -0,0 +1 @@ +de88087b490b3496fa937850bcbb5fe8fb13f1710387a06a8bed61e51b5ec407 \ 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 e9fa1e33e4..1fc1da5baa 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 @@ -14056ade87692d8ec27ad4eb6c0c4fe55a14f87682b26af5acd829388326ed1b \ No newline at end of file +39356631a4f52af86bd9b10e46836bec70679b2ab67f230261ba3dde6144adaf \ 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 0470eab220..1981d8f12d 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 @@ -ee1bbe38698683159bc2d51940ebda03e22f766302c26f71d9792fbd68f71fd7 \ No newline at end of file +f7486efa6610c3ea280b1e097e5fafbc5f4cc72b1a53a2eec872555e133c38a0 \ 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 ace70f07b3..fcb619aef6 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 @@ -84f915ea5ab8d247a451de6844c66af348889f8ae789f8378b52660745f78ad2 \ No newline at end of file +fb0f4cbdc19ce7b744405afcd849e3f4228c910ba055a5050e0c4b35dfe9da79 \ 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 8bf2a5316c..b2e5b3200f 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 @@ -6906847e636f09c0d8476074392e6d79de745bed39adc79624a1a305477ad09b \ No newline at end of file +95e8c1aab784b8503fd4f99d89bdaa0314d0a57ff0d75966c7603afca725e1c5 \ 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 4b545db70e..3065302f08 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 @@ -d0a10b3fa63f100b16b8a595e805485cbb7b2ac7a7b25cc68024a726f4e69e6a \ No newline at end of file +1a80c1925b89e34c13ac276dbb112fcce892243c0e8351790ce481bb8f024e1b \ 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 423413f73f..f07a6d219f 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 @@ -a38e589b73daa4c8b1eb6fe5a592bc85fdd00139e7278e7289aed0d0c2a0b048 \ No newline at end of file +97e8c64b23fa8458e6e420b1de47263789c16eec774f76eadf5466718ebc3c56 \ 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 1c8ee20619..5becfb1a08 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 @@ -f7350a1ea142f50c7f8847cffa2ca1ec89faac07a57660b8b20f2d0d068f5747 \ No newline at end of file +e311b1473c5e8a3862803554962109db4359d32837ed44c3e55696fcef10b15a \ 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 bf4069851d..2fc2741da8 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 @@ -7031a1c1d49c9b953bd177ce268460d3702574b6d8b57542f9896c3cbcb91cac \ No newline at end of file +bd2829967465c023c8238753af48fc2b686f6c028692f8da224838e3a914caee \ 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 5b4b6e80cb..f78c98b2b2 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 @@ -bde6b98b6bf32e479277e34bbb410e08bf05840f6e34959c545ced0592f1f4c5 \ No newline at end of file +dc9d2ff90d480e7b92434d8f45351f238ff21b5ee4b697f2593a863187487b7f \ 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 c703153eb6..5d07d5df7e 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 @@ -4a24fb909c6346d5419905fc0a7ff69b9dda00fe77db6b953497d47f4dbe4149 \ No newline at end of file +52fd2dd2d802613fac3c1b69429990ead7eb2375613d8f3c70ddbcc777a91064 \ 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 1c8ee20619..5becfb1a08 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 @@ -f7350a1ea142f50c7f8847cffa2ca1ec89faac07a57660b8b20f2d0d068f5747 \ No newline at end of file +e311b1473c5e8a3862803554962109db4359d32837ed44c3e55696fcef10b15a \ 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 c703153eb6..5d07d5df7e 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 @@ -4a24fb909c6346d5419905fc0a7ff69b9dda00fe77db6b953497d47f4dbe4149 \ No newline at end of file +52fd2dd2d802613fac3c1b69429990ead7eb2375613d8f3c70ddbcc777a91064 \ 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 c703153eb6..5d07d5df7e 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 @@ -4a24fb909c6346d5419905fc0a7ff69b9dda00fe77db6b953497d47f4dbe4149 \ No newline at end of file +52fd2dd2d802613fac3c1b69429990ead7eb2375613d8f3c70ddbcc777a91064 \ 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 517d3eb1ab..b925464012 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 @@ -2ce30cc545efe24f68a1b6efe7dd27971f6b40c777f08098f4d68c8e861f843f \ No newline at end of file +52a03a10b8a051ae72877ce9c2abab155382a71ca3e406773e212851e3ff1867 \ 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 98854c1a7a..6491e68cac 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 @@ -c662b150f085a83819bb6250c63095ace7c2d8fe0c44b9a7b16a0ed633177955 \ No newline at end of file +08722fcfa246e0cba21e8d20a962aa67b5b038d6beb48eefe53d6a8a37decfa7 \ 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 1c8ee20619..5becfb1a08 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 @@ -f7350a1ea142f50c7f8847cffa2ca1ec89faac07a57660b8b20f2d0d068f5747 \ No newline at end of file +e311b1473c5e8a3862803554962109db4359d32837ed44c3e55696fcef10b15a \ 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 c703153eb6..5d07d5df7e 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 @@ -4a24fb909c6346d5419905fc0a7ff69b9dda00fe77db6b953497d47f4dbe4149 \ No newline at end of file +52fd2dd2d802613fac3c1b69429990ead7eb2375613d8f3c70ddbcc777a91064 \ 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 c703153eb6..5d07d5df7e 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 @@ -4a24fb909c6346d5419905fc0a7ff69b9dda00fe77db6b953497d47f4dbe4149 \ No newline at end of file +52fd2dd2d802613fac3c1b69429990ead7eb2375613d8f3c70ddbcc777a91064 \ No newline at end of file diff --git a/test/data/zip/perf_track_sym.zip.sha256 b/test/data/zip/perf_track_sym.zip.sha256 new file mode 100644 index 0000000000..53dfcb3d3b --- /dev/null +++ b/test/data/zip/perf_track_sym.zip.sha256 @@ -0,0 +1 @@ +146b1d8f48743323e91fd63d6ab5a1f6d8fcca8c5ea8455ebe8f087667a23c3f \ No newline at end of file diff --git a/test/gtest_and_gmock.h b/test/gtest_and_gmock.h index 3438c0fbac..24d8bedda8 100644 --- a/test/gtest_and_gmock.h +++ b/test/gtest_and_gmock.h @@ -48,6 +48,7 @@ #include // IWYU pragma: export #include // IWYU pragma: export +#include // IWYU pragma: export #include // IWYU pragma: export #include // IWYU pragma: export #include // IWYU pragma: export diff --git a/test/trace_processor/PRESUBMIT.py b/test/trace_processor/PRESUBMIT.py index 23d5d641c9..f0b70ab10e 100644 --- a/test/trace_processor/PRESUBMIT.py +++ b/test/trace_processor/PRESUBMIT.py @@ -25,7 +25,7 @@ def RunAndReportIfLong(func, *args, **kargs): start = time.time() results = func(*args, **kargs) end = time.time() - limit = 0.5 # seconds + limit = 3.0 # seconds name = func.__name__ runtime = end - start if runtime > limit: diff --git a/test/trace_processor/diff_tests/include_index.py b/test/trace_processor/diff_tests/include_index.py index dd8fadedcb..57705ad85b 100644 --- a/test/trace_processor/diff_tests/include_index.py +++ b/test/trace_processor/diff_tests/include_index.py @@ -48,6 +48,7 @@ from diff_tests.metrics.webview.tests import WebView from diff_tests.parser.android_fs.tests import AndroidFs from diff_tests.parser.android.tests import AndroidParser +from diff_tests.parser.android.tests_android_input_event import AndroidInputEvent from diff_tests.parser.android.tests_bugreport import AndroidBugreport from diff_tests.parser.android.tests_games import AndroidGames from diff_tests.parser.android.tests_inputmethod_clients import InputMethodClients @@ -57,6 +58,8 @@ from diff_tests.parser.android.tests_shell_transitions import ShellTransitions from diff_tests.parser.android.tests_surfaceflinger_layers import SurfaceFlingerLayers from diff_tests.parser.android.tests_surfaceflinger_transactions import SurfaceFlingerTransactions +from diff_tests.parser.android.tests_viewcapture import ViewCapture +from diff_tests.parser.android.tests_windowmanager import WindowManager from diff_tests.parser.atrace.tests import Atrace from diff_tests.parser.atrace.tests_error_handling import AtraceErrorHandling from diff_tests.parser.chrome.tests import ChromeParser @@ -69,13 +72,14 @@ from diff_tests.parser.graphics.tests import GraphicsParser from diff_tests.parser.graphics.tests_drm_related_ftrace_events import GraphicsDrmRelatedFtraceEvents from diff_tests.parser.graphics.tests_gpu_trace import GraphicsGpuTrace -from diff_tests.parser.json.tests import JsonTests +from diff_tests.parser.json.tests import JsonParser from diff_tests.parser.memory.tests import MemoryParser from diff_tests.parser.network.tests import NetworkParser from diff_tests.parser.parsing.tests import Parsing from diff_tests.parser.parsing.tests_debug_annotation import ParsingDebugAnnotation from diff_tests.parser.parsing.tests_memory_counters import ParsingMemoryCounters from diff_tests.parser.parsing.tests_rss_stats import ParsingRssStats +from diff_tests.parser.parsing.tests_traced_stats import ParsingTracedStats from diff_tests.parser.power.tests_energy_breakdown import PowerEnergyBreakdown from diff_tests.parser.power.tests_entity_state_residency import EntityStateResidency from diff_tests.parser.power.tests_linux_sysfs_power import LinuxSysfsPower @@ -87,6 +91,7 @@ from diff_tests.parser.profiling.tests_heap_profiling import ProfilingHeapProfiling from diff_tests.parser.profiling.tests_llvm_symbolizer import ProfilingLlvmSymbolizer from diff_tests.parser.sched.tests import SchedParser +from diff_tests.parser.simpleperf.tests import Simpleperf from diff_tests.parser.smoke.tests import Smoke from diff_tests.parser.smoke.tests_compute_metrics import SmokeComputeMetrics from diff_tests.parser.smoke.tests_json import SmokeJson @@ -94,22 +99,31 @@ from diff_tests.parser.track_event.tests import TrackEvent from diff_tests.parser.translated_args.tests import TranslatedArgs from diff_tests.parser.ufs.tests import Ufs +from diff_tests.parser.zip.tests import Zip +from diff_tests.stdlib.android.cpu_cluster_tests import CpuClusters from diff_tests.stdlib.android.frames_tests import Frames +from diff_tests.stdlib.android.gpu import AndroidGpu +from diff_tests.stdlib.android.heap_graph_tests import HeapGraph +from diff_tests.stdlib.android.memory import AndroidMemory from diff_tests.stdlib.android.startups_tests import Startups from diff_tests.stdlib.android.tests import AndroidStdlib from diff_tests.stdlib.chrome.chrome_stdlib_testsuites import CHROME_STDLIB_TESTSUITES from diff_tests.stdlib.common.tests import StdlibCommon from diff_tests.stdlib.common.tests import StdlibCommon from diff_tests.stdlib.counters.tests import StdlibCounterIntervals -from diff_tests.stdlib.cpu.tests import CpuStdlib from diff_tests.stdlib.dynamic_tables.tests import DynamicTables +from diff_tests.stdlib.export.tests import ExportTests +from diff_tests.stdlib.graphs.critical_path_tests import CriticalPathTests from diff_tests.stdlib.graphs.dominator_tree_tests import DominatorTree from diff_tests.stdlib.graphs.partition_tests import GraphPartitionTests +from diff_tests.stdlib.graphs.scan_tests import GraphScanTests from diff_tests.stdlib.graphs.search_tests import GraphSearchTests from diff_tests.stdlib.intervals.intersect_tests import IntervalsIntersect from diff_tests.stdlib.intervals.tests import StdlibIntervals -from diff_tests.stdlib.linux.tests import LinuxStdlib -from diff_tests.stdlib.memory.heap_graph_dominator_tree_tests import HeapGraphDominatorTree +from diff_tests.stdlib.linux.cpu import LinuxCpu +from diff_tests.stdlib.linux.memory import Memory +from diff_tests.stdlib.metasql.column_list import ColumnListTests +from diff_tests.stdlib.metasql.table_list import TableListTests from diff_tests.stdlib.pkvm.tests import Pkvm from diff_tests.stdlib.prelude.math_functions_tests import PreludeMathFunctions from diff_tests.stdlib.prelude.pprof_functions_tests import PreludePprofFunctions @@ -137,6 +151,7 @@ sys.path.pop() + def fetch_all_diff_tests(index_path: str) -> List['testing.TestCase']: parser_tests = [ *AndroidBugreport(index_path, 'parser/android', @@ -159,7 +174,7 @@ def fetch_all_diff_tests(index_path: str) -> List['testing.TestCase']: *GraphicsGpuTrace(index_path, 'parser/graphics', 'GraphicsGpuTrace').fetch(), *GraphicsParser(index_path, 'parser/graphics', 'GraphicsParser').fetch(), - *JsonTests(index_path, 'parser/json', 'JsonParser').fetch(), + *JsonParser(index_path, 'parser/json', 'JsonParser').fetch(), *MemoryParser(index_path, 'parser/memory', 'MemoryParser').fetch(), *NetworkParser(index_path, 'parser/network', 'NetworkParser').fetch(), *PowerEnergyBreakdown(index_path, 'parser/power', @@ -180,6 +195,7 @@ def fetch_all_diff_tests(index_path: str) -> List['testing.TestCase']: *ProfilingLlvmSymbolizer(index_path, 'parser/profiling', 'ProfilingLlvmSymbolizer').fetch(), *SchedParser(index_path, 'parser/sched', 'SchedParser').fetch(), + *Simpleperf(index_path, 'parser/simpleperf', 'Simpleperf').fetch(), *StdlibSched(index_path, 'stdlib/sched', 'StdlibSched').fetch(), *Smoke(index_path, 'parser/smoke', 'Smoke').fetch(), *SmokeComputeMetrics(index_path, 'parser/smoke', @@ -187,11 +203,11 @@ def fetch_all_diff_tests(index_path: str) -> List['testing.TestCase']: *SmokeJson(index_path, 'parser/smoke', 'SmokeJson').fetch(), *SmokeSchedEvents(index_path, 'parser/smoke', 'SmokeSchedEvents').fetch(), *InputMethodClients(index_path, 'parser/android', - 'InputMethodClients').fetch(), + 'InputMethodClients').fetch(), *InputMethodManagerService(index_path, 'parser/android', - 'InputMethodManagerService').fetch(), + 'InputMethodManagerService').fetch(), *InputMethodService(index_path, 'parser/android', - 'InputMethodService').fetch(), + 'InputMethodService').fetch(), *SurfaceFlingerLayers(index_path, 'parser/android', 'SurfaceFlingerLayers').fetch(), *SurfaceFlingerTransactions(index_path, 'parser/android', @@ -199,6 +215,8 @@ def fetch_all_diff_tests(index_path: str) -> List['testing.TestCase']: *ShellTransitions(index_path, 'parser/android', 'ShellTransitions').fetch(), *ProtoLog(index_path, 'parser/android', 'ProtoLog').fetch(), + *ViewCapture(index_path, 'parser/android', 'ViewCapture').fetch(), + *WindowManager(index_path, 'parser/android', 'WindowManager').fetch(), *TrackEvent(index_path, 'parser/track_event', 'TrackEvent').fetch(), *TranslatedArgs(index_path, 'parser/translated_args', 'TranslatedArgs').fetch(), @@ -212,6 +230,11 @@ def fetch_all_diff_tests(index_path: str) -> List['testing.TestCase']: *ParsingMemoryCounters(index_path, 'parser/parsing', 'ParsingMemoryCounters').fetch(), *FtraceCrop(index_path, 'parser/ftrace', 'FtraceCrop').fetch(), + *ParsingTracedStats(index_path, 'parser/parsing', + 'ParsingTracedStats').fetch(), + *Zip(index_path, 'parser/zip', 'Zip').fetch(), + *AndroidInputEvent(index_path, 'parser/android', + 'AndroidInputEvent').fetch(), ] metrics_tests = [ @@ -254,9 +277,15 @@ def fetch_all_diff_tests(index_path: str) -> List['testing.TestCase']: chrome_stdlib_tests += test_suite.fetch() stdlib_tests = [ + *AndroidMemory(index_path, 'stdlib/android', 'AndroidMemory').fetch(), + *AndroidGpu(index_path, 'stdlib/android', 'AndroidGpu').fetch(), *AndroidStdlib(index_path, 'stdlib/android', 'AndroidStdlib').fetch(), - *CpuStdlib(index_path, 'stdlib/cpu', 'CpuStdlib').fetch(), + *CpuClusters(index_path, 'stdlib/android', 'CpuClusters').fetch(), + *LinuxCpu(index_path, 'stdlib/linux/cpu', 'LinuxCpu').fetch(), *DominatorTree(index_path, 'stdlib/graphs', 'DominatorTree').fetch(), + *CriticalPathTests(index_path, 'stdlib/graphs', 'CriticalPath').fetch(), + *GraphScanTests(index_path, 'stdlib/graphs', 'GraphScan').fetch(), + *ExportTests(index_path, 'stdlib/export', 'ExportTests').fetch(), *Frames(index_path, 'stdlib/android', 'Frames').fetch(), *GraphSearchTests(index_path, 'stdlib/graphs', 'GraphSearchTests').fetch(), @@ -266,11 +295,15 @@ def fetch_all_diff_tests(index_path: str) -> List['testing.TestCase']: 'StdlibCounterIntervals').fetch(), *DynamicTables(index_path, 'stdlib/dynamic_tables', 'DynamicTables').fetch(), - *LinuxStdlib(index_path, 'stdlib/linux', 'LinuxStdlib').fetch(), + *ColumnListTests(index_path, 'stdlib/column_list', + 'ColumnListTests').fetch(), + *TableListTests(index_path, 'stdlib/table_list', + 'TableListTests').fetch(), + *Memory(index_path, 'stdlib/linux', 'Memory').fetch(), *PreludeMathFunctions(index_path, 'stdlib/prelude', 'PreludeMathFunctions').fetch(), - *HeapGraphDominatorTree(index_path, 'stdlib/memory', - 'HeapGraphDominatorTree').fetch(), + *HeapGraph(index_path, 'stdlib/android', + 'HeapGraphDominatorTree').fetch(), *PreludePprofFunctions(index_path, 'stdlib/prelude', 'PreludePprofFunctions').fetch(), *PreludeWindowFunctions(index_path, 'stdlib/prelude', diff --git a/test/trace_processor/diff_tests/metrics/android/ad_services_metric.py b/test/trace_processor/diff_tests/metrics/android/ad_services_metric.py index f1e8aadda8..8cd36d1ca0 100644 --- a/test/trace_processor/diff_tests/metrics/android/ad_services_metric.py +++ b/test/trace_processor/diff_tests/metrics/android/ad_services_metric.py @@ -47,15 +47,13 @@ trace.add_atrace_begin(ts=900, tid=42, pid=42, buf=APP_SET_ID_EVENT) trace.add_atrace_end(ts=1200, tid=42, pid=42) -trace.add_atrace_begin( - ts=1500, tid=43, pid=43, buf=CONSENT_MANAGER_READ_EVENT) +trace.add_atrace_begin(ts=1500, tid=43, pid=43, buf=CONSENT_MANAGER_READ_EVENT) trace.add_atrace_end(ts=1650, tid=43, pid=43) trace.add_atrace_begin( ts=2500, tid=43, pid=44, buf=ODP_MANAGER_INITIALIZATION_EVENT) trace.add_atrace_end(ts=2550, tid=43, pid=44) -trace.add_atrace_begin( - ts=2600, tid=43, pid=44, buf=ODP_MANAGER_EXECUTE_EVENT) +trace.add_atrace_begin(ts=2600, tid=43, pid=44, buf=ODP_MANAGER_EXECUTE_EVENT) trace.add_atrace_end(ts=2700, tid=43, pid=44) trace.add_atrace_begin( ts=2800, tid=43, pid=44, buf=ODP_MANAGER_REQUEST_SURFACE_PACKAGE_EVENT) diff --git a/test/trace_processor/diff_tests/metrics/android/android_auto_multiuser.textproto b/test/trace_processor/diff_tests/metrics/android/android_auto_multiuser.textproto index c33839e229..e14c0776d7 100644 --- a/test/trace_processor/diff_tests/metrics/android/android_auto_multiuser.textproto +++ b/test/trace_processor/diff_tests/metrics/android/android_auto_multiuser.textproto @@ -11,6 +11,16 @@ packet { uid: 1000010 cmdline: "dummy:2" } + processes { + pid: 12 + uid: 1300010 + cmdline: "dummy:3" + } + processes { + pid: 20 + uid: 1000 + cmdline: "finishUserStopped-10" + } } } packet { @@ -65,6 +75,38 @@ packet { } } } +packet { + ftrace_events { + cpu: 1 + event { + timestamp: 5000000001 + pid: 10 + sched_switch { + prev_comm: "dummy:3" + prev_pid: 12 + prev_state: 2 + next_comm: "dummy:2" + next_pid: 11 + } + } + } +} +packet { + ftrace_events { + cpu: 1 + event { + timestamp: 5010000000 + pid: 11 + sched_switch { + prev_comm: "dummy:2" + prev_pid: 11 + prev_state: 2 + next_comm: "dummy:3" + next_pid: 13 + } + } + } +} packet { timestamp: 3000000001 process_stats { @@ -101,6 +143,15 @@ packet { } } } +packet { + timestamp: 5000000002 + process_stats { + processes { + pid: 11 + vm_rss_kb: 3000 + } + } +} packet { ftrace_events { cpu: 1 @@ -124,4 +175,23 @@ packet { } } } -} \ No newline at end of file +} +packet { + ftrace_events { + cpu: 1 + event { + timestamp: 5000000000 + pid: 20 + print { + buf: "B|20|finishUserStopped-10-[stopUser]\n" + } + } + event { + timestamp: 5100000000 + pid: 20 + print { + buf: "E|20\n" + } + } + } +} diff --git a/test/trace_processor/diff_tests/metrics/android/android_binder_metric.out b/test/trace_processor/diff_tests/metrics/android/android_binder_metric.out index f775c1cc86..2d7b2ffd8a 100644 --- a/test/trace_processor/diff_tests/metrics/android/android_binder_metric.out +++ b/test/trace_processor/diff_tests/metrics/android/android_binder_metric.out @@ -396,6 +396,8 @@ android_binder { is_sync: true client_monotonic_dur: 95231 server_monotonic_dur: 69578 + server_package_version_code: 33 + is_server_package_debuggable: false thread_states { thread_state_type: "binder_reply" thread_state: "Running" @@ -440,6 +442,8 @@ android_binder { is_sync: true client_monotonic_dur: 43573 server_monotonic_dur: 27121 + server_package_version_code: 33 + is_server_package_debuggable: false thread_states { thread_state_type: "binder_reply" thread_state: "Running" @@ -1721,6 +1725,8 @@ android_binder { is_sync: true client_monotonic_dur: 56472 server_monotonic_dur: 10317 + server_package_version_code: 33 + is_server_package_debuggable: false thread_states { thread_state_type: "binder_reply" thread_state: "Running" @@ -25033,7 +25039,9 @@ android_binder { client_monotonic_dur: 491565 server_monotonic_dur: 252231 client_package_version_code: 33 + server_package_version_code: 33 is_client_package_debuggable: false + is_server_package_debuggable: false thread_states { thread_state_type: "binder_reply" thread_state: "Running" @@ -25094,7 +25102,9 @@ android_binder { client_monotonic_dur: 1359588 server_monotonic_dur: 1280511 client_package_version_code: 33 + server_package_version_code: 33 is_client_package_debuggable: false + is_server_package_debuggable: false thread_states { thread_state_type: "binder_reply" thread_state: "R" @@ -40764,6 +40774,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IUidObserver::onUidGone::server" @@ -40787,6 +40799,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IUidObserver::onUidGone::server" @@ -40810,6 +40824,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { client_process: "system_server" @@ -40830,6 +40846,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { client_process: "system_server" @@ -40850,6 +40868,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IUidObserver::onUidGone::server" @@ -40873,6 +40893,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IUidObserver::onUidGone::server" @@ -40896,6 +40918,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IUidObserver::onUidGone::server" @@ -40919,6 +40943,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IUidObserver::onUidGone::server" @@ -40942,6 +40968,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IUidObserver::onUidGone::server" @@ -40965,6 +40993,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IUidObserver::onUidGone::server" @@ -40988,6 +41018,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IUidObserver::onUidGone::server" @@ -41011,6 +41043,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IUidObserver::onUidGone::server" @@ -41034,6 +41068,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IUidObserver::onUidGone::server" @@ -41057,6 +41093,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IUidObserver::onUidStateChanged::server" @@ -41080,6 +41118,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IUidObserver::onUidStateChanged::server" @@ -41103,6 +41143,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IUidObserver::onUidGone::server" @@ -41126,6 +41168,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IUidObserver::onUidStateChanged::server" @@ -41149,6 +41193,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IUidObserver::onUidStateChanged::server" @@ -41172,6 +41218,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { client_process: "system_server" @@ -41295,6 +41343,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IUidObserver::onUidGone::server" @@ -41318,6 +41368,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IUidObserver::onUidGone::server" @@ -41341,6 +41393,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IUidObserver::onUidGone::server" @@ -41364,6 +41418,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IUidObserver::onUidGone::server" @@ -41387,6 +41443,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IUidObserver::onUidGone::server" @@ -41410,6 +41468,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IUidObserver::onUidGone::server" @@ -41433,6 +41493,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IUidObserver::onUidGone::server" @@ -41456,6 +41518,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IUidObserver::onUidGone::server" @@ -41479,6 +41543,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IUidObserver::onUidGone::server" @@ -41502,6 +41568,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IUidObserver::onUidGone::server" @@ -41525,6 +41593,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { client_process: "system_server" @@ -42928,6 +42998,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IApplicationThread::setProcessState::server" @@ -43148,6 +43220,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IRemoteSessionCallback::onSessionChanged::server" @@ -43171,6 +43245,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { client_process: "system_server" @@ -43191,6 +43267,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IStorageEventListener::onVolumeStateChanged::server" @@ -43214,6 +43292,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IStorageEventListener::onStorageStateChanged::server" @@ -43237,6 +43317,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IStorageEventListener::onVolumeStateChanged::server" @@ -43260,6 +43342,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IStorageEventListener::onStorageStateChanged::server" @@ -43283,6 +43367,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IApplicationThread::scheduleRegisteredReceiver::server" @@ -43306,6 +43392,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IApplicationThread::scheduleRegisteredReceiver::server" @@ -43329,6 +43417,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IApplicationThread::scheduleRegisteredReceiver::server" @@ -43352,6 +43442,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IVoldListener::onVolumeStateChanged::server" @@ -43740,6 +43832,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { client_process: "system_server" @@ -43760,6 +43854,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IStorageEventListener::onDiskScanned::server" @@ -43783,6 +43879,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IPhoneStateListener::onSignalStrengthsChanged::server" @@ -43806,6 +43904,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IStorageEventListener::onVolumeStateChanged::server" @@ -43829,6 +43929,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::INotificationListener::onNotificationRemoved::server" @@ -43852,6 +43954,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { client_process: "/system/bin/surfaceflinger" @@ -43872,6 +43976,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { client_process: "/system/bin/surfaceflinger" @@ -43892,6 +43998,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { client_process: "/system/bin/surfaceflinger" @@ -43912,6 +44020,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IPackageManager::notifyDexLoad::server" @@ -44944,6 +45054,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IUidObserver::onUidStateChanged::server" @@ -44967,6 +45079,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IUidObserver::onUidStateChanged::server" @@ -44990,6 +45104,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IUidObserver::onUidGone::server" @@ -45013,6 +45129,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IUidObserver::onUidStateChanged::server" @@ -45036,6 +45154,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IUidObserver::onUidStateChanged::server" @@ -45059,6 +45179,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IApplicationThread::bindApplication::server" @@ -45082,6 +45204,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IApplicationThread::setProcessState::server" @@ -45105,6 +45229,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IApplicationThread::scheduleCreateService::server" @@ -45128,6 +45254,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IApplicationThread::scheduleBindService::server" @@ -45151,6 +45279,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IApplicationThread::setProcessState::server" @@ -45174,6 +45304,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IStorageEventListener::onDiskScanned::server" @@ -45197,6 +45329,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IExternalStorageService::notifyVolumeStateChanged::server" @@ -45220,6 +45354,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IApplicationThread::scheduleCreateService::server" @@ -45243,6 +45379,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IApplicationThread::scheduleBindService::server" @@ -45266,6 +45404,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IExternalStorageService::notifyVolumeStateChanged::server" @@ -45289,6 +45429,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IStorageEventListener::onVolumeStateChanged::server" @@ -45312,6 +45454,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IStorageEventListener::onStorageStateChanged::server" @@ -45335,6 +45479,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IApplicationThread::scheduleReceiver::server" @@ -45358,6 +45504,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IApplicationThread::scheduleReceiver::server" @@ -45381,6 +45529,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IExternalStorageService::startSession::server" @@ -45404,6 +45554,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IExternalStorageService::notifyVolumeStateChanged::server" @@ -45427,6 +45579,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IStorageEventListener::onVolumeStateChanged::server" @@ -45450,6 +45604,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IExternalStorageService::startSession::server" @@ -45473,6 +45629,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IExternalStorageService::notifyVolumeStateChanged::server" @@ -45496,6 +45654,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IStorageEventListener::onVolumeStateChanged::server" @@ -45519,6 +45679,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IExternalStorageService::notifyVolumeStateChanged::server" @@ -45542,6 +45704,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IStorageEventListener::onVolumeStateChanged::server" @@ -45565,6 +45729,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IStorageEventListener::onStorageStateChanged::server" @@ -45588,6 +45754,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IJobService::startJob::server" @@ -45611,6 +45779,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IStorageEventListener::onVolumeStateChanged::server" @@ -45634,6 +45804,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IStorageEventListener::onStorageStateChanged::server" @@ -45657,6 +45829,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IExternalStorageService::notifyVolumeStateChanged::server" @@ -45680,6 +45854,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IStorageEventListener::onVolumeStateChanged::server" @@ -45703,6 +45879,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IStorageEventListener::onStorageStateChanged::server" @@ -45726,6 +45904,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IExternalStorageService::endSession::server" @@ -45749,6 +45929,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IExternalStorageService::endSession::server" @@ -45772,6 +45954,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IApplicationThread::scheduleUnbindService::server" @@ -45795,6 +45979,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IApplicationThread::scheduleStopService::server" @@ -45818,6 +46004,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IApplicationThread::setProcessState::server" @@ -45841,6 +46029,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IApplicationThread::setProcessState::server" @@ -45864,6 +46054,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IApplicationThread::scheduleCreateService::server" @@ -45887,6 +46079,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IApplicationThread::scheduleBindService::server" @@ -45910,6 +46104,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::cpp::IMediaMetricsService::submitBuffer::cppServer" @@ -47970,6 +48166,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IPhoneStateListener::onSignalStrengthsChanged::server" @@ -47993,6 +48191,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::INotificationListener::onNotificationRemoved::server" @@ -48016,6 +48216,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 309999900 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::INotificationListener::onNotificationEnqueuedWithChannel::server" @@ -48039,6 +48241,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 309999900 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::INotificationListener::onNotificationEnqueuedWithChannel::server" @@ -48062,6 +48266,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 309999900 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::INotificationListener::onNotificationPosted::server" @@ -48085,6 +48291,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 309999900 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::INotificationListener::onNotificationPosted::server" @@ -48108,6 +48316,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 309999900 + is_server_package_debuggable: false } unaggregated_txn_breakdown { client_process: "/system/bin/surfaceflinger" @@ -48614,6 +48824,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { client_process: "/system/bin/surfaceflinger" @@ -48634,6 +48846,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { client_process: "/system/bin/surfaceflinger" @@ -48654,6 +48868,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { client_process: "/system/bin/surfaceflinger" @@ -48674,6 +48890,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IStorageEventListener::onVolumeStateChanged::server" @@ -48697,6 +48915,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IStorageEventListener::onVolumeStateChanged::server" @@ -48720,6 +48940,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IStorageEventListener::onStorageStateChanged::server" @@ -48743,6 +48965,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IStorageEventListener::onVolumeStateChanged::server" @@ -48766,6 +48990,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IStorageEventListener::onStorageStateChanged::server" @@ -48789,6 +49015,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::INotificationListener::onNotificationPosted::server" @@ -48812,6 +49040,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::INotificationListener::onNotificationPosted::server" @@ -48835,6 +49065,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { client_process: "/system/bin/surfaceflinger" @@ -48855,6 +49087,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { client_process: "/system/bin/surfaceflinger" @@ -48875,6 +49109,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { client_process: "/system/bin/surfaceflinger" @@ -48895,6 +49131,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { client_process: "/system/bin/surfaceflinger" @@ -48915,6 +49153,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { client_process: "/system/bin/surfaceflinger" @@ -48935,6 +49175,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { client_process: "/system/bin/surfaceflinger" @@ -48955,6 +49197,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { client_process: "/system/bin/surfaceflinger" @@ -48975,6 +49219,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IApplicationThread::bindApplication::server" @@ -48998,6 +49244,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IApplicationThread::scheduleReceiver::server" @@ -49021,6 +49269,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IApplicationThread::scheduleReceiver::server" @@ -49044,6 +49294,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IApplicationThread::scheduleReceiver::server" @@ -49067,6 +49319,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IApplicationThread::setProcessState::server" @@ -49090,6 +49344,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IApplicationThread::scheduleReceiver::server" @@ -49113,6 +49369,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { aidl_name: "AIDL::java::IApplicationThread::setProcessState::server" @@ -49136,6 +49394,8 @@ android_binder { is_sync: false client_monotonic_dur: 0 server_monotonic_dur: 0 + server_package_version_code: 33 + is_server_package_debuggable: false } unaggregated_txn_breakdown { client_process: "system_server" diff --git a/test/trace_processor/diff_tests/metrics/android/android_blocking_calls_cuj_metric.py b/test/trace_processor/diff_tests/metrics/android/android_blocking_calls_cuj_metric.py index 8e005a1d36..8a72d5bc7c 100755 --- a/test/trace_processor/diff_tests/metrics/android/android_blocking_calls_cuj_metric.py +++ b/test/trace_processor/diff_tests/metrics/android/android_blocking_calls_cuj_metric.py @@ -53,6 +53,7 @@ 'android.os.Handler: #0', ] + def add_main_thread_atrace(trace, ts, ts_end, buf, pid): trace.add_atrace_begin(ts=ts, tid=pid, pid=pid, buf=buf) trace.add_atrace_end(ts=ts_end, tid=pid, pid=pid) diff --git a/test/trace_processor/diff_tests/metrics/android/android_blocking_calls_on_jank_cuj_metric.out b/test/trace_processor/diff_tests/metrics/android/android_blocking_calls_on_jank_cuj_metric.out index b42f9a25c1..2241252d73 100644 --- a/test/trace_processor/diff_tests/metrics/android/android_blocking_calls_on_jank_cuj_metric.out +++ b/test/trace_processor/diff_tests/metrics/android/android_blocking_calls_on_jank_cuj_metric.out @@ -10,11 +10,6 @@ apk_version_code: 1 debuggable: false } - packages_for_uid { - package_name: "com.android.systemui" - apk_version_code: 1 - debuggable: false - } pid: 1000 } ts: 0 @@ -41,11 +36,6 @@ apk_version_code: 1 debuggable: false } - packages_for_uid { - package_name: "com.android.systemui" - apk_version_code: 1 - debuggable: false - } pid: 1000 } ts: 0 diff --git a/test/trace_processor/diff_tests/metrics/android/android_blocking_calls_unagg.out b/test/trace_processor/diff_tests/metrics/android/android_blocking_calls_unagg.out index 4f288a7ba6..eb989f767c 100644 --- a/test/trace_processor/diff_tests/metrics/android/android_blocking_calls_unagg.out +++ b/test/trace_processor/diff_tests/metrics/android/android_blocking_calls_unagg.out @@ -14,6 +14,8 @@ android_blocking_calls_unagg { total_dur_ns: 20000000 max_dur_ns: 10000000 min_dur_ns: 1000000 + avg_dur_ms: 2 + avg_dur_ns: 2857142 } blocking_calls { name: "monitor contention with <...>" @@ -24,6 +26,8 @@ android_blocking_calls_unagg { total_dur_ns: 12000000 max_dur_ns: 12000000 min_dur_ns: 12000000 + avg_dur_ms: 12 + avg_dur_ns: 12000000 } blocking_calls { name: "AIDL::java::IWindowManager::hasNavigationBar::server" @@ -34,6 +38,8 @@ android_blocking_calls_unagg { total_dur_ns: 10000000 max_dur_ns: 10000000 min_dur_ns: 10000000 + avg_dur_ms: 10 + avg_dur_ns: 10000000 } } process_with_blocking_calls { @@ -51,6 +57,8 @@ android_blocking_calls_unagg { total_dur_ns: 10000000 max_dur_ns: 3000000 min_dur_ns: 1000000 + avg_dur_ms: 1 + avg_dur_ns: 1666666 } } process_with_blocking_calls { @@ -69,6 +77,8 @@ android_blocking_calls_unagg { total_dur_ns: 20000000 max_dur_ns: 10000000 min_dur_ns: 10000000 + avg_dur_ms: 10 + avg_dur_ns: 10000000 } blocking_calls { name: "Contending for pthread mutex" @@ -79,6 +89,8 @@ android_blocking_calls_unagg { total_dur_ns: 10000000 max_dur_ns: 10000000 min_dur_ns: 10000000 + avg_dur_ms: 10 + avg_dur_ns: 10000000 } blocking_calls { name: "ExpNotRow#onMeasure(BigTextStyle)" @@ -89,6 +101,8 @@ android_blocking_calls_unagg { total_dur_ns: 10000000 max_dur_ns: 10000000 min_dur_ns: 10000000 + avg_dur_ms: 10 + avg_dur_ns: 10000000 } blocking_calls { name: "ExpNotRow#onMeasure(MessagingStyle)" @@ -99,6 +113,8 @@ android_blocking_calls_unagg { total_dur_ns: 10000000 max_dur_ns: 10000000 min_dur_ns: 10000000 + avg_dur_ms: 10 + avg_dur_ns: 10000000 } blocking_calls { name: "Garbage Collector" @@ -109,6 +125,8 @@ android_blocking_calls_unagg { total_dur_ns: 10000000 max_dur_ns: 10000000 min_dur_ns: 10000000 + avg_dur_ms: 10 + avg_dur_ns: 10000000 } blocking_calls { name: "ImageDecoder#decodeBitmap" @@ -119,6 +137,8 @@ android_blocking_calls_unagg { total_dur_ns: 10000000 max_dur_ns: 10000000 min_dur_ns: 10000000 + avg_dur_ms: 10 + avg_dur_ns: 10000000 } blocking_calls { name: "ImageDecoder#decodeDrawable" @@ -129,6 +149,8 @@ android_blocking_calls_unagg { total_dur_ns: 10000000 max_dur_ns: 10000000 min_dur_ns: 10000000 + avg_dur_ms: 10 + avg_dur_ns: 10000000 } blocking_calls { name: "LoadApkAssetsFd <...>" @@ -139,6 +161,8 @@ android_blocking_calls_unagg { total_dur_ns: 10000000 max_dur_ns: 10000000 min_dur_ns: 10000000 + avg_dur_ms: 10 + avg_dur_ns: 10000000 } blocking_calls { name: "Lock contention on a monitor lock <...>" @@ -149,6 +173,8 @@ android_blocking_calls_unagg { total_dur_ns: 10000000 max_dur_ns: 10000000 min_dur_ns: 10000000 + avg_dur_ms: 10 + avg_dur_ns: 10000000 } blocking_calls { name: "Lock contention on thread list lock <...>" @@ -159,6 +185,8 @@ android_blocking_calls_unagg { total_dur_ns: 10000000 max_dur_ns: 10000000 min_dur_ns: 10000000 + avg_dur_ms: 10 + avg_dur_ns: 10000000 } blocking_calls { name: "Lock contention on thread suspend count lock <...>" @@ -169,6 +197,8 @@ android_blocking_calls_unagg { total_dur_ns: 10000000 max_dur_ns: 10000000 min_dur_ns: 10000000 + avg_dur_ms: 10 + avg_dur_ns: 10000000 } blocking_calls { name: "NotificationStackScrollLayout#onMeasure" @@ -179,6 +209,8 @@ android_blocking_calls_unagg { total_dur_ns: 10000000 max_dur_ns: 10000000 min_dur_ns: 10000000 + avg_dur_ms: 10 + avg_dur_ns: 10000000 } blocking_calls { name: "SuspendThreadByThreadId <...>" @@ -189,6 +221,8 @@ android_blocking_calls_unagg { total_dur_ns: 10000000 max_dur_ns: 10000000 min_dur_ns: 10000000 + avg_dur_ms: 10 + avg_dur_ns: 10000000 } blocking_calls { name: "animation" @@ -199,6 +233,8 @@ android_blocking_calls_unagg { total_dur_ns: 10000000 max_dur_ns: 10000000 min_dur_ns: 10000000 + avg_dur_ms: 10 + avg_dur_ns: 10000000 } blocking_calls { name: "binder transaction" @@ -209,6 +245,8 @@ android_blocking_calls_unagg { total_dur_ns: 10000000 max_dur_ns: 10000000 min_dur_ns: 10000000 + avg_dur_ms: 10 + avg_dur_ns: 10000000 } blocking_calls { name: "configChanged" @@ -219,6 +257,8 @@ android_blocking_calls_unagg { total_dur_ns: 10000000 max_dur_ns: 10000000 min_dur_ns: 10000000 + avg_dur_ms: 10 + avg_dur_ns: 10000000 } blocking_calls { name: "inflate" @@ -229,6 +269,8 @@ android_blocking_calls_unagg { total_dur_ns: 10000000 max_dur_ns: 10000000 min_dur_ns: 10000000 + avg_dur_ms: 10 + avg_dur_ns: 10000000 } blocking_calls { name: "input" @@ -239,6 +281,8 @@ android_blocking_calls_unagg { total_dur_ns: 10000000 max_dur_ns: 10000000 min_dur_ns: 10000000 + avg_dur_ms: 10 + avg_dur_ns: 10000000 } blocking_calls { name: "layout" @@ -249,6 +293,8 @@ android_blocking_calls_unagg { total_dur_ns: 10000000 max_dur_ns: 10000000 min_dur_ns: 10000000 + avg_dur_ms: 10 + avg_dur_ns: 10000000 } blocking_calls { name: "measure" @@ -259,6 +305,8 @@ android_blocking_calls_unagg { total_dur_ns: 10000000 max_dur_ns: 10000000 min_dur_ns: 10000000 + avg_dur_ms: 10 + avg_dur_ns: 10000000 } blocking_calls { name: "monitor contention with <...>" @@ -269,6 +317,8 @@ android_blocking_calls_unagg { total_dur_ns: 10000000 max_dur_ns: 10000000 min_dur_ns: 10000000 + avg_dur_ms: 10 + avg_dur_ns: 10000000 } blocking_calls { name: "postAndWait" @@ -279,6 +329,8 @@ android_blocking_calls_unagg { total_dur_ns: 10000000 max_dur_ns: 10000000 min_dur_ns: 10000000 + avg_dur_ms: 10 + avg_dur_ns: 10000000 } blocking_calls { name: "relayoutWindow <...>" @@ -289,6 +341,8 @@ android_blocking_calls_unagg { total_dur_ns: 10000000 max_dur_ns: 10000000 min_dur_ns: 10000000 + avg_dur_ms: 10 + avg_dur_ns: 10000000 } blocking_calls { name: "traversal" @@ -299,6 +353,8 @@ android_blocking_calls_unagg { total_dur_ns: 10000000 max_dur_ns: 10000000 min_dur_ns: 10000000 + avg_dur_ms: 10 + avg_dur_ns: 10000000 } } process_with_blocking_calls { @@ -317,6 +373,8 @@ process_with_blocking_calls { total_dur_ns: 10000000 max_dur_ns: 10000000 min_dur_ns: 10000000 + avg_dur_ms: 10 + avg_dur_ns: 10000000 } blocking_calls { name: "Handler: android.view.View" @@ -327,6 +385,8 @@ process_with_blocking_calls { total_dur_ns: 10000000 max_dur_ns: 10000000 min_dur_ns: 10000000 + avg_dur_ms: 10 + avg_dur_ns: 10000000 } blocking_calls { name: "Handler: com.android.keyguard.KeyguardUpdateMonitor" @@ -337,6 +397,8 @@ process_with_blocking_calls { total_dur_ns: 10000000 max_dur_ns: 10000000 min_dur_ns: 10000000 + avg_dur_ms: 10 + avg_dur_ns: 10000000 } blocking_calls { name: "Handler: com.android.systemui.broadcast.ActionReceiver" @@ -347,6 +409,8 @@ process_with_blocking_calls { total_dur_ns: 10000000 max_dur_ns: 10000000 min_dur_ns: 10000000 + avg_dur_ms: 10 + avg_dur_ns: 10000000 } blocking_calls { name: "Handler: com.android.systemui.qs.external.TileServiceManager" @@ -357,6 +421,8 @@ process_with_blocking_calls { total_dur_ns: 10000000 max_dur_ns: 10000000 min_dur_ns: 10000000 + avg_dur_ms: 10 + avg_dur_ns: 10000000 } } } 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 50f4757dfc..3d69fdcad6 100644 --- a/test/trace_processor/diff_tests/metrics/android/android_boot.out +++ b/test/trace_processor/diff_tests/metrics/android/android_boot.out @@ -912,4 +912,304 @@ avg_oom_adj_dur: 272766.0 oom_adj_event_count: 2 oom_adj_reason: "unbindService" } +post_boot_broadcast_process_count_by_intent { +name: "android.appwidget.action.APPWIDGET_ENABLED" +count: 2 +} +post_boot_broadcast_process_count_by_intent { +name: "android.appwidget.action.APPWIDGET_UPDATE" +count: 2 } +post_boot_broadcast_process_count_by_intent { +name: "android.appwidget.action.APPWIDGET_UPDATE_OPTIONS" +count: 1 +} +post_boot_broadcast_process_count_by_intent { +name: "android.intent.action.BATTERY_CHANGED" +count: 2 +} +post_boot_broadcast_process_count_by_intent { +name: "android.intent.action.SIM_STATE_CHANGED" +count: 38 +} +post_boot_broadcast_process_count_by_intent { +name: "android.intent.action.USER_PRESENT" +count: 10 +} +post_boot_broadcast_process_count_by_intent { +name: "android.intent.action.USER_UNLOCKED" +count: 104 +} +post_boot_broadcast_process_count_by_intent { +name: "android.net.conn.CONNECTIVITY_CHANGE" +count: 1 +} +post_boot_broadcast_process_count_by_intent { +name: "android.net.wifi.WIFI_STATE_CHANGED" +count: 2 +} +post_boot_broadcast_process_count_by_intent { +name: "com.google.android.calendar.APPWIDGET_REFRESH_MODEL" +count: 1 +} +post_boot_broadcast_count_by_process { +name: "com.android.phone" +count: 43 +} +post_boot_broadcast_count_by_process { +name: "com.android.settings" +count: 1 +} +post_boot_broadcast_count_by_process { +name: "com.android.systemui" +count: 5 +} +post_boot_broadcast_count_by_process { +name: "com.google.android.apps.nexuslauncher" +count: 2 +} +post_boot_broadcast_count_by_process { +name: "com.google.android.apps.scone" +count: 9 +} +post_boot_broadcast_count_by_process { +name: "com.google.android.apps.wellbeing" +count: 11 +} +post_boot_broadcast_count_by_process { +name: "com.google.android.apps.work.clouddpc" +count: 11 +} +post_boot_broadcast_count_by_process { +name: "com.google.android.apps.youtube.music" +count: 1 +} +post_boot_broadcast_count_by_process { +name: "com.google.android.bluetooth" +count: 2 +} +post_boot_broadcast_count_by_process { +name: "com.google.android.calendar" +count: 4 +} +post_boot_broadcast_count_by_process { +name: "com.google.android.connectivitythermalpowermanager" +count: 4 +} +post_boot_broadcast_count_by_process { +name: "com.google.android.deskclock" +count: 11 +} +post_boot_broadcast_count_by_process { +name: "com.google.android.devicelockcontroller" +count: 1 +} +post_boot_broadcast_count_by_process { +name: "com.google.android.dialer" +count: 14 +} +post_boot_broadcast_count_by_process { +name: "com.google.android.euicc" +count: 11 +} +post_boot_broadcast_count_by_process { +name: "com.google.android.gms" +count: 2 +} +post_boot_broadcast_count_by_process { +name: "com.google.android.gms.persistent" +count: 5 +} +post_boot_broadcast_count_by_process { +name: "com.google.android.ims" +count: 2 +} +post_boot_broadcast_count_by_process { +name: "com.shannon.imsservice" +count: 2 +} +post_boot_broadcast_count_by_process { +name: "system" +count: 22 +} +post_boot_brodcast_duration_agg_by_intent { +name: "android.appwidget.action.APPWIDGET_ENABLED" +avg_duration: 832137248.0 +max_duration: 1604539755 +sum_duration: 1664274496 +} +post_boot_brodcast_duration_agg_by_intent { +name: "android.appwidget.action.APPWIDGET_UPDATE" +avg_duration: 91613911.5 +max_duration: 183227824 +sum_duration: 183227823 +} +post_boot_brodcast_duration_agg_by_intent { +name: "android.appwidget.action.APPWIDGET_UPDATE_OPTIONS" +avg_duration: 105377889.0 +max_duration: 105377889 +sum_duration: 105377889 +} +post_boot_brodcast_duration_agg_by_intent { +name: "android.intent.action.BATTERY_CHANGED" +avg_duration: 165913.5 +max_duration: 222493 +sum_duration: 331827 +} +post_boot_brodcast_duration_agg_by_intent { +name: "android.intent.action.SIM_STATE_CHANGED" +avg_duration: 9982735.5 +max_duration: 195921793 +sum_duration: 379343949 +} +post_boot_brodcast_duration_agg_by_intent { +name: "android.intent.action.USER_PRESENT" +avg_duration: 14583.3 +max_duration: 20304 +sum_duration: 145833 +} +post_boot_brodcast_duration_agg_by_intent { +name: "android.intent.action.USER_UNLOCKED" +avg_duration: 66555.72115384616 +max_duration: 1414754 +sum_duration: 6921795 +} +post_boot_brodcast_duration_agg_by_intent { +name: "android.net.conn.CONNECTIVITY_CHANGE" +avg_duration: 50415.0 +max_duration: 50415 +sum_duration: 50415 +} +post_boot_brodcast_duration_agg_by_intent { +name: "android.net.wifi.WIFI_STATE_CHANGED" +avg_duration: 290506.5 +max_duration: 538533 +sum_duration: 581013 +} +post_boot_brodcast_duration_agg_by_intent { +name: "com.google.android.calendar.APPWIDGET_REFRESH_MODEL" +avg_duration: -1.0 +max_duration: -1 +sum_duration: -1 +} +post_boot_brodcast_duration_agg_by_process { +name: "com.android.phone" +avg_duration: 3517922.534883721 +max_duration: 146637410 +sum_duration: 151270669 +} +post_boot_brodcast_duration_agg_by_process { +name: "com.android.settings" +avg_duration: 38697.0 +max_duration: 38697 +sum_duration: 38697 +} +post_boot_brodcast_duration_agg_by_process { +name: "com.android.systemui" +avg_duration: 95906.4 +max_duration: 189982 +sum_duration: 479532 +} +post_boot_brodcast_duration_agg_by_process { +name: "com.google.android.apps.nexuslauncher" +avg_duration: 33793.0 +max_duration: 50252 +sum_duration: 67586 +} +post_boot_brodcast_duration_agg_by_process { +name: "com.google.android.apps.scone" +avg_duration: 22935.666666666668 +max_duration: 41830 +sum_duration: 206421 +} +post_boot_brodcast_duration_agg_by_process { +name: "com.google.android.apps.wellbeing" +avg_duration: 69202.63636363637 +max_duration: 523967 +sum_duration: 761229 +} +post_boot_brodcast_duration_agg_by_process { +name: "com.google.android.apps.work.clouddpc" +avg_duration: 53555.63636363636 +max_duration: 371867 +sum_duration: 589112 +} +post_boot_brodcast_duration_agg_by_process { +name: "com.google.android.apps.youtube.music" +avg_duration: 50415.0 +max_duration: 50415 +sum_duration: 50415 +} +post_boot_brodcast_duration_agg_by_process { +name: "com.google.android.bluetooth" +avg_duration: 221008.0 +max_duration: 406819 +sum_duration: 442016 +} +post_boot_brodcast_duration_agg_by_process { +name: "com.google.android.calendar" +avg_duration: 87085113.25 +max_duration: 183227824 +sum_duration: 348340453 +} +post_boot_brodcast_duration_agg_by_process { +name: "com.google.android.connectivitythermalpowermanager" +avg_duration: 228210.0 +max_duration: 538533 +sum_duration: 912840 +} +post_boot_brodcast_duration_agg_by_process { +name: "com.google.android.deskclock" +avg_duration: 145975538.0 +max_duration: 1604539755 +sum_duration: 1605730918 +} +post_boot_brodcast_duration_agg_by_process { +name: "com.google.android.devicelockcontroller" +avg_duration: 23682.0 +max_duration: 23682 +sum_duration: 23682 +} +post_boot_brodcast_duration_agg_by_process { +name: "com.google.android.dialer" +avg_duration: 138898.5 +max_duration: 1414754 +sum_duration: 1944579 +} +post_boot_brodcast_duration_agg_by_process { +name: "com.google.android.euicc" +avg_duration: 47296.72727272727 +max_duration: 305339 +sum_duration: 520264 +} +post_boot_brodcast_duration_agg_by_process { +name: "com.google.android.gms" +avg_duration: 113945576.5 +max_duration: 195921793 +sum_duration: 227891153 +} +post_boot_brodcast_duration_agg_by_process { +name: "com.google.android.gms.persistent" +avg_duration: 18220.8 +max_duration: 32430 +sum_duration: 91104 +} +post_boot_brodcast_duration_agg_by_process { +name: "com.google.android.ims" +avg_duration: 307169.5 +max_duration: 436726 +sum_duration: 614339 +} +post_boot_brodcast_duration_agg_by_process { +name: "com.shannon.imsservice" +avg_duration: 40283.0 +max_duration: 46305 +sum_duration: 80566 +} +post_boot_brodcast_duration_agg_by_process { +name: "system" +avg_duration: 9066.545454545454 +max_duration: 26774 +sum_duration: 199464 +} +} \ No newline at end of file diff --git a/test/trace_processor/diff_tests/metrics/android/android_broadcasts.out b/test/trace_processor/diff_tests/metrics/android/android_broadcasts.out new file mode 100644 index 0000000000..2d033cea4b --- /dev/null +++ b/test/trace_processor/diff_tests/metrics/android/android_broadcasts.out @@ -0,0 +1,302 @@ +android_broadcasts { +process_count_by_intent { +name: "android.appwidget.action.APPWIDGET_ENABLED" +count: 2 +} +process_count_by_intent { +name: "android.appwidget.action.APPWIDGET_UPDATE" +count: 2 +} +process_count_by_intent { +name: "android.appwidget.action.APPWIDGET_UPDATE_OPTIONS" +count: 1 +} +process_count_by_intent { +name: "android.intent.action.BATTERY_CHANGED" +count: 2 +} +process_count_by_intent { +name: "android.intent.action.SIM_STATE_CHANGED" +count: 38 +} +process_count_by_intent { +name: "android.intent.action.USER_PRESENT" +count: 10 +} +process_count_by_intent { +name: "android.intent.action.USER_UNLOCKED" +count: 105 +} +process_count_by_intent { +name: "android.net.conn.CONNECTIVITY_CHANGE" +count: 1 +} +process_count_by_intent { +name: "android.net.wifi.WIFI_STATE_CHANGED" +count: 2 +} +process_count_by_intent { +name: "com.google.android.calendar.APPWIDGET_REFRESH_MODEL" +count: 1 +} +broadcast_count_by_process { +name: "com.android.phone" +count: 43 +} +broadcast_count_by_process { +name: "com.android.settings" +count: 1 +} +broadcast_count_by_process { +name: "com.android.systemui" +count: 5 +} +broadcast_count_by_process { +name: "com.google.android.apps.nexuslauncher" +count: 2 +} +broadcast_count_by_process { +name: "com.google.android.apps.scone" +count: 9 +} +broadcast_count_by_process { +name: "com.google.android.apps.wellbeing" +count: 11 +} +broadcast_count_by_process { +name: "com.google.android.apps.work.clouddpc" +count: 11 +} +broadcast_count_by_process { +name: "com.google.android.apps.youtube.music" +count: 1 +} +broadcast_count_by_process { +name: "com.google.android.bluetooth" +count: 2 +} +broadcast_count_by_process { +name: "com.google.android.calendar" +count: 4 +} +broadcast_count_by_process { +name: "com.google.android.connectivitythermalpowermanager" +count: 4 +} +broadcast_count_by_process { +name: "com.google.android.deskclock" +count: 11 +} +broadcast_count_by_process { +name: "com.google.android.devicelockcontroller" +count: 1 +} +broadcast_count_by_process { +name: "com.google.android.dialer" +count: 14 +} +broadcast_count_by_process { +name: "com.google.android.euicc" +count: 11 +} +broadcast_count_by_process { +name: "com.google.android.gms" +count: 2 +} +broadcast_count_by_process { +name: "com.google.android.gms.persistent" +count: 5 +} +broadcast_count_by_process { +name: "com.google.android.ims" +count: 2 +} +broadcast_count_by_process { +name: "com.shannon.imsservice" +count: 2 +} +broadcast_count_by_process { +name: "system" +count: 23 +} +brodcast_duration_agg_by_intent { +name: "android.appwidget.action.APPWIDGET_ENABLED" +avg_duration: 832137248.0 +max_duration: 1604539755 +sum_duration: 1664274496 +} +brodcast_duration_agg_by_intent { +name: "android.appwidget.action.APPWIDGET_UPDATE" +avg_duration: 91613911.5 +max_duration: 183227824 +sum_duration: 183227823 +} +brodcast_duration_agg_by_intent { +name: "android.appwidget.action.APPWIDGET_UPDATE_OPTIONS" +avg_duration: 105377889.0 +max_duration: 105377889 +sum_duration: 105377889 +} +brodcast_duration_agg_by_intent { +name: "android.intent.action.BATTERY_CHANGED" +avg_duration: 165913.5 +max_duration: 222493 +sum_duration: 331827 +} +brodcast_duration_agg_by_intent { +name: "android.intent.action.SIM_STATE_CHANGED" +avg_duration: 9982735.5 +max_duration: 195921793 +sum_duration: 379343949 +} +brodcast_duration_agg_by_intent { +name: "android.intent.action.USER_PRESENT" +avg_duration: 14583.3 +max_duration: 20304 +sum_duration: 145833 +} +brodcast_duration_agg_by_intent { +name: "android.intent.action.USER_UNLOCKED" +avg_duration: 66119.10476190476 +max_duration: 1414754 +sum_duration: 6942506 +} +brodcast_duration_agg_by_intent { +name: "android.net.conn.CONNECTIVITY_CHANGE" +avg_duration: 50415.0 +max_duration: 50415 +sum_duration: 50415 +} +brodcast_duration_agg_by_intent { +name: "android.net.wifi.WIFI_STATE_CHANGED" +avg_duration: 290506.5 +max_duration: 538533 +sum_duration: 581013 +} +brodcast_duration_agg_by_intent { +name: "com.google.android.calendar.APPWIDGET_REFRESH_MODEL" +avg_duration: -1.0 +max_duration: -1 +sum_duration: -1 +} +brodcast_duration_agg_by_process { +name: "com.android.phone" +avg_duration: 3517922.534883721 +max_duration: 146637410 +sum_duration: 151270669 +} +brodcast_duration_agg_by_process { +name: "com.android.settings" +avg_duration: 38697.0 +max_duration: 38697 +sum_duration: 38697 +} +brodcast_duration_agg_by_process { +name: "com.android.systemui" +avg_duration: 95906.4 +max_duration: 189982 +sum_duration: 479532 +} +brodcast_duration_agg_by_process { +name: "com.google.android.apps.nexuslauncher" +avg_duration: 33793.0 +max_duration: 50252 +sum_duration: 67586 +} +brodcast_duration_agg_by_process { +name: "com.google.android.apps.scone" +avg_duration: 22935.666666666668 +max_duration: 41830 +sum_duration: 206421 +} +brodcast_duration_agg_by_process { +name: "com.google.android.apps.wellbeing" +avg_duration: 69202.63636363637 +max_duration: 523967 +sum_duration: 761229 +} +brodcast_duration_agg_by_process { +name: "com.google.android.apps.work.clouddpc" +avg_duration: 53555.63636363636 +max_duration: 371867 +sum_duration: 589112 +} +brodcast_duration_agg_by_process { +name: "com.google.android.apps.youtube.music" +avg_duration: 50415.0 +max_duration: 50415 +sum_duration: 50415 +} +brodcast_duration_agg_by_process { +name: "com.google.android.bluetooth" +avg_duration: 221008.0 +max_duration: 406819 +sum_duration: 442016 +} +brodcast_duration_agg_by_process { +name: "com.google.android.calendar" +avg_duration: 87085113.25 +max_duration: 183227824 +sum_duration: 348340453 +} +brodcast_duration_agg_by_process { +name: "com.google.android.connectivitythermalpowermanager" +avg_duration: 228210.0 +max_duration: 538533 +sum_duration: 912840 +} +brodcast_duration_agg_by_process { +name: "com.google.android.deskclock" +avg_duration: 145975538.0 +max_duration: 1604539755 +sum_duration: 1605730918 +} +brodcast_duration_agg_by_process { +name: "com.google.android.devicelockcontroller" +avg_duration: 23682.0 +max_duration: 23682 +sum_duration: 23682 +} +brodcast_duration_agg_by_process { +name: "com.google.android.dialer" +avg_duration: 138898.5 +max_duration: 1414754 +sum_duration: 1944579 +} +brodcast_duration_agg_by_process { +name: "com.google.android.euicc" +avg_duration: 47296.72727272727 +max_duration: 305339 +sum_duration: 520264 +} +brodcast_duration_agg_by_process { +name: "com.google.android.gms" +avg_duration: 113945576.5 +max_duration: 195921793 +sum_duration: 227891153 +} +brodcast_duration_agg_by_process { +name: "com.google.android.gms.persistent" +avg_duration: 18220.8 +max_duration: 32430 +sum_duration: 91104 +} +brodcast_duration_agg_by_process { +name: "com.google.android.ims" +avg_duration: 307169.5 +max_duration: 436726 +sum_duration: 614339 +} +brodcast_duration_agg_by_process { +name: "com.shannon.imsservice" +avg_duration: 40283.0 +max_duration: 46305 +sum_duration: 80566 +} +brodcast_duration_agg_by_process { +name: "system" +avg_duration: 9572.826086956522 +max_duration: 26774 +sum_duration: 220175 +} +} \ No newline at end of file diff --git a/test/trace_processor/diff_tests/metrics/android/tests.py b/test/trace_processor/diff_tests/metrics/android/tests.py index a1c244843b..499b248753 100644 --- a/test/trace_processor/diff_tests/metrics/android/tests.py +++ b/test/trace_processor/diff_tests/metrics/android/tests.py @@ -140,16 +140,16 @@ def test_android_sysui_notifications_blocking_calls(self): out=Path('android_sysui_notifications_blocking_calls_metric.out')) def test_sysui_notif_shade_list_builder(self): - return DiffTestBlueprint( - trace=Path('android_sysui_notif_shade_list_builder_metric.py'), - query=Metric('sysui_notif_shade_list_builder_metric'), - out=Path('sysui_notif_shade_list_builder_metric.out')) + return DiffTestBlueprint( + trace=Path('android_sysui_notif_shade_list_builder_metric.py'), + query=Metric('sysui_notif_shade_list_builder_metric'), + out=Path('sysui_notif_shade_list_builder_metric.out')) def test_sysui_update_notif_on_ui_mode_changed(self): - return DiffTestBlueprint( - trace=Path('sysui_update_notif_on_ui_mode_changed_metric.py'), - query=Metric('sysui_update_notif_on_ui_mode_changed_metric'), - out=Path('sysui_update_notif_on_ui_mode_changed_metric.out')) + return DiffTestBlueprint( + trace=Path('sysui_update_notif_on_ui_mode_changed_metric.py'), + query=Metric('sysui_update_notif_on_ui_mode_changed_metric'), + out=Path('sysui_update_notif_on_ui_mode_changed_metric.out')) def test_monitor_contention_metric(self): return DiffTestBlueprint( @@ -225,17 +225,15 @@ def test_ad_services_metric(self): def test_android_boot_unagg(self): return DiffTestBlueprint( - trace=DataPath('android_postboot_unlock.pftrace'), - query=Metric("android_boot_unagg"), - out=Path('android_boot_unagg.out') - ) + trace=DataPath('android_postboot_unlock.pftrace'), + query=Metric("android_boot_unagg"), + out=Path('android_boot_unagg.out')) def test_android_app_process_starts(self): return DiffTestBlueprint( - trace=DataPath('android_postboot_unlock.pftrace'), - query=Metric("android_app_process_starts"), - out=Path('android_app_process_starts.out') - ) + trace=DataPath('android_postboot_unlock.pftrace'), + query=Metric("android_app_process_starts"), + out=Path('android_app_process_starts.out')) def test_android_garbage_collection(self): return DiffTestBlueprint( @@ -295,8 +293,8 @@ def test_android_auto_multiuser_switch(self): } } """), - query=Metric('android_auto_multiuser'), - out=TextProto(r""" + query=Metric('android_auto_multiuser'), + out=TextProto(r""" android_auto_multiuser { user_switch { user_id: 11 @@ -311,9 +309,9 @@ def test_android_auto_multiuser_switch(self): def test_android_auto_multiuser_switch_with_previous_user_data(self): return DiffTestBlueprint( - trace=Path("android_auto_multiuser.textproto"), - query=Metric('android_auto_multiuser'), - out=TextProto(r""" + trace=Path("android_auto_multiuser.textproto"), + query=Metric('android_auto_multiuser'), + out=TextProto(r""" android_auto_multiuser { user_switch { user_id: 11 @@ -326,12 +324,107 @@ def test_android_auto_multiuser_switch_with_previous_user_data(self): total_memory_usage_kb: 2048 } } + user_switch { + user_id: 11 + start_event: "UserController.startUser-11-fg-start-mode-1" + end_event: "finishUserStopped-10-[stopUser]" + duration_ms: 2100 + previous_user_info { + user_id: 10 + total_cpu_time_ms: 19 + total_memory_usage_kb: 3072 + } + } } """)) + def test_android_auto_multiuser_timing_table(self): + return DiffTestBlueprint( + trace=Path("android_auto_multiuser.textproto"), + query=""" + INCLUDE PERFETTO MODULE android.auto.multiuser; + SELECT * FROM android_auto_multiuser_timing; + """, + out=Csv(""" + "event_start_user_id","event_start_time","event_end_time","event_end_name","event_start_name","duration" + "11",3000000000,3999999999,"com.android.car.carlauncher","UserController.startUser-11-fg-start-mode-1",999999999 + "11",3000000000,5100000000,"finishUserStopped-10-[stopUser]","UserController.startUser-11-fg-start-mode-1",2100000000 + """)) + def test_android_oom_adjuster(self): return DiffTestBlueprint( - trace=DataPath('android_postboot_unlock.pftrace'), - query=Metric("android_oom_adjuster"), - out=Path('android_oom_adjuster.out') - ) + trace=DataPath('android_postboot_unlock.pftrace'), + query=Metric("android_oom_adjuster"), + out=Path('android_oom_adjuster.out')) + + def test_android_broadcasts(self): + return DiffTestBlueprint( + trace=DataPath('android_postboot_unlock.pftrace'), + query=Metric("android_broadcasts"), + out=Path('android_broadcasts.out')) + + def test_wattson_app_startup_output(self): + return DiffTestBlueprint( + trace=DataPath('android_calculator_startup.pb'), + query=Metric("wattson_app_startup"), + out=Csv(""" + wattson_app_startup { + metric_version: 1 + period_info { + period_id: 1 + period_dur: 385136434 + rail { + name: "cpu_subsystem" + estimate_mw: 4568.159180 + rail { + name: "DSU_SCU" + estimate_mw: 1142.600708 + } + rail { + name: "policy0" + estimate_mw: 578.353088 + rail { + name: "cpu0" + estimate_mw: 149.026062 + } + rail { + name: "cpu1" + estimate_mw: 130.140015 + } + rail { + name: "cpu2" + estimate_mw: 127.601807 + } + rail { + name: "cpu3" + estimate_mw: 171.585205 + } + } + rail { + name: "policy4" + estimate_mw: 684.187256 + rail { + name: "cpu4" + estimate_mw: 344.394531 + } + rail { + name: "cpu5" + estimate_mw: 339.792725 + } + } + rail { + name: "policy6" + estimate_mw: 2163.018066 + rail { + name: "cpu6" + estimate_mw: 1080.465820 + } + rail { + name: "cpu7" + estimate_mw: 1082.552246 + } + } + } + } + } + """)) 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 fa52426ec5..00a297aef2 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,18 +429,55 @@ def test_chrome_scroll_jank_v3(self): query=Metric('chrome_scroll_jank_v3'), out=TextProto(r""" [perfetto.protos.chrome_scroll_jank_v3] { - trace_num_frames: 354 - trace_num_janky_frames: 1 - trace_scroll_jank_percentage: 0.2824858757062147 - vsync_interval_ms: 10.483 + trace_num_frames: 364 + trace_num_janky_frames: 6 + trace_scroll_jank_percentage: 1.6483516483516483 + vsync_interval_ms: 10.318 scrolls { - num_frames: 122 + num_frames: 119 num_janky_frames: 1 - scroll_jank_percentage: 0.819672131147541 - max_delay_since_last_frame: 2.13021081751407 + scroll_jank_percentage: 0.8403361344537815 + max_delay_since_last_frame: 2.153421205660012 + scroll_jank_causes { + cause: "SubmitCompositorFrameToPresentationCompositorFrame" + sub_cause: "StartDrawToSwapStart" + delay_since_last_frame: 2.153421205660012 + } + } + scrolls { + num_frames: 6 + num_janky_frames: 1 + scroll_jank_percentage: 16.666666666666668 + max_delay_since_last_frame: 2.155456483814693 + scroll_jank_causes { + cause: "SubmitCompositorFrameToPresentationCompositorFrame" + sub_cause: "StartDrawToSwapStart" + delay_since_last_frame: 2.155456483814693 + } + } + scrolls { + num_frames: 129 + num_janky_frames: 4 + scroll_jank_percentage: 3.10077519379845 + max_delay_since_last_frame: 2.1642760224849775 + scroll_jank_causes { + cause: "SubmitCompositorFrameToPresentationCompositorFrame" + sub_cause: "StartDrawToSwapStart" + delay_since_last_frame: 2.1556503198294243 + } + scroll_jank_causes { + cause: "SubmitCompositorFrameToPresentationCompositorFrame" + sub_cause: "BufferReadyToLatch" + delay_since_last_frame: 2.1564256638883506 + } + scroll_jank_causes { + cause: "SubmitCompositorFrameToPresentationCompositorFrame" + sub_cause: "StartDrawToSwapStart" + delay_since_last_frame: 2.15758867997674 + } scroll_jank_causes { cause: "RendererCompositorQueueingDelay" - delay_since_last_frame: 2.13021081751407 + delay_since_last_frame: 2.1642760224849775 } } } diff --git a/test/trace_processor/diff_tests/metrics/graphics/android_jank_cuj.out b/test/trace_processor/diff_tests/metrics/graphics/android_jank_cuj.out index 22045da503..1bcd5fdb8c 100644 --- a/test/trace_processor/diff_tests/metrics/graphics/android_jank_cuj.out +++ b/test/trace_processor/diff_tests/metrics/graphics/android_jank_cuj.out @@ -11,11 +11,6 @@ android_jank_cuj { apk_version_code: 1 debuggable: false } - packages_for_uid { - package_name: "com.android.systemui" - apk_version_code: 1 - debuggable: false - } pid: 1000 } ts: 0 @@ -192,11 +187,6 @@ android_jank_cuj { apk_version_code: 1 debuggable: false } - packages_for_uid { - package_name: "com.android.systemui" - apk_version_code: 1 - debuggable: false - } pid: 1000 } ts: 0 diff --git a/test/trace_processor/diff_tests/metrics/memory/android_lmk_reason.out b/test/trace_processor/diff_tests/metrics/memory/android_lmk_reason.out index 69de6240ba..98d13d37ac 100644 --- a/test/trace_processor/diff_tests/metrics/memory/android_lmk_reason.out +++ b/test/trace_processor/diff_tests/metrics/memory/android_lmk_reason.out @@ -36,16 +36,6 @@ android_lmk_reason { apk_version_code: 123 debuggable: false } - packages_for_uid { - package_name: "app" - apk_version_code: 123 - debuggable: false - } - packages_for_uid { - package_name: "shared_uid_app" - apk_version_code: 345 - debuggable: false - } } oom_score_adj: 0 size: 5000 diff --git a/test/trace_processor/diff_tests/metrics/memory/tests.py b/test/trace_processor/diff_tests/metrics/memory/tests.py index 7ea5fda012..1332a0a2cf 100644 --- a/test/trace_processor/diff_tests/metrics/memory/tests.py +++ b/test/trace_processor/diff_tests/metrics/memory/tests.py @@ -93,6 +93,17 @@ def test_android_ion_stat(self): def test_android_dma_heap_stat(self): return DiffTestBlueprint( trace=TextProto(r""" + packet { + timestamp: 1 + process_tree { + processes { + pid: 1 + ppid: 1 + uid: 0 + cmdline: "myprocess" + } + } + } packet { ftrace_events { cpu: 0 @@ -105,18 +116,22 @@ def test_android_dma_heap_stat(self): total_allocated: 2048 } } - } - } - packet { - ftrace_events { - cpu: 0 + event { + timestamp: 150 + pid: 1 + dma_heap_stat { + inode: 124 + len: 2048 + total_allocated: 4096 + } + } event { timestamp: 200 pid: 1 dma_heap_stat { inode: 123 len: -1024 - total_allocated: 1024 + total_allocated: 3072 } } } @@ -125,10 +140,15 @@ def test_android_dma_heap_stat(self): query=Metric('android_dma_heap'), out=TextProto(r""" android_dma_heap { - avg_size_bytes: 2048.0 - min_size_bytes: 1024.0 - max_size_bytes: 2048.0 - total_alloc_size_bytes: 1024.0 + avg_size_bytes: 3072.0 + min_size_bytes: 2048.0 + max_size_bytes: 4096.0 + total_alloc_size_bytes: 3072.0 + total_delta_bytes: 2048 + process_stats { + process_name: "myprocess" + delta_bytes: 2048 + } } """)) @@ -248,6 +268,17 @@ def test_android_lmk_oom(self): } """)) + def test_android_lmk_reason(self): + return DiffTestBlueprint( + trace=DataPath('lmk_userspace.pb'), + query=Metric('android_lmk_reason'), + # TODO(mayzner): Find a trace that returns results. This is still + # beneficial though, as at least this metric is run. + out=TextProto(r""" + android_lmk_reason { + } + """)) + def test_android_mem_delta(self): return DiffTestBlueprint( trace=Path('android_mem_delta.py'), @@ -360,3 +391,53 @@ def test_android_dma_buffer_tracks(self): "name","ts","dur","name" "mem.dma_buffer",100,100,"1 kB" """)) + + def test_android_dma_heap_inode(self): + return DiffTestBlueprint( + trace=TextProto(r""" + packet { + ftrace_events { + cpu: 0 + event { + timestamp: 100 + pid: 1 + dma_heap_stat { + inode: 123 + len: 1024 + total_allocated: 2048 + } + } + } + } + packet { + ftrace_events { + cpu: 0 + event { + timestamp: 200 + pid: 1 + dma_heap_stat { + inode: 123 + len: -1024 + total_allocated: 1024 + } + } + } + } + """), + query=""" + SELECT + tt.name, + tt.utid, + c.ts, + CAST(c.value AS INT) AS value, + args.int_value AS inode + FROM thread_counter_track tt + JOIN counter c ON c.track_id = tt.id + JOIN args USING (arg_set_id) + WHERE tt.name = 'mem.dma_heap_change' AND args.key = 'inode'; + """, + out=Csv(""" + "name","utid","ts","value","inode" + "mem.dma_heap_change",1,100,1024,123 + "mem.dma_heap_change",1,200,-1024,123 + """)) diff --git a/test/trace_processor/diff_tests/metrics/profiling/java_heap_class_stats.out b/test/trace_processor/diff_tests/metrics/profiling/java_heap_class_stats.out index 2a2a361d74..0f1154e8f1 100644 --- a/test/trace_processor/diff_tests/metrics/profiling/java_heap_class_stats.out +++ b/test/trace_processor/diff_tests/metrics/profiling/java_heap_class_stats.out @@ -10,6 +10,7 @@ java_heap_class_stats { ts: 10 type_count { type_name: "FactoryProducerDelegateImplActor" + is_libcore_or_array: false obj_count: 1 size_bytes: 64 native_size_bytes: 0 @@ -22,6 +23,7 @@ java_heap_class_stats { } type_count { type_name: "Foo" + is_libcore_or_array: false obj_count: 2 size_bytes: 160 native_size_bytes: 0 @@ -34,6 +36,7 @@ java_heap_class_stats { } type_count { type_name: "DeobfuscatedA" + is_libcore_or_array: false obj_count: 1 size_bytes: 1024 native_size_bytes: 0 @@ -46,6 +49,7 @@ java_heap_class_stats { } type_count { type_name: "DeobfuscatedA[]" + is_libcore_or_array: false obj_count: 1 size_bytes: 256 native_size_bytes: 0 @@ -58,6 +62,7 @@ java_heap_class_stats { } type_count { type_name: "java.lang.Class" + is_libcore_or_array: false obj_count: 1 size_bytes: 256 native_size_bytes: 0 diff --git a/test/trace_processor/diff_tests/metrics/startup/android_startup.out b/test/trace_processor/diff_tests/metrics/startup/android_startup.out index 9dfc289115..29e6a3c6b4 100644 --- a/test/trace_processor/diff_tests/metrics/startup/android_startup.out +++ b/test/trace_processor/diff_tests/metrics/startup/android_startup.out @@ -1,6 +1,6 @@ android_startup { startup { - startup_id: 2 + startup_id: 0 package_name: "com.google.android.calendar" process_name: "com.google.android.calendar" zygote_new_process: false @@ -42,11 +42,6 @@ android_startup { apk_version_code: 123 debuggable: false } - packages_for_uid { - package_name: "com.google.android.calendar" - apk_version_code: 123 - debuggable: false - }, pid: 3 } report_fully_drawn { @@ -67,10 +62,27 @@ android_startup { installd_dur_ns: 0 dex2oat_dur_ns: 0 } - slow_start_reason_detailed { + slow_start_reason_with_details { + reason_id: MAIN_THREAD_TIME_SPENT_IN_RUNNABLE reason: "Main Thread - Time spent in Runnable state" - details: " target 15% actual 74.07% [ longest_chunk: start_s 3.0e-08 dur_ms 8.0e-0 thread_id 3 thread_name com.google.android.calendar ] [ extra_info: launches_dur_ms 0.0001 runnable_dur_ms 8.0e-0 R_sum_dur_ms 8.0e-0 R+(Preempted)_sum_dur_ms 0.0 ]" - } + severity: WARNING + expected_value { + value: 15 + unit: PERCENTAGE + higher_expected: false + } + actual_value { + value: 74 + dur: 80 + } + launch_dur: 108 + trace_thread_sections { + start_timestamp: 130 + end_timestamp: 210 + thread_utid: 3 + thread_name: "com.google.android.calendar" + } + } startup_type: "warm" } } diff --git a/test/trace_processor/diff_tests/metrics/startup/android_startup_attribution.out b/test/trace_processor/diff_tests/metrics/startup/android_startup_attribution.out index 9b6e742927..9dba7b67c0 100644 --- a/test/trace_processor/diff_tests/metrics/startup/android_startup_attribution.out +++ b/test/trace_processor/diff_tests/metrics/startup/android_startup_attribution.out @@ -1,6 +1,6 @@ android_startup { startup { - startup_id: 1 + startup_id: 0 package_name: "com.some.app" process_name: "com.some.app" zygote_new_process: false @@ -67,11 +67,6 @@ android_startup { apk_version_code: 123 debuggable: false } - packages_for_uid { - package_name: "com.some.app" - apk_version_code: 123 - debuggable: false - } pid: 3 } event_timestamps { @@ -134,14 +129,70 @@ android_startup { slow_start_reason: "GC Activity" slow_start_reason: "Main Thread - Time spent in OpenDexFilesFromOat*" slow_start_reason: "Main Thread - Binder transactions blocked" - slow_start_reason_detailed { + slow_start_reason_with_details { + reason_id: GC_ACTIVITY reason: "GC Activity" + severity: ERROR + expected_value { + value: 0 + unit: TRUE_OR_FALSE + } + actual_value { + value: 1 + } + launch_dur: 999999900 + trace_slice_sections { + start_timestamp: 340 + end_timestamp: 390 + slice_id: 18 + slice_name: "CollectorTransition mark sweep GC" + } } - slow_start_reason_detailed { + slow_start_reason_with_details { + reason_id: MAIN_THREAD_TIME_SPENT_IN_OPEN_DEX_FILES_FROM_OAT reason: "Main Thread - Time spent in OpenDexFilesFromOat*" + severity: WARNING + expected_value { + value: 20 + unit: PERCENTAGE + higher_expected: false + } + actual_value { + value: 49 + dur: 499999845 + } + launch_dur: 999999900 + trace_slice_sections { + start_timestamp: 170 + end_timestamp: 500000000 + slice_id: 9 + slice_name: "OpenDexFilesFromOat(something else)" + } + trace_slice_sections { + start_timestamp: 150 + end_timestamp: 165 + slice_id: 5 + slice_name: "OpenDexFilesFromOat(something)" + } } - slow_start_reason_detailed { + slow_start_reason_with_details { + reason_id: MAIN_THREAD_BINDER_TRANSCATIONS_BLOCKED reason: "Main Thread - Binder transactions blocked" + severity: WARNING + expected_value { + value: 0 + unit: TRUE_OR_FALSE + } + actual_value { + value: 1 + } + launch_dur: 999999900 + trace_slice_sections { + start_timestamp: 10000000 + end_timestamp: 50000000 + slice_id: 19 + slice_name: "binder transaction" + } } } } diff --git a/test/trace_processor/diff_tests/metrics/startup/android_startup_attribution.py b/test/trace_processor/diff_tests/metrics/startup/android_startup_attribution.py index a48edcc1a2..dc3e0abd92 100644 --- a/test/trace_processor/diff_tests/metrics/startup/android_startup_attribution.py +++ b/test/trace_processor/diff_tests/metrics/startup/android_startup_attribution.py @@ -118,7 +118,7 @@ # JIT compilation slices trace.add_atrace_begin( - ts=150, pid=APP_PID, tid=JIT_TID, buf='JIT compiling someting') + ts=150, pid=APP_PID, tid=JIT_TID, buf='JIT compiling something') trace.add_atrace_end(ts=160, pid=APP_PID, tid=JIT_TID) trace.add_sched(ts=155, prev_pid=0, next_pid=JIT_TID) diff --git a/test/trace_processor/diff_tests/metrics/startup/android_startup_attribution_slow.out b/test/trace_processor/diff_tests/metrics/startup/android_startup_attribution_slow.out index 3e8ff275f6..07eded2b4c 100644 --- a/test/trace_processor/diff_tests/metrics/startup/android_startup_attribution_slow.out +++ b/test/trace_processor/diff_tests/metrics/startup/android_startup_attribution_slow.out @@ -1,6 +1,6 @@ android_startup { startup { - startup_id: 1 + startup_id: 0 package_name: "com.some.app" process_name: "com.some.app" zygote_new_process: false @@ -63,11 +63,6 @@ android_startup { apk_version_code: 123 debuggable: false } - packages_for_uid { - package_name: "com.some.app" - apk_version_code: 123 - debuggable: false - } pid: 3 } event_timestamps { @@ -98,15 +93,88 @@ android_startup { slow_start_reason: "GC Activity" slow_start_reason: "JIT Activity" slow_start_reason: "JIT compiled methods" - slow_start_reason_detailed { + slow_start_reason_with_details { + reason_id: GC_ACTIVITY reason: "GC Activity" + severity: ERROR + expected_value { + value: 0 + unit: TRUE_OR_FALSE + } + actual_value { + value: 1 + } + launch_dur: 999999900000000000 + trace_slice_sections { + start_timestamp: 340000000000 + end_timestamp: 390000000000 + slice_id: 91 + slice_name: "CollectorTransition mark sweep GC" + } } - slow_start_reason_detailed { + slow_start_reason_with_details { + reason_id: JIT_ACTIVITY reason: "JIT Activity" - details: " target 100ms actual 20000.ms [ longest_chunk: start_s 154.99 dur_ms 10000. thread_id 4 thread_name Jit thread pool ]" + severity: WARNING + expected_value { + value: 100000000 + unit: NS + higher_expected: false + } + actual_value { + value: 20000000000 + } + launch_dur: 999999900000000000 + trace_thread_sections { + start_timestamp: 155000000000 + end_timestamp: 165000000000 + thread_utid: 4 + thread_name: "Jit thread pool" + } + trace_thread_sections { + start_timestamp: 170000000000 + end_timestamp: 175000000000 + thread_utid: 4 + thread_name: "Jit thread pool" + } + trace_thread_sections { + start_timestamp: 185000000000 + end_timestamp: 190000000000 + thread_utid: 4 + thread_name: "Jit thread pool" + } } - slow_start_reason_detailed { + slow_start_reason_with_details { + reason_id: JIT_COMPILED_METHODS reason: "JIT compiled methods" + severity: WARNING + expected_value { + value: 65 + unit: COUNT + higher_expected: false + } + actual_value { + value: 71 + } + launch_dur: 999999900000000000 + trace_slice_sections { + start_timestamp: 200000000000 + end_timestamp: 210000000000 + slice_id: 84 + slice_name: "JIT compiling nothing" + } + trace_slice_sections { + start_timestamp: 100000000000 + end_timestamp: 101000000000 + slice_id: 9 + slice_name: "JIT compiling something" + } + trace_slice_sections { + start_timestamp: 101000000000 + end_timestamp: 102000000000 + slice_id: 10 + slice_name: "JIT compiling something" + } } } } diff --git a/test/trace_processor/diff_tests/metrics/startup/android_startup_attribution_slow.py b/test/trace_processor/diff_tests/metrics/startup/android_startup_attribution_slow.py index 93e652fac8..f146157572 100644 --- a/test/trace_processor/diff_tests/metrics/startup/android_startup_attribution_slow.py +++ b/test/trace_processor/diff_tests/metrics/startup/android_startup_attribution_slow.py @@ -126,7 +126,7 @@ def to_s(ts): for t in range(100, 160, 1): # JIT compilation slices trace.add_atrace_begin( - ts=to_s(t), pid=APP_PID, tid=JIT_TID, buf='JIT compiling someting') + ts=to_s(t), pid=APP_PID, tid=JIT_TID, buf='JIT compiling something') trace.add_atrace_end(ts=to_s(t + 1), pid=APP_PID, tid=JIT_TID) trace.add_sched(ts=to_s(155), prev_pid=0, next_pid=JIT_TID) diff --git a/test/trace_processor/diff_tests/metrics/startup/android_startup_breakdown.out b/test/trace_processor/diff_tests/metrics/startup/android_startup_breakdown.out index f40578d661..01a8a83306 100644 --- a/test/trace_processor/diff_tests/metrics/startup/android_startup_breakdown.out +++ b/test/trace_processor/diff_tests/metrics/startup/android_startup_breakdown.out @@ -1,6 +1,6 @@ android_startup { startup { - startup_id: 1 + startup_id: 0 package_name: "com.google.android.calendar" process_name: "com.google.android.calendar" zygote_new_process: true @@ -69,11 +69,6 @@ android_startup { apk_version_code: 123 debuggable: false } - packages_for_uid { - package_name: "com.google.android.calendar" - apk_version_code: 123 - debuggable: false - } pid: 3 } activities { @@ -119,26 +114,129 @@ android_startup { slow_start_reason: "Time spent in bindApplication" slow_start_reason: "Time spent in view inflation" slow_start_reason: "Time spent in ResourcesManager#getResources" - slow_start_reason_detailed { + slow_start_reason_with_details { + reason_id: NO_BASELINE_OR_CLOUD_PROFILES reason: "No baseline or cloud profiles" - details: " target FALSE actual TRUE [ longest_chunk: start_s 154.0 dur_ms 1000.0 thread_id -1 thread_name com.google.android.calendar ] [ extra_info: slice_name location=/system/framework/oat/arm/com.google.android.calendar.odex status=up-to-date filter=speed reason=install-dm ]" + severity: ERROR + expected_value { + value: 0 + unit: TRUE_OR_FALSE + } + actual_value { + value: 1 + } + launch_dur: 108000000000 + trace_slice_sections { + start_timestamp: 204000000000 + end_timestamp: 205000000000 + slice_id: 13 + slice_name: "location=/system/framework/oat/arm/com.google.android.calendar.odex status=up-to-date filter=speed reason=install-dm" + } } - slow_start_reason_detailed { + slow_start_reason_with_details { + reason_id: RUN_FROM_APK reason: "Optimized artifacts missing, run from apk" + severity: ERROR + expected_value { + value: 0 + unit: TRUE_OR_FALSE + } + actual_value { + value: 1 + } + launch_dur: 108000000000 + trace_slice_sections { + start_timestamp: 200000000000 + end_timestamp: 202000000000 + slice_id: 12 + slice_name: "location=error status=io-error-no-oat filter=run-from-apk reason=unknown" + } } - slow_start_reason_detailed { + slow_start_reason_with_details { + reason_id: TIME_SPENT_IN_BIND_APPLICATION reason: "Time spent in bindApplication" + severity: WARNING + expected_value { + value: 1250000000 + unit: NS + higher_expected: false + } + actual_value { + value: 2000000000 + } + launch_dur: 108000000000 + trace_slice_sections { + start_timestamp: 185000000000 + end_timestamp: 187000000000 + slice_id: 4 + slice_name: "bindApplication" + } } - slow_start_reason_detailed { + slow_start_reason_with_details { + reason_id: TIME_SPENT_IN_VIEW_INFLATION reason: "Time spent in view inflation" + severity: WARNING + expected_value { + value: 450000000 + unit: NS + higher_expected: false + } + actual_value { + value: 2000000000 + } + launch_dur: 108000000000 + trace_slice_sections { + start_timestamp: 188000000000 + end_timestamp: 189000000000 + slice_id: 6 + slice_name: "inflate" + } + trace_slice_sections { + start_timestamp: 191000000000 + end_timestamp: 192000000000 + slice_id: 8 + slice_name: "inflate" + } } - slow_start_reason_detailed { + slow_start_reason_with_details { + reason_id: TIME_SPENT_IN_RESOURCES_MANAGER_GET_RESOURCES reason: "Time spent in ResourcesManager#getResources" - details: " target 130ms actual 1000.0ms [ longest_chunk: start_s 138.0 dur_ms 1000.0 thread_id 3 thread_name com.google.android.calendar ]" + severity: WARNING + expected_value { + value: 130000000 + unit: NS + higher_expected: false + } + actual_value { + value: 1000000000 + } + launch_dur: 108000000000 + trace_slice_sections { + start_timestamp: 188000000000 + end_timestamp: 189000000000 + slice_id: 7 + slice_name: "ResourcesManager#getResources" + } } - slow_start_reason_detailed { + slow_start_reason_with_details { + reason_id: POTENTIAL_CPU_CONTENTION_WITH_ANOTHER_PROCESS reason: "Potential CPU contention with another process" - details: " target 100ms actual 5000.0ms most_active_process_for_launch init [ longest_chunk: start_s 155.0 dur_ms 5000.0 thread_id 3 thread_name com.google.android.calendar ] [ extra_info: runnable_dur_ms 5000.0 R_sum_dur_ms 5000.0 R+(Preempted)_sum_dur 0 ]" + severity: WARNING + expected_value { + value: 100000000 + unit: NS + higher_expected: false + } + actual_value { + value: 5000000000 + } + launch_dur: 108000000000 + trace_thread_sections { + start_timestamp: 205000000000 + end_timestamp: 210000000000 + thread_utid: 3 + thread_name: "com.google.android.calendar" + } } startup_type: "cold" } diff --git a/test/trace_processor/diff_tests/metrics/startup/android_startup_breakdown_slow.out b/test/trace_processor/diff_tests/metrics/startup/android_startup_breakdown_slow.out index 891060b100..d972c12550 100644 --- a/test/trace_processor/diff_tests/metrics/startup/android_startup_breakdown_slow.out +++ b/test/trace_processor/diff_tests/metrics/startup/android_startup_breakdown_slow.out @@ -1,6 +1,6 @@ android_startup { startup { - startup_id: 1 + startup_id: 0 package_name: "com.google.android.calendar" process_name: "com.google.android.calendar" zygote_new_process: true @@ -69,11 +69,6 @@ android_startup { apk_version_code: 123 debuggable: false } - packages_for_uid { - package_name: "com.google.android.calendar" - apk_version_code: 123 - debuggable: false - } pid: 3 } activities { @@ -118,22 +113,110 @@ android_startup { slow_start_reason: "Time spent in bindApplication" slow_start_reason: "Time spent in view inflation" slow_start_reason: "Time spent in ResourcesManager#getResources" - slow_start_reason_detailed { + slow_start_reason_with_details { + reason_id: RUN_FROM_APK reason: "Optimized artifacts missing, run from apk" + severity: ERROR + expected_value { + value: 0 + unit: TRUE_OR_FALSE + } + actual_value { + value: 1 + } + launch_dur: 108000000000 + trace_slice_sections { + start_timestamp: 200000000000 + end_timestamp: 202000000000 + slice_id: 12 + slice_name: "location=error status=io-error-no-oat filter=run-from-apk reason=unknown" + } } - slow_start_reason_detailed { + slow_start_reason_with_details { + reason_id: TIME_SPENT_IN_BIND_APPLICATION reason: "Time spent in bindApplication" + severity: WARNING + expected_value { + value: 1250000000 + unit: NS + higher_expected: false + } + actual_value { + value: 10000000000 + } + launch_dur: 108000000000 + trace_slice_sections { + start_timestamp: 185000000000 + end_timestamp: 195000000000 + slice_id: 4 + slice_name: "bindApplication" + } } - slow_start_reason_detailed { + slow_start_reason_with_details { + reason_id: TIME_SPENT_IN_VIEW_INFLATION reason: "Time spent in view inflation" + severity: WARNING + expected_value { + value: 450000000 + unit: NS + higher_expected: false + } + actual_value { + value: 3000000000 + } + launch_dur: 108000000000 + trace_slice_sections { + start_timestamp: 190000000000 + end_timestamp: 192000000000 + slice_id: 8 + slice_name: "inflate" + } + trace_slice_sections { + start_timestamp: 188000000000 + end_timestamp: 189000000000 + slice_id: 7 + slice_name: "inflate" + } } - slow_start_reason_detailed { + slow_start_reason_with_details { + reason_id: TIME_SPENT_IN_RESOURCES_MANAGER_GET_RESOURCES reason: "Time spent in ResourcesManager#getResources" - details: " target 130ms actual 5000.0ms [ longest_chunk: start_s 137.0 dur_ms 5000.0 thread_id 3 thread_name com.google.android.calendar ]" + severity: WARNING + expected_value { + value: 130000000 + unit: NS + higher_expected: false + } + actual_value { + value: 5000000000 + } + launch_dur: 108000000000 + trace_slice_sections { + start_timestamp: 187000000000 + end_timestamp: 192000000000 + slice_id: 5 + slice_name: "ResourcesManager#getResources" + } } - slow_start_reason_detailed { + slow_start_reason_with_details { + reason_id: POTENTIAL_CPU_CONTENTION_WITH_ANOTHER_PROCESS reason: "Potential CPU contention with another process" - details: " target 100ms actual 5000.0ms most_active_process_for_launch init [ longest_chunk: start_s 155.0 dur_ms 5000.0 thread_id 3 thread_name com.google.android.calendar ] [ extra_info: runnable_dur_ms 5000.0 R_sum_dur_ms 5000.0 R+(Preempted)_sum_dur 0 ]" + severity: WARNING + expected_value { + value: 100000000 + unit: NS + higher_expected: false + } + actual_value { + value: 5000000000 + } + launch_dur: 108000000000 + trace_thread_sections { + start_timestamp: 205000000000 + end_timestamp: 210000000000 + thread_utid: 3 + thread_name: "com.google.android.calendar" + } } startup_type: "cold" } diff --git a/test/trace_processor/diff_tests/metrics/startup/android_startup_broadcast_multiple.out b/test/trace_processor/diff_tests/metrics/startup/android_startup_broadcast_multiple.out index 02e142c62a..2fe9ca884e 100644 --- a/test/trace_processor/diff_tests/metrics/startup/android_startup_broadcast_multiple.out +++ b/test/trace_processor/diff_tests/metrics/startup/android_startup_broadcast_multiple.out @@ -31,11 +31,69 @@ android_startup { } slow_start_reason: "Broadcast dispatched count" slow_start_reason: "Broadcast received count" - slow_start_reason_detailed { + slow_start_reason_with_details { + reason_id: BROADCAST_DISPATCHED_COUNT reason: "Broadcast dispatched count" + severity: WARNING + expected_value { + value: 15 + unit: COUNT + higher_expected: false + } + actual_value { + value: 24 + } + launch_dur: 100 + trace_slice_sections { + start_timestamp: 105 + end_timestamp: 106 + slice_id: 6 + slice_name: "Broadcast dispatched from android (2005:system/1000) x" + } + trace_slice_sections { + start_timestamp: 106 + end_timestamp: 107 + slice_id: 8 + slice_name: "Broadcast dispatched from android (2005:system/1000) x" + } + trace_slice_sections { + start_timestamp: 107 + end_timestamp: 108 + slice_id: 10 + slice_name: "Broadcast dispatched from android (2005:system/1000) x" + } } - slow_start_reason_detailed { + slow_start_reason_with_details { + reason_id: BROADCAST_RECEIVED_COUNT reason: "Broadcast received count" + severity: WARNING + expected_value { + value: 50 + unit: COUNT + higher_expected: false + } + actual_value { + value: 52 + } + launch_dur: 100 + trace_slice_sections { + start_timestamp: 100 + end_timestamp: 101 + slice_id: 1 + slice_name: "broadcastReceiveReg: x" + } + trace_slice_sections { + start_timestamp: 101 + end_timestamp: 102 + slice_id: 2 + slice_name: "broadcastReceiveReg: x" + } + trace_slice_sections { + start_timestamp: 102 + end_timestamp: 103 + slice_id: 3 + slice_name: "broadcastReceiveReg: x" + } } } } diff --git a/test/trace_processor/diff_tests/metrics/startup/android_startup_installd_dex2oat.out b/test/trace_processor/diff_tests/metrics/startup/android_startup_installd_dex2oat.out index 1fd7f2ed04..7c886eb068 100644 --- a/test/trace_processor/diff_tests/metrics/startup/android_startup_installd_dex2oat.out +++ b/test/trace_processor/diff_tests/metrics/startup/android_startup_installd_dex2oat.out @@ -62,8 +62,19 @@ android_startup { dex2oat_dur_ns: 5 } slow_start_reason: "dex2oat running during launch" - slow_start_reason_detailed { + slow_start_reason_with_details { + reason_id: DEX2OAT_RUNNING reason: "dex2oat running during launch" + severity: WARNING + expected_value { + value: 0 + unit: TRUE_OR_FALSE + } + actual_value { + value: 1 + } + launch_dur: 100 + additional_info: "Process: dex2oat64" } } startup { @@ -98,8 +109,19 @@ android_startup { dex2oat_dur_ns: 0 } slow_start_reason: "installd running during launch" - slow_start_reason_detailed { + slow_start_reason_with_details { + reason_id: INSTALLD_RUNNING reason: "installd running during launch" + severity: ERROR + expected_value { + value: 0 + unit: TRUE_OR_FALSE + } + actual_value { + value: 1 + } + launch_dur: 100 + additional_info: "Process: installd" } } startup { @@ -136,11 +158,33 @@ android_startup { } slow_start_reason: "dex2oat running during launch" slow_start_reason: "installd running during launch" - slow_start_reason_detailed { + slow_start_reason_with_details { + reason_id: DEX2OAT_RUNNING reason: "dex2oat running during launch" + severity: WARNING + expected_value { + value: 0 + unit: TRUE_OR_FALSE + } + actual_value { + value: 1 + } + launch_dur: 100 + additional_info: "Process: dex2oat64" } - slow_start_reason_detailed { + slow_start_reason_with_details { + reason_id: INSTALLD_RUNNING reason: "installd running during launch" + severity: ERROR + expected_value { + value: 0 + unit: TRUE_OR_FALSE + } + actual_value { + value: 1 + } + launch_dur: 100 + additional_info: "Process: installd" } } } diff --git a/test/trace_processor/diff_tests/metrics/startup/android_startup_installd_dex2oat_slow.out b/test/trace_processor/diff_tests/metrics/startup/android_startup_installd_dex2oat_slow.out index 91e0d5aefc..9be93fc2cc 100644 --- a/test/trace_processor/diff_tests/metrics/startup/android_startup_installd_dex2oat_slow.out +++ b/test/trace_processor/diff_tests/metrics/startup/android_startup_installd_dex2oat_slow.out @@ -62,8 +62,19 @@ android_startup { dex2oat_dur_ns: 5000000000 } slow_start_reason: "dex2oat running during launch" - slow_start_reason_detailed { + slow_start_reason_with_details { + reason_id: DEX2OAT_RUNNING reason: "dex2oat running during launch" + severity: WARNING + expected_value { + value: 0 + unit: TRUE_OR_FALSE + } + actual_value { + value: 1 + } + launch_dur: 100000000000 + additional_info: "Process: dex2oat64" } } startup { @@ -101,14 +112,47 @@ android_startup { slow_start_reason: "dex2oat running during launch" slow_start_reason: "installd running during launch" slow_start_reason: "Startup running concurrent to launch" - slow_start_reason_detailed { + slow_start_reason_with_details { + reason_id: DEX2OAT_RUNNING reason: "dex2oat running during launch" + severity: WARNING + expected_value { + value: 0 + unit: TRUE_OR_FALSE + } + actual_value { + value: 1 + } + launch_dur: 250000000000 + additional_info: "Process: dex2oat64" } - slow_start_reason_detailed { + slow_start_reason_with_details { + reason_id: INSTALLD_RUNNING reason: "installd running during launch" + severity: ERROR + expected_value { + value: 0 + unit: TRUE_OR_FALSE + } + actual_value { + value: 1 + } + launch_dur: 250000000000 + additional_info: "Process: installd" } - slow_start_reason_detailed { + slow_start_reason_with_details { + reason_id: STARTUP_RUNNING_CONCURRENT reason: "Startup running concurrent to launch" + severity: ERROR + expected_value { + value: 0 + unit: TRUE_OR_FALSE + } + actual_value { + value: 1 + } + launch_dur: 250000000000 + additional_info: "Package: com.google.android.gm" } startup_concurrent_to_launch: "com.google.android.gm" } @@ -145,14 +189,47 @@ android_startup { slow_start_reason: "dex2oat running during launch" slow_start_reason: "installd running during launch" slow_start_reason: "Startup running concurrent to launch" - slow_start_reason_detailed { + slow_start_reason_with_details { + reason_id: DEX2OAT_RUNNING reason: "dex2oat running during launch" + severity: WARNING + expected_value { + value: 0 + unit: TRUE_OR_FALSE + } + actual_value { + value: 1 + } + launch_dur: 100000000000 + additional_info: "Process: dex2oat64" } - slow_start_reason_detailed { + slow_start_reason_with_details { + reason_id: INSTALLD_RUNNING reason: "installd running during launch" + severity: ERROR + expected_value { + value: 0 + unit: TRUE_OR_FALSE + } + actual_value { + value: 1 + } + launch_dur: 100000000000 + additional_info: "Process: installd" } - slow_start_reason_detailed { + slow_start_reason_with_details { + reason_id: STARTUP_RUNNING_CONCURRENT reason: "Startup running concurrent to launch" + severity: ERROR + expected_value { + value: 0 + unit: TRUE_OR_FALSE + } + actual_value { + value: 1 + } + launch_dur: 100000000000 + additional_info: "Package: com.google.android.deskclock" } startup_concurrent_to_launch: "com.google.android.deskclock" } diff --git a/test/trace_processor/diff_tests/metrics/startup/android_startup_lock_contention.out b/test/trace_processor/diff_tests/metrics/startup/android_startup_lock_contention.out index ba1311c7ff..7c638b5058 100644 --- a/test/trace_processor/diff_tests/metrics/startup/android_startup_lock_contention.out +++ b/test/trace_processor/diff_tests/metrics/startup/android_startup_lock_contention.out @@ -50,11 +50,6 @@ android_startup { apk_version_code: 123 debuggable: false } - packages_for_uid { - package_name: "com.google.android.calendar" - apk_version_code: 123 - debuggable: false - } pid: 3 } event_timestamps { diff --git a/test/trace_processor/diff_tests/metrics/startup/android_startup_lock_contention_slow.out b/test/trace_processor/diff_tests/metrics/startup/android_startup_lock_contention_slow.out index 3e5f6a6596..61782f889e 100644 --- a/test/trace_processor/diff_tests/metrics/startup/android_startup_lock_contention_slow.out +++ b/test/trace_processor/diff_tests/metrics/startup/android_startup_lock_contention_slow.out @@ -50,11 +50,6 @@ android_startup { apk_version_code: 123 debuggable: false } - packages_for_uid { - package_name: "com.google.android.calendar" - apk_version_code: 123 - debuggable: false - } pid: 3 } event_timestamps { @@ -72,14 +67,55 @@ android_startup { slow_start_reason: "Time spent in bindApplication" slow_start_reason: "Main Thread - Lock contention" slow_start_reason: "Main Thread - Monitor contention" - slow_start_reason_detailed { + slow_start_reason_with_details { + reason_id: TIME_SPENT_IN_BIND_APPLICATION reason: "Time spent in bindApplication" + severity: WARNING + expected_value { + value: 1250000000 + unit: NS + higher_expected: false + } + actual_value { + value: 3000000000 + } + launch_dur: 100000000000 + trace_slice_sections { + start_timestamp: 112000000000 + end_timestamp: 115000000000 + slice_id: 1 + slice_name: "bindApplication" + } } - slow_start_reason_detailed { + slow_start_reason_with_details { + reason_id: MAIN_THREAD_LOCK_CONTENTION reason: "Main Thread - Lock contention" + severity: WARNING + expected_value { + value: 20 + unit: PERCENTAGE + higher_expected: false + } + actual_value { + value: 27 + dur: 27000000000 + } + launch_dur: 100000000000 } - slow_start_reason_detailed { - reason: "Main Thread - Monitor contention" + slow_start_reason_with_details { + reason_id: MAIN_THREAD_MONITOR_CONTENTION + reason: "Main Thread - Monitor contention" + severity: WARNING + expected_value { + value: 15 + unit: PERCENTAGE + higher_expected: false + } + actual_value { + value: 17 + dur: 17000000000 + } + launch_dur: 100000000000 } startup_type: "cold" } diff --git a/test/trace_processor/diff_tests/metrics/startup/android_startup_minsdk33.out b/test/trace_processor/diff_tests/metrics/startup/android_startup_minsdk33.out index e56ae6f00b..fdd584f1f9 100644 --- a/test/trace_processor/diff_tests/metrics/startup/android_startup_minsdk33.out +++ b/test/trace_processor/diff_tests/metrics/startup/android_startup_minsdk33.out @@ -30,8 +30,18 @@ android_startup { dex2oat_dur_ns: 0 } slow_start_reason: "App in debuggable mode" - slow_start_reason_detailed { + slow_start_reason_with_details { + severity: ERROR + reason_id: APP_IN_DEBUGGABLE_MODE reason: "App in debuggable mode" + expected_value { + value: 0 + unit: TRUE_OR_FALSE + } + actual_value { + value: 1 + } + launch_dur: 100 } } startup { @@ -65,11 +75,6 @@ android_startup { apk_version_code: 123 debuggable: true } - packages_for_uid { - package_name: "com.google.android.calendar" - apk_version_code: 123 - debuggable: true - } pid: 3 } event_timestamps { @@ -86,8 +91,18 @@ android_startup { } startup_type: "hot" slow_start_reason: "App in debuggable mode" - slow_start_reason_detailed { + slow_start_reason_with_details { + reason_id: APP_IN_DEBUGGABLE_MODE reason: "App in debuggable mode" + severity: ERROR + expected_value { + value: 0 + unit: TRUE_OR_FALSE + } + actual_value { + value: 1 + } + launch_dur: 10 } } } diff --git a/test/trace_processor/diff_tests/metrics/startup/android_startup_process_track.out b/test/trace_processor/diff_tests/metrics/startup/android_startup_process_track.out index fb10a32828..be657e6dee 100644 --- a/test/trace_processor/diff_tests/metrics/startup/android_startup_process_track.out +++ b/test/trace_processor/diff_tests/metrics/startup/android_startup_process_track.out @@ -1,6 +1,6 @@ android_startup { startup { - startup_id: 1 + startup_id: 0 package_name: "com.google.android.calendar" process_name: "com.google.android.calendar:debug" zygote_new_process: false @@ -46,11 +46,6 @@ android_startup { apk_version_code: 123 debuggable: false } - packages_for_uid { - package_name: "com.google.android.calendar" - apk_version_code: 123 - debuggable: false - } pid: 3 } event_timestamps { @@ -66,13 +61,30 @@ android_startup { dex2oat_dur_ns: 0 } startup_type: "cold" - slow_start_reason_detailed { + slow_start_reason_with_details { + reason_id: MAIN_THREAD_TIME_SPENT_IN_RUNNABLE reason: "Main Thread - Time spent in Runnable state" - details: " target 15% actual 57.14% [ longest_chunk: start_s 3.0e-09 dur_ms 4.0e-0 thread_id 3 thread_name com.google.android.calendar ] [ extra_info: launches_dur_ms 7.0e-0 runnable_dur_ms 4.0e-0 R_sum_dur_ms 4.0e-0 R+(Preempted)_sum_dur_ms 0.0 ]" + severity: WARNING + expected_value { + value: 15 + unit: PERCENTAGE + higher_expected: false + } + actual_value { + value: 57 + dur: 4 + } + launch_dur: 7 + trace_thread_sections { + start_timestamp: 103 + end_timestamp: 107 + thread_utid: 3 + thread_name: "com.google.android.calendar" + } } } startup { - startup_id: 2 + startup_id: 1 package_name: "com.google.android.calendar" process_name: "com.google.android.calendar" zygote_new_process: false @@ -119,11 +131,6 @@ android_startup { apk_version_code: 123 debuggable: false } - packages_for_uid { - package_name: "com.google.android.calendar" - apk_version_code: 123 - debuggable: false - } pid: 4 } event_timestamps { @@ -139,9 +146,26 @@ android_startup { dex2oat_dur_ns: 0 } startup_type: "cold" - slow_start_reason_detailed { + slow_start_reason_with_details { + reason_id: MAIN_THREAD_TIME_SPENT_IN_RUNNABLE reason: "Main Thread - Time spent in Runnable state" - details: " target 15% actual 57.14% [ longest_chunk: start_s 1.03e-07 dur_ms 4.0e-0 thread_id 4 thread_name com.google.android.calendar ] [ extra_info: launches_dur_ms 7.0e-0 runnable_dur_ms 4.0e-0 R_sum_dur_ms 4.0e-0 R+(Preempted)_sum_dur_ms 0.0 ]" + severity: WARNING + expected_value { + value: 15 + unit: PERCENTAGE + higher_expected: false + } + actual_value { + value: 57 + dur: 4 + } + launch_dur: 7 + trace_thread_sections { + start_timestamp: 203 + end_timestamp: 207 + thread_utid: 4 + thread_name: "com.google.android.calendar" + } } } } diff --git a/test/trace_processor/diff_tests/metrics/startup/android_startup_slow.out b/test/trace_processor/diff_tests/metrics/startup/android_startup_slow.out index 4cdc5e9cea..2bd4bd865e 100644 --- a/test/trace_processor/diff_tests/metrics/startup/android_startup_slow.out +++ b/test/trace_processor/diff_tests/metrics/startup/android_startup_slow.out @@ -1,6 +1,6 @@ android_startup { startup { - startup_id: 2 + startup_id: 0 package_name: "com.google.android.calendar" process_name: "com.google.android.calendar" zygote_new_process: false @@ -42,11 +42,6 @@ android_startup { apk_version_code: 123 debuggable: false } - packages_for_uid { - package_name: "com.google.android.calendar" - apk_version_code: 123 - debuggable: false - } pid: 3 } report_fully_drawn { @@ -70,19 +65,86 @@ android_startup { startup_type: "warm" slow_start_reason: "Main Thread - Time spent in interruptible sleep state" slow_start_reason: "Main Thread - Time spent in Blocking I/O" - slow_start_reason_detailed { + slow_start_reason_with_details { + reason_id: MAIN_THREAD_TIME_SPENT_IN_RUNNABLE reason: "Main Thread - Time spent in Runnable state" - details: " target 15% actual 74.07% [ longest_chunk: start_s 30.0 dur_ms 80000. thread_id 3 thread_name com.google.android.calendar ] [ extra_info: launches_dur_ms 108000 runnable_dur_ms 80000. R_sum_dur_ms 80000. R+(Preempted)_sum_dur_ms 0.0 ]" + severity: WARNING + expected_value { + value: 15 + unit: PERCENTAGE + higher_expected: false + } + actual_value { + value: 74 + dur: 80000000000 + } + launch_dur: 108000000000 + trace_thread_sections { + start_timestamp: 130000000000 + end_timestamp: 210000000000 + thread_utid: 3 + thread_name: "com.google.android.calendar" + } } - slow_start_reason_detailed { + slow_start_reason_with_details { + reason_id: MAIN_THREAD_TIME_SPENT_IN_INTERRUPTIBLE_SLEEP reason: "Main Thread - Time spent in interruptible sleep state" + severity: WARNING + expected_value { + value: 2900000000 + unit: NS + higher_expected: false + } + actual_value { + value: 5000000000 + } + launch_dur: 108000000000 + trace_thread_sections { + start_timestamp: 120000000000 + end_timestamp: 125000000000 + thread_utid: 3 + thread_name: "com.google.android.calendar" + } } - slow_start_reason_detailed { + slow_start_reason_with_details { + reason_id: MAIN_THREAD_TIME_SPENT_IN_BLOCKING_IO reason: "Main Thread - Time spent in Blocking I/O" + severity: WARNING + expected_value { + value: 450000000 + unit: NS + higher_expected: false + } + actual_value { + value: 5000000000 + } + launch_dur: 108000000000 + trace_thread_sections { + start_timestamp: 125000000000 + end_timestamp: 130000000000 + thread_utid: 3 + thread_name: "com.google.android.calendar" + } } - slow_start_reason_detailed { + slow_start_reason_with_details { + reason_id: POTENTIAL_CPU_CONTENTION_WITH_ANOTHER_PROCESS reason: "Potential CPU contention with another process" - details: " target 100ms actual 80000.ms most_active_process_for_launch init [ longest_chunk: start_s 30.0 dur_ms 80000. thread_id 3 thread_name com.google.android.calendar ] [ extra_info: runnable_dur_ms 80000. R_sum_dur_ms 80000. R+(Preempted)_sum_dur 0 ]" + severity: WARNING + expected_value { + value: 100000000 + unit: NS + higher_expected: false + } + actual_value { + value: 80000000000 + } + launch_dur: 108000000000 + trace_thread_sections { + start_timestamp: 130000000000 + end_timestamp: 210000000000 + thread_utid: 3 + thread_name: "com.google.android.calendar" + } } } } diff --git a/test/trace_processor/diff_tests/metrics/startup/android_startup_unlock.out b/test/trace_processor/diff_tests/metrics/startup/android_startup_unlock.out index 9686456ca2..03407756da 100644 --- a/test/trace_processor/diff_tests/metrics/startup/android_startup_unlock.out +++ b/test/trace_processor/diff_tests/metrics/startup/android_startup_unlock.out @@ -30,8 +30,24 @@ android_startup { dex2oat_dur_ns: 0 } slow_start_reason: "Unlock running during launch" - slow_start_reason_detailed { + slow_start_reason_with_details { + reason_id: UNLOCK_RUNNING reason: "Unlock running during launch" + severity: ERROR + expected_value { + value: 0 + unit: TRUE_OR_FALSE + } + actual_value { + value: 1 + } + launch_dur: 100 + trace_slice_sections { + start_timestamp: 130 + end_timestamp: 133 + slice_id: 1 + slice_name: "KeyguardUpdateMonitor#onAuthenticationSucceeded" + } } } } diff --git a/test/trace_processor/diff_tests/metrics/startup/ttid_and_ttfd.out b/test/trace_processor/diff_tests/metrics/startup/ttid_and_ttfd.out index f5dcfcf925..8999dd6f8b 100644 --- a/test/trace_processor/diff_tests/metrics/startup/ttid_and_ttfd.out +++ b/test/trace_processor/diff_tests/metrics/startup/ttid_and_ttfd.out @@ -1,6 +1,6 @@ android_startup { startup { - startup_id: 1 + startup_id: 0 package_name: "androidx.benchmark.integration.macrobenchmark.target" process_name: "androidx.benchmark.integration.macrobenchmark.target" zygote_new_process: false diff --git a/test/trace_processor/diff_tests/parser/android/android_bugreport_logs_test.out b/test/trace_processor/diff_tests/parser/android/android_bugreport_logs_test.out index 95af605ae7..a09b6bbd54 100644 --- a/test/trace_processor/diff_tests/parser/android/android_bugreport_logs_test.out +++ b/test/trace_processor/diff_tests/parser/android/android_bugreport_logs_test.out @@ -1,201 +1,201 @@ "cnt","ts","prio","tag","msg" -45028,1609462800670000000,5,"auditd","type=2000 audit(0.0:1): initialized" -45028,1609462800670000000,4,"auditd","type=2000 audit(0.0:1): initialized" -45028,1609463611529000000,4,"","c0 0 Booting Linux on physical CPU 0x0" -45028,1609463611529000000,4,"","c0 0 Linux version 4.9.270-g862f51bac900-ab7613625 (android-build@abfarm-east4-101) (Android (7284624, based on r416183b) clang version 12.0.5 (https://android.googlesource.com/toolchain/llvm-project c935d99d7cf2016289302412d708641d52d2f7ee)) #0 SMP PREEMPT Thu Aug 5 07:04:42 UTC 2021" -45028,1609463611529000000,4,"","c0 0 Boot CPU: AArch64 Processor [517f803c]" -45028,1609463611529000000,4,"","c0 0 Machine: Google Inc. MSM sdm845 C1 EVT v1.0" -45028,1609463611529000000,4,"","c0 0 Reserved memory: created CMA memory pool at 0x00000000fec00000, size 16 MiB" -45028,1609463611529000000,4,"","c0 0 OF: reserved mem: initialized node adsp_region, compatible id shared-dma-pool" -45028,1609463611529000000,4,"","c0 0 Reserved memory: created CMA memory pool at 0x00000000fdc00000, size 16 MiB" -45028,1609463611529000000,4,"","c0 0 OF: reserved mem: initialized node qseecom_ta_region, compatible id shared-dma-pool" -45028,1609463611529000000,4,"","c0 0 Reserved memory: created CMA memory pool at 0x00000000f4c00000, size 144 MiB" -45028,1609463611529000000,4,"","c0 0 OF: reserved mem: initialized node secure_display_region, compatible id shared-dma-pool" -45028,1609463611529000000,4,"","c0 0 Reserved memory: created CMA memory pool at 0x000000017b800000, size 36 MiB" -45028,1609463611529000000,4,"","c0 0 OF: reserved mem: initialized node mem_dump_region, compatible id shared-dma-pool" -45028,1609463611529000000,4,"","c0 0 Reserved memory: created CMA memory pool at 0x00000000f4400000, size 8 MiB" -45028,1609463611529000000,4,"","c0 0 OF: reserved mem: initialized node secure_sp_region, compatible id shared-dma-pool" -45028,1609463611529000000,4,"","c0 0 Reserved memory: created CMA memory pool at 0x00000000f2400000, size 32 MiB" -45028,1609463611529000000,4,"","c0 0 OF: reserved mem: initialized node linux,cma, compatible id shared-dma-pool" -45028,1609463611529000000,4,"","c0 0 Reserved memory: created DMA memory pool at 0x000000008ab00000, size 20 MiB" -45028,1609463611529000000,4,"","c0 0 OF: reserved mem: initialized node qseecom_region@0x8ab00000, compatible id shared-dma-pool" -45028,1609463611529000000,4,"","c0 0 Removed memory: created DMA memory pool at 0x000000008bf00000, size 5 MiB" -45028,1609463611529000000,4,"","c0 0 OF: reserved mem: initialized node camera_region@0x8bf00000, compatible id removed-dma-pool" -45028,1609463611529000000,4,"","c0 0 Removed memory: created DMA memory pool at 0x000000008c400000, size 0 MiB" -45028,1609463611529000000,4,"","c0 0 OF: reserved mem: initialized node ips_fw_region@0x8c400000, compatible id removed-dma-pool" -45028,1609463611529000000,4,"","c0 0 Removed memory: created DMA memory pool at 0x000000008c410000, size 0 MiB" -45028,1609463611529000000,4,"","c0 0 OF: reserved mem: initialized node ipa_gsi_region@0x8c410000, compatible id removed-dma-pool" -45028,1609463611529000000,4,"","c0 0 Removed memory: created DMA memory pool at 0x000000008c415000, size 0 MiB" -45028,1609463611529000000,4,"","c0 0 OF: reserved mem: initialized node gpu_region@0x8c415000, compatible id removed-dma-pool" -45028,1609463611529000000,4,"","c0 0 Removed memory: created DMA memory pool at 0x000000008c500000, size 26 MiB" -45028,1609463611529000000,4,"","c0 0 OF: reserved mem: initialized node adsp_region@0x8c500000, compatible id removed-dma-pool" -45028,1609463611529000000,4,"","c0 0 Removed memory: created DMA memory pool at 0x000000008df00000, size 1 MiB" -45028,1609463611529000000,4,"","c0 0 OF: reserved mem: initialized node wlan_fw_region@0x8df00000, compatible id removed-dma-pool" -45028,1609463611529000000,4,"","c0 0 Removed memory: created DMA memory pool at 0x000000008e000000, size 152 MiB" -45028,1609463611529000000,4,"","c0 0 OF: reserved mem: initialized node modem_region@0x8e000000, compatible id removed-dma-pool" -45028,1609463611529000000,4,"","c0 0 Removed memory: created DMA memory pool at 0x0000000097800000, size 5 MiB" -45028,1609463611529000000,4,"","c0 0 OF: reserved mem: initialized node video_region@0x95800000, compatible id removed-dma-pool" -45028,1609463611529000000,4,"","c0 0 Removed memory: created DMA memory pool at 0x0000000097d00000, size 8 MiB" -45028,1609463611529000000,4,"","c0 0 OF: reserved mem: initialized node cdsp_region@0x95d00000, compatible id removed-dma-pool" -45028,1609463611529000000,4,"","c0 0 Removed memory: created DMA memory pool at 0x0000000098500000, size 2 MiB" -45028,1609463611529000000,4,"","c0 0 OF: reserved mem: initialized node mba_region@0x96500000, compatible id removed-dma-pool" -45028,1609463611529000000,4,"","c0 0 Removed memory: created DMA memory pool at 0x0000000098700000, size 20 MiB" -45028,1609463611529000000,4,"","c0 0 OF: reserved mem: initialized node slpi_region@0x96700000, compatible id removed-dma-pool" -45028,1609463611529000000,4,"","c0 0 Removed memory: created DMA memory pool at 0x0000000099b00000, size 1 MiB" -45028,1609463611529000000,4,"","c0 0 OF: reserved mem: initialized node pil_spss_region@0x97b00000, compatible id removed-dma-pool" -45028,1609463611529000000,4,"","c0 0 Removed memory: created DMA memory pool at 0x00000000a1800000, size 0 MiB" -45028,1609463611529000000,4,"","c0 0 OF: reserved mem: initialized node debug_info@0, compatible id removed-dma-pool" -45028,1609463611529000000,4,"","c0 0 Removed memory: created DMA memory pool at 0x00000000a1810000, size 2 MiB" -45028,1609463611529000000,4,"","c0 0 OF: reserved mem: initialized node ramoops_region@a1810000, compatible id removed-dma-pool" -45028,1609463611529000000,4,"","c0 0 Removed memory: created DMA memory pool at 0x00000000a1a10000, size 2 MiB" -45028,1609463611529000000,4,"","c0 0 OF: reserved mem: initialized node alt_ramoops_region@a1a10000, compatible id removed-dma-pool" -45028,1609463611529000000,4,"","c0 0 Removed memory: created DMA memory pool at 0x00000000a1c10000, size 0 MiB" -45028,1609463611529000000,4,"","c0 0 OF: reserved mem: initialized node ramoops_meta_region@a1c10000, compatible id removed-dma-pool" -45028,1609463611529000000,3,"","c0 0 On node 0 totalpages: 963896" -45028,1609463611529000000,3,"","c0 0 DMA zone: 6999 pages used for memmap" -45028,1609463611529000000,3,"","c0 0 DMA zone: 0 pages reserved" -45028,1609463611529000000,3,"","c0 0 DMA zone: 447896 pages, LIFO batch:31" -45028,1609463611529000000,3,"","c0 0 Normal zone: 8063 pages used for memmap" -45028,1609463611529000000,3,"","c0 0 Normal zone: 516000 pages, LIFO batch:31" -45028,1609463611529000000,4,"","c0 0 psci: probing for conduit method from DT." -45028,1609463611529000000,4,"","c0 0 psci: PSCIv1.1 detected in firmware." -45028,1609463611529000000,4,"","c0 0 psci: Using standard PSCI v0.2 function IDs" -45028,1609463611529000000,4,"","c0 0 psci: MIGRATE_INFO_TYPE not supported." -45028,1609463611529000000,4,"","c0 0 psci: SMC Calling Convention v1.0" -45028,1609463611529000000,4,"","c0 0 random: fast init done" -45028,1609463611529000000,4,"","c0 0 percpu: Embedded 23 pages/cpu s54296 r8192 d31720 u94208" -45028,1609463611529000000,3,"","c0 0 pcpu-alloc: s54296 r8192 d31720 u94208 alloc=23*4096" -45028,1609463611529000000,3,"","c0 0 pcpu-alloc: [0] 0 [0] 1 [0] 2 [0] 3 [0] 4 [0] 5 [0] 6 [0] 7" -45028,1609463611529000000,4,"","c0 0 Built 1 zonelists in Zone order, mobility grouping on. Total pages: 948834" -45028,1609463611529000000,4,"","c0 0 Kernel command line: rcupdate.rcu_expedited=1 rootwait ro init=/init androidboot.bootdevice=1d84000.ufshc androidboot.baseband=sdm androidboot.keymaster=1 msm_drm.dsi_display0=dsi_s6e3ha8_cmd_display::timing0 androidboot.force_normal_boot=1 androidboot.serialno=84TY005M7 androidboot.slot_suffix=_a androidboot.slot_retry_count=2 androidboot.slot_successful=yes androidboot.hardware.platform=sdm845 androidboot.hardware=crosshatch androidboot.revision=EVT1.0 androidboot.bootloader=b1c1-0.4-7617406 androidboot.hardware.sku=G013C androidboot.hardware.radio.subtype=0 androidboot.hardware.dsds=0 androidboot.secure_boot=NONE androidboot.cdt_hwid=0x05010A00 androidboot.hardware.majorid=0x01 androidboot.dtb_idx=0 androidboot.dtbo_idx=10 androidboot.mode=normal androidboot.bootreason=reboot androidboot.hardware.ddr=4GB,Samsung,LPDDR4X androidboot.ddr_info=Samsung androidboot.ddr_size=4GB androidboot.hardware.ufs=64GB,Samsung androidboot.cid=00000000 androidboot.boottime=0BLE:59,1BLL:22," -45028,1609463611529000000,4,"","c0 0 PID hash table entries: 4096 (order: 3, 32768 bytes)" -45028,1609463611529000000,4,"","c0 0 Dentry cache hash table entries: 524288 (order: 10, 4194304 bytes)" -45028,1609463611529000000,4,"","c0 0 Inode-cache hash table entries: 262144 (order: 9, 2097152 bytes)" -45028,1609463611529000000,4,"","c0 0 software IO TLB: mapped [mem 0xee400000-0xf2400000] (64MB)" -45028,1609463611529000000,4,"","c0 0 Memory: 3347020K/3855584K available (26108K kernel code, 3324K rwdata, 8608K rodata, 8192K init, 9407K bss, 250516K reserved, 258048K cma-reserved)" -45028,1609463611529000000,4,"","c0 0 Virtual kernel memory layout:" -45028,1609463611529000000,4,"","c0 0 modules : 0xffffff8000000000 - 0xffffff8008000000 ( 128 MB)" -45028,1609463611529000000,4,"","c0 0 vmalloc : 0xffffff8008000000 - 0xffffffbebfff0000 ( 250 GB)" -45028,1609463611529000000,4,"","c0 0 .text : 0x (ptrval) - 0x (ptrval) ( 26112 KB)" -45028,1609463611529000000,4,"","c0 0 .rodata : 0x (ptrval) - 0x (ptrval) ( 10240 KB)" -45028,1609463611529000000,4,"","c0 0 .init : 0x (ptrval) - 0x (ptrval) ( 8192 KB)" -45028,1609463611529000000,4,"","c0 0 .data : 0x (ptrval) - 0x (ptrval) ( 3324 KB)" -45028,1609463611529000000,4,"","c0 0 .bss : 0x (ptrval) - 0x (ptrval) ( 9408 KB)" -45028,1609463611529000000,4,"","c0 0 fixed : 0xffffffbefe7fb000 - 0xffffffbefec00000 ( 4116 KB)" -45028,1609463611529000000,4,"","c0 0 PCI I/O : 0xffffffbefee00000 - 0xffffffbeffe00000 ( 16 MB)" -45028,1609463611529000000,4,"","c0 0 vmemmap : 0xffffffbf00000000 - 0xffffffc000000000 ( 4 GB maximum)" -45028,1609463611529000000,4,"","c0 0 0xffffffbf7b000000 - 0xffffffbf7ef7e800 ( 63 MB actual)" -45028,1609463611529000000,4,"","c0 0 memory : 0xffffffdec0000000 - 0xffffffdfbdfa0000 ( 4063 MB)" -45028,1609463611529000000,4,"","c0 0 SLUB: HWalign=64, Order=0-3, MinObjects=0, CPUs=8, Nodes=1" -45028,1609463611529000000,4,"","c0 0 Preemptible hierarchical RCU implementation." -45028,1609463611529000000,4,"","c0 0 RCU dyntick-idle grace-period acceleration is enabled." -45028,1609463611529000000,4,"","c0 0 NR_IRQS:64 nr_irqs:64 0" -45028,1609463611529000000,4,"","c0 0 PDC SDM845 v2 initialized" -45028,1609463611529000000,4,"","c0 0 Offload RCU callbacks from all CPUs" -45028,1609463611529000000,4,"","c0 0 Offload RCU callbacks from CPUs: 0-7." -45028,1609463611529000000,4,"","c0 0 arm_arch_timer: Architected cp15 and mmio timer(s) running at 19.20MHz (virt/virt)." -45028,1609463611529000000,4,"","c0 0 clocksource: arch_sys_counter: mask: 0xffffffffffffff max_cycles: 0x46d987e47, max_idle_ns: 440795202767 ns" -45028,1609463611529000000,4,"","c0 0 sched_clock: 56 bits at 19MHz, resolution 52ns, wraps every 4398046511078ns" -45028,1609463611529000000,4,"","c0 0 clocksource: Switched to clocksource arch_sys_counter" -45028,1609463611531000000,4,"","c0 0 Console: colour dummy device 80x25" -45028,1609463611531000000,4,"","c0 0 Calibrating delay loop (skipped), value calculated using timer frequency.. 38.00 BogoMIPS (lpj=64000)" -45028,1629848355866000000,4,"","c3 698 logd: logdr: UID=2000 GID=2000 PID=12111 n tail=0 logMask=99 pid=0 start=1629844543000000000ns deadline=0ns" -45028,1629848355796000000,3,"dumpstate","Duration of 'DUMPSTATE': 199.82s" -45028,1629848355680000000,5,"IorapForwardingService","No service published for: iorapd" -45028,1629848355606000000,3,"dumpstate","Adding dir /sys/fs/cgroup (recursive: 1)" -45028,1629848355577000000,3,"dumpstate","Adding dir /linkerconfig (recursive: 1)" -45028,1629848355566000000,3,"ProcessState","Binder ioctl to enable oneway spam detection failed: Invalid argument" -45028,1629848355566000000,4,"","c1 12109 binder: 12109:12109 ioctl 40046210 7fcfdfc5c4 returned -22" -45028,1629848355540000000,3,"ProcessState","Binder ioctl to enable oneway spam detection failed: Invalid argument" -45028,1629848355540000000,4,"","c0 12107 binder: 12107:12107 ioctl 40046210 7fcff784d4 returned -22" -45028,1629848355514000000,3,"ProcessState","Binder ioctl to enable oneway spam detection failed: Invalid argument" -45028,1629848355513000000,4,"","c0 12105 binder: 12105:12105 ioctl 40046210 7fcac734d4 returned -22" -45028,1629848355475000000,3,"dumpstate","Duration of 'APP PROVIDERS NON-PLATFORM': 1.22s" -45028,1629848355023000000,6,"earchbox:searc","Resource 7f030033 is a complex map type." -45028,1629848355023000000,6,"earchbox:searc","Resource 7f030038 is a complex map type." -45028,1629848355022000000,6,"earchbox:searc","Resource 7f030013 is a complex map type." -45028,1629848355022000000,6,"earchbox:searc","Resource 7f030014 is a complex map type." -45028,1629848355022000000,6,"earchbox:searc","Resource 7f03001b is a complex map type." -45028,1629848355022000000,6,"earchbox:searc","Resource 7f03001c is a complex map type." -45028,1629848355022000000,6,"earchbox:searc","Resource 7f03001f is a complex map type." -45028,1629848355022000000,6,"earchbox:searc","Resource 7f03002c is a complex map type." -45028,1629848355022000000,6,"earchbox:searc","Resource 7f030032 is a complex map type." -45028,1629848355021000000,6,"earchbox:searc","Resource 7f030000 is a complex map type." -45028,1629848355021000000,6,"earchbox:searc","Resource 7f030012 is a complex map type." -45028,1629848354946000000,4,"PCP.MediaManager","Dumping pcp media manager events" -45028,1629848354944000000,4,"PCP.ImplV0","on dump start" -45028,1629848354932000000,4,"A","Loaded shared library: nativecrashreporter" -45028,1629848354910000000,4,"A","[null" -45028,1629848354910000000,4,"A",", GlobalData" -45028,1629848354910000000,4,"A","]" -45028,1629848354892000000,4,"A","[AccountId{id=1}" -45028,1629848354892000000,4,"A",", SharedProactiveData" -45028,1629848354892000000,4,"A",", ClientProactiveData" -45028,1629848354892000000,4,"A",", LastClientDataQuery" -45028,1629848354892000000,4,"A","]" -45028,1629848354683000000,4,"AmbBrdcstRcvrServClient","created monitors of size 4" -45028,1629848354682000000,4,"AmbBrdcstRcvrServClient","mapping trigger monitor: MEDIA_SESSION" -45028,1629848354682000000,4,"AmbBrdcstRcvrServClient","mapping trigger monitor: INFERENCE_START" -45028,1629848354682000000,4,"AmbBrdcstRcvrServClient","mapping trigger monitor: SCREEN_STATE" -45028,1629848354679000000,4,"AmbBrdcstRcvrServClient","mapping trigger monitor: HEADSET_STATE" -45028,1629848354677000000,5,"IorapForwardingService","No service published for: iorapd" -45028,1629848354557000000,4,"PeopleDatabaseHelper","cleanUpNonGplusAccounts done." -45028,1629848354413000000,5,"Icing","Record file /data/user/0/com.google.android.gms/files/AppDataSearch/main/query-record-log.tmp not found, ignoring" -45028,1629848354323000000,4,"","c3 1 init: Service 'apexd' (pid 11731) exited with status 0 oneshot service took 3.818000 seconds in background" -45028,1629848354323000000,4,"","c3 1 init: Sending signal 9 to service 'apexd' (pid 11731) process group..." -45028,1629848354323000000,4,"","c3 1 libprocessgroup: Successfully killed process cgroup uid 0 pid 11731 in 0ms" -45028,1629848354316000000,4,"BpBinder","onLastStrongRef automatically unlinking death recipients: " -45028,1629848354316000000,4,"","c3 11732 AidlLazyServiceRegistrar: Unregistered all clients and exiting" -45028,1629848354315000000,4,"","c0 11732 AidlLazyServiceRegistrar: Process has 1 (of 1 available) client(s) in use after notification apexservice has clients: 1" -45028,1629848354315000000,4,"","c0 11732 AidlLazyServiceRegistrar: Process has 0 (of 1 available) client(s) in use after notification apexservice has clients: 0" -45028,1629848354315000000,4,"","c0 11732 AidlLazyServiceRegistrar: Trying to shut down the service. No clients in use for any service in process." -45028,1629848354314000000,4,"servicemanager","Notifying apexservice they have clients: 1" -45028,1629848354314000000,4,"servicemanager","Notifying apexservice they have clients: 0" -45028,1629848354273000000,3,"ProcessState","Binder ioctl to enable oneway spam detection failed: Invalid argument" -45028,1629848354272000000,4,"","c2 11994 binder: 11994:11994 ioctl 40046210 7fde058b34 returned -22" -45028,1629848354251000000,3,"dumpstate","Adjusting max progress from 9795 to 10791" -45028,1629848354130000000,3,"ProcessState","Binder ioctl to enable oneway spam detection failed: Invalid argument" -45028,1629848354130000000,4,"","c2 11947 binder: 11947:11947 ioctl 40046210 7fff3be7c4 returned -22" -45028,1629848354106000000,3,"dumpstate","Duration of 'APP SERVICES NON-PLATFORM': 1.36s" -45028,1629848353981000000,4,"putmethod.lati","Explicit concurrent copying GC freed 64704(3613KB) AllocSpace objects, 6(112KB) LOS objects, 67% free, 4033KB/11MB, paused 89us total 70.172ms" -45028,1629848353798000000,4,"FallbackOnDeviceRecognizerModule","FallbackOnDeviceRecognizerModule.dump():36 dump()" -45028,1629848353792000000,4,"Environment","Environment.isPackageInstalled():311 com.bitstrips.imoji is not installed" -45028,1629848353675000000,5,"IorapForwardingService","No service published for: iorapd" -45028,1629848353458000000,3,"ActivityThread","Loading provider com.google.android.gms.location.preferences: com.google.android.gms.location.preferences.LocationPreferencesContentProvider" -45028,1629848353340000000,5,"GCM","GCM FAILED TO INITIALIZE - missing checkin" -45028,1629848353339000000,6,"GCM","Missing checkin config file" -45028,1629848353180000000,3,"CompatibilityChangeReporter","Compat change id reported: 132649864; UID 10130; state: DISABLED" -45028,1629848353179000000,3,"CompatibilityChangeReporter","Compat change id reported: 149924527; UID 10130; state: ENABLED" -45028,1629848352782000000,3,"ProcessState","Binder ioctl to enable oneway spam detection failed: Invalid argument" -45028,1629848352782000000,4,"","c1 11872 binder: 11872:11872 ioctl 40046210 7ff18c3184 returned -22" -45028,1629848352706000000,4,"BufferEulogizer","Not eulogizing buffers; they are 452734 hours old" -45028,1629848352671000000,5,"IorapForwardingService","No service published for: iorapd" -45028,1629848352661000000,5,"BroadcastQueue","Background execution not allowed: receiving Intent { act=android.intent.action.DROPBOX_ENTRY_ADDED flg=0x10 (has extras) } to com.google.android.gms/.stats.service.DropBoxEntryAddedReceiver" -45028,1629848352661000000,5,"BroadcastQueue","Background execution not allowed: receiving Intent { act=android.intent.action.DROPBOX_ENTRY_ADDED flg=0x10 (has extras) } to com.google.android.gms/.chimera.GmsIntentOperationService$PersistentTrustedReceiver" -45028,1629848352512000000,3,"TelephonyProvider","Using old permission behavior for telephony provider compat" -45028,1629848352470000000,3,"TelephonyProvider","Using old permission behavior for telephony provider compat" -45028,1629848352462000000,3,"TelephonyProvider","Using old permission behavior for telephony provider compat" -45028,1629848352453000000,3,"TelephonyProvider","Using old permission behavior for telephony provider compat" -45028,1629848352445000000,3,"TelephonyProvider","Using old permission behavior for telephony provider compat" -45028,1629848352438000000,3,"TelephonyProvider","Using old permission behavior for telephony provider compat" -45028,1629848352430000000,3,"TelephonyProvider","Using old permission behavior for telephony provider compat" -45028,1629848352421000000,3,"TelephonyProvider","Using old permission behavior for telephony provider compat" -45028,1629848352414000000,3,"TelephonyProvider","Using old permission behavior for telephony provider compat" -45028,1629848352402000000,3,"TelephonyProvider","Using old permission behavior for telephony provider compat" -45028,1629848352350000000,3,"ProcessState","Binder ioctl to enable oneway spam detection failed: Invalid argument" -45028,1629848352350000000,4,"","c1 11839 binder: 11839:11839 ioctl 40046210 7feb290494 returned -22" -45028,1629848352273000000,3,"ProcessState","Binder ioctl to enable oneway spam detection failed: Invalid argument" -45028,1629848352272000000,4,"","c0 11835 binder: 11835:11835 ioctl 40046210 7ffa868704 returned -22" -45028,1629848352178000000,3,"dumpstate","Adding dir /data/misc/bluetooth/logs (recursive: 1)" -45028,1629848352025000000,3,"dumpstate","Duration of 'DUMPSYS': 8.76s" -45028,1629848352014000000,3,"WificondScannerImpl","Latest native scan results nowMs = 411505" -45028,1629848352010000000,3,"WifiScanningService","Latest scan results nowMs = 411501" -45028,1629848351951000000,3,"PermissionCache","checking android.permission.DUMP for uid=2000 => granted (2356 us)" -45028,1629848351814000000,4,"","c0 698 logd: logdr: UID=1000 GID=1000 PID=11826 n tail=127 logMask=80 pid=0 start=0ns deadline=0ns" -45028,1629848351673000000,4,"","c1 698 logd: logdr: UID=1000 GID=1000 PID=11823 n tail=127 logMask=19 pid=0 start=0ns deadline=0ns" -45028,1629848351670000000,5,"IorapForwardingService","No service published for: iorapd" -45028,1629848351641000000,4,"","c1 2150 R0: [cds_mc_thread][8121993061] [22:39:11.641914] wlan: [2150:I :WMI] Sent WMI_DEBUG_MESG_FLUSH_CMDID to FW" -45028,1629848351640000000,4,"","c6 1060 R0: [wifi_ext@1.0-se][8121970301] [22:39:11.640728] wlan: [1060:I :HDD] __wlan_hdd_cfg80211_wifi_logger_get_ring_data: 6571: Bug report triggered by framework" -45028,1629848351640000000,4,"","c6 1060 R0: [wifi_ext@1.0-se][8121971425] [22:39:11.640787] wlan: [1060:I :QDF] cds_flush_logs: Triggering bug report: type:0, indicator=1 reason_code=0" -45028,1629848351640000000,4,"","c6 1060 R0: [wifi_ext@1.0-se][8121971808] [22:39:11.640807] wlan: [1060:I :SYS] DPT: Total Records: 113, Head: 0, Tail: 112" -45028,1629848351539000000,2,"UserManagerService","dumpPackageWhitelistProblems(): using mode ENFORCE|IMPLICIT_WHITELIST|IMPLICIT_WHITELIST_SYSTEM" +45030,1609462800670000000,5,"auditd","type=2000 audit(0.0:1): initialized" +45030,1609462800670000000,4,"auditd","type=2000 audit(0.0:1): initialized" +45030,1609463611529000000,4,"","c0 0 Booting Linux on physical CPU 0x0" +45030,1609463611529000000,4,"","c0 0 Linux version 4.9.270-g862f51bac900-ab7613625 (android-build@abfarm-east4-101) (Android (7284624, based on r416183b) clang version 12.0.5 (https://android.googlesource.com/toolchain/llvm-project c935d99d7cf2016289302412d708641d52d2f7ee)) #0 SMP PREEMPT Thu Aug 5 07:04:42 UTC 2021" +45030,1609463611529000000,4,"","c0 0 Boot CPU: AArch64 Processor [517f803c]" +45030,1609463611529000000,4,"","c0 0 Machine: Google Inc. MSM sdm845 C1 EVT v1.0" +45030,1609463611529000000,4,"","c0 0 Reserved memory: created CMA memory pool at 0x00000000fec00000, size 16 MiB" +45030,1609463611529000000,4,"","c0 0 OF: reserved mem: initialized node adsp_region, compatible id shared-dma-pool" +45030,1609463611529000000,4,"","c0 0 Reserved memory: created CMA memory pool at 0x00000000fdc00000, size 16 MiB" +45030,1609463611529000000,4,"","c0 0 OF: reserved mem: initialized node qseecom_ta_region, compatible id shared-dma-pool" +45030,1609463611529000000,4,"","c0 0 Reserved memory: created CMA memory pool at 0x00000000f4c00000, size 144 MiB" +45030,1609463611529000000,4,"","c0 0 OF: reserved mem: initialized node secure_display_region, compatible id shared-dma-pool" +45030,1609463611529000000,4,"","c0 0 Reserved memory: created CMA memory pool at 0x000000017b800000, size 36 MiB" +45030,1609463611529000000,4,"","c0 0 OF: reserved mem: initialized node mem_dump_region, compatible id shared-dma-pool" +45030,1609463611529000000,4,"","c0 0 Reserved memory: created CMA memory pool at 0x00000000f4400000, size 8 MiB" +45030,1609463611529000000,4,"","c0 0 OF: reserved mem: initialized node secure_sp_region, compatible id shared-dma-pool" +45030,1609463611529000000,4,"","c0 0 Reserved memory: created CMA memory pool at 0x00000000f2400000, size 32 MiB" +45030,1609463611529000000,4,"","c0 0 OF: reserved mem: initialized node linux,cma, compatible id shared-dma-pool" +45030,1609463611529000000,4,"","c0 0 Reserved memory: created DMA memory pool at 0x000000008ab00000, size 20 MiB" +45030,1609463611529000000,4,"","c0 0 OF: reserved mem: initialized node qseecom_region@0x8ab00000, compatible id shared-dma-pool" +45030,1609463611529000000,4,"","c0 0 Removed memory: created DMA memory pool at 0x000000008bf00000, size 5 MiB" +45030,1609463611529000000,4,"","c0 0 OF: reserved mem: initialized node camera_region@0x8bf00000, compatible id removed-dma-pool" +45030,1609463611529000000,4,"","c0 0 Removed memory: created DMA memory pool at 0x000000008c400000, size 0 MiB" +45030,1609463611529000000,4,"","c0 0 OF: reserved mem: initialized node ips_fw_region@0x8c400000, compatible id removed-dma-pool" +45030,1609463611529000000,4,"","c0 0 Removed memory: created DMA memory pool at 0x000000008c410000, size 0 MiB" +45030,1609463611529000000,4,"","c0 0 OF: reserved mem: initialized node ipa_gsi_region@0x8c410000, compatible id removed-dma-pool" +45030,1609463611529000000,4,"","c0 0 Removed memory: created DMA memory pool at 0x000000008c415000, size 0 MiB" +45030,1609463611529000000,4,"","c0 0 OF: reserved mem: initialized node gpu_region@0x8c415000, compatible id removed-dma-pool" +45030,1609463611529000000,4,"","c0 0 Removed memory: created DMA memory pool at 0x000000008c500000, size 26 MiB" +45030,1609463611529000000,4,"","c0 0 OF: reserved mem: initialized node adsp_region@0x8c500000, compatible id removed-dma-pool" +45030,1609463611529000000,4,"","c0 0 Removed memory: created DMA memory pool at 0x000000008df00000, size 1 MiB" +45030,1609463611529000000,4,"","c0 0 OF: reserved mem: initialized node wlan_fw_region@0x8df00000, compatible id removed-dma-pool" +45030,1609463611529000000,4,"","c0 0 Removed memory: created DMA memory pool at 0x000000008e000000, size 152 MiB" +45030,1609463611529000000,4,"","c0 0 OF: reserved mem: initialized node modem_region@0x8e000000, compatible id removed-dma-pool" +45030,1609463611529000000,4,"","c0 0 Removed memory: created DMA memory pool at 0x0000000097800000, size 5 MiB" +45030,1609463611529000000,4,"","c0 0 OF: reserved mem: initialized node video_region@0x95800000, compatible id removed-dma-pool" +45030,1609463611529000000,4,"","c0 0 Removed memory: created DMA memory pool at 0x0000000097d00000, size 8 MiB" +45030,1609463611529000000,4,"","c0 0 OF: reserved mem: initialized node cdsp_region@0x95d00000, compatible id removed-dma-pool" +45030,1609463611529000000,4,"","c0 0 Removed memory: created DMA memory pool at 0x0000000098500000, size 2 MiB" +45030,1609463611529000000,4,"","c0 0 OF: reserved mem: initialized node mba_region@0x96500000, compatible id removed-dma-pool" +45030,1609463611529000000,4,"","c0 0 Removed memory: created DMA memory pool at 0x0000000098700000, size 20 MiB" +45030,1609463611529000000,4,"","c0 0 OF: reserved mem: initialized node slpi_region@0x96700000, compatible id removed-dma-pool" +45030,1609463611529000000,4,"","c0 0 Removed memory: created DMA memory pool at 0x0000000099b00000, size 1 MiB" +45030,1609463611529000000,4,"","c0 0 OF: reserved mem: initialized node pil_spss_region@0x97b00000, compatible id removed-dma-pool" +45030,1609463611529000000,4,"","c0 0 Removed memory: created DMA memory pool at 0x00000000a1800000, size 0 MiB" +45030,1609463611529000000,4,"","c0 0 OF: reserved mem: initialized node debug_info@0, compatible id removed-dma-pool" +45030,1609463611529000000,4,"","c0 0 Removed memory: created DMA memory pool at 0x00000000a1810000, size 2 MiB" +45030,1609463611529000000,4,"","c0 0 OF: reserved mem: initialized node ramoops_region@a1810000, compatible id removed-dma-pool" +45030,1609463611529000000,4,"","c0 0 Removed memory: created DMA memory pool at 0x00000000a1a10000, size 2 MiB" +45030,1609463611529000000,4,"","c0 0 OF: reserved mem: initialized node alt_ramoops_region@a1a10000, compatible id removed-dma-pool" +45030,1609463611529000000,4,"","c0 0 Removed memory: created DMA memory pool at 0x00000000a1c10000, size 0 MiB" +45030,1609463611529000000,4,"","c0 0 OF: reserved mem: initialized node ramoops_meta_region@a1c10000, compatible id removed-dma-pool" +45030,1609463611529000000,3,"","c0 0 On node 0 totalpages: 963896" +45030,1609463611529000000,3,"","c0 0 DMA zone: 6999 pages used for memmap" +45030,1609463611529000000,3,"","c0 0 DMA zone: 0 pages reserved" +45030,1609463611529000000,3,"","c0 0 DMA zone: 447896 pages, LIFO batch:31" +45030,1609463611529000000,3,"","c0 0 Normal zone: 8063 pages used for memmap" +45030,1609463611529000000,3,"","c0 0 Normal zone: 516000 pages, LIFO batch:31" +45030,1609463611529000000,4,"","c0 0 psci: probing for conduit method from DT." +45030,1609463611529000000,4,"","c0 0 psci: PSCIv1.1 detected in firmware." +45030,1609463611529000000,4,"","c0 0 psci: Using standard PSCI v0.2 function IDs" +45030,1609463611529000000,4,"","c0 0 psci: MIGRATE_INFO_TYPE not supported." +45030,1609463611529000000,4,"","c0 0 psci: SMC Calling Convention v1.0" +45030,1609463611529000000,4,"","c0 0 random: fast init done" +45030,1609463611529000000,4,"","c0 0 percpu: Embedded 23 pages/cpu s54296 r8192 d31720 u94208" +45030,1609463611529000000,3,"","c0 0 pcpu-alloc: s54296 r8192 d31720 u94208 alloc=23*4096" +45030,1609463611529000000,3,"","c0 0 pcpu-alloc: [0] 0 [0] 1 [0] 2 [0] 3 [0] 4 [0] 5 [0] 6 [0] 7" +45030,1609463611529000000,4,"","c0 0 Built 1 zonelists in Zone order, mobility grouping on. Total pages: 948834" +45030,1609463611529000000,4,"","c0 0 Kernel command line: rcupdate.rcu_expedited=1 rootwait ro init=/init androidboot.bootdevice=1d84000.ufshc androidboot.baseband=sdm androidboot.keymaster=1 msm_drm.dsi_display0=dsi_s6e3ha8_cmd_display::timing0 androidboot.force_normal_boot=1 androidboot.serialno=84TY005M7 androidboot.slot_suffix=_a androidboot.slot_retry_count=2 androidboot.slot_successful=yes androidboot.hardware.platform=sdm845 androidboot.hardware=crosshatch androidboot.revision=EVT1.0 androidboot.bootloader=b1c1-0.4-7617406 androidboot.hardware.sku=G013C androidboot.hardware.radio.subtype=0 androidboot.hardware.dsds=0 androidboot.secure_boot=NONE androidboot.cdt_hwid=0x05010A00 androidboot.hardware.majorid=0x01 androidboot.dtb_idx=0 androidboot.dtbo_idx=10 androidboot.mode=normal androidboot.bootreason=reboot androidboot.hardware.ddr=4GB,Samsung,LPDDR4X androidboot.ddr_info=Samsung androidboot.ddr_size=4GB androidboot.hardware.ufs=64GB,Samsung androidboot.cid=00000000 androidboot.boottime=0BLE:59,1BLL:22," +45030,1609463611529000000,4,"","c0 0 PID hash table entries: 4096 (order: 3, 32768 bytes)" +45030,1609463611529000000,4,"","c0 0 Dentry cache hash table entries: 524288 (order: 10, 4194304 bytes)" +45030,1609463611529000000,4,"","c0 0 Inode-cache hash table entries: 262144 (order: 9, 2097152 bytes)" +45030,1609463611529000000,4,"","c0 0 software IO TLB: mapped [mem 0xee400000-0xf2400000] (64MB)" +45030,1609463611529000000,4,"","c0 0 Memory: 3347020K/3855584K available (26108K kernel code, 3324K rwdata, 8608K rodata, 8192K init, 9407K bss, 250516K reserved, 258048K cma-reserved)" +45030,1609463611529000000,4,"","c0 0 Virtual kernel memory layout:" +45030,1609463611529000000,4,"","c0 0 modules : 0xffffff8000000000 - 0xffffff8008000000 ( 128 MB)" +45030,1609463611529000000,4,"","c0 0 vmalloc : 0xffffff8008000000 - 0xffffffbebfff0000 ( 250 GB)" +45030,1609463611529000000,4,"","c0 0 .text : 0x (ptrval) - 0x (ptrval) ( 26112 KB)" +45030,1609463611529000000,4,"","c0 0 .rodata : 0x (ptrval) - 0x (ptrval) ( 10240 KB)" +45030,1609463611529000000,4,"","c0 0 .init : 0x (ptrval) - 0x (ptrval) ( 8192 KB)" +45030,1609463611529000000,4,"","c0 0 .data : 0x (ptrval) - 0x (ptrval) ( 3324 KB)" +45030,1609463611529000000,4,"","c0 0 .bss : 0x (ptrval) - 0x (ptrval) ( 9408 KB)" +45030,1609463611529000000,4,"","c0 0 fixed : 0xffffffbefe7fb000 - 0xffffffbefec00000 ( 4116 KB)" +45030,1609463611529000000,4,"","c0 0 PCI I/O : 0xffffffbefee00000 - 0xffffffbeffe00000 ( 16 MB)" +45030,1609463611529000000,4,"","c0 0 vmemmap : 0xffffffbf00000000 - 0xffffffc000000000 ( 4 GB maximum)" +45030,1609463611529000000,4,"","c0 0 0xffffffbf7b000000 - 0xffffffbf7ef7e800 ( 63 MB actual)" +45030,1609463611529000000,4,"","c0 0 memory : 0xffffffdec0000000 - 0xffffffdfbdfa0000 ( 4063 MB)" +45030,1609463611529000000,4,"","c0 0 SLUB: HWalign=64, Order=0-3, MinObjects=0, CPUs=8, Nodes=1" +45030,1609463611529000000,4,"","c0 0 Preemptible hierarchical RCU implementation." +45030,1609463611529000000,4,"","c0 0 RCU dyntick-idle grace-period acceleration is enabled." +45030,1609463611529000000,4,"","c0 0 NR_IRQS:64 nr_irqs:64 0" +45030,1609463611529000000,4,"","c0 0 PDC SDM845 v2 initialized" +45030,1609463611529000000,4,"","c0 0 Offload RCU callbacks from all CPUs" +45030,1609463611529000000,4,"","c0 0 Offload RCU callbacks from CPUs: 0-7." +45030,1609463611529000000,4,"","c0 0 arm_arch_timer: Architected cp15 and mmio timer(s) running at 19.20MHz (virt/virt)." +45030,1609463611529000000,4,"","c0 0 clocksource: arch_sys_counter: mask: 0xffffffffffffff max_cycles: 0x46d987e47, max_idle_ns: 440795202767 ns" +45030,1609463611529000000,4,"","c0 0 sched_clock: 56 bits at 19MHz, resolution 52ns, wraps every 4398046511078ns" +45030,1609463611529000000,4,"","c0 0 clocksource: Switched to clocksource arch_sys_counter" +45030,1609463611531000000,4,"","c0 0 Console: colour dummy device 80x25" +45030,1609463611531000000,4,"","c0 0 Calibrating delay loop (skipped), value calculated using timer frequency.. 38.00 BogoMIPS (lpj=64000)" +45030,1629848355866000000,4,"","c3 698 logd: logdr: UID=2000 GID=2000 PID=12111 n tail=0 logMask=99 pid=0 start=1629844543000000000ns deadline=0ns" +45030,1629848355796000000,3,"dumpstate","Duration of 'DUMPSTATE': 199.82s" +45030,1629848355680000000,5,"IorapForwardingService","No service published for: iorapd" +45030,1629848355606000000,3,"dumpstate","Adding dir /sys/fs/cgroup (recursive: 1)" +45030,1629848355577000000,3,"dumpstate","Adding dir /linkerconfig (recursive: 1)" +45030,1629848355566000000,3,"ProcessState","Binder ioctl to enable oneway spam detection failed: Invalid argument" +45030,1629848355566000000,4,"","c1 12109 binder: 12109:12109 ioctl 40046210 7fcfdfc5c4 returned -22" +45030,1629848355540000000,3,"ProcessState","Binder ioctl to enable oneway spam detection failed: Invalid argument" +45030,1629848355540000000,4,"","c0 12107 binder: 12107:12107 ioctl 40046210 7fcff784d4 returned -22" +45030,1629848355514000000,3,"ProcessState","Binder ioctl to enable oneway spam detection failed: Invalid argument" +45030,1629848355513000000,4,"","c0 12105 binder: 12105:12105 ioctl 40046210 7fcac734d4 returned -22" +45030,1629848355475000000,3,"dumpstate","Duration of 'APP PROVIDERS NON-PLATFORM': 1.22s" +45030,1629848355023000000,6,"earchbox:searc","Resource 7f030033 is a complex map type." +45030,1629848355023000000,6,"earchbox:searc","Resource 7f030038 is a complex map type." +45030,1629848355022000000,6,"earchbox:searc","Resource 7f030013 is a complex map type." +45030,1629848355022000000,6,"earchbox:searc","Resource 7f030014 is a complex map type." +45030,1629848355022000000,6,"earchbox:searc","Resource 7f03001b is a complex map type." +45030,1629848355022000000,6,"earchbox:searc","Resource 7f03001c is a complex map type." +45030,1629848355022000000,6,"earchbox:searc","Resource 7f03001f is a complex map type." +45030,1629848355022000000,6,"earchbox:searc","Resource 7f03002c is a complex map type." +45030,1629848355022000000,6,"earchbox:searc","Resource 7f030032 is a complex map type." +45030,1629848355021000000,6,"earchbox:searc","Resource 7f030000 is a complex map type." +45030,1629848355021000000,6,"earchbox:searc","Resource 7f030012 is a complex map type." +45030,1629848354946000000,4,"PCP.MediaManager","Dumping pcp media manager events" +45030,1629848354944000000,4,"PCP.ImplV0","on dump start" +45030,1629848354932000000,4,"A","Loaded shared library: nativecrashreporter" +45030,1629848354910000000,4,"A","[null" +45030,1629848354910000000,4,"A",", GlobalData" +45030,1629848354910000000,4,"A","]" +45030,1629848354892000000,4,"A","[AccountId{id=1}" +45030,1629848354892000000,4,"A",", SharedProactiveData" +45030,1629848354892000000,4,"A",", ClientProactiveData" +45030,1629848354892000000,4,"A",", LastClientDataQuery" +45030,1629848354892000000,4,"A","]" +45030,1629848354683000000,4,"AmbBrdcstRcvrServClient","created monitors of size 4" +45030,1629848354682000000,4,"AmbBrdcstRcvrServClient","mapping trigger monitor: MEDIA_SESSION" +45030,1629848354682000000,4,"AmbBrdcstRcvrServClient","mapping trigger monitor: INFERENCE_START" +45030,1629848354682000000,4,"AmbBrdcstRcvrServClient","mapping trigger monitor: SCREEN_STATE" +45030,1629848354679000000,4,"AmbBrdcstRcvrServClient","mapping trigger monitor: HEADSET_STATE" +45030,1629848354677000000,5,"IorapForwardingService","No service published for: iorapd" +45030,1629848354557000000,4,"PeopleDatabaseHelper","cleanUpNonGplusAccounts done." +45030,1629848354413000000,5,"Icing","Record file /data/user/0/com.google.android.gms/files/AppDataSearch/main/query-record-log.tmp not found, ignoring" +45030,1629848354323000000,4,"","c3 1 init: Service 'apexd' (pid 11731) exited with status 0 oneshot service took 3.818000 seconds in background" +45030,1629848354323000000,4,"","c3 1 init: Sending signal 9 to service 'apexd' (pid 11731) process group..." +45030,1629848354323000000,4,"","c3 1 libprocessgroup: Successfully killed process cgroup uid 0 pid 11731 in 0ms" +45030,1629848354316000000,4,"BpBinder","onLastStrongRef automatically unlinking death recipients: " +45030,1629848354316000000,4,"","c3 11732 AidlLazyServiceRegistrar: Unregistered all clients and exiting" +45030,1629848354315000000,4,"","c0 11732 AidlLazyServiceRegistrar: Process has 1 (of 1 available) client(s) in use after notification apexservice has clients: 1" +45030,1629848354315000000,4,"","c0 11732 AidlLazyServiceRegistrar: Process has 0 (of 1 available) client(s) in use after notification apexservice has clients: 0" +45030,1629848354315000000,4,"","c0 11732 AidlLazyServiceRegistrar: Trying to shut down the service. No clients in use for any service in process." +45030,1629848354314000000,4,"servicemanager","Notifying apexservice they have clients: 1" +45030,1629848354314000000,4,"servicemanager","Notifying apexservice they have clients: 0" +45030,1629848354273000000,3,"ProcessState","Binder ioctl to enable oneway spam detection failed: Invalid argument" +45030,1629848354272000000,4,"","c2 11994 binder: 11994:11994 ioctl 40046210 7fde058b34 returned -22" +45030,1629848354251000000,3,"dumpstate","Adjusting max progress from 9795 to 10791" +45030,1629848354130000000,3,"ProcessState","Binder ioctl to enable oneway spam detection failed: Invalid argument" +45030,1629848354130000000,4,"","c2 11947 binder: 11947:11947 ioctl 40046210 7fff3be7c4 returned -22" +45030,1629848354106000000,3,"dumpstate","Duration of 'APP SERVICES NON-PLATFORM': 1.36s" +45030,1629848353981000000,4,"putmethod.lati","Explicit concurrent copying GC freed 64704(3613KB) AllocSpace objects, 6(112KB) LOS objects, 67% free, 4033KB/11MB, paused 89us total 70.172ms" +45030,1629848353798000000,4,"FallbackOnDeviceRecognizerModule","FallbackOnDeviceRecognizerModule.dump():36 dump()" +45030,1629848353792000000,4,"Environment","Environment.isPackageInstalled():311 com.bitstrips.imoji is not installed" +45030,1629848353675000000,5,"IorapForwardingService","No service published for: iorapd" +45030,1629848353458000000,3,"ActivityThread","Loading provider com.google.android.gms.location.preferences: com.google.android.gms.location.preferences.LocationPreferencesContentProvider" +45030,1629848353340000000,5,"GCM","GCM FAILED TO INITIALIZE - missing checkin" +45030,1629848353339000000,6,"GCM","Missing checkin config file" +45030,1629848353180000000,3,"CompatibilityChangeReporter","Compat change id reported: 132649864; UID 10130; state: DISABLED" +45030,1629848353179000000,3,"CompatibilityChangeReporter","Compat change id reported: 149924527; UID 10130; state: ENABLED" +45030,1629848352782000000,3,"ProcessState","Binder ioctl to enable oneway spam detection failed: Invalid argument" +45030,1629848352782000000,4,"","c1 11872 binder: 11872:11872 ioctl 40046210 7ff18c3184 returned -22" +45030,1629848352706000000,4,"BufferEulogizer","Not eulogizing buffers; they are 452734 hours old" +45030,1629848352671000000,5,"IorapForwardingService","No service published for: iorapd" +45030,1629848352661000000,5,"BroadcastQueue","Background execution not allowed: receiving Intent { act=android.intent.action.DROPBOX_ENTRY_ADDED flg=0x10 (has extras) } to com.google.android.gms/.stats.service.DropBoxEntryAddedReceiver" +45030,1629848352661000000,5,"BroadcastQueue","Background execution not allowed: receiving Intent { act=android.intent.action.DROPBOX_ENTRY_ADDED flg=0x10 (has extras) } to com.google.android.gms/.chimera.GmsIntentOperationService$PersistentTrustedReceiver" +45030,1629848352512000000,3,"TelephonyProvider","Using old permission behavior for telephony provider compat" +45030,1629848352470000000,3,"TelephonyProvider","Using old permission behavior for telephony provider compat" +45030,1629848352462000000,3,"TelephonyProvider","Using old permission behavior for telephony provider compat" +45030,1629848352453000000,3,"TelephonyProvider","Using old permission behavior for telephony provider compat" +45030,1629848352445000000,3,"TelephonyProvider","Using old permission behavior for telephony provider compat" +45030,1629848352438000000,3,"TelephonyProvider","Using old permission behavior for telephony provider compat" +45030,1629848352430000000,3,"TelephonyProvider","Using old permission behavior for telephony provider compat" +45030,1629848352421000000,3,"TelephonyProvider","Using old permission behavior for telephony provider compat" +45030,1629848352414000000,3,"TelephonyProvider","Using old permission behavior for telephony provider compat" +45030,1629848352402000000,3,"TelephonyProvider","Using old permission behavior for telephony provider compat" +45030,1629848352350000000,3,"ProcessState","Binder ioctl to enable oneway spam detection failed: Invalid argument" +45030,1629848352350000000,4,"","c1 11839 binder: 11839:11839 ioctl 40046210 7feb290494 returned -22" +45030,1629848352273000000,3,"ProcessState","Binder ioctl to enable oneway spam detection failed: Invalid argument" +45030,1629848352272000000,4,"","c0 11835 binder: 11835:11835 ioctl 40046210 7ffa868704 returned -22" +45030,1629848352178000000,3,"dumpstate","Adding dir /data/misc/bluetooth/logs (recursive: 1)" +45030,1629848352025000000,3,"dumpstate","Duration of 'DUMPSYS': 8.76s" +45030,1629848352014000000,3,"WificondScannerImpl","Latest native scan results nowMs = 411505" +45030,1629848352010000000,3,"WifiScanningService","Latest scan results nowMs = 411501" +45030,1629848351951000000,3,"PermissionCache","checking android.permission.DUMP for uid=2000 => granted (2356 us)" +45030,1629848351814000000,4,"","c0 698 logd: logdr: UID=1000 GID=1000 PID=11826 n tail=127 logMask=80 pid=0 start=0ns deadline=0ns" +45030,1629848351673000000,4,"","c1 698 logd: logdr: UID=1000 GID=1000 PID=11823 n tail=127 logMask=19 pid=0 start=0ns deadline=0ns" +45030,1629848351670000000,5,"IorapForwardingService","No service published for: iorapd" +45030,1629848351641000000,4,"","c1 2150 R0: [cds_mc_thread][8121993061] [22:39:11.641914] wlan: [2150:I :WMI] Sent WMI_DEBUG_MESG_FLUSH_CMDID to FW" +45030,1629848351640000000,4,"","c6 1060 R0: [wifi_ext@1.0-se][8121970301] [22:39:11.640728] wlan: [1060:I :HDD] __wlan_hdd_cfg80211_wifi_logger_get_ring_data: 6571: Bug report triggered by framework" +45030,1629848351640000000,4,"","c6 1060 R0: [wifi_ext@1.0-se][8121971425] [22:39:11.640787] wlan: [1060:I :QDF] cds_flush_logs: Triggering bug report: type:0, indicator=1 reason_code=0" +45030,1629848351640000000,4,"","c6 1060 R0: [wifi_ext@1.0-se][8121971808] [22:39:11.640807] wlan: [1060:I :SYS] DPT: Total Records: 113, Head: 0, Tail: 112" +45030,1629848351539000000,2,"UserManagerService","dumpPackageWhitelistProblems(): using mode ENFORCE|IMPLICIT_WHITELIST|IMPLICIT_WHITELIST_SYSTEM" diff --git a/test/trace_processor/diff_tests/parser/android/input_event_trace.textproto b/test/trace_processor/diff_tests/parser/android/input_event_trace.textproto new file mode 100644 index 0000000000..ac125c14b2 --- /dev/null +++ b/test/trace_processor/diff_tests/parser/android/input_event_trace.textproto @@ -0,0 +1,1584 @@ +packet { + clock_snapshot { + primary_trace_clock: BUILTIN_CLOCK_BOOTTIME + clocks { + clock_id: 6 + timestamp: 674770590741767 + } + clocks { + clock_id: 2 + timestamp: 1718386902201012395 + } + clocks { + clock_id: 4 + timestamp: 517479917684233 + } + clocks { + clock_id: 1 + timestamp: 1718386902204523056 + } + clocks { + clock_id: 3 + timestamp: 517479921194975 + } + clocks { + clock_id: 5 + timestamp: 517479921195099 + } + } + trusted_uid: 9999 + trusted_packet_sequence_id: 1 +} +packet { + clock_snapshot { + primary_trace_clock: BUILTIN_CLOCK_BOOTTIME + clocks { + clock_id: 6 + timestamp: 674770590825792 + } + clocks { + clock_id: 2 + timestamp: 1718386902201012395 + } + clocks { + clock_id: 4 + timestamp: 517479917684233 + } + clocks { + clock_id: 1 + timestamp: 1718386902204607000 + } + clocks { + clock_id: 3 + timestamp: 517479921278919 + } + clocks { + clock_id: 5 + timestamp: 517479921279042 + } + } + trusted_uid: 9999 + trusted_packet_sequence_id: 1 +} +packet { + trusted_uid: 9999 + trusted_packet_sequence_id: 1 + synchronization_marker: "\202Gzv\262\215B\272\201\33432mW\240y" +} +packet { + trusted_uid: 9999 + trusted_packet_sequence_id: 1 + trace_config { + buffers { + size_kb: 1000000 + fill_policy: RING_BUFFER + } + data_sources { + config { + name: "android.input.inputevent" + android_input_event_config { + mode: TRACE_MODE_TRACE_ALL + } + } + } + duration_ms: 10000 + enable_extra_guardrails: false + statsd_metadata { + } + statsd_logging: STATSD_LOGGING_DISABLED + trace_uuid_msb: 8003899888869335060 + trace_uuid_lsb: -5519492449794714505 + } +} +packet { + trusted_uid: 9999 + trusted_packet_sequence_id: 1 + trace_uuid { + lsb: -5519492449794714505 + msb: 8003899888869335060 + } +} +packet { + system_info { + tracing_service_version: "Perfetto v45.0 (N/A)" + timezone_off_mins: -240 + utsname { + sysname: "Linux" + version: "#1 SMP PREEMPT Thu May 23 13:08:51 UTC 2024" + machine: "aarch64" + release: "5.10.209-android13-4-02835-gce4db2481eb5-ab11882700" + } + page_size: 4096 + num_cpus: 8 + android_build_fingerprint: "google/tangorpro/tangorpro:VanillaIceCream/MAIN/eng.prabir.20240606.172326:eng/dev-keys" + android_sdk_version: 35 + } + trusted_uid: 9999 + trusted_packet_sequence_id: 1 +} +packet { + timestamp: 674770590804552 + trusted_uid: 9999 + trusted_packet_sequence_id: 1 + service_event { + tracing_started: true + } +} +packet { + timestamp: 674770593089261 + trusted_uid: 9999 + trusted_packet_sequence_id: 1 + service_event { + all_data_sources_started: true + } +} +packet { + timestamp: 674775802354888 + trusted_uid: 9999 + trusted_packet_sequence_id: 1 + service_event { + all_data_sources_flushed: true + } +} +packet { + timestamp: 674775804499013 + trusted_uid: 9999 + trusted_packet_sequence_id: 1 + service_event { + tracing_disabled: true + } +} +packet { + first_packet_on_sequence: true + timestamp: 517481517002349 + timestamp_clock_id: 3 + winscope_extensions { + [perfetto.protos.WinscopeExtensionsImpl.android_input_event] { + dispatcher_motion_event { + event_id: 330184796 + event_time_nanos: 517481507875000 + down_time_nanos: 517481507875000 + source: 4098 + action: 0 + device_id: 4 + display_id: 0 + classification: 0 + flags: 128 + policy_flags: 1644167168 + cursor_position_x: nan + cursor_position_y: nan + meta_state: 0 + pointer { + pointer_id: 0 + tool_type: 1 + axis_value { + axis: 0 + value: 431.000000 + } + axis_value { + axis: 1 + value: 624.000000 + } + axis_value { + axis: 2 + value: 1.324219 + } + axis_value { + axis: 3 + value: 0.039273 + } + axis_value { + axis: 4 + value: 110.000000 + } + axis_value { + axis: 5 + value: 91.000000 + } + axis_value { + axis: 6 + value: 110.000000 + } + axis_value { + axis: 7 + value: 91.000000 + } + axis_value { + axis: 8 + value: 1.120190 + } + } + } + } + } + trusted_uid: 1000 + trusted_packet_sequence_id: 2 + trusted_pid: 31778 + previous_packet_dropped: true +} +packet { + timestamp: 517481517002349 + timestamp_clock_id: 3 + winscope_extensions { + [perfetto.protos.WinscopeExtensionsImpl.android_input_event] { + dispatcher_motion_event { + event_id: 1327679296 + event_time_nanos: 517481507875000 + down_time_nanos: 517481507875000 + source: 4098 + action: 4 + device_id: 4 + display_id: 0 + classification: 0 + flags: 128 + policy_flags: 1644167168 + cursor_position_x: nan + cursor_position_y: nan + meta_state: 0 + pointer { + pointer_id: 0 + tool_type: 1 + axis_value { + axis: 0 + value: 431.000000 + } + axis_value { + axis: 1 + value: 624.000000 + } + axis_value { + axis: 2 + value: 1.324219 + } + axis_value { + axis: 3 + value: 0.039273 + } + axis_value { + axis: 4 + value: 110.000000 + } + axis_value { + axis: 5 + value: 91.000000 + } + axis_value { + axis: 6 + value: 110.000000 + } + axis_value { + axis: 7 + value: 91.000000 + } + axis_value { + axis: 8 + value: 1.120190 + } + } + } + } + } + trusted_uid: 1000 + trusted_packet_sequence_id: 2 + trusted_pid: 31778 +} +packet { + timestamp: 517481517002349 + timestamp_clock_id: 3 + winscope_extensions { + [perfetto.protos.WinscopeExtensionsImpl.android_input_event] { + dispatcher_window_dispatch_event { + event_id: 1327679296 + vsync_id: 89110 + window_id: 98 + resolved_flags: 128 + dispatched_pointer { + pointer_id: 0 + x_in_display: 1936.000000 + y_in_display: 431.000000 + axis_value_in_window { + axis: 0 + value: 1936.000000 + } + axis_value_in_window { + axis: 1 + value: -830.000000 + } + axis_value_in_window { + axis: 8 + value: -0.450654 + } + } + } + } + } + trusted_uid: 1000 + trusted_packet_sequence_id: 2 + trusted_pid: 31778 +} +packet { + timestamp: 517481517002349 + timestamp_clock_id: 3 + winscope_extensions { + [perfetto.protos.WinscopeExtensionsImpl.android_input_event] { + dispatcher_window_dispatch_event { + event_id: 330184796 + vsync_id: 89110 + window_id: 212 + resolved_flags: 128 + dispatched_pointer { + pointer_id: 0 + x_in_display: 1936.000000 + y_in_display: 431.000000 + axis_value_in_window { + axis: 0 + value: 1936.000000 + } + axis_value_in_window { + axis: 1 + value: 431.000000 + } + axis_value_in_window { + axis: 8 + value: -0.450638 + } + } + } + } + } + trusted_uid: 1000 + trusted_packet_sequence_id: 2 + trusted_pid: 31778 +} +packet { + timestamp: 517481517002349 + timestamp_clock_id: 3 + winscope_extensions { + [perfetto.protos.WinscopeExtensionsImpl.android_input_event] { + dispatcher_window_dispatch_event { + event_id: 330184796 + vsync_id: 89110 + window_id: 64 + resolved_flags: 131 + dispatched_pointer { + pointer_id: 0 + x_in_display: 1936.000000 + y_in_display: 431.000000 + axis_value_in_window { + axis: 0 + value: 1876.363281 + } + axis_value_in_window { + axis: 1 + value: 464.545898 + } + axis_value_in_window { + axis: 8 + value: -0.450681 + } + } + } + } + } + trusted_uid: 1000 + trusted_packet_sequence_id: 2 + trusted_pid: 31778 +} +packet { + timestamp: 517481517002349 + timestamp_clock_id: 3 + winscope_extensions { + [perfetto.protos.WinscopeExtensionsImpl.android_input_event] { + dispatcher_window_dispatch_event { + event_id: 330184796 + vsync_id: 89110 + window_id: 82 + resolved_flags: 128 + dispatched_pointer { + pointer_id: 0 + x_in_display: 1936.000000 + y_in_display: 431.000000 + axis_value_in_window { + axis: 0 + value: 1936.000000 + } + axis_value_in_window { + axis: 1 + value: 431.000000 + } + axis_value_in_window { + axis: 8 + value: -0.450638 + } + } + } + } + } + trusted_uid: 1000 + trusted_packet_sequence_id: 2 + trusted_pid: 31778 +} +packet { + timestamp: 517481517002349 + timestamp_clock_id: 3 + winscope_extensions { + [perfetto.protos.WinscopeExtensionsImpl.android_input_event] { + dispatcher_window_dispatch_event { + event_id: 330184796 + vsync_id: 89110 + window_id: 75 + resolved_flags: 128 + dispatched_pointer { + pointer_id: 0 + x_in_display: 1936.000000 + y_in_display: 431.000000 + axis_value_in_window { + axis: 0 + value: 1936.000000 + } + axis_value_in_window { + axis: 1 + value: 431.000000 + } + axis_value_in_window { + axis: 8 + value: -0.450638 + } + } + } + } + } + trusted_uid: 1000 + trusted_packet_sequence_id: 2 + trusted_pid: 31778 +} +packet { + timestamp: 517481517002349 + timestamp_clock_id: 3 + winscope_extensions { + [perfetto.protos.WinscopeExtensionsImpl.android_input_event] { + dispatcher_window_dispatch_event { + event_id: 330184796 + vsync_id: 0 + window_id: 0 + resolved_flags: 128 + dispatched_pointer { + pointer_id: 0 + x_in_display: 1936.000000 + y_in_display: 431.000000 + axis_value_in_window { + axis: 0 + value: 1936.000000 + } + axis_value_in_window { + axis: 1 + value: 431.000000 + } + axis_value_in_window { + axis: 8 + value: -0.450638 + } + } + } + } + } + trusted_uid: 1000 + trusted_packet_sequence_id: 2 + trusted_pid: 31778 +} +packet { + timestamp: 517481538183257 + timestamp_clock_id: 3 + winscope_extensions { + [perfetto.protos.WinscopeExtensionsImpl.android_input_event] { + dispatcher_motion_event { + event_id: 557261353 + event_time_nanos: 517481533371000 + down_time_nanos: 517481507875000 + source: 4098 + action: 2 + device_id: 4 + display_id: 0 + classification: 1 + flags: 128 + policy_flags: 1644167168 + cursor_position_x: nan + cursor_position_y: nan + meta_state: 0 + pointer { + pointer_id: 0 + tool_type: 1 + axis_value { + axis: 0 + value: 431.000000 + } + axis_value { + axis: 1 + value: 624.000000 + } + axis_value { + axis: 2 + value: 1.320312 + } + axis_value { + axis: 3 + value: 0.039273 + } + axis_value { + axis: 4 + value: 110.000000 + } + axis_value { + axis: 5 + value: 91.000000 + } + axis_value { + axis: 6 + value: 110.000000 + } + axis_value { + axis: 7 + value: 91.000000 + } + axis_value { + axis: 8 + value: 1.120190 + } + } + } + } + } + trusted_uid: 1000 + trusted_packet_sequence_id: 2 + trusted_pid: 31778 +} +packet { + timestamp: 517481538183257 + timestamp_clock_id: 3 + winscope_extensions { + [perfetto.protos.WinscopeExtensionsImpl.android_input_event] { + dispatcher_window_dispatch_event { + event_id: 557261353 + vsync_id: 89110 + window_id: 212 + resolved_flags: 128 + dispatched_pointer { + pointer_id: 0 + x_in_display: 1936.000000 + y_in_display: 431.000000 + axis_value_in_window { + axis: 0 + value: 1936.000000 + } + axis_value_in_window { + axis: 1 + value: 431.000000 + } + axis_value_in_window { + axis: 8 + value: -0.450638 + } + } + } + } + } + trusted_uid: 1000 + trusted_packet_sequence_id: 2 + trusted_pid: 31778 +} +packet { + timestamp: 517481538183257 + timestamp_clock_id: 3 + winscope_extensions { + [perfetto.protos.WinscopeExtensionsImpl.android_input_event] { + dispatcher_window_dispatch_event { + event_id: 557261353 + vsync_id: 89110 + window_id: 64 + resolved_flags: 131 + dispatched_pointer { + pointer_id: 0 + x_in_display: 1936.000000 + y_in_display: 431.000000 + axis_value_in_window { + axis: 0 + value: 1876.363281 + } + axis_value_in_window { + axis: 1 + value: 464.545898 + } + axis_value_in_window { + axis: 8 + value: -0.450681 + } + } + } + } + } + trusted_uid: 1000 + trusted_packet_sequence_id: 2 + trusted_pid: 31778 +} +packet { + timestamp: 517481538183257 + timestamp_clock_id: 3 + winscope_extensions { + [perfetto.protos.WinscopeExtensionsImpl.android_input_event] { + dispatcher_window_dispatch_event { + event_id: 557261353 + vsync_id: 89110 + window_id: 82 + resolved_flags: 128 + dispatched_pointer { + pointer_id: 0 + x_in_display: 1936.000000 + y_in_display: 431.000000 + axis_value_in_window { + axis: 0 + value: 1936.000000 + } + axis_value_in_window { + axis: 1 + value: 431.000000 + } + axis_value_in_window { + axis: 8 + value: -0.450638 + } + } + } + } + } + trusted_uid: 1000 + trusted_packet_sequence_id: 2 + trusted_pid: 31778 +} +packet { + timestamp: 517481538183257 + timestamp_clock_id: 3 + winscope_extensions { + [perfetto.protos.WinscopeExtensionsImpl.android_input_event] { + dispatcher_window_dispatch_event { + event_id: 557261353 + vsync_id: 89110 + window_id: 75 + resolved_flags: 128 + dispatched_pointer { + pointer_id: 0 + x_in_display: 1936.000000 + y_in_display: 431.000000 + axis_value_in_window { + axis: 0 + value: 1936.000000 + } + axis_value_in_window { + axis: 1 + value: 431.000000 + } + axis_value_in_window { + axis: 8 + value: -0.450638 + } + } + } + } + } + trusted_uid: 1000 + trusted_packet_sequence_id: 2 + trusted_pid: 31778 +} +packet { + timestamp: 517481538183257 + timestamp_clock_id: 3 + winscope_extensions { + [perfetto.protos.WinscopeExtensionsImpl.android_input_event] { + dispatcher_window_dispatch_event { + event_id: 557261353 + vsync_id: 0 + window_id: 0 + resolved_flags: 128 + dispatched_pointer { + pointer_id: 0 + x_in_display: 1936.000000 + y_in_display: 431.000000 + axis_value_in_window { + axis: 0 + value: 1936.000000 + } + axis_value_in_window { + axis: 1 + value: 431.000000 + } + axis_value_in_window { + axis: 8 + value: -0.450638 + } + } + } + } + } + trusted_uid: 1000 + trusted_packet_sequence_id: 2 + trusted_pid: 31778 +} +packet { + timestamp: 517481543976511 + timestamp_clock_id: 3 + winscope_extensions { + [perfetto.protos.WinscopeExtensionsImpl.android_input_event] { + dispatcher_motion_event { + event_id: 106022695 + event_time_nanos: 517481540876000 + down_time_nanos: 517481507875000 + source: 4098 + action: 2 + device_id: 4 + display_id: 0 + classification: 1 + flags: 128 + policy_flags: 1644167168 + cursor_position_x: nan + cursor_position_y: nan + meta_state: 0 + pointer { + pointer_id: 0 + tool_type: 1 + axis_value { + axis: 0 + value: 431.000000 + } + axis_value { + axis: 1 + value: 624.000000 + } + axis_value { + axis: 2 + value: 1.105469 + } + axis_value { + axis: 3 + value: 0.035952 + } + axis_value { + axis: 4 + value: 101.000000 + } + axis_value { + axis: 5 + value: 83.000000 + } + axis_value { + axis: 6 + value: 101.000000 + } + axis_value { + axis: 7 + value: 83.000000 + } + axis_value { + axis: 8 + value: 1.175413 + } + } + } + } + } + trusted_uid: 1000 + trusted_packet_sequence_id: 2 + trusted_pid: 31778 +} +packet { + timestamp: 517481543976511 + timestamp_clock_id: 3 + winscope_extensions { + [perfetto.protos.WinscopeExtensionsImpl.android_input_event] { + dispatcher_window_dispatch_event { + event_id: 106022695 + vsync_id: 89110 + window_id: 212 + resolved_flags: 128 + dispatched_pointer { + pointer_id: 0 + x_in_display: 1936.000000 + y_in_display: 431.000000 + axis_value_in_window { + axis: 0 + value: 1936.000000 + } + axis_value_in_window { + axis: 1 + value: 431.000000 + } + axis_value_in_window { + axis: 8 + value: -0.395468 + } + } + } + } + } + trusted_uid: 1000 + trusted_packet_sequence_id: 2 + trusted_pid: 31778 +} +packet { + timestamp: 517481543976511 + timestamp_clock_id: 3 + winscope_extensions { + [perfetto.protos.WinscopeExtensionsImpl.android_input_event] { + dispatcher_window_dispatch_event { + event_id: 106022695 + vsync_id: 89110 + window_id: 64 + resolved_flags: 131 + dispatched_pointer { + pointer_id: 0 + x_in_display: 1936.000000 + y_in_display: 431.000000 + axis_value_in_window { + axis: 0 + value: 1876.363281 + } + axis_value_in_window { + axis: 1 + value: 464.545898 + } + axis_value_in_window { + axis: 8 + value: -0.395334 + } + } + } + } + } + trusted_uid: 1000 + trusted_packet_sequence_id: 2 + trusted_pid: 31778 +} +packet { + timestamp: 517481543976511 + timestamp_clock_id: 3 + winscope_extensions { + [perfetto.protos.WinscopeExtensionsImpl.android_input_event] { + dispatcher_window_dispatch_event { + event_id: 106022695 + vsync_id: 89110 + window_id: 82 + resolved_flags: 128 + dispatched_pointer { + pointer_id: 0 + x_in_display: 1936.000000 + y_in_display: 431.000000 + axis_value_in_window { + axis: 0 + value: 1936.000000 + } + axis_value_in_window { + axis: 1 + value: 431.000000 + } + axis_value_in_window { + axis: 8 + value: -0.395468 + } + } + } + } + } + trusted_uid: 1000 + trusted_packet_sequence_id: 2 + trusted_pid: 31778 +} +packet { + timestamp: 517481543976511 + timestamp_clock_id: 3 + winscope_extensions { + [perfetto.protos.WinscopeExtensionsImpl.android_input_event] { + dispatcher_window_dispatch_event { + event_id: 106022695 + vsync_id: 89110 + window_id: 75 + resolved_flags: 128 + dispatched_pointer { + pointer_id: 0 + x_in_display: 1936.000000 + y_in_display: 431.000000 + axis_value_in_window { + axis: 0 + value: 1936.000000 + } + axis_value_in_window { + axis: 1 + value: 431.000000 + } + axis_value_in_window { + axis: 8 + value: -0.395468 + } + } + } + } + } + trusted_uid: 1000 + trusted_packet_sequence_id: 2 + trusted_pid: 31778 +} +packet { + timestamp: 517481543976511 + timestamp_clock_id: 3 + winscope_extensions { + [perfetto.protos.WinscopeExtensionsImpl.android_input_event] { + dispatcher_window_dispatch_event { + event_id: 106022695 + vsync_id: 0 + window_id: 0 + resolved_flags: 128 + dispatched_pointer { + pointer_id: 0 + x_in_display: 1936.000000 + y_in_display: 431.000000 + axis_value_in_window { + axis: 0 + value: 1936.000000 + } + axis_value_in_window { + axis: 1 + value: 431.000000 + } + axis_value_in_window { + axis: 8 + value: -0.395468 + } + } + } + } + } + trusted_uid: 1000 + trusted_packet_sequence_id: 2 + trusted_pid: 31778 +} +packet { + timestamp: 517481553353301 + timestamp_clock_id: 3 + winscope_extensions { + [perfetto.protos.WinscopeExtensionsImpl.android_input_event] { + dispatcher_motion_event { + event_id: 313395000 + event_time_nanos: 517481550152000 + down_time_nanos: 517481507875000 + source: 4098 + action: 2 + device_id: 4 + display_id: 0 + classification: 1 + flags: 128 + policy_flags: 1644167168 + cursor_position_x: nan + cursor_position_y: nan + meta_state: 0 + pointer { + pointer_id: 0 + tool_type: 1 + axis_value { + axis: 0 + value: 431.000000 + } + axis_value { + axis: 1 + value: 624.000000 + } + axis_value { + axis: 2 + value: 0.113281 + } + axis_value { + axis: 3 + value: 0.011333 + } + axis_value { + axis: 4 + value: 29.000000 + } + axis_value { + axis: 5 + value: 29.000000 + } + axis_value { + axis: 6 + value: 29.000000 + } + axis_value { + axis: 7 + value: 29.000000 + } + axis_value { + axis: 8 + value: 1.175413 + } + } + } + } + } + trusted_uid: 1000 + trusted_packet_sequence_id: 2 + trusted_pid: 31778 +} +packet { + timestamp: 517481553353301 + timestamp_clock_id: 3 + winscope_extensions { + [perfetto.protos.WinscopeExtensionsImpl.android_input_event] { + dispatcher_window_dispatch_event { + event_id: 313395000 + vsync_id: 89110 + window_id: 212 + resolved_flags: 128 + dispatched_pointer { + pointer_id: 0 + x_in_display: 1936.000000 + y_in_display: 431.000000 + axis_value_in_window { + axis: 0 + value: 1936.000000 + } + axis_value_in_window { + axis: 1 + value: 431.000000 + } + axis_value_in_window { + axis: 8 + value: -0.395468 + } + } + } + } + } + trusted_uid: 1000 + trusted_packet_sequence_id: 2 + trusted_pid: 31778 +} +packet { + timestamp: 517481553353301 + timestamp_clock_id: 3 + winscope_extensions { + [perfetto.protos.WinscopeExtensionsImpl.android_input_event] { + dispatcher_window_dispatch_event { + event_id: 313395000 + vsync_id: 89110 + window_id: 64 + resolved_flags: 131 + dispatched_pointer { + pointer_id: 0 + x_in_display: 1936.000000 + y_in_display: 431.000000 + axis_value_in_window { + axis: 0 + value: 1876.363281 + } + axis_value_in_window { + axis: 1 + value: 464.545898 + } + axis_value_in_window { + axis: 8 + value: -0.395334 + } + } + } + } + } + trusted_uid: 1000 + trusted_packet_sequence_id: 2 + trusted_pid: 31778 +} +packet { + timestamp: 517481553353301 + timestamp_clock_id: 3 + winscope_extensions { + [perfetto.protos.WinscopeExtensionsImpl.android_input_event] { + dispatcher_window_dispatch_event { + event_id: 313395000 + vsync_id: 89110 + window_id: 82 + resolved_flags: 128 + dispatched_pointer { + pointer_id: 0 + x_in_display: 1936.000000 + y_in_display: 431.000000 + axis_value_in_window { + axis: 0 + value: 1936.000000 + } + axis_value_in_window { + axis: 1 + value: 431.000000 + } + axis_value_in_window { + axis: 8 + value: -0.395468 + } + } + } + } + } + trusted_uid: 1000 + trusted_packet_sequence_id: 2 + trusted_pid: 31778 +} +packet { + timestamp: 517481553353301 + timestamp_clock_id: 3 + winscope_extensions { + [perfetto.protos.WinscopeExtensionsImpl.android_input_event] { + dispatcher_window_dispatch_event { + event_id: 313395000 + vsync_id: 89110 + window_id: 75 + resolved_flags: 128 + dispatched_pointer { + pointer_id: 0 + x_in_display: 1936.000000 + y_in_display: 431.000000 + axis_value_in_window { + axis: 0 + value: 1936.000000 + } + axis_value_in_window { + axis: 1 + value: 431.000000 + } + axis_value_in_window { + axis: 8 + value: -0.395468 + } + } + } + } + } + trusted_uid: 1000 + trusted_packet_sequence_id: 2 + trusted_pid: 31778 +} +packet { + timestamp: 517481553353301 + timestamp_clock_id: 3 + winscope_extensions { + [perfetto.protos.WinscopeExtensionsImpl.android_input_event] { + dispatcher_window_dispatch_event { + event_id: 313395000 + vsync_id: 0 + window_id: 0 + resolved_flags: 128 + dispatched_pointer { + pointer_id: 0 + x_in_display: 1936.000000 + y_in_display: 431.000000 + axis_value_in_window { + axis: 0 + value: 1936.000000 + } + axis_value_in_window { + axis: 1 + value: 431.000000 + } + axis_value_in_window { + axis: 8 + value: -0.395468 + } + } + } + } + } + trusted_uid: 1000 + trusted_packet_sequence_id: 2 + trusted_pid: 31778 +} +packet { + timestamp: 517481558399200 + timestamp_clock_id: 3 + winscope_extensions { + [perfetto.protos.WinscopeExtensionsImpl.android_input_event] { + dispatcher_motion_event { + event_id: 436499943 + event_time_nanos: 517481557012000 + down_time_nanos: 517481507875000 + source: 4098 + action: 1 + device_id: 4 + display_id: 0 + classification: 1 + flags: 128 + policy_flags: 1644167168 + cursor_position_x: nan + cursor_position_y: nan + meta_state: 0 + pointer { + pointer_id: 0 + tool_type: 1 + axis_value { + axis: 0 + value: 431.000000 + } + axis_value { + axis: 1 + value: 624.000000 + } + axis_value { + axis: 2 + value: 0.113281 + } + axis_value { + axis: 3 + value: 0.011333 + } + axis_value { + axis: 4 + value: 29.000000 + } + axis_value { + axis: 5 + value: 29.000000 + } + axis_value { + axis: 6 + value: 29.000000 + } + axis_value { + axis: 7 + value: 29.000000 + } + axis_value { + axis: 8 + value: 1.175413 + } + } + } + } + } + trusted_uid: 1000 + trusted_packet_sequence_id: 2 + trusted_pid: 31778 +} +packet { + timestamp: 517481558399200 + timestamp_clock_id: 3 + winscope_extensions { + [perfetto.protos.WinscopeExtensionsImpl.android_input_event] { + dispatcher_window_dispatch_event { + event_id: 436499943 + vsync_id: 89110 + window_id: 212 + resolved_flags: 128 + dispatched_pointer { + pointer_id: 0 + x_in_display: 1936.000000 + y_in_display: 431.000000 + axis_value_in_window { + axis: 0 + value: 1936.000000 + } + axis_value_in_window { + axis: 1 + value: 431.000000 + } + axis_value_in_window { + axis: 8 + value: -0.395468 + } + } + } + } + } + trusted_uid: 1000 + trusted_packet_sequence_id: 2 + trusted_pid: 31778 +} +packet { + timestamp: 517481558399200 + timestamp_clock_id: 3 + winscope_extensions { + [perfetto.protos.WinscopeExtensionsImpl.android_input_event] { + dispatcher_window_dispatch_event { + event_id: 436499943 + vsync_id: 89110 + window_id: 64 + resolved_flags: 131 + dispatched_pointer { + pointer_id: 0 + x_in_display: 1936.000000 + y_in_display: 431.000000 + axis_value_in_window { + axis: 0 + value: 1876.363281 + } + axis_value_in_window { + axis: 1 + value: 464.545898 + } + axis_value_in_window { + axis: 8 + value: -0.395334 + } + } + } + } + } + trusted_uid: 1000 + trusted_packet_sequence_id: 2 + trusted_pid: 31778 +} +packet { + timestamp: 517481558399200 + timestamp_clock_id: 3 + winscope_extensions { + [perfetto.protos.WinscopeExtensionsImpl.android_input_event] { + dispatcher_window_dispatch_event { + event_id: 436499943 + vsync_id: 89110 + window_id: 82 + resolved_flags: 128 + dispatched_pointer { + pointer_id: 0 + x_in_display: 1936.000000 + y_in_display: 431.000000 + axis_value_in_window { + axis: 0 + value: 1936.000000 + } + axis_value_in_window { + axis: 1 + value: 431.000000 + } + axis_value_in_window { + axis: 8 + value: -0.395468 + } + } + } + } + } + trusted_uid: 1000 + trusted_packet_sequence_id: 2 + trusted_pid: 31778 +} +packet { + timestamp: 517481558399200 + timestamp_clock_id: 3 + winscope_extensions { + [perfetto.protos.WinscopeExtensionsImpl.android_input_event] { + dispatcher_window_dispatch_event { + event_id: 436499943 + vsync_id: 89110 + window_id: 75 + resolved_flags: 128 + dispatched_pointer { + pointer_id: 0 + x_in_display: 1936.000000 + y_in_display: 431.000000 + axis_value_in_window { + axis: 0 + value: 1936.000000 + } + axis_value_in_window { + axis: 1 + value: 431.000000 + } + axis_value_in_window { + axis: 8 + value: -0.395468 + } + } + } + } + } + trusted_uid: 1000 + trusted_packet_sequence_id: 2 + trusted_pid: 31778 +} +packet { + timestamp: 517481558399200 + timestamp_clock_id: 3 + winscope_extensions { + [perfetto.protos.WinscopeExtensionsImpl.android_input_event] { + dispatcher_window_dispatch_event { + event_id: 436499943 + vsync_id: 0 + window_id: 0 + resolved_flags: 128 + dispatched_pointer { + pointer_id: 0 + x_in_display: 1936.000000 + y_in_display: 431.000000 + axis_value_in_window { + axis: 0 + value: 1936.000000 + } + axis_value_in_window { + axis: 1 + value: 431.000000 + } + axis_value_in_window { + axis: 8 + value: -0.395468 + } + } + } + } + } + trusted_uid: 1000 + trusted_packet_sequence_id: 2 + trusted_pid: 31778 +} +packet { + timestamp: 517482831698151 + timestamp_clock_id: 3 + winscope_extensions { + [perfetto.protos.WinscopeExtensionsImpl.android_input_event] { + dispatcher_key_event { + event_id: 759309047 + event_time_nanos: 517482680619000 + down_time_nanos: 517482680619000 + source: 257 + action: 0 + device_id: 2 + display_id: -1 + repeat_count: 0 + flags: 8 + policy_flags: 1644167168 + key_code: 24 + scan_code: 115 + meta_state: 0 + } + } + } + trusted_uid: 1000 + trusted_packet_sequence_id: 2 + trusted_pid: 31778 +} +packet { + timestamp: 517482831698151 + timestamp_clock_id: 3 + winscope_extensions { + [perfetto.protos.WinscopeExtensionsImpl.android_input_event] { + dispatcher_window_dispatch_event { + event_id: 759309047 + vsync_id: 89110 + window_id: 212 + resolved_flags: 8 + } + } + } + trusted_uid: 1000 + trusted_packet_sequence_id: 2 + trusted_pid: 31778 +} +packet { + timestamp: 517482831698151 + timestamp_clock_id: 3 + winscope_extensions { + [perfetto.protos.WinscopeExtensionsImpl.android_input_event] { + dispatcher_window_dispatch_event { + event_id: 759309047 + vsync_id: 0 + window_id: 0 + resolved_flags: 8 + } + } + } + trusted_uid: 1000 + trusted_packet_sequence_id: 2 + trusted_pid: 31778 +} +packet { + timestamp: 517482839729238 + timestamp_clock_id: 3 + winscope_extensions { + [perfetto.protos.WinscopeExtensionsImpl.android_input_event] { + dispatcher_key_event { + event_id: 894093732 + event_time_nanos: 517482832173000 + down_time_nanos: 517482680619000 + source: 257 + action: 1 + device_id: 2 + display_id: -1 + repeat_count: 0 + flags: 8 + policy_flags: 1644167168 + key_code: 24 + scan_code: 115 + meta_state: 0 + } + } + } + trusted_uid: 1000 + trusted_packet_sequence_id: 2 + trusted_pid: 31778 +} +packet { + timestamp: 517482839729238 + timestamp_clock_id: 3 + winscope_extensions { + [perfetto.protos.WinscopeExtensionsImpl.android_input_event] { + dispatcher_window_dispatch_event { + event_id: 894093732 + vsync_id: 89110 + window_id: 212 + resolved_flags: 8 + } + } + } + trusted_uid: 1000 + trusted_packet_sequence_id: 2 + trusted_pid: 31778 +} +packet { + timestamp: 517482839729238 + timestamp_clock_id: 3 + winscope_extensions { + [perfetto.protos.WinscopeExtensionsImpl.android_input_event] { + dispatcher_window_dispatch_event { + event_id: 894093732 + vsync_id: 0 + window_id: 0 + resolved_flags: 8 + } + } + } + trusted_uid: 1000 + trusted_packet_sequence_id: 2 + trusted_pid: 31778 +} +packet { + timestamp: 674775805961781 + trusted_uid: 9999 + trusted_packet_sequence_id: 1 + service_event { + read_tracing_buffers_completed: true + } +} +packet { + trusted_uid: 9999 + trusted_packet_sequence_id: 1 + trace_stats { + buffer_stats { + buffer_size: 1024000000 + bytes_written: 4096 + chunks_written: 1 + } + producers_connected: 18 + producers_seen: 838 + data_sources_registered: 51 + data_sources_seen: 197 + tracing_sessions: 2 + total_buffers: 4 + chunks_discarded: 78 + patches_discarded: 0 + invalid_packets: 0 + flushes_requested: 1 + flushes_succeeded: 1 + flushes_failed: 0 + final_flush_outcome: FINAL_FLUSH_UNSPECIFIED + } +} \ No newline at end of file diff --git a/test/trace_processor/diff_tests/parser/android/protolog.textproto b/test/trace_processor/diff_tests/parser/android/protolog.textproto index 6e65ffdd3b..a7c172facb 100644 --- a/test/trace_processor/diff_tests/parser/android/protolog.textproto +++ b/test/trace_processor/diff_tests/parser/android/protolog.textproto @@ -101,6 +101,55 @@ packet { str_param_iids: 1 } } +packet { + trusted_uid: 1000 + trusted_packet_sequence_id: 3 + interned_data { + protolog_string_args { + iid: 12 + str: "MyTestString" + } + } + trusted_pid: 1716 +} +packet { + trusted_uid: 1000 + trusted_packet_sequence_id: 3 + protolog_viewer_config { + groups { + id: 12 + name: "MY_NON_PROCESSED_GROUP" + tag: "MyNonProcessedGroup" + } + } +} +packet { + trusted_uid: 1000 + trusted_packet_sequence_id: 3 + protolog_viewer_config { + messages { + message_id: 9084537961316395367 + message: "My non-processed proto message with a string (%s), an int (%d), a double %g, and a boolean %b." + level: PROTOLOG_LEVEL_VERBOSE + group_id: 12 + } + } +} +packet { + trusted_uid: 1000 + trusted_packet_sequence_id: 3 + sequence_flags: 2 + trusted_pid: 1716 + timestamp: 857384140 + protolog_message { + message_id: 9084537961316395367 + str_param_iids: 12 + sint64_params: 888 + double_params: 8.88 + boolean_params: 1 + stacktrace_iid: 1 + } +} packet { trusted_uid: 10224 trusted_packet_sequence_id: 10 diff --git a/test/trace_processor/diff_tests/parser/android/tests_android_input_event.py b/test/trace_processor/diff_tests/parser/android/tests_android_input_event.py new file mode 100644 index 0000000000..c7c8e96640 --- /dev/null +++ b/test/trace_processor/diff_tests/parser/android/tests_android_input_event.py @@ -0,0 +1,284 @@ +#!/usr/bin/env python3 +# Copyright (C) 2024 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License a +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from python.generators.diff_tests.testing import Path +from python.generators.diff_tests.testing import Csv +from python.generators.diff_tests.testing import DiffTestBlueprint +from python.generators.diff_tests.testing import TestSuite + +class AndroidInputEvent(TestSuite): + + def test_key_events_table(self): + return DiffTestBlueprint( + trace=Path('input_event_trace.textproto'), + query=""" + INCLUDE PERFETTO MODULE android.input; + SELECT + event_id, ts + FROM + android_key_events; + """, + out=Csv(""" + "event_id","ts" + 759309047,674773501245024 + 894093732,674773509276111 + """)) + + def test_key_events_args(self): + return DiffTestBlueprint( + trace=Path('input_event_trace.textproto'), + query=""" + INCLUDE PERFETTO MODULE android.input; + SELECT + args.key, args.display_value + FROM + android_key_events AS e JOIN args ON e.arg_set_id = args.arg_set_id + WHERE e.event_id = 894093732 + ORDER BY args.key; + """, + out=Csv(""" + "key","display_value" + "action","1" + "device_id","2" + "display_id","-1" + "down_time_nanos","517482680619000" + "event_id","894093732" + "event_time_nanos","517482832173000" + "flags","8" + "key_code","24" + "meta_state","0" + "policy_flags","1644167168" + "repeat_count","0" + "scan_code","115" + "source","257" + """)) + + def test_motion_events_table(self): + return DiffTestBlueprint( + trace=Path('input_event_trace.textproto'), + query=""" + INCLUDE PERFETTO MODULE android.input; + SELECT + event_id, ts + FROM + android_motion_events; + """, + out=Csv(""" + "event_id","ts" + 330184796,674772186549222 + 1327679296,674772186549222 + 557261353,674772207730130 + 106022695,674772213523384 + 313395000,674772222900174 + 436499943,674772227946073 + """)) + + def test_motion_events_args(self): + return DiffTestBlueprint( + trace=Path('input_event_trace.textproto'), + query=""" + INCLUDE PERFETTO MODULE android.input; + SELECT + args.key, args.display_value + FROM + android_motion_events AS e JOIN args ON e.arg_set_id = args.arg_set_id + WHERE e.event_id = 557261353 + ORDER BY args.key; + """, + out=Csv(""" + "key","display_value" + "action","2" + "classification","1" + "cursor_position_x","[NULL]" + "cursor_position_y","[NULL]" + "device_id","4" + "display_id","0" + "down_time_nanos","517481507875000" + "event_id","557261353" + "event_time_nanos","517481533371000" + "flags","128" + "meta_state","0" + "pointer[0].axis_value[0].axis","0" + "pointer[0].axis_value[0].value","431.0" + "pointer[0].axis_value[1].axis","1" + "pointer[0].axis_value[1].value","624.0" + "pointer[0].axis_value[2].axis","2" + "pointer[0].axis_value[2].value","1.32031202316284" + "pointer[0].axis_value[3].axis","3" + "pointer[0].axis_value[3].value","0.0392730012536049" + "pointer[0].axis_value[4].axis","4" + "pointer[0].axis_value[4].value","110.0" + "pointer[0].axis_value[5].axis","5" + "pointer[0].axis_value[5].value","91.0" + "pointer[0].axis_value[6].axis","6" + "pointer[0].axis_value[6].value","110.0" + "pointer[0].axis_value[7].axis","7" + "pointer[0].axis_value[7].value","91.0" + "pointer[0].axis_value[8].axis","8" + "pointer[0].axis_value[8].value","1.12019002437592" + "pointer[0].pointer_id","0" + "pointer[0].tool_type","1" + "policy_flags","1644167168" + "source","4098" + """)) + + def test_dispatch_table(self): + return DiffTestBlueprint( + trace=Path('input_event_trace.textproto'), + query=""" + INCLUDE PERFETTO MODULE android.input; + SELECT + id, event_id, vsync_id, window_id + FROM + android_input_event_dispatch; + """, + out=Csv(""" + "id","event_id","vsync_id","window_id" + 0,1327679296,89110,98 + 1,330184796,89110,212 + 2,330184796,89110,64 + 3,330184796,89110,82 + 4,330184796,89110,75 + 5,330184796,0,0 + 6,557261353,89110,212 + 7,557261353,89110,64 + 8,557261353,89110,82 + 9,557261353,89110,75 + 10,557261353,0,0 + 11,106022695,89110,212 + 12,106022695,89110,64 + 13,106022695,89110,82 + 14,106022695,89110,75 + 15,106022695,0,0 + 16,313395000,89110,212 + 17,313395000,89110,64 + 18,313395000,89110,82 + 19,313395000,89110,75 + 20,313395000,0,0 + 21,436499943,89110,212 + 22,436499943,89110,64 + 23,436499943,89110,82 + 24,436499943,89110,75 + 25,436499943,0,0 + 26,759309047,89110,212 + 27,759309047,0,0 + 28,894093732,89110,212 + 29,894093732,0,0 + """)) + + def test_motion_dispatch_args(self): + return DiffTestBlueprint( + trace=Path('input_event_trace.textproto'), + query=""" + INCLUDE PERFETTO MODULE android.input; + SELECT + d.id, args.key, args.display_value + FROM + android_input_event_dispatch AS d JOIN args ON d.arg_set_id = args.arg_set_id + WHERE d.event_id = 330184796 + ORDER BY d.id, args.key; + """, + out=Csv(""" + "id","key","display_value" + 1,"dispatched_pointer[0].axis_value_in_window[0].axis","0" + 1,"dispatched_pointer[0].axis_value_in_window[0].value","1936.0" + 1,"dispatched_pointer[0].axis_value_in_window[1].axis","1" + 1,"dispatched_pointer[0].axis_value_in_window[1].value","431.0" + 1,"dispatched_pointer[0].axis_value_in_window[2].axis","8" + 1,"dispatched_pointer[0].axis_value_in_window[2].value","-0.450637996196747" + 1,"dispatched_pointer[0].pointer_id","0" + 1,"dispatched_pointer[0].x_in_display","1936.0" + 1,"dispatched_pointer[0].y_in_display","431.0" + 1,"event_id","330184796" + 1,"resolved_flags","128" + 1,"vsync_id","89110" + 1,"window_id","212" + 2,"dispatched_pointer[0].axis_value_in_window[0].axis","0" + 2,"dispatched_pointer[0].axis_value_in_window[0].value","1876.36328125" + 2,"dispatched_pointer[0].axis_value_in_window[1].axis","1" + 2,"dispatched_pointer[0].axis_value_in_window[1].value","464.5458984375" + 2,"dispatched_pointer[0].axis_value_in_window[2].axis","8" + 2,"dispatched_pointer[0].axis_value_in_window[2].value","-0.450681000947952" + 2,"dispatched_pointer[0].pointer_id","0" + 2,"dispatched_pointer[0].x_in_display","1936.0" + 2,"dispatched_pointer[0].y_in_display","431.0" + 2,"event_id","330184796" + 2,"resolved_flags","131" + 2,"vsync_id","89110" + 2,"window_id","64" + 3,"dispatched_pointer[0].axis_value_in_window[0].axis","0" + 3,"dispatched_pointer[0].axis_value_in_window[0].value","1936.0" + 3,"dispatched_pointer[0].axis_value_in_window[1].axis","1" + 3,"dispatched_pointer[0].axis_value_in_window[1].value","431.0" + 3,"dispatched_pointer[0].axis_value_in_window[2].axis","8" + 3,"dispatched_pointer[0].axis_value_in_window[2].value","-0.450637996196747" + 3,"dispatched_pointer[0].pointer_id","0" + 3,"dispatched_pointer[0].x_in_display","1936.0" + 3,"dispatched_pointer[0].y_in_display","431.0" + 3,"event_id","330184796" + 3,"resolved_flags","128" + 3,"vsync_id","89110" + 3,"window_id","82" + 4,"dispatched_pointer[0].axis_value_in_window[0].axis","0" + 4,"dispatched_pointer[0].axis_value_in_window[0].value","1936.0" + 4,"dispatched_pointer[0].axis_value_in_window[1].axis","1" + 4,"dispatched_pointer[0].axis_value_in_window[1].value","431.0" + 4,"dispatched_pointer[0].axis_value_in_window[2].axis","8" + 4,"dispatched_pointer[0].axis_value_in_window[2].value","-0.450637996196747" + 4,"dispatched_pointer[0].pointer_id","0" + 4,"dispatched_pointer[0].x_in_display","1936.0" + 4,"dispatched_pointer[0].y_in_display","431.0" + 4,"event_id","330184796" + 4,"resolved_flags","128" + 4,"vsync_id","89110" + 4,"window_id","75" + 5,"dispatched_pointer[0].axis_value_in_window[0].axis","0" + 5,"dispatched_pointer[0].axis_value_in_window[0].value","1936.0" + 5,"dispatched_pointer[0].axis_value_in_window[1].axis","1" + 5,"dispatched_pointer[0].axis_value_in_window[1].value","431.0" + 5,"dispatched_pointer[0].axis_value_in_window[2].axis","8" + 5,"dispatched_pointer[0].axis_value_in_window[2].value","-0.450637996196747" + 5,"dispatched_pointer[0].pointer_id","0" + 5,"dispatched_pointer[0].x_in_display","1936.0" + 5,"dispatched_pointer[0].y_in_display","431.0" + 5,"event_id","330184796" + 5,"resolved_flags","128" + 5,"vsync_id","0" + 5,"window_id","0" + """)) + + def test_key_dispatch_args(self): + return DiffTestBlueprint( + trace=Path('input_event_trace.textproto'), + query=""" + INCLUDE PERFETTO MODULE android.input; + SELECT + d.id, args.key, args.display_value + FROM + android_input_event_dispatch AS d JOIN args ON d.arg_set_id = args.arg_set_id + WHERE d.event_id = 759309047 + ORDER BY d.id, args.key; + """, + out=Csv(""" + "id","key","display_value" + 26,"event_id","759309047" + 26,"resolved_flags","8" + 26,"vsync_id","89110" + 26,"window_id","212" + 27,"event_id","759309047" + 27,"resolved_flags","8" + 27,"vsync_id","0" + 27,"window_id","0" + """)) diff --git a/test/trace_processor/diff_tests/parser/android/tests_bugreport.py b/test/trace_processor/diff_tests/parser/android/tests_bugreport.py index 790b529848..d67867dbb2 100644 --- a/test/trace_processor/diff_tests/parser/android/tests_bugreport.py +++ b/test/trace_processor/diff_tests/parser/android/tests_bugreport.py @@ -59,3 +59,19 @@ def test_android_bugreport_dumpsys(self): WHERE service = 'color_display'; """, out=Path('android_bugreport_dumpsys_test.out')) + + def test_android_bugreport_trace_types(self): + return DiffTestBlueprint( + trace=DataPath('bugreport-crosshatch-SPB5.zip'), + query=""" + SELECT * + FROM __intrinsic_trace_file + ORDER BY id + """, + out=Csv(""" + "id","type","parent_id","name","size","trace_type" + 0,"__intrinsic_trace_file","[NULL]","[NULL]",6220586,"zip" + 1,"__intrinsic_trace_file",0,"FS/data/misc/logd/logcat.01",2169697,"android_logcat" + 2,"__intrinsic_trace_file",0,"FS/data/misc/logd/logcat",2152073,"android_logcat" + 3,"__intrinsic_trace_file",0,"bugreport-crosshatch-SPB5.210812.002-2021-08-24-23-35-40.txt",43132864,"android_dumpstate" + """)) \ 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 358fe51250..adf65a599c 100644 --- a/test/trace_processor/diff_tests/parser/android/tests_protolog.py +++ b/test/trace_processor/diff_tests/parser/android/tests_protolog.py @@ -30,6 +30,7 @@ def test_has_expected_protolog_rows(self): 0,857384100,"DEBUG","MyFirstGroup","Test message with a string (MyTestString), an int (1776), a double 8.88, and a boolean true.","A STACK TRACE" 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]" + 3,857384140,"VERBOSE","MyNonProcessedGroup","My non-processed proto message with a string (MyTestString), an int (1776), a double 8.88, and a boolean true.","[NULL]" """)) def test_handles_packet_loss(self): diff --git a/test/trace_processor/diff_tests/parser/android/tests_viewcapture.py b/test/trace_processor/diff_tests/parser/android/tests_viewcapture.py new file mode 100644 index 0000000000..e089ecb00d --- /dev/null +++ b/test/trace_processor/diff_tests/parser/android/tests_viewcapture.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 +# Copyright (C) 2024 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License a +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from python.generators.diff_tests.testing import Path +from python.generators.diff_tests.testing import Csv +from python.generators.diff_tests.testing import DiffTestBlueprint +from python.generators.diff_tests.testing import TestSuite + + +class ViewCapture(TestSuite): + + def test_has_expected_rows(self): + return DiffTestBlueprint( + trace=Path('viewcapture.textproto'), + query=""" + INCLUDE PERFETTO MODULE android.winscope.viewcapture; + SELECT + id, ts + FROM + android_viewcapture; + """, + out=Csv(""" + "id","ts" + 0,448881087865 + 1,448883575576 + """)) + + def test_has_expected_args(self): + return DiffTestBlueprint( + trace=Path('viewcapture.textproto'), + query=""" + INCLUDE PERFETTO MODULE android.winscope.viewcapture; + SELECT + args.key, args.display_value + FROM + android_viewcapture AS vc JOIN args ON vc.arg_set_id = args.arg_set_id + WHERE vc.id = 0 + ORDER BY args.key + LIMIT 10; + """, + out=Csv(""" + "key","display_value" + "package_name","com.google.android.apps.nexuslauncher" + "views[0].alpha","1.0" + "views[0].class_name","com.android.internal.policy.PhoneWindow@6cec234" + "views[0].hashcode","182652084" + "views[0].height","2400" + "views[0].parent_id","-1" + "views[0].scale_x","1.0" + "views[0].scale_y","1.0" + "views[0].view_id","NO_ID" + "views[0].width","1080" + """)) + + def test_handle_string_deinterning_errors(self): + return DiffTestBlueprint( + trace=Path('viewcapture.textproto'), + query=""" + INCLUDE PERFETTO MODULE android.winscope.viewcapture; + SELECT + args.key, args.display_value + FROM + android_viewcapture AS vc JOIN args ON vc.arg_set_id = args.arg_set_id + WHERE vc.id = 1 and args.key = 'views[1].class_name'; + """, + out=Csv(""" + "key","display_value" + "views[1].class_name","STRING DE-INTERNING ERROR" + """)) diff --git a/test/trace_processor/diff_tests/parser/android/tests_windowmanager.py b/test/trace_processor/diff_tests/parser/android/tests_windowmanager.py new file mode 100644 index 0000000000..9349b8d4e9 --- /dev/null +++ b/test/trace_processor/diff_tests/parser/android/tests_windowmanager.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +# Copyright (C) 2024 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License a +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from python.generators.diff_tests.testing import Path +from python.generators.diff_tests.testing import Csv +from python.generators.diff_tests.testing import DiffTestBlueprint +from python.generators.diff_tests.testing import TestSuite + + +class WindowManager(TestSuite): + + def test_has_expected_rows(self): + return DiffTestBlueprint( + trace=Path('windowmanager.textproto'), + query=""" + INCLUDE PERFETTO MODULE android.winscope.windowmanager; + SELECT + ts + FROM + android_windowmanager; + """, + out=Csv(""" + "ts" + 558296470731 + 558884171862 + """)) + + def test_has_expected_args(self): + return DiffTestBlueprint( + trace=Path('windowmanager.textproto'), + query=""" + INCLUDE PERFETTO MODULE android.winscope.windowmanager; + SELECT + args.key, args.display_value + FROM + android_windowmanager AS vc JOIN args ON vc.arg_set_id = args.arg_set_id + WHERE vc.id = 0 + ORDER BY args.key + LIMIT 10; + """, + out=Csv(""" + "key","display_value" + "elapsed_realtime_nanos","558296470731" + "where","trace.enable" + "window_manager_service.focused_app","com.google.android.apps.nexuslauncher/.NexusLauncherActivity" + "window_manager_service.focused_window.hash_code","160447612" + "window_manager_service.focused_window.title","com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity" + "window_manager_service.input_method_window.hash_code","217051718" + "window_manager_service.input_method_window.title","InputMethod" + "window_manager_service.policy.keyguard_delegate.interactive_state","INTERACTIVE_STATE_AWAKE" + "window_manager_service.policy.keyguard_delegate.screen_state","SCREEN_STATE_ON" + "window_manager_service.policy.keyguard_draw_complete","true" + """)) diff --git a/test/trace_processor/diff_tests/parser/android/viewcapture.textproto b/test/trace_processor/diff_tests/parser/android/viewcapture.textproto new file mode 100644 index 0000000000..7f484b8ea8 --- /dev/null +++ b/test/trace_processor/diff_tests/parser/android/viewcapture.textproto @@ -0,0 +1,128 @@ +packet { + clock_snapshot { + primary_trace_clock: BUILTIN_CLOCK_BOOTTIME + clocks { + clock_id: 6 + timestamp: 448243204726 + } + clocks { + clock_id: 2 + timestamp: 1716366701256000218 + } + clocks { + clock_id: 4 + timestamp: 448237110366 + } + clocks { + clock_id: 1 + timestamp: 1716366701262094741 + } + clocks { + clock_id: 3 + timestamp: 448243204971 + } + clocks { + clock_id: 5 + timestamp: 448243205052 + } + } + trusted_uid: 9999 + trusted_packet_sequence_id: 1 +} + +packet { + first_packet_on_sequence: true + timestamp: 448881087865 + winscope_extensions { + [perfetto.protos.WinscopeExtensionsImpl.viewcapture] { + package_name_iid: 1 + window_name_iid: 1 + views { + parent_id: -1 + hashcode: 182652084 + view_id_iid: 1 + class_name_iid: 1 + width: 1080 + height: 2400 + scale_x: 1.000000 + scale_y: 1.000000 + alpha: 1.000000 + will_not_draw: true + } + views { + id: 1 + hashcode: 130248925 + view_id_iid: 3 + class_name_iid: 2 + width: 1080 + height: 2400 + scale_x: 1.000000 + scale_y: 1.000000 + alpha: 1.000000 + will_not_draw: true + } + } + } + sequence_flags: 3 + interned_data { + viewcapture_class_name { + iid: 1 + str: "com.android.internal.policy.PhoneWindow@6cec234" + } + viewcapture_class_name { + iid: 2 + str: "com.android.internal.policy.DecorView" + } + viewcapture_package_name { + iid: 1 + str: "com.google.android.apps.nexuslauncher" + } + viewcapture_view_id { + iid: 1 + str: "NO_ID" + } + } + trusted_uid: 10230 + trusted_packet_sequence_id: 2 + trusted_pid: 2688 + previous_packet_dropped: true +} + +packet { + timestamp: 448883575576 + winscope_extensions { + [perfetto.protos.WinscopeExtensionsImpl.viewcapture] { + package_name_iid: 1 + window_name_iid: 1 + views { + parent_id: -1 + hashcode: 182652084 + view_id_iid: 1 + class_name_iid: 1 + width: 1080 + height: 2400 + scale_x: 1.000000 + scale_y: 1.000000 + alpha: 1.000000 + will_not_draw: true + } + views { + id: 1 + hashcode: 130248925 + view_id_iid: 1 + # triggers de-interning error because of unavailable iid-to-string mapping + class_name_iid: 3 + width: 1080 + height: 2400 + scale_x: 1.000000 + scale_y: 1.000000 + alpha: 1.000000 + will_not_draw: true + } + } + } + sequence_flags: 2 + trusted_uid: 10230 + trusted_packet_sequence_id: 2 + trusted_pid: 2688 +} diff --git a/test/trace_processor/diff_tests/parser/android/windowmanager.textproto b/test/trace_processor/diff_tests/parser/android/windowmanager.textproto new file mode 100644 index 0000000000..f8cd29ac63 --- /dev/null +++ b/test/trace_processor/diff_tests/parser/android/windowmanager.textproto @@ -0,0 +1,8113 @@ +packet { + clock_snapshot { + primary_trace_clock: BUILTIN_CLOCK_BOOTTIME + clocks { + clock_id: 6 + timestamp: 558295279732 + } + clocks { + clock_id: 2 + timestamp: 1719409456328000272 + } + clocks { + clock_id: 4 + timestamp: 558289385119 + } + clocks { + clock_id: 1 + timestamp: 1719409456333895007 + } + clocks { + clock_id: 3 + timestamp: 558295279935 + } + clocks { + clock_id: 5 + timestamp: 558295279976 + } + } + trusted_uid: 9999 + trusted_packet_sequence_id: 1 +} + +packet { + first_packet_on_sequence: true + timestamp: 558296470731 + winscope_extensions { + [perfetto.protos.WinscopeExtensionsImpl.windowmanager] { + elapsed_realtime_nanos: 558296470731 + where: "trace.enable" + window_manager_service { + policy { + orientation: SCREEN_ORIENTATION_UNSPECIFIED + screen_on_fully: true + keyguard_draw_complete: true + window_manager_draw_complete: true + keyguard_delegate { + screen_state: SCREEN_STATE_ON + interactive_state: INTERACTIVE_STATE_AWAKE + } + } + root_window_container { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 64646999 + user_id: -10000 + title: "WindowContainer" + } + children { + display_content { + root_display_area { + window_container { + configuration_container { + override_configuration { + font_scale: 1.000000 + locale_list: "en-US" + screen_layout: 268435810 + color_mode: 10 + touchscreen: 3 + keyboard: 1 + keyboard_hidden: 1 + hard_keyboard_hidden: 2 + navigation: 1 + navigation_hidden: 2 + ui_mode: 33 + smallest_screen_width_dp: 411 + density_dpi: 420 + window_configuration { + app_bounds { + right: 1080 + bottom: 2400 + } + windowing_mode: 1 + bounds { + right: 1080 + bottom: 2400 + } + max_bounds { + right: 1080 + bottom: 2400 + } + } + orientation: 1 + screen_width_dp: 411 + screen_height_dp: 914 + } + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 22021243 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 46739922 + name: "Display 0 name=\"Built-in Screen\"" + layerId: 43 + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 201937930 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 177197394 + name: "WindowedMagnification:0:31" + layerId: 3 + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 59047029 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 121922211 + name: "HideDisplayCutout:0:14" + layerId: 4 + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 171681708 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 188602784 + name: "OneHanded:0:14" + layerId: 5 + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 204976479 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 56924505 + name: "FullscreenMagnification:0:12" + layerId: 6 + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 233428478 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 207919390 + name: "Leaf:0:1" + layerId: 7 + } + children { + window_token { + window_container { + configuration_container { + override_configuration { + window_configuration { + windowing_mode: 1 + } + font_weight_adjustment: 2147483647 + grammatical_gender: 4294967295 + } + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 75161973 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 145786367 + name: "WallpaperWindowToken{47ae175 token=android.os.Binder@4a8d2b9}" + layerId: 56 + } + children { + window { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 235446378 + title: "com.android.systemui.wallpapers.ImageWallpaper" + } + surface_control { + hash_code: 238481356 + name: "e08a06a com.android.systemui.wallpapers.ImageWallpaper" + layerId: 57 + } + } + stack_id: 1 + attributes { + type: 2013 + width: 1080 + height: 2400 + gravity: 8388659 + format: RGBX_8888 + window_animations: 16974617 + alpha: 1.000000 + screen_brightness: -1.000000 + button_brightness: -1.000000 + user_activity_timeout: -1 + flags: 82712 + private_flags: 20 + behavior: 1 + fit_insets_sides: 15 + } + window_frames { + parent_frame { + right: 1080 + bottom: 2400 + } + display_frame { + left: -100000 + top: -100000 + right: 100000 + bottom: 100000 + } + frame { + right: 1080 + bottom: 2400 + } + compat_frame { + right: 1080 + bottom: 2400 + } + } + surface_position { + left: -54 + top: -120 + } + animator { + surface { + shown: true + } + draw_state: HAS_DRAWN + } + requested_width: 1080 + requested_height: 2400 + has_surface: true + is_ready_for_display: true + is_on_screen: true + is_visible: true + global_scale: 1.000000 + requested_visible_types: -9 + } + } + } + hash_code: 75161973 + } + } + } + name: "Leaf:0:1" + feature_id: 2 + } + } + children { + display_area { + window_container { + configuration_container { + override_configuration { + window_configuration { + windowing_mode: 1 + } + font_weight_adjustment: 2147483647 + grammatical_gender: 4294967295 + } + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 163003833 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 238842389 + name: "DefaultTaskDisplayArea" + layerId: 8 + } + children { + task { + id: 2 + root_task_id: 2 + resize_mode: 2 + fills_parent: true + bounds { + right: 1080 + bottom: 2400 + } + created_by_organizer: true + task_fragment { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 201556144 + title: "Task" + } + surface_control { + hash_code: 196946218 + name: "Task=2" + layerId: 49 + } + children { + task { + id: 3 + root_task_id: 2 + resize_mode: 2 + fills_parent: true + bounds { + right: 1080 + bottom: 2400 + } + created_by_organizer: true + task_fragment { + window_container { + configuration_container { + override_configuration { + window_configuration { + windowing_mode: 6 + } + font_weight_adjustment: 2147483647 + grammatical_gender: 4294967295 + } + full_configuration { + window_configuration { + windowing_mode: 6 + } + } + } + orientation: -2 + identifier { + hash_code: 200443489 + title: "Task" + } + surface_control { + hash_code: 9753511 + name: "Task=3" + layerId: 50 + } + } + min_width: -1 + min_height: -1 + } + } + } + children { + task { + id: 4 + root_task_id: 2 + resize_mode: 2 + bounds { + top: 2400 + right: 1080 + bottom: 3600 + } + created_by_organizer: true + task_fragment { + window_container { + configuration_container { + override_configuration { + window_configuration { + windowing_mode: 6 + bounds { + top: 2400 + right: 1080 + bottom: 3600 + } + } + font_weight_adjustment: 2147483647 + grammatical_gender: 4294967295 + } + full_configuration { + window_configuration { + windowing_mode: 6 + } + } + } + orientation: -2 + identifier { + hash_code: 172024850 + title: "Task" + } + surface_control { + hash_code: 33894484 + name: "Task=4" + layerId: 51 + } + } + min_width: -1 + min_height: -1 + } + } + } + } + min_width: -1 + min_height: -1 + } + } + } + children { + task { + id: 1 + root_task_id: 1 + resumed_activity { + hash_code: 193718205 + title: "com.google.android.apps.nexuslauncher/.NexusLauncherActivity" + } + resize_mode: 2 + fills_parent: true + bounds { + right: 1080 + bottom: 2400 + } + task_fragment { + window_container { + configuration_container { + override_configuration { + window_configuration { + activity_type: 2 + } + font_weight_adjustment: 2147483647 + grammatical_gender: 4294967295 + } + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 43094680 + title: "Task" + } + surface_control { + hash_code: 43090715 + name: "Task=1" + layerId: 47 + } + children { + task { + id: 21 + root_task_id: 1 + resumed_activity { + hash_code: 193718205 + title: "com.google.android.apps.nexuslauncher/.NexusLauncherActivity" + } + real_activity: "com.google.android.apps.nexuslauncher/.NexusLauncherActivity" + resize_mode: 2 + fills_parent: true + bounds { + right: 1080 + bottom: 2400 + } + last_non_fullscreen_bounds { + left: 276 + top: 692 + right: 804 + bottom: 1772 + } + task_fragment { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 155178243 + title: "com.google.android.apps.nexuslauncher/.NexusLauncherActivity" + } + surface_control { + hash_code: 264846520 + name: "Task=21" + layerId: 89 + } + children { + task_fragment { + window_container { + configuration_container { + override_configuration { + window_configuration { + windowing_mode: 6 + } + font_weight_adjustment: 2147483647 + grammatical_gender: 4294967295 + } + full_configuration { + window_configuration { + windowing_mode: 6 + } + } + } + orientation: 3 + identifier { + hash_code: 157300931 + user_id: -10000 + title: "TaskFragment" + } + surface_control { + hash_code: 125770385 + name: "TaskFragment{96038c3 mode=fullscreen}" + layerId: 94 + } + } + activity_type: 2 + min_width: -1 + min_height: -1 + } + } + children { + activity { + name: "com.google.android.apps.nexuslauncher/.NexusLauncherActivity" + window_token { + window_container { + configuration_container { + override_configuration { + window_configuration { + activity_type: 2 + } + font_weight_adjustment: 2147483647 + } + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -1 + visible: true + identifier { + hash_code: 193718205 + title: "com.google.android.apps.nexuslauncher/.NexusLauncherActivity" + } + surface_control { + hash_code: 140291574 + name: "ActivityRecord{b8be7bd u0 com.google.android.apps.nexuslauncher/.NexusLauncherActivity" + layerId: 90 + } + children { + window { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 160447612 + title: "com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity" + } + surface_control { + hash_code: 247150071 + name: "9903c7c com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity" + layerId: 91 + } + } + stack_id: 1 + attributes { + type: 1 + width: -1 + height: -1 + soft_input_mode: 304 + format: TRANSPARENT + window_animations: 16974589 + alpha: 1.000000 + screen_brightness: -1.000000 + button_brightness: -1.000000 + user_activity_timeout: -1 + flags: 2173763840 + private_flags: 268470848 + subtree_system_ui_visibility_flags: 1792 + behavior: 1 + } + window_frames { + parent_frame { + right: 1080 + bottom: 2400 + } + display_frame { + right: 1080 + bottom: 2400 + } + frame { + right: 1080 + bottom: 2400 + } + compat_frame { + right: 1080 + bottom: 2400 + } + } + animator { + surface { + shown: true + } + draw_state: HAS_DRAWN + } + requested_width: 1080 + requested_height: 2400 + has_surface: true + is_ready_for_display: true + is_on_screen: true + is_visible: true + global_scale: 1.000000 + requested_visible_types: -9 + } + } + } + hash_code: 193718205 + } + last_surface_showing: true + fills_parent: true + visible: true + visible_requested: true + client_visible: true + reported_drawn: true + reported_visible: true + num_interesting_windows: 1 + num_drawn_windows: 1 + all_drawn: true + state: "RESUMED" + front_of_task: true + proc_id: 2645 + enable_recents_screenshot: true + override_orientation: -1 + } + } + } + activity_type: 2 + min_width: -1 + min_height: -1 + } + } + } + } + activity_type: 2 + min_width: -1 + min_height: -1 + } + } + } + } + name: "DefaultTaskDisplayArea" + is_task_display_area: true + feature_id: 1 + is_organized: true + } + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 1889152 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 70347876 + name: "Leaf:3:12" + layerId: 9 + } + children { + window_token { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 113069007 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 23711437 + name: "WindowToken{6bd4bcf type=2038 android.os.BinderProxy@23c8fa3}" + layerId: 52 + } + children { + window { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 227146288 + title: "ShellDropTarget" + } + surface_control { + hash_code: 110998402 + name: "d89fa30 ShellDropTarget" + layerId: 53 + } + } + stack_id: 1 + attributes { + type: 2038 + width: -1 + height: -1 + soft_input_mode: 32 + format: TRANSLUCENT + alpha: 1.000000 + screen_brightness: -1.000000 + button_brightness: -1.000000 + user_activity_timeout: -1 + flags: 16777224 + private_flags: 2415919184 + behavior: 1 + fit_insets_sides: 15 + } + window_frames { + parent_frame { + right: 1080 + bottom: 2400 + } + display_frame { + right: 1080 + bottom: 2400 + } + frame { + right: 1080 + bottom: 2400 + } + compat_frame { + right: 1080 + bottom: 2400 + } + } + requested_width: 1080 + requested_height: 2400 + view_visibility: 4 + global_scale: 1.000000 + requested_visible_types: -9 + } + } + } + hash_code: 113069007 + } + } + } + name: "Leaf:3:12" + feature_id: 2 + } + } + } + name: "FullscreenMagnification:0:12" + feature_id: 5 + } + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 92116995 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 107166355 + name: "ImePlaceholder:13:14" + layerId: 10 + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 96100530 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 80407248 + name: "ImeContainer" + layerId: 11 + } + children { + window_token { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 39691830 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 5894540 + name: "WindowToken{25da636 type=2011 android.os.Binder@cdc3a0c}" + layerId: 107 + } + children { + window { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 217051718 + title: "InputMethod" + } + surface_animator { + leash { + hash_code: 204255445 + name: "Surface(name=ceff246 InputMethod)/@0x6509134 - animation-leash of insets_animation" + layerId: 110 + } + } + surface_control { + hash_code: 105943348 + name: "ceff246 InputMethod" + layerId: 108 + } + } + stack_id: 1 + attributes { + type: 2011 + width: -2 + height: -2 + gravity: 80 + soft_input_mode: 288 + format: TRANSPARENT + window_animations: 16973910 + alpha: 1.000000 + screen_brightness: -1.000000 + button_brightness: -1.000000 + user_activity_timeout: -1 + flags: 2172649736 + private_flags: 268435584 + behavior: 1 + fit_insets_types: 3 + fit_insets_sides: 7 + } + window_frames { + parent_frame { + top: 128 + right: 1080 + bottom: 2400 + } + display_frame { + top: 128 + right: 1080 + bottom: 2400 + } + frame { + left: 120 + top: 2274 + right: 960 + bottom: 2400 + } + compat_frame { + left: 120 + top: 2274 + right: 960 + bottom: 2400 + } + } + surface_position { + left: 120 + top: 128 + } + requested_width: 840 + requested_height: 126 + view_visibility: 8 + global_scale: 1.000000 + requested_visible_types: -9 + } + } + } + hash_code: 39691830 + } + } + } + name: "ImeContainer" + feature_id: 8 + } + } + } + name: "ImePlaceholder:13:14" + feature_id: 7 + } + } + } + name: "OneHanded:0:14" + feature_id: 3 + } + } + } + name: "HideDisplayCutout:0:14" + feature_id: 6 + } + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 40186557 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 181504713 + name: "OneHanded:15:15" + layerId: 12 + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 76360212 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 66553294 + name: "FullscreenMagnification:15:15" + layerId: 13 + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 238918759 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 108850415 + name: "Leaf:15:15" + layerId: 14 + } + children { + window_token { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 31410325 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 54256636 + name: "WindowToken{1df4895 type=2000 android.os.BinderProxy@290d87f}" + layerId: 74 + } + children { + window { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 208502186 + title: "StatusBar" + } + surface_animator { + leash { + hash_code: 111567082 + name: "Surface(name=c6d7daa StatusBar)/@0x25aeec6 - animation-leash of insets_animation" + layerId: 105 + } + } + surface_control { + hash_code: 39513798 + name: "c6d7daa StatusBar" + layerId: 75 + } + } + stack_id: 1 + attributes { + type: 2000 + width: -1 + height: 128 + gravity: 48 + soft_input_mode: 32 + format: TRANSLUCENT + alpha: 1.000000 + screen_brightness: -1.000000 + button_brightness: -1.000000 + user_activity_timeout: -1 + flags: 2172649480 + private_flags: 285212672 + behavior: 1 + fit_insets_sides: 15 + } + window_frames { + parent_frame { + right: 1080 + bottom: 2400 + } + display_frame { + right: 1080 + bottom: 2400 + } + frame { + right: 1080 + bottom: 128 + } + compat_frame { + right: 1080 + bottom: 128 + } + } + animator { + surface { + shown: true + } + draw_state: HAS_DRAWN + } + requested_width: 1080 + requested_height: 128 + has_surface: true + is_ready_for_display: true + is_on_screen: true + is_visible: true + global_scale: 1.000000 + requested_visible_types: -9 + } + } + } + hash_code: 31410325 + } + } + } + name: "Leaf:15:15" + feature_id: 2 + } + } + } + name: "FullscreenMagnification:15:15" + feature_id: 5 + } + } + } + name: "OneHanded:15:15" + feature_id: 3 + } + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 29878310 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 201994458 + name: "HideDisplayCutout:16:16" + layerId: 15 + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 166141825 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 232089867 + name: "OneHanded:16:16" + layerId: 16 + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 13336424 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 56706024 + name: "FullscreenMagnification:16:16" + layerId: 17 + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 215835275 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 9095681 + name: "Leaf:16:16" + layerId: 18 + } + } + name: "Leaf:16:16" + feature_id: 2 + } + } + } + name: "FullscreenMagnification:16:16" + feature_id: 5 + } + } + } + name: "OneHanded:16:16" + feature_id: 3 + } + } + } + name: "HideDisplayCutout:16:16" + feature_id: 6 + } + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 163061850 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 40534182 + name: "OneHanded:17:17" + layerId: 19 + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 81332229 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 15698663 + name: "FullscreenMagnification:17:17" + layerId: 20 + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 34695036 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 133871252 + name: "Leaf:17:17" + layerId: 21 + } + children { + window_token { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 4102877 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 64554301 + name: "WindowToken{3e9add type=2040 android.os.BinderProxy@2a17f87}" + layerId: 72 + } + children { + window { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 29718098 + title: "NotificationShade" + } + surface_control { + hash_code: 221118770 + name: "1c57652 NotificationShade" + layerId: 73 + } + } + stack_id: 1 + attributes { + type: 2040 + width: -1 + height: -1 + gravity: 48 + soft_input_mode: 32 + format: TRANSLUCENT + alpha: 1.000000 + screen_brightness: -1.000000 + button_brightness: -1.000000 + user_activity_timeout: -1 + flags: 2172911688 + private_flags: 285213184 + behavior: 2 + fit_insets_sides: 15 + } + window_frames { + parent_frame { + right: 1080 + bottom: 2400 + } + display_frame { + right: 1080 + bottom: 2400 + } + frame { + right: 1080 + bottom: 2400 + } + compat_frame { + right: 1080 + bottom: 2400 + } + } + requested_width: 1080 + requested_height: 2400 + view_visibility: 4 + global_scale: 1.000000 + requested_visible_types: -9 + } + } + } + hash_code: 4102877 + } + } + } + name: "Leaf:17:17" + feature_id: 2 + } + } + } + name: "FullscreenMagnification:17:17" + feature_id: 5 + } + } + } + name: "OneHanded:17:17" + feature_id: 3 + } + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 133189231 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 262139523 + name: "HideDisplayCutout:18:23" + layerId: 22 + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 108887374 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 236365824 + name: "OneHanded:18:23" + layerId: 23 + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 257863753 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 114669625 + name: "FullscreenMagnification:18:23" + layerId: 24 + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 191313488 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 204930686 + name: "Leaf:18:23" + layerId: 25 + } + } + name: "Leaf:18:23" + feature_id: 2 + } + } + } + name: "FullscreenMagnification:18:23" + feature_id: 5 + } + } + } + name: "OneHanded:18:23" + feature_id: 3 + } + } + } + name: "HideDisplayCutout:18:23" + feature_id: 6 + } + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 182158355 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 141094879 + name: "Leaf:24:25" + layerId: 26 + } + children { + window_token { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 6180179 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 54412332 + name: "WindowToken{5e4d53 type=2019 android.os.BinderProxy@226738d}" + layerId: 67 + } + children { + window { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 82339472 + title: "NavigationBar0" + } + surface_animator { + leash { + hash_code: 89833435 + name: "Surface(name=4e86690 NavigationBar0)/@0x89c2e87 - animation-leash of insets_animation" + layerId: 106 + } + } + surface_control { + hash_code: 144453255 + name: "4e86690 NavigationBar0" + layerId: 68 + } + } + stack_id: 1 + attributes { + type: 2019 + width: -1 + height: 126 + gravity: 80 + soft_input_mode: 32 + format: TRANSLUCENT + alpha: 1.000000 + screen_brightness: -1.000000 + button_brightness: -1.000000 + user_activity_timeout: -1 + flags: 8650792 + private_flags: 822087680 + behavior: 1 + fit_insets_sides: 15 + } + window_frames { + parent_frame { + right: 1080 + bottom: 2400 + } + display_frame { + right: 1080 + bottom: 2400 + } + frame { + top: 2274 + right: 1080 + bottom: 2400 + } + compat_frame { + top: 2274 + right: 1080 + bottom: 2400 + } + } + surface_position { + top: 2274 + } + animator { + surface { + shown: true + } + draw_state: HAS_DRAWN + } + requested_width: 1080 + requested_height: 126 + has_surface: true + is_ready_for_display: true + is_on_screen: true + is_visible: true + global_scale: 1.000000 + requested_visible_types: -9 + } + } + } + hash_code: 6180179 + } + } + children { + window_token { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 174687675 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 155223178 + name: "WindowToken{a6985bb type=2024 android.os.BinderProxy@95dcbb5}" + layerId: 70 + } + children { + window { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 256842456 + title: "EdgeBackGestureHandler0" + } + surface_control { + hash_code: 93760251 + name: "f4f1ad8 EdgeBackGestureHandler0" + layerId: 71 + } + } + stack_id: 1 + attributes { + type: 2024 + width: 276 + height: 704 + soft_input_mode: 32 + format: TRANSLUCENT + alpha: 1.000000 + screen_brightness: -1.000000 + button_brightness: -1.000000 + user_activity_timeout: -1 + flags: 16777496 + private_flags: 807403536 + behavior: 1 + fit_insets_sides: 15 + } + window_frames { + parent_frame { + right: 1080 + bottom: 2400 + } + display_frame { + top: 128 + right: 1080 + bottom: 2400 + } + frame { + left: 402 + top: 848 + right: 678 + bottom: 1552 + } + compat_frame { + left: 402 + top: 848 + right: 678 + bottom: 1552 + } + } + requested_width: -1 + requested_height: -1 + view_visibility: 8 + global_scale: 1.000000 + requested_visible_types: -9 + } + } + } + hash_code: 174687675 + } + } + children { + window_token { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 144331467 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 39941912 + name: "WindowToken{89a52cb type=2024 android.os.BinderProxy@a0da99a}" + layerId: 78 + } + children { + window { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 191637953 + title: "SecondaryHomeHandle0" + } + surface_control { + hash_code: 173618545 + name: "b6c29c1 SecondaryHomeHandle0" + layerId: 79 + } + } + stack_id: 1 + attributes { + type: 2024 + soft_input_mode: 32 + format: TRANSLUCENT + alpha: 1.000000 + screen_brightness: -1.000000 + button_brightness: -1.000000 + user_activity_timeout: -1 + flags: 553648440 + private_flags: 4160 + behavior: 1 + fit_insets_types: 518 + fit_insets_sides: 15 + } + window_frames { + parent_frame { + right: 1080 + bottom: 2400 + } + display_frame { + top: 128 + right: 1080 + bottom: 2400 + } + frame { + left: 540 + top: 1200 + right: 540 + bottom: 1200 + } + compat_frame { + left: 540 + top: 1200 + right: 540 + bottom: 1200 + } + } + requested_width: -1 + requested_height: -1 + view_visibility: 8 + global_scale: 1.000000 + requested_visible_types: -9 + } + } + } + hash_code: 144331467 + } + } + } + name: "Leaf:24:25" + feature_id: 2 + } + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 51065602 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 212492118 + name: "HideDisplayCutout:26:31" + layerId: 27 + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 2595917 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 1768407 + name: "OneHanded:26:31" + layerId: 28 + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 37224420 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 58911940 + name: "FullscreenMagnification:26:27" + layerId: 29 + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 138012535 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 108705709 + name: "Leaf:26:27" + layerId: 30 + } + } + name: "Leaf:26:27" + feature_id: 2 + } + } + } + name: "FullscreenMagnification:26:27" + feature_id: 5 + } + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 164181366 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 50666210 + name: "Leaf:28:28" + layerId: 31 + } + } + name: "Leaf:28:28" + feature_id: 2 + } + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 53678097 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 69828211 + name: "FullscreenMagnification:29:31" + layerId: 32 + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 161847352 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 170135856 + name: "Leaf:29:31" + layerId: 33 + } + } + name: "Leaf:29:31" + feature_id: 2 + } + } + } + name: "FullscreenMagnification:29:31" + feature_id: 5 + } + } + } + name: "OneHanded:26:31" + feature_id: 3 + } + } + } + name: "HideDisplayCutout:26:31" + feature_id: 6 + } + } + } + name: "WindowedMagnification:0:31" + feature_id: 4 + } + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 234888347 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 211648989 + name: "HideDisplayCutout:32:35" + layerId: 34 + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 46676138 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 163062185 + name: "OneHanded:32:32" + layerId: 35 + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 5509013 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 257819438 + name: "Leaf:32:32" + layerId: 36 + } + } + name: "Leaf:32:32" + feature_id: 2 + } + } + } + name: "OneHanded:32:32" + feature_id: 3 + } + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 10261324 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 60001999 + name: "FullscreenMagnification:33:33" + layerId: 37 + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 257984383 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 242290780 + name: "Leaf:33:33" + layerId: 38 + } + } + name: "Leaf:33:33" + feature_id: 2 + } + } + } + name: "FullscreenMagnification:33:33" + feature_id: 5 + } + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 112570526 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 206057317 + name: "OneHanded:34:35" + layerId: 39 + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 24469209 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 105587770 + name: "FullscreenMagnification:34:35" + layerId: 40 + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 111691040 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 31738091 + name: "Leaf:34:35" + layerId: 41 + } + } + name: "Leaf:34:35" + feature_id: 2 + } + } + } + name: "FullscreenMagnification:34:35" + feature_id: 5 + } + } + } + name: "OneHanded:34:35" + feature_id: 3 + } + } + } + name: "HideDisplayCutout:32:35" + feature_id: 6 + } + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 141511715 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 193380788 + name: "Leaf:36:36" + layerId: 42 + } + children { + window_token { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 34107245 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 221042248 + name: "WindowToken{2086f6d type=2024 android.os.BinderProxy@3d9ab84}" + layerId: 60 + } + children { + window { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 181983138 + title: "ScreenDecorOverlay" + } + surface_control { + hash_code: 152132833 + name: "ad8d7a2 ScreenDecorOverlay" + layerId: 61 + } + } + stack_id: 1 + attributes { + type: 2024 + width: -1 + height: -2 + gravity: 48 + soft_input_mode: 32 + format: TRANSLUCENT + alpha: 1.000000 + screen_brightness: -1.000000 + button_brightness: -1.000000 + user_activity_timeout: -1 + flags: 562037048 + private_flags: 823132240 + subtree_system_ui_visibility_flags: 256 + behavior: 1 + fit_insets_sides: 15 + } + window_frames { + parent_frame { + right: 1080 + bottom: 2400 + } + display_frame { + right: 1080 + bottom: 2400 + } + frame { + right: 1080 + bottom: 128 + } + compat_frame { + right: 1080 + bottom: 128 + } + } + animator { + surface { + shown: true + } + draw_state: HAS_DRAWN + } + requested_width: 1080 + requested_height: 128 + has_surface: true + is_ready_for_display: true + is_on_screen: true + is_visible: true + force_seamless_rotation: true + global_scale: 1.000000 + requested_visible_types: -9 + } + } + } + hash_code: 34107245 + } + } + children { + window_token { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 53747371 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 230115846 + name: "WindowToken{3341eab type=2024 android.os.BinderProxy@bc1b8fa}" + layerId: 62 + } + children { + window { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 169081479 + title: "ScreenDecorOverlayBottom" + } + surface_control { + hash_code: 104630471 + name: "a13fa87 ScreenDecorOverlayBottom" + layerId: 63 + } + } + stack_id: 1 + attributes { + type: 2024 + width: -1 + height: -2 + gravity: 80 + soft_input_mode: 32 + format: TRANSLUCENT + alpha: 1.000000 + screen_brightness: -1.000000 + button_brightness: -1.000000 + user_activity_timeout: -1 + flags: 562037048 + private_flags: 823132240 + subtree_system_ui_visibility_flags: 256 + behavior: 1 + fit_insets_sides: 15 + } + window_frames { + parent_frame { + right: 1080 + bottom: 2400 + } + display_frame { + right: 1080 + bottom: 2400 + } + frame { + top: 2326 + right: 1080 + bottom: 2400 + } + compat_frame { + top: 2326 + right: 1080 + bottom: 2400 + } + } + surface_position { + top: 2326 + } + animator { + surface { + shown: true + } + draw_state: HAS_DRAWN + } + requested_width: 1080 + requested_height: 74 + has_surface: true + is_ready_for_display: true + is_on_screen: true + is_visible: true + force_seamless_rotation: true + global_scale: 1.000000 + requested_visible_types: -9 + } + } + } + hash_code: 53747371 + } + } + } + name: "Leaf:36:36" + feature_id: 2 + } + } + } + name: "DisplayContent" + is_root_display_area: true + is_organized: true + } + dpi: 420 + display_info { + logical_width: 1080 + logical_height: 2400 + app_width: 1080 + app_height: 2400 + name: "Built-in Screen" + flags: 16515 + cutout { + insets { + top: 128 + } + bound_top { + left: 492 + right: 610 + bottom: 128 + } + side_overrides: -1 + side_overrides: -1 + side_overrides: -1 + side_overrides: -1 + } + } + display_rotation { + last_orientation: -1 + } + min_size_of_resizeable_task_dp: 220 + focused_app: "com.google.android.apps.nexuslauncher/.NexusLauncherActivity" + focused_root_task_id: 1 + resumed_activity { + hash_code: 193718205 + title: "com.google.android.apps.nexuslauncher/.NexusLauncherActivity" + } + display_ready: true + input_method_target { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 160447612 + title: "com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity" + } + surface_control { + hash_code: 247150071 + name: "9903c7c com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity" + layerId: 91 + } + } + stack_id: 1 + attributes { + type: 1 + width: -1 + height: -1 + soft_input_mode: 304 + format: TRANSPARENT + window_animations: 16974589 + alpha: 1.000000 + screen_brightness: -1.000000 + button_brightness: -1.000000 + user_activity_timeout: -1 + flags: 2173763840 + private_flags: 268470848 + subtree_system_ui_visibility_flags: 1792 + behavior: 1 + } + window_frames { + parent_frame { + right: 1080 + bottom: 2400 + } + display_frame { + right: 1080 + bottom: 2400 + } + frame { + right: 1080 + bottom: 2400 + } + compat_frame { + right: 1080 + bottom: 2400 + } + } + animator { + surface { + shown: true + } + draw_state: HAS_DRAWN + } + requested_width: 1080 + requested_height: 2400 + has_surface: true + is_ready_for_display: true + is_on_screen: true + is_visible: true + global_scale: 1.000000 + requested_visible_types: -9 + } + input_method_input_target { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 160447612 + title: "com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity" + } + surface_control { + hash_code: 247150071 + name: "9903c7c com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity" + layerId: 91 + } + } + stack_id: 1 + attributes { + type: 1 + width: -1 + height: -1 + soft_input_mode: 304 + format: TRANSPARENT + window_animations: 16974589 + alpha: 1.000000 + screen_brightness: -1.000000 + button_brightness: -1.000000 + user_activity_timeout: -1 + flags: 2173763840 + private_flags: 268470848 + subtree_system_ui_visibility_flags: 1792 + behavior: 1 + } + window_frames { + parent_frame { + right: 1080 + bottom: 2400 + } + display_frame { + right: 1080 + bottom: 2400 + } + frame { + right: 1080 + bottom: 2400 + } + compat_frame { + right: 1080 + bottom: 2400 + } + } + animator { + surface { + shown: true + } + draw_state: HAS_DRAWN + } + requested_width: 1080 + requested_height: 2400 + has_surface: true + is_ready_for_display: true + is_on_screen: true + is_visible: true + global_scale: 1.000000 + requested_visible_types: -9 + } + input_method_control_target { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 160447612 + title: "com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity" + } + surface_control { + hash_code: 247150071 + name: "9903c7c com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity" + layerId: 91 + } + } + stack_id: 1 + attributes { + type: 1 + width: -1 + height: -1 + soft_input_mode: 304 + format: TRANSPARENT + window_animations: 16974589 + alpha: 1.000000 + screen_brightness: -1.000000 + button_brightness: -1.000000 + user_activity_timeout: -1 + flags: 2173763840 + private_flags: 268470848 + subtree_system_ui_visibility_flags: 1792 + behavior: 1 + } + window_frames { + parent_frame { + right: 1080 + bottom: 2400 + } + display_frame { + right: 1080 + bottom: 2400 + } + frame { + right: 1080 + bottom: 2400 + } + compat_frame { + right: 1080 + bottom: 2400 + } + } + animator { + surface { + shown: true + } + draw_state: HAS_DRAWN + } + requested_width: 1080 + requested_height: 2400 + has_surface: true + is_ready_for_display: true + is_on_screen: true + is_visible: true + global_scale: 1.000000 + requested_visible_types: -9 + } + current_focus { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 160447612 + title: "com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity" + } + surface_control { + hash_code: 247150071 + name: "9903c7c com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity" + layerId: 91 + } + } + stack_id: 1 + attributes { + type: 1 + width: -1 + height: -1 + soft_input_mode: 304 + format: TRANSPARENT + window_animations: 16974589 + alpha: 1.000000 + screen_brightness: -1.000000 + button_brightness: -1.000000 + user_activity_timeout: -1 + flags: 2173763840 + private_flags: 268470848 + subtree_system_ui_visibility_flags: 1792 + behavior: 1 + } + window_frames { + parent_frame { + right: 1080 + bottom: 2400 + } + display_frame { + right: 1080 + bottom: 2400 + } + frame { + right: 1080 + bottom: 2400 + } + compat_frame { + right: 1080 + bottom: 2400 + } + } + animator { + surface { + shown: true + } + draw_state: HAS_DRAWN + } + requested_width: 1080 + requested_height: 2400 + has_surface: true + is_ready_for_display: true + is_on_screen: true + is_visible: true + global_scale: 1.000000 + requested_visible_types: -9 + } + insets_source_providers { + source { + frame { + right: 1080 + bottom: 128 + } + visible: true + type_number: 64 + } + frame { + right: 1080 + bottom: 128 + } + fake_control { + type_number: 64 + } + is_leash_ready_for_dispatching: true + client_visible: true + server_visible: true + source_window_state { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 208502186 + title: "StatusBar" + } + surface_animator { + leash { + hash_code: 111567082 + name: "Surface(name=c6d7daa StatusBar)/@0x25aeec6 - animation-leash of insets_animation" + layerId: 105 + } + } + surface_control { + hash_code: 39513798 + name: "c6d7daa StatusBar" + layerId: 75 + } + } + stack_id: 1 + attributes { + type: 2000 + width: -1 + height: 128 + gravity: 48 + soft_input_mode: 32 + format: TRANSLUCENT + alpha: 1.000000 + screen_brightness: -1.000000 + button_brightness: -1.000000 + user_activity_timeout: -1 + flags: 2172649480 + private_flags: 285212672 + behavior: 1 + fit_insets_sides: 15 + } + window_frames { + parent_frame { + right: 1080 + bottom: 2400 + } + display_frame { + right: 1080 + bottom: 2400 + } + frame { + right: 1080 + bottom: 128 + } + compat_frame { + right: 1080 + bottom: 128 + } + } + animator { + surface { + shown: true + } + draw_state: HAS_DRAWN + } + requested_width: 1080 + requested_height: 128 + has_surface: true + is_ready_for_display: true + is_on_screen: true + is_visible: true + global_scale: 1.000000 + requested_visible_types: -9 + } + } + insets_source_providers { + source { + frame { + right: 1080 + bottom: 160 + } + visible: true + type_number: 32 + } + frame { + right: 1080 + bottom: 160 + } + fake_control { + type_number: 32 + } + is_leash_ready_for_dispatching: true + client_visible: true + server_visible: true + source_window_state { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 208502186 + title: "StatusBar" + } + surface_animator { + leash { + hash_code: 111567082 + name: "Surface(name=c6d7daa StatusBar)/@0x25aeec6 - animation-leash of insets_animation" + layerId: 105 + } + } + surface_control { + hash_code: 39513798 + name: "c6d7daa StatusBar" + layerId: 75 + } + } + stack_id: 1 + attributes { + type: 2000 + width: -1 + height: 128 + gravity: 48 + soft_input_mode: 32 + format: TRANSLUCENT + alpha: 1.000000 + screen_brightness: -1.000000 + button_brightness: -1.000000 + user_activity_timeout: -1 + flags: 2172649480 + private_flags: 285212672 + behavior: 1 + fit_insets_sides: 15 + } + window_frames { + parent_frame { + right: 1080 + bottom: 2400 + } + display_frame { + right: 1080 + bottom: 2400 + } + frame { + right: 1080 + bottom: 128 + } + compat_frame { + right: 1080 + bottom: 128 + } + } + animator { + surface { + shown: true + } + draw_state: HAS_DRAWN + } + requested_width: 1080 + requested_height: 128 + has_surface: true + is_ready_for_display: true + is_on_screen: true + is_visible: true + global_scale: 1.000000 + requested_visible_types: -9 + } + } + insets_source_providers { + source { + frame { + right: 1080 + bottom: 128 + } + visible: true + type_number: 1 + } + frame { + right: 1080 + bottom: 128 + } + fake_control { + type_number: 1 + } + control { + leash { + hash_code: 111567082 + name: "Surface(name=c6d7daa StatusBar)/@0x25aeec6 - animation-leash of insets_animation" + layerId: 105 + } + type_number: 1 + } + control_target { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 160447612 + title: "com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity" + } + surface_control { + hash_code: 247150071 + name: "9903c7c com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity" + layerId: 91 + } + } + stack_id: 1 + attributes { + type: 1 + width: -1 + height: -1 + soft_input_mode: 304 + format: TRANSPARENT + window_animations: 16974589 + alpha: 1.000000 + screen_brightness: -1.000000 + button_brightness: -1.000000 + user_activity_timeout: -1 + flags: 2173763840 + private_flags: 268470848 + subtree_system_ui_visibility_flags: 1792 + behavior: 1 + } + window_frames { + parent_frame { + right: 1080 + bottom: 2400 + } + display_frame { + right: 1080 + bottom: 2400 + } + frame { + right: 1080 + bottom: 2400 + } + compat_frame { + right: 1080 + bottom: 2400 + } + } + animator { + surface { + shown: true + } + draw_state: HAS_DRAWN + } + requested_width: 1080 + requested_height: 2400 + has_surface: true + is_ready_for_display: true + is_on_screen: true + is_visible: true + global_scale: 1.000000 + requested_visible_types: -9 + } + captured_leash { + hash_code: 111567082 + name: "Surface(name=c6d7daa StatusBar)/@0x25aeec6 - animation-leash of insets_animation" + layerId: 105 + } + is_leash_ready_for_dispatching: true + client_visible: true + server_visible: true + controllable: true + source_window_state { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 208502186 + title: "StatusBar" + } + surface_animator { + leash { + hash_code: 111567082 + name: "Surface(name=c6d7daa StatusBar)/@0x25aeec6 - animation-leash of insets_animation" + layerId: 105 + } + } + surface_control { + hash_code: 39513798 + name: "c6d7daa StatusBar" + layerId: 75 + } + } + stack_id: 1 + attributes { + type: 2000 + width: -1 + height: 128 + gravity: 48 + soft_input_mode: 32 + format: TRANSLUCENT + alpha: 1.000000 + screen_brightness: -1.000000 + button_brightness: -1.000000 + user_activity_timeout: -1 + flags: 2172649480 + private_flags: 285212672 + behavior: 1 + fit_insets_sides: 15 + } + window_frames { + parent_frame { + right: 1080 + bottom: 2400 + } + display_frame { + right: 1080 + bottom: 2400 + } + frame { + right: 1080 + bottom: 128 + } + compat_frame { + right: 1080 + bottom: 128 + } + } + animator { + surface { + shown: true + } + draw_state: HAS_DRAWN + } + requested_width: 1080 + requested_height: 128 + has_surface: true + is_ready_for_display: true + is_on_screen: true + is_visible: true + global_scale: 1.000000 + requested_visible_types: -9 + } + } + ime_insets_source_provider { + insets_source_provider { + source { + type_number: 8 + } + fake_control { + type_number: 8 + } + control { + position { + x: 120 + y: 2274 + } + leash { + hash_code: 204255445 + name: "Surface(name=ceff246 InputMethod)/@0x6509134 - animation-leash of insets_animation" + layerId: 110 + } + type_number: 8 + } + control_target { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 160447612 + title: "com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity" + } + surface_control { + hash_code: 247150071 + name: "9903c7c com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity" + layerId: 91 + } + } + stack_id: 1 + attributes { + type: 1 + width: -1 + height: -1 + soft_input_mode: 304 + format: TRANSPARENT + window_animations: 16974589 + alpha: 1.000000 + screen_brightness: -1.000000 + button_brightness: -1.000000 + user_activity_timeout: -1 + flags: 2173763840 + private_flags: 268470848 + subtree_system_ui_visibility_flags: 1792 + behavior: 1 + } + window_frames { + parent_frame { + right: 1080 + bottom: 2400 + } + display_frame { + right: 1080 + bottom: 2400 + } + frame { + right: 1080 + bottom: 2400 + } + compat_frame { + right: 1080 + bottom: 2400 + } + } + animator { + surface { + shown: true + } + draw_state: HAS_DRAWN + } + requested_width: 1080 + requested_height: 2400 + has_surface: true + is_ready_for_display: true + is_on_screen: true + is_visible: true + global_scale: 1.000000 + requested_visible_types: -9 + } + captured_leash { + hash_code: 204255445 + name: "Surface(name=ceff246 InputMethod)/@0x6509134 - animation-leash of insets_animation" + layerId: 110 + } + is_leash_ready_for_dispatching: true + controllable: true + source_window_state { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 217051718 + title: "InputMethod" + } + surface_animator { + leash { + hash_code: 204255445 + name: "Surface(name=ceff246 InputMethod)/@0x6509134 - animation-leash of insets_animation" + layerId: 110 + } + } + surface_control { + hash_code: 105943348 + name: "ceff246 InputMethod" + layerId: 108 + } + } + stack_id: 1 + attributes { + type: 2011 + width: -2 + height: -2 + gravity: 80 + soft_input_mode: 288 + format: TRANSPARENT + window_animations: 16973910 + alpha: 1.000000 + screen_brightness: -1.000000 + button_brightness: -1.000000 + user_activity_timeout: -1 + flags: 2172649736 + private_flags: 268435584 + behavior: 1 + fit_insets_types: 3 + fit_insets_sides: 7 + } + window_frames { + parent_frame { + top: 128 + right: 1080 + bottom: 2400 + } + display_frame { + top: 128 + right: 1080 + bottom: 2400 + } + frame { + left: 120 + top: 2274 + right: 960 + bottom: 2400 + } + compat_frame { + left: 120 + top: 2274 + right: 960 + bottom: 2400 + } + } + surface_position { + left: 120 + top: 128 + } + requested_width: 840 + requested_height: 126 + view_visibility: 8 + global_scale: 1.000000 + requested_visible_types: -9 + } + } + } + insets_source_providers { + source { + frame { + left: 1002 + right: 1080 + bottom: 2400 + } + visible: true + type_number: 16 + } + frame { + left: 1002 + right: 1080 + bottom: 2400 + } + fake_control { + type_number: 16 + } + is_leash_ready_for_dispatching: true + client_visible: true + server_visible: true + source_window_state { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 82339472 + title: "NavigationBar0" + } + surface_animator { + leash { + hash_code: 89833435 + name: "Surface(name=4e86690 NavigationBar0)/@0x89c2e87 - animation-leash of insets_animation" + layerId: 106 + } + } + surface_control { + hash_code: 144453255 + name: "4e86690 NavigationBar0" + layerId: 68 + } + } + stack_id: 1 + attributes { + type: 2019 + width: -1 + height: 126 + gravity: 80 + soft_input_mode: 32 + format: TRANSLUCENT + alpha: 1.000000 + screen_brightness: -1.000000 + button_brightness: -1.000000 + user_activity_timeout: -1 + flags: 8650792 + private_flags: 822087680 + behavior: 1 + fit_insets_sides: 15 + } + window_frames { + parent_frame { + right: 1080 + bottom: 2400 + } + display_frame { + right: 1080 + bottom: 2400 + } + frame { + top: 2274 + right: 1080 + bottom: 2400 + } + compat_frame { + top: 2274 + right: 1080 + bottom: 2400 + } + } + surface_position { + top: 2274 + } + animator { + surface { + shown: true + } + draw_state: HAS_DRAWN + } + requested_width: 1080 + requested_height: 126 + has_surface: true + is_ready_for_display: true + is_on_screen: true + is_visible: true + global_scale: 1.000000 + requested_visible_types: -9 + } + } + insets_source_providers { + source { + visible: true + type_number: 64 + } + fake_control { + type_number: 64 + } + is_leash_ready_for_dispatching: true + client_visible: true + server_visible: true + source_window_state { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 82339472 + title: "NavigationBar0" + } + surface_animator { + leash { + hash_code: 89833435 + name: "Surface(name=4e86690 NavigationBar0)/@0x89c2e87 - animation-leash of insets_animation" + layerId: 106 + } + } + surface_control { + hash_code: 144453255 + name: "4e86690 NavigationBar0" + layerId: 68 + } + } + stack_id: 1 + attributes { + type: 2019 + width: -1 + height: 126 + gravity: 80 + soft_input_mode: 32 + format: TRANSLUCENT + alpha: 1.000000 + screen_brightness: -1.000000 + button_brightness: -1.000000 + user_activity_timeout: -1 + flags: 8650792 + private_flags: 822087680 + behavior: 1 + fit_insets_sides: 15 + } + window_frames { + parent_frame { + right: 1080 + bottom: 2400 + } + display_frame { + right: 1080 + bottom: 2400 + } + frame { + top: 2274 + right: 1080 + bottom: 2400 + } + compat_frame { + top: 2274 + right: 1080 + bottom: 2400 + } + } + surface_position { + top: 2274 + } + animator { + surface { + shown: true + } + draw_state: HAS_DRAWN + } + requested_width: 1080 + requested_height: 126 + has_surface: true + is_ready_for_display: true + is_on_screen: true + is_visible: true + global_scale: 1.000000 + requested_visible_types: -9 + } + } + insets_source_providers { + source { + frame { + top: 2316 + right: 1080 + bottom: 2400 + } + visible: true + type_number: 32 + } + frame { + top: 2316 + right: 1080 + bottom: 2400 + } + fake_control { + type_number: 32 + } + is_leash_ready_for_dispatching: true + client_visible: true + server_visible: true + source_window_state { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 82339472 + title: "NavigationBar0" + } + surface_animator { + leash { + hash_code: 89833435 + name: "Surface(name=4e86690 NavigationBar0)/@0x89c2e87 - animation-leash of insets_animation" + layerId: 106 + } + } + surface_control { + hash_code: 144453255 + name: "4e86690 NavigationBar0" + layerId: 68 + } + } + stack_id: 1 + attributes { + type: 2019 + width: -1 + height: 126 + gravity: 80 + soft_input_mode: 32 + format: TRANSLUCENT + alpha: 1.000000 + screen_brightness: -1.000000 + button_brightness: -1.000000 + user_activity_timeout: -1 + flags: 8650792 + private_flags: 822087680 + behavior: 1 + fit_insets_sides: 15 + } + window_frames { + parent_frame { + right: 1080 + bottom: 2400 + } + display_frame { + right: 1080 + bottom: 2400 + } + frame { + top: 2274 + right: 1080 + bottom: 2400 + } + compat_frame { + top: 2274 + right: 1080 + bottom: 2400 + } + } + surface_position { + top: 2274 + } + animator { + surface { + shown: true + } + draw_state: HAS_DRAWN + } + requested_width: 1080 + requested_height: 126 + has_surface: true + is_ready_for_display: true + is_on_screen: true + is_visible: true + global_scale: 1.000000 + requested_visible_types: -9 + } + } + insets_source_providers { + source { + frame { + right: 78 + bottom: 2400 + } + visible: true + type_number: 16 + } + frame { + right: 78 + bottom: 2400 + } + fake_control { + type_number: 16 + } + is_leash_ready_for_dispatching: true + client_visible: true + server_visible: true + source_window_state { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 82339472 + title: "NavigationBar0" + } + surface_animator { + leash { + hash_code: 89833435 + name: "Surface(name=4e86690 NavigationBar0)/@0x89c2e87 - animation-leash of insets_animation" + layerId: 106 + } + } + surface_control { + hash_code: 144453255 + name: "4e86690 NavigationBar0" + layerId: 68 + } + } + stack_id: 1 + attributes { + type: 2019 + width: -1 + height: 126 + gravity: 80 + soft_input_mode: 32 + format: TRANSLUCENT + alpha: 1.000000 + screen_brightness: -1.000000 + button_brightness: -1.000000 + user_activity_timeout: -1 + flags: 8650792 + private_flags: 822087680 + behavior: 1 + fit_insets_sides: 15 + } + window_frames { + parent_frame { + right: 1080 + bottom: 2400 + } + display_frame { + right: 1080 + bottom: 2400 + } + frame { + top: 2274 + right: 1080 + bottom: 2400 + } + compat_frame { + top: 2274 + right: 1080 + bottom: 2400 + } + } + surface_position { + top: 2274 + } + animator { + surface { + shown: true + } + draw_state: HAS_DRAWN + } + requested_width: 1080 + requested_height: 126 + has_surface: true + is_ready_for_display: true + is_on_screen: true + is_visible: true + global_scale: 1.000000 + requested_visible_types: -9 + } + } + insets_source_providers { + source { + frame { + top: 2337 + right: 1080 + bottom: 2400 + } + visible: true + type_number: 2 + } + frame { + top: 2337 + right: 1080 + bottom: 2400 + } + fake_control { + type_number: 2 + } + control { + position { + y: 2274 + } + leash { + hash_code: 89833435 + name: "Surface(name=4e86690 NavigationBar0)/@0x89c2e87 - animation-leash of insets_animation" + layerId: 106 + } + type_number: 2 + } + control_target { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 160447612 + title: "com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity" + } + surface_control { + hash_code: 247150071 + name: "9903c7c com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity" + layerId: 91 + } + } + stack_id: 1 + attributes { + type: 1 + width: -1 + height: -1 + soft_input_mode: 304 + format: TRANSPARENT + window_animations: 16974589 + alpha: 1.000000 + screen_brightness: -1.000000 + button_brightness: -1.000000 + user_activity_timeout: -1 + flags: 2173763840 + private_flags: 268470848 + subtree_system_ui_visibility_flags: 1792 + behavior: 1 + } + window_frames { + parent_frame { + right: 1080 + bottom: 2400 + } + display_frame { + right: 1080 + bottom: 2400 + } + frame { + right: 1080 + bottom: 2400 + } + compat_frame { + right: 1080 + bottom: 2400 + } + } + animator { + surface { + shown: true + } + draw_state: HAS_DRAWN + } + requested_width: 1080 + requested_height: 2400 + has_surface: true + is_ready_for_display: true + is_on_screen: true + is_visible: true + global_scale: 1.000000 + requested_visible_types: -9 + } + captured_leash { + hash_code: 89833435 + name: "Surface(name=4e86690 NavigationBar0)/@0x89c2e87 - animation-leash of insets_animation" + layerId: 106 + } + is_leash_ready_for_dispatching: true + client_visible: true + server_visible: true + controllable: true + source_window_state { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 82339472 + title: "NavigationBar0" + } + surface_animator { + leash { + hash_code: 89833435 + name: "Surface(name=4e86690 NavigationBar0)/@0x89c2e87 - animation-leash of insets_animation" + layerId: 106 + } + } + surface_control { + hash_code: 144453255 + name: "4e86690 NavigationBar0" + layerId: 68 + } + } + stack_id: 1 + attributes { + type: 2019 + width: -1 + height: 126 + gravity: 80 + soft_input_mode: 32 + format: TRANSLUCENT + alpha: 1.000000 + screen_brightness: -1.000000 + button_brightness: -1.000000 + user_activity_timeout: -1 + flags: 8650792 + private_flags: 822087680 + behavior: 1 + fit_insets_sides: 15 + } + window_frames { + parent_frame { + right: 1080 + bottom: 2400 + } + display_frame { + right: 1080 + bottom: 2400 + } + frame { + top: 2274 + right: 1080 + bottom: 2400 + } + compat_frame { + top: 2274 + right: 1080 + bottom: 2400 + } + } + surface_position { + top: 2274 + } + animator { + surface { + shown: true + } + draw_state: HAS_DRAWN + } + requested_width: 1080 + requested_height: 126 + has_surface: true + is_ready_for_display: true + is_on_screen: true + is_visible: true + global_scale: 1.000000 + requested_visible_types: -9 + } + } + } + } + } + keyguard_controller { + keyguard_per_display { + } + } + is_home_recents_component: true + } + focused_window { + hash_code: 160447612 + title: "com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity" + } + focused_app: "com.google.android.apps.nexuslauncher/.NexusLauncherActivity" + input_method_window { + hash_code: 217051718 + title: "InputMethod" + } + window_frames_valid: true + } + } + } + trusted_uid: 1000 + trusted_packet_sequence_id: 2 + trusted_pid: 1634 + previous_packet_dropped: true +} + +packet { + first_packet_on_sequence: true + timestamp: 558884171862 + winscope_extensions { + [perfetto.protos.WinscopeExtensionsImpl.windowmanager] { + elapsed_realtime_nanos: 558884171862 + where: "WindowAnimator" + window_manager_service { + policy { + orientation: SCREEN_ORIENTATION_UNSPECIFIED + screen_on_fully: true + keyguard_draw_complete: true + window_manager_draw_complete: true + keyguard_delegate { + screen_state: SCREEN_STATE_ON + interactive_state: INTERACTIVE_STATE_AWAKE + } + } + root_window_container { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 64646999 + user_id: -10000 + title: "WindowContainer" + } + children { + display_content { + root_display_area { + window_container { + configuration_container { + override_configuration { + font_scale: 1.000000 + locale_list: "en-US" + screen_layout: 268435810 + color_mode: 10 + touchscreen: 3 + keyboard: 1 + keyboard_hidden: 1 + hard_keyboard_hidden: 2 + navigation: 1 + navigation_hidden: 2 + ui_mode: 33 + smallest_screen_width_dp: 411 + density_dpi: 420 + window_configuration { + app_bounds { + right: 1080 + bottom: 2400 + } + windowing_mode: 1 + bounds { + right: 1080 + bottom: 2400 + } + max_bounds { + right: 1080 + bottom: 2400 + } + } + orientation: 1 + screen_width_dp: 411 + screen_height_dp: 914 + } + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 22021243 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 46739922 + name: "Display 0 name=\"Built-in Screen\"" + layerId: 43 + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 201937930 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 177197394 + name: "WindowedMagnification:0:31" + layerId: 3 + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 59047029 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 121922211 + name: "HideDisplayCutout:0:14" + layerId: 4 + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 171681708 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 188602784 + name: "OneHanded:0:14" + layerId: 5 + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 204976479 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 56924505 + name: "FullscreenMagnification:0:12" + layerId: 6 + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 233428478 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 207919390 + name: "Leaf:0:1" + layerId: 7 + } + children { + window_token { + window_container { + configuration_container { + override_configuration { + window_configuration { + windowing_mode: 1 + } + font_weight_adjustment: 2147483647 + grammatical_gender: 4294967295 + } + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 75161973 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 145786367 + name: "WallpaperWindowToken{47ae175 token=android.os.Binder@4a8d2b9}" + layerId: 56 + } + children { + window { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 235446378 + title: "com.android.systemui.wallpapers.ImageWallpaper" + } + surface_control { + hash_code: 238481356 + name: "e08a06a com.android.systemui.wallpapers.ImageWallpaper" + layerId: 57 + } + } + stack_id: 1 + attributes { + type: 2013 + width: 1080 + height: 2400 + gravity: 8388659 + format: RGBX_8888 + window_animations: 16974617 + alpha: 1.000000 + screen_brightness: -1.000000 + button_brightness: -1.000000 + user_activity_timeout: -1 + flags: 82712 + private_flags: 20 + behavior: 1 + fit_insets_sides: 15 + } + window_frames { + parent_frame { + right: 1080 + bottom: 2400 + } + display_frame { + left: -100000 + top: -100000 + right: 100000 + bottom: 100000 + } + frame { + right: 1080 + bottom: 2400 + } + compat_frame { + right: 1080 + bottom: 2400 + } + } + surface_position { + left: -54 + top: -119 + } + animator { + surface { + shown: true + } + draw_state: HAS_DRAWN + } + requested_width: 1080 + requested_height: 2400 + has_surface: true + is_ready_for_display: true + is_on_screen: true + is_visible: true + global_scale: 1.000000 + requested_visible_types: -9 + } + } + } + hash_code: 75161973 + } + } + } + name: "Leaf:0:1" + feature_id: 2 + } + } + children { + display_area { + window_container { + configuration_container { + override_configuration { + window_configuration { + windowing_mode: 1 + } + font_weight_adjustment: 2147483647 + grammatical_gender: 4294967295 + } + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 163003833 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 238842389 + name: "DefaultTaskDisplayArea" + layerId: 8 + } + children { + task { + id: 2 + root_task_id: 2 + resize_mode: 2 + fills_parent: true + bounds { + right: 1080 + bottom: 2400 + } + created_by_organizer: true + task_fragment { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 201556144 + title: "Task" + } + surface_control { + hash_code: 196946218 + name: "Task=2" + layerId: 49 + } + children { + task { + id: 3 + root_task_id: 2 + resize_mode: 2 + fills_parent: true + bounds { + right: 1080 + bottom: 2400 + } + created_by_organizer: true + task_fragment { + window_container { + configuration_container { + override_configuration { + window_configuration { + windowing_mode: 6 + } + font_weight_adjustment: 2147483647 + grammatical_gender: 4294967295 + } + full_configuration { + window_configuration { + windowing_mode: 6 + } + } + } + orientation: -2 + identifier { + hash_code: 200443489 + title: "Task" + } + surface_control { + hash_code: 9753511 + name: "Task=3" + layerId: 50 + } + } + min_width: -1 + min_height: -1 + } + } + } + children { + task { + id: 4 + root_task_id: 2 + resize_mode: 2 + bounds { + top: 2400 + right: 1080 + bottom: 3600 + } + created_by_organizer: true + task_fragment { + window_container { + configuration_container { + override_configuration { + window_configuration { + windowing_mode: 6 + bounds { + top: 2400 + right: 1080 + bottom: 3600 + } + } + font_weight_adjustment: 2147483647 + grammatical_gender: 4294967295 + } + full_configuration { + window_configuration { + windowing_mode: 6 + } + } + } + orientation: -2 + identifier { + hash_code: 172024850 + title: "Task" + } + surface_control { + hash_code: 33894484 + name: "Task=4" + layerId: 51 + } + } + min_width: -1 + min_height: -1 + } + } + } + } + min_width: -1 + min_height: -1 + } + } + } + children { + task { + id: 1 + root_task_id: 1 + resumed_activity { + hash_code: 193718205 + title: "com.google.android.apps.nexuslauncher/.NexusLauncherActivity" + } + resize_mode: 2 + fills_parent: true + bounds { + right: 1080 + bottom: 2400 + } + task_fragment { + window_container { + configuration_container { + override_configuration { + window_configuration { + activity_type: 2 + } + font_weight_adjustment: 2147483647 + grammatical_gender: 4294967295 + } + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 43094680 + title: "Task" + } + surface_control { + hash_code: 43090715 + name: "Task=1" + layerId: 47 + } + children { + task { + id: 21 + root_task_id: 1 + resumed_activity { + hash_code: 193718205 + title: "com.google.android.apps.nexuslauncher/.NexusLauncherActivity" + } + real_activity: "com.google.android.apps.nexuslauncher/.NexusLauncherActivity" + resize_mode: 2 + fills_parent: true + bounds { + right: 1080 + bottom: 2400 + } + last_non_fullscreen_bounds { + left: 276 + top: 692 + right: 804 + bottom: 1772 + } + task_fragment { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 155178243 + title: "com.google.android.apps.nexuslauncher/.NexusLauncherActivity" + } + surface_control { + hash_code: 264846520 + name: "Task=21" + layerId: 89 + } + children { + task_fragment { + window_container { + configuration_container { + override_configuration { + window_configuration { + windowing_mode: 6 + } + font_weight_adjustment: 2147483647 + grammatical_gender: 4294967295 + } + full_configuration { + window_configuration { + windowing_mode: 6 + } + } + } + orientation: 3 + identifier { + hash_code: 157300931 + user_id: -10000 + title: "TaskFragment" + } + surface_control { + hash_code: 125770385 + name: "TaskFragment{96038c3 mode=fullscreen}" + layerId: 94 + } + } + activity_type: 2 + min_width: -1 + min_height: -1 + } + } + children { + activity { + name: "com.google.android.apps.nexuslauncher/.NexusLauncherActivity" + window_token { + window_container { + configuration_container { + override_configuration { + window_configuration { + activity_type: 2 + } + font_weight_adjustment: 2147483647 + } + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -1 + visible: true + identifier { + hash_code: 193718205 + title: "com.google.android.apps.nexuslauncher/.NexusLauncherActivity" + } + surface_control { + hash_code: 140291574 + name: "ActivityRecord{b8be7bd u0 com.google.android.apps.nexuslauncher/.NexusLauncherActivity" + layerId: 90 + } + children { + window { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 160447612 + title: "com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity" + } + surface_control { + hash_code: 247150071 + name: "9903c7c com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity" + layerId: 91 + } + } + stack_id: 1 + attributes { + type: 1 + width: -1 + height: -1 + soft_input_mode: 304 + format: TRANSPARENT + window_animations: 16974589 + alpha: 1.000000 + screen_brightness: -1.000000 + button_brightness: -1.000000 + user_activity_timeout: -1 + flags: 2173763840 + private_flags: 268470848 + subtree_system_ui_visibility_flags: 1792 + behavior: 1 + } + window_frames { + parent_frame { + right: 1080 + bottom: 2400 + } + display_frame { + right: 1080 + bottom: 2400 + } + frame { + right: 1080 + bottom: 2400 + } + compat_frame { + right: 1080 + bottom: 2400 + } + } + animator { + surface { + shown: true + } + draw_state: HAS_DRAWN + } + requested_width: 1080 + requested_height: 2400 + has_surface: true + is_ready_for_display: true + is_on_screen: true + is_visible: true + global_scale: 1.000000 + requested_visible_types: -9 + } + } + } + hash_code: 193718205 + } + last_surface_showing: true + fills_parent: true + visible: true + visible_requested: true + client_visible: true + reported_drawn: true + reported_visible: true + num_interesting_windows: 1 + num_drawn_windows: 1 + all_drawn: true + state: "RESUMED" + front_of_task: true + proc_id: 2645 + enable_recents_screenshot: true + override_orientation: -1 + } + } + } + activity_type: 2 + min_width: -1 + min_height: -1 + } + } + } + } + activity_type: 2 + min_width: -1 + min_height: -1 + } + } + } + } + name: "DefaultTaskDisplayArea" + is_task_display_area: true + feature_id: 1 + is_organized: true + } + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 1889152 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 70347876 + name: "Leaf:3:12" + layerId: 9 + } + children { + window_token { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 113069007 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 23711437 + name: "WindowToken{6bd4bcf type=2038 android.os.BinderProxy@23c8fa3}" + layerId: 52 + } + children { + window { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 227146288 + title: "ShellDropTarget" + } + surface_control { + hash_code: 110998402 + name: "d89fa30 ShellDropTarget" + layerId: 53 + } + } + stack_id: 1 + attributes { + type: 2038 + width: -1 + height: -1 + soft_input_mode: 32 + format: TRANSLUCENT + alpha: 1.000000 + screen_brightness: -1.000000 + button_brightness: -1.000000 + user_activity_timeout: -1 + flags: 16777224 + private_flags: 2415919184 + behavior: 1 + fit_insets_sides: 15 + } + window_frames { + parent_frame { + right: 1080 + bottom: 2400 + } + display_frame { + right: 1080 + bottom: 2400 + } + frame { + right: 1080 + bottom: 2400 + } + compat_frame { + right: 1080 + bottom: 2400 + } + } + requested_width: 1080 + requested_height: 2400 + view_visibility: 4 + global_scale: 1.000000 + requested_visible_types: -9 + } + } + } + hash_code: 113069007 + } + } + } + name: "Leaf:3:12" + feature_id: 2 + } + } + } + name: "FullscreenMagnification:0:12" + feature_id: 5 + } + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 92116995 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 107166355 + name: "ImePlaceholder:13:14" + layerId: 10 + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 96100530 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 80407248 + name: "ImeContainer" + layerId: 11 + } + children { + window_token { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 39691830 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 5894540 + name: "WindowToken{25da636 type=2011 android.os.Binder@cdc3a0c}" + layerId: 107 + } + children { + window { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 217051718 + title: "InputMethod" + } + surface_animator { + leash { + hash_code: 204255445 + name: "Surface(name=ceff246 InputMethod)/@0x6509134 - animation-leash of insets_animation" + layerId: 110 + } + } + surface_control { + hash_code: 105943348 + name: "ceff246 InputMethod" + layerId: 108 + } + } + stack_id: 1 + attributes { + type: 2011 + width: -2 + height: -2 + gravity: 80 + soft_input_mode: 288 + format: TRANSPARENT + window_animations: 16973910 + alpha: 1.000000 + screen_brightness: -1.000000 + button_brightness: -1.000000 + user_activity_timeout: -1 + flags: 2172649736 + private_flags: 268435584 + behavior: 1 + fit_insets_types: 3 + fit_insets_sides: 7 + } + window_frames { + parent_frame { + top: 128 + right: 1080 + bottom: 2400 + } + display_frame { + top: 128 + right: 1080 + bottom: 2400 + } + frame { + left: 120 + top: 2274 + right: 960 + bottom: 2400 + } + compat_frame { + left: 120 + top: 2274 + right: 960 + bottom: 2400 + } + } + surface_position { + left: 120 + top: 128 + } + requested_width: 840 + requested_height: 126 + view_visibility: 8 + global_scale: 1.000000 + requested_visible_types: -9 + } + } + } + hash_code: 39691830 + } + } + } + name: "ImeContainer" + feature_id: 8 + } + } + } + name: "ImePlaceholder:13:14" + feature_id: 7 + } + } + } + name: "OneHanded:0:14" + feature_id: 3 + } + } + } + name: "HideDisplayCutout:0:14" + feature_id: 6 + } + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 40186557 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 181504713 + name: "OneHanded:15:15" + layerId: 12 + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 76360212 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 66553294 + name: "FullscreenMagnification:15:15" + layerId: 13 + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 238918759 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 108850415 + name: "Leaf:15:15" + layerId: 14 + } + children { + window_token { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 31410325 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 54256636 + name: "WindowToken{1df4895 type=2000 android.os.BinderProxy@290d87f}" + layerId: 74 + } + children { + window { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 208502186 + title: "StatusBar" + } + surface_animator { + leash { + hash_code: 111567082 + name: "Surface(name=c6d7daa StatusBar)/@0x25aeec6 - animation-leash of insets_animation" + layerId: 105 + } + } + surface_control { + hash_code: 39513798 + name: "c6d7daa StatusBar" + layerId: 75 + } + } + stack_id: 1 + attributes { + type: 2000 + width: -1 + height: 128 + gravity: 48 + soft_input_mode: 32 + format: TRANSLUCENT + alpha: 1.000000 + screen_brightness: -1.000000 + button_brightness: -1.000000 + user_activity_timeout: -1 + flags: 2172649480 + private_flags: 285212672 + behavior: 1 + fit_insets_sides: 15 + } + window_frames { + parent_frame { + right: 1080 + bottom: 2400 + } + display_frame { + right: 1080 + bottom: 2400 + } + frame { + right: 1080 + bottom: 128 + } + compat_frame { + right: 1080 + bottom: 128 + } + } + animator { + surface { + shown: true + } + draw_state: HAS_DRAWN + } + requested_width: 1080 + requested_height: 128 + has_surface: true + is_ready_for_display: true + is_on_screen: true + is_visible: true + global_scale: 1.000000 + requested_visible_types: -9 + } + } + } + hash_code: 31410325 + } + } + } + name: "Leaf:15:15" + feature_id: 2 + } + } + } + name: "FullscreenMagnification:15:15" + feature_id: 5 + } + } + } + name: "OneHanded:15:15" + feature_id: 3 + } + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 29878310 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 201994458 + name: "HideDisplayCutout:16:16" + layerId: 15 + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 166141825 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 232089867 + name: "OneHanded:16:16" + layerId: 16 + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 13336424 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 56706024 + name: "FullscreenMagnification:16:16" + layerId: 17 + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 215835275 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 9095681 + name: "Leaf:16:16" + layerId: 18 + } + } + name: "Leaf:16:16" + feature_id: 2 + } + } + } + name: "FullscreenMagnification:16:16" + feature_id: 5 + } + } + } + name: "OneHanded:16:16" + feature_id: 3 + } + } + } + name: "HideDisplayCutout:16:16" + feature_id: 6 + } + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 163061850 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 40534182 + name: "OneHanded:17:17" + layerId: 19 + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 81332229 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 15698663 + name: "FullscreenMagnification:17:17" + layerId: 20 + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 34695036 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 133871252 + name: "Leaf:17:17" + layerId: 21 + } + children { + window_token { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 4102877 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 64554301 + name: "WindowToken{3e9add type=2040 android.os.BinderProxy@2a17f87}" + layerId: 72 + } + children { + window { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 29718098 + title: "NotificationShade" + } + surface_control { + hash_code: 221118770 + name: "1c57652 NotificationShade" + layerId: 73 + } + } + stack_id: 1 + attributes { + type: 2040 + width: -1 + height: -1 + gravity: 48 + soft_input_mode: 32 + format: TRANSLUCENT + alpha: 1.000000 + screen_brightness: -1.000000 + button_brightness: -1.000000 + user_activity_timeout: -1 + flags: 2172911688 + private_flags: 285213184 + behavior: 2 + fit_insets_sides: 15 + } + window_frames { + parent_frame { + right: 1080 + bottom: 2400 + } + display_frame { + right: 1080 + bottom: 2400 + } + frame { + right: 1080 + bottom: 2400 + } + compat_frame { + right: 1080 + bottom: 2400 + } + } + requested_width: 1080 + requested_height: 2400 + view_visibility: 4 + global_scale: 1.000000 + requested_visible_types: -9 + } + } + } + hash_code: 4102877 + } + } + } + name: "Leaf:17:17" + feature_id: 2 + } + } + } + name: "FullscreenMagnification:17:17" + feature_id: 5 + } + } + } + name: "OneHanded:17:17" + feature_id: 3 + } + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 133189231 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 262139523 + name: "HideDisplayCutout:18:23" + layerId: 22 + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 108887374 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 236365824 + name: "OneHanded:18:23" + layerId: 23 + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 257863753 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 114669625 + name: "FullscreenMagnification:18:23" + layerId: 24 + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 191313488 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 204930686 + name: "Leaf:18:23" + layerId: 25 + } + } + name: "Leaf:18:23" + feature_id: 2 + } + } + } + name: "FullscreenMagnification:18:23" + feature_id: 5 + } + } + } + name: "OneHanded:18:23" + feature_id: 3 + } + } + } + name: "HideDisplayCutout:18:23" + feature_id: 6 + } + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 182158355 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 141094879 + name: "Leaf:24:25" + layerId: 26 + } + children { + window_token { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 6180179 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 54412332 + name: "WindowToken{5e4d53 type=2019 android.os.BinderProxy@226738d}" + layerId: 67 + } + children { + window { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 82339472 + title: "NavigationBar0" + } + surface_animator { + leash { + hash_code: 89833435 + name: "Surface(name=4e86690 NavigationBar0)/@0x89c2e87 - animation-leash of insets_animation" + layerId: 106 + } + } + surface_control { + hash_code: 144453255 + name: "4e86690 NavigationBar0" + layerId: 68 + } + } + stack_id: 1 + attributes { + type: 2019 + width: -1 + height: 126 + gravity: 80 + soft_input_mode: 32 + format: TRANSLUCENT + alpha: 1.000000 + screen_brightness: -1.000000 + button_brightness: -1.000000 + user_activity_timeout: -1 + flags: 8650792 + private_flags: 822087680 + behavior: 1 + fit_insets_sides: 15 + } + window_frames { + parent_frame { + right: 1080 + bottom: 2400 + } + display_frame { + right: 1080 + bottom: 2400 + } + frame { + top: 2274 + right: 1080 + bottom: 2400 + } + compat_frame { + top: 2274 + right: 1080 + bottom: 2400 + } + } + surface_position { + top: 2274 + } + animator { + surface { + shown: true + } + draw_state: HAS_DRAWN + } + requested_width: 1080 + requested_height: 126 + has_surface: true + is_ready_for_display: true + is_on_screen: true + is_visible: true + global_scale: 1.000000 + requested_visible_types: -9 + } + } + } + hash_code: 6180179 + } + } + children { + window_token { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 174687675 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 155223178 + name: "WindowToken{a6985bb type=2024 android.os.BinderProxy@95dcbb5}" + layerId: 70 + } + children { + window { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 256842456 + title: "EdgeBackGestureHandler0" + } + surface_control { + hash_code: 93760251 + name: "f4f1ad8 EdgeBackGestureHandler0" + layerId: 71 + } + } + stack_id: 1 + attributes { + type: 2024 + width: 276 + height: 704 + soft_input_mode: 32 + format: TRANSLUCENT + alpha: 1.000000 + screen_brightness: -1.000000 + button_brightness: -1.000000 + user_activity_timeout: -1 + flags: 16777496 + private_flags: 807403536 + behavior: 1 + fit_insets_sides: 15 + } + window_frames { + parent_frame { + right: 1080 + bottom: 2400 + } + display_frame { + top: 128 + right: 1080 + bottom: 2400 + } + frame { + left: 402 + top: 848 + right: 678 + bottom: 1552 + } + compat_frame { + left: 402 + top: 848 + right: 678 + bottom: 1552 + } + } + requested_width: -1 + requested_height: -1 + view_visibility: 8 + global_scale: 1.000000 + requested_visible_types: -9 + } + } + } + hash_code: 174687675 + } + } + children { + window_token { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 144331467 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 39941912 + name: "WindowToken{89a52cb type=2024 android.os.BinderProxy@a0da99a}" + layerId: 78 + } + children { + window { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 191637953 + title: "SecondaryHomeHandle0" + } + surface_control { + hash_code: 173618545 + name: "b6c29c1 SecondaryHomeHandle0" + layerId: 79 + } + } + stack_id: 1 + attributes { + type: 2024 + soft_input_mode: 32 + format: TRANSLUCENT + alpha: 1.000000 + screen_brightness: -1.000000 + button_brightness: -1.000000 + user_activity_timeout: -1 + flags: 553648440 + private_flags: 4160 + behavior: 1 + fit_insets_types: 518 + fit_insets_sides: 15 + } + window_frames { + parent_frame { + right: 1080 + bottom: 2400 + } + display_frame { + top: 128 + right: 1080 + bottom: 2400 + } + frame { + left: 540 + top: 1200 + right: 540 + bottom: 1200 + } + compat_frame { + left: 540 + top: 1200 + right: 540 + bottom: 1200 + } + } + requested_width: -1 + requested_height: -1 + view_visibility: 8 + global_scale: 1.000000 + requested_visible_types: -9 + } + } + } + hash_code: 144331467 + } + } + } + name: "Leaf:24:25" + feature_id: 2 + } + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 51065602 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 212492118 + name: "HideDisplayCutout:26:31" + layerId: 27 + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 2595917 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 1768407 + name: "OneHanded:26:31" + layerId: 28 + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 37224420 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 58911940 + name: "FullscreenMagnification:26:27" + layerId: 29 + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 138012535 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 108705709 + name: "Leaf:26:27" + layerId: 30 + } + } + name: "Leaf:26:27" + feature_id: 2 + } + } + } + name: "FullscreenMagnification:26:27" + feature_id: 5 + } + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 164181366 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 50666210 + name: "Leaf:28:28" + layerId: 31 + } + } + name: "Leaf:28:28" + feature_id: 2 + } + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 53678097 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 69828211 + name: "FullscreenMagnification:29:31" + layerId: 32 + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 161847352 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 170135856 + name: "Leaf:29:31" + layerId: 33 + } + } + name: "Leaf:29:31" + feature_id: 2 + } + } + } + name: "FullscreenMagnification:29:31" + feature_id: 5 + } + } + } + name: "OneHanded:26:31" + feature_id: 3 + } + } + } + name: "HideDisplayCutout:26:31" + feature_id: 6 + } + } + } + name: "WindowedMagnification:0:31" + feature_id: 4 + } + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 234888347 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 211648989 + name: "HideDisplayCutout:32:35" + layerId: 34 + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 46676138 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 163062185 + name: "OneHanded:32:32" + layerId: 35 + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 5509013 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 257819438 + name: "Leaf:32:32" + layerId: 36 + } + } + name: "Leaf:32:32" + feature_id: 2 + } + } + } + name: "OneHanded:32:32" + feature_id: 3 + } + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 10261324 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 60001999 + name: "FullscreenMagnification:33:33" + layerId: 37 + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 257984383 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 242290780 + name: "Leaf:33:33" + layerId: 38 + } + } + name: "Leaf:33:33" + feature_id: 2 + } + } + } + name: "FullscreenMagnification:33:33" + feature_id: 5 + } + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 112570526 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 206057317 + name: "OneHanded:34:35" + layerId: 39 + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 24469209 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 105587770 + name: "FullscreenMagnification:34:35" + layerId: 40 + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 111691040 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 31738091 + name: "Leaf:34:35" + layerId: 41 + } + } + name: "Leaf:34:35" + feature_id: 2 + } + } + } + name: "FullscreenMagnification:34:35" + feature_id: 5 + } + } + } + name: "OneHanded:34:35" + feature_id: 3 + } + } + } + name: "HideDisplayCutout:32:35" + feature_id: 6 + } + } + children { + display_area { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 141511715 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 193380788 + name: "Leaf:36:36" + layerId: 42 + } + children { + window_token { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 34107245 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 221042248 + name: "WindowToken{2086f6d type=2024 android.os.BinderProxy@3d9ab84}" + layerId: 60 + } + children { + window { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 181983138 + title: "ScreenDecorOverlay" + } + surface_control { + hash_code: 152132833 + name: "ad8d7a2 ScreenDecorOverlay" + layerId: 61 + } + } + stack_id: 1 + attributes { + type: 2024 + width: -1 + height: -2 + gravity: 48 + soft_input_mode: 32 + format: TRANSLUCENT + alpha: 1.000000 + screen_brightness: -1.000000 + button_brightness: -1.000000 + user_activity_timeout: -1 + flags: 562037048 + private_flags: 823132240 + subtree_system_ui_visibility_flags: 256 + behavior: 1 + fit_insets_sides: 15 + } + window_frames { + parent_frame { + right: 1080 + bottom: 2400 + } + display_frame { + right: 1080 + bottom: 2400 + } + frame { + right: 1080 + bottom: 128 + } + compat_frame { + right: 1080 + bottom: 128 + } + } + animator { + surface { + shown: true + } + draw_state: HAS_DRAWN + } + requested_width: 1080 + requested_height: 128 + has_surface: true + is_ready_for_display: true + is_on_screen: true + is_visible: true + force_seamless_rotation: true + global_scale: 1.000000 + requested_visible_types: -9 + } + } + } + hash_code: 34107245 + } + } + children { + window_token { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 53747371 + user_id: -10000 + title: "WindowContainer" + } + surface_control { + hash_code: 230115846 + name: "WindowToken{3341eab type=2024 android.os.BinderProxy@bc1b8fa}" + layerId: 62 + } + children { + window { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 169081479 + title: "ScreenDecorOverlayBottom" + } + surface_control { + hash_code: 104630471 + name: "a13fa87 ScreenDecorOverlayBottom" + layerId: 63 + } + } + stack_id: 1 + attributes { + type: 2024 + width: -1 + height: -2 + gravity: 80 + soft_input_mode: 32 + format: TRANSLUCENT + alpha: 1.000000 + screen_brightness: -1.000000 + button_brightness: -1.000000 + user_activity_timeout: -1 + flags: 562037048 + private_flags: 823132240 + subtree_system_ui_visibility_flags: 256 + behavior: 1 + fit_insets_sides: 15 + } + window_frames { + parent_frame { + right: 1080 + bottom: 2400 + } + display_frame { + right: 1080 + bottom: 2400 + } + frame { + top: 2326 + right: 1080 + bottom: 2400 + } + compat_frame { + top: 2326 + right: 1080 + bottom: 2400 + } + } + surface_position { + top: 2326 + } + animator { + surface { + shown: true + } + draw_state: HAS_DRAWN + } + requested_width: 1080 + requested_height: 74 + has_surface: true + is_ready_for_display: true + is_on_screen: true + is_visible: true + force_seamless_rotation: true + global_scale: 1.000000 + requested_visible_types: -9 + } + } + } + hash_code: 53747371 + } + } + } + name: "Leaf:36:36" + feature_id: 2 + } + } + } + name: "DisplayContent" + is_root_display_area: true + is_organized: true + } + dpi: 420 + display_info { + logical_width: 1080 + logical_height: 2400 + app_width: 1080 + app_height: 2400 + name: "Built-in Screen" + flags: 16515 + cutout { + insets { + top: 128 + } + bound_top { + left: 492 + right: 610 + bottom: 128 + } + side_overrides: -1 + side_overrides: -1 + side_overrides: -1 + side_overrides: -1 + } + } + display_rotation { + last_orientation: -1 + } + min_size_of_resizeable_task_dp: 220 + focused_app: "com.google.android.apps.nexuslauncher/.NexusLauncherActivity" + focused_root_task_id: 1 + resumed_activity { + hash_code: 193718205 + title: "com.google.android.apps.nexuslauncher/.NexusLauncherActivity" + } + display_ready: true + input_method_target { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 160447612 + title: "com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity" + } + surface_control { + hash_code: 247150071 + name: "9903c7c com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity" + layerId: 91 + } + } + stack_id: 1 + attributes { + type: 1 + width: -1 + height: -1 + soft_input_mode: 304 + format: TRANSPARENT + window_animations: 16974589 + alpha: 1.000000 + screen_brightness: -1.000000 + button_brightness: -1.000000 + user_activity_timeout: -1 + flags: 2173763840 + private_flags: 268470848 + subtree_system_ui_visibility_flags: 1792 + behavior: 1 + } + window_frames { + parent_frame { + right: 1080 + bottom: 2400 + } + display_frame { + right: 1080 + bottom: 2400 + } + frame { + right: 1080 + bottom: 2400 + } + compat_frame { + right: 1080 + bottom: 2400 + } + } + animator { + surface { + shown: true + } + draw_state: HAS_DRAWN + } + requested_width: 1080 + requested_height: 2400 + has_surface: true + is_ready_for_display: true + is_on_screen: true + is_visible: true + global_scale: 1.000000 + requested_visible_types: -9 + } + input_method_input_target { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 160447612 + title: "com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity" + } + surface_control { + hash_code: 247150071 + name: "9903c7c com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity" + layerId: 91 + } + } + stack_id: 1 + attributes { + type: 1 + width: -1 + height: -1 + soft_input_mode: 304 + format: TRANSPARENT + window_animations: 16974589 + alpha: 1.000000 + screen_brightness: -1.000000 + button_brightness: -1.000000 + user_activity_timeout: -1 + flags: 2173763840 + private_flags: 268470848 + subtree_system_ui_visibility_flags: 1792 + behavior: 1 + } + window_frames { + parent_frame { + right: 1080 + bottom: 2400 + } + display_frame { + right: 1080 + bottom: 2400 + } + frame { + right: 1080 + bottom: 2400 + } + compat_frame { + right: 1080 + bottom: 2400 + } + } + animator { + surface { + shown: true + } + draw_state: HAS_DRAWN + } + requested_width: 1080 + requested_height: 2400 + has_surface: true + is_ready_for_display: true + is_on_screen: true + is_visible: true + global_scale: 1.000000 + requested_visible_types: -9 + } + input_method_control_target { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 160447612 + title: "com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity" + } + surface_control { + hash_code: 247150071 + name: "9903c7c com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity" + layerId: 91 + } + } + stack_id: 1 + attributes { + type: 1 + width: -1 + height: -1 + soft_input_mode: 304 + format: TRANSPARENT + window_animations: 16974589 + alpha: 1.000000 + screen_brightness: -1.000000 + button_brightness: -1.000000 + user_activity_timeout: -1 + flags: 2173763840 + private_flags: 268470848 + subtree_system_ui_visibility_flags: 1792 + behavior: 1 + } + window_frames { + parent_frame { + right: 1080 + bottom: 2400 + } + display_frame { + right: 1080 + bottom: 2400 + } + frame { + right: 1080 + bottom: 2400 + } + compat_frame { + right: 1080 + bottom: 2400 + } + } + animator { + surface { + shown: true + } + draw_state: HAS_DRAWN + } + requested_width: 1080 + requested_height: 2400 + has_surface: true + is_ready_for_display: true + is_on_screen: true + is_visible: true + global_scale: 1.000000 + requested_visible_types: -9 + } + current_focus { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 160447612 + title: "com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity" + } + surface_control { + hash_code: 247150071 + name: "9903c7c com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity" + layerId: 91 + } + } + stack_id: 1 + attributes { + type: 1 + width: -1 + height: -1 + soft_input_mode: 304 + format: TRANSPARENT + window_animations: 16974589 + alpha: 1.000000 + screen_brightness: -1.000000 + button_brightness: -1.000000 + user_activity_timeout: -1 + flags: 2173763840 + private_flags: 268470848 + subtree_system_ui_visibility_flags: 1792 + behavior: 1 + } + window_frames { + parent_frame { + right: 1080 + bottom: 2400 + } + display_frame { + right: 1080 + bottom: 2400 + } + frame { + right: 1080 + bottom: 2400 + } + compat_frame { + right: 1080 + bottom: 2400 + } + } + animator { + surface { + shown: true + } + draw_state: HAS_DRAWN + } + requested_width: 1080 + requested_height: 2400 + has_surface: true + is_ready_for_display: true + is_on_screen: true + is_visible: true + global_scale: 1.000000 + requested_visible_types: -9 + } + insets_source_providers { + source { + frame { + right: 1080 + bottom: 128 + } + visible: true + type_number: 64 + } + frame { + right: 1080 + bottom: 128 + } + fake_control { + type_number: 64 + } + is_leash_ready_for_dispatching: true + client_visible: true + server_visible: true + source_window_state { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 208502186 + title: "StatusBar" + } + surface_animator { + leash { + hash_code: 111567082 + name: "Surface(name=c6d7daa StatusBar)/@0x25aeec6 - animation-leash of insets_animation" + layerId: 105 + } + } + surface_control { + hash_code: 39513798 + name: "c6d7daa StatusBar" + layerId: 75 + } + } + stack_id: 1 + attributes { + type: 2000 + width: -1 + height: 128 + gravity: 48 + soft_input_mode: 32 + format: TRANSLUCENT + alpha: 1.000000 + screen_brightness: -1.000000 + button_brightness: -1.000000 + user_activity_timeout: -1 + flags: 2172649480 + private_flags: 285212672 + behavior: 1 + fit_insets_sides: 15 + } + window_frames { + parent_frame { + right: 1080 + bottom: 2400 + } + display_frame { + right: 1080 + bottom: 2400 + } + frame { + right: 1080 + bottom: 128 + } + compat_frame { + right: 1080 + bottom: 128 + } + } + animator { + surface { + shown: true + } + draw_state: HAS_DRAWN + } + requested_width: 1080 + requested_height: 128 + has_surface: true + is_ready_for_display: true + is_on_screen: true + is_visible: true + global_scale: 1.000000 + requested_visible_types: -9 + } + } + insets_source_providers { + source { + frame { + right: 1080 + bottom: 160 + } + visible: true + type_number: 32 + } + frame { + right: 1080 + bottom: 160 + } + fake_control { + type_number: 32 + } + is_leash_ready_for_dispatching: true + client_visible: true + server_visible: true + source_window_state { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 208502186 + title: "StatusBar" + } + surface_animator { + leash { + hash_code: 111567082 + name: "Surface(name=c6d7daa StatusBar)/@0x25aeec6 - animation-leash of insets_animation" + layerId: 105 + } + } + surface_control { + hash_code: 39513798 + name: "c6d7daa StatusBar" + layerId: 75 + } + } + stack_id: 1 + attributes { + type: 2000 + width: -1 + height: 128 + gravity: 48 + soft_input_mode: 32 + format: TRANSLUCENT + alpha: 1.000000 + screen_brightness: -1.000000 + button_brightness: -1.000000 + user_activity_timeout: -1 + flags: 2172649480 + private_flags: 285212672 + behavior: 1 + fit_insets_sides: 15 + } + window_frames { + parent_frame { + right: 1080 + bottom: 2400 + } + display_frame { + right: 1080 + bottom: 2400 + } + frame { + right: 1080 + bottom: 128 + } + compat_frame { + right: 1080 + bottom: 128 + } + } + animator { + surface { + shown: true + } + draw_state: HAS_DRAWN + } + requested_width: 1080 + requested_height: 128 + has_surface: true + is_ready_for_display: true + is_on_screen: true + is_visible: true + global_scale: 1.000000 + requested_visible_types: -9 + } + } + insets_source_providers { + source { + frame { + right: 1080 + bottom: 128 + } + visible: true + type_number: 1 + } + frame { + right: 1080 + bottom: 128 + } + fake_control { + type_number: 1 + } + control { + leash { + hash_code: 111567082 + name: "Surface(name=c6d7daa StatusBar)/@0x25aeec6 - animation-leash of insets_animation" + layerId: 105 + } + type_number: 1 + } + control_target { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 160447612 + title: "com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity" + } + surface_control { + hash_code: 247150071 + name: "9903c7c com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity" + layerId: 91 + } + } + stack_id: 1 + attributes { + type: 1 + width: -1 + height: -1 + soft_input_mode: 304 + format: TRANSPARENT + window_animations: 16974589 + alpha: 1.000000 + screen_brightness: -1.000000 + button_brightness: -1.000000 + user_activity_timeout: -1 + flags: 2173763840 + private_flags: 268470848 + subtree_system_ui_visibility_flags: 1792 + behavior: 1 + } + window_frames { + parent_frame { + right: 1080 + bottom: 2400 + } + display_frame { + right: 1080 + bottom: 2400 + } + frame { + right: 1080 + bottom: 2400 + } + compat_frame { + right: 1080 + bottom: 2400 + } + } + animator { + surface { + shown: true + } + draw_state: HAS_DRAWN + } + requested_width: 1080 + requested_height: 2400 + has_surface: true + is_ready_for_display: true + is_on_screen: true + is_visible: true + global_scale: 1.000000 + requested_visible_types: -9 + } + captured_leash { + hash_code: 111567082 + name: "Surface(name=c6d7daa StatusBar)/@0x25aeec6 - animation-leash of insets_animation" + layerId: 105 + } + is_leash_ready_for_dispatching: true + client_visible: true + server_visible: true + controllable: true + source_window_state { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 208502186 + title: "StatusBar" + } + surface_animator { + leash { + hash_code: 111567082 + name: "Surface(name=c6d7daa StatusBar)/@0x25aeec6 - animation-leash of insets_animation" + layerId: 105 + } + } + surface_control { + hash_code: 39513798 + name: "c6d7daa StatusBar" + layerId: 75 + } + } + stack_id: 1 + attributes { + type: 2000 + width: -1 + height: 128 + gravity: 48 + soft_input_mode: 32 + format: TRANSLUCENT + alpha: 1.000000 + screen_brightness: -1.000000 + button_brightness: -1.000000 + user_activity_timeout: -1 + flags: 2172649480 + private_flags: 285212672 + behavior: 1 + fit_insets_sides: 15 + } + window_frames { + parent_frame { + right: 1080 + bottom: 2400 + } + display_frame { + right: 1080 + bottom: 2400 + } + frame { + right: 1080 + bottom: 128 + } + compat_frame { + right: 1080 + bottom: 128 + } + } + animator { + surface { + shown: true + } + draw_state: HAS_DRAWN + } + requested_width: 1080 + requested_height: 128 + has_surface: true + is_ready_for_display: true + is_on_screen: true + is_visible: true + global_scale: 1.000000 + requested_visible_types: -9 + } + } + ime_insets_source_provider { + insets_source_provider { + source { + type_number: 8 + } + fake_control { + type_number: 8 + } + control { + position { + x: 120 + y: 2274 + } + leash { + hash_code: 204255445 + name: "Surface(name=ceff246 InputMethod)/@0x6509134 - animation-leash of insets_animation" + layerId: 110 + } + type_number: 8 + } + control_target { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 160447612 + title: "com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity" + } + surface_control { + hash_code: 247150071 + name: "9903c7c com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity" + layerId: 91 + } + } + stack_id: 1 + attributes { + type: 1 + width: -1 + height: -1 + soft_input_mode: 304 + format: TRANSPARENT + window_animations: 16974589 + alpha: 1.000000 + screen_brightness: -1.000000 + button_brightness: -1.000000 + user_activity_timeout: -1 + flags: 2173763840 + private_flags: 268470848 + subtree_system_ui_visibility_flags: 1792 + behavior: 1 + } + window_frames { + parent_frame { + right: 1080 + bottom: 2400 + } + display_frame { + right: 1080 + bottom: 2400 + } + frame { + right: 1080 + bottom: 2400 + } + compat_frame { + right: 1080 + bottom: 2400 + } + } + animator { + surface { + shown: true + } + draw_state: HAS_DRAWN + } + requested_width: 1080 + requested_height: 2400 + has_surface: true + is_ready_for_display: true + is_on_screen: true + is_visible: true + global_scale: 1.000000 + requested_visible_types: -9 + } + captured_leash { + hash_code: 204255445 + name: "Surface(name=ceff246 InputMethod)/@0x6509134 - animation-leash of insets_animation" + layerId: 110 + } + is_leash_ready_for_dispatching: true + controllable: true + source_window_state { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + identifier { + hash_code: 217051718 + title: "InputMethod" + } + surface_animator { + leash { + hash_code: 204255445 + name: "Surface(name=ceff246 InputMethod)/@0x6509134 - animation-leash of insets_animation" + layerId: 110 + } + } + surface_control { + hash_code: 105943348 + name: "ceff246 InputMethod" + layerId: 108 + } + } + stack_id: 1 + attributes { + type: 2011 + width: -2 + height: -2 + gravity: 80 + soft_input_mode: 288 + format: TRANSPARENT + window_animations: 16973910 + alpha: 1.000000 + screen_brightness: -1.000000 + button_brightness: -1.000000 + user_activity_timeout: -1 + flags: 2172649736 + private_flags: 268435584 + behavior: 1 + fit_insets_types: 3 + fit_insets_sides: 7 + } + window_frames { + parent_frame { + top: 128 + right: 1080 + bottom: 2400 + } + display_frame { + top: 128 + right: 1080 + bottom: 2400 + } + frame { + left: 120 + top: 2274 + right: 960 + bottom: 2400 + } + compat_frame { + left: 120 + top: 2274 + right: 960 + bottom: 2400 + } + } + surface_position { + left: 120 + top: 128 + } + requested_width: 840 + requested_height: 126 + view_visibility: 8 + global_scale: 1.000000 + requested_visible_types: -9 + } + } + } + insets_source_providers { + source { + frame { + left: 1002 + right: 1080 + bottom: 2400 + } + visible: true + type_number: 16 + } + frame { + left: 1002 + right: 1080 + bottom: 2400 + } + fake_control { + type_number: 16 + } + is_leash_ready_for_dispatching: true + client_visible: true + server_visible: true + source_window_state { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 82339472 + title: "NavigationBar0" + } + surface_animator { + leash { + hash_code: 89833435 + name: "Surface(name=4e86690 NavigationBar0)/@0x89c2e87 - animation-leash of insets_animation" + layerId: 106 + } + } + surface_control { + hash_code: 144453255 + name: "4e86690 NavigationBar0" + layerId: 68 + } + } + stack_id: 1 + attributes { + type: 2019 + width: -1 + height: 126 + gravity: 80 + soft_input_mode: 32 + format: TRANSLUCENT + alpha: 1.000000 + screen_brightness: -1.000000 + button_brightness: -1.000000 + user_activity_timeout: -1 + flags: 8650792 + private_flags: 822087680 + behavior: 1 + fit_insets_sides: 15 + } + window_frames { + parent_frame { + right: 1080 + bottom: 2400 + } + display_frame { + right: 1080 + bottom: 2400 + } + frame { + top: 2274 + right: 1080 + bottom: 2400 + } + compat_frame { + top: 2274 + right: 1080 + bottom: 2400 + } + } + surface_position { + top: 2274 + } + animator { + surface { + shown: true + } + draw_state: HAS_DRAWN + } + requested_width: 1080 + requested_height: 126 + has_surface: true + is_ready_for_display: true + is_on_screen: true + is_visible: true + global_scale: 1.000000 + requested_visible_types: -9 + } + } + insets_source_providers { + source { + visible: true + type_number: 64 + } + fake_control { + type_number: 64 + } + is_leash_ready_for_dispatching: true + client_visible: true + server_visible: true + source_window_state { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 82339472 + title: "NavigationBar0" + } + surface_animator { + leash { + hash_code: 89833435 + name: "Surface(name=4e86690 NavigationBar0)/@0x89c2e87 - animation-leash of insets_animation" + layerId: 106 + } + } + surface_control { + hash_code: 144453255 + name: "4e86690 NavigationBar0" + layerId: 68 + } + } + stack_id: 1 + attributes { + type: 2019 + width: -1 + height: 126 + gravity: 80 + soft_input_mode: 32 + format: TRANSLUCENT + alpha: 1.000000 + screen_brightness: -1.000000 + button_brightness: -1.000000 + user_activity_timeout: -1 + flags: 8650792 + private_flags: 822087680 + behavior: 1 + fit_insets_sides: 15 + } + window_frames { + parent_frame { + right: 1080 + bottom: 2400 + } + display_frame { + right: 1080 + bottom: 2400 + } + frame { + top: 2274 + right: 1080 + bottom: 2400 + } + compat_frame { + top: 2274 + right: 1080 + bottom: 2400 + } + } + surface_position { + top: 2274 + } + animator { + surface { + shown: true + } + draw_state: HAS_DRAWN + } + requested_width: 1080 + requested_height: 126 + has_surface: true + is_ready_for_display: true + is_on_screen: true + is_visible: true + global_scale: 1.000000 + requested_visible_types: -9 + } + } + insets_source_providers { + source { + frame { + top: 2316 + right: 1080 + bottom: 2400 + } + visible: true + type_number: 32 + } + frame { + top: 2316 + right: 1080 + bottom: 2400 + } + fake_control { + type_number: 32 + } + is_leash_ready_for_dispatching: true + client_visible: true + server_visible: true + source_window_state { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 82339472 + title: "NavigationBar0" + } + surface_animator { + leash { + hash_code: 89833435 + name: "Surface(name=4e86690 NavigationBar0)/@0x89c2e87 - animation-leash of insets_animation" + layerId: 106 + } + } + surface_control { + hash_code: 144453255 + name: "4e86690 NavigationBar0" + layerId: 68 + } + } + stack_id: 1 + attributes { + type: 2019 + width: -1 + height: 126 + gravity: 80 + soft_input_mode: 32 + format: TRANSLUCENT + alpha: 1.000000 + screen_brightness: -1.000000 + button_brightness: -1.000000 + user_activity_timeout: -1 + flags: 8650792 + private_flags: 822087680 + behavior: 1 + fit_insets_sides: 15 + } + window_frames { + parent_frame { + right: 1080 + bottom: 2400 + } + display_frame { + right: 1080 + bottom: 2400 + } + frame { + top: 2274 + right: 1080 + bottom: 2400 + } + compat_frame { + top: 2274 + right: 1080 + bottom: 2400 + } + } + surface_position { + top: 2274 + } + animator { + surface { + shown: true + } + draw_state: HAS_DRAWN + } + requested_width: 1080 + requested_height: 126 + has_surface: true + is_ready_for_display: true + is_on_screen: true + is_visible: true + global_scale: 1.000000 + requested_visible_types: -9 + } + } + insets_source_providers { + source { + frame { + right: 78 + bottom: 2400 + } + visible: true + type_number: 16 + } + frame { + right: 78 + bottom: 2400 + } + fake_control { + type_number: 16 + } + is_leash_ready_for_dispatching: true + client_visible: true + server_visible: true + source_window_state { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 82339472 + title: "NavigationBar0" + } + surface_animator { + leash { + hash_code: 89833435 + name: "Surface(name=4e86690 NavigationBar0)/@0x89c2e87 - animation-leash of insets_animation" + layerId: 106 + } + } + surface_control { + hash_code: 144453255 + name: "4e86690 NavigationBar0" + layerId: 68 + } + } + stack_id: 1 + attributes { + type: 2019 + width: -1 + height: 126 + gravity: 80 + soft_input_mode: 32 + format: TRANSLUCENT + alpha: 1.000000 + screen_brightness: -1.000000 + button_brightness: -1.000000 + user_activity_timeout: -1 + flags: 8650792 + private_flags: 822087680 + behavior: 1 + fit_insets_sides: 15 + } + window_frames { + parent_frame { + right: 1080 + bottom: 2400 + } + display_frame { + right: 1080 + bottom: 2400 + } + frame { + top: 2274 + right: 1080 + bottom: 2400 + } + compat_frame { + top: 2274 + right: 1080 + bottom: 2400 + } + } + surface_position { + top: 2274 + } + animator { + surface { + shown: true + } + draw_state: HAS_DRAWN + } + requested_width: 1080 + requested_height: 126 + has_surface: true + is_ready_for_display: true + is_on_screen: true + is_visible: true + global_scale: 1.000000 + requested_visible_types: -9 + } + } + insets_source_providers { + source { + frame { + top: 2337 + right: 1080 + bottom: 2400 + } + visible: true + type_number: 2 + } + frame { + top: 2337 + right: 1080 + bottom: 2400 + } + fake_control { + type_number: 2 + } + control { + position { + y: 2274 + } + leash { + hash_code: 89833435 + name: "Surface(name=4e86690 NavigationBar0)/@0x89c2e87 - animation-leash of insets_animation" + layerId: 106 + } + type_number: 2 + } + control_target { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 160447612 + title: "com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity" + } + surface_control { + hash_code: 247150071 + name: "9903c7c com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity" + layerId: 91 + } + } + stack_id: 1 + attributes { + type: 1 + width: -1 + height: -1 + soft_input_mode: 304 + format: TRANSPARENT + window_animations: 16974589 + alpha: 1.000000 + screen_brightness: -1.000000 + button_brightness: -1.000000 + user_activity_timeout: -1 + flags: 2173763840 + private_flags: 268470848 + subtree_system_ui_visibility_flags: 1792 + behavior: 1 + } + window_frames { + parent_frame { + right: 1080 + bottom: 2400 + } + display_frame { + right: 1080 + bottom: 2400 + } + frame { + right: 1080 + bottom: 2400 + } + compat_frame { + right: 1080 + bottom: 2400 + } + } + animator { + surface { + shown: true + } + draw_state: HAS_DRAWN + } + requested_width: 1080 + requested_height: 2400 + has_surface: true + is_ready_for_display: true + is_on_screen: true + is_visible: true + global_scale: 1.000000 + requested_visible_types: -9 + } + captured_leash { + hash_code: 89833435 + name: "Surface(name=4e86690 NavigationBar0)/@0x89c2e87 - animation-leash of insets_animation" + layerId: 106 + } + is_leash_ready_for_dispatching: true + client_visible: true + server_visible: true + controllable: true + source_window_state { + window_container { + configuration_container { + full_configuration { + window_configuration { + windowing_mode: 1 + } + } + } + orientation: -2 + visible: true + identifier { + hash_code: 82339472 + title: "NavigationBar0" + } + surface_animator { + leash { + hash_code: 89833435 + name: "Surface(name=4e86690 NavigationBar0)/@0x89c2e87 - animation-leash of insets_animation" + layerId: 106 + } + } + surface_control { + hash_code: 144453255 + name: "4e86690 NavigationBar0" + layerId: 68 + } + } + stack_id: 1 + attributes { + type: 2019 + width: -1 + height: 126 + gravity: 80 + soft_input_mode: 32 + format: TRANSLUCENT + alpha: 1.000000 + screen_brightness: -1.000000 + button_brightness: -1.000000 + user_activity_timeout: -1 + flags: 8650792 + private_flags: 822087680 + behavior: 1 + fit_insets_sides: 15 + } + window_frames { + parent_frame { + right: 1080 + bottom: 2400 + } + display_frame { + right: 1080 + bottom: 2400 + } + frame { + top: 2274 + right: 1080 + bottom: 2400 + } + compat_frame { + top: 2274 + right: 1080 + bottom: 2400 + } + } + surface_position { + top: 2274 + } + animator { + surface { + shown: true + } + draw_state: HAS_DRAWN + } + requested_width: 1080 + requested_height: 126 + has_surface: true + is_ready_for_display: true + is_on_screen: true + is_visible: true + global_scale: 1.000000 + requested_visible_types: -9 + } + } + } + } + } + keyguard_controller { + keyguard_per_display { + } + } + is_home_recents_component: true + } + focused_window { + hash_code: 160447612 + title: "com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity" + } + focused_app: "com.google.android.apps.nexuslauncher/.NexusLauncherActivity" + input_method_window { + hash_code: 217051718 + title: "InputMethod" + } + window_frames_valid: true + } + } + } + trusted_uid: 1000 + trusted_packet_sequence_id: 3 + trusted_pid: 1634 + previous_packet_dropped: true +} diff --git a/test/trace_processor/diff_tests/parser/json/tests.py b/test/trace_processor/diff_tests/parser/json/tests.py index f700d34142..32993c3e0c 100644 --- a/test/trace_processor/diff_tests/parser/json/tests.py +++ b/test/trace_processor/diff_tests/parser/json/tests.py @@ -18,7 +18,7 @@ from python.generators.diff_tests.testing import TestSuite -class JsonTests(TestSuite): +class JsonParser(TestSuite): def test_string_pid_tid(self): return DiffTestBlueprint( @@ -51,3 +51,58 @@ def test_string_pid_tid(self): "ts","dur","name","process_name","thread_name" 5100,500100,"name.exec","foo","bar" """)) + + def test_args_ordered(self): + # This is a regression test for https://github.com/google/perfetto/issues/553. + # When importing from JSON, we expect arguments to be ordered. + # + # The bug was that we have sorted keys using their interned id when grouping + # args from different events (e.g. begin / end pair). This was working most + # of the time (as the key are processed in sorted order and interned ids are + # incremental). + # + # This test, however, is crafted to trigger the bug by ensuring that some + # keys are seens first (due to being seen in a different event, and therefore + # being already interned and therefore having a lower interned id. + return DiffTestBlueprint( + trace=Json(''' + [ + { + "name": "Event1", + "cat": "C", + "ph": "b", + "ts": 40000, + "pid": 1, + "id": 1, + "args": { + "02.step2": 2, + } + }, + { + "name": "Event2", + "cat": "C", + "ph": "b", + "ts": 40000, + "pid": 2, + "id": 1, + "args": { + "01.step1": 1, + "02.step2": 2, + } + }, + ]'''), + query=''' + SELECT + slice.name, + args.key, + args.int_value + FROM slice + JOIN args ON slice.arg_set_id = args.arg_set_id + ORDER BY slice.id, args.id + ''', + out=Csv(""" + "name","key","int_value" + "Event1","args.02.step2",2 + "Event2","args.01.step1",1 + "Event2","args.02.step2",2 + """)) \ No newline at end of file diff --git a/test/trace_processor/diff_tests/parser/parsing/print_systrace_unsigned.out b/test/trace_processor/diff_tests/parser/parsing/print_systrace_unsigned.out index 43dbb63fa0..c636ad7656 100644 --- a/test/trace_processor/diff_tests/parser/parsing/print_systrace_unsigned.out +++ b/test/trace_processor/diff_tests/parser/parsing/print_systrace_unsigned.out @@ -1,4 +1,4 @@ "to_ftrace(id)" " -10 ( 10) [000] .... 0.000000: kfree: call_site=16 ptr=32" " -10 ( 10) [000] .... 0.000000: kfree: call_site=18446744073709551600 ptr=18446744073709551584" -" -10 ( 10) [000] .... 0.000000: kmalloc: gfp_flags=GFP_NOWAIT call_site=18446744073709551600 ptr=18446744073709551584 bytes_alloc=32 bytes_req=16" +" -10 ( 10) [000] .... 0.000000: kmalloc: bytes_alloc=32 bytes_req=16 call_site=18446744073709551600 gfp_flags=GFP_NOWAIT ptr=18446744073709551584" diff --git a/test/trace_processor/diff_tests/parser/parsing/systrace_html.out b/test/trace_processor/diff_tests/parser/parsing/systrace_html.out index 9b736bd659..f41239c608 100644 --- a/test/trace_processor/diff_tests/parser/parsing/systrace_html.out +++ b/test/trace_processor/diff_tests/parser/parsing/systrace_html.out @@ -3022,8 +3022,8 @@ 6824713437995000,7,43000,6824713438038000,0,"R",120,0,"swapper",0 6824713438009000,6,19000,6824713438028000,630,"S",100,630,"kworker/u17:1",1134 6824713438028000,6,10000,6824713438038000,669,"S",120,669,"kworker/6:1",14833 -6824713438038000,6,283000,6824713438321000,0,"R",120,0,"swapper",0 6824713438038000,7,7000,6824713438045000,2487,"S",120,739,"UsbFfs-worker",20308 +6824713438038000,6,283000,6824713438321000,0,"R",120,0,"swapper",0 6824713438045000,7,470000,6824713438515000,0,"R",120,0,"swapper",0 6824713438321000,6,20000,6824713438341000,630,"S",100,630,"kworker/u17:1",1134 6824713438341000,6,34000,6824713438375000,0,"R",120,0,"swapper",0 @@ -3194,8 +3194,8 @@ 6824713461977000,0,38000,6824713462015000,0,"R",120,0,"swapper",0 6824713462015000,0,162000,6824713462177000,771,"S",97,493,"DispSync",676 6824713462129000,1,262000,6824713462391000,0,"R",120,0,"swapper",0 -6824713462177000,0,142000,6824713462319000,0,"R",120,0,"swapper",0 6824713462177000,2,255000,6824713462432000,773,"S",97,493,"app",678 +6824713462177000,0,142000,6824713462319000,0,"R",120,0,"swapper",0 6824713462319000,0,1790000,6824713464109000,644,"S",120,644,"ndroid.systemui",1664 6824713462391000,1,75000,6824713462466000,771,"S",97,493,"DispSync",676 6824713462432000,2,1186000,6824713463618000,0,"R",120,0,"swapper",0 diff --git a/test/trace_processor/diff_tests/parser/parsing/tests.py b/test/trace_processor/diff_tests/parser/parsing/tests.py index 91d2f6e466..b9d05e4ae3 100644 --- a/test/trace_processor/diff_tests/parser/parsing/tests.py +++ b/test/trace_processor/diff_tests/parser/parsing/tests.py @@ -338,7 +338,7 @@ def test_systrace_html(self): SELECT ts, cpu, dur, ts_end, utid, end_state, priority, upid, name, tid FROM sched JOIN thread USING(utid) - ORDER BY ts; + ORDER BY ts, sched.id; """, out=Path('systrace_html.out')) @@ -735,13 +735,13 @@ def test_cpu(self): trace=Path('cpu_info.textproto'), query=""" SELECT - id, + cpu, cluster_id, processor FROM cpu; """, out=Csv(""" - "id","cluster_id","processor" + "cpu","cluster_id","processor" 0,0,"AArch64 Processor rev 13 (aarch64)" 1,0,"AArch64 Processor rev 13 (aarch64)" 2,0,"AArch64 Processor rev 13 (aarch64)" @@ -758,8 +758,8 @@ def test_cpu_freq(self): query=""" SELECT freq, - GROUP_CONCAT(cpu_id) AS cpus - FROM cpu_freq + GROUP_CONCAT(cpu) AS cpus + FROM cpu_available_frequencies GROUP BY freq ORDER BY freq; """, @@ -846,10 +846,10 @@ def test_process_metadata_matching(self): } """), query=""" - SELECT RUN_METRIC('android/process_metadata.sql'); + INCLUDE PERFETTO MODULE android.process_metadata; SELECT upid, process_name, uid, shared_uid, package_name, version_code - FROM process_metadata_table + FROM android_process_metadata WHERE upid != 0; """, out=Csv(""" @@ -1369,14 +1369,14 @@ def test_cpu_machine_id(self): trace_modifier=TraceInjector(['cpu_info'], {'machine_id': 1001}), query=""" SELECT - id, + cpu, cluster_id, processor FROM cpu WHERE machine_id is not NULL; """, out=Csv(""" - "id","cluster_id","processor" + "cpu","cluster_id","processor" 0,0,"AArch64 Processor rev 13 (aarch64)" 1,0,"AArch64 Processor rev 13 (aarch64)" 2,0,"AArch64 Processor rev 13 (aarch64)" @@ -1394,8 +1394,9 @@ def test_cpu_freq_machine_id(self): query=""" SELECT freq, - GROUP_CONCAT(cpu_id) AS cpus - FROM cpu_freq + GROUP_CONCAT(cpu.cpu) AS cpus + FROM cpu_available_frequencies + JOIN cpu using (ucpu) WHERE machine_id is not NULL GROUP BY freq ORDER BY freq; @@ -1412,7 +1413,143 @@ def test_sched_waking_instants_compact_sched_machine_id(self): SELECT ts, thread.name, thread.tid FROM thread_state JOIN thread USING (utid) - WHERE state = 'R' AND thread_state.machine_id is not NULL + WHERE state = 'R' AND thread.machine_id is not NULL ORDER BY ts; """, out=Path('sched_waking_instants_compact_sched.out')) + + def test_cpu_capacity_present(self): + return DiffTestBlueprint( + trace=TextProto(r""" + packet { + cpu_info { + cpus { + processor: "AArch64 Processor rev 13 (aarch64)" + frequencies: 150000 + frequencies: 300000 + capacity: 256 + } + cpus { + processor: "AArch64 Processor rev 13 (aarch64)" + frequencies: 300000 + frequencies: 576000 + capacity: 1024 + } + } + } + """), + query=""" + SELECT + cpu, + cluster_id, + capacity, + processor + FROM cpu + ORDER BY cpu + """, + out=Csv(""" + "cpu","cluster_id","capacity","processor" + 0,0,256,"AArch64 Processor rev 13 (aarch64)" + 1,1,1024,"AArch64 Processor rev 13 (aarch64)" + """)) + + def test_cpu_capacity_not_present(self): + return DiffTestBlueprint( + trace=TextProto(r""" + packet { + cpu_info { + cpus { + processor: "AArch64 Processor rev 13 (aarch64)" + frequencies: 150000 + frequencies: 300000 + } + cpus { + processor: "AArch64 Processor rev 13 (aarch64)" + frequencies: 300000 + frequencies: 576000 + } + } + } + """), + query=""" + SELECT + cpu, + cluster_id, + capacity, + processor + FROM cpu + ORDER BY cpu + """, + out=Csv(""" + "cpu","cluster_id","capacity","processor" + 0,0,"[NULL]","AArch64 Processor rev 13 (aarch64)" + 1,1,"[NULL]","AArch64 Processor rev 13 (aarch64)" + """)) + + # Test that the sched slices of a VM guest is ingested and not filtered + # because timestamp is far before the tracing session. + def test_sched_remote_clock_sync(self): + return DiffTestBlueprint( + trace=DataPath('multi_machine_trace.pb'), + query=""" + SELECT ts, cpu.cpu, thread.name, thread.tid + FROM sched JOIN cpu USING(ucpu) JOIN thread USING(utid) + WHERE cpu.machine_id IS NOT NULL LIMIT 10 + """, + out=Csv(""" + "ts","cpu","name","tid" + 5230310112669,5,"kworker/5:7",32536 + 5230310132355,5,"swapper",0 + 5230310284063,4,"traced_probes",550 + 5230310421518,1,"swapper",0 + 5230310428373,3,"swapper",0 + 5230310587630,1,"rcuog/4",49 + 5230310590258,3,"logd.klogd",246 + 5230310592868,1,"swapper",0 + 5230310659357,3,"swapper",0 + 5230310671279,5,"traced_relay",25171 + """)) + + # A query that selects the sched slices of a host vcpu thread and the guest + # sched slices. If remote clock sync works, guest sched slices should not be + # far off from host vcpu slices, and the query should return both host and + # guest slices. + def test_sched_remote_clock_sync_vcpu0(self): + return DiffTestBlueprint( + trace=DataPath('multi_machine_trace.pb'), + query=""" + SELECT ts, cpu.cpu, utid, machine_id + FROM sched JOIN cpu USING (ucpu) + WHERE ucpu = 4096 + UNION + SELECT ts, cpu.cpu, utid, machine_id + FROM sched JOIN cpu USING (ucpu) + WHERE utid = ( + SELECT utid + FROM thread + WHERE name = 'crosvm_vcpu0') + LIMIT 20 + """, + out=Csv(""" + "ts","cpu","utid","machine_id" + 5230311628979,0,1,1 + 5230315517287,0,11,1 + 5230315524649,0,1,1 + 5230315676788,0,10,1 + 5230315684911,0,1,1 + 5230319663217,0,10,1 + 5230319684310,0,1,1 + 5230323692459,0,10,1 + 5230323726976,0,11,1 + 5230323764556,0,1,1 + 5230327702466,0,10,1 + 5230327736100,0,1,1 + 5230331761483,0,10,1 + 5230331800905,0,11,1 + 5230331837332,0,1,1 + 5230421799455,0,10,1 + 5230421810047,0,1,1 + 5230422048874,0,1306,"[NULL]" + 5230422153284,0,1306,"[NULL]" + 5230425693562,0,10,1 + """)) diff --git a/test/trace_processor/diff_tests/parser/parsing/tests_traced_stats.py b/test/trace_processor/diff_tests/parser/parsing/tests_traced_stats.py new file mode 100644 index 0000000000..af2f362a90 --- /dev/null +++ b/test/trace_processor/diff_tests/parser/parsing/tests_traced_stats.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python3 +# Copyright (C) 2024 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License a +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from python.generators.diff_tests.testing import Csv, TextProto +from python.generators.diff_tests.testing import DiffTestBlueprint +from python.generators.diff_tests.testing import TestSuite + + +class ParsingTracedStats(TestSuite): + # Check that `previous_packed_dropped: true` maps to + # `traced_buf_sequence_packet_loss`. + def test_sequence_packet_loss(self): + return DiffTestBlueprint( + trace=TextProto(r""" + packet { + trusted_packet_sequence_id: 2 + previous_packet_dropped: true + } + packet { + trusted_packet_sequence_id: 2 + } + packet { + trusted_packet_sequence_id: 2 + } + packet { + trusted_packet_sequence_id: 3 + previous_packet_dropped: true + } + packet { + trusted_packet_sequence_id: 3 + previous_packet_dropped: true + } + packet { + trusted_packet_sequence_id: 3 + } + packet { + trusted_packet_sequence_id: 4 + previous_packet_dropped: true + } + packet { + trusted_packet_sequence_id: 4 + previous_packet_dropped: true + } + packet { + trusted_packet_sequence_id: 4 + } + packet { + trusted_packet_sequence_id: 5 + previous_packet_dropped: true + } + packet { + trusted_packet_sequence_id: 5 + previous_packet_dropped: true + } + packet { + trusted_packet_sequence_id: 5 + } + packet { + trusted_uid: 9999 + trusted_packet_sequence_id: 1 + trace_stats { + writer_stats { + sequence_id: 2 + buffer: 0 + } + writer_stats { + sequence_id: 3 + buffer: 1 + } + writer_stats { + sequence_id: 4 + buffer: 2 + } + writer_stats { + sequence_id: 5 + buffer: 2 + } + } + } + """), + query=""" + SELECT idx, value + FROM stats + WHERE name = 'traced_buf_sequence_packet_loss' + ORDER BY idx; + """, + out=Csv(""" + "idx","value" + 0,0 + 1,1 + 2,2 + """)) diff --git a/test/trace_processor/diff_tests/parser/power/tests_power_rails.py b/test/trace_processor/diff_tests/parser/power/tests_power_rails.py index d08bbbcad4..3fa58fde84 100644 --- a/test/trace_processor/diff_tests/parser/power/tests_power_rails.py +++ b/test/trace_processor/diff_tests/parser/power/tests_power_rails.py @@ -34,7 +34,7 @@ def test_android_power_rails_counters(self): """, out=Csv(""" "power_rail_name","AVG(value)","COUNT(*)" - "power.PPVAR_VPH_PWR_ABH_uws",7390700.360656,61 + "power.PPVAR_VPH_PWR_ABH_uws",7380269.414634,41 "power.PPVAR_VPH_PWR_OLED_uws",202362991.655738,61 """)) diff --git a/test/trace_processor/diff_tests/parser/process_tracking/process_tracking_exec.py b/test/trace_processor/diff_tests/parser/process_tracking/process_tracking_exec.py index a706d7ea4e..83e10342f6 100644 --- a/test/trace_processor/diff_tests/parser/process_tracking/process_tracking_exec.py +++ b/test/trace_processor/diff_tests/parser/process_tracking/process_tracking_exec.py @@ -22,24 +22,25 @@ trace = synth_common.create_trace() -# Create a parent process which will be forked below. +# Create a parent process which will be forked below. trace.add_packet(ts=1) trace.add_process(10, 0, "parent") -# Fork off the new process and then kill it 5ns later. +# Fork the process into a child. trace.add_ftrace_packet(0) trace.add_newtask(ts=15, tid=10, new_tid=11, new_comm='child', flags=0) trace.add_sched(ts=16, prev_pid=10, next_pid=11, next_comm='child') -# Create a parent process which will be forked below. +# Scrape event for the forked process. trace.add_packet(ts=20) -trace.add_process(11, 0, "child_process") +trace.add_process(11, 10, "child_process") +# Rename of the main thread of forked process. trace.add_ftrace_packet(0) trace.add_rename( ts=25, tid=11, old_comm='child', new_comm='true_name', oom_score_adj=1000) -# Create a parent process which will be forked below. +# Scrape of the forked process. trace.add_packet(ts=30) trace.add_process(11, 10, "true_process_name") diff --git a/test/trace_processor/diff_tests/parser/process_tracking/synth_process_tracking.py b/test/trace_processor/diff_tests/parser/process_tracking/synth_process_tracking.py index 68641adf4c..19bf834d73 100644 --- a/test/trace_processor/diff_tests/parser/process_tracking/synth_process_tracking.py +++ b/test/trace_processor/diff_tests/parser/process_tracking/synth_process_tracking.py @@ -90,7 +90,7 @@ ts=28, prev_pid=32, next_pid=40, prev_comm='p3-t2', next_comm='p4-t0') trace.add_packet(ts=29) -trace.add_process(40, 0, "process_4") +trace.add_process(40, 30, "process_4") # And now, this new process starts a new thread that recycles TID=31 (previously # used as p3-t1, now becomes p4-t1). diff --git a/test/trace_processor/diff_tests/parser/sched/tests.py b/test/trace_processor/diff_tests/parser/sched/tests.py index 5568147be2..bc63ff985e 100644 --- a/test/trace_processor/diff_tests/parser/sched/tests.py +++ b/test/trace_processor/diff_tests/parser/sched/tests.py @@ -83,28 +83,29 @@ def test_sched_cpu_util_cfs_machine_id(self): t.name, c.ts, c.value, - c.machine_id + m.raw_id as raw_machine_id FROM counter AS c - LEFT JOIN + JOIN counter_track AS t ON c.track_id = t.id + JOIN machine as m on t.machine_id = m.id WHERE name GLOB "Cpu ? Cap" OR name GLOB "Cpu ? Util" OR name GLOB "Cpu ? Nr Running" ORDER BY ts; """, out=Csv(""" - "name","ts","value","machine_id" - "Cpu 6 Util",10000,1.000000,1 - "Cpu 6 Cap",10000,1004.000000,1 - "Cpu 6 Nr Running",10000,0.000000,1 - "Cpu 7 Util",11000,1.000000,1 - "Cpu 7 Cap",11000,1007.000000,1 - "Cpu 7 Nr Running",11000,0.000000,1 - "Cpu 4 Util",12000,43.000000,1 - "Cpu 4 Cap",12000,760.000000,1 - "Cpu 4 Nr Running",12000,0.000000,1 - "Cpu 5 Util",13000,125.000000,1 - "Cpu 5 Cap",13000,757.000000,1 - "Cpu 5 Nr Running",13000,1.000000,1 + "name","ts","value","raw_machine_id" + "Cpu 6 Util",10000,1.000000,1001 + "Cpu 6 Cap",10000,1004.000000,1001 + "Cpu 6 Nr Running",10000,0.000000,1001 + "Cpu 7 Util",11000,1.000000,1001 + "Cpu 7 Cap",11000,1007.000000,1001 + "Cpu 7 Nr Running",11000,0.000000,1001 + "Cpu 4 Util",12000,43.000000,1001 + "Cpu 4 Cap",12000,760.000000,1001 + "Cpu 4 Nr Running",12000,0.000000,1001 + "Cpu 5 Util",13000,125.000000,1001 + "Cpu 5 Cap",13000,757.000000,1001 + "Cpu 5 Nr Running",13000,1.000000,1001 """)) diff --git a/test/trace_processor/diff_tests/parser/simpleperf/clocks_align_test.sql b/test/trace_processor/diff_tests/parser/simpleperf/clocks_align_test.sql new file mode 100644 index 0000000000..f5a65eef32 --- /dev/null +++ b/test/trace_processor/diff_tests/parser/simpleperf/clocks_align_test.sql @@ -0,0 +1,99 @@ +-- +-- 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 VIEW perf_sample_in(ts INT, dur INT) +AS +SELECT ts, 0 AS dur FROM perf_sample; + +CREATE VIRTUAL TABLE span +USING + SPAN_JOIN(perf_sample_in, slice PARTITIONED depth); + +CREATE PERFETTO TABLE slice_stack +AS +WITH + tmp AS ( + SELECT + ts, + parent_stack_id, + string_AGG(IIF(name = 'Main loop', 'main', name), ',') + OVER ( + PARTITION BY ts + ORDER BY depth ASC + RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING + ) AS stack + FROM span + ) +SELECT ts, stack FROM tmp WHERE parent_stack_id = 0 ORDER BY TS ASC; + +CREATE PERFETTO TABLE perf_stack +AS +WITH + symbol AS ( + SELECT + id, + symbol_set_id, + replace(replace(name, '(anonymous namespace)::', ''), '()', '') AS name + FROM stack_profile_symbol + ), + symbol_agg AS ( + SELECT + id, + symbol_set_id, + string_agg(name, ',') + OVER ( + PARTITION BY symbol_set_id + ORDER BY id DESC + RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING + ) AS name + FROM symbol + WHERE name IN ('main', 'A', 'B', 'C', 'D', 'E') + ), + inline AS ( + SELECT symbol_set_id, name FROM symbol_agg WHERE id = symbol_set_id + ), + frame AS ( + SELECT f.id AS frame_id, i.name + FROM STACK_PROFILE_FRAME f, inline i + USING (symbol_set_id) + ), + child AS ( + SELECT + s.ts, + spc.id, + spc.parent_id, + name + FROM perf_sample s, stack_profile_callsite spc + ON (s.callsite_id = spc.id), + frame USING (frame_id) + UNION ALL + SELECT + child.ts, + parent.id, + parent.parent_id, + COALESCE(f.name || ',', '') || child.name AS name + FROM child, stack_profile_callsite parent + ON (child.parent_id = parent.id) + LEFT JOIN frame f + USING (frame_id) + ) +SELECT ts, name AS stack FROM child WHERE parent_id IS NULL ORDER BY ts ASC; + +SELECT COUNT(*) AS misaligned_count +FROM slice_stack s +FULL JOIN perf_stack p + USING (ts) +WHERE s.stack <> p.stack; diff --git a/test/trace_processor/diff_tests/parser/simpleperf/perf_test.sql b/test/trace_processor/diff_tests/parser/simpleperf/perf_test.sql new file mode 100644 index 0000000000..fad6c5c318 --- /dev/null +++ b/test/trace_processor/diff_tests/parser/simpleperf/perf_test.sql @@ -0,0 +1,45 @@ +WITH + counter_delta_base AS ( + SELECT + *, + LAG(value) OVER (PARTITION BY track_id ORDER BY ts) AS lag_value + FROM counter + ), + counter_delta AS ( + SELECT + id, + type, + ts, + track_id, + IIF(lag_value IS NULL, value, value - lag_value) AS delta, + arg_set_id + FROM counter_delta_base + ) +SELECT + CAST(SUM(c.delta) AS INTEGER) AS event_count, + thread.name AS command, + pid, + tid, + spm.name AS shared_object, + IIF( + spf.name IS NOT NULL AND spf.name <> '', + spf.name, + format( + '%s[+%x]', + -- substring after last / + replace(spm.name, rtrim(spm.name, replace(spm.name, '/', '')), ''), + spf.rel_pc)) AS symbol +FROM counter_delta AS c, perf_counter_track AS t +ON c.track_id = t.id, +perf_sample AS s +ON c.ts = s.ts AND t.perf_session_id = s.perf_session_id AND t.cpu = s.cpu, +thread USING (utid), +process USING (upid), +stack_profile_callsite AS spc ON (s.callsite_id = spc.id), +stack_profile_frame AS spf ON (spc.frame_id = spf.id), +stack_profile_mapping AS spm +ON (spf.mapping = spm.id) +WHERE + s.cpu IN (2, 6, 7) +GROUP BY command, pid, tid, shared_object, symbol +ORDER BY event_count DESC; diff --git a/test/trace_processor/diff_tests/parser/simpleperf/perf_with_add_counter_test.sql b/test/trace_processor/diff_tests/parser/simpleperf/perf_with_add_counter_test.sql new file mode 100644 index 0000000000..17ff666de1 --- /dev/null +++ b/test/trace_processor/diff_tests/parser/simpleperf/perf_with_add_counter_test.sql @@ -0,0 +1,67 @@ +WITH + counter_delta_base AS ( + SELECT + *, + LAG(value) OVER (PARTITION BY track_id ORDER BY ts) AS lag_value + FROM counter + ), + counter_delta AS ( + SELECT + id, + type, + ts, + track_id, + IIF(lag_value IS NULL, value, value - lag_value) AS delta, + arg_set_id + FROM counter_delta_base + ), + named_counter AS ( + SELECT + perf_session_id, + ts, + cpu, + SUM(cast_int !(IIF(t.name = 'cpu-cycles', c.delta, 0))) + AS cpu_cycles, + SUM(cast_int !(IIF(t.name = 'instructions', c.delta, 0))) + AS instructions, + SUM( + cast_int + !(IIF(t.name NOT IN ('cpu-cycles', 'instructions'), c.delta, 0))) + AS others + FROM counter_delta AS c, perf_counter_track AS t + ON (c.track_id = t.id) + GROUP BY + perf_session_id, + ts, + cpu + ) +SELECT + SUM(c.cpu_cycles) AS cpu_cycles, + SUM(c.instructions) AS instructions, + -- Additional column (not present in simpleperf output) to validate that there + -- are no other counters. + SUM(c.others) AS others, + thread.name AS command, + pid, + tid, + spm.name AS shared_object, + IIF( + spf.name IS NOT NULL AND spf.name <> '', + spf.name, + format( + '%s[+%x]', + -- substring after last / + replace(spm.name, rtrim(spm.name, replace(spm.name, '/', '')), ''), + spf.rel_pc)) AS symbol +FROM + named_counter AS c, + perf_sample AS s +USING (perf_session_id, ts, cpu), +thread USING (utid), +process USING (upid), +stack_profile_callsite AS spc ON (s.callsite_id = spc.id), +stack_profile_frame AS spf ON (spc.frame_id = spf.id), +stack_profile_mapping AS spm +ON (spf.mapping = spm.id) +GROUP BY command, pid, tid, shared_object, symbol +ORDER BY cpu_cycles DESC; diff --git a/test/trace_processor/diff_tests/parser/simpleperf/stacks_test.sql b/test/trace_processor/diff_tests/parser/simpleperf/stacks_test.sql new file mode 100644 index 0000000000..68543f1970 --- /dev/null +++ b/test/trace_processor/diff_tests/parser/simpleperf/stacks_test.sql @@ -0,0 +1,48 @@ +WITH + symbol AS ( + SELECT + id, + symbol_set_id, + replace(replace(name, '(anonymous namespace)::', ''), '()', '') AS name + FROM stack_profile_symbol + ), + symbol_agg AS ( + SELECT + id, + symbol_set_id, + string_agg(name, ',') + OVER ( + PARTITION BY symbol_set_id + ORDER BY id DESC + RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING + ) AS name + FROM symbol + WHERE name IN ('main', 'A', 'B', 'C', 'D', 'E') + ), + inline AS ( + SELECT symbol_set_id, name FROM symbol_agg WHERE id = symbol_set_id + ), + frame AS ( + SELECT f.id AS frame_id, i.name + FROM STACK_PROFILE_FRAME f, inline i + USING (symbol_set_id) + ), + child AS ( + SELECT + spc.id, + spc.parent_id, + name + FROM perf_sample s, stack_profile_callsite spc + ON (s.callsite_id = spc.id), + frame USING (frame_id) + UNION ALL + SELECT + parent.id, + parent.parent_id, + COALESCE(f.name || ',', '') || child.name AS name + FROM child, stack_profile_callsite parent + ON (child.parent_id = parent.id) + LEFT JOIN frame f + USING (frame_id) + ) +SELECT DISTINCT name FROM child WHERE parent_id IS NULL ORDER BY name \ No newline at end of file diff --git a/test/trace_processor/diff_tests/parser/simpleperf/tests.py b/test/trace_processor/diff_tests/parser/simpleperf/tests.py new file mode 100644 index 0000000000..f29cba248a --- /dev/null +++ b/test/trace_processor/diff_tests/parser/simpleperf/tests.py @@ -0,0 +1,247 @@ +#!/usr/bin/env python3 +# Copyright (C) 2024 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License a +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from python.generators.diff_tests.testing import Csv, Path, DataPath +from python.generators.diff_tests.testing import DiffTestBlueprint +from python.generators.diff_tests.testing import TestSuite + + +# These diff tests are based on the same test data simpleperf uses for its +# testing +# (https://android.googlesource.com/platform/system/extras/+/refs/heads/main/simpleperf/testdata). +# Basically we load these perf files and make sure we can get the same data we +# would get via `simpleperf report` +class Simpleperf(TestSuite): + # simpleperf report -i perf.data --print-event-count --csv --cpu 2,6,7 + def test_perf(self): + return DiffTestBlueprint( + trace=DataPath('simpleperf/perf.data'), + query=Path('perf_test.sql'), + out=Csv(''' + "event_count","command","pid","tid","shared_object","symbol" + 130707953,"t2",26130,26130,"/t2","t2[+51c]" + 126249237,"elf",26083,26083,"/elf","elf[+51c]" + 109687208,"t1",26124,26124,"/t1","t1[+523]" + 107027760,"t1",26124,26124,"/t1","t1[+51c]" + 101887409,"t2",26130,26130,"/t2","t2[+523]" + 92421568,"elf",26083,26083,"/elf","elf[+523]" + 61539363,"t1",26124,26124,"/t1","t1[+518]" + 60355129,"elf",26083,26083,"/elf","elf[+513]" + 54840659,"t1",26124,26124,"/t1","t1[+4ed]" + 52233968,"elf",26083,26083,"/elf","elf[+4ed]" + 50833094,"t1",26124,26124,"/t1","t1[+4f7]" + 50746374,"t2",26130,26130,"/t2","t2[+4ed]" + 49185691,"elf",26083,26083,"/elf","elf[+4f7]" + 47520901,"t2",26130,26130,"/t2","t2[+513]" + 45979652,"elf",26083,26083,"/elf","elf[+518]" + 44834371,"t2",26130,26130,"/t2","t2[+4f7]" + 42928068,"t2",26130,26130,"/t2","t2[+518]" + 39608138,"t1",26124,26124,"/t1","t1[+513]" + 1390415,"t1",26124,26124,"/t1","t1[+4fa]" + 1390305,"t2",26130,26130,"/t2","t2[+4fa]" + 1390173,"elf",26083,26083,"/elf","elf[+500]" + 1389030,"t2",26130,26130,"/t2","t2[+500]" + 693786,"t2",26130,26130,"/lib/modules/3.13.0-76-generic/kernel/drivers/ata/pata_acpi.ko","pata_acpi.ko[+ffffffffa05c4da4]" + ''')) + + def test_perf_tracks(self): + return DiffTestBlueprint( + trace=DataPath('simpleperf/perf.data'), + query=''' + SELECT + name, + unit, + description, + cpu, + is_timebase + FROM perf_counter_track + ORDER BY perf_session_id, name, cpu; + ''', + out=Csv(''' + "name","unit","description","cpu","is_timebase" + "","","",2,1 + "","","",6,1 + "","","",7,1 + "","","",16,1 + ''')) + + def test_perf_with_add_counter_tracks(self): + return DiffTestBlueprint( + trace=DataPath('simpleperf/perf_with_add_counter.data'), + query=''' + SELECT + name, + unit, + description, + cpu, + is_timebase + FROM perf_counter_track + ORDER BY perf_session_id, name, cpu; + ''', + out=Csv(''' + "name","unit","description","cpu","is_timebase" + "cpu-cycles","","",40,1 + "instructions","","",40,0 + ''')) + + # simpleperf report -i perf.data --print-event-count --csv + # The thread name in this trace changes over time. simpleperf shows samples + # with the old and new name. Perfetto does not support threads changing names, + # it only keeps the last name, thus there is a slight mismatch in the outputs. + def test_perf_with_add_counter(self): + return DiffTestBlueprint( + trace=DataPath('simpleperf/perf_with_add_counter.data'), + query=Path('perf_with_add_counter_test.sql'), + out=Csv(''' + "cpu_cycles","instructions","others","command","pid","tid","shared_object","symbol" + 1011567,1188389,0,"sleep",689664,689664,"[kernel.kallsyms]","[kernel.kallsyms][+ffffffffa8cc9d30]" + 219490,233619,0,"sleep",689664,689664,"[kernel.kallsyms]","[kernel.kallsyms][+ffffffffa8e498c6]" + 191017,157031,0,"sleep",689664,689664,"[kernel.kallsyms]","[kernel.kallsyms][+ffffffffa94d0901]" + 175099,140443,0,"sleep",689664,689664,"/lib/x86_64-linux-gnu/libc-2.32.so","_dl_addr" + 152310,130151,0,"sleep",689664,689664,"[kernel.kallsyms]","[kernel.kallsyms][+ffffffffa8e30c70]" + 122439,87058,0,"sleep",689664,689664,"[kernel.kallsyms]","[kernel.kallsyms][+ffffffffa960015d]" + 89368,68332,0,"sleep",689664,689664,"[kernel.kallsyms]","[kernel.kallsyms][+ffffffffa8e03757]" + 40272,30457,0,"sleep",689664,689664,"/lib/x86_64-linux-gnu/ld-2.32.so","ld-2.32.so[+1767b]" + 14742,7858,0,"sleep",689664,689664,"[kernel.kallsyms]","[kernel.kallsyms][+ffffffffa8ce7a78]" + 7551,1953,0,"sleep",689664,689664,"[kernel.kallsyms]","[kernel.kallsyms][+ffffffffa8cc90c5]" + 7080,2940,0,"sleep",689664,689664,"[kernel.kallsyms]","[kernel.kallsyms][+ffffffffa8cc8119]" + 3520,295,0,"sleep",689664,689664,"[kernel.kallsyms]","[kernel.kallsyms][+ffffffffa8c6b3e6]" + ''')) + + def test_build_id_feature(self): + return DiffTestBlueprint( + trace=DataPath('simpleperf/perf.data'), + query=''' + SELECT build_id, name + FROM stack_profile_mapping + WHERE build_id <> "" + ORDER BY name + ''', + out=Csv(''' + "build_id","name" + "0b12a384a9f4a3f3659b7171ca615dbec3a81f71","/elf" + "0b12a384a9f4a3f3659b7171ca615dbec3a81f71","/elf" + "47111a47babdcd27ca2f9ff450dc1897ded761ed","/lib/modules/3.13.0-76-generic/kernel/drivers/ata/pata_acpi.ko" + "0b12a384a9f4a3f3659b7171ca615dbec3a81f71","/t1" + "0b12a384a9f4a3f3659b7171ca615dbec3a81f71","/t2" + ''')) + + def test_clocks_align(self): + return DiffTestBlueprint( + trace=DataPath('zip/perf_track_sym.zip'), + query=Path('clocks_align_test.sql'), + out=Csv(''' + "misaligned_count" + 0 + ''')) + + def test_cmdline(self): + return DiffTestBlueprint( + trace=DataPath('simpleperf/perf.data'), + query=''' + SELECT cmdline + FROM perf_session + ''', + out=Csv(''' + "cmdline" + "/ssd/android/aosp_master/out/host/linux-x86/bin/simpleperf record -p 26083,26090,26124,26130 sleep 0.0001" + ''')) + + # Make sure we can parse perf.data files with synthetic events (perf will + # write those with an id = 0). This trace file has some synthetic COMM events. + def test_perf_with_synthetic_events(self): + return DiffTestBlueprint( + trace=DataPath('simpleperf/perf_with_synthetic_events.data'), + query=''' + SELECT tid, name + FROM thread + ORDER BY tid + ''', + out=Csv(''' + "tid","name" + 0,"[NULL]" + 289003,"trace_processor" + ''')) + + # Counters are not updated for samples with no CPU (b/352257666) + def test_perf_with_no_cpu_in_sample_no_counters(self): + return DiffTestBlueprint( + trace=DataPath('simpleperf/perf_with_synthetic_events.data'), + query=''' + SELECT + ( + SELECT value AS sample_count + FROM stats + WHERE name = 'perf_counter_skipped_because_no_cpu' + ) AS counter_skipped, + (SELECT COUNT(*) FROM perf_sample) AS sample_count + ''', + out=Csv(''' + "counter_skipped","sample_count" + 9126,9126 + ''')) + + def test_perf_with_no_cpu_in_sample(self): + return DiffTestBlueprint( + trace=DataPath('simpleperf/perf_with_synthetic_events.data'), + query=''' + SELECT cpu, COUNT(*) AS count + FROM perf_sample + WHERE callsite_id IS NOT NULL + GROUP BY cpu ORDER BY cpu + ''', + out=Csv(''' + "cpu","count" + "[NULL]",9126 + ''')) + + def test_linux_perf_unwinding(self): + return DiffTestBlueprint( + trace=DataPath('simpleperf/linux_perf_with_symbols.zip'), + query=Path('stacks_test.sql'), + out=Csv(''' + "name" + "main,A" + "main,A,B" + "main,A,B,C" + "main,A,B,C,D" + "main,A,B,C,D,E" + "main,A,B,C,E" + "main,A,B,D" + "main,A,B,D,E" + "main,A,B,E" + "main,A,C" + "main,A,C,D" + "main,A,C,D,E" + "main,A,C,E" + "main,A,D" + "main,A,D,E" + "main,A,E" + "main,B" + "main,B,C" + "main,B,C,D" + "main,B,C,D,E" + "main,B,C,E" + "main,B,D" + "main,B,D,E" + "main,B,E" + "main,C" + "main,C,D" + "main,C,D,E" + "main,C,E" + "main,D" + "main,D,E" + "main,E" + ''')) diff --git a/test/trace_processor/diff_tests/parser/track_event/legacy_async_event.out b/test/trace_processor/diff_tests/parser/track_event/legacy_async_event.out index ddf1b1faaf..56a8f2ede6 100644 --- a/test/trace_processor/diff_tests/parser/track_event/legacy_async_event.out +++ b/test/trace_processor/diff_tests/parser/track_event/legacy_async_event.out @@ -1,15 +1,15 @@ "track","process","thread","thread_process","ts","dur","category","name","key","string_value","int_value" +"name1","[NULL]","[NULL]","[NULL]",1000,7000,"cat","name1","debug.arg1","value1","[NULL]" "name1","[NULL]","[NULL]","[NULL]",1000,7000,"cat","name1","legacy_event.passthrough_utid","[NULL]",1 "name1","[NULL]","[NULL]","[NULL]",1000,7000,"cat","name1","legacy_event.phase","S","[NULL]" -"name1","[NULL]","[NULL]","[NULL]",1000,7000,"cat","name1","debug.arg1","value1","[NULL]" "name1","[NULL]","[NULL]","[NULL]",1000,7000,"cat","name1","debug.arg2","value2","[NULL]" "name1","[NULL]","[NULL]","[NULL]",2000,1000,"cat","name1","legacy_event.passthrough_utid","[NULL]",2 "name1","[NULL]","[NULL]","[NULL]",2000,1000,"cat","name1","legacy_event.phase","S","[NULL]" -"name1","[NULL]","[NULL]","[NULL]",3000,0,"cat","name1","legacy_event.passthrough_utid","[NULL]",1 -"name1","[NULL]","[NULL]","[NULL]",3000,0,"cat","name1","legacy_event.phase","T","[NULL]" "name1","[NULL]","[NULL]","[NULL]",3000,0,"cat","name1","debug.arg3","value3","[NULL]" "name1","[NULL]","[NULL]","[NULL]",3000,0,"cat","name1","debug.step","Step1","[NULL]" +"name1","[NULL]","[NULL]","[NULL]",3000,0,"cat","name1","legacy_event.passthrough_utid","[NULL]",1 +"name1","[NULL]","[NULL]","[NULL]",3000,0,"cat","name1","legacy_event.phase","T","[NULL]" +"name1","[NULL]","[NULL]","[NULL]",5000,0,"cat","name1","debug.arg4","value4","[NULL]" +"name1","[NULL]","[NULL]","[NULL]",5000,0,"cat","name1","debug.step","Step2","[NULL]" "name1","[NULL]","[NULL]","[NULL]",5000,0,"cat","name1","legacy_event.passthrough_utid","[NULL]",1 "name1","[NULL]","[NULL]","[NULL]",5000,0,"cat","name1","legacy_event.phase","p","[NULL]" -"name1","[NULL]","[NULL]","[NULL]",5000,0,"cat","name1","debug.step","Step2","[NULL]" -"name1","[NULL]","[NULL]","[NULL]",5000,0,"cat","name1","debug.arg4","value4","[NULL]" diff --git a/test/trace_processor/diff_tests/parser/track_event/tests.py b/test/trace_processor/diff_tests/parser/track_event/tests.py index d71a2fa500..76bedbdff8 100644 --- a/test/trace_processor/diff_tests/parser/track_event/tests.py +++ b/test/trace_processor/diff_tests/parser/track_event/tests.py @@ -391,7 +391,7 @@ def test_legacy_async_event(self): LEFT JOIN thread ON thread_track.utid = thread.utid LEFT JOIN process thread_process ON thread.upid = thread_process.upid LEFT JOIN args ON slice.arg_set_id = args.arg_set_id - ORDER BY ts ASC; + ORDER BY slice.ts, args.id; """, out=Path('legacy_async_event.out')) diff --git a/test/trace_processor/diff_tests/parser/translated_args/process_track_name.textproto b/test/trace_processor/diff_tests/parser/translated_args/process_track_name.textproto new file mode 100644 index 0000000000..22dc8fb784 --- /dev/null +++ b/test/trace_processor/diff_tests/parser/translated_args/process_track_name.textproto @@ -0,0 +1,100 @@ +packet { + translation_table { + process_track_name { + raw_to_deobfuscated_name { key: "raw_track_name" value: "explicitly_renamed" } + raw_to_deobfuscated_name { key: "raw_slice1" value: "should_not_be_renamed" } + raw_to_deobfuscated_name { key: "raw_slice2" value: "implicitly_renamed" } + raw_to_deobfuscated_name { key: "raw_counter" value: "renamed_counter" } + } + } +} +packet { + trusted_packet_sequence_id: 1 + timestamp: 1000 + track_descriptor { + uuid: 1 + process: { + process_name: "exampleProcess" + pid: 1234 + } + } +} +# Define a named track +packet { + trusted_packet_sequence_id: 1 + timestamp: 1001 + track_descriptor { + uuid: 2 + name: "raw_track_name" + parent_uuid: 1 + } +} +# define a track who's name will be implicitly defined by slice names +packet { + trusted_packet_sequence_id: 1 + timestamp: 1002 + track_descriptor { + uuid: 3 + parent_uuid: 1 + } +} +# Named track for the counter +packet { + trusted_packet_sequence_id: 1 + timestamp: 1002 + track_descriptor { + uuid: 4 + name: "raw_counter" + parent_uuid: 1 + counter { + } + } +} +# Counter Event +packet { + trusted_packet_sequence_id: 1 + timestamp: 1004 + track_event { + track_uuid: 4 + type: 4 + counter_value: 99 + } +} + +# begin/end pair for explicitly named track +packet { + trusted_packet_sequence_id: 1 + timestamp: 1500 + track_event { + track_uuid: 2 + name: "raw_slice1" + type: 1 + } +} +packet { + trusted_packet_sequence_id: 1 + timestamp: 2000 + track_event { + track_uuid: 2 + type: 2 + } +} + +# begin/end pair for implicitly named slice +packet { + trusted_packet_sequence_id: 1 + timestamp: 3000 + track_event { + track_uuid: 3 + name: "raw_slice2" + type: 1 + } +} +packet { + trusted_packet_sequence_id: 1 + timestamp: 4000 + track_event { + track_uuid: 3 + type: 2 + } +} \ No newline at end of file diff --git a/test/trace_processor/diff_tests/parser/translated_args/tests.py b/test/trace_processor/diff_tests/parser/translated_args/tests.py index 7e28f3877a..3bada0dd76 100644 --- a/test/trace_processor/diff_tests/parser/translated_args/tests.py +++ b/test/trace_processor/diff_tests/parser/translated_args/tests.py @@ -121,6 +121,25 @@ def test_slice_name_2(self): "slice_begin" """)) + def test_process_track_name(self): + return DiffTestBlueprint( + trace=Path('process_track_name.textproto'), + query=""" + SELECT + name + FROM track + WHERE + name IS NOT NULL + AND type in ('process_track', 'process_counter_track') + ORDER BY name; + """, + out=Csv(""" + "name" + "explicitly_renamed" + "implicitly_renamed" + "renamed_counter" + """)) + def test_native_symbol_arg(self): return DiffTestBlueprint( trace=Path('native_symbol_arg.textproto'), diff --git a/test/trace_processor/diff_tests/parser/zip/tests.py b/test/trace_processor/diff_tests/parser/zip/tests.py new file mode 100644 index 0000000000..51ce024c51 --- /dev/null +++ b/test/trace_processor/diff_tests/parser/zip/tests.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 +# 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 a +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from python.generators.diff_tests.testing import Csv, Path, DataPath +from python.generators.diff_tests.testing import DiffTestBlueprint +from python.generators.diff_tests.testing import TestSuite + + +class Zip(TestSuite): + + def test_perf_proto_sym(self): + return DiffTestBlueprint( + trace=DataPath('zip/perf_track_sym.zip'), + query=Path('../simpleperf/stacks_test.sql'), + out=Csv(''' + "name" + "main,A" + "main,A,B" + "main,A,B,C" + "main,A,B,C,D" + "main,A,B,C,D,E" + "main,A,B,C,E" + "main,A,B,D" + "main,A,B,D,E" + "main,A,B,E" + "main,A,C" + "main,A,C,D" + "main,A,C,D,E" + "main,A,C,E" + "main,A,D" + "main,A,D,E" + "main,A,E" + "main,B" + "main,B,C" + "main,B,C,D" + "main,B,C,D,E" + "main,B,C,E" + "main,B,D" + "main,B,D,E" + "main,B,E" + "main,C" + "main,C,D" + "main,C,D,E" + "main,C,E" + "main,D" + "main,D,E" + "main,E" + ''')) + + def test_tokenization_order(self): + return DiffTestBlueprint( + trace=DataPath('zip/perf_track_sym.zip'), + query=''' + SELECT * + FROM __intrinsic_trace_file + ORDER BY id + ''', + out=Csv(''' + "id","type","parent_id","name","size","trace_type" + 0,"__intrinsic_trace_file","[NULL]","[NULL]",94651,"zip" + 1,"__intrinsic_trace_file",0,"c.trace.pb",379760,"proto" + 2,"__intrinsic_trace_file",0,"b.simpleperf.data",554911,"perf" + 3,"__intrinsic_trace_file",0,"a.symbols.pb",186149,"symbols" + ''')) \ No newline at end of file diff --git a/test/trace_processor/diff_tests/stdlib/android/cpu_cluster_tests.py b/test/trace_processor/diff_tests/stdlib/android/cpu_cluster_tests.py new file mode 100644 index 0000000000..fc1820bd7f --- /dev/null +++ b/test/trace_processor/diff_tests/stdlib/android/cpu_cluster_tests.py @@ -0,0 +1,314 @@ +#!/usr/bin/env python3 +# Copyright (C) 2024 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License a +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from python.generators.diff_tests.testing import Path +from python.generators.diff_tests.testing import Csv, TextProto +from python.generators.diff_tests.testing import DiffTestBlueprint +from python.generators.diff_tests.testing import TestSuite + + +class CpuClusters(TestSuite): + + def test_android_cpu_cluster_type_one_core(self): + return DiffTestBlueprint( + trace=TextProto(r""" + packet { + cpu_info { + cpus { + processor: "unknown" + capacity: 1024 + frequencies: 100000 + frequencies: 200000 + } + } + } + """), + query=""" + INCLUDE PERFETTO MODULE android.cpu.cluster_type; + + SELECT + ucpu, + cpu, + cluster_type + FROM + android_cpu_cluster_mapping; + """, + out=Csv(""" + "ucpu","cpu","cluster_type" + 0,0,"[NULL]" + """)) + + def test_android_cpu_cluster_type_two_core(self): + return DiffTestBlueprint( + trace=TextProto(r""" + packet { + cpu_info { + cpus { + processor: "unknown" + capacity: 158 + frequencies: 100000 + frequencies: 200000 + } + cpus { + processor: "unknown" + capacity: 1024 + frequencies: 500000 + frequencies: 574000 + } + } + } + """), + query=""" + INCLUDE PERFETTO MODULE android.cpu.cluster_type; + + SELECT + ucpu, + cpu, + cluster_type + FROM + android_cpu_cluster_mapping; + """, + out=Csv(""" + "ucpu","cpu","cluster_type" + 0,0,"little" + 1,1,"big" + """)) + + def test_android_cpu_cluster_type_three_core(self): + return DiffTestBlueprint( + trace=TextProto(r""" + packet { + cpu_info { + cpus { + processor: "unknown" + capacity: 158 + frequencies: 100000 + frequencies: 200000 + } + cpus { + processor: "unknown" + capacity: 550 + frequencies: 300000 + frequencies: 400000 + } + cpus { + processor: "unknown" + capacity: 1024 + frequencies: 500000 + frequencies: 574000 + } + } + } + """), + query=""" + INCLUDE PERFETTO MODULE android.cpu.cluster_type; + + SELECT + ucpu, + cpu, + cluster_type + FROM + android_cpu_cluster_mapping; + """, + out=Csv(""" + "ucpu","cpu","cluster_type" + 0,0,"little" + 1,1,"medium" + 2,2,"big" + """)) + + def test_android_cpu_cluster_type_four_core(self): + return DiffTestBlueprint( + trace=TextProto(r""" + packet { + cpu_info { + cpus { + processor: "unknown" + capacity: 158 + frequencies: 100000 + frequencies: 200000 + } + cpus { + processor: "unknown" + capacity: 550 + frequencies: 300000 + frequencies: 400000 + } + cpus { + processor: "unknown" + capacity: 700 + frequencies: 400000 + frequencies: 500000 + } + cpus { + processor: "unknown" + capacity: 1024 + frequencies: 500000 + frequencies: 574000 + } + } + } + """), + query=""" + INCLUDE PERFETTO MODULE android.cpu.cluster_type; + + SELECT + ucpu, + cpu, + cluster_type + FROM + android_cpu_cluster_mapping; + """, + out=Csv(""" + "ucpu","cpu","cluster_type" + 0,0,"little" + 1,1,"medium" + 2,2,"medium" + 3,3,"big" + """)) + + def test_android_cpu_cluster_type_five_core(self): + return DiffTestBlueprint( + trace=TextProto(r""" + packet { + cpu_info { + cpus { + processor: "unknown" + capacity: 158 + frequencies: 100000 + frequencies: 200000 + } + cpus { + processor: "unknown" + capacity: 550 + frequencies: 300000 + frequencies: 400000 + } + cpus { + processor: "unknown" + capacity: 700 + frequencies: 400000 + frequencies: 500000 + } + cpus { + processor: "unknown" + capacity: 800 + frequencies: 500000 + frequencies: 520000 + } + cpus { + processor: "unknown" + capacity: 1024 + frequencies: 500000 + frequencies: 574000 + } + } + } + """), + query=""" + INCLUDE PERFETTO MODULE android.cpu.cluster_type; + + SELECT + ucpu, + cpu, + cluster_type + FROM + android_cpu_cluster_mapping; + """, + out=Csv(""" + "ucpu","cpu","cluster_type" + 0,0,"[NULL]" + 1,1,"[NULL]" + 2,2,"[NULL]" + 3,3,"[NULL]" + 4,4,"[NULL]" + """)) + + def test_android_cpu_cluster_type_capacity_not_present(self): + return DiffTestBlueprint( + trace=TextProto(r""" + packet { + cpu_info { + cpus { + processor: "unknown" + frequencies: 100000 + frequencies: 200000 + } + cpus { + processor: "unknown" + frequencies: 300000 + frequencies: 400000 + } + cpus { + processor: "unknown" + frequencies: 500000 + frequencies: 574000 + } + } + } + """), + query=""" + INCLUDE PERFETTO MODULE android.cpu.cluster_type; + + SELECT + ucpu, + cpu, + cluster_type + FROM + android_cpu_cluster_mapping; + """, + out=Csv(""" + "ucpu","cpu","cluster_type" + 0,0,"little" + 1,1,"medium" + 2,2,"big" + """)) + + def test_android_cpu_cluster_type_insufficient_data_to_calculate(self): + return DiffTestBlueprint( + trace=TextProto(r""" + packet { + cpu_info { + cpus { + processor: "unknown" + frequencies: 10000 + } + cpus { + processor: "unknown" + frequencies: 10000 + } + cpus { + processor: "unknown" + frequencies: 10000 + } + } + } + """), + query=""" + INCLUDE PERFETTO MODULE android.cpu.cluster_type; + + SELECT + ucpu, + cpu, + cluster_type + FROM + android_cpu_cluster_mapping; + """, + out=Csv(""" + "ucpu","cpu","cluster_type" + 0,0,"[NULL]" + 1,1,"[NULL]" + 2,2,"[NULL]" + """)) diff --git a/test/trace_processor/diff_tests/stdlib/android/gpu.py b/test/trace_processor/diff_tests/stdlib/android/gpu.py new file mode 100644 index 0000000000..8ae8ae5498 --- /dev/null +++ b/test/trace_processor/diff_tests/stdlib/android/gpu.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +# Copyright (C) 2024 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License a +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from python.generators.diff_tests.testing import Path, DataPath, Metric, Systrace +from python.generators.diff_tests.testing import Csv, Json, TextProto, BinaryProto +from python.generators.diff_tests.testing import DiffTestBlueprint +from python.generators.diff_tests.testing import TestSuite +from python.generators.diff_tests.testing import PrintProfileProto + + +class AndroidGpu(TestSuite): + + def test_memory_gpu_per_process(self): + return DiffTestBlueprint( + trace=Path('../../metrics/graphics/gpu_metric.py'), + query=""" + INCLUDE PERFETTO MODULE android.gpu.memory; + SELECT * + FROM android_gpu_memory_per_process; + """, + out=Csv(""" + "ts","dur","upid","gpu_memory" + 2,2,2,6 + 4,6,2,8 + 4,5,1,2 + 9,1,1,8 + 6,1,3,7 + 7,3,3,10 + """)) + + def test_gpu_frequency(self): + return DiffTestBlueprint( + trace=Path('../../metrics/graphics/gpu_frequency_metric.textproto'), + query=""" + INCLUDE PERFETTO MODULE android.gpu.frequency; + SELECT * + FROM android_gpu_frequency; + """, + out=Csv(""" + "ts","dur","gpu_id","gpu_freq" + 200001000000,2000000,0,585000 + 200003000000,1000000,0,0 + 200004000000,2000000,0,603000 + 200002000000,3000000,1,400000 + 200005000000,1000000,1,758000 + """)) diff --git a/test/trace_processor/diff_tests/stdlib/android/heap_graph_for_aggregation.textproto b/test/trace_processor/diff_tests/stdlib/android/heap_graph_for_aggregation.textproto new file mode 100644 index 0000000000..52210679f2 --- /dev/null +++ b/test/trace_processor/diff_tests/stdlib/android/heap_graph_for_aggregation.textproto @@ -0,0 +1,83 @@ +packet { + process_tree { + processes { + pid: 1 + ppid: 0 + cmdline: "init" + uid: 0 + } + processes { + pid: 2 + ppid: 1 + cmdline: "system_server" + uid: 1000 + } + } +} +packet { + trusted_packet_sequence_id: 999 + timestamp: 10 + # A[0x1] java.lang.String[0x4] + # / \ + # A[0x2] B[0x3] + # / + # java.lang.String[0x5] + heap_graph { + pid: 2 + roots { + root_type: ROOT_JNI_GLOBAL + object_ids: 0x1 + object_ids: 0x4 + } + objects { + id: 0x01 + type_id: 1 + reference_object_id: 0x2 + reference_object_id: 0x3 + } + objects { + id: 0x02 + type_id: 1 + reference_object_id: 0x5 + } + objects { + id: 0x03 + type_id: 2 + } + objects { + id: 0x04 + type_id: 3 + self_size: 666 + } + objects { + id: 0x05 + type_id: 3 + self_size: 10000 + } + continued: true + index: 0 + } +} +packet { + trusted_packet_sequence_id: 999 + timestamp: 10 + heap_graph { + pid: 2 + types { + id: 1 + class_name: "A" + object_size: 100 + } + types { + id: 2 + class_name: "B" + object_size: 1000 + } + types { + id: 3 + class_name: "java.lang.String" + } + continued: false + index: 1 + } +} diff --git a/test/trace_processor/diff_tests/stdlib/memory/heap_graph_for_dominator_tree.textproto b/test/trace_processor/diff_tests/stdlib/android/heap_graph_for_dominator_tree.textproto similarity index 100% rename from test/trace_processor/diff_tests/stdlib/memory/heap_graph_for_dominator_tree.textproto rename to test/trace_processor/diff_tests/stdlib/android/heap_graph_for_dominator_tree.textproto diff --git a/test/trace_processor/diff_tests/stdlib/memory/heap_graph_dominator_tree_tests.py b/test/trace_processor/diff_tests/stdlib/android/heap_graph_tests.py similarity index 68% rename from test/trace_processor/diff_tests/stdlib/memory/heap_graph_dominator_tree_tests.py rename to test/trace_processor/diff_tests/stdlib/android/heap_graph_tests.py index 0aa0f6489b..fe3ba2d906 100644 --- a/test/trace_processor/diff_tests/stdlib/memory/heap_graph_dominator_tree_tests.py +++ b/test/trace_processor/diff_tests/stdlib/android/heap_graph_tests.py @@ -19,13 +19,13 @@ from python.generators.diff_tests.testing import TestSuite -class HeapGraphDominatorTree(TestSuite): +class HeapGraph(TestSuite): def test_heap_graph_dominator_tree(self): return DiffTestBlueprint( trace=Path('heap_graph_for_dominator_tree.textproto'), query=""" - INCLUDE PERFETTO MODULE memory.heap_graph_dominator_tree; + INCLUDE PERFETTO MODULE android.memory.heap_graph.dominator_tree; SELECT node.id, @@ -34,14 +34,13 @@ def test_heap_graph_dominator_tree(self): node.dominated_size_bytes, node.depth, cls.name AS type_name - FROM memory_heap_graph_dominator_tree node + FROM heap_graph_dominator_tree node JOIN heap_graph_object obj USING(id) JOIN heap_graph_class cls ON obj.type_id = cls.id ORDER BY type_name; """, out=Csv(""" - "id","idom_id","dominated_obj_count","dominated_size_bytes",\ -"depth","type_name" + "id","idom_id","dominated_obj_count","dominated_size_bytes","depth","type_name" 0,12,1,3,2,"A" 2,12,1,3,2,"B" 4,12,4,12,2,"C" @@ -59,24 +58,29 @@ def test_heap_graph_dominator_tree(self): 14,13,4,904,3,"O" 15,13,1,16,3,"P" 17,16,1,32,3,"Q" - 12,25,13,39,1,"R" - 22,25,10,1023,1,"S" + 12,"[NULL]",13,39,1,"R" + 22,"[NULL]",10,1023,1,"S" 18,16,1,64,3,"T" 19,14,1,128,4,"U" 20,14,1,256,4,"V" 21,14,1,512,4,"W" - 23,25,1,1024,1,"sun.misc.Cleaner" + 23,"[NULL]",1,1024,1,"sun.misc.Cleaner" """)) - def test_heap_graph_super_root_fn(self): + def test_heap_graph_aggregation(self): return DiffTestBlueprint( - trace=Path('heap_graph_for_dominator_tree.textproto'), + trace=Path('heap_graph_for_aggregation.textproto'), query=""" - INCLUDE PERFETTO MODULE memory.heap_graph_dominator_tree; + INCLUDE PERFETTO MODULE android.memory.heap_graph.heap_graph_class_aggregation; - SELECT memory_heap_graph_super_root_fn(); + SELECT graph_sample_ts, upid, type_name, is_libcore_or_array, + obj_count, size_bytes, + dominated_obj_count, dominated_size_bytes + FROM android_heap_graph_class_aggregation; """, out=Csv(""" - "memory_heap_graph_super_root_fn()" - 25 + "graph_sample_ts","upid","type_name","is_libcore_or_array","obj_count","size_bytes","dominated_obj_count","dominated_size_bytes" + 10,2,"A",0,2,200,4,11200 + 10,2,"B",0,1,1000,1,1000 + 10,2,"java.lang.String",1,2,10666,2,10666 """)) diff --git a/test/trace_processor/diff_tests/stdlib/android/memory.py b/test/trace_processor/diff_tests/stdlib/android/memory.py new file mode 100644 index 0000000000..b62c680c2f --- /dev/null +++ b/test/trace_processor/diff_tests/stdlib/android/memory.py @@ -0,0 +1,144 @@ +#!/usr/bin/env python3 +# Copyright (C) 2024 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License a +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from python.generators.diff_tests.testing import Path, DataPath, Metric, Systrace +from python.generators.diff_tests.testing import Csv, Json, TextProto, BinaryProto +from python.generators.diff_tests.testing import DiffTestBlueprint +from python.generators.diff_tests.testing import TestSuite +from python.generators.diff_tests.testing import PrintProfileProto + + +class AndroidMemory(TestSuite): + + def test_memory_oom_score_with_rss_and_swap_per_process(self): + return DiffTestBlueprint( + trace=DataPath('sched_wakeup_trace.atr'), + query=""" + INCLUDE PERFETTO MODULE android.memory.process; + SELECT * + FROM memory_oom_score_with_rss_and_swap_per_process + WHERE oom_adj_reason IS NOT NULL + ORDER BY ts + LIMIT 10; + """, + out=Csv(""" + "ts","dur","score","bucket","upid","process_name","pid","oom_adj_id","oom_adj_ts","oom_adj_dur","oom_adj_track_id","oom_adj_thread_name","oom_adj_reason","oom_adj_trigger","anon_rss","file_rss","shmem_rss","rss","swap","anon_rss_and_swap","rss_and_swap" + 1737065264829,701108081,925,"cached",269,"com.android.providers.calendar",1937,332,1737064421516,29484835,1217,"binder:642_1","processEnd","IActivityManager#1598246212",49229824,57495552,835584,107560960,0,49229824,107560960 + 1737066678827,2934486383,935,"cached",287,"com.android.imsserviceentitlement",2397,332,1737064421516,29484835,1217,"binder:642_1","processEnd","IActivityManager#1598246212",48881664,57081856,831488,106795008,0,48881664,106795008 + 1737066873002,2934292208,945,"cached",292,"com.android.carrierconfig",2593,332,1737064421516,29484835,1217,"binder:642_1","processEnd","IActivityManager#1598246212",48586752,49872896,823296,99282944,0,48586752,99282944 + 1737067058812,2934106398,955,"cached",288,"com.android.messaging",2416,332,1737064421516,29484835,1217,"binder:642_1","processEnd","IActivityManager#1598246212",54956032,71417856,843776,127217664,0,54956032,127217664 + 1737067246975,699224817,955,"cached",267,"android.process.acore",1866,332,1737064421516,29484835,1217,"binder:642_1","processEnd","IActivityManager#1598246212",52498432,72048640,856064,125403136,0,52498432,125403136 + 1737068421919,2932743291,965,"cached",273,"com.android.shell",2079,332,1737064421516,29484835,1217,"binder:642_1","processEnd","IActivityManager#1598246212",48738304,52056064,823296,101617664,0,48738304,101617664 + 1737068599673,970398,965,"cached",271,"android.process.media",2003,332,1737064421516,29484835,1217,"binder:642_1","processEnd","IActivityManager#1598246212",49917952,60444672,839680,111202304,0,49917952,111202304 + 1737068933602,2932231608,975,"cached",286,"com.android.gallery3d",2371,332,1737064421516,29484835,1217,"binder:642_1","processEnd","IActivityManager#1598246212",49561600,54521856,831488,104914944,0,49561600,104914944 + 1737069091010,682459310,975,"cached",289,"com.android.packageinstaller",2480,332,1737064421516,29484835,1217,"binder:642_1","processEnd","IActivityManager#1598246212",49364992,52539392,827392,102731776,0,49364992,102731776 + 1737069240534,489635,985,"cached",268,"com.android.managedprovisioning",1868,332,1737064421516,29484835,1217,"binder:642_1","processEnd","IActivityManager#1598246212",50683904,53985280,815104,105484288,0,50683904,105484288 + """)) + + def test_memory_dmabuf(self): + return DiffTestBlueprint( + trace=TextProto(r""" + packet { + ftrace_events { + cpu: 0 + event { + timestamp: 1 + pid: 3000 + dma_heap_stat { + inode: 13583 + len: 3000 + total_allocated: 3000 + } + } + event { + timestamp: 2 + pid: 3000 + dma_heap_stat { + inode: 13583 + len: -3000 + total_allocated: 0 + } + } + event { + timestamp: 4144791776152 + pid: 9403 + binder_transaction { + debug_id: 3052940 + target_node: 256 + to_proc: 572 + to_thread: 0 + reply: 0 + code: 1 + flags: 16 + } + } + event { + timestamp: 4144791793486 + pid: 591 + binder_transaction_received { + debug_id: 3052940 + } + } + event { + timestamp: 4144792258492 + pid: 591 + dma_heap_stat { + inode: 13583 + len: 10399744 + total_allocated: 254873600 + } + } + event { + timestamp: 4144792517566 + pid: 591 + binder_transaction { + debug_id: 3052950 + target_node: 0 + to_proc: 2051 + to_thread: 9403 + reply: 1 + code: 0 + flags: 0 + } + } + event { + timestamp: 4144792572498 + pid: 9403 + binder_transaction_received { + debug_id: 3052950 + } + } + event { + timestamp: 4145263509021 + pid: 613 + dma_heap_stat { + inode: 13583 + len: -10399744 + total_allocated: 390160384 + } + } + } + }"""), + query=""" + INCLUDE PERFETTO MODULE android.memory.dmabuf; + SELECT * FROM android_dmabuf_allocs; + """, + out=Csv(""" + "ts","buf_size","inode","utid","tid","thread_name","upid","pid","process_name" + 1,3000,13583,1,3000,"[NULL]","[NULL]","[NULL]","[NULL]" + 2,-3000,13583,1,3000,"[NULL]","[NULL]","[NULL]","[NULL]" + 4144792258492,10399744,13583,3,591,"[NULL]","[NULL]","[NULL]","[NULL]" + 4145263509021,-10399744,13583,3,591,"[NULL]","[NULL]","[NULL]","[NULL]" + """)) \ No newline at end of file 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 95ff515ac6..c4f5a2e1ed 100644 --- a/test/trace_processor/diff_tests/stdlib/android/startups_tests.py +++ b/test/trace_processor/diff_tests/stdlib/android/startups_tests.py @@ -30,7 +30,7 @@ def test_hot_startups(self): """, out=Csv(""" "startup_id","ts","ts_end","dur","package","startup_type" - 1,186969441973689,186969489302704,47329015,"androidx.benchmark.integration.macrobenchmark.target","[NULL]" + 0,186969441973689,186969489302704,47329015,"androidx.benchmark.integration.macrobenchmark.target","[NULL]" """)) def test_warm_startups(self): @@ -42,7 +42,7 @@ def test_warm_startups(self): """, out=Csv(""" "startup_id","ts","ts_end","dur","package","startup_type" - 1,186982050780778,186982115528805,64748027,"androidx.benchmark.integration.macrobenchmark.target","[NULL]" + 0,186982050780778,186982115528805,64748027,"androidx.benchmark.integration.macrobenchmark.target","[NULL]" """)) def test_cold_startups(self): @@ -54,7 +54,7 @@ def test_cold_startups(self): """, out=Csv(""" "startup_id","ts","ts_end","dur","package","startup_type" - 1,186974938196632,186975083989042,145792410,"androidx.benchmark.integration.macrobenchmark.target","[NULL]" + 0,186974938196632,186975083989042,145792410,"androidx.benchmark.integration.macrobenchmark.target","[NULL]" """)) def test_hot_startups_maxsdk28(self): @@ -66,8 +66,8 @@ def test_hot_startups_maxsdk28(self): """, out=Csv(""" "startup_id","ts","ts_end","dur","package","startup_type" - 1,779860286416,779893485322,33198906,"com.google.android.googlequicksearchbox","hot" - 2,780778904571,780813944498,35039927,"androidx.benchmark.integration.macrobenchmark.target","hot" + 0,779860286416,779893485322,33198906,"com.google.android.googlequicksearchbox","hot" + 1,780778904571,780813944498,35039927,"androidx.benchmark.integration.macrobenchmark.target","hot" """)) def test_warm_startups_maxsdk28(self): @@ -79,8 +79,8 @@ def test_warm_startups_maxsdk28(self): """, out=Csv(""" "startup_id","ts","ts_end","dur","package","startup_type" - 1,799979565075,800014194731,34629656,"com.google.android.googlequicksearchbox","hot" - 2,800868511677,800981929562,113417885,"androidx.benchmark.integration.macrobenchmark.target","[NULL]" + 0,799979565075,800014194731,34629656,"com.google.android.googlequicksearchbox","hot" + 1,800868511677,800981929562,113417885,"androidx.benchmark.integration.macrobenchmark.target","[NULL]" """)) def test_cold_startups_maxsdk28(self): @@ -92,7 +92,7 @@ def test_cold_startups_maxsdk28(self): """, out=Csv(""" "startup_id","ts","ts_end","dur","package","startup_type" - 1,791231114368,791501060868,269946500,"androidx.benchmark.integration.macrobenchmark.target","[NULL]" + 0,791231114368,791501060868,269946500,"androidx.benchmark.integration.macrobenchmark.target","[NULL]" """)) def test_android_startup_time_to_display_hot_maxsdk28(self): @@ -104,8 +104,8 @@ def test_android_startup_time_to_display_hot_maxsdk28(self): """, out=Csv(""" "startup_id","time_to_initial_display","time_to_full_display","ttid_frame_id","ttfd_frame_id","upid" - 1,33198906,"[NULL]",1,"[NULL]",355 - 2,35039927,537343160,4,5,383 + 0,33198906,"[NULL]",1,"[NULL]",355 + 1,35039927,537343160,4,5,383 """)) def test_android_startup_time_to_display_warm_maxsdk28(self): @@ -117,8 +117,8 @@ def test_android_startup_time_to_display_warm_maxsdk28(self): """, out=Csv(""" "startup_id","time_to_initial_display","time_to_full_display","ttid_frame_id","ttfd_frame_id","upid" - 1,34629656,"[NULL]",1,"[NULL]",355 - 2,108563770,581026583,4,5,388 + 0,34629656,"[NULL]",1,"[NULL]",355 + 1,108563770,581026583,4,5,388 """)) def test_android_startup_time_to_display_cold_maxsdk28(self): @@ -130,7 +130,7 @@ def test_android_startup_time_to_display_cold_maxsdk28(self): """, out=Csv(""" "startup_id","time_to_initial_display","time_to_full_display","ttid_frame_id","ttfd_frame_id","upid" - 1,264869885,715406822,65,66,396 + 0,264869885,715406822,65,66,396 """)) def test_android_startup_time_to_display_hot(self): @@ -142,7 +142,7 @@ def test_android_startup_time_to_display_hot(self): """, out=Csv(""" "startup_id","time_to_initial_display","time_to_full_display","ttid_frame_id","ttfd_frame_id","upid" - 1,40534066,542222554,5872867,5872953,184 + 0,40534066,542222554,5872867,5872953,184 """)) def test_android_startup_time_to_display_warm(self): @@ -154,7 +154,7 @@ def test_android_startup_time_to_display_warm(self): """, out=Csv(""" "startup_id","time_to_initial_display","time_to_full_display","ttid_frame_id","ttfd_frame_id","upid" - 1,62373965,555968701,5873800,5873889,185 + 0,62373965,555968701,5873800,5873889,185 """)) def test_android_startup_time_to_display_cold(self): @@ -166,5 +166,5 @@ def test_android_startup_time_to_display_cold(self): """, out=Csv(""" "startup_id","time_to_initial_display","time_to_full_display","ttid_frame_id","ttfd_frame_id","upid" - 1,143980066,620815843,5873276,5873353,229 + 0,143980066,620815843,5873276,5873353,229 """)) diff --git a/test/trace_processor/diff_tests/stdlib/android/tests.py b/test/trace_processor/diff_tests/stdlib/android/tests.py index 59a8877a92..6781e3e32a 100644 --- a/test/trace_processor/diff_tests/stdlib/android/tests.py +++ b/test/trace_processor/diff_tests/stdlib/android/tests.py @@ -1244,16 +1244,16 @@ def test_oom_adjuster_transitions(self): """, out=Csv(""" "ts","dur","score","bucket","process_name","oom_adj_ts","oom_adj_dur","oom_adj_thread_name","oom_adj_reason","oom_adj_trigger" - 1737065264829,701108081,925,"cached_app","com.android.providers.calendar",1737064421516,29484835,"binder:642_1","processEnd","IActivityManager#1598246212" - 1737066678827,3470211742,935,"cached_app","com.android.imsserviceentitlement",1737064421516,29484835,"binder:642_1","processEnd","IActivityManager#1598246212" - 1737066873002,3470017567,945,"cached_app","com.android.carrierconfig",1737064421516,29484835,"binder:642_1","processEnd","IActivityManager#1598246212" - 1737067058812,3469831757,955,"cached_app_lmk_first","com.android.messaging",1737064421516,29484835,"binder:642_1","processEnd","IActivityManager#1598246212" - 1737067246975,699224817,955,"cached_app_lmk_first","android.process.acore",1737064421516,29484835,"binder:642_1","processEnd","IActivityManager#1598246212" - 1737068421919,3468468650,965,"cached_app_lmk_first","com.android.shell",1737064421516,29484835,"binder:642_1","processEnd","IActivityManager#1598246212" - 1737068599673,697908135,965,"cached_app_lmk_first","android.process.media",1737064421516,29484835,"binder:642_1","processEnd","IActivityManager#1598246212" - 1737068933602,3467956967,975,"cached_app_lmk_first","com.android.gallery3d",1737064421516,29484835,"binder:642_1","processEnd","IActivityManager#1598246212" - 1737069091010,3467799559,975,"cached_app_lmk_first","com.android.packageinstaller",1737064421516,29484835,"binder:642_1","processEnd","IActivityManager#1598246212" - 1737069240534,3467650035,985,"cached_app_lmk_first","com.android.managedprovisioning",1737064421516,29484835,"binder:642_1","processEnd","IActivityManager#1598246212" +1737065264829,701108081,925,"cached","com.android.providers.calendar",1737064421516,29484835,"binder:642_1","processEnd","IActivityManager#1598246212" +1737066678827,3470211742,935,"cached","com.android.imsserviceentitlement",1737064421516,29484835,"binder:642_1","processEnd","IActivityManager#1598246212" +1737066873002,3470017567,945,"cached","com.android.carrierconfig",1737064421516,29484835,"binder:642_1","processEnd","IActivityManager#1598246212" +1737067058812,3469831757,955,"cached","com.android.messaging",1737064421516,29484835,"binder:642_1","processEnd","IActivityManager#1598246212" +1737067246975,699224817,955,"cached","android.process.acore",1737064421516,29484835,"binder:642_1","processEnd","IActivityManager#1598246212" +1737068421919,3468468650,965,"cached","com.android.shell",1737064421516,29484835,"binder:642_1","processEnd","IActivityManager#1598246212" +1737068599673,697908135,965,"cached","android.process.media",1737064421516,29484835,"binder:642_1","processEnd","IActivityManager#1598246212" +1737068933602,3467956967,975,"cached","com.android.gallery3d",1737064421516,29484835,"binder:642_1","processEnd","IActivityManager#1598246212" +1737069091010,3467799559,975,"cached","com.android.packageinstaller",1737064421516,29484835,"binder:642_1","processEnd","IActivityManager#1598246212" +1737069240534,3467650035,985,"cached","com.android.managedprovisioning",1737064421516,29484835,"binder:642_1","processEnd","IActivityManager#1598246212" """)) def test_broadcast_minsdk_u(self): diff --git a/test/trace_processor/diff_tests/stdlib/chrome/chrome_speedometer.out b/test/trace_processor/diff_tests/stdlib/chrome/chrome_speedometer.out deleted file mode 100644 index 2a524853ad..0000000000 --- a/test/trace_processor/diff_tests/stdlib/chrome/chrome_speedometer.out +++ /dev/null @@ -1,11 +0,0 @@ -"iteration","ts","dur","total","mean","geomean","score","num_measurements" -1,693997310311984,7020297000,5191976000,"324498500.0","254177934.4","78.7",96 -2,694004414619984,6308034000,4600497000,"287531062.5","224382472.1","89.1",96 -3,694010770005984,5878289000,4209484000,"263092750.0","200261720.2","99.9",96 -4,694016699502984,5934578000,4213632000,"263352000.0","201163561.9","99.4",96 -5,694022683560984,5952163000,4259111000,"266194437.5","203014932.4","98.5",96 -6,694028690570984,5966530000,4269728000,"266858000.0","204306068.2","97.9",96 -7,694034719276984,5853043000,4219351000,"263709437.5","200358118.8","99.8",96 -8,694040637173984,6087435000,4261576000,"266348500.0","202962356.2","98.5",96 -9,694046772284984,6040820000,4245060000,"265316250.0","199331263.1","100.3",96 -10,694052857814984,6063770000,4388487000,"274280437.5","208004176.2","96.2",96 diff --git a/test/trace_processor/diff_tests/stdlib/chrome/scroll_jank_v3.out b/test/trace_processor/diff_tests/stdlib/chrome/scroll_jank_v3.out index 15270aba30..686a526c2e 100644 --- a/test/trace_processor/diff_tests/stdlib/chrome/scroll_jank_v3.out +++ b/test/trace_processor/diff_tests/stdlib/chrome/scroll_jank_v3.out @@ -1,2 +1,7 @@ "cause_of_jank","sub_cause_of_jank","delay_since_last_frame","vsync_interval" -"RendererCompositorQueueingDelay","[NULL]",22.331000,10.483000 +"SubmitCompositorFrameToPresentationCompositorFrame","StartDrawToSwapStart",22.219000,10.318000 +"SubmitCompositorFrameToPresentationCompositorFrame","StartDrawToSwapStart",22.240000,10.318000 +"SubmitCompositorFrameToPresentationCompositorFrame","StartDrawToSwapStart",22.242000,10.318000 +"SubmitCompositorFrameToPresentationCompositorFrame","BufferReadyToLatch",22.250000,10.318000 +"SubmitCompositorFrameToPresentationCompositorFrame","StartDrawToSwapStart",22.262000,10.318000 +"RendererCompositorQueueingDelay","[NULL]",22.331000,10.318000 diff --git a/test/trace_processor/diff_tests/stdlib/chrome/scroll_jank_v3_new.out b/test/trace_processor/diff_tests/stdlib/chrome/scroll_jank_v3_new.out new file mode 100644 index 0000000000..7203356b2b --- /dev/null +++ b/test/trace_processor/diff_tests/stdlib/chrome/scroll_jank_v3_new.out @@ -0,0 +1,9 @@ +"cause_of_jank","sub_cause_of_jank","delay_since_last_frame","vsync_interval" +"BrowserMainToRendererCompositor","[NULL]",22.219000,11.111000 +"SubmitCompositorFrameToPresentationCompositorFrame","StartDrawToSwapStart",22.242000,11.111000 +"SubmitCompositorFrameToPresentationCompositorFrame","StartDrawToSwapStart",22.194000,11.111000 +"SubmitCompositorFrameToPresentationCompositorFrame","StartDrawToSwapStart",22.223000,11.111000 +"SubmitCompositorFrameToPresentationCompositorFrame","SwapEndToPresentationCompositorFrame",22.222000,11.111000 +"SubmitCompositorFrameToPresentationCompositorFrame","StartDrawToSwapStart",33.326000,11.111000 +"SubmitCompositorFrameToPresentationCompositorFrame","StartDrawToSwapStart",22.220000,11.111000 +"SubmitCompositorFrameToPresentationCompositorFrame","StartDrawToSwapStart",33.324000,11.111000 diff --git a/test/trace_processor/diff_tests/stdlib/chrome/scroll_jank_v3_percentage.out b/test/trace_processor/diff_tests/stdlib/chrome/scroll_jank_v3_percentage.out index f070675745..8617ff9dcf 100644 --- a/test/trace_processor/diff_tests/stdlib/chrome/scroll_jank_v3_percentage.out +++ b/test/trace_processor/diff_tests/stdlib/chrome/scroll_jank_v3_percentage.out @@ -1,2 +1,2 @@ "delayed_frame_percentage" -0.282486 +1.648352 diff --git a/test/trace_processor/diff_tests/stdlib/chrome/tests.py b/test/trace_processor/diff_tests/stdlib/chrome/tests.py index ddc088e526..d2afd6a05f 100755 --- a/test/trace_processor/diff_tests/stdlib/chrome/tests.py +++ b/test/trace_processor/diff_tests/stdlib/chrome/tests.py @@ -109,32 +109,85 @@ def test_chrome_histograms(self): "Net.HttpResponseCode",541 """)) - def test_speedometer(self): + def test_speedometer_2_1_score(self): return DiffTestBlueprint( - trace=DataPath('speedometer.perfetto_trace.gz'), + trace=DataPath('speedometer_21.perfetto_trace.gz'), + query=""" + INCLUDE PERFETTO MODULE chrome.speedometer; + + SELECT format("%.1f", chrome_speedometer_score()) AS score + """, + out=Csv(""" + "score" + "95.8" + """)) + + def test_speedometer_2_1_iteration(self): + return DiffTestBlueprint( + trace=DataPath('speedometer_21.perfetto_trace.gz'), + query=""" + INCLUDE PERFETTO MODULE chrome.speedometer; + + SELECT + ts,dur,name,iteration, + format("%.1f", geomean) AS geomean, + format("%.1f", score) AS score + FROM chrome_speedometer_iteration + ORDER BY iteration ASC + """, + out=Csv(""" + "ts","dur","name","iteration","geomean","score" + 693997310311984,7020297000,"iteration-1",1,"254.2","78.7" + 694004414619984,6308034000,"iteration-2",2,"224.4","89.1" + 694010770005984,5878289000,"iteration-3",3,"200.3","99.9" + 694016699502984,5934578000,"iteration-4",4,"201.2","99.4" + 694022683560984,5952163000,"iteration-5",5,"203.0","98.5" + 694028690570984,5966530000,"iteration-6",6,"204.3","97.9" + 694034719276984,5853043000,"iteration-7",7,"200.4","99.8" + 694040637173984,6087435000,"iteration-8",8,"203.0","98.5" + 694046772284984,6040820000,"iteration-9",9,"199.3","100.3" + 694052857814984,6063770000,"iteration-10",10,"208.0","96.2" + """)) + + def test_speedometer_3_score(self): + return DiffTestBlueprint( + trace=DataPath('speedometer_3.perfetto_trace.gz'), + query=""" + INCLUDE PERFETTO MODULE chrome.speedometer; + + SELECT format("%.2f", chrome_speedometer_score()) AS score + """, + out=Csv(""" + "score" + "9.32" + """)) + + def test_speedometer_3_iteration(self): + return DiffTestBlueprint( + trace=DataPath('speedometer_3.perfetto_trace.gz'), query=""" INCLUDE PERFETTO MODULE chrome.speedometer; SELECT - iteration, - ts, - dur, - total, - format('%.1f', mean) AS mean, - format('%.1f', geomean) AS geomean, - format('%.1f', score) AS score, - num_measurements - FROM - chrome_speedometer_iteration, - ( - SELECT iteration, COUNT(*) AS num_measurements - FROM chrome_speedometer_measure - GROUP BY iteration - ) - USING (iteration) - ORDER BY iteration; + ts,dur,name,iteration, + format("%.1f", geomean) AS geomean, + format("%.1f", score) AS score + FROM chrome_speedometer_iteration + ORDER BY iteration ASC """, - out=Path('chrome_speedometer.out')) + out=Csv(""" + "ts","dur","name","iteration","geomean","score" + 303831755756,29237440000,"iteration-0",0,"149.6","6.7" + 333069212756,5852045000,"iteration-1",1,"110.7","9.0" + 338921282756,5128440000,"iteration-2",2,"113.5","8.8" + 344049763756,4640412000,"iteration-3",3,"105.0","9.5" + 348690198756,4790109000,"iteration-4",4,"106.9","9.4" + 353480329756,5150878000,"iteration-5",5,"106.0","9.4" + 358631265756,4825246000,"iteration-6",6,"103.1","9.7" + 363456560756,4447621000,"iteration-7",7,"95.5","10.5" + 367904208756,4566333000,"iteration-8",8,"100.8","9.9" + 372470568756,4301553000,"iteration-9",9,"96.9","10.3" + """)) # CPU power ups def test_cpu_powerups(self): @@ -149,4 +202,4 @@ def test_cpu_powerups(self): 424,2 703,2 708,2 - """)) \ No newline at end of file + """)) 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 bdde2422fb..fcb98a79dc 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 @@ -26,6 +26,25 @@ def test_chrome_frames_with_missed_vsyncs(self): """, out=Path('scroll_jank_v3.out')) + # https://crrev.com/c/5634125 introduces new *ToPresentation slices, + # and a new test trace file was added that contains them. + # TODO(b/341047059): after M128 is rolled out to stable, + # the test using the old trace can be removed. + def test_chrome_frames_with_missed_vsyncs_m128(self): + return DiffTestBlueprint( + trace=DataPath('chrome_input_with_frame_view_new.pftrace'), + query=""" + INCLUDE PERFETTO MODULE chrome.scroll_jank.scroll_jank_v3; + + SELECT + cause_of_jank, + sub_cause_of_jank, + delay_since_last_frame, + vsync_interval + FROM chrome_janky_frames; + """, + out=Path('scroll_jank_v3_new.out')) + def test_chrome_frames_with_missed_vsyncs_percentage(self): return DiffTestBlueprint( trace=DataPath('chrome_input_with_frame_view.pftrace'), @@ -61,26 +80,6 @@ def test_chrome_scrolls(self): 4652,1035868607429926,1517121000,1035868607429926,1035870086449926 """)) - def test_chrome_scroll_intervals(self): - return DiffTestBlueprint( - trace=DataPath('chrome_input_with_frame_view.pftrace'), - query=""" - INCLUDE PERFETTO MODULE chrome.chrome_scrolls; - - SELECT - id, - ts, - dur - FROM chrome_scrolling_intervals - ORDER by id; - """, - out=Csv(""" - "id","ts","dur" - 1,1035865535981926,1255745000 - 2,1035866799527926,1458525000 - 3,1035868607429926,1517121000 - """)) - def test_chrome_scroll_input_offsets(self): return DiffTestBlueprint( trace=DataPath('scroll_offsets_trace_2.pftrace'), @@ -99,11 +98,11 @@ def test_chrome_scroll_input_offsets(self): """, out=Csv(""" "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 + 130,1349914859791,-6.932281,-6.932281 + 132,1349923327791,-32.999954,-39.932235 + 134,1349931893791,-39.999954,-79.932189 + 136,1349940237791,-50.000076,-129.932266 + 138,1349948670791,-57.999939,-187.932205 """)) def test_chrome_janky_event_latencies_v3(self): @@ -126,7 +125,12 @@ def test_chrome_janky_event_latencies_v3(self): """, out=Csv(""" "ts","dur","track_id","name","cause_of_jank","sub_cause_of_jank","delayed_frame_count","frame_jank_ts","frame_jank_dur" - 1035869386651926,60311000,2314,"EventLatency","RendererCompositorQueueingDelay","[NULL]",1,1035869435114926,11847999 + 1035866897893926,49303000,968,"EventLatency","SubmitCompositorFrameToPresentationCompositorFrame","StartDrawToSwapStart",1,1035866935295926,11901000 + 1035868162888926,61845000,1672,"EventLatency","SubmitCompositorFrameToPresentationCompositorFrame","StartDrawToSwapStart",1,1035868212811926,11921999 + 1035868886494926,49285000,2055,"EventLatency","SubmitCompositorFrameToPresentationCompositorFrame","StartDrawToSwapStart",1,1035868923855926,11924000 + 1035869208882926,60201000,2230,"EventLatency","SubmitCompositorFrameToPresentationCompositorFrame","BufferReadyToLatch",1,1035869257151926,11932000 + 1035869319831926,71490000,2287,"EventLatency","SubmitCompositorFrameToPresentationCompositorFrame","StartDrawToSwapStart",1,1035869379377926,11944000 + 1035869386651926,60311000,2314,"EventLatency","RendererCompositorQueueingDelay","[NULL]",1,1035869434949926,12013000 """)) def test_chrome_janky_frame_presentation_intervals(self): @@ -147,7 +151,12 @@ def test_chrome_janky_frame_presentation_intervals(self): """, out=Csv(""" "id","ts","dur","cause_of_jank","sub_cause_of_jank","delayed_frame_count" - 1,1035869435114926,11847999,"RendererCompositorQueueingDelay","[NULL]",1 + 1,1035866935295926,11901000,"SubmitCompositorFrameToPresentationCompositorFrame","StartDrawToSwapStart",1 + 2,1035868212811926,11921999,"SubmitCompositorFrameToPresentationCompositorFrame","StartDrawToSwapStart",1 + 3,1035868923855926,11924000,"SubmitCompositorFrameToPresentationCompositorFrame","StartDrawToSwapStart",1 + 4,1035869257151926,11932000,"SubmitCompositorFrameToPresentationCompositorFrame","BufferReadyToLatch",1 + 5,1035869379377926,11944000,"SubmitCompositorFrameToPresentationCompositorFrame","StartDrawToSwapStart",1 + 6,1035869434949926,12013000,"RendererCompositorQueueingDelay","[NULL]",1 """)) def test_chrome_scroll_stats(self): @@ -169,9 +178,9 @@ def test_chrome_scroll_stats(self): out=Csv(""" "scroll_id","missed_vsyncs","frame_count","presented_frame_count","janky_frame_count","janky_frame_percent" 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 + 4471,1,120,119,1,0.840000 + 4620,1,9,6,1,16.670000 + 4652,4,133,129,4,3.100000 """)) def test_chrome_scroll_jank_intervals_v3(self): @@ -189,7 +198,12 @@ def test_chrome_scroll_jank_intervals_v3(self): """, out=Csv(""" "id","ts","dur" - 1,1035869435114926,11847999 + 1,1035866935295926,11901000 + 2,1035868212811926,11921999 + 3,1035868923855926,11924000 + 4,1035869257151926,11932000 + 5,1035869379377926,11944000 + 6,1035869434949926,12013000 """)) def test_chrome_presented_scroll_offsets(self): return DiffTestBlueprint( @@ -216,6 +230,34 @@ def test_chrome_presented_scroll_offsets(self): 147,1350018935791,-89.808540,-379.559608 """)) + def test_chrome_predictor_metrics(self): + return DiffTestBlueprint( + trace=DataPath('scroll_offsets_trace_2.pftrace'), + query=""" + INCLUDE PERFETTO MODULE chrome.scroll_jank.predictor_error; + + SELECT + scroll_update_id, + present_ts, + delta_y, + prev_delta, + next_delta, + predictor_jank, + delta_threshold + FROM chrome_predictor_error + WHERE scroll_update_id IS NOT NULL + ORDER by present_ts + LIMIT 5; + """, + out=Csv(""" + "scroll_update_id","present_ts","delta_y","prev_delta","next_delta","predictor_jank","delta_threshold" + 132,1349985554791,-16.573090,-6.932281,-107.517273,0.000000,1.200000 + 134,1349996680791,-107.517273,-16.573090,-158.728424,0.000000,1.200000 + 140,1350007850791,-158.728424,-107.517273,-89.808540,0.276306,1.200000 + 147,1350018935791,-89.808540,-158.728424,-47.583618,0.000000,1.200000 + 148,1350030066791,-47.583618,-89.808540,-98.283493,0.687384,1.200000 + """)) + def test_scroll_jank_cause_map(self): return DiffTestBlueprint( trace=TextProto(''), @@ -238,3 +280,30 @@ def test_scroll_jank_cause_map(self): out=Csv(""" "event_latency_stage" """)) + + def test_chrome_scroll_jank_with_pinches(self): + return DiffTestBlueprint( + trace=DataPath('scroll_jank_with_pinch.pftrace'), + query=""" + INCLUDE PERFETTO MODULE chrome.scroll_jank.scroll_jank_v3; + + SELECT + janks.cause_of_jank, + janks.sub_cause_of_jank, + janks.delay_since_last_frame, + janks.event_latency_id, + presented_frames.event_type + FROM chrome_janky_frames janks + LEFT JOIN chrome_presented_gesture_scrolls presented_frames + ON janks.event_latency_id = presented_frames.id + ORDER by event_latency_id; + """, + out=Csv(""" + "cause_of_jank","sub_cause_of_jank","delay_since_last_frame","event_latency_id","event_type" + "SubmitCompositorFrameToPresentationCompositorFrame","StartDrawToSwapStart",22.252000,754,"GESTURE_SCROLL_UPDATE" + "SubmitCompositorFrameToPresentationCompositorFrame","SubmitToReceiveCompositorFrame",22.263000,25683,"GESTURE_SCROLL_UPDATE" + "SubmitCompositorFrameToPresentationCompositorFrame","ReceiveCompositorFrameToStartDraw",22.266000,26098,"GESTURE_SCROLL_UPDATE" + "SubmitCompositorFrameToPresentationCompositorFrame","BufferReadyToLatch",22.262000,40846,"GESTURE_SCROLL_UPDATE" + "BrowserMainToRendererCompositor","[NULL]",22.250000,50230,"GESTURE_SCROLL_UPDATE" + "SubmitCompositorFrameToPresentationCompositorFrame","BufferReadyToLatch",22.267000,50517,"GESTURE_SCROLL_UPDATE" + """)) diff --git a/test/trace_processor/diff_tests/stdlib/common/tests.py b/test/trace_processor/diff_tests/stdlib/common/tests.py index 453fd086e8..d103320d8f 100644 --- a/test/trace_processor/diff_tests/stdlib/common/tests.py +++ b/test/trace_processor/diff_tests/stdlib/common/tests.py @@ -21,32 +21,6 @@ class StdlibCommon(TestSuite): - def test_thread_state_summary(self): - return DiffTestBlueprint( - trace=Path('../../common/synth_1.py'), - query=""" - INCLUDE PERFETTO MODULE deprecated.v42.common.thread_states; - - SELECT - state, - cpu, - dur - FROM thread_state_summary_for_interval( - 25, - 75, - ( - SELECT utid - FROM thread - WHERE name = 'init' - ) - ) - """, - out=Csv(""" - "state","cpu","dur" - "Running",1,50 - "Runnable","[NULL]",25 - """)) - def test_spans_overlapping_dur_intersect_edge(self): return DiffTestBlueprint( trace=TextProto(r""" diff --git a/test/trace_processor/diff_tests/stdlib/counters/tests.py b/test/trace_processor/diff_tests/stdlib/counters/tests.py index 270d074cec..d3ade3eb5c 100644 --- a/test/trace_processor/diff_tests/stdlib/counters/tests.py +++ b/test/trace_processor/diff_tests/stdlib/counters/tests.py @@ -23,7 +23,7 @@ class StdlibCounterIntervals(TestSuite): def test_intervals_counter_leading(self): return DiffTestBlueprint( - trace=DataPath('counters.json'), + trace=DataPath('counters.json'), query=""" INCLUDE PERFETTO MODULE counters.intervals; @@ -42,9 +42,9 @@ def test_intervals_counter_leading(self): SELECT * FROM counter_leading_intervals !(foo); """, out=Csv(""" - "id","ts","track_id","dur","value" - 0,0,1,20,10 - 4,20,1,19980,30 - 1,0,2,10,10 - 3,10,2,19990,20 + "id","ts","dur","track_id","value","next_value","delta_value" + 0,0,20,1,10,30,"[NULL]" + 4,20,19980,1,30,"[NULL]",20 + 1,0,10,2,10,20,"[NULL]" + 3,10,19990,2,20,"[NULL]",10 """)) diff --git a/test/trace_processor/diff_tests/stdlib/cpu/tests.py b/test/trace_processor/diff_tests/stdlib/cpu/tests.py deleted file mode 100644 index 0c32ed3da7..0000000000 --- a/test/trace_processor/diff_tests/stdlib/cpu/tests.py +++ /dev/null @@ -1,84 +0,0 @@ -#!/usr/bin/env python3 -# Copyright (C) 2024 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License a -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from python.generators.diff_tests.testing import Csv, Path, DataPath -from python.generators.diff_tests.testing import DiffTestBlueprint -from python.generators.diff_tests.testing import TestSuite - - -class CpuStdlib(TestSuite): - # Test CPU frequency counter grouping. - def test_cpu_eos_counters_freq(self): - return DiffTestBlueprint( - trace=DataPath('android_cpu_eos.pb'), - query=(""" - INCLUDE PERFETTO MODULE cpu.freq; - select - track_id, - freq, - cpu, - sum(dur) as dur - from cpu_freq_counters - GROUP BY freq, cpu - """), - out=Csv(""" - "track_id","freq","cpu","dur" - 33,614400,0,4755967239 - 34,614400,1,4755971561 - 35,614400,2,4755968228 - 36,614400,3,4755964320 - 33,864000,0,442371195 - 34,864000,1,442397134 - 35,864000,2,442417916 - 36,864000,3,442434530 - 33,1363200,0,897122398 - 34,1363200,1,897144167 - 35,1363200,2,897180154 - 36,1363200,3,897216772 - 33,1708800,0,2553979530 - 34,1708800,1,2553923073 - 35,1708800,2,2553866772 - 36,1708800,3,2553814688 - """)) - - # Test CPU idle state counter grouping. - def test_cpu_eos_counters_idle(self): - return DiffTestBlueprint( - trace=DataPath('android_cpu_eos.pb'), - query=(""" - INCLUDE PERFETTO MODULE cpu.idle; - select - track_id, - idle, - cpu, - sum(dur) as dur - from cpu_idle_counters - GROUP BY idle, cpu - """), - out=Csv(""" - "track_id","idle","cpu","dur" - 0,-1,0,2839828332 - 37,-1,1,1977033843 - 32,-1,2,1800498713 - 1,-1,3,1884366297 - 0,0,0,1833971336 - 37,0,1,2285260950 - 32,0,2,1348416182 - 1,0,3,1338508968 - 0,1,0,4013820433 - 37,1,1,4386917600 - 32,1,2,5532102915 - 1,1,3,5462026920 - """)) diff --git a/test/trace_processor/diff_tests/stdlib/dynamic_tables/tests.py b/test/trace_processor/diff_tests/stdlib/dynamic_tables/tests.py index 60b170c9ad..135a60e885 100644 --- a/test/trace_processor/diff_tests/stdlib/dynamic_tables/tests.py +++ b/test/trace_processor/diff_tests/stdlib/dynamic_tables/tests.py @@ -203,9 +203,10 @@ def test_various_clocks_to_timecode(self): query=""" SELECT TO_TIMECODE(0) AS t0, - TO_TIMECODE(123456789123456789) AS tN + TO_TIMECODE(3599123456789) AS t1, + TO_TIMECODE(3600123456789) AS t2 """, out=Csv(""" - "t0","tN" - "00:00:00 000 000 000","33:33:09 123 456 789" + "t0","t1","t2" + "00:00:00 000 000 000","00:59:59 123 456 789","01:00:00 123 456 789" """)) diff --git a/test/trace_processor/diff_tests/stdlib/export/firefox_profile.out b/test/trace_processor/diff_tests/stdlib/export/firefox_profile.out new file mode 100644 index 0000000000..bf896f90da --- /dev/null +++ b/test/trace_processor/diff_tests/stdlib/export/firefox_profile.out @@ -0,0 +1,2 @@ +"export_to_firefox_profile()" +"{"meta":{"interval":1,"startTime":0,"processType":0,"categories":[{"name":"Other","color":"grey","subcategories":["Other"]}],"product":"Perfetto","stackwalk":1,"version":29,"preprocessedProfileVersion":48,"markerSchema":[],"sampleUnits":{"time":"ms","eventDelay":"ms","threadCPUDelta":"µs"}},"libs":[],"pages":null,"counters":null,"profilerOverhead":null,"threads":[{"processType":"default","name":"swapper","isMainThread":0,"pid":0,"tid":0,"samples":{"stack":[],"time":[],"weight":null,"weightType":"samples","length":0},"markers":{"data":[],"name":[],"startTime":[],"endTime":[],"phase":[],"category":[],"length":0},"stackTable":{"frame":[],"category":[],"subcategory":[],"prefix":[],"length":0},"frameTable":{"address":[],"inlineDepth":[],"category":[],"subcategory":[],"func":[],"nativeSymbol":[],"innerWindowID":[],"implementation":[],"line":[],"column":[],"length":0},"stringArray":[],"funcTable":{"name":[],"isJS":[],"relevantForJS":[],"resource":[],"fileName":[],"lineNumber":[],"columnNumber":[],"length":0},"resourceTable":{"length":0,"lib":[],"name":[],"host":[],"type":[]},"nativeSymbols":{"libIndex":[],"address":[],"name":[],"functionSize":[],"length":0}},{"processType":"default","name":"callstack_gen","isMainThread":0,"pid":21596,"tid":21596,"samples":{"stack":[7,9,11,32,35,37,39,41,44,46,48,51,53,55,57,58,60,63,65,67,86,89,91,93,95,98,100,102,102,105,107,108,109,110,112,112,7,9,114,117,119,121,39,123,126,128,130,133,135,53,136,57,138,141,144,146,148,151,153,154,156,159,161,177,102,105,179,180,109,181,181,183,186,188,114,190,35,192,193,195,198,126,200,202,205,135,221,222,57,234,141,237,239,241,244,153,153,245,95,248,161,250,102,105,252,252,253,254,255,183,258,260,190,262,265,37,39,195,268,271,128,273,276,53,222,57,277,279,63,281,67,284,153,286,287,156,290,292,294,296,299,252,108,254,255,112,301,7,303,305,308,121,37,193,41,126,310,48,313,133,315,136,316,138,319,146,148,89,344,153,245,346,290,100,102,349,349,179,350,109,255,352,258,354,356,305,265,358,39,41,271,128,360,133,53,379,136,57,279,141,146,148,244,153,245,380,95,383,250,102,386,349,388,108,254,181,112,112,391,393,190,265,121,395,396,195,44,200,48,276,53,53,136,397,60,63,65,399,89,244,286,400,402,383,250,102,102,349,179,403,254,254,404,183,186,9,305,308,121,121,193,195,126,200,406,133,409,411,412,397,414,237,146,148,399,244,153,400,95,417,292,419,421,424,179,425,180,109,255,112,428,303,190,262,265,358,429,431,126,451,128,130,276,135,452,397,279,481,237,483,399,244,286,506,287,95,159,250,102,509,105,179,403,510,404,112,535,258,303,190,538,37,396,431,431,44,540,130,543,53,135,452,544,279,237,483,148,547,550,286,245,95,248,250,102,349,349,552,403,254,181,112,186,554,578,305,265,358,39,195,271,310,48,205,53,579,452,57,279,63,146,581,244,286,582,95,95,98,419,584,349,179,403,585,109,586,183,186,9,588,538,121,193,431,126,608,46,130,205,610,412,611,613,141,65,148,148,89,153,287,95,159,292,102,105,105,179,403,254,255,183,616,188,645,190,308,358,39,195,271,200,48,276,53,646,452,397,613,649,65,651,89,653,287,95,95,159,250,102,105,179,179,403,654,655,112,391,188,9,190,658,121,659,431,44,126,661,130,276,315,222,662,138,279,141,146,67,547,286,286,93,156,159,250,102,665,668,252,403,109,181,112,670,258,393,190,265,358,193,193,195,126,672,202,276,53,411,222,397,674,141,146,704,148,244,153,245,706,383,419,250,102,105,179,728,180,254,729,112,258,9,190,538,538,119,39,195,198,310,202,133,53,579,397,397,613,237,239,148,732,153,733,706,290,250,250,102,349,252,108,254,255,181,352,258,9,190,538,121,39,735,44,737,739,130,205,741,136,397,138,141,146,399,744,153,91,245,95,747,250,102,750,752,179,403,254,753,183,258,9,305,756,358,193,39,123,44,200,48,276,315,757,57,782,279,237,784,786,550,91,582,95,383,250,102,105,252,179,787,109,181,789,792,186,9,305,308,192,193,123,126,271,794,130,276,53,136,58,397,60,237,65,67,244,91,816,733,95,290,161,102,349,509,179,403,254,181,183,819,391,188,190,265,37,193,821,44,126,310,130,824,411,222,397,279,237,65,146,148,284,153,245,706,290,292,826,105,105,828,180,254,255,112,7,851,9,114,265,853,854,123,857,128,130,276,824,53,222,397,60,141,146,148,860,874,153,287,876,290,250,878,881,752,179,403,109,181,183,258,188,305,265,883,121,884,195,271,310,130,276,53,757,316,279,141,901,146,399,89,653,245,156,904,250,102,102,105,179,108,905,255,255,183,7,393,588,265,358,193,39,123,908,310,48,276,53,53,136,57,910,63,784,148,244,244,153,245,156,913,292,296,102,349,179,108,254,404,181,915,428,303,917,265,121,918,195,431,126,310,920,276,53,921,57,279,237,63,483,148,550,153,922,924,290,250,102,102,105,179,108,109,255,183,7,258,303,114,308,121,925,431,126,310,130,276,276,315,222,397,613,237,483,241,241,928,286,287,95,383,292,930,349,105,932,403,933,934,112,7,9,190,265,121,121,39,195,126,310,920,276,53,53,136,935,279,63,65,67,860,91,287,95,876,159,250,937,105,179,403,403,933,181,112,7,303,114,538,265,939,193,431,942,310,130,276,276,411,222,662,138,141,65,148,399,244,91,245,156,98,292,419,102,349,179,967,350,109,181,352,970,9,11,973,308,121,659,975,44,46,48,48,276,315,136,57,279,978,65,399,550,244,91,287,156,290,419,102,105,105,107,403,254,181,112,258,9,114,308,981,37,193,431,984,200,130,130,276,135,985,397,279,63,146,399,89,244,91,986,95,98,419,102,349,349,252,108,109,729,112,7,391,393,190,265,358,39,988,44,310,920,276,991,135,222,992,994,141,146,148,244,732,91,245,95,159,250,102,665,105,179,403,254,995,352,186,997,9,305,117,121,39,195,44,200,1012,130,205,1014,136,992,138,63,141,146,399,860,153,1015,924,95,159,292,102,1018,105,179,403,1019,181,112,112,258,303,190,265,192,1020,1022,1025,857,128,130,1028,135,222,1029,613,1032,649,146,148,284,286,154,876,290,292,1051,294,349,107,403,109,110,112,183,258,303,588,538,121,193,1053,126,1071,200,130,991,135,222,1072,279,141,65,148,1077,244,153,245,1079,290,292,1081,881,105,252,1082,109,110,112,186,393,9,305,265,121,659,195,44,310,130,133,409,411,1083,397,613,141,1085,786,284,1087,287,346,876,290,161,102,105,932,350,510,1088,255,789,616,303,190,538,658,121,193,975,126,128,130,133,135,1089,1090,1090,138,144,239,399,1093,91,245,156,159,250,102,102,349,179,108,933,181,1095,258,186,303,1097,265,121,1098,41,44,1100,1110,130,409,315,985,397,1112,237,146,67,284,89,1114,245,156,383,161,102,105,252,179,180,109,181,352,970,303,588,538,119,193,193,431,44,1116,920,276,135,136,662,138,141,65,483,67,860,286,287,876,290,161,102,105,299,179,403,109,255,112,352,391,393,262,658,121,429,195,195,44,200,920,409,610,136,57,57,279,649,281,148,89,653,1118,287,706,159,161,102,1081,349,252,1082,933,254,181,670,258,303,917,265,1121,121,39,1123,126,200,1125,130,133,53,222,397,613,141,1150,483,148,244,91,245,95,1153,98,1155,102,105,105,179,403,933,181,352,183,258,9,114,538,121,1175,1020,735,126,310,1177,1180,135,222,57,674,279,1183,146,67,244,153,287,706,417,1153,250,102,105,179,350,254,1184,181,112,792,393,305,1187,883,193,195,126,198,310,1177,1190,315,452,397,138,63,483,67,284,244,153,287,95,98,161,421,349,105,252,1191,109,404,112,7,303,190,308,1121,883,39,195,126,310,920,276,53,222,662,613,138,141,65,148,860,653,400,95,747,161,102,421,349,179,403,109,1192,352,7,9,9,588,1187,121,193,123,1195,46,920,130,1028,135,222,57,613,63,65,239,148,244,153,1196,876,290,159,1198,102,349,252,107,108,254,404,183,258,391,1200,305,265,121,193,195,195,126,310,48,276,135,222,57,613,237,237,65,148,860,153,380,876,98,1202,102,102,349,179,403,1203,181,670,186,186,1205,190,308,358,193,1207,271,310,130,276,276,135,136,1072,279,141,784,148,284,153,245,287,156,290,419,878,105,179,108,403,1019,255,1209,258,9,588,658,1228,37,1229,195,126,128,1231,276,135,757,57,60,141,141,65,148,89,153,287,95,98,250,102,1233,105,179,1234,654,181,183,391,9,11,588,35,121,193,123,1237,310,48,133,409,1239,222,57,60,237,65,241,399,244,1241,245,876,159,250,102,826,881,752,905,905,655,112,391,393,588,190,35,358,193,195,1237,310,310,130,1244,411,136,397,279,1183,63,146,148,550,153,287,876,159,250,102,102,105,1246,108,254,181,915,7,186,9,190,1249,121,1098,431,126,46,48,130,276,53,136,397,279,63,146,67,89,653,287,245,876,290,250,102,105,252,388,108,254,255,352,258,260,588,265,121,1251,39,195,44,200,130,276,1014,136,397,58,613,63,483,241,744,91,986,706,383,250,102,102,105,1253,108,1254,181,112,7,1263,9,11,265,121,1264,431,126,310,130,202,1244,411,222,397,279,319,1266,67,67,284,286,245,876,290,250,250,1081,349,179,108,403,109,181,1268,1271,303,305,190,658,1273,39,123,1195,200,1293,360,276,53,222,57,279,141,1299,65,67,284,153,400,156,159,419,1327,102,105,252,1328,254,404,301,183,428,303,305,265,1330,39,41,1237,200,920,409,276,53,136,611,279,237,483,67,244,1241,287,876,876,98,250,102,105,252,403,109,254,110,112,7,9,190,538,883,193,195,44,310,310,1332,205,1014,222,57,613,63,1334,241,244,244,653,287,95,98,250,102,102,750,252,403,254,181,352,1337,303,9,190,973,358,193,123,271,46,130,205,1360,135,55,397,138,141,65,148,241,89,653,245,156,290,250,250,421,349,252,403,108,254,181,112,258,303,190,756,538,121,39,431,1363,540,920,130,276,135,136,397,1365,237,141,146,241,244,153,380,95,290,98,250,102,105,179,252,108,254,181,112,7,1387,9,190,658,121,396,431,735,44,310,130,205,53,1404,579,57,138,63,146,67,148,89,286,582,156,290,250,102,102,105,179,1405,254,109,586,112,7,188,190,308,1426,121,39,1428,126,46,1440,202,276,53,136,397,279,141,141,146,148,89,653,1441,95,159,250,102,102,668,252,403,585,255,352,1444,7,303,1446,265,192,193,1428,126,794,202,543,409,135,55,57,910,141,483,67,928,286,922,876,1458,159,250,102,105,1246,252,108,254,255,301,7,1460,114,1474,1249,1488,1489,195,126,44,46,130,205,1014,136,57,397,613,141,239,244,653,153,287,95,290,161,102,102,386,552,403,254,255,112,352,428,9,190,538,358,884,431,1195,739,200,130,276,53,1083,57,279,141,146,281,67,244,286,733,95,159,383,419,1491,105,179,179,108,254,1492,112,258,1200,1494,190,308,192,39,123,126,200,1502,130,205,53,222,397,613,279,63,65,148,284,153,245,876,290,1521,1523,102,1526,252,1234,109,181,181,112,792,9,190,35,395,39,123,126,310,310,920,205,53,452,57,279,1529,1531,1533,284,244,653,287,876,290,161,1491,105,105,828,403,254,404,670,391,9,588,117,1542,358,39,431,1237,200,130,409,1239,222,1543,279,1546,141,483,241,89,91,287,876,383,292,584,937,105,252,108,933,181,112,186,9,1551,305,1121,37,1489,195,126,310,48,205,205,53,222,662,279,1183,483,148,284,244,91,1196,1553,290,1523,161,102,349,252,403,254,254,181,183,258,303,190,308,538,121,1098,195,857,46,48,1578,205,741,1579,57,279,1582,1587,483,148,89,153,245,156,98,290,419,102,665,179,108,109,1019,655,112,7,188,190,308,358,193,431,44,271,46,130,205,411,136,397,613,141,144,65,148,284,153,380,706,159,419,102,102,105,179,108,1588,255,1209,186,186,9,305,265,1590,918,123,908,310,1592,409,205,411,222,397,279,141,146,148,89,653,287,95,876,98,161,102,105,252,1597,403,109,181,112,258,9,190,1249,358,121,193,195,126,737,130,543,53,55,57,1365,138,63,146,148,284,153,245,287,1599,98,161,102,299,105,1601,108,254,181,112,258,258,9,190,265,358,193,195,1237,271,310,48,276,315,136,397,935,279,63,65,148,284,1612,653,400,95,383,292,102,102,105,179,403,254,1613,255,352,258,9,114,265,121,1621,1020,431,1025,1623,1177,276,53,53,136,397,279,1529,146,1625,244,244,653,287,706,98,250,102,102,349,179,403,109,1626,255,112,391,393,305,265,121,192,193,195,44,200,920,205,1647,53,757,57,279,1650,146,148,244,244,1652,245,95,98,250,1654,349,349,1656,403,254,181,301,7,9,114,35,265,1273,193,195,271,672,130,276,53,1657,1658,57,279,63,146,67,284,153,153,380,876,290,419,102,105,932,252,108,254,181,352,970,1200,190,1249,358,39,193,195,268,310,202,991,53,222,1659,613,237,1687,65,148,284,286,287,876,98,98,250,102,105,179,350,1688,255,255,112,258,260,114,35,358,918,1428,268,46,310,130,133,411,452,992,1690,237,483,241,1693,244,653,287,95,290,250,102,102,349,252,1234,933,934,1694,183,1444,9,305,981,37,39,193,123,1237,46,920,276,53,452,452,613,237,1266,239,241,89,153,400,95,1715,159,250,102,1755,105,252,1082,109,995,1757,258,1763,9,114,1121,358,39,1123,126,672,200,202,276,741,136,1072,613,141,65,67,1766,1087,91,245,1553,1153,250,102,252,179,108,109,181,183,1769,9,190,114,265,37,193,41,271,200,130,409,205,315,222,1090,1690,63,1771,241,732,1652,287,876,706,383,250,1773,349,179,108,254,109,729,183,1769,9,190,35,121,193,195,1776,200,46,920,276,53,222,397,1690,141,1334,1778,284,547,153,1015,1780,159,1198,102,105,349,179,108,109,1694,670,112,7,260,190,308,37,429,41,431,126,310,130,276,135,222,136,57,279,141,1782,1784,244,653,286,287,95,290,1786,102,424,1789,252,108,109,181,112,391,303,1446,190,1792,358,193,195,271,46,48,991,135,1808,222,397,138,649,65,241,89,1810,245,156,876,290,250,584,349,179,403,654,109,255,112,391,9,305,1121,1330,193,431,268,1363,310,202,276,1014,222,57,138,141,65,1822,399,1825,91,245,95,1828,250,102,349,105,252,403,109,255,670,7,1833,1460,190,265,121,39,195,268,128,1332,130,276,1835,222,1090,279,319,483,148,244,153,286,245,95,290,250,102,105,349,252,108,109,181,183,258,258,303,190,538,358,39,123,431,44,310,48,205,610,136,1859,397,910,144,483,1778,244,153,286,245,156,290,161,102,105,349,179,403,254,255,112,186,258,1861,190,121,854,193,123,1237,310,130,130,276,53,412,397,613,1582,237,483,399,244,153,1862,876,706,1865,419,102,105,252,179,403,654,181,670,1271,188,9,190,538,121,193,195,126,126,310,1332,543,135,1866,277,1867,613,141,146,67,860,153,287,287,95,290,250,1869,105,105,179,403,109,181,183,258,1879,1881,356,538,121,39,431,1237,310,1892,48,276,315,222,397,279,144,146,148,67,1093,153,245,95,159,419,102,386,105,107,350,1254,181,670,7,9,305,190,265,37,1489,195,271,1894,1332,133,315,610,55,397,279,63,146,399,284,153,287,287,876,290,250,102,105,1246,179,108,109,181,112,391,393,190,538,37,37,925,41,126,200,130,313,53,136,136,57,613,144,483,148,89,153,1895,287,346,159,250,296,509,252,179,403,1203,753,352,186,9,9,114,756,121,925,431,126,271,310,130,991,53,579,57,279,613,144,146,399,860,91,287,706,156,159,1897,102,105,179,179,403,109,255,670,1900,303,190,308,1909,37,193,123,908,310,273,276,610,136,57,1072,1112,141,65,241,244,153,245,95,904,1897,294,102,105,1253,350,1203,1910,112,186,428,393,190,308,37,193,195,126,672,1935,130,276,411,136,57,910,1938,65,148,244,1766,153,733,156,290,161,102,1789,107,932,403,109,181,183,970,188,1940,538,37,37,39,195,1237,310,130,409,53,222,57,1365,613,63,146,67,284,286,287,876,159,161,250,102,105,252,108,109,109,655,352,258,354,190,308,308,192,39,1022,126,1623,130,409,1943,53,136,57,613,141,1334,1965,148,151,653,287,95,1828,290,292,102,349,252,179,108,254,255,301,258,1975,9,1977,1187,358,193,431,195,44,1979,48,409,135,136,222,57,279,63,1981,67,244,91,1087,1862,95,98,419,102,349,105,252,108,254,254,255,112,258,393,190,265,192,121,193,195,44,310,48,276,1239,53,452,544,138,144,1531,241,244,244,153,287,1780,98,161,102,102,105,252,403,109,181,181,352,258,9,588,35,121,2003,39,195,908,672,202,205,2004,53,222,1659,279,141,146,241,651,89,91,245,156,290,161,1786,1081,386,179,403,2005,404,995,183,186,260,588,308,121,39,195,268,672,2007,2009,205,53,136,397,613,1183,146,148,89,153,286,733,876,159,161,102,105,252,179,108,109,181,352,186,393,114,35,358,2024,2025,195,126,46,130,276,53,136,397,279,63,2034,483,241,284,286,287,95,383,250,102,102,349,179,403,254,255,670,301,7,188,190,1792,358,193,195,126,310,48,130,133,411,222,57,138,63,65,399,860,1766,286,245,2036,290,2038,102,930,349,252,108,254,109,995,2040,7,9,190,308,121,37,1098,195,126,310,130,205,53,53,222,2041,279,63,146,1778,547,244,153,2042,95,290,1523,102,102,105,252,108,654,109,181,2044,7,9,190,981,192,192,39,1053,126,200,2046,205,2047,53,1866,57,613,2050,146,148,241,244,153,245,156,159,250,250,102,105,252,2051,1588,109,255,183,7,303,190,265,121,39,2052,431,1237,128,130,276,53,412,316,60,141,2055,146,2057,89,1810,287,706,290,292,102,930,105,2059,1191,254,1492,112,391,303,2078,305,265,358,39,123,2081,310,1231,409,53,53,136,277,2083,141,483,241,89,153,2103,245,95,248,292,102,349,252,2125,403,254,181,352,258,9,190,308,37,358,39,431,126,46,130,205,135,136,935,138,1938,2127,146,241,244,153,245,402,747,250,102,102,386,932,350,109,181,255,183,616,303,190,538,121,1489,123,1123,1237,1100,2009,205,53,2135,222,57,2083,1582,146,399,89,2156,286,986,95,290,2158,102,2162,349,179,108,109,404,255,352,258,9,190,538,358,39,195,1237,126,739,48,276,53,1089,544,138,141,146,651,67,89,653,287,2164,98,161,1773,421,105,179,108,933,181,183,7,303,190,190,265,37,39,195,126,46,48,409,53,136,2173,935,279,63,146,241,284,653,287,95,156,98,250,2175,105,252,108,1328,2005,934,112,258,9,1977,308,358,193,2177,44,1894,1894,48,409,135,222,1543,2179,63,239,241,244,89,286,733,2181,290,161,2175,105,349,252,1191,109,255,112,7,2190,393,2192,35,939,39,431,123,271,794,130,276,1014,2198,757,57,279,63,65,67,241,1093,153,245,706,98,1523,102,102,105,2200,108,109,1203,1910,112,258,1460,114,538,37,39,429,195,126,310,130,205,53,222,1072,2202,237,237,146,148,244,653,2203,156,2206,161,102,878,299,179,403,1088,255,112,7,9,2212,190,973,121,39,195,44,1623,130,276,53,136,222,397,279,237,146,241,244,153,582,95,290,2242,419,102,349,107,403,109,181,255,112,186,9,588,1249,121,2243,195,1025,44,46,202,205,53,222,57,138,141,146,2249,148,89,153,287,95,159,161,102,2252,105,1246,108,109,181,183,186,393,1205,190,973,358,193,1022,271,46,46,920,133,610,222,57,674,613,141,65,651,244,653,287,876,156,913,292,102,881,752,252,2253,109,655,301,7,303,9,262,265,853,193,431,271,310,130,2259,1190,53,1083,1072,279,141,146,148,284,2283,653,2284,95,159,250,584,105,179,252,1082,1588,255,112,258,9,190,305,35,192,39,195,126,128,48,1943,53,452,2296,1029,279,1938,146,67,732,1114,287,156,290,2327,250,102,105,179,108,109,933,181,112,7,188,114,265,37,193,41,126,1237,46,130,276,53,136,397,613,141,146,148,241,244,153,287,924,2206,292,102,2330,179,179,403,254,181,183,258,303,588,308,265,121,193,41,126,46,737,130,276,53,2334,222,397,138,63,65,241,928,244,653,245,156,417,2337,419,2339,105,252,2345,108,254,255,183,7,9,1977,265,538,121,918,195,44,310,2350,130,205,135,2351,935,613,144,146,148,244,153,153,287,876,290,292,102,105,252,179,108,254,729,670,258,260,190,265,538,119,39,195,44,200,130,205,205,53,136,57,138,141,146,241,89,153,287,380,876,290,250,102,299,179,787,108,254,255,112,258,393,305,265,121,193,41,123,271,661,202,205,53,55,1659,613,237,1531,148,148,1825,1810,287,876,98,419,102,349,2380,932,403,109,1492,2382,258,9,190,2386,308,358,193,1022,195,126,310,130,409,53,222,136,57,414,237,1085,67,89,1118,653,2042,95,290,250,102,2394,105,179,403,254,181,352,112,391,1200,190,265,121,193,39,41,942,1979,130,276,411,135,136,57,613,1938,483,148,284,286,245,706,95,98,161,102,750,1253,108,2401,933,2402,112,7,188,305,35,192,193,193,195,198,128,360,409,53,136,397,613,141,483,1531,399,89,153,287,876,98,161,102,881,509,179,108,254,181,352,258,9,190,265,265,37,39,195,271,200,920,205,135,412,397,1112,279,63,146,241,89,286,245,95,98,292,102,102,2405,179,403,1088,181,112,428,303,9,11,265,37,39,41,271,310,310,130,991,135,222,397,279,144,141,2407,148,89,91,287,95,156,1828,250,102,349,179,828,403,254,1613,1209,258,9,2414,190,538,121,193,123,126,44,540,48,205,315,1089,57,1112,63,2428,146,241,284,153,245,95,98,250,102,105,105,179,253,109,181,112,186,9,190,356,308,192,39,2430,126,46,1231,276,53,2435,222,397,414,2050,483,148,89,286,287,706,98,250,292,1233,349,179,403,109,586,112,112,7,1861,305,265,37,925,1428,126,126,200,130,276,2437,136,57,138,237,146,148,2451,284,91,400,95,290,1523,102,105,252,2453,108,254,1694,670,7,303,1446,265,853,2456,39,431,198,310,130,409,1014,579,1658,2041,674,144,65,148,244,286,1810,154,95,98,250,102,2330,750,179,1082,254,255,352,2459,258,9,305,538,121,39,123,735,126,46,360,409,610,757,316,397,138,1650,1531,148,284,286,380,93,876,290,250,1773,668,252,2200],"time":[38623894771186,38623895026761,38623895277493,38623895526802,38623895776232,38623896025703,38623896275133,38623896524686,38623896774238,38623897023831,38623897273017,38623897522773,38623897772895,38623898024523,38623898273343,38623898522733,38623898772529,38623899022407,38623899272285,38623899522122,38623899771960,38623900021878,38623900271878,38623900521878,38623900771756,38623901021634,38623901271837,38623901522000,38623901772082,38623902022285,38623902272448,38623902522651,38623902772733,38623903022814,38623903272895,38623903522855,38623903773017,38623904023140,38623904273465,38623904523791,38623904773831,38623905023669,38623905273953,38623905523994,38623905773913,38623906023953,38623906273953,38623906523872,38623906773791,38623907023669,38623907273669,38623907523709,38623907773669,38623908023669,38623908273384,38623908523506,38623908773628,38623909023587,38623909273953,38623909542467,38623909805325,38623910065498,38623910324043,38623910581408,38623910837755,38623911093086,38623911347887,38623911601916,38623911855252,38623912136665,38623912388984,38623912637967,38623912886543,38623913134753,38623913384712,38623913633857,38623913883206,38623914132718,38623914382189,38623914631782,38623914881294,38623915130928,38623915380684,38623915630480,38623915880277,38623916130073,38623916379870,38623916629910,38623916879829,38623917148791,38623917381701,38623917629219,38623917879219,38623918129463,38623918379666,38623918630032,38623918880032,38623919130277,38623919380399,38623919630439,38623919880480,38623920142117,38623920380032,38623920616645,38623920854805,38623921094469,38623921335965,38623921578315,38623921821683,38623922065986,38623922311144,38623922557319,38623922803983,38623923050727,38623923298164,38623923545967,38623923794095,38623924042467,38623924290921,38623924539578,38623924788724,38623925037910,38623925287462,38623925536852,38623925786323,38623926035794,38623926285184,38623926534777,38623926784533,38623927034248,38623927284126,38623927533882,38623927783678,38623928033475,38623928283393,38623928533271,38623928783231,38623929033109,38623929283475,38623929533109,38623929782539,38623930032865,38623930282783,38623930532661,38623930782458,38623931032336,38623931282091,38623931532051,38623931782010,38623932032010,38623932281969,38623932531929,38623932781766,38623933031807,38623933284736,38623933534940,38623933784696,38623934034451,38623934284167,38623934533841,38623934783719,38623935033638,38623935283516,38623935533434,38623935783393,38623936033434,38623936283434,38623936533312,38623936783231,38623937033353,38623937283678,38623937533597,38623937812324,38623938062365,38623938309150,38623938555773,38623938802803,38623939050361,38623939298205,38623939546374,38623939794909,38623940043647,38623940292508,38623940541572,38623940790718,38623941039985,38623941289660,38623941539009,38623941788480,38623942037869,38623942287381,38623942536974,38623942786649,38623943036364,38623943286079,38623943535672,38623943785876,38623944036120,38623944286120,38623944535998,38623944785835,38623945035713,38623945311144,38623945574084,38623945833564,38623946091296,38623946348091,38623946603828,38623946858833,38623947113024,38623947366523,38623947619534,38623947872220,38623948124417,38623948376330,38623948627917,38623948879219,38623949152534,38623949381823,38623949630114,38623949881294,38623950132515,38623950383817,38623950634630,38623950885526,38623951136217,38623951386950,38623951637397,38623951887926,38623952138252,38623952388577,38623952637967,38623952889106,38623953139391,38623953389757,38623953639717,38623953889798,38623954139676,38623954389066,38623954639635,38623954889595,38623955138862,38623955389513,38623955639595,38623955889473,38623956139432,38623956389310,38623956639310,38623956889310,38623957139513,38623957389595,38623957639635,38623957889676,38623958139798,38623958389920,38623958640002,38623958890083,38623959140164,38623959390246,38623959640164,38623959890246,38623960140286,38623960390327,38623960640449,38623960890531,38623961140490,38623961390571,38623961640571,38623961890734,38623962140856,38623962390856,38623962640775,38623962890815,38623963140815,38623963390612,38623963640531,38623963890571,38623964140531,38623964390490,38623964640449,38623964890408,38623965139880,38623965390653,38623965640653,38623965890775,38623966140653,38623966390490,38623966640449,38623966890449,38623967140531,38623967390531,38623967640571,38623967890571,38623968140408,38623968390368,38623968640408,38623968890490,38623969140693,38623969390693,38623969640978,38623969891426,38623970141344,38623970391263,38623970641141,38623970891100,38623971140978,38623971390815,38623971640734,38623971890856,38623972140937,38623972390937,38623972640856,38623972890856,38623973140897,38623973391182,38623973641100,38623973890937,38623974140897,38623974390856,38623974640815,38623974890571,38623975140490,38623975390531,38623975640449,38623975890531,38623976140408,38623976390408,38623976640449,38623976890531,38623977140775,38623977390653,38623977640490,38623977890368,38623978140124,38623978389920,38623978639839,38623978889635,38623979139635,38623979389554,38623979639473,38623979889310,38623980139310,38623980389310,38623980639351,38623980899727,38623981137967,38623981374946,38623981613187,38623981852892,38623982094266,38623982336901,38623982580635,38623982825223,38623983070666,38623983316800,38623983563382,38623983810452,38623984058092,38623984306017,38623984554268,38623984802721,38623985051460,38623985300646,38623985549792,38623985799059,38623986048490,38623986298490,38623986548490,38623986799141,38623987047432,38623987297513,38623987547269,38623987796292,38623988046618,38623988296414,38623988546089,38623988795763,38623989045641,38623989295845,38623989545885,38623989795926,38623990046007,38623990295967,38623990545967,38623990796089,38623991046007,38623991295885,38623991545885,38623991795926,38623992046007,38623992295926,38623992546007,38623992795926,38623993046007,38623993296211,38623993546048,38623993795845,38623994045763,38623994295601,38623994545478,38623994795438,38623995045438,38623995295275,38623995545194,38623995795112,38623996044990,38623996295072,38623996545112,38623996795072,38623997045112,38623997294583,38623997545356,38623997795682,38623998045845,38623998296333,38623998546414,38623998796130,38623999045885,38623999296170,38623999546170,38623999796170,38624000046130,38624000296170,38624000546170,38624000796130,38624001046007,38624001296130,38624001546252,38624001796414,38624002046618,38624002295885,38624002546699,38624002796862,38624003049873,38624003296862,38624003546455,38624003796414,38624004046455,38624004296414,38624004546414,38624004796414,38624005046536,38624005296699,38624005546455,38624005796414,38624006072782,38624006335843,38624006595405,38624006853258,38624007110013,38624007366157,38624007621121,38624007875272,38624008128771,38624008381742,38624008634386,38624008885811,38624009138455,38624009390734,38624009641385,38624009893053,38624010143379,38624010394884,38624010645535,38624010896064,38624011146553,38624011396878,38624011647163,38624011897407,38624012147733,38624012397895,38624012648058,38624012898343,38624013154040,38624013404284,38624013653633,38624013903022,38624014152697,38624014402575,38624014652249,38624014902005,38624015151802,38624015401558,38624015651232,38624015901069,38624016150988,38624016400907,38624016650825,38624016900784,38624017150866,38624017400866,38624017650662,38624017900622,38624018150581,38624018400500,38624018650337,38624018900256,38624019150011,38624019400256,38624019650215,38624019900174,38624020150093,38624020399971,38624020650011,38624020900011,38624021150093,38624021400378,38624021650540,38624021900622,38624022150500,38624022400540,38624022650418,38624022900337,38624023150296,38624023400296,38624023650174,38624023900052,38624024150093,38624024400011,38624024649971,38624024900011,38624025150215,38624025400174,38624025651395,38624025901395,38624026150500,38624026401110,38624026650988,38624026901273,38624027159777,38624027397326,38624027634102,38624027872301,38624028112251,38624028368843,38624028596260,38624028838122,38624029083239,38624029329251,38624029575264,38624029821886,38624030068631,38624030316230,38624030564277,38624030812446,38624031060819,38624031309476,38624031558418,38624031807238,38624032056343,38624032305488,38624032554797,38624032804186,38624033053576,38624033303291,38624033553250,38624033803128,38624034053006,38624034302925,38624034552925,38624034802762,38624035052681,38624035302559,38624035552396,38624035802274,38624036052152,38624036302111,38624036552070,38624036802111,38624037052070,38624037302355,38624037552396,38624037802314,38624038052314,38624038302355,38624038552437,38624038802518,38624039052599,38624039302599,38624039552599,38624039802599,38624040052559,38624040302640,38624040552477,38624040802518,38624041052599,38624041302925,38624041552721,38624041802437,38624042052274,38624042302111,38624042551948,38624042801867,38624043051704,38624043301541,38624043551297,38624043801338,38624044051175,38624044301134,38624044551094,38624044801094,38624045051134,38624045301338,38624045551379,38624045801460,38624046051582,38624046301663,38624046551704,38624046801785,38624047051989,38624047302030,38624047552030,38624047802152,38624048052111,38624048302030,38624048552152,38624048802192,38624049052274,38624049302396,38624049552559,38624049802640,38624050052518,38624050302477,38624050552274,38624050802437,38624051052396,38624051302437,38624051552437,38624051802396,38624052052437,38624052328315,38624052591540,38624052851183,38624053366239,38624053623400,38624053863919,38624054103625,38624054344469,38624054586738,38624054830065,38624055074409,38624055319608,38624055565539,38624055811917,38624056058988,38624056306506,38624056553779,38624056802437,38624057050931,38624057299141,38624057547798,38624057797513,38624058046862,38624058296130,38624058545479,38624058794827,38624059044298,38624059293810,38624059543363,38624059792996,38624060042793,38624060292630,38624060542467,38624060792264,38624061042183,38624061292183,38624061542549,38624061792508,38624062042549,38624062292752,38624062543159,38624062801623,38624063039253,38624063276029,38624063514391,38624063754382,38624063995796,38624064238512,38624064482205,38624064726875,38624064972358,38624065218818,38624065464465,38624065711942,38624065959500,38624066207262,38624066455594,38624066703763,38624066952339,38624067201118,38624067450060,38624067699084,38624067948351,38624068197660,38624068447131,38624068696520,38624068946032,38624069195869,38624069445747,38624069695462,38624069945299,38624070195177,38624070444933,38624070694811,38624070944730,38624071194648,38624071444567,38624071694404,38624071944364,38624072194242,38624072444242,38624072694160,38624072944201,38624073194445,38624073444323,38624073694486,38624073944404,38624074194404,38624074444404,38624074694404,38624074944282,38624075223172,38624075473172,38624075719998,38624075966743,38624076213732,38624076461291,38624076709053,38624076957222,38624077206164,38624077454699,38624077703519,38624077952339,38624078201200,38624078450304,38624078699816,38624078949206,38624079198636,38624079448148,38624079697782,38624079947415,38624080197090,38624080446805,38624080696357,38624080946235,38624081196235,38624081446357,38624081696479,38624081946520,38624082196520,38624082446642,38624082696642,38624082946683,38624083196602,38624083446276,38624083696561,38624083946846,38624084196846,38624084446927,38624084696846,38624084946764,38624085197008,38624085446846,38624085696642,38624085946480,38624086196276,38624086446276,38624086696235,38624086946154,38624087196073,38624087445991,38624087695951,38624087972277,38624088235379,38624088495063,38624088752998,38624089009753,38624089265693,38624089520902,38624089775744,38624090029569,38624090282824,38624090535632,38624090788154,38624091040189,38624091292061,38624091543566,38624091794868,38624092045967,38624092296740,38624092547513,38624092798245,38624093048815,38624093298937,38624093549832,38624093800321,38624094050524,38624094300768,38624094551012,38624094801134,38624095051338,38624095301663,38624095551785,38624095801745,38624096051663,38624096301745,38624096551745,38624096801745,38624097051785,38624097301908,38624097551704,38624097802030,38624098052070,38624098316230,38624098553942,38624098790148,38624099027900,38624099266792,38624099507840,38624099751777,38624099994412,38624100238757,38624100483914,38624100730008,38624100976346,38624101223538,38624101470649,38624101718249,38624101966174,38624102214424,38624102462878,38624102711535,38624102960477,38624103209500,38624103458687,38624103707954,38624103957262,38624104206774,38624104456367,38624104706082,38624104955798,38624105205228,38624105455635,38624105705635,38624105955431,38624106205187,38624106454984,38624106704862,38624106954780,38624107204658,38624107454577,38624107704984,38624107954495,38624108204211,38624108454251,38624108704292,38624108954333,38624109204536,38624109454618,38624109704699,38624109954658,38624110204618,38624110454618,38624110704618,38624110954577,38624111204373,38624111454373,38624111704414,38624111954414,38624112204292,38624112454333,38624112704373,38624112954373,38624113204536,38624113454658,38624113704821,38624113954902,38624114204943,38624114456164,38624114706245,38624114956123,38624115206042,38624115455798,38624115705675,38624115955635,38624116205635,38624116455675,38624116705472,38624116955675,38624117206001,38624117455838,38624117705635,38624117955391,38624118205147,38624118455106,38624118705065,38624118954943,38624119204862,38624119454780,38624119704780,38624119954618,38624120204495,38624120454495,38624120704495,38624120954495,38624121204658,38624121454821,38624121704740,38624121954699,38624122204740,38624122454740,38624122704536,38624122954373,38624123204373,38624123476468,38624123739692,38624123999784,38624124258410,38624124515490,38624124771593,38624125026924,38624125281725,38624125535957,38624125789782,38624126042874,38624126295275,38624126547594,38624126799385,38624127051012,38624127302437,38624127553657,38624127804797,38624128055326,38624128306383,38624128556994,38624128807360,38624129057808,38624129310575,38624129560941,38624129810859,38624130060819,38624130310697,38624130560615,38624130810412,38624131060290,38624131310412,38624131560371,38624131810330,38624132060249,38624132310168,38624132560086,38624132810168,38624133060127,38624133310493,38624133560330,38624133810900,38624134070422,38624134308092,38624134544787,38624134782865,38624135022814,38624135264066,38624135506660,38624135750150,38624135994819,38624136240221,38624136486274,38624136732897,38624136980090,38624137227892,38624137475532,38624137723498,38624137971911,38624138220609,38624138469592,38624138718452,38624138967516,38624139216825,38624139466174,38624139715482,38624139964871,38624140214465,38624140464058,38624140713773,38624140963529,38624141213529,38624141463691,38624141713569,38624141963488,38624142213488,38624142463407,38624142713488,38624142963447,38624143213407,38624143463488,38624143713529,38624143963569,38624144213529,38624144463407,38624144742663,38624144992703,38624145239652,38624145486600,38624145733914,38624145981554,38624146229439,38624146477689,38624146726224,38624146975125,38624147224027,38624147473091,38624147722318,38624147971707,38624148221219,38624148470772,38624148720202,38624148969917,38624149219917,38624149469551,38624149719225,38624149968940,38624150218615,38624150468330,38624150717964,38624150967801,38624151217720,38624151467679,38624151717476,38624151967435,38624152217313,38624152467150,38624152717150,38624152967109,38624153217313,38624153467394,38624153717760,38624153967638,38624154217801,38624154467964,38624154718005,38624154968005,38624155217923,38624155467964,38624155718045,38624155968086,38624156218127,38624156468127,38624156718167,38624156968249,38624157218412,38624157468330,38624157718289,38624157968249,38624158218208,38624158468249,38624158718289,38624158968249,38624159244779,38624159507922,38624159767647,38624160025703,38624160282580,38624160538480,38624160793485,38624161047717,38624161301460,38624161555570,38624161808540,38624162060819,38624162312731,38624162564277,38624162815661,38624163066759,38624163317655,38624163568468,38624163819364,38624164070015,38624164320544,38624164571032,38624164821317,38624165071642,38624165322131,38624165572415,38624165822660,38624166072944,38624166323107,38624166573392,38624166823514,38624167073514,38624167323555,38624167573595,38624167824165,38624168074531,38624168324531,38624168574328,38624168824328,38624169074287,38624169324653,38624169589383,38624169827135,38624170063219,38624170300850,38624170540311,38624170781318,38624171023669,38624171267118,38624171511543,38624171756701,38624172002510,38624172248848,38624172495959,38624172743355,38624172991239,38624173239815,38624173488146,38624173736681,38624173985298,38624174233955,38624174482816,38624174731799,38624174981392,38624175230659,38624175480090,38624175729683,38624175979154,38624176228828,38624176478421,38624176728136,38624176978055,38624177228136,38624177478177,38624177728096,38624177978055,38624178228014,38624178478014,38624178727892,38624178977933,38624179227933,38624179477974,38624179728014,38624179978014,38624180228055,38624180478014,38624180727974,38624180977892,38624181228177,38624181478218,38624181728014,38624181977811,38624182227689,38624182477648,38624182727445,38624182977404,38624183227282,38624183477282,38624183727119,38624183977119,38624184227119,38624184477079,38624184727079,38624184977079,38624185227363,38624185477282,38624185727160,38624185976997,38624186226916,38624186476916,38624186726875,38624186976916,38624187226875,38624187476834,38624187726794,38624187976631,38624188226753,38624188476712,38624188726753,38624188976712,38624189227079,38624189477160,38624189727323,38624189977363,38624190227445,38624190477404,38624190727485,38624190977607,38624191227689,38624191477730,38624191727770,38624191977892,38624192227892,38624192477892,38624192727933,38624192978055,38624193228259,38624193477811,38624193727892,38624193977730,38624194227567,38624194477526,38624194727689,38624194977730,38624195227567,38624195477404,38624195727404,38624195977404,38624196227404,38624196477363,38624196727363,38624196977282,38624197227445,38624197477445,38624197727404,38624197977363,38624198227282,38624198477241,38624198727160,38624198977119,38624199227119,38624199477079,38624199726997,38624199976834,38624200226834,38624200476794,38624200726875,38624200977079,38624201227241,38624201477241,38624201727282,38624201977404,38624202227567,38624202477567,38624202727811,38624202977445,38624203227526,38624203477404,38624203727404,38624203977241,38624204227282,38624204477282,38624204727241,38624204977079,38624205227363,38624205477689,38624205727730,38624205977526,38624206227404,38624206477404,38624206727241,38624206977119,38624207227038,38624207477201,38624207727119,38624207977160,38624208227119,38624208477079,38624208727079,38624208977038,38624209227485,38624209477485,38624209727201,38624209977282,38624210227079,38624210477282,38624210727241,38624210977201,38624211227241,38624211477201,38624211727119,38624211977038,38624212226956,38624212476875,38624212726916,38624212976916,38624213227241,38624213477241,38624213727526,38624213977648,38624214230415,38624214479642,38624214729805,38624214979357,38624215229276,38624215478991,38624215728462,38624215978503,38624216228340,38624216478055,38624216727933,38624216977811,38624217227974,38624217477079,38624217727730,38624217977485,38624218227363,38624218477485,38624218727445,38624218977526,38624219227445,38624219477363,38624219746650,38624220009793,38624220270129,38624220528796,38624220786405,38624221042712,38624221298571,38624221553576,38624221807808,38624222061592,38624222314684,38624222567288,38624222819730,38624223071846,38624223323595,38624223574857,38624223825915,38624224077013,38624224328153,38624224578397,38624224829658,38624225080309,38624225331205,38624225581571,38624225831652,38624226081611,38624226331774,38624226581896,38624226832100,38624227082100,38624227332262,38624227582222,38624227832140,38624228082262,38624228332303,38624228582303,38624228832385,38624229082466,38624229332751,38624229582913,38624229833036,38624230083117,38624230333117,38624230583158,38624230833239,38624231083320,38624231333361,38624231583565,38624231833646,38624232083646,38624232333605,38624232583687,38624232833768,38624233083727,38624233334053,38624233583890,38624233833931,38624234084094,38624234333849,38624234583971,38624234834989,38624235083565,38624235333402,38624235583198,38624235833117,38624236083239,38624236333320,38624236583198,38624236833076,38624237083036,38624237333280,38624237583239,38624237833198,38624238083239,38624238333320,38624238583361,38624238833320,38624239083402,38624239333402,38624239583524,38624239833565,38624240083565,38624240333524,38624240583565,38624240843859,38624241089383,38624241326362,38624241563586,38624241802192,38624242042630,38624242284370,38624242527290,38624242771349,38624243016263,38624243261869,38624243508084,38624243754829,38624244002103,38624244249702,38624244497627,38624244745999,38624244994697,38624245243761,38624245492907,38624245742215,38624245992012,38624246241361,38624246490710,38624246739855,38624246989733,38624247239448,38624247489163,38624247738512,38624247988635,38624248238512,38624248488268,38624248738106,38624248987983,38624249238146,38624249488146,38624249738187,38624249988024,38624250237983,38624250487861,38624250737699,38624250987617,38624251237455,38624251487495,38624251737536,38624251987536,38624252237536,38624252487536,38624252737495,38624252987495,38624253237861,38624253487902,38624253737902,38624253987739,38624254237617,38624254487617,38624254737617,38624254987577,38624255237536,38624255487495,38624255737495,38624255987495,38624256237373,38624256487373,38624256737373,38624256987373,38624257237658,38624257486966,38624257737780,38624257987861,38624258237943,38624258487577,38624258737699,38624258987658,38624259237739,38624259487658,38624259737739,38624259987617,38624260237699,38624260487617,38624260737658,38624260987902,38624261238106,38624261492134,38624261737780,38624261987210,38624262237251,38624262487332,38624262737332,38624262987373,38624263237455,38624263487455,38624263737455,38624263987455,38624264237455,38624264487414,38624264737373,38624264987536,38624265237821,38624265487780,38624265737780,38624266001045,38624266269111,38624266530220,38624266789131,38624267046740,38624267302477,38624267558377,38624267813748,38624268067858,38624268321276,38624268574247,38624268826484,38624269078682,38624269330879,38624269582222,38624269833849,38624270084907,38624270335803,38624270586616,38624270837227,38624271087715,38624271338285,38624271588773,38624271839139,38624272089505,38624272339749,38624272589953,38624272840034,38624273090197,38624273340726,38624273590807,38624273840929,38624274090889,38624274340889,38624274590929,38624274841011,38624275091052,38624275341214,38624275591092,38624275841174,38624276091296,38624276341255,38624276603910,38624276841784,38624277078112,38624277316190,38624277555651,38624277796740,38624278039009,38624278282458,38624278526883,38624278772000,38624279017931,38624279264391,38624279511177,38624279758776,38624280006579,38624280254911,38624280503446,38624280752184,38624281000923,38624281250191,38624281499539,38624281748970,38624281998482,38624282247871,38624282497464,38624282747179,38624282996895,38624283246732,38624283496447,38624283746284,38624283996976,38624284246773,38624284496569,38624284746488,38624284996284,38624285246528,38624285496488,38624285746406,38624285996447,38624286246447,38624286496406,38624286746284,38624286996162,38624287246081,38624287496162,38624287746162,38624287996203,38624288246284,38624288496284,38624288746325,38624288996284,38624289246610,38624289496528,38624289746366,38624289996325,38624290246122,38624290496406,38624290746488,38624290996447,38624291246488,38624291496406,38624291746203,38624291996284,38624292246244,38624292496284,38624292746244,38624292996325,38624293246773,38624293496447,38624293746732,38624293996813,38624294246691,38624294496651,38624294746569,38624294996528,38624295246488,38624295496488,38624295746447,38624295996366,38624296246528,38624296496366,38624296746325,38624296996122,38624297246366,38624297496366,38624297746162,38624297996040,38624298246162,38624298496284,38624298746203,38624298996040,38624299245837,38624299495633,38624299745552,38624299995511,38624300245470,38624300495389,38624300745348,38624300995308,38624301245308,38624301495308,38624301769193,38624302032295,38624302291898,38624302550728,38624302808093,38624303064074,38624303318957,38624303573555,38624303827136,38624304080554,38624304333117,38624304585355,38624304837308,38624305088569,38624305341052,38624305592313,38624305843290,38624306094022,38624306344551,38624306595161,38624306845690,38624307096097,38624307346382,38624307596707,38624307847074,38624308097196,38624308347236,38624308597359,38624308847562,38624309347684,38624309597847,38624309832588,38624310066963,38624310303576,38624310542345,38624310782621,38624311024076,38624311266955,38624311510811,38624311755562,38624312001248,38624312260200,38624312494779,38624312728340,38624312964017,38624313201810,38624313441271,38624313682157,38624313924385,38624314167752,38624314412056,38624314657092,38624314902982,38624315149483,38624315396471,38624315643989,38624315891792,38624316139920,38624316388374,38624316636909,38624316885770,38624317134671,38624317384061,38624317633491,38624317883288,38624318132962,38624318382474,38624318632149,38624318881742,38624319131375,38624319381172,38624319630928,38624319880724,38624320130806,38624320380521,38624320630684,38624320880602,38624321130765,38624321380643,38624321630399,38624321880318,38624322130318,38624322380033,38624322630236,38624322880195,38624323130155,38624323380155,38624323630236,38624323880155,38624324130073,38624324379992,38624324629911,38624324879870,38624325130073,38624325380277,38624325630440,38624325880521,38624326130602,38624326380562,38624326630684,38624326880724,38624327130806,38624327380928,38624327631009,38624327881091,38624328131172,38624328381131,38624328631050,38624328880969,38624329134183,38624329381091,38624329630602,38624329880480,38624330130440,38624330380277,38624330630277,38624330880155,38624331130033,38624331379911,38624331629911,38624331879911,38624332129992,38624332380033,38624332629992,38624332880073,38624333130195,38624333380358,38624333630277,38624333880195,38624334130073,38624334379951,38624334629707,38624334879992,38624335129829,38624335379829,38624335630155,38624335880195,38624336130073,38624336380033,38624336630195,38624336880236,38624337130480,38624337402372,38624337665759,38624337926053,38624338184598,38624338442044,38624338698270,38624338953519,38624339208158,38624339462105,38624339715482,38624339968330,38624340220731,38624340472888,38624340724719,38624340976265,38624341227730,38624341478625,38624341729357,38624341979927,38624342230374,38624342480781,38624342731107,38624342981310,38624343231636,38624343481880,38624343732083,38624343982328,38624344232328,38624344482409,38624344732653,38624344982816,38624345233182,38624345483589,38624345733833,38624345983955,38624346234159,38624346484362,38624346734566,38624346984688,38624347234769,38624347484891,38624347735298,38624347994250,38624348232083,38624348468941,38624348707222,38624348947212,38624349188911,38624349430936,38624349674019,38624349918200,38624350163236,38624350408923,38624350655261,38624350902453,38624351149645,38624351397326,38624351645210,38624351893460,38624352141507,38624352391100,38624352640083,38624352889269,38624353138578,38624353387886,38624353639147,38624353888618,38624354138008,38624354387642,38624354636991,38624354886869,38624355136543,38624355386340,38624355635851,38624355885811,38624356135689,38624356385607,38624356635404,38624356885119,38624357135282,38624357385444,38624357635485,38624357885363,38624358135241,38624358385119,38624358634997,38624358884875,38624359134916,38624359384753,38624359634631,38624359884468,38624360134387,38624360384468,38624360634468,38624360884427,38624361134590,38624361384427,38624361634305,38624361884427,38624362134468,38624362384509,38624362634549,38624362884671,38624363134631,38624363384590,38624363634590,38624363884549,38624364134549,38624364384468,38624364634427,38624364884346,38624365134509,38624365384549,38624365634509,38624365884509,38624366134549,38624366384468,38624366634387,38624366884468,38624367134468,38624367384427,38624367634305,38624367884346,38624368134387,38624368384427,38624368634346,38624368884387,38624369134631,38624369384509,38624369634631,38624369884590,38624370134631,38624370384549,38624370634509,38624370884224,38624371134509,38624371384509,38624371634387,38624371884468,38624372134468,38624372385485,38624372634509,38624372884387,38624373160754,38624373424019,38624373683703,38624373941678,38624374198392,38624374454374,38624374709460,38624374963651,38624375217232,38624375470324,38624375723050,38624375975329,38624376227038,38624376478747,38624376730212,38624376981351,38624377232694,38624377483467,38624377734037,38624377984484,38624378234932,38624378485257,38624378735664,38624378985868,38624379235990,38624379486152,38624379736397,38624379986519,38624380236763,38624380486844,38624380737088,38624380987048,38624381237414,38624381487536,38624381737617,38624381987617,38624382237658,38624382487699,38624382737658,38624382987536,38624383237658,38624383487577,38624383737577,38624383987577,38624384237536,38624384487617,38624384737495,38624384987536,38624385237780,38624385487739,38624385737292,38624385987210,38624386237210,38624386487210,38624386736926,38624386986844,38624387236844,38624387486804,38624387736763,38624387986519,38624388236519,38624388486478,38624388736437,38624388986397,38624389236641,38624389486722,38624389737088,38624389987210,38624390237292,38624390487414,38624390737495,38624390987536,38624391237455,38624391487455,38624391737577,38624391987699,38624392237780,38624392487739,38624392737699,38624392987780,38624393237984,38624393488024,38624393737739,38624393988024,38624394237984,38624394487821,38624394737739,38624394987699,38624395237699,38624395487617,38624395737617,38624395987617,38624396237373,38624396487292,38624396737536,38624396987699,38624397238065,38624397487577,38624397737495,38624397987373,38624398237251,38624398487129,38624398737088,38624398987007,38624399236926,38624399486804,38624399737292,38624399986885,38624400236763,38624400486844,38624400736763,38624400986681,38624401237455,38624401495145,38624401733304,38624401970324,38624402208727,38624402448758,38624402690295,38624402932604,38624403176908,38624403421659,38624403666979,38624403913399,38624404160388,38624404407458,38624404654894,38624404902738,38624405151558,38624405399727,38624405648221,38624405896919,38624406145820,38624406394925,38624406644071,38624406893420,38624407142769,38624407392240,38624407641833,38624407891589,38624408141100,38624408391100,38624408640938,38624408890694,38624409140653,38624409390816,38624409640897,38624409890816,38624410140694,38624410390694,38624410640694,38624410890694,38624411140694,38624411390734,38624411640734,38624411890816,38624412140856,38624412390897,38624412640897,38624412890775,38624413152697,38624413391629,38624413640246,38624413890042,38624414140490,38624414390734,38624414640775,38624414890897,38624415141019,38624415391182,38624415641304,38624415891426,38624416141589,38624416391629,38624416649401,38624416891792,38624417171211,38624417754463,38624418001737,38624418230781,38624418460192,38624418692370,38624418926867,38624419163806,38624419402982,38624419643542,38624419885404,38624420128364,38624420372424,38624420617826,38624420863594,38624421355700,38624421600492,38624421829658,38624422060127,38624422323189,38624422558906,38624422792630,38624423028104,38624423265490,38624423504911,38624423745877,38624423988146,38624424231432,38624424475939,38624424721097,38624424966906,38624425213814,38624425460884,38624425708320,38624425956164,38624426204292,38624426470690,38624426733060,38624426992459,38624427250231,38624427506782,38624427762560,38624428017443,38624428271594,38624428525215,38624428778226,38624429030830,38624429283312,38624429535754,38624429787178,38624430038561,38624430289619,38624430540555,38624430791288,38624431041979,38624431292630,38624431542915,38624431793403,38624432043810,38624432294095,38624432544421,38624432794461,38624433044706,38624433295072,38624433545275,38624433795479,38624434045601,38624434295641,38624434545601,38624434795560,38624435045641,38624435295682,38624435545723,38624435795804,38624436045764,38624436295764,38624436545682,38624436795845,38624437058906,38624437297025,38624437533638,38624437771594,38624438011299,38624438252469,38624438495023,38624438738757,38624438983386,38624439228788,38624439474841,38624439721463,38624439968330,38624440215848,38624440466743,38624440716377,38624440964668,38624441213407,38624441461494,38624441709989,38624441958809,38624442207751,38624442456815,38624442706123,38624442955513,38624443205106,38624443454699,38624443704251,38624443953967,38624444203723,38624444453478,38624444703234,38624444953031,38624445203031,38624445453112,38624445703153,38624445953153,38624446203153,38624446453234,38624446703234,38624446953275,38624447203316,38624447453316,38624447703194,38624447953112,38624448203275,38624448453234,38624448703275,38624448953194,38624449203397,38624449453397,38624449703356,38624449953031,38624450202909,38624450452827,38624450702705,38624450952624,38624451202583,38624451452502,38624451702461,38624451952298,38624452202217,38624452452380,38624452702502,38624452952258,38624453202583,38624453452746,38624453702583,38624453952827,38624454201891,38624454452543,38624454702624,38624454952543,38624455202502,38624455452298,38624455702420,38624455951688,38624456202461,38624456452502,38624456702543,38624456952543,38624457202583,38624457452624,38624457702746,38624457952746,38624458202543,38624458452543,38624458702543,38624458952543,38624459202543,38624459452583,38624459702543,38624459952583,38624460202543,38624460452502,38624460702624,38624460952583,38624461202868,38624461452868,38624461702827,38624461952705,38624462226102,38624462488513,38624462749255,38624463007596,38624463264758,38624463520739,38624463775907,38624464030261,38624464284085,38624464537381,38624464790067,38624465042346,38624465294584,38624465546333,38624465797798,38624466049059,38624466300158,38624466551094,38624466801745,38624467052640,38624467303128,38624467553617,38624467803983,38624468054349,38624468304593,38624468554837,38624468805163,38624469055407,38624469305936,38624469556221,38624469806506,38624470056587,38624470306709,38624470556913,38624470807035,38624471057157,38624471307238,38624471557279,38624471807279,38624472057442,38624472307320,38624472557971,38624472814766,38624473052722,38624473289986,38624473528267,38624473768338,38624474009753,38624474252429,38624474496162,38624474740873,38624474986153,38624475232206,38624475478747,38624475726021,38624475972806,38624476221545,38624476469958,38624476718330,38624476966947,38624477216092,38624477465482,38624477714953,38624477964872,38624478214180,38624478463732,38624478713529,38624478963163,38624479212878,38624479462715,38624479712471,38624479962267,38624480212145,38624480462064,38624480712064,38624480961983,38624481212349,38624481462186,38624481711820,38624481961657,38624482211332,38624482461210,38624482711047,38624482961006,38624483210965,38624483460925,38624483710843,38624483960762,38624484210721,38624484460721,38624484710477,38624484960477,38624485210803,38624485460965,38624485711047,38624485961169,38624486211210,38624486461210,38624486711169,38624486961250,38624487211413,38624487461454,38624487711454,38624487961535,38624488211535,38624488461576,38624488711576,38624488961657,38624489211861,38624489461779,38624489711779,38624489961738,38624490211779,38624490461779,38624490711779,38624490961820,38624491243477,38624491493395,38624491739896,38624491986397,38624492233223,38624492480415,38624492728137,38624492976306,38624493224881,38624493473376,38624493721992,38624493970731,38624494219795,38624494468900,38624494718168,38624494967435,38624495216947,38624495466418,38624495716092,38624495965645,38624496215238,38624496464912,38624496714668,38624496964505,38624497214587,38624497464709,38624497714872,38624497994291,38624498256945,38624498516141,38624498773791,38624499030179,38624499285713,38624499540392,38624499794543,38624500047961,38624500301013,38624500553576,38624500805773,38624501057442,38624501309435,38624501560697,38624501811755,38624502062731,38624502313464,38624502564033,38624502814603,38624503065051,38624503315498,38624503565824,38624503816190,38624504066434,38624504316516,38624504566800,38624504816963,38624505066922,38624505317777,38624505567940,38624505818021,38624506068469,38624506317858,38624506568021,38624506818143,38624507068224,38624507318184,38624507568143,38624507818102,38624508067980,38624508317940,38624508567940,38624508817980,38624509068021,38624509318265,38624509568224,38624509818387,38624510068428,38624510318347,38624510568306,38624510818306,38624511068306,38624511318102,38624511568021,38624511818021,38624512068062,38624512318062,38624512568062,38624512817980,38624513067980,38624513318347,38624513568306,38624513818306,38624514068225,38624514318225,38624514568184,38624514817736,38624515067451,38624515317980,38624515568184,38624515818225,38624516068387,38624516318265,38624516568102,38624516818102,38624517068062,38624517318347,38624517568062,38624517818225,38624518068265,38624518318184,38624518568021,38624518818021,38624519068021,38624519317980,38624519567980,38624519817940,38624520067899,38624520317777,38624520567736,38624520817818,38624521067818,38624521318062,38624521567940,38624521817940,38624522068062,38624522318062,38624522568062,38624522818102,38624523068102,38624523318021,38624523567899,38624523817899,38624524067899,38624524317940,38624524567940,38624524817980,38624525067899,38624525318062,38624525568184,38624525818265,38624526085030,38624526322822,38624526558825,38624526796089,38624527035388,38624527275744,38624527518420,38624527761950,38624528005806,38624528251167,38624528496162,38624528743151,38624528990425,38624529237821,38624529485257,38624529733386,38624529981595,38624530229968,38624530478584,38624530727364,38624530976428,38624531225532,38624531474800,38624531724108,38624531973498,38624532223091,38624532472806,38624532722359,38624532972155,38624533222237,38624533471789,38624533721952,38624533971830,38624534221626,38624534471626,38624534721504,38624534971341,38624535221219,38624535471179,38624535721138,38624535971057,38624536221016,38624536470894,38624536720934,38624536970975,38624537220365,38624537471382,38624537721423,38624537971463,38624538221911,38624538471708,38624538721952,38624538971911,38624539221952,38624539472033,38624539722033,38624539972074,38624540222074,38624540471992,38624540721992,38624540971992,38624541221870,38624541472277,38624541722033,38624541972440,38624542222359,38624542472277,38624542722399,38624542972318,38624543222237,38624543472155,38624543722196,38624543972033,38624544222196,38624544472277,38624544722277,38624544972237,38624545222603,38624545472603,38624545722766,38624545972603,38624546222725,38624546472806,38624546722847,38624546972847,38624547222969,38624547472928,38624547722928,38624547972928,38624548223050,38624548473050,38624548723132,38624548973172,38624549223457,38624549473295,38624549723172,38624549973091,38624550222847,38624550472725,38624550722521,38624550972481,38624551238838,38624551504178,38624551765043,38624552023872,38624552281400,38624552537788,38624552793404,38624553048083,38624553302111,38624553555733,38624553809029,38624554061511,38624554313667,38624554565539,38624554817085,38624555068550,38624555319527,38624555570503,38624555821398,38624556072171,38624556322741,38624556573270,38624556823677,38624557074043,38624557324898,38624557575183,38624557825508,38624558075671,38624558325915,38624558575915,38624558825956,38624559076078,38624559326118,38624559576240,38624559826403,38624560107328,38624560357409,38624560603869,38624560850329,38624561344877,38624561592435,38624561837471,38624562058662,38624562280668,38624562506091,38624562735135,38624562967313,38624563202095,38624563439074,38624563678088,38624563918770,38624564160754,38624564407906,38624564649890,38624564894396,38624565140002,38624565386055,38624565633044,38624565880562,38624566128080,38624566375882,38624566624173,38624566872342,38624567121406,38624567370226,38624567619291,38624567868355,38624568117663,38624568366971,38624568616157,38624568866076,38624569115913,38624569365669,38624569615466,38624569865181,38624570114977,38624570364815,38624570614571,38624570864489,38624571114367,38624571364245,38624571614042,38624571864042,38624572113960,38624572363920,38624572613920,38624572863960,38624573363838,38624573613879,38624573848498,38624574082954,38624574319486,38624574557849,38624574798002,38624575039660,38624575282377,38624575526151,38624575770861,38624576016385,38624576262560,38624576509305,38624576756538,38624577004178,38624577252429,38624577500882,38624577749458,38624577998400,38624578247505,38624578496651,38624578745959,38624578995389,38624579244942,38624579494616,38624579744250,38624579993802,38624580243518,38624580493518,38624580743233,38624580993070,38624581243111,38624581492989,38624581742704,38624581992378,38624582242460,38624582492460,38624582741931,38624582992053,38624583242053,38624583491890,38624583741809,38624583991768,38624584241605,38624584491646,38624584741524,38624584991524,38624585240954,38624585491402,38624585741605,38624585991686,38624586241564,38624586491524,38624586741524,38624587015694,38624587278837,38624587538724,38624587796984,38624588053983,38624588309964,38624588564929,38624588819364,38624589073148,38624589326647,38624589579618,38624589832181,38624590084297,38624590336087,38624590587390,38624590838976,38624591090238,38624591341214,38624591592028,38624591842679,38624592093208,38624592343737,38624592594307,38624592844795,38624593095121,38624593345731,38624593595935,38624593846016,38624594095975,38624594346097,38624594596301,38624594846382,38624595096463,38624595346545,38624595596504,38624595846463,38624596096504,38624596346504,38624596596504,38624596846586,38624597096545,38624597356799,38624597602567,38624597839261,38624598076444,38624598315295,38624598555692,38624598797554,38624599040596,38624599284615,38624599529488,38624599775256,38624600021553,38624600268420,38624600515734,38624600763537,38624601011584,38624601260282,38624601508817,38624601757434,38624602006213,38624602255358,38624602504504,38624602753853,38624603003202,38624603252713,38624603502307,38624603751900,38624604001452,38624604251086,38624604501004,38624604750882,38624605000720,38624605250760,38624605500679,38624605750760,38624606000638,38624606250557,38624606500191,38624606750476,38624607000313,38624607250231,38624607499824,38624607750231,38624608000150,38624608250150,38624608500109,38624608750028,38624608999906,38624609249947,38624609500231,38624609750557,38624610000679,38624610250801,38624610500882,38624610750964,38624611000923,38624611251004,38624611500964,38624611750882,38624612000964,38624612251004,38624612501086,38624612751045,38624613001045,38624613251208,38624613501086,38624613751208,38624614000720,38624614250557,38624614500842,38624614750801,38624615000679,38624615250557,38624615500435,38624615750394,38624616000272,38624616250272,38624616500150,38624616749947,38624616999906,38624617249458,38624617500272,38624617750394,38624618000353,38624618250353,38624618500313,38624618750435,38624619000476,38624619250476,38624619500516,38624619750394,38624620000435,38624620250435,38624620500394,38624620750435,38624621000272,38624621250272,38624621500313,38624621750516,38624622000191,38624622250394,38624622506823,38624622780220,38624623042956,38624623302111,38624623559720,38624623815987,38624624071480,38624624326159,38624624580106,38624624833605,38624625086494,38624625339383,38624625591703,38624625843778,38624626095731,38624626347115,38624626598457,38624626849637,38624627100532,38624627351306,38624627602038,38624627852730,38624628103218,38624628353747,38624628604113,38624628854357,38624629128649,38624629363187,38624629610380,38624629859769,38624630109444,38624630359077,38624630608874,38624630858548,38624631108508,38624631358345,38624631608223,38624631858019,38624632107979,38624632357897,38624632607857,38624632857653,38624633107816,38624633357857,38624633607857,38624633857857,38624634107775,38624634357450,38624634607246,38624634857572,38624635107450,38624635357572,38624635607694,38624635857653,38624636107572,38624636357531,38624636607328,38624636857613,38624637357653,38624637607735,38624637842150,38624638076688,38624638313138,38624638551501,38624638791491,38624639032987,38624639275825,38624639519681,38624639764514,38624640010038,38624640256254,38624640502876,38624640749947,38624640997668,38624641245959,38624641494372,38624641743355,38624641992297,38624642241320,38624642490384,38624642739774,38624642989204,38624643238716,38624643488269,38624643737862,38624643987455,38624644237048,38624644487170,38624644736926,38624644986844,38624645236763,38624645486682,38624645736315,38624645986153,38624646235949,38624646485827,38624646735705,38624646985583,38624647235339,38624647485135,38624647735095,38624647985054,38624648234973,38624648485013,38624648734891,38624648984932,38624649235095,38624649485176,38624649735135,38624649985054,38624650235013,38624650484973,38624650735339,38624650995389,38624651232979,38624651469551,38624651707710,38624651947538,38624652188871,38624652431343,38624652674914,38624652919584,38624653165189,38624653411242,38624653657661,38624653904569,38624654151883,38624654399564,38624654647814,38624654896187,38624655144763,38624655393298,38624655642525,38624655891630,38624656140775,38624656390083,38624656639432,38624656889107,38624657138944,38624657388578,38624657638293,38624657887927,38624658137683,38624658387479,38624658637276,38624658887113,38624659136869,38624659386787,38624659636706,38624659886625,38624660136503,38624660386462,38624660636299,38624660886299,38624661136462,38624661386299,38624661636299,38624661885607,38624662136014,38624662386421,38624662636421,38624662886421,38624663136462,38624663386503,38624663636340,38624663886258,38624664136299,38624664386258,38624664636258,38624664886177,38624665136096,38624665386625,38624665636543,38624665886421,38624666136869,38624666387357,38624666637113,38624666887357,38624667137357,38624667387398,38624667637398,38624667887398,38624668137438,38624668387479,38624668637276,38624668887316,38624669137479,38624669387398,38624669637316,38624669887113,38624670136991,38624670386747,38624670636665,38624670886543,38624671136462,38624671386543,38624671636421,38624671886503,38624672136584,38624672386421,38624672636381,38624672886258,38624673136503,38624673386462,38624673636462,38624673886299,38624674136381,38624674386258,38624674636299,38624674886218,38624675136096,38624675386177,38624675636096,38624675886014,38624676161893,38624676425117,38624676684924,38624676943103,38624677200305,38624677456205,38624677711779,38624677966337,38624678220243,38624678473579,38624678726387,38624678978625,38624679230537,38624679482328,38624679733793,38624679985135,38624680236193,38624680487251,38624680738024,38624680988635,38624681239571,38624681489896,38624681740466,38624681990791,38624682240995,38624682491198,38624682741280,38624682991402,38624683241605,38624683491727,38624683741809,38624683991890,38624684241890,38624684491890,38624684741849,38624684991931,38624685242175,38624685492338,38624685742297,38624685992338,38624686242378,38624686508044,38624686745837,38624686982165,38624687219755,38624687458972,38624687699003,38624687942004,38624688185331,38624688429675,38624688674385,38624688921048,38624689167508,38624689414619,38624689661934,38624689909126,38624690157214,38624690405261,38624690653755,38624690902453,38624691151192,38624691400174,38624691649320,38624691898669,38624692148018,38624692397530,38624692647041,38624692896594,38624693146431,38624693396268,38624693645983,38624693895861,38624694145658,38624694395454,38624694645414,38624694895292,38624695145129,38624695395048,38624695644925,38624695894885,38624696144763,38624696394722,38624696644722,38624696894681,38624697144844,38624697394844,38624697645088,38624697895129,38624698150948,38624698400907,38624698649971,38624698899198,38624699148872,38624699398343,38624699647855,38624699897448,38624700147082,38624700401314,38624700646634,38624700895821,38624701145739,38624701395943,38624701646512,38624701896390,38624702146390,38624702396228,38624702646268,38624702896512,38624703146228,38624703396431,38624703646553,38624703896553,38624704146594,38624704396594,38624704646594,38624704896553,38624705146838,38624705396512,38624705646228,38624705896024,38624706145780,38624706395617,38624706645617,38624706895495,38624707145373,38624707395292,38624707644925,38624707895007,38624708145332,38624708395251,38624708645007,38624708894966,38624709144437,38624709395251,38624709645088,38624709895332,38624710145048,38624710395292,38624710645210,38624710895129,38624711145210,38624711395129,38624711664131,38624711927315,38624712187813,38624712446602,38624712704048,38624712960559,38624713216296,38624713471545,38624713725899,38624713979724,38624714232857,38624714485624,38624714737862,38624714989937,38624715241768,38624715493314,38624715744657,38624715995756,38624716246732,38624716497383,38624716748034,38624716998726,38624717249173,38624717499499,38624717750109,38624718000272,38624718250394,38624718500394,38624718750638,38624719000842,38624719250882,38624719500964,38624719750964,38624720001045,38624720251005,38624720501167,38624720751249,38624721001249,38624721251615,38624721501534,38624721751493,38624722001330,38624722251371,38624722501411,38624722751371,38624723001411,38624723251208,38624723501249,38624723751208,38624724001249,38624724251289,38624724501289,38624724772774,38624725023018,38624725270007,38624725518420,38624725766670,38624726014798,38624726263212,38624726511787,38624726760689,38624727009794,38624727259021,38624727508329,38624727757637,38624728007108,38624728256579,38624728506294,38624728755969,38624729005684,38624729255603,38624729505358,38624729755277,38624730004992,38624730254585,38624730504463,38624730754260,38624731004056,38624731254585,38624731504423,38624731754178,38624732003894,38624732253853,38624732503690,38624732753568,38624733003405,38624733253527,38624733503568,38624733753568,38624734003487,38624734253487,38624734503405,38624734753487,38624735003283,38624735253243,38624735503243,38624735753243,38624736003161,38624736253202,38624736503243,38624736753120,38624737002998,38624737252591,38624737503202,38624737753568,38624738003568,38624738253446,38624738503405,38624738753365,38624739003202,38624739253243,38624739503243,38624739753161,38624740003161,38624740253039,38624740502795,38624740753202,38624741003243,38624741253609,38624741503487,38624741753690,38624742003609,38624742253487,38624742503487,38624742753446,38624743003527,38624743253568,38624743503527,38624743753527,38624744003365,38624744253527,38624744503527,38624744753487,38624745003446,38624745253731,38624745503690,38624745753609,38624746003649,38624746253568,38624746503568,38624746753446,38624747003446,38624747266101,38624747503853,38624747740100,38624747977933,38624748217557,38624748458768,38624748701200,38624748944689,38624749189400,38624749434354,38624749680082,38624749926135,38624750173083,38624750420397,38624750668037,38624750915922,38624751164131,38624751412666,38624751661446,38624751910550,38624752159655,38624752408841,38624752658313,38624752907906,38624753157621,38624753407214,38624753656888,38624753906522,38624754156197,38624754406034,38624754655790,38624754905708,38624755155546,38624755405424,38624755655342,38624755905301,38624756155301,38624756405179,38624756655057,38624756904895,38624757155098,38624757405139,38624757655098,38624757905057,38624758155057,38624758405057,38624758655057,38624758904854,38624759154854,38624759404854,38624759654691,38624759904813,38624760154895,38624760404976,38624760655017,38624760905017,38624761155301,38624761405179,38624761655261,38624761905424,38624762155383,38624762405383,38624762655301,38624762905098,38624763155261,38624763405342,38624763655301,38624763905261,38624764155139,38624764405057,38624764655220,38624764905139,38624765155424,38624765405586,38624765655668,38624765905627,38624766155749,38624766405749,38624766686714,38624766936674,38624767183296,38624767429919,38624767676949,38624767924426,38624768172310,38624768420357,38624768668729,38624768917386,38624769166451,38624769415352,38624769664172,38624769913195,38624770162300,38624770411608,38624770660957,38624770910428,38624771159940,38624771409493,38624771659126,38624771908923,38624772158760,38624772432198,38624772695259,38624772955025,38624773213610,38624773470935,38624773727160,38624773982369,38624774237007,38624774490873,38624774744169,38624774996936,38624775249336,38624775501411,38624775753039,38624776004504,38624776255847,38624776506905,38624776757841,38624777008573,38624777259590,38624777510160,38624777760607,38624778011014,38624778261340,38624778511543,38624778761787,38624779011869,38624779262032,38624779512154,38624779762276,38624780012357,38624780262479,38624780512561,38624780762520,38624781012520,38624781262845,38624781512927,38624781762886,38624782012927,38624782262927,38624782512967,38624782762886,38624783012764,38624783262764,38624783512845,38624783762805,38624784012886,38624784262845,38624784512276,38624784762561,38624785012642,38624785263008,38624785513090,38624785763212,38624786013090,38624786263049,38624786513090,38624786763049,38624787013090,38624787263090,38624787513049,38624787763049,38624788012886,38624788262886,38624788512886,38624788762845,38624789012845,38624789263171,38624789513130,38624789763090,38624790013171,38624790263212,38624790513130,38624790762967,38624791013008,38624791262967,38624791512886,38624791762845,38624792012601,38624792262072,38624792512967,38624792763171,38624793013090,38624793263700,38624793523466,38624793760852,38624793997546,38624794236031,38624794475085,38624794716866,38624794959745,38624795203397,38624795448107,38624795693469,38624795939603,38624796186226,38624796433337,38624796680936,38624796928861,38624797177152,38624797425769,38624797674426,38624797923246,38624798172188,38624798421374,38624798670804,38624798920357,38624799169787,38624799419258,38624799668811,38624799918363,38624800168160,38624800417834,38624800667631,38624800917427,38624801167468,38624801417305,38624801667224,38624801917020,38624802166980,38624802416695,38624802666613,38624802916613,38624803166613,38624803416573,38624803666451,38624803916410,38624804166410,38624804416328,38624804666288,38624804916410,38624805166613,38624805416695,38624805666654,38624805916410,38624806166573,38624806416532,38624806666491,38624806916451,38624807166369,38624807416206,38624807666206,38624807916247,38624808166288,38624808416247,38624808666288,38624808916166,38624809166939,38624809417102,38624809666776,38624809916857,38624810166735,38624810416939,38624810666939,38624810916939,38624811166776,38624811416776,38624811666613,38624811916654,38624812166613,38624812416573,38624812666573,38624812916532,38624813166695,38624813416857,38624813666776,38624813916735,38624814166532,38624814416532,38624814666451,38624814916451,38624815166410,38624815416410,38624815666369,38624815916369,38624816166410,38624816416328,38624816665881,38624816916288,38624817166613,38624817416695,38624817666695,38624817918729,38624818168973,38624818418729,38624818693835,38624818956774,38624819216255,38624819474231,38624819730822,38624819986600,38624820241524,38624820495715,38624820749336,38624821002347,38624821254748,38624821507474,38624821759916,38624822011706,38624822263293,38624822514432,38624822765531,38624823016426,38624823267077,38624823517606,38624823768257,38624824018705,38624824269152,38624824519478,38624824769763,38624825020007,38624825270007,38624825520577,38624825770983,38624826021106,38624826271187,38624826521268,38624826771187,38624827021268,38624827271390,38624827521146,38624827771065,38624828021472,38624828271309,38624828521594,38624828771431,38624829032784,38624829277168,38624829513700,38624829750883,38624829989733,38624830230171,38624830472115,38624830715279,38624830959460,38624831204496,38624831450264,38624831696643,38624831943428,38624832190743,38624832438423,38624832686389,38624832934761,38624833183622,38624833432686,38624833681587,38624833930814,38624834181506,38624834430936,38624834680245,38624834929593,38624835179227,38624835462268,38624835711942,38624835957914,38624836204048,38624836450549,38624836697619,38624836945137,38624837193306,38624837441272,38624837689644,38624837938301,38624838187080,38624838436023,38624838685209,38624838934191,38624839183907,38624839433459,38624839682889,38624839932401,38624840182035,38624840431831,38624840681628,38624840931384,38624841181343,38624841431384,38624841681221,38624841931018,38624842180936,38624842430773,38624842680651,38624842930651,38624843180651,38624843430489,38624843680529,38624843930570,38624844180529,38624844430489,38624844680407,38624844930326,38624845180489,38624845430529,38624845680611,38624845930570,38624846180611,38624846430570,38624846680448,38624846930285,38624847180367,38624847429553,38624847680082,38624847930529,38624848180407,38624848430611,38624848680651,38624848930651,38624849180936,38624849430936,38624849680814,38624849930448,38624850180977,38624850432198,38624850682645,38624850933215,38624851182727,38624851432360,38624851682035,38624851931872,38624852181669,38624852431465,38624852681302,38624852931018,38624853181140,38624853431099,38624853680977,38624853930814,38624854197741,38624854462552,38624854723173,38624854981840,38624855239367,38624855495837,38624855751493,38624856006050,38624856259997,38624856513415,38624856766345,38624857018868,38624857271512,38624857523710,38624857774849,38624858026680,38624858277779,38624858528552,38624858779366,38624859030098,38624859280708,38624859531237,38624859781644,38624860033068,38624860283475,38624860533597,38624860783719,38624861033760,38624861283801,38624861534452,38624861784452,38624862034655,38624862284777,38624862534818,38624862784818,38624863034899,38624863284940,38624863534940,38624863784899,38624864036324,38624864285022,38624864535306,38624864795031,38624865032702,38624865269559,38624865507474,38624865747505,38624865988798,38624866231270,38624866474922,38624866718656,38624866964465,38624867210762,38624867457466,38624867704577,38624867952095,38624868200061,38624868448026,38624868696561,38624868945178,38624869194323,38624869443469,38624869692777,38624869942085,38624870191556,38624870441149,38624870689969,38624870940417,38624871190295,38624871439929,38624871689766,38624871939563,38624872189522,38624872439196,38624872689074,38624872938993,38624873189156,38624873439115,38624873689115,38624873939034,38624874188912,38624874439318,38624874688749,38624874938789,38624875188789,38624875438749,38624875688749,38624875938789,38624876188749,38624876438667,38624876688667,38624876938708,38624877188952,38624877438912,38624877688708,38624877938708,38624878188667,38624878438667,38624878688952,38624878938952,38624879188912,38624879438912,38624879688830,38624879938789,38624880188667,38624880438586,38624880688789,38624880938871,38624881189074,38624881439196,38624881689156,38624881939156,38624882188993,38624882438830,38624882688871,38624882938708,38624883188749,38624883438749,38624883688789,38624883938789,38624884188830,38624884438749,38624884688627,38624884938464,38624885188749,38624885438912,38624885688871,38624885938830,38624886188749,38624886438749,38624886688708,38624886938749,38624887188667,38624887438586,38624887688667,38624887938627,38624888188667,38624888438627,38624888688667,38624888938627,38624889188789,38624889438912,38624889688871,38624889965197,38624890228422,38624890488106,38624890746081,38624891002876,38624891258736,38624891513741,38624891767932,38624892021553,38624892274727,38624892527494,38624892779691,38624893031766,38624893283801,38624893534981,38624893786202,38624894037015,38624894287666,38624894538643,38624894789213,38624895039742,38624895290149,38624895540474,38624895790718,38624896041003,38624896291247,38624896541491,38624896791695,38624897041858,38624897292102,38624897542386,38624897792509,38624898042386,38624898292671,38624898542834,38624898792793,38624899042834,38624899292631,38624899542753,38624899792997,38624900042834,38624900309720,38624900547310,38624900783353,38624901020821,38624901260079,38624901500638,38624901742948,38624901985990,38624902230009,38624902474963,38624902720691,38624902966906,38624903213773,38624903464709,38624903712430,38624903960152,38624904212430,38624904460803,38624904708728,38624904956815,38624905205391,38624905459175,38624905708117,38624905956612,38624906205066,38624906453886,38624906703031,38624906952095,38624907201363,38624907450590,38624907699979,38624907949288,38624908198881,38624908448514,38624908698189,38624908947904,38624909197985,38624909447172,38624909697945,38624909947945,38624910197985,38624910447945,38624910698026,38624910948148,38624911198148,38624911448026,38624911698067,38624911948067,38624912197945,38624912447945,38624912697782,38624912947945,38624913198026,38624913448270,38624913697985,38624913948230,38624914198026,38624914448067,38624914697945,38624914947823,38624915197904,38624915447823,38624915698026,38624915951892,38624916197904,38624916447334,38624916697375,38624916947375,38624917197579,38624917447660,38624917697782,38624917947823,38624918197823,38624918447863,38624918697863,38624918947863,38624919197863,38624919447701,38624919697579,38624919947579,38624920197538,38624920447579,38624920696846,38624920947701,38624921197985,38624921447863,38624921697701,38624921947334,38624922197334,38624922447375,38624922697131,38624922947090,38624923196765,38624923447009,38624923696928,38624923946887,38624924196846,38624924446805,38624924696846,38624924946724,38624925196968,38624925466296,38624925729358,38624925989652,38624926248726,38624926506294,38624926762723,38624927018217,38624927272977,38624927526761,38624927780179,38624928033109,38624928285551,38624928537748,38624928789457,38624929040922,38624929292631,38624929543770,38624929794706,38624930045316,38624930296089,38624930546740,38624930797229,38624931047636,38624931298042,38624931548409,38624931798571,38624932048653,38624932298856,38624932549019,38624932799182,38624933049304,38624933299711,38624933549996,38624933800158,38624934050077,38624934300036,38624934549955,38624934800158,38624935050118,38624935300118,38624935550036,38624935800525,38624936059720,38624936297513,38624936534208,38624936772245,38624937012194,38624937253812,38624937496366,38624937739774,38624937984078,38624938229154,38624938475248,38624938721748,38624938968493,38624939216255,38624939464058,38624939712186,38624939960721,38624940209257,38624940458077,38624940707059,38624940956123,38624941205676,38624941455269,38624941704781,38624941954333,38624942203967,38624942453479,38624942703234,38624942953194,38624943202868,38624943452746,38624943702624,38624943952624,38624944202421,38624944452299,38624944702258,38624944952258,38624945202461,38624945452258,38624945702177,38624945951932,38624946201729,38624946451566,38624946701403,38624946951403,38624947201281,38624947451037,38624947700997,38624947950956,38624948204781,38624948450915,38624948700386,38624948950345,38624949200834,38624949451037,38624949701200,38624949951281,38624950201281,38624950451403,38624950701444,38624950951525,38624951201607,38624951451444,38624951701688,38624951952095,38624952201403,38624952451973,38624952702177,38624952952421,38624953202380,38624953452258,38624953702136,38624953951892,38624954201892,38624954451770,38624954701525,38624954951322,38624955201119,38624955451159,38624955701200,38624955951159,38624956201119,38624956451119,38624956701119,38624956950997,38624957201363,38624957451322,38624957701200,38624957951037,38624958200997,38624958450997,38624958700915,38624958950956,38624959200915,38624959450956,38624959700874,38624959950834,38624960200874,38624960451037,38624960701119,38624960950997,38624961226957,38624961490751,38624961749906,38624962007841,38624962264880,38624962520658,38624962775663,38624963030057,38624963283638,38624963536771,38624963789416,38624964041654,38624964293689,38624964545438,38624964796822,38624965048042,38624965299385,38624965549792,38624965800565,38624966050809,38624966301298,38624966551745,38624966801908,38624967052030,38624967302193,38624967552152,38624967802315,38624968052437,38624968302356,38624968552518,38624968802803,38624969052518,38624969302885,38624969553291,38624969803210,38624970053291,38624970303251,38624970553169,38624970803088,38624971053047,38624971303169,38624971568632,38624971806465,38624972042631,38624972313952,38624972553129,38624972790108,38624973028430,38624973268664,38624973509916,38624973752347,38624973995796,38624974240181,38624974485380,38624974731270,38624974977852,38624975224922,38624975472522,38624975720406,38624975968615,38624976217028,38624976465523,38624976714343,38624976963610,38624977212919,38624977462512,38624977712024,38624977961576,38624978211088,38624978460721,38624978710274,38624978960030,38624979209786,38624979459582,38624979709460,38624979959419,38624980209297,38624980459094,38624980709135,38624980959012,38624981209094,38624981459216,38624981709379,38624981959379,38624982209419,38624982459460,38624982713692,38624982959501,38624983209094,38624983459135,38624983709175,38624983959297,38624984209297,38624984459460,38624984709460,38624984959541,38624985209908,38624985459786,38624985709582,38624985959501,38624986209501,38624986459216,38624986709094,38624986959094,38624987208931,38624987458931,38624987708768,38624987958931,38624988209338,38624988459297,38624988709297,38624988959135,38624989209297,38624989459297,38624989709257,38624989958931,38624990209094,38624990459053,38624990709135,38624990958972,38624991208972,38624991458931,38624991708931,38624991958809,38624992208728,38624992458768,38624992708809,38624992958768,38624993208931,38624993459175,38624993709216,38624993959297,38624994209379,38624994459460,38624994709460,38624994959216,38624995209460,38624995459664,38624995709704,38624995959704,38624996209826,38624996459908,38624996729927,38624996993151,38624997253731,38624997512072,38624997770455,38624998027128,38624998282662,38624998537341,38624998791451,38624999044747,38624999297025,38624999550362,38624999802681,38625000054431,38625000306140,38625000557564,38625000808581,38625001059436,38625001310494,38625001561104,38625001811429,38625002061836,38625002312040,38625002562365,38625002812447,38625003062528,38625003312691,38625003562935,38625003812813,38625004062894,38625004313627,38625004563423,38625004813586,38625005063179,38625005313952,38625005564481,38625005813830,38625006063627,38625006313505,38625006563667,38625006813667,38625007064074,38625007322782,38625007560372,38625007797107,38625008035266,38625008275337,38625008516711,38625008759387,38625009002958,38625009248075,38625009493314,38625009739001,38625009985461,38625010232043,38625010479683,38625010727486,38625010975411,38625011223702,38625011472318,38625011720975,38625011969999,38625012219063,38625012468168,38625012717639,38625012966988,38625013216703,38625013466459,38625013716215,38625013965197,38625014215645,38625014465482,38625014716499,38625014965238,38625015214872,38625015464709,38625015714668,38625015964709,38625016214587,38625016464628,38625016714546,38625016964628,38625017214953,38625017464791,38625017714628,38625017964709,38625018214668,38625018464628,38625018714628,38625018964506,38625019214506,38625019464506,38625019714506,38625019964546,38625020215035,38625020464465,38625020714424,38625020964302,38625021214709,38625021464831,38625021714791,38625021964628,38625022214587,38625022468656,38625022714424,38625022963733,38625023213773,38625023463977,38625023714058,38625023964139,38625024214180,38625024464221,38625024714221,38625024964262,38625025213773,38625025464709,38625025714872,38625025964831,38625026214750,38625026464262,38625026714628,38625026964587,38625027214587,38625027464587,38625027714587,38625027964546,38625028214668,38625028464791,38625028714750,38625028964668,38625029214872,38625029464913,38625029714872,38625029964709,38625030214628,38625030464465,38625030714506,38625030964384,38625031214343,38625031464506,38625031714587,38625031964668,38625032214709,38625032492175,38625032754301,38625033014514,38625033272896,38625033530180,38625033785876,38625034041207,38625034295601,38625034549345,38625034802559,38625035055285,38625035307483,38625035559436,38625035811145,38625036062610,38625036313790,38625036564970,38625036815824,38625037066719,38625037317614,38625037568184,38625037818591,38625038068876,38625038319242,38625038569486,38625038819608,38625039069690,38625039319771,38625039569893,38625039820097,38625040070137,38625040320219,38625040570219,38625040820219,38625041070422,38625041323881,38625041574003,38625041823677,38625042073108,38625042322863,38625042572497,38625042822253,38625043071968,38625043321765,38625043571643,38625043822904,38625044072782,38625044322538,38625044572253,38625044822050,38625045086169,38625045324166,38625045560331,38625045798165,38625046037504,38625046278348,38625046520495,38625046763659,38625047007678,38625047253487,38625047498726,38625047746081,38625047993192,38625048240425],"weight":null,"weightType":"samples","length":4610},"markers":{"data":[],"name":[],"startTime":[],"endTime":[],"phase":[],"category":[],"length":0},"stackTable":{"frame":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,5,6,7,42,43,44,45,46,47,48,49,50,51,52,4,39,53,40,41,5,6,7,54,55,56,57,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,58,59,60,61,62,63,64,65,66,67,68,69,5,6,7,70,71,72,73,74,75,76,77,78,79,80,53,68,69,56,57,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,88,89,90,91,92,103,104,105,8,9,106,107,108,109,110,86,87,111,88,89,112,113,114,54,55,115,116,117,118,119,120,121,122,21,22,23,24,123,124,125,126,127,128,129,130,131,132,112,113,114,42,43,106,107,64,65,130,68,69,133,134,135,54,55,136,137,138,139,140,141,142,143,144,145,146,147,148,149,21,22,23,150,151,152,153,80,154,155,156,157,158,159,160,161,162,163,164,165,112,113,114,93,94,166,167,168,169,170,39,171,172,173,8,9,174,175,176,102,39,90,91,92,177,178,179,180,168,169,170,181,182,183,112,113,114,184,185,186,187,188,189,68,69,190,191,192,193,194,195,196,130,90,91,92,42,43,197,198,199,200,201,202,203,204,205,54,55,166,167,192,193,194,8,9,206,207,208,209,210,211,171,172,173,212,213,214,143,144,145,146,147,148,149,21,22,23,24,25,26,27,215,216,217,218,219,220,221,222,223,224,225,226,227,228,88,89,229,230,231,232,195,196,233,234,235,236,237,238,239,240,241,242,21,22,23,24,123,124,125,243,244,245,246,247,103,104,105,248,249,250,251,252,103,104,105,93,94,253,254,53,130,255,256,257,258,259,260,257,261,262,263,264,265,266,267,268,223,224,269,270,271,93,94,272,273,274,275,276,277,181,182,183,257,131,132,278,279,15,16,17,18,19,20,21,22,23,24,123,124,125,243,280,281,282,283,284,285,286,287,15,16,17,18,19,20,21,22,23,24,25,26,27,215,288,289,290,291,220,221,292,293,294,295,296,297,42,43,298,299,143,144,145,146,147,148,149,21,22,23,24,123,124,125,243,280,281,300,301,302,303,304,305,306,307,285,286,287,15,16,17,18,19,20,21,22,23,24,123,124,125,243,244,245,308,309,310,311,312,313,61,62,63,314,315,316,317,318,319,320,321,322,323,324,325,326,327,314,315,328,329,117,118,119,120,121,122,21,22,23,24,25,26,27,58,330,331,332,333,334,335,336,337,338,339,340,53,341,342,338,189,255,256,343,344,345,346,347,348,349,350,351,21,22,23,24,25,26,27,215,352,353,354,355,356,357,131,132,358,359,360,361,362,143,144,145,146,147,148,149,21,22,23,24,25,26,27,215,216,217,218,219,220,221,292,293,294,295,363,364,365,133,134,135,366,367,36,37,368,369,370,371,372,373,374,375,257,376,377,378,379,380,381,40,41,382,383,204,205,361,362,143,144,145,146,147,148,149,21,22,23,24,25,26,27,28,29,30,31,384,220,221,292,293,294,295,385,386,387,40,41,388,389,390,391,392,393,394,395,21,22,23,24,25,26,27,215,352,396,397,398,399,400,401,402,403,404,357,405,406,407,408,70,71,409,410,370,371,372,181,182,183,411,412,413,414,415,416,417,418,419,368,420,421,117,118,119,120,121,122,21,22,23,24,25,422,423,424,425,426,334,427,428,429,430,431,432,177,178,179,180,433,434,435,133,134,135,190,191,436,437,438,439,440,441,442,443,21,22,23,24,123,124,125,243,244,245,444,445,446,447,448,449,450,451,452,453,454,455,456,457,458,459,278,279,15,16,17,18,19,20,21,22,23,24,123,124,125,243,244,245,308,309,310,460,461,462,463,464,465,466,467,33,34,35,288,289,290,291,220,221,292,293,294,295,385,386,468,469,131,132,470,471,472,473,474,475,476,111,343,344,345,346,347,348,349,350,351,21,22,23,24,123,477,478,479,358,359,360,480,481,482,483,405,406,484,485,486,487,488,489,490,247,491,492,493,189,204,205,369,494,495,496,497,498,499,500,284,211,67,501,502,503,504,269,270,271,505,506,507,508,509,510,511,21,22,23,24,25,422,423,424,425,426,334,427,428,429,430,431,512,513,171,172,173,320,321,322,204,205,181,182,183,323,324,325,171,172,173,514,369,223,224,515,516,517,111,451,452,357,518,519,520,521,522,523,524,525,526,527,21,22,23,24,25,528,529,530,531,373,532,533,534,535,319,434,435,484,485,486,536,537,538,401,539,540,541,520,521,522,523,524,525,526,527,21,22,23,24,25,26,27,215,352,542,543,544,545,546,547,548,143,144,145,146,147,148,149,21,22,23,24,25,528,549,550,247,330,331,332,551,552,553,554,555,556,557,480,314,315,558,559,514,560,369,417,418,419,223,224,561,562,401,563,564,26,27,28,29,30,31,565,566,567,568,544,545,84,85,518,519,569,570,494,495,496,553,554,150,571,546,547,548,143,144,145,146,147,148,149,21,22,23,24,123,124,125,243,280,281,300,301,572,573,574,481,482,483,382,383,575,576,577,578,579,580,581,21,22,23,24,123,124,125,243,280,281,300,582,583,584,585,586,587,588,589,590,591,111,108,109,110,592,593,594,595,319,358,359,360,319,177,178,382,383,518,519,52,70,71,487,488,544,545,596,597,15,16,17,18,19,20,21,22,23,24,25,422,423,424,425,598,599,357,600,601,602,603,604,103,104,105,605,606,607,608,609,610,611,612,613,402,403,404,607,608,614,615,268,25,26,27,58,330,616,617,218,618,211,70,71,451,452,481,482,483,569,570,619,620,117,118,119,120,121,122,21,22,23,24,25,422,423,424,425,426,621,622,25,26,27,215,352,623,361,362,143,144,145,146,147,148,149,21,22,23,24,25,26,27,624,625,626,627,220,221,292,293,294,295,628,629,630,631,632,633,634,563,564,269,270,271,635,636,345,346,347,348,349,350,351,21,22,23,24,25,26,27,215,352,637,309,310,638,639,589,590,591,640,641,343,344,345,346,347,348,349,350,351,21,22,23,24,25,26,27,215,216,217,218,219,642,643,644,645,646,647,648,649,21,22,23,24,25,26,27,215,352,650,651,212,213,214,143,144,145,146,147,148,149,21,22,23,24,25,26,27,624,625,626,652,258,259,653,654,218,219,220,221,292,293,294,295,628,655,211,539,540,541,656,657,658,659,156,157,158,159,160,161,162,163,164,165,190,191,422,423,653,654,218,219,220,221,292,293,294,295,363,660,661,662,156,157,158,159,160,161,162,163,164,663,664,665,189,666,667,67,668,669,123,124,125,243,280,281,282,670,278,279,15,16,17,18,19,20,21,22,23,24,25,422,423,424,425,426,671,407,408,672,673,674,465,466,467,382,383,561,562,26,27,215,352,675,676,677,678,679,373,680,681,682,123,124,125,243,683,684,685,686,687,688,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,689,690,691,692,693,694,484,485,486,637,309,310,311,695,560,696,697,698,699,216,217,218,219,700,487,488,701,702,123,124,125,243,280,281,300,301,572,573,703,704,25,422,423,424,425,598,705,706,177,178,668,669,707,708,709,15,16,17,18,19,20,21,22,23,24,25,26,27,58,330,331,332,551,552,481,482,483,462,463,710,711,712,713,714,535,704,278,279,15,16,17,18,19,20,21,22,23,24,25,26,27,215,288,289,290,291,220,221,292,293,294,295,628,715,716,684,685,150,151,717,247,285,286,287,15,16,17,18,19,20,21,22,23,24,25,26,27,215,216,217,218,718,719,720,721,15,16,17,18,19,20,21,22,23,24,25,26,27,215,352,396,722,723,724,725,726,727,728,729,730,731,732,733,734,735,736,737,738,739,740,741,742,553,554,123,124,125,243,280,743,744,745,746,484,485,486,229,230,747,748,448,449,450,656,657,405,406,407,408,749,750,751,752,753,754,755,744,745,746,24,25,26,27,624,625,626,627,220,221,292,293,294,295,385,756,631,632,215,216,217,218,219,220,221,292,293,294,295,757,81,82,83,133,134,135,331,332,333,621,758,759,760,575,576,577,578,579,580,581,21,22,23,24,25,422,423,424,425,426,334,427,428,429,430,431,761,751,752,704,465,466,467,307,762,763,764,28,29,30,31,384,220,221,292,293,765,407,408,26,27,215,352,637,309,310,638,766,692,767,229,230,416,190,191,465,466,467,58,330,331,332,333,768,769,770,771,464,361,362,143,144,145,146,147,148,149,21,22,23,24,123,124,125,243,244,245,308,309,310,638,766,772,269,270,271,773,774,775,776,777,328,329,117,118,119,120,121,122,21,22,23,24,25,26,27,215,352,637,309,310,778,779,334,427,428,429,430,431,512,780,781,782,339,340,751,752,374,375,436,437,438,439,440,441,442,443,21,22,23,24,25,26,27,624,625,626,627,220,221,783,784,493,785,786,787,788,28,29,30,31,384,220,221,292,293,294,295,789,790,791,792,704,123,124,125,243,280,281,300,582,793,434,435,229,230,640,641,464,401,684,685,794,795,796,358,359,360,797,67,58,59,798,773,774,799,800,520,521,522,523,524,525,526,527,21,22,23,24,25,528,549,801,802,803,804,539,540,541,258,259,575,576,577,578,579,580,581,21,22,23,24,123,124,125,243,280,281,300,582,793,805,806,807,808,809,810,811,21,22,23,24,25,26,27,28,29,30,31,565,566,567,812,813,814,123,124,125,243,244,245,444,815,816,817,345,346,347,348,349,350,351,21,22,23,24,25,26,27,624,625,626,818,819,314,315,820,821,822,823,640,641,123,124,125,243,244,245,444,445,824,825,826,684,685,487,488,451,452,215,352,396,397,827,828,829,830,831,749,750,280,281,300,301,832,833,834,835,553,554,836,539,540,541,123,124,125,243,280,837,546,547,548,143,144,145,146,147,148,149,21,22,23,24,25,26,27,624,625,626,627,220,221,292,293,294,295,296,838,839,416,58,330,331,332,551,840,841,842,843,844,528,549,801,802,803,804,596,597,15,16,17,18,19,20,21,22,23,24,25,422,423,424,425,426,334,427,428,429,430,845,464,653,654,218,219,220,221,292,293,294,295,789,846,343,344,345,346,347,348,349,350,351,21,22,23,24,25,26,27,28,29,30,31,384,220,221,292,293,294,295,789,790,791,847,848,849,850,444,851,852,853,854,855,856,857,858,58,330,331,332,551,859,624,625,626,627,860,707,861,862,863,143,144,145,146,147,148,149,21,22,23,24,25,26,27,215,216,217,218,219,220,221,292,293,294,295,864,258,259,865,866,867,868,123,124,125,243,280,281,300,869,643,644,645,646,647,870,871,373,872,873,874,518,519,244,245,308,309,310,638,875,330,616,617,218,219,220,221,292,293,294,295,296,838,876,640,641,25,26,27,877,878,879,880,28,29,30,31,384,220,221,292,293,294,295,296,838,881,882,883,884,885,886,589,590,591],"category":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"subcategory":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"prefix":[null,0,1,2,3,4,5,6,4,8,3,10,3,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,3,33,34,3,36,2,38,38,40,38,42,43,38,45,2,47,2,49,50,2,52,1,54,54,56,56,56,59,56,61,62,56,64,54,66,54,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,54,87,88,54,90,1,92,92,94,92,96,97,92,99,1,101,1,103,104,1,106,2,3,4,4,111,3,113,3,115,116,3,118,3,120,38,122,38,124,125,38,127,2,129,2,131,132,2,134,54,56,137,56,139,140,56,142,143,56,145,54,147,54,149,150,54,152,92,92,155,92,157,158,92,160,1,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,1,178,2,4,4,182,4,184,185,4,187,3,189,3,191,38,38,194,38,196,197,38,199,2,201,2,203,204,2,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,54,56,223,224,225,226,227,228,229,230,231,232,233,56,235,236,56,238,54,240,54,242,243,92,92,246,247,92,249,1,251,2,3,4,4,256,257,4,259,3,261,3,263,264,38,266,267,38,269,270,2,272,2,274,275,56,56,278,56,280,54,282,283,54,285,92,92,288,289,92,291,1,293,1,295,1,297,298,4,300,4,302,3,304,3,306,307,38,309,2,311,312,2,314,56,56,317,318,54,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,92,345,1,347,348,2,4,351,4,353,3,355,3,357,2,359,2,361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376,377,378,92,92,381,382,1,384,385,1,387,4,389,390,4,392,3,394,38,56,54,398,92,92,401,2,4,2,405,2,407,408,2,410,54,56,413,92,415,416,92,418,1,420,1,422,423,2,4,426,427,38,38,430,38,432,433,434,435,436,437,438,439,440,441,442,443,444,445,446,447,448,449,450,54,56,453,454,455,456,457,458,459,460,461,462,463,464,465,466,467,468,469,470,471,472,473,474,475,476,477,478,479,480,56,482,54,484,485,486,487,488,489,490,491,492,493,494,495,496,497,498,499,500,501,502,503,504,505,1,507,508,3,4,511,512,513,514,515,516,517,518,519,520,521,522,523,524,525,526,527,528,529,530,531,532,533,534,3,536,537,38,539,2,541,542,56,54,545,546,54,548,549,1,551,4,553,3,555,556,557,558,559,560,561,562,563,564,565,566,567,568,569,570,571,572,573,574,575,576,577,54,54,580,92,1,583,3,4,3,587,38,589,590,591,592,593,594,595,596,597,598,599,600,601,602,603,604,605,606,607,2,609,56,56,612,4,614,615,4,617,618,619,620,621,622,623,624,625,626,627,628,629,630,631,632,633,634,635,636,637,638,639,640,641,642,643,644,54,56,647,648,54,650,54,652,3,4,3,656,657,38,38,660,56,1,663,664,1,666,667,4,669,38,671,56,673,56,675,676,677,678,679,680,681,682,683,684,685,686,687,688,689,690,691,692,693,694,695,696,697,698,699,700,701,702,703,92,705,1,707,708,709,710,711,712,713,714,715,716,717,718,719,720,721,722,723,724,725,726,727,4,54,730,731,92,38,734,38,736,38,738,2,740,54,742,743,92,745,746,1,748,749,1,751,4,3,754,755,54,56,758,759,760,761,762,763,764,765,766,767,768,769,770,771,772,773,774,775,776,777,778,779,780,781,56,783,54,785,2,4,788,4,790,791,38,793,54,795,796,797,798,799,800,801,802,803,804,805,806,807,808,809,810,811,812,813,814,815,4,817,818,38,820,2,822,823,1,825,1,827,4,829,830,831,832,833,834,835,836,837,838,839,840,841,842,843,844,845,846,847,848,849,850,3,852,38,38,855,856,54,858,859,337,861,862,863,864,865,866,867,868,869,870,871,872,873,92,875,1,877,1,879,880,3,882,38,56,885,886,887,888,889,890,891,892,893,894,895,896,897,898,899,900,92,902,903,3,38,906,907,56,909,92,911,912,4,914,3,916,38,2,919,54,92,92,923,38,54,926,927,1,929,1,931,3,4,56,1,936,3,938,38,940,941,1,943,944,945,946,947,948,949,950,951,952,953,954,955,956,957,958,959,960,961,962,963,964,965,966,4,968,969,3,971,972,38,974,56,976,977,3,979,980,38,982,983,54,92,38,987,2,989,990,56,56,993,4,4,996,38,998,999,1000,1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1011,2,1013,92,1,1016,1017,3,38,38,1021,38,1023,1024,2,1026,1027,56,56,1030,1031,92,1033,1034,1035,1036,1037,1038,1039,1040,1041,1042,1043,1044,1045,1046,1047,1048,1049,1050,38,1052,38,1054,1055,1056,1057,1058,1059,1060,1061,1062,1063,1064,1065,1066,1067,1068,1069,1070,56,84,1073,1074,1075,1076,92,1078,1,1080,2,54,56,1084,54,1086,3,54,56,54,1091,1092,4,1094,3,1096,38,38,1099,1010,1101,1102,1103,1104,1105,1106,1107,1108,1109,56,1111,54,1113,38,1115,54,1117,3,1119,1120,38,1122,1008,1124,56,1126,1127,1128,1129,1130,1131,1132,1133,1134,1135,1136,1137,1138,1139,1140,1141,1142,1143,1144,1145,1146,1147,1148,1149,92,1151,1152,92,1154,38,1156,1157,1158,1159,1160,1161,1162,1163,1164,1165,1166,1167,1168,1169,1170,1171,1172,1173,1174,2,1176,2,1178,1179,56,1181,1182,4,3,1185,1186,2,1188,1189,2,4,38,1193,1194,92,92,1197,4,1199,92,1201,3,4,1204,38,1206,4,1208,3,1210,1211,1212,1213,1214,1215,1216,1217,1218,1219,1220,1221,1222,1223,1224,1225,1226,1227,38,2,1230,1,1232,2,38,1235,1236,2,1238,54,1240,2,1242,1243,1,1245,3,1247,1248,3,1250,1,1252,3,840,1255,1256,1257,1258,1259,1260,1261,1262,38,56,1265,4,1267,4,1269,1270,3,1272,2,1274,1275,1276,1277,1278,1279,1280,1281,1282,1283,1284,1285,1286,1287,1288,1289,1290,1291,1292,1139,1294,1295,1296,1297,1298,92,1300,1301,1302,1303,1304,1305,1306,1307,1308,1309,1310,1311,1312,1313,1314,1315,1316,1317,1318,1319,1320,1321,1322,1323,1324,1325,1326,2,3,1329,2,1331,56,1333,4,1335,1336,2,1338,1339,1340,1341,1342,1343,1344,1345,1346,1347,1348,1349,1350,1351,1352,1353,1354,1355,1356,1357,1358,1359,38,1361,1362,56,1364,4,1366,1367,1368,1369,1370,1371,1372,1373,1374,1375,1376,1377,1378,1379,1380,1381,1382,1383,1384,1385,1386,54,1388,1389,1390,1391,1392,1393,1394,1395,1396,1397,1398,1399,1400,1401,1402,1403,2,3,1406,1407,1408,1409,1410,1411,1412,1413,1414,1415,1416,1417,1418,1419,1420,1421,1422,1423,1424,1425,38,1427,1288,1429,1430,1431,1432,1433,1434,1435,1436,1437,1438,1439,92,4,1442,1443,3,1445,92,1447,1448,1449,1450,1451,1452,1453,1454,1455,1456,1457,4,1459,25,1461,1462,1463,1464,1465,1466,1467,1468,1469,1470,1471,1472,1473,3,1475,1476,1477,1478,1479,1480,1481,1482,1483,1484,1485,1486,1487,38,1,1490,4,3,1493,1009,1495,1496,1497,1498,1499,1500,1501,92,1503,1504,1505,1506,1507,1508,1509,1510,1511,1512,1513,1514,1515,1516,1517,1518,1519,1520,92,1522,1,1524,1525,56,1527,1528,56,1530,54,1532,1222,1534,1535,1536,1537,1538,1539,1540,1541,56,465,1544,1545,629,1547,1548,1549,1550,92,1552,2,1554,1555,1556,1557,1558,1559,1560,1561,1562,1563,1564,1565,1566,1567,1568,1569,1570,1571,1572,1573,1574,1575,1576,1577,54,56,1580,1581,1298,1583,1584,1585,1586,3,3,1589,2,1591,722,1593,1594,1595,1596,92,1598,1,1600,333,1602,1603,1604,1605,1606,1607,1608,1609,1610,1611,4,1166,1614,1615,1616,1617,1618,1619,1620,38,1622,54,1624,3,2,1627,1628,1629,1630,1631,1632,1633,1634,1635,1636,1637,1638,1639,1640,1641,1642,1643,1644,1645,1646,56,1648,1649,54,1651,1,1653,1,1655,219,54,56,56,1660,1661,1662,1663,1664,1665,1666,1667,1668,1669,1670,1671,1672,1673,1674,1675,1676,1677,1678,1679,1680,1681,1682,1683,1684,1685,1686,3,56,1689,79,1691,1692,4,92,1695,1696,1697,1698,1699,1700,1701,1702,1703,1704,1705,1706,1707,1708,1709,1710,1711,1712,1713,1714,1,1716,1717,1718,1719,1720,1721,1722,1723,1724,1725,1726,1727,1728,1729,1730,1731,1732,1733,1734,1735,1736,1737,1738,1739,1740,1741,1742,1743,1744,1745,1746,1747,1748,1749,1750,1751,1752,1753,1754,4,1756,1378,1758,1759,1760,1761,1762,54,1764,1765,4,1767,1768,56,1770,1,1772,38,1774,1775,54,1777,92,1779,56,1781,54,1783,92,1785,1,1787,1788,3,1790,1791,217,1793,1794,1795,1796,1797,1798,1799,1800,1801,1802,1803,1804,1805,1806,1807,54,1809,690,1811,1812,1813,1814,1815,1816,1817,1818,1819,1820,1821,54,1823,1824,92,1826,1827,1259,1829,1830,1831,1832,2,1834,56,1836,1837,1838,1839,1840,1841,1842,1843,1844,1845,1846,1847,1848,1849,1850,1851,1852,1853,1854,1855,1856,1857,1858,4,1860,92,92,1863,1864,54,776,1,1868,1381,1870,1871,1872,1873,1874,1875,1876,1877,1878,4,1880,1286,1882,1883,1884,1885,1886,1887,1888,1889,1890,1891,38,1893,92,92,1896,4,1898,1899,1535,1901,1902,1903,1904,1905,1906,1907,1908,4,38,1911,1912,1913,1914,1915,1916,1917,1918,1919,1920,1921,1922,1923,1924,1925,1926,1927,1928,1929,1930,1931,1932,1933,1934,56,1936,1937,3,1939,2,1941,1942,54,1944,1945,1946,1947,1948,1949,1950,1951,1952,1953,1954,1955,1956,1957,1958,1959,1960,1961,1962,1963,1964,1831,1966,1967,1968,1969,1970,1971,1972,1973,1974,3,1976,38,1978,56,1980,3,1982,1983,1984,1985,1986,1987,1988,1989,1990,1991,1992,1993,1994,1995,1996,1997,1998,1999,2000,2001,2002,1356,3,2,2006,2,2008,1996,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019,2020,2021,2022,2023,38,1671,2026,2027,2028,2029,2030,2031,2032,2033,92,2035,92,2037,4,2039,56,92,4,2043,2,2045,1645,56,2048,2049,2,38,1674,2053,2054,54,2056,1,2058,4,2060,2061,2062,2063,2064,2065,2066,2067,2068,2069,2070,2071,2072,2073,2074,2075,2076,2077,38,2079,2080,56,2082,92,2084,2085,2086,2087,2088,2089,2090,2091,2092,2093,2094,2095,2096,2097,2098,2099,2100,2101,2102,2,2104,2105,2106,2107,2108,2109,2110,2111,2112,2113,2114,2115,2116,2117,2118,2119,2120,2121,2122,2123,2124,887,2126,1398,2128,2129,2130,2131,2132,2133,2134,54,2136,2137,2138,2139,2140,2141,2142,2143,2144,2145,2146,2147,2148,2149,2150,2151,2152,2153,2154,2155,92,2157,1718,2159,2160,2161,92,2163,1846,2165,2166,2167,2168,2169,2170,2171,2172,1,2174,38,2176,56,2178,92,2180,1257,2182,2183,2184,2185,2186,2187,2188,2189,3,2191,376,2193,2194,2195,2196,2197,1,2199,56,2201,92,92,2204,2205,566,2207,2208,2209,2210,2211,92,2213,2214,2215,2216,2217,2218,2219,2220,2221,2222,2223,2224,2225,2226,2227,2228,2229,2230,2231,2232,2233,2234,2235,2236,2237,2238,2239,2240,2241,38,1958,2244,2245,2246,2247,2248,1,2250,2251,2,1567,2254,2255,2256,2257,2258,54,2260,2261,2262,2263,2264,2265,2266,2267,2268,2269,2270,2271,2272,2273,2274,2275,2276,2277,2278,2279,2280,2281,2282,92,1849,2285,2286,2287,2288,2289,2290,2291,2292,2293,2294,2295,92,2297,2298,2299,2300,2301,2302,2303,2304,2305,2306,2307,2308,2309,2310,2311,2312,2313,2314,2315,2316,2317,2318,2319,2320,2321,2322,2323,2324,2325,2326,1,2328,2329,378,2331,2332,2333,2240,2335,2336,1,2338,2117,2340,2341,2342,2343,2344,1102,2346,2347,2348,2349,54,1,2352,2353,2354,2355,2356,2357,2358,2359,2360,2361,2362,2363,2364,2365,2366,2367,2368,2369,2370,2371,2372,2373,2374,2375,2376,2377,2378,2379,4,2381,14,2383,2384,2385,1728,2387,2388,2389,2390,2391,2392,2393,3,2395,2396,2397,2398,2399,2400,4,1,2403,2404,56,2406,1550,2408,2409,2410,2411,2412,2413,2053,2415,2416,2417,2418,2419,2420,2421,2422,2423,2424,2425,2426,2427,38,2429,372,2431,2432,2433,2434,2,2436,83,2438,2439,2440,2441,2442,2443,2444,2445,2446,2447,2448,2449,2450,1,2452,2010,2454,2455,4,2457,2458],"length":2460},"frameTable":{"address":[-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],"inlineDepth":[0,0,0,0,0,0,1,2,0,1,0,1,0,1,2,0,1,2,0,0,1,0,1,2,0,0,0,1,0,0,0,0,0,0,1,2,0,1,0,0,0,1,0,1,0,1,0,1,2,0,1,0,0,0,0,1,0,1,0,0,0,0,1,2,0,1,0,0,0,1,0,1,0,1,0,1,2,0,1,0,0,0,1,2,0,1,0,1,0,1,0,1,2,0,1,0,1,0,1,2,0,1,0,0,1,2,0,1,0,1,2,0,0,1,2,0,1,0,1,2,0,0,1,0,0,0,0,0,1,0,0,0,1,0,1,2,0,1,0,1,2,0,1,0,0,1,2,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,1,0,1,2,0,1,2,0,1,0,0,1,0,1,0,1,2,0,1,0,1,2,0,0,1,0,1,2,0,1,0,1,0,1,0,1,2,0,1,0,1,2,0,1,0,0,1,2,0,0,0,0,0,0,0,0,0,1,0,1,2,0,0,1,0,1,0,1,0,0,0,1,2,0,0,1,0,0,0,0,0,0,1,2,0,1,0,1,0,1,0,0,1,0,0,1,0,1,2,0,1,0,0,1,2,0,1,0,1,2,0,0,1,0,0,0,0,0,0,1,2,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,2,0,0,0,0,0,0,0,0,1,0,1,2,0,0,1,2,0,1,2,0,1,0,1,0,0,0,0,0,0,0,0,0,0,1,0,1,0,1,0,0,1,2,0,0,1,0,0,0,0,1,0,0,1,2,0,1,0,0,0,0,1,0,0,0,1,2,0,0,1,0,1,2,0,1,2,0,1,0,0,0,0,0,0,0,1,2,0,0,1,0,0,0,0,0,0,0,1,2,0,1,0,1,0,1,0,1,2,0,1,0,0,1,2,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,2,0,0,1,0,0,0,0,0,1,2,0,1,0,1,2,0,1,0,1,0,0,0,1,0,0,1,2,0,0,0,1,0,1,2,0,1,0,0,0,0,0,1,2,0,1,2,0,1,0,1,0,1,0,0,1,2,0,1,0,1,0,1,0,1,0,0,1,2,0,0,1,0,0,0,0,1,2,0,1,0,0,0,1,2,0,0,1,0,0,0,1,0,1,2,0,0,1,2,0,1,2,0,0,0,1,0,1,2,0,0,0,0,0,1,0,1,0,0,1,0,0,1,0,1,0,0,0,0,0,1,0,0,0,0,0,0,1,2,0,0,1,0,0,0,1,0,1,2,0,1,2,0,1,2,0,0,1,0,0,0,1,0,1,0,0,1,0,1,0,1,2,0,1,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,1,0,0,0,0,1,0,0,0,1,2,0,0,1,0,0,0,0,0,0,0,1,0,1,0,0,1,0,0,0,0,1,0,1,0,0,0,1,2,0,0,0,0,0,0,0,0,0,0,1,0,1,2,0,0,0,0,0,0,0,0,1,0,1,0,0,1,0,0,0,0,0,0,1,0,1,0,1,0,0,0,0,0,0,1,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,2,0,1,0,1,0,1,0,1,2,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,1,2,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,1,2,0,0,1,0,0,1,0,0,1,0,0,0,1,2,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,2,0,0,0,0,0,1,2,0,0,0,0,0,0,0,1,0,0,0,1,2,0,0,1,2,0,0,0,0,0,1,2,0,0,0,0,0,1,0,0,1,0,0,0],"category":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"subcategory":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"func":[35,33,37,1,7,13,19,25,13,25,7,19,7,19,25,26,219,195,220,27,227,228,229,231,199,230,44,45,81,47,86,87,91,7,19,25,7,25,1,13,13,19,13,25,1,19,1,19,25,1,25,37,7,13,13,25,7,19,81,186,43,7,19,25,7,25,37,13,13,19,13,25,37,19,37,19,25,37,25,1,7,7,19,25,7,25,7,25,13,19,13,19,25,13,25,1,19,1,19,25,1,25,7,13,19,25,7,19,7,19,25,13,13,19,25,37,19,20,215,193,216,21,227,230,48,105,109,37,25,1,13,13,19,13,19,25,1,19,1,19,25,1,25,30,28,221,196,222,29,227,199,202,201,76,13,19,0,0,0,0,0,0,0,0,0,0,7,19,7,19,25,13,19,25,37,25,1,13,25,7,19,13,19,25,1,19,1,19,25,13,13,25,7,19,25,7,25,37,19,37,19,37,19,25,13,19,1,19,25,1,25,13,7,19,25,81,80,85,115,234,95,111,112,13,19,37,19,25,1,13,25,7,19,1,19,1,6,4,205,188,206,5,227,109,106,65,51,13,37,19,25,37,25,7,25,7,19,13,13,19,1,1,19,1,19,25,1,25,7,13,19,25,37,19,37,19,25,1,13,25,106,93,92,93,7,13,19,25,80,84,114,233,112,180,166,169,170,164,7,25,92,97,98,150,37,19,25,7,62,110,100,146,142,173,13,25,1,19,25,13,7,19,25,7,19,25,37,25,7,19,186,82,182,183,60,155,156,130,7,7,19,37,19,13,19,24,22,217,194,218,23,227,80,64,176,1,25,13,13,19,25,13,25,170,136,7,7,19,7,13,7,19,25,13,13,25,37,19,25,37,19,25,13,25,91,170,178,177,37,42,40,225,198,226,41,227,64,63,57,174,117,13,7,19,25,13,19,13,25,1,25,37,19,25,37,25,13,7,19,25,13,19,44,45,83,184,185,155,160,157,158,159,133,1,13,19,7,12,10,209,190,210,11,227,62,50,118,52,13,19,25,13,19,1,19,25,37,19,37,25,146,151,7,25,13,13,19,25,177,135,37,19,37,19,25,7,25,48,103,107,7,13,19,25,13,19,25,13,19,7,19,1,19,7,7,19,25,37,19,37,25,37,19,7,25,37,38,223,197,224,39,227,134,147,7,1,19,25,13,25,13,18,16,213,192,214,17,227,44,79,1,25,37,19,25,7,1,19,25,13,19,25,64,52,13,19,13,19,25,79,69,183,67,13,19,37,19,1,7,25,7,7,19,13,25,91,94,121,181,7,25,202,98,143,129,13,14,211,191,212,15,227,97,144,1,19,1,19,25,13,19,25,1,19,25,1,7,25,185,175,1,19,37,19,1,1,25,7,25,1,19,25,37,25,37,25,82,88,116,1,19,60,154,113,81,90,235,232,170,172,1,7,25,1,19,1,19,64,146,141,13,19,95,7,8,207,189,208,9,227,101,1,116,83,89,172,7,19,13,19,136,7,25,0,0,0,37,19,7,19,93,60,37,19,25,64,49,140,125,123,230,46,102,106,13,19,1,19,25,91,99,145,126,124,7,142,7,25,1,19,96,37,25,127,13,68,139,7,1,25,37,19,37,25,201,163,7,201,234,37,19,25,63,66,58,59,74,200,75,71,72,73,237,0,0,0,0,0,0,0,0,0,0,93,7,19,25,37,19,7,19,13,25,37,19,25,178,163,154,1,25,134,155,37,19,166,141,122,60,131,137,153,173,7,19,1,19,25,146,149,132,138,152,112,110,1,19,1,19,170,167,168,171,144,1,19,183,1,43,37,25,78,69,70,32,1,2,203,187,204,3,227,31,22,217,50,7,19,232,148,26,219,195,120,118,37,19,57,54,53,55,56,98,128,37,25,13,96,164,162,34,37,19,25,1,159,167,161,37,19,25,50,119,236,164,36,179,37,19,34,95,37,19,25,170,26,219,195,77,97,9,61,37,19,25,141,162,81,102,1,25,165,37,25,47,104,108],"nativeSymbol":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"innerWindowID":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"implementation":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"line":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"column":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"length":887},"stringArray":["","(anonymous namespace)::A()","(anonymous namespace)::A()::$_0::operator()() const","(anonymous namespace)::A()::$_0::operator()() const::'lambda0'(unsigned int)::operator()(unsigned int) const","(anonymous namespace)::A()::ScopedEvent72::EventFinalizer::~EventFinalizer()","(anonymous namespace)::A()::ScopedEvent72::EventFinalizer::~EventFinalizer()::'lambda0'(unsigned int)::operator()(unsigned int) const","(anonymous namespace)::A()::ScopedEvent72::~ScopedEvent72()","(anonymous namespace)::B()","(anonymous namespace)::B()::$_0::operator()() const","(anonymous namespace)::B()::$_0::operator()() const::'lambda0'(unsigned int)::operator()(unsigned int) const","(anonymous namespace)::B()::ScopedEvent60::EventFinalizer::~EventFinalizer()","(anonymous namespace)::B()::ScopedEvent60::EventFinalizer::~EventFinalizer()::'lambda0'(unsigned int)::operator()(unsigned int) const","(anonymous namespace)::B()::ScopedEvent60::~ScopedEvent60()","(anonymous namespace)::C()","(anonymous namespace)::C()::$_0::operator()() const","(anonymous namespace)::C()::$_0::operator()() const::'lambda0'(unsigned int)::operator()(unsigned int) const","(anonymous namespace)::C()::ScopedEvent49::EventFinalizer::~EventFinalizer()","(anonymous namespace)::C()::ScopedEvent49::EventFinalizer::~EventFinalizer()::'lambda0'(unsigned int)::operator()(unsigned int) const","(anonymous namespace)::C()::ScopedEvent49::~ScopedEvent49()","(anonymous namespace)::D()","(anonymous namespace)::D()::$_0::operator()() const","(anonymous namespace)::D()::$_0::operator()() const::'lambda0'(unsigned int)::operator()(unsigned int) const","(anonymous namespace)::D()::ScopedEvent39::EventFinalizer::~EventFinalizer()","(anonymous namespace)::D()::ScopedEvent39::EventFinalizer::~EventFinalizer()::'lambda0'(unsigned int)::operator()(unsigned int) const","(anonymous namespace)::D()::ScopedEvent39::~ScopedEvent39()","(anonymous namespace)::E()","(anonymous namespace)::E()::$_0::operator()() const","(anonymous namespace)::E()::$_0::operator()() const::'lambda0'(unsigned int)::operator()(unsigned int) const","(anonymous namespace)::E()::ScopedEvent30::EventFinalizer::~EventFinalizer()","(anonymous namespace)::E()::ScopedEvent30::EventFinalizer::~EventFinalizer()::'lambda0'(unsigned int)::operator()(unsigned int) const","(anonymous namespace)::E()::ScopedEvent30::~ScopedEvent30()","__aarch64_ldadd4_relax","__kernel_clock_gettime","__libc_init","__strcmp_aarch64","_start_main","decltype(std::__ndk1::__unwrap_iter_impl::__unwrap(std::declval())) std::__ndk1::__unwrap_iter[abi:v170000], 0>(unsigned char const*)","main","main::$_0::operator()() const","main::$_0::operator()() const::'lambda0'(unsigned int)::operator()(unsigned int) const","main::ScopedEvent94::EventFinalizer::~EventFinalizer()","main::ScopedEvent94::EventFinalizer::~EventFinalizer()::'lambda0'(unsigned int)::operator()(unsigned int) const","main::ScopedEvent94::~ScopedEvent94()","perfetto::Category::GetNameSize(unsigned long) const","perfetto::EventContext perfetto::internal::TrackEventDataSource::WriteTrackEvent(perfetto::DataSource::TraceContext&, unsigned long const&, perfetto::StaticString const&, perfetto::protos::pbzero::perfetto_pbzero_enum_TrackEvent::Type, perfetto::Track const&)","perfetto::EventContext perfetto::internal::TrackEventDataSource::WriteTrackEventImpl(perfetto::DataSource::TraceContext&, unsigned long const&, perfetto::StaticString const&, perfetto::protos::pbzero::perfetto_pbzero_enum_TrackEvent::Type, perfetto::Track const&, perfetto::TraceTimestamp const&)","perfetto::EventContext::EventContext(perfetto::EventContext&&)","perfetto::EventContext::EventContext(perfetto::TraceWriterBase*, protozero::MessageHandle, perfetto::internal::TrackEventIncrementalState*, perfetto::internal::TrackEventTlsState*)","perfetto::EventContext::~EventContext()","perfetto::PatchList::empty() const","perfetto::SharedMemoryABI::Chunk::IncrementPacketCount()","perfetto::SharedMemoryABI::Chunk::is_valid() const","perfetto::SharedMemoryABI::ChunkHeader::Packets std::__ndk1::__cxx_atomic_load[abi:v170000](std::__ndk1::__cxx_atomic_base_impl const*, std::__ndk1::memory_order)","perfetto::SharedMemoryABI::TryAcquireChunk(unsigned long, unsigned long, perfetto::SharedMemoryABI::ChunkState, perfetto::SharedMemoryABI::ChunkHeader const*)","perfetto::SharedMemoryABI::TryAcquireChunkForWriting(unsigned long, unsigned long, perfetto::SharedMemoryABI::ChunkHeader const*)","perfetto::SharedMemoryABI::page_header(unsigned long)","perfetto::SharedMemoryABI::page_start(unsigned long)","perfetto::SharedMemoryArbiterImpl::GetNewChunk(perfetto::SharedMemoryABI::ChunkHeader const&, perfetto::BufferExhaustedPolicy)","perfetto::SharedMemoryArbiterImpl::ReturnCompletedChunk(perfetto::SharedMemoryABI::Chunk, unsigned int, perfetto::PatchList*)","perfetto::SharedMemoryArbiterImpl::UpdateCommitDataRequest(perfetto::SharedMemoryABI::Chunk, unsigned short, unsigned int, perfetto::PatchList*)","perfetto::SmallInternedDataTraits::Index::LookUpOrInsert(unsigned long*, char const* const&)","perfetto::StaticString::StaticString(char const*)","perfetto::TraceWriterImpl::FinishTracePacket()","perfetto::TraceWriterImpl::GetNewBuffer()","perfetto::TraceWriterImpl::NewTracePacket()","perfetto::TraceWriterImpl::OnMessageFinalized(protozero::Message*)","perfetto::TraceWriterImpl::ReturnCompletedChunk()","perfetto::TrackEventInternedDataIndex::GetOrCreateIndexForField(perfetto::internal::TrackEventIncrementalState*)","perfetto::TrackEventInternedDataIndex::GetOrCreateIndexForField(perfetto::internal::TrackEventIncrementalState*)","perfetto::base::GetBootTimeNs()","perfetto::base::GetTimeInternalNs(int)","perfetto::base::ThreadTaskRunner::PostDelayedTask(std::__ndk1::function, unsigned int)","perfetto::base::UnixTaskRunner::PostDelayedTask(std::__ndk1::function, unsigned int)","perfetto::base::UnixTaskRunner::WakeUp()","perfetto::internal::(anonymous namespace)::NonReentrantTaskRunner::PostDelayedTask(std::__ndk1::function, unsigned int)","perfetto::internal::(anonymous namespace)::NonReentrantTaskRunner::PostDelayedTask(std::__ndk1::function, unsigned int)::'lambda'()::operator()() const","perfetto::internal::DataSourceStaticState::TryGetCached(unsigned int, unsigned long)","perfetto::internal::TrackEventCategoryRegistry::GetCategoryState(unsigned long) const","perfetto::internal::TrackEventInternal::GetTimeNs()","perfetto::internal::TrackEventInternal::GetTraceTime()","perfetto::internal::TrackEventInternal::NewTracePacket(perfetto::TraceWriterBase*, perfetto::internal::TrackEventIncrementalState*, perfetto::internal::TrackEventTlsState const&, perfetto::TraceTimestamp, unsigned int)","perfetto::internal::TrackEventInternal::WriteEvent(perfetto::TraceWriterBase*, perfetto::internal::TrackEventIncrementalState*, perfetto::internal::TrackEventTlsState&, perfetto::Category const*, perfetto::protos::pbzero::perfetto_pbzero_enum_TrackEvent::Type, perfetto::TraceTimestamp const&, bool)","perfetto::internal::TrackEventInternal::WriteEvent(perfetto::TraceWriterBase*, perfetto::internal::TrackEventIncrementalState*, perfetto::internal::TrackEventTlsState&, perfetto::Category const*, perfetto::protos::pbzero::perfetto_pbzero_enum_TrackEvent::Type, perfetto::TraceTimestamp const&, bool)::$_0::operator()(char const*, unsigned long) const","perfetto::internal::TrackEventInternal::WriteEventName(perfetto::StaticString, perfetto::EventContext&, perfetto::internal::TrackEventTlsState const&)","perfetto::protos::pbzero::TracePacket::set_sequence_flags(unsigned int)","perfetto::protos::pbzero::TracePacket::set_timestamp(unsigned long)","perfetto::protos::pbzero::TrackEvent* perfetto::protos::pbzero::TracePacket::set_track_event()","perfetto::protos::pbzero::TrackEvent* protozero::Message::BeginNestedMessage(unsigned int)","perfetto::protos::pbzero::TrackEvent::add_category_iids(unsigned long)","perfetto::protos::pbzero::TrackEvent::set_name_iid(unsigned long)","perfetto::protos::pbzero::TrackEvent::set_type(perfetto::protos::pbzero::perfetto_pbzero_enum_TrackEvent::Type)","protozero::Message::BeginNestedMessageInternal(unsigned int)","protozero::Message::EndNestedMessage()","protozero::Message::Finalize()","protozero::Message::Reset(protozero::ScatteredStreamWriter*, protozero::MessageArena*)","protozero::Message::WriteToStream(unsigned char const*, unsigned char const*)","protozero::Message::is_finalized() const","protozero::MessageArena::DeleteLastMessage(protozero::Message*)","protozero::MessageArena::DeleteLastMessageInternal()","protozero::MessageArena::NewMessage()","protozero::MessageArena::Reset()","protozero::MessageHandle::MessageHandle(perfetto::protos::pbzero::TracePacket*)","protozero::MessageHandle::MessageHandle(protozero::MessageHandle&&)","protozero::MessageHandle::operator bool() const","protozero::MessageHandle::operator->() const","protozero::MessageHandle::~MessageHandle()","protozero::MessageHandleBase::FinalizeMessage()","protozero::MessageHandleBase::operator bool() const","protozero::MessageHandleBase::operator->() const","protozero::MessageHandleBase::~MessageHandleBase()","protozero::RootMessage::Reset(protozero::ScatteredStreamWriter*)","protozero::ScatteredStreamWriter::WriteBytes(unsigned char const*, unsigned long)","protozero::ScatteredStreamWriter::WriteBytesUnsafe(unsigned char const*, unsigned long)","protozero::ScatteredStreamWriter::bytes_available() const","protozero::internal::FieldWriter<(protozero::proto_utils::ProtoSchemaType)13>::Append(protozero::Message&, unsigned int, unsigned int)","protozero::internal::FieldWriter<(protozero::proto_utils::ProtoSchemaType)4>::Append(protozero::Message&, unsigned int, unsigned long)","protozero::proto_utils::MakeTagVarInt(unsigned int)","pthread_mutex_lock","std::__ndk1::__atomic_base::load[abi:v170000](std::__ndk1::memory_order) const","std::__ndk1::__atomic_base::store[abi:v170000](perfetto::SharedMemoryABI::ChunkHeader::Packets, std::__ndk1::memory_order)","std::__ndk1::__atomic_base::load[abi:v170000](std::__ndk1::memory_order) const","std::__ndk1::__atomic_base::fetch_add[abi:v170000](unsigned int, std::__ndk1::memory_order)","std::__ndk1::__compressed_pair*>, std::__ndk1::allocator > >::first[abi:v170000]()","std::__ndk1::__compressed_pair_elem*>, 0, false>::__get[abi:v170000]() const","std::__ndk1::__compressed_pair_elem*>, 0, false>::__get[abi:v170000]()","std::__ndk1::__forward_list_base >::__before_begin[abi:v170000]() const","std::__ndk1::__forward_list_base >::__before_begin[abi:v170000]()","std::__ndk1::__forward_list_const_iterator*>::__forward_list_const_iterator[abi:v170000](std::__ndk1::__forward_list_node*)","std::__ndk1::__forward_list_const_iterator*>::__forward_list_const_iterator[abi:v170000](std::nullptr_t)","std::__ndk1::__forward_node_traits*>::__as_iter_node[abi:v170000](std::__ndk1::__forward_list_node*)","std::__ndk1::__map_iterator, std::__ndk1::__tree_node, void*>*, long> >::__map_iterator[abi:v170000](std::__ndk1::__tree_iterator, std::__ndk1::__tree_node, void*>*, long>)","std::__ndk1::__map_iterator, std::__ndk1::__tree_node, void*>*, long> >::operator->[abi:v170000]() const","std::__ndk1::__map_value_compare, std::__ndk1::less, true>::operator()[abi:v170000](char const* const&, std::__ndk1::__value_type const&) const","std::__ndk1::__tree, std::__ndk1::__map_value_compare, std::__ndk1::less, true>, std::__ndk1::allocator > >::value_comp[abi:v170000]()","std::__ndk1::__tree_node_base*& std::__ndk1::__tree, std::__ndk1::__map_value_compare, std::__ndk1::less, true>, std::__ndk1::allocator > >::__find_equal(std::__ndk1::__tree_end_node*>*&, char const* const&)","std::__ndk1::__unwrap_iter_impl::__rewrap[abi:v170000](unsigned char const*, unsigned char const*)","std::__ndk1::__unwrap_iter_impl::__rewrap[abi:v170000](unsigned char*, unsigned char*)","std::__ndk1::__value_type::__get_value[abi:v170000]()","std::__ndk1::__value_type::__get_value[abi:v170000]() const","std::__ndk1::array > >, 32ul>::data[abi:v170000]()","std::__ndk1::forward_list >::empty[abi:v170000]() const","std::__ndk1::forward_list >::before_begin[abi:v170000]()","std::__ndk1::forward_list >::begin[abi:v170000]()","std::__ndk1::forward_list >::cbegin[abi:v170000]() const","std::__ndk1::forward_list >::empty[abi:v170000]() const","std::__ndk1::forward_list >::front[abi:v170000]()","std::__ndk1::forward_list >::resize(unsigned long)","std::__ndk1::less::operator()[abi:v170000](char const* const&, char const* const&) const","std::__ndk1::make_unsigned::value, unsigned int, long>::type>::type protozero::proto_utils::ExtendValueForVarIntSerialization(unsigned int)","std::__ndk1::operator!=[abi:v170000](std::__ndk1::__forward_list_iterator*> const&, std::__ndk1::__forward_list_iterator*> const&)","std::__ndk1::operator==[abi:v170000](std::__ndk1::__forward_list_const_iterator*> const&, std::__ndk1::__forward_list_const_iterator*> const&)","std::__ndk1::operator==[abi:v170000](std::__ndk1::__forward_list_iterator*> const&, std::__ndk1::__forward_list_iterator*> const&)","std::__ndk1::pair const* std::__ndk1::__launder[abi:v170000] const>(std::__ndk1::pair const*)","std::__ndk1::pair* std::__ndk1::__launder[abi:v170000] >(std::__ndk1::pair*)","std::__ndk1::pair::pair[abi:v170000](char const* const&, unsigned long&)","std::__ndk1::pair, std::__ndk1::__tree_node, void*>*, long> >, bool> std::__ndk1::map, std::__ndk1::allocator > >::insert[abi:v170000], void>(std::__ndk1::pair&&)","std::__ndk1::pair, std::__ndk1::__tree_node, void*>*, long> >, bool>::pair[abi:v170000], std::__ndk1::__tree_node, void*>*, long>, bool, (void*)0>(std::__ndk1::pair, std::__ndk1::__tree_node, void*>*, long>, bool>&&)","std::__ndk1::pair, std::__ndk1::__tree_node, void*>*, long>, bool> std::__ndk1::__tree, std::__ndk1::__map_value_compare, std::__ndk1::less, true>, std::__ndk1::allocator > >::__emplace_unique[abi:v170000] >(std::__ndk1::pair&&)","std::__ndk1::pair, std::__ndk1::__tree_node, void*>*, long>, bool> std::__ndk1::__tree, std::__ndk1::__map_value_compare, std::__ndk1::less, true>, std::__ndk1::allocator > >::__emplace_unique_extract_key[abi:v170000] >(std::__ndk1::pair&&, std::__ndk1::__extract_key_first_tag)","std::__ndk1::pair, std::__ndk1::__tree_node, void*>*, long>, bool> std::__ndk1::__tree, std::__ndk1::__map_value_compare, std::__ndk1::less, true>, std::__ndk1::allocator > >::__emplace_unique_key_args >(char const* const&, std::__ndk1::pair&&)","std::__ndk1::pair, std::__ndk1::__tree_node, void*>*, long>, bool> std::__ndk1::__tree, std::__ndk1::__map_value_compare, std::__ndk1::less, true>, std::__ndk1::allocator > >::__insert_unique[abi:v170000], void>(std::__ndk1::pair&&)","std::__ndk1::pair::type, std::__ndk1::__unwrap_ref_decay::type> std::__ndk1::make_pair[abi:v170000](unsigned char const*&, unsigned char*&&)","std::__ndk1::pair::type, std::__ndk1::__unwrap_ref_decay::type> std::__ndk1::make_pair[abi:v170000](unsigned char const*&&, unsigned char const*&&)","std::__ndk1::pair::type, std::__ndk1::__unwrap_ref_decay::type> std::__ndk1::make_pair[abi:v170000](unsigned char const*&&, unsigned char*&&)","std::__ndk1::pair std::__ndk1::__unwrap_range[abi:v170000](unsigned char const*, unsigned char const*)","std::__ndk1::pair::pair[abi:v170000](unsigned char const*&&, unsigned char const*&&)","std::__ndk1::pair std::__ndk1::__copy[abi:v170000](unsigned char const*, unsigned char const*, unsigned char*)","std::__ndk1::pair std::__ndk1::__copy_trivial::operator()[abi:v170000](unsigned char const*, unsigned char const*, unsigned char*) const","std::__ndk1::pair std::__ndk1::__copy_trivial_impl[abi:v170000](unsigned char const*, unsigned char const*, unsigned char*)","std::__ndk1::pair std::__ndk1::__dispatch_copy_or_move[abi:v170000], std::__ndk1::__copy_trivial, unsigned char const*, unsigned char const*, unsigned char*>(unsigned char const*, unsigned char const*, unsigned char*)","std::__ndk1::pair std::__ndk1::__unwrap_and_dispatch[abi:v170000], std::__ndk1::__copy_trivial>, unsigned char const*, unsigned char const*, unsigned char*, 0>(unsigned char const*, unsigned char const*, unsigned char*)","std::__ndk1::pair::pair[abi:v170000](unsigned char const*&, unsigned char*&&)","std::__ndk1::pair::pair[abi:v170000](unsigned char const*&&, unsigned char*&&)","std::__ndk1::pointer_traits*>*>::pointer_to[abi:v170000](std::__ndk1::__forward_begin_node*>&)","std::__ndk1::unique_lock::unique_lock[abi:v170000](std::__ndk1::mutex&)","std::__ndk1::unique_ptr >::get[abi:v170000]() const","std::__ndk1::unique_ptr, std::__ndk1::default_delete > >::get[abi:v170000]() const","unsigned char const* std::__ndk1::__rewrap_iter[abi:v170000] >(unsigned char const*, unsigned char const*)","unsigned char const* std::__ndk1::__rewrap_range[abi:v170000](unsigned char const*, unsigned char const*)","unsigned char const* std::__ndk1::__to_address[abi:v170000](unsigned char const*)","unsigned char* std::__ndk1::copy[abi:v170000](unsigned char const*, unsigned char const*, unsigned char*)","unsigned int std::__ndk1::__cxx_atomic_fetch_add[abi:v170000](std::__ndk1::__cxx_atomic_base_impl*, unsigned int, std::__ndk1::memory_order)","unsigned long perfetto::TrackEventInternedDataIndex::Get(perfetto::EventContext*, char const* const&, unsigned long&)","unsigned long perfetto::TrackEventInternedDataIndex::Get(perfetto::internal::TrackEventIncrementalState*, char const* const&, unsigned long&)","unsigned long perfetto::TrackEventInternedDataIndex::Get<>(perfetto::EventContext*, char const* const&)","unsigned long perfetto::TrackEventInternedDataIndex::Get<>(perfetto::internal::TrackEventIncrementalState*, char const* const&)","void perfetto::Category::ForEachGroupMember(perfetto::internal::TrackEventInternal::WriteEvent(perfetto::TraceWriterBase*, perfetto::internal::TrackEventIncrementalState*, perfetto::internal::TrackEventTlsState&, perfetto::Category const*, perfetto::protos::pbzero::perfetto_pbzero_enum_TrackEvent::Type, perfetto::TraceTimestamp const&, bool)::$_0) const","void perfetto::DataSource::CallIfEnabled::CategoryTracePointTraits, void perfetto::internal::TrackEventDataSource::CallIfCategoryEnabled<(anonymous namespace)::A()::$_0::operator()() const::'lambda0'(unsigned int)>(unsigned long, (anonymous namespace)::A()::$_0::operator()() const::'lambda0'(unsigned int))::'lambda'(unsigned int)>(void perfetto::internal::TrackEventDataSource::CallIfCategoryEnabled<(anonymous namespace)::A()::$_0::operator()() const::'lambda0'(unsigned int)>(unsigned long, (anonymous namespace)::A()::$_0::operator()() const::'lambda0'(unsigned int))::'lambda'(unsigned int), (anonymous namespace)::A()::$_0::operator()() const::'lambda0'(unsigned int)::TracePointData)","void perfetto::DataSource::CallIfEnabled::CategoryTracePointTraits, void perfetto::internal::TrackEventDataSource::CallIfCategoryEnabled<(anonymous namespace)::A()::ScopedEvent72::EventFinalizer::~EventFinalizer()::'lambda0'(unsigned int)>(unsigned long, (anonymous namespace)::A()::ScopedEvent72::EventFinalizer::~EventFinalizer()::'lambda0'(unsigned int))::'lambda'(unsigned int)>(void perfetto::internal::TrackEventDataSource::CallIfCategoryEnabled<(anonymous namespace)::A()::ScopedEvent72::EventFinalizer::~EventFinalizer()::'lambda0'(unsigned int)>(unsigned long, (anonymous namespace)::A()::ScopedEvent72::EventFinalizer::~EventFinalizer()::'lambda0'(unsigned int))::'lambda'(unsigned int), (anonymous namespace)::A()::ScopedEvent72::EventFinalizer::~EventFinalizer()::'lambda0'(unsigned int)::TracePointData)","void perfetto::DataSource::CallIfEnabled::CategoryTracePointTraits, void perfetto::internal::TrackEventDataSource::CallIfCategoryEnabled<(anonymous namespace)::B()::$_0::operator()() const::'lambda0'(unsigned int)>(unsigned long, (anonymous namespace)::B()::$_0::operator()() const::'lambda0'(unsigned int))::'lambda'(unsigned int)>(void perfetto::internal::TrackEventDataSource::CallIfCategoryEnabled<(anonymous namespace)::B()::$_0::operator()() const::'lambda0'(unsigned int)>(unsigned long, (anonymous namespace)::B()::$_0::operator()() const::'lambda0'(unsigned int))::'lambda'(unsigned int), (anonymous namespace)::B()::$_0::operator()() const::'lambda0'(unsigned int)::TracePointData)","void perfetto::DataSource::CallIfEnabled::CategoryTracePointTraits, void perfetto::internal::TrackEventDataSource::CallIfCategoryEnabled<(anonymous namespace)::B()::ScopedEvent60::EventFinalizer::~EventFinalizer()::'lambda0'(unsigned int)>(unsigned long, (anonymous namespace)::B()::ScopedEvent60::EventFinalizer::~EventFinalizer()::'lambda0'(unsigned int))::'lambda'(unsigned int)>(void perfetto::internal::TrackEventDataSource::CallIfCategoryEnabled<(anonymous namespace)::B()::ScopedEvent60::EventFinalizer::~EventFinalizer()::'lambda0'(unsigned int)>(unsigned long, (anonymous namespace)::B()::ScopedEvent60::EventFinalizer::~EventFinalizer()::'lambda0'(unsigned int))::'lambda'(unsigned int), (anonymous namespace)::B()::ScopedEvent60::EventFinalizer::~EventFinalizer()::'lambda0'(unsigned int)::TracePointData)","void perfetto::DataSource::CallIfEnabled::CategoryTracePointTraits, void perfetto::internal::TrackEventDataSource::CallIfCategoryEnabled<(anonymous namespace)::C()::$_0::operator()() const::'lambda0'(unsigned int)>(unsigned long, (anonymous namespace)::C()::$_0::operator()() const::'lambda0'(unsigned int))::'lambda'(unsigned int)>(void perfetto::internal::TrackEventDataSource::CallIfCategoryEnabled<(anonymous namespace)::C()::$_0::operator()() const::'lambda0'(unsigned int)>(unsigned long, (anonymous namespace)::C()::$_0::operator()() const::'lambda0'(unsigned int))::'lambda'(unsigned int), (anonymous namespace)::C()::$_0::operator()() const::'lambda0'(unsigned int)::TracePointData)","void perfetto::DataSource::CallIfEnabled::CategoryTracePointTraits, void perfetto::internal::TrackEventDataSource::CallIfCategoryEnabled<(anonymous namespace)::C()::ScopedEvent49::EventFinalizer::~EventFinalizer()::'lambda0'(unsigned int)>(unsigned long, (anonymous namespace)::C()::ScopedEvent49::EventFinalizer::~EventFinalizer()::'lambda0'(unsigned int))::'lambda'(unsigned int)>(void perfetto::internal::TrackEventDataSource::CallIfCategoryEnabled<(anonymous namespace)::C()::ScopedEvent49::EventFinalizer::~EventFinalizer()::'lambda0'(unsigned int)>(unsigned long, (anonymous namespace)::C()::ScopedEvent49::EventFinalizer::~EventFinalizer()::'lambda0'(unsigned int))::'lambda'(unsigned int), (anonymous namespace)::C()::ScopedEvent49::EventFinalizer::~EventFinalizer()::'lambda0'(unsigned int)::TracePointData)","void perfetto::DataSource::CallIfEnabled::CategoryTracePointTraits, void perfetto::internal::TrackEventDataSource::CallIfCategoryEnabled<(anonymous namespace)::D()::$_0::operator()() const::'lambda0'(unsigned int)>(unsigned long, (anonymous namespace)::D()::$_0::operator()() const::'lambda0'(unsigned int))::'lambda'(unsigned int)>(void perfetto::internal::TrackEventDataSource::CallIfCategoryEnabled<(anonymous namespace)::D()::$_0::operator()() const::'lambda0'(unsigned int)>(unsigned long, (anonymous namespace)::D()::$_0::operator()() const::'lambda0'(unsigned int))::'lambda'(unsigned int), (anonymous namespace)::D()::$_0::operator()() const::'lambda0'(unsigned int)::TracePointData)","void perfetto::DataSource::CallIfEnabled::CategoryTracePointTraits, void perfetto::internal::TrackEventDataSource::CallIfCategoryEnabled<(anonymous namespace)::D()::ScopedEvent39::EventFinalizer::~EventFinalizer()::'lambda0'(unsigned int)>(unsigned long, (anonymous namespace)::D()::ScopedEvent39::EventFinalizer::~EventFinalizer()::'lambda0'(unsigned int))::'lambda'(unsigned int)>(void perfetto::internal::TrackEventDataSource::CallIfCategoryEnabled<(anonymous namespace)::D()::ScopedEvent39::EventFinalizer::~EventFinalizer()::'lambda0'(unsigned int)>(unsigned long, (anonymous namespace)::D()::ScopedEvent39::EventFinalizer::~EventFinalizer()::'lambda0'(unsigned int))::'lambda'(unsigned int), (anonymous namespace)::D()::ScopedEvent39::EventFinalizer::~EventFinalizer()::'lambda0'(unsigned int)::TracePointData)","void perfetto::DataSource::CallIfEnabled::CategoryTracePointTraits, void perfetto::internal::TrackEventDataSource::CallIfCategoryEnabled<(anonymous namespace)::E()::$_0::operator()() const::'lambda0'(unsigned int)>(unsigned long, (anonymous namespace)::E()::$_0::operator()() const::'lambda0'(unsigned int))::'lambda'(unsigned int)>(void perfetto::internal::TrackEventDataSource::CallIfCategoryEnabled<(anonymous namespace)::E()::$_0::operator()() const::'lambda0'(unsigned int)>(unsigned long, (anonymous namespace)::E()::$_0::operator()() const::'lambda0'(unsigned int))::'lambda'(unsigned int), (anonymous namespace)::E()::$_0::operator()() const::'lambda0'(unsigned int)::TracePointData)","void perfetto::DataSource::CallIfEnabled::CategoryTracePointTraits, void perfetto::internal::TrackEventDataSource::CallIfCategoryEnabled<(anonymous namespace)::E()::ScopedEvent30::EventFinalizer::~EventFinalizer()::'lambda0'(unsigned int)>(unsigned long, (anonymous namespace)::E()::ScopedEvent30::EventFinalizer::~EventFinalizer()::'lambda0'(unsigned int))::'lambda'(unsigned int)>(void perfetto::internal::TrackEventDataSource::CallIfCategoryEnabled<(anonymous namespace)::E()::ScopedEvent30::EventFinalizer::~EventFinalizer()::'lambda0'(unsigned int)>(unsigned long, (anonymous namespace)::E()::ScopedEvent30::EventFinalizer::~EventFinalizer()::'lambda0'(unsigned int))::'lambda'(unsigned int), (anonymous namespace)::E()::ScopedEvent30::EventFinalizer::~EventFinalizer()::'lambda0'(unsigned int)::TracePointData)","void perfetto::DataSource::CallIfEnabled::CategoryTracePointTraits, void perfetto::internal::TrackEventDataSource::CallIfCategoryEnabled(unsigned long, main::$_0::operator()() const::'lambda0'(unsigned int))::'lambda'(unsigned int)>(void perfetto::internal::TrackEventDataSource::CallIfCategoryEnabled(unsigned long, main::$_0::operator()() const::'lambda0'(unsigned int))::'lambda'(unsigned int), main::$_0::operator()() const::'lambda0'(unsigned int)::TracePointData)","void perfetto::DataSource::CallIfEnabled::CategoryTracePointTraits, void perfetto::internal::TrackEventDataSource::CallIfCategoryEnabled(unsigned long, main::ScopedEvent94::EventFinalizer::~EventFinalizer()::'lambda0'(unsigned int))::'lambda'(unsigned int)>(void perfetto::internal::TrackEventDataSource::CallIfCategoryEnabled(unsigned long, main::ScopedEvent94::EventFinalizer::~EventFinalizer()::'lambda0'(unsigned int))::'lambda'(unsigned int), main::ScopedEvent94::EventFinalizer::~EventFinalizer()::'lambda0'(unsigned int)::TracePointData)","void perfetto::DataSource::TraceWithInstances::CategoryTracePointTraits, void perfetto::internal::TrackEventDataSource::TraceForCategoryImplNoTimestamp(unsigned int, unsigned long const&, perfetto::StaticString const&, perfetto::protos::pbzero::perfetto_pbzero_enum_TrackEvent::Type, perfetto::Track const&)::'lambda'(perfetto::DataSource::TraceContext)>(unsigned int, perfetto::StaticString, unsigned long::TracePointData)","void perfetto::internal::(anonymous namespace)::NonReentrantTaskRunner::CallWithGuard, unsigned int)::'lambda'()>(perfetto::internal::(anonymous namespace)::NonReentrantTaskRunner::PostDelayedTask(std::__ndk1::function, unsigned int)::'lambda'()) const","void perfetto::internal::DataSourceType::FirstActiveInstance::CategoryTracePointTraits>(perfetto::internal::DataSourceType::InstancesIterator*, perfetto::internal::DataSourceThreadLocalState*, perfetto::internal::TrackEventDataSource::CategoryTracePointTraits::TracePointData)","void perfetto::internal::DataSourceType::NextIteration::CategoryTracePointTraits>(perfetto::internal::DataSourceType::InstancesIterator*, perfetto::internal::DataSourceThreadLocalState*, perfetto::internal::TrackEventDataSource::CategoryTracePointTraits::TracePointData)","void perfetto::internal::TrackEventDataSource::CallIfCategoryEnabled<(anonymous namespace)::A()::$_0::operator()() const::'lambda0'(unsigned int)>(unsigned long, (anonymous namespace)::A()::$_0::operator()() const::'lambda0'(unsigned int))","void perfetto::internal::TrackEventDataSource::CallIfCategoryEnabled<(anonymous namespace)::A()::$_0::operator()() const::'lambda0'(unsigned int)>(unsigned long, (anonymous namespace)::A()::$_0::operator()() const::'lambda0'(unsigned int))::'lambda'(unsigned int)::operator()(unsigned int) const","void perfetto::internal::TrackEventDataSource::CallIfCategoryEnabled<(anonymous namespace)::A()::ScopedEvent72::EventFinalizer::~EventFinalizer()::'lambda0'(unsigned int)>(unsigned long, (anonymous namespace)::A()::ScopedEvent72::EventFinalizer::~EventFinalizer()::'lambda0'(unsigned int))","void perfetto::internal::TrackEventDataSource::CallIfCategoryEnabled<(anonymous namespace)::A()::ScopedEvent72::EventFinalizer::~EventFinalizer()::'lambda0'(unsigned int)>(unsigned long, (anonymous namespace)::A()::ScopedEvent72::EventFinalizer::~EventFinalizer()::'lambda0'(unsigned int))::'lambda'(unsigned int)::operator()(unsigned int) const","void perfetto::internal::TrackEventDataSource::CallIfCategoryEnabled<(anonymous namespace)::B()::$_0::operator()() const::'lambda0'(unsigned int)>(unsigned long, (anonymous namespace)::B()::$_0::operator()() const::'lambda0'(unsigned int))","void perfetto::internal::TrackEventDataSource::CallIfCategoryEnabled<(anonymous namespace)::B()::$_0::operator()() const::'lambda0'(unsigned int)>(unsigned long, (anonymous namespace)::B()::$_0::operator()() const::'lambda0'(unsigned int))::'lambda'(unsigned int)::operator()(unsigned int) const","void perfetto::internal::TrackEventDataSource::CallIfCategoryEnabled<(anonymous namespace)::B()::ScopedEvent60::EventFinalizer::~EventFinalizer()::'lambda0'(unsigned int)>(unsigned long, (anonymous namespace)::B()::ScopedEvent60::EventFinalizer::~EventFinalizer()::'lambda0'(unsigned int))","void perfetto::internal::TrackEventDataSource::CallIfCategoryEnabled<(anonymous namespace)::B()::ScopedEvent60::EventFinalizer::~EventFinalizer()::'lambda0'(unsigned int)>(unsigned long, (anonymous namespace)::B()::ScopedEvent60::EventFinalizer::~EventFinalizer()::'lambda0'(unsigned int))::'lambda'(unsigned int)::operator()(unsigned int) const","void perfetto::internal::TrackEventDataSource::CallIfCategoryEnabled<(anonymous namespace)::C()::$_0::operator()() const::'lambda0'(unsigned int)>(unsigned long, (anonymous namespace)::C()::$_0::operator()() const::'lambda0'(unsigned int))","void perfetto::internal::TrackEventDataSource::CallIfCategoryEnabled<(anonymous namespace)::C()::$_0::operator()() const::'lambda0'(unsigned int)>(unsigned long, (anonymous namespace)::C()::$_0::operator()() const::'lambda0'(unsigned int))::'lambda'(unsigned int)::operator()(unsigned int) const","void perfetto::internal::TrackEventDataSource::CallIfCategoryEnabled<(anonymous namespace)::C()::ScopedEvent49::EventFinalizer::~EventFinalizer()::'lambda0'(unsigned int)>(unsigned long, (anonymous namespace)::C()::ScopedEvent49::EventFinalizer::~EventFinalizer()::'lambda0'(unsigned int))","void perfetto::internal::TrackEventDataSource::CallIfCategoryEnabled<(anonymous namespace)::C()::ScopedEvent49::EventFinalizer::~EventFinalizer()::'lambda0'(unsigned int)>(unsigned long, (anonymous namespace)::C()::ScopedEvent49::EventFinalizer::~EventFinalizer()::'lambda0'(unsigned int))::'lambda'(unsigned int)::operator()(unsigned int) const","void perfetto::internal::TrackEventDataSource::CallIfCategoryEnabled<(anonymous namespace)::D()::$_0::operator()() const::'lambda0'(unsigned int)>(unsigned long, (anonymous namespace)::D()::$_0::operator()() const::'lambda0'(unsigned int))","void perfetto::internal::TrackEventDataSource::CallIfCategoryEnabled<(anonymous namespace)::D()::$_0::operator()() const::'lambda0'(unsigned int)>(unsigned long, (anonymous namespace)::D()::$_0::operator()() const::'lambda0'(unsigned int))::'lambda'(unsigned int)::operator()(unsigned int) const","void perfetto::internal::TrackEventDataSource::CallIfCategoryEnabled<(anonymous namespace)::D()::ScopedEvent39::EventFinalizer::~EventFinalizer()::'lambda0'(unsigned int)>(unsigned long, (anonymous namespace)::D()::ScopedEvent39::EventFinalizer::~EventFinalizer()::'lambda0'(unsigned int))","void perfetto::internal::TrackEventDataSource::CallIfCategoryEnabled<(anonymous namespace)::D()::ScopedEvent39::EventFinalizer::~EventFinalizer()::'lambda0'(unsigned int)>(unsigned long, (anonymous namespace)::D()::ScopedEvent39::EventFinalizer::~EventFinalizer()::'lambda0'(unsigned int))::'lambda'(unsigned int)::operator()(unsigned int) const","void perfetto::internal::TrackEventDataSource::CallIfCategoryEnabled<(anonymous namespace)::E()::$_0::operator()() const::'lambda0'(unsigned int)>(unsigned long, (anonymous namespace)::E()::$_0::operator()() const::'lambda0'(unsigned int))","void perfetto::internal::TrackEventDataSource::CallIfCategoryEnabled<(anonymous namespace)::E()::$_0::operator()() const::'lambda0'(unsigned int)>(unsigned long, (anonymous namespace)::E()::$_0::operator()() const::'lambda0'(unsigned int))::'lambda'(unsigned int)::operator()(unsigned int) const","void perfetto::internal::TrackEventDataSource::CallIfCategoryEnabled<(anonymous namespace)::E()::ScopedEvent30::EventFinalizer::~EventFinalizer()::'lambda0'(unsigned int)>(unsigned long, (anonymous namespace)::E()::ScopedEvent30::EventFinalizer::~EventFinalizer()::'lambda0'(unsigned int))","void perfetto::internal::TrackEventDataSource::CallIfCategoryEnabled<(anonymous namespace)::E()::ScopedEvent30::EventFinalizer::~EventFinalizer()::'lambda0'(unsigned int)>(unsigned long, (anonymous namespace)::E()::ScopedEvent30::EventFinalizer::~EventFinalizer()::'lambda0'(unsigned int))::'lambda'(unsigned int)::operator()(unsigned int) const","void perfetto::internal::TrackEventDataSource::CallIfCategoryEnabled(unsigned long, main::$_0::operator()() const::'lambda0'(unsigned int))","void perfetto::internal::TrackEventDataSource::CallIfCategoryEnabled(unsigned long, main::$_0::operator()() const::'lambda0'(unsigned int))::'lambda'(unsigned int)::operator()(unsigned int) const","void perfetto::internal::TrackEventDataSource::CallIfCategoryEnabled(unsigned long, main::ScopedEvent94::EventFinalizer::~EventFinalizer()::'lambda0'(unsigned int))","void perfetto::internal::TrackEventDataSource::CallIfCategoryEnabled(unsigned long, main::ScopedEvent94::EventFinalizer::~EventFinalizer()::'lambda0'(unsigned int))::'lambda'(unsigned int)::operator()(unsigned int) const","void perfetto::internal::TrackEventDataSource::TraceForCategory(unsigned int, unsigned long const&, perfetto::StaticString const&, perfetto::protos::pbzero::perfetto_pbzero_enum_TrackEvent::Type)","void perfetto::internal::TrackEventDataSource::TraceForCategoryBody(unsigned int, unsigned long const&, perfetto::StaticString const&, perfetto::protos::pbzero::perfetto_pbzero_enum_TrackEvent::Type)","void perfetto::internal::TrackEventDataSource::TraceForCategoryImplNoTimestamp(unsigned int, unsigned long const&, perfetto::StaticString const&, perfetto::protos::pbzero::perfetto_pbzero_enum_TrackEvent::Type, perfetto::Track const&)","void perfetto::internal::TrackEventDataSource::TraceForCategoryImplNoTimestamp(unsigned int, unsigned long const&, perfetto::StaticString const&, perfetto::protos::pbzero::perfetto_pbzero_enum_TrackEvent::Type, perfetto::Track const&)::'lambda'(perfetto::DataSource::TraceContext)::operator()(perfetto::DataSource::TraceContext) const","void perfetto::internal::TrackEventDataSource::TraceWithInstances::TraceForCategoryImplNoTimestamp(unsigned int, unsigned long const&, perfetto::StaticString const&, perfetto::protos::pbzero::perfetto_pbzero_enum_TrackEvent::Type, perfetto::Track const&)::'lambda'(perfetto::DataSource::TraceContext)>(unsigned int, unsigned long const&, perfetto::StaticString)","void protozero::Message::AppendVarInt(unsigned int, perfetto::protos::pbzero::perfetto_pbzero_enum_TrackEvent::Type)","void protozero::Message::AppendVarInt(unsigned int, unsigned int)","void protozero::Message::AppendVarInt(unsigned int, unsigned long)","void protozero::internal::FieldWriter<(protozero::proto_utils::ProtoSchemaType)14>::Append(protozero::Message&, unsigned int, perfetto::protos::pbzero::perfetto_pbzero_enum_TrackEvent::Type)","void std::__ndk1::__cxx_atomic_store[abi:v170000](std::__ndk1::__cxx_atomic_base_impl*, perfetto::SharedMemoryABI::ChunkHeader::Packets, std::__ndk1::memory_order)","write"],"funcTable":{"name":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237],"isJS":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"relevantForJS":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"resource":[-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],"fileName":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"lineNumber":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"columnNumber":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"length":238},"resourceTable":{"length":0,"lib":[],"name":[],"host":[],"type":[]},"nativeSymbols":{"libIndex":[],"address":[],"name":[],"functionSize":[],"length":0}},{"processType":"default","name":"","isMainThread":0,"pid":16388,"tid":16388,"samples":{"stack":[],"time":[],"weight":null,"weightType":"samples","length":0},"markers":{"data":[],"name":[],"startTime":[],"endTime":[],"phase":[],"category":[],"length":0},"stackTable":{"frame":[],"category":[],"subcategory":[],"prefix":[],"length":0},"frameTable":{"address":[],"inlineDepth":[],"category":[],"subcategory":[],"func":[],"nativeSymbol":[],"innerWindowID":[],"implementation":[],"line":[],"column":[],"length":0},"stringArray":[],"funcTable":{"name":[],"isJS":[],"relevantForJS":[],"resource":[],"fileName":[],"lineNumber":[],"columnNumber":[],"length":0},"resourceTable":{"length":0,"lib":[],"name":[],"host":[],"type":[]},"nativeSymbols":{"libIndex":[],"address":[],"name":[],"functionSize":[],"length":0}},{"processType":"default","name":"ThreadPoolForegroundWorker","isMainThread":0,"pid":16388,"tid":16414,"samples":{"stack":[],"time":[],"weight":null,"weightType":"samples","length":0},"markers":{"data":[],"name":[],"startTime":[],"endTime":[],"phase":[],"category":[],"length":0},"stackTable":{"frame":[],"category":[],"subcategory":[],"prefix":[],"length":0},"frameTable":{"address":[],"inlineDepth":[],"category":[],"subcategory":[],"func":[],"nativeSymbol":[],"innerWindowID":[],"implementation":[],"line":[],"column":[],"length":0},"stringArray":[],"funcTable":{"name":[],"isJS":[],"relevantForJS":[],"resource":[],"fileName":[],"lineNumber":[],"columnNumber":[],"length":0},"resourceTable":{"length":0,"lib":[],"name":[],"host":[],"type":[]},"nativeSymbols":{"libIndex":[],"address":[],"name":[],"functionSize":[],"length":0}},{"processType":"default","name":"","isMainThread":0,"pid":3353,"tid":3353,"samples":{"stack":[],"time":[],"weight":null,"weightType":"samples","length":0},"markers":{"data":[],"name":[],"startTime":[],"endTime":[],"phase":[],"category":[],"length":0},"stackTable":{"frame":[],"category":[],"subcategory":[],"prefix":[],"length":0},"frameTable":{"address":[],"inlineDepth":[],"category":[],"subcategory":[],"func":[],"nativeSymbol":[],"innerWindowID":[],"implementation":[],"line":[],"column":[],"length":0},"stringArray":[],"funcTable":{"name":[],"isJS":[],"relevantForJS":[],"resource":[],"fileName":[],"lineNumber":[],"columnNumber":[],"length":0},"resourceTable":{"length":0,"lib":[],"name":[],"host":[],"type":[]},"nativeSymbols":{"libIndex":[],"address":[],"name":[],"functionSize":[],"length":0}},{"processType":"default","name":"ThreadPoolForegroundWorker","isMainThread":0,"pid":3353,"tid":15835,"samples":{"stack":[],"time":[],"weight":null,"weightType":"samples","length":0},"markers":{"data":[],"name":[],"startTime":[],"endTime":[],"phase":[],"category":[],"length":0},"stackTable":{"frame":[],"category":[],"subcategory":[],"prefix":[],"length":0},"frameTable":{"address":[],"inlineDepth":[],"category":[],"subcategory":[],"func":[],"nativeSymbol":[],"innerWindowID":[],"implementation":[],"line":[],"column":[],"length":0},"stringArray":[],"funcTable":{"name":[],"isJS":[],"relevantForJS":[],"resource":[],"fileName":[],"lineNumber":[],"columnNumber":[],"length":0},"resourceTable":{"length":0,"lib":[],"name":[],"host":[],"type":[]},"nativeSymbols":{"libIndex":[],"address":[],"name":[],"functionSize":[],"length":0}},{"processType":"default","name":"","isMainThread":0,"pid":16339,"tid":16339,"samples":{"stack":[],"time":[],"weight":null,"weightType":"samples","length":0},"markers":{"data":[],"name":[],"startTime":[],"endTime":[],"phase":[],"category":[],"length":0},"stackTable":{"frame":[],"category":[],"subcategory":[],"prefix":[],"length":0},"frameTable":{"address":[],"inlineDepth":[],"category":[],"subcategory":[],"func":[],"nativeSymbol":[],"innerWindowID":[],"implementation":[],"line":[],"column":[],"length":0},"stringArray":[],"funcTable":{"name":[],"isJS":[],"relevantForJS":[],"resource":[],"fileName":[],"lineNumber":[],"columnNumber":[],"length":0},"resourceTable":{"length":0,"lib":[],"name":[],"host":[],"type":[]},"nativeSymbols":{"libIndex":[],"address":[],"name":[],"functionSize":[],"length":0}},{"processType":"default","name":"ThreadPoolForegroundWorker","isMainThread":0,"pid":16339,"tid":16403,"samples":{"stack":[],"time":[],"weight":null,"weightType":"samples","length":0},"markers":{"data":[],"name":[],"startTime":[],"endTime":[],"phase":[],"category":[],"length":0},"stackTable":{"frame":[],"category":[],"subcategory":[],"prefix":[],"length":0},"frameTable":{"address":[],"inlineDepth":[],"category":[],"subcategory":[],"func":[],"nativeSymbol":[],"innerWindowID":[],"implementation":[],"line":[],"column":[],"length":0},"stringArray":[],"funcTable":{"name":[],"isJS":[],"relevantForJS":[],"resource":[],"fileName":[],"lineNumber":[],"columnNumber":[],"length":0},"resourceTable":{"length":0,"lib":[],"name":[],"host":[],"type":[]},"nativeSymbols":{"libIndex":[],"address":[],"name":[],"functionSize":[],"length":0}},{"processType":"default","name":"","isMainThread":0,"pid":9376,"tid":9376,"samples":{"stack":[],"time":[],"weight":null,"weightType":"samples","length":0},"markers":{"data":[],"name":[],"startTime":[],"endTime":[],"phase":[],"category":[],"length":0},"stackTable":{"frame":[],"category":[],"subcategory":[],"prefix":[],"length":0},"frameTable":{"address":[],"inlineDepth":[],"category":[],"subcategory":[],"func":[],"nativeSymbol":[],"innerWindowID":[],"implementation":[],"line":[],"column":[],"length":0},"stringArray":[],"funcTable":{"name":[],"isJS":[],"relevantForJS":[],"resource":[],"fileName":[],"lineNumber":[],"columnNumber":[],"length":0},"resourceTable":{"length":0,"lib":[],"name":[],"host":[],"type":[]},"nativeSymbols":{"libIndex":[],"address":[],"name":[],"functionSize":[],"length":0}},{"processType":"default","name":"ThreadPoolForegroundWorker","isMainThread":0,"pid":9376,"tid":16326,"samples":{"stack":[],"time":[],"weight":null,"weightType":"samples","length":0},"markers":{"data":[],"name":[],"startTime":[],"endTime":[],"phase":[],"category":[],"length":0},"stackTable":{"frame":[],"category":[],"subcategory":[],"prefix":[],"length":0},"frameTable":{"address":[],"inlineDepth":[],"category":[],"subcategory":[],"func":[],"nativeSymbol":[],"innerWindowID":[],"implementation":[],"line":[],"column":[],"length":0},"stringArray":[],"funcTable":{"name":[],"isJS":[],"relevantForJS":[],"resource":[],"fileName":[],"lineNumber":[],"columnNumber":[],"length":0},"resourceTable":{"length":0,"lib":[],"name":[],"host":[],"type":[]},"nativeSymbols":{"libIndex":[],"address":[],"name":[],"functionSize":[],"length":0}}],"profilingLog":null,"profileGatheringLog":null}" diff --git a/test/trace_processor/diff_tests/stdlib/export/tests.py b/test/trace_processor/diff_tests/stdlib/export/tests.py new file mode 100644 index 0000000000..0c76dc606c --- /dev/null +++ b/test/trace_processor/diff_tests/stdlib/export/tests.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +# Copyright (C) 2024 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License a +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from python.generators.diff_tests.testing import DataPath, Path, DiffTestBlueprint, TestSuite + + +class ExportTests(TestSuite): + + def test_to_firefox_profile(self): + return DiffTestBlueprint( + trace=DataPath('zip/perf_track_sym.zip'), + query=""" + INCLUDE PERFETTO MODULE export.to_firefox_profile; + + SELECT export_to_firefox_profile(); + """, + out=Path('firefox_profile.out')) diff --git a/test/trace_processor/diff_tests/stdlib/graphs/critical_path_tests.py b/test/trace_processor/diff_tests/stdlib/graphs/critical_path_tests.py new file mode 100644 index 0000000000..7b746aa899 --- /dev/null +++ b/test/trace_processor/diff_tests/stdlib/graphs/critical_path_tests.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python3 +# Copyright (C) 2024 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License a +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from python.generators.diff_tests.testing import DataPath +from python.generators.diff_tests.testing import Csv +from python.generators.diff_tests.testing import DiffTestBlueprint +from python.generators.diff_tests.testing import TestSuite + +class CriticalPathTests(TestSuite): + + def test_critical_path_empty(self): + return DiffTestBlueprint( + trace=DataPath('counters.json'), + query=""" + INCLUDE PERFETTO MODULE graphs.critical_path; + + WITH edge AS ( + SELECT 0 as source_node_id, 0 AS dest_node_id + WHERE FALSE + ), root AS ( + SELECT 0 as root_node_id, 0 AS capacity + WHERE FALSE + ) + SELECT * FROM _critical_path!( + (SELECT *, source_node_id - dest_node_id AS edge_weight FROM edge), + root + ); + """, + out=Csv(""" + "root_id","parent_id","id" + """)) + + def test_critical_path(self): + return DiffTestBlueprint( + trace=DataPath('counters.json'), + query=""" + INCLUDE PERFETTO MODULE graphs.critical_path; + + WITH edge(source_node_id, dest_node_id) AS ( + values(8, 7), (7, 6), (6, 5), (6, 4), (4, 1), (5, 3), (3, 0) + ), root(root_node_id, capacity) AS ( + values(8, 6) + ) + SELECT * FROM _critical_path!( + (SELECT *, source_node_id - dest_node_id AS edge_weight FROM edge), + root + ); + """, + out=Csv(""" + "root_id","parent_id","id" + 8,"[NULL]",8 + 8,3,0 + 8,5,3 + 8,6,5 + 8,7,6 + 8,8,7 + """)) + + def test_critical_path_intervals(self): + return DiffTestBlueprint( + trace=DataPath('counters.json'), + query=""" + INCLUDE PERFETTO MODULE graphs.critical_path; + + WITH edge(source_node_id, dest_node_id) AS ( + values(8, 7), (7, 6), (6, 5), (6, 4), (4, 1), (5, 3), (3, 0) + ), root(root_node_id, capacity) AS ( + values(8, 6) + ), interval(id, ts, dur, idle_dur) AS ( + values(8, 8, 1, 6), + (7, 7, 1, 1), + (6, 6, 1, 1), + (5, 5, 1, 1), + (4, 4, 1, 1), + (3, 3, 1, 1), + (2, 2, 1, 1), + (1, 1, 1, 1) + ) + SELECT * FROM _critical_path_intervals!( + (SELECT *, source_node_id - dest_node_id AS edge_weight FROM edge), + root, + interval + ); + """, + out=Csv(""" + "root_id","id","ts","dur" + 8,3,3,2 + 8,5,5,1 + 8,6,6,1 + 8,7,7,1 + 8,8,8,1 + """)) diff --git a/test/trace_processor/diff_tests/stdlib/graphs/scan_tests.py b/test/trace_processor/diff_tests/stdlib/graphs/scan_tests.py new file mode 100644 index 0000000000..b221ec130f --- /dev/null +++ b/test/trace_processor/diff_tests/stdlib/graphs/scan_tests.py @@ -0,0 +1,227 @@ +#!/usr/bin/env python3 +# Copyright (C) 2024 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License a +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from python.generators.diff_tests.testing import DataPath +from python.generators.diff_tests.testing import Csv +from python.generators.diff_tests.testing import DiffTestBlueprint +from python.generators.diff_tests.testing import TestSuite + + +class GraphScanTests(TestSuite): + + def test_scan_empty(self): + return DiffTestBlueprint( + trace=DataPath('counters.json'), + query=""" + INCLUDE PERFETTO MODULE graphs.scan; + + WITH foo AS ( + SELECT 0 as source_node_id, 0 AS dest_node_id + WHERE FALSE + ) + SELECT * FROM _graph_scan!( + foo, + (SELECT 0 AS id, 0 as depth WHERE FALSE), + (depth), + ( + select id, depth + 1 as depth + from $table + ) + ) + """, + out=Csv(""" + "id","depth" + """)) + + def test_scan_single_row(self): + return DiffTestBlueprint( + trace=DataPath('counters.json'), + query=""" + INCLUDE PERFETTO MODULE graphs.scan; + + WITH foo AS ( + SELECT 0 as source_node_id, 0 AS dest_node_id + WHERE FALSE + ) + SELECT * FROM _graph_scan!( + foo, + (SELECT 0 AS id, 0 as depth), + (depth), + ( + select id, depth + 1 as depth + from $table + ) + ) + """, + out=Csv(""" + "id","depth" + 0,0 + """)) + + def test_scan_root_depth(self): + return DiffTestBlueprint( + trace=DataPath('counters.json'), + query=""" + INCLUDE PERFETTO MODULE graphs.scan; + + WITH + edges(source_node_id, dest_node_id) AS ( + VALUES(0, 1), (0, 2), (1, 2), (2, 3) + ), + init(id, root_id, depth) AS ( + VALUES(0, 0, 0), (1, 1, 0) + ) + SELECT * FROM _graph_scan!( + edges, + init, + (root_id, depth), + ( + SELECT id, root_id, depth + 1 as depth + FROM $table + ) + ) + ORDER BY id, root_id + """, + out=Csv(""" + "id","root_id","depth" + 0,0,0 + 1,0,1 + 1,1,0 + 2,0,1 + 2,0,2 + 2,1,1 + 3,0,2 + 3,0,3 + 3,1,2 + """)) + + def test_aggregating_scan_empty(self): + return DiffTestBlueprint( + trace=DataPath('counters.json'), + query=""" + INCLUDE PERFETTO MODULE graphs.scan; + + WITH foo AS ( + SELECT 0 as source_node_id, 0 AS dest_node_id + WHERE FALSE + ) + SELECT * FROM _graph_aggregating_scan!( + foo, + (SELECT 0 AS id, 0 as depth WHERE FALSE), + (depth), + ( + select id, depth + 1 as depth + from $table + ) + ) + """, + out=Csv(""" + "id","depth" + """)) + + def test_aggregating_scan_single_row(self): + return DiffTestBlueprint( + trace=DataPath('counters.json'), + query=""" + INCLUDE PERFETTO MODULE graphs.scan; + + WITH foo AS ( + SELECT 0 as source_node_id, 0 AS dest_node_id + WHERE FALSE + ) + SELECT * FROM _graph_aggregating_scan!( + foo, + (SELECT 0 AS id, 0 as depth), + (depth), + ( + select id, depth + 1 as depth + from $table + ) + ) + """, + out=Csv(""" + "id","depth" + 0,0 + """)) + + def test_aggregating_scan_max_recursive(self): + return DiffTestBlueprint( + trace=DataPath('counters.json'), + query=""" + INCLUDE PERFETTO MODULE graphs.scan; + + WITH + edges(source_node_id, dest_node_id) AS ( + VALUES(0, 1), (0, 2), (1, 2), (2, 3), (4, 5) + ), + init(id, max_depth) AS ( + VALUES(0, 0), (4, 0) + ) + SELECT * FROM _graph_aggregating_scan!( + edges, + init, + (max_depth), + ( + SELECT id, MAX(max_depth) + 1 as max_depth + FROM $table + GROUP BY id + ) + ) + ORDER BY id + """, + out=Csv(""" + "id","max_depth" + 0,0 + 1,1 + 2,2 + 3,3 + 4,0 + 5,1 + """)) + + def test_aggregating_scan_min_recursive(self): + return DiffTestBlueprint( + trace=DataPath('counters.json'), + query=""" + INCLUDE PERFETTO MODULE graphs.scan; + + WITH + edges(source_node_id, dest_node_id) AS ( + VALUES(0, 1), (0, 2), (1, 2), (2, 3), (4, 5) + ), + init(id, min_depth) AS ( + VALUES(0, 0), (4, 0) + ) + SELECT * FROM _graph_aggregating_scan!( + edges, + init, + (min_depth), + ( + SELECT id, MIN(min_depth) + 1 as min_depth + FROM $table + GROUP BY id + ) + ) + ORDER BY id + """, + out=Csv(""" + "id","min_depth" + 0,0 + 1,1 + 2,1 + 3,2 + 4,0 + 5,1 + """)) diff --git a/test/trace_processor/diff_tests/stdlib/graphs/search_tests.py b/test/trace_processor/diff_tests/stdlib/graphs/search_tests.py index 37f5d52e52..e1eee98a1a 100644 --- a/test/trace_processor/diff_tests/stdlib/graphs/search_tests.py +++ b/test/trace_processor/diff_tests/stdlib/graphs/search_tests.py @@ -21,7 +21,7 @@ class GraphSearchTests(TestSuite): - def test_empty_table(self): + def test_dfs_empty_table(self): return DiffTestBlueprint( trace=DataPath('counters.json'), query=""" @@ -31,13 +31,16 @@ def test_empty_table(self): SELECT 0 as source_node_id, 0 AS dest_node_id WHERE FALSE ) - SELECT * FROM graph_reachable_dfs!(foo, NULL) + SELECT * FROM graph_reachable_dfs!( + foo, + (SELECT 0 AS node_id WHERE FALSE) + ) """, out=Csv(""" "node_id","parent_node_id" """)) - def test_one_node(self): + def test_dfs_one_node(self): return DiffTestBlueprint( trace=DataPath('counters.json'), query=""" @@ -48,7 +51,7 @@ def test_one_node(self): UNION ALL SELECT 10, 10 ) - SELECT * FROM graph_reachable_dfs!(foo, 5); + SELECT * FROM graph_reachable_dfs!(foo, (SELECT 5 AS node_id)); """, out=Csv(""" "node_id","parent_node_id" @@ -56,7 +59,7 @@ def test_one_node(self): 10,5 """)) - def test_two_nodes(self): + def test_dfs_two_nodes(self): return DiffTestBlueprint( trace=DataPath('counters.json'), query=""" @@ -69,7 +72,7 @@ def test_two_nodes(self): UNION ALL VALUES (0, 10); - SELECT * FROM graph_reachable_dfs!(foo, 0); + SELECT * FROM graph_reachable_dfs!(foo, (SELECT 0 AS node_id)); """, out=Csv(""" "node_id","parent_node_id" @@ -78,7 +81,7 @@ def test_two_nodes(self): 11,10 """)) - def test_lengauer_tarjan_example(self): + def test_dfs_lengauer_tarjan_example(self): return DiffTestBlueprint( trace=DataPath('counters.json'), query=""" @@ -112,7 +115,7 @@ def test_lengauer_tarjan_example(self): NULL, char(parent_node_id) ) AS parent_node_id - FROM graph_reachable_dfs!(bar, unicode('R')) + FROM graph_reachable_dfs!(bar, (SELECT unicode('R') AS node_id)) ORDER BY node_id; """, out=Csv(""" @@ -132,6 +135,119 @@ def test_lengauer_tarjan_example(self): "R","[NULL]" """)) + def test_bfs_empty_table(self): + return DiffTestBlueprint( + trace=DataPath('counters.json'), + query=""" + INCLUDE PERFETTO MODULE graphs.search; + + WITH foo AS ( + SELECT 0 as source_node_id, 0 AS dest_node_id + WHERE FALSE + ) + SELECT * FROM graph_reachable_bfs!( + foo, (SELECT 0 AS node_id WHERE FALSE) + ) + """, + out=Csv(""" + "node_id","parent_node_id" + """)) + + def test_bfs_one_node(self): + return DiffTestBlueprint( + trace=DataPath('counters.json'), + query=""" + INCLUDE PERFETTO MODULE graphs.search; + + WITH foo AS ( + SELECT 5 AS source_node_id, 10 AS dest_node_id + UNION ALL + SELECT 10, 10 + ) + SELECT * FROM graph_reachable_bfs!(foo, (SELECT 5 AS node_id)); + """, + out=Csv(""" + "node_id","parent_node_id" + 5,"[NULL]" + 10,5 + """)) + + def test_bfs_two_nodes(self): + return DiffTestBlueprint( + trace=DataPath('counters.json'), + query=""" + INCLUDE PERFETTO MODULE graphs.search; + + CREATE PERFETTO TABLE foo AS + SELECT NULL AS source_node_id, NULL AS dest_node_id WHERE FALSE + UNION ALL + VALUES (10, 11) + UNION ALL + VALUES (0, 10); + + SELECT * FROM graph_reachable_bfs!(foo, (SELECT 0 AS node_id)); + """, + out=Csv(""" + "node_id","parent_node_id" + 0,"[NULL]" + 10,0 + 11,10 + """)) + + def test_bfs_lengauer_tarjan_example(self): + return DiffTestBlueprint( + trace=DataPath('counters.json'), + query=""" + INCLUDE PERFETTO MODULE graphs.search; + + CREATE PERFETTO TABLE foo AS + SELECT NULL AS source, NULL AS dest WHERE FALSE + UNION ALL + VALUES ('R', 'A'), ('R', 'B'), ('R', 'C'), ('A', 'D') + UNION ALL + VALUES ('B', 'A'), ('B', 'D'), ('B', 'E'), ('C', 'F') + UNION ALL + VALUES ('C', 'G'), ('D', 'L'), ('E', 'H'), ('F', 'I') + UNION ALL + VALUES ('G', 'I'), ('G', 'J'), ('H', 'E'), ('H', 'K') + UNION ALL + VALUES ('I', 'K'), ('J', 'I'), ('K', 'I'), ('K', 'R') + UNION ALL + VALUES ('L', 'H'); + + WITH bar AS ( + SELECT + unicode(source) AS source_node_id, + unicode(dest) AS dest_node_id + FROM foo + ) + SELECT + char(node_id) AS node_id, + IIF( + parent_node_id IS NULL, + NULL, + char(parent_node_id) + ) AS parent_node_id + FROM graph_reachable_bfs!(bar, (SELECT unicode('R') AS node_id)) + ORDER BY node_id; + """, + out=Csv(""" + "node_id","parent_node_id" + "A","R" + "B","R" + "C","R" + "D","A" + "E","B" + "F","C" + "G","C" + "H","E" + "I","F" + "J","G" + "K","H" + "L","D" + "R","[NULL]" + """)) + def test_next_sibling(self): return DiffTestBlueprint( trace=DataPath('counters.json'), diff --git a/test/trace_processor/diff_tests/stdlib/intervals/intersect_tests.py b/test/trace_processor/diff_tests/stdlib/intervals/intersect_tests.py index b4bd982f95..e642538878 100644 --- a/test/trace_processor/diff_tests/stdlib/intervals/intersect_tests.py +++ b/test/trace_processor/diff_tests/stdlib/intervals/intersect_tests.py @@ -47,18 +47,18 @@ def test_simple_interval_intersect(self): ) SELECT * FROM data; - SELECT ts, dur, left_id, right_id - FROM _interval_intersect!(A, B) + SELECT ts, dur, id_0, id_1 + FROM _interval_intersect!((A, B), ()) ORDER BY ts; """, out=Csv(""" - "ts","dur","left_id","right_id" + "ts","dur","id_0","id_1" 1,1,0,0 3,2,0,1 6,1,0,2 """)) - def test_simple_interval_intersect_rev(self): + def test_simple_interval_intersect_two_tabs(self): return DiffTestBlueprint( trace=TextProto(""), # 0 1 2 3 4 5 6 7 @@ -84,17 +84,62 @@ def test_simple_interval_intersect_rev(self): ) SELECT * FROM data; - SELECT ts, dur, left_id, right_id - FROM _interval_intersect!(B, A) + SELECT ts, dur, id_0, id_1 + FROM _interval_intersect!((B, A), ()) ORDER BY ts; """, out=Csv(""" - "ts","dur","left_id","right_id" + "ts","dur","id_0","id_1" 1,1,0,0 3,2,1,0 6,1,2,0 """)) + def test_simple_interval_intersect_three_tabs(self): + return DiffTestBlueprint( + trace=TextProto(""), + # 0 1 2 3 4 5 6 7 + # A: 0 1 1 1 1 1 1 0 + # B: 1 1 0 1 1 0 1 1 + # C: 1 0 1 1 1 1 0 1 + # res: 0 0 0 1 1 0 0 0 + query=""" + INCLUDE PERFETTO MODULE intervals.intersect; + + CREATE PERFETTO TABLE A AS + WITH data(id, ts, dur) AS ( + VALUES + (0, 1, 6) + ) + SELECT * FROM data; + + CREATE PERFETTO TABLE B AS + WITH data(id, ts, dur) AS ( + VALUES + (0, 0, 2), + (1, 3, 2), + (2, 6, 2) + ) + SELECT * FROM data; + + CREATE PERFETTO TABLE C AS + WITH data(id, ts, dur) AS ( + VALUES + (10, 0, 1), + (20, 2, 4), + (30, 7, 1) + ) + SELECT * FROM data; + + SELECT ts, dur, id_0, id_1, id_2 + FROM _interval_intersect!((A, B, C), ()) + ORDER BY ts; + """, + out=Csv(""" + "ts","dur","id_0","id_1","id_2" + 3,2,0,1,20 + """)) + def test_no_overlap(self): return DiffTestBlueprint( trace=TextProto(""), @@ -118,12 +163,12 @@ def test_no_overlap(self): ) SELECT * FROM data; - SELECT ts, dur, left_id, right_id - FROM _interval_intersect!(A, B) + SELECT ts, dur, id_0, id_1 + FROM _interval_intersect!((A, B), ()) ORDER BY ts; """, out=Csv(""" - "ts","dur","left_id","right_id" + "ts","dur","id_0","id_1" """)) def test_no_overlap_rev(self): @@ -149,12 +194,12 @@ def test_no_overlap_rev(self): ) SELECT * FROM data; - SELECT ts, dur, left_id, right_id - FROM _interval_intersect!(B, A) + SELECT ts, dur, id_0, id_1 + FROM _interval_intersect!((B, A), ()) ORDER BY ts; """, out=Csv(""" - "ts","dur","left_id","right_id" + "ts","dur","id_0","id_1" """)) def test_no_empty(self): @@ -176,12 +221,12 @@ def test_no_empty(self): CREATE PERFETTO TABLE B AS SELECT * FROM A LIMIT 0; - SELECT ts, dur, left_id, right_id - FROM _interval_intersect!(A, B) + SELECT ts, dur, id_0, id_1 + FROM _interval_intersect!((A, B), ()) ORDER BY ts; """, out=Csv(""" - "ts","dur","left_id","right_id" + "ts","dur","id_0","id_1" """)) def test_no_empty_rev(self): @@ -203,12 +248,12 @@ def test_no_empty_rev(self): CREATE PERFETTO TABLE B AS SELECT * FROM A LIMIT 0; - SELECT ts, dur, left_id, right_id - FROM _interval_intersect!(B, A) + SELECT ts, dur, id_0, id_1 + FROM _interval_intersect!((B, A), ()) ORDER BY ts; """, out=Csv(""" - "ts","dur","left_id","right_id" + "ts","dur","id_0","id_1" """)) def test_single_point_overlap(self): @@ -234,12 +279,12 @@ def test_single_point_overlap(self): ) SELECT * FROM data; - SELECT ts, dur, left_id, right_id - FROM _interval_intersect!(A, B) + SELECT ts, dur, id_0, id_1 + FROM _interval_intersect!((A, B), ()) ORDER BY ts; """, out=Csv(""" - "ts","dur","left_id","right_id" + "ts","dur","id_0","id_1" """)) def test_single_point_overlap_rev(self): @@ -265,12 +310,12 @@ def test_single_point_overlap_rev(self): ) SELECT * FROM data; - SELECT ts, dur, left_id, right_id - FROM _interval_intersect!(B, A) + SELECT ts, dur, id_0, id_1 + FROM _interval_intersect!((B, A), ()) ORDER BY ts; """, out=Csv(""" - "ts","dur","left_id","right_id" + "ts","dur","id_0","id_1" """)) def test_single_interval(self): @@ -310,6 +355,163 @@ def test_single_interval(self): 2,6,1 """)) + def test_ii_wrong_partition(self): + return DiffTestBlueprint( + trace=TextProto(''), + query=""" + INCLUDE PERFETTO MODULE intervals.intersect; + + CREATE PERFETTO TABLE A + AS + WITH x(id, ts, dur, c0) AS (VALUES(1, 1, 1, 1), (2, 3, 1, 2)) + SELECT * FROM x; + + CREATE PERFETTO TABLE B + AS + WITH x(id, ts, dur, c0) AS (VALUES(1, 5, 1, 3)) + SELECT * FROM x; + + SELECT ts FROM _interval_intersect!((A, B), (c0)); + """, + out=Csv(""" + "ts" + """)) + + def test_compare_with_span_join_partitioned(self): + return DiffTestBlueprint( + trace=DataPath('example_android_trace_30s.pb'), + query=""" + INCLUDE PERFETTO MODULE intervals.intersect; + + CREATE PERFETTO TABLE big_foo AS + SELECT + ts, + dur, + id, + cpu + FROM sched + WHERE dur > 0 AND utid != 0; + + CREATE PERFETTO TABLE small_foo AS + SELECT + ts + 1000 AS ts, + dur + 1000 AS dur, + id * 10 AS id, + cpu + FROM sched + WHERE dur > 0 AND utid != 0; + + CREATE PERFETTO TABLE small_foo_for_sj AS + SELECT + id AS small_id, + ts, + dur, + cpu + FROM small_foo; + + CREATE PERFETTO TABLE big_foo_for_sj AS + SELECT + id AS big_id, + ts, + dur, + cpu + FROM big_foo; + + CREATE VIRTUAL TABLE sj_res + USING SPAN_JOIN( + small_foo_for_sj PARTITIONED cpu, + big_foo_for_sj PARTITIONED cpu); + + CREATE PERFETTO TABLE both AS + SELECT + id_0, + id_1, + cpu, + cat, + count() AS c + FROM ( + SELECT id_0, id_1, ts, dur, cpu, "ii" AS cat + FROM _interval_intersect!((big_foo, small_foo), (cpu)) + UNION + SELECT big_id AS id_0, small_id AS id_1, ts, dur, cpu, "sj" AS cat FROM sj_res + ) + GROUP BY id_0, id_1, ts, dur, cpu ; + + SELECT + SUM(c) FILTER (WHERE c == 2) AS good, + SUM(c) FILTER (WHERE c != 2) AS bad + FROM both; + """, + out=Csv(""" + "good","bad" + 880364,"[NULL]" + """)) + + def test_compare_ii_with_span_join(self): + return DiffTestBlueprint( + trace=DataPath('example_android_trace_30s.pb'), + query=""" + INCLUDE PERFETTO MODULE intervals.intersect; + + CREATE PERFETTO TABLE big_foo AS + SELECT + ts, + dur, + id + FROM sched + WHERE dur > 0 AND utid == 44; + + CREATE PERFETTO TABLE small_foo AS + SELECT + ts, + dur, + id + FROM sched + WHERE dur > 0 AND utid == 103; + + CREATE PERFETTO TABLE small_foo_for_sj AS + SELECT + id AS small_id, + ts, + dur + FROM small_foo; + + CREATE PERFETTO TABLE big_foo_for_sj AS + SELECT + id AS big_id, + ts, + dur + FROM big_foo; + + CREATE VIRTUAL TABLE sj_res + USING SPAN_JOIN( + small_foo_for_sj, + big_foo_for_sj); + + CREATE PERFETTO TABLE both AS + SELECT + left_id, + right_id, + cat, + count() AS c + FROM ( + SELECT id_0 AS left_id, id_1 AS right_id, ts, dur, "ii" AS cat + FROM _interval_intersect!((big_foo, small_foo), ()) + UNION + SELECT big_id AS left_id, small_id AS right_id, ts, dur, "sj" AS cat FROM sj_res + ) + GROUP BY left_id, right_id; + + SELECT + SUM(c) FILTER (WHERE c == 2) AS good, + SUM(c) FILTER (WHERE c != 2) AS bad + FROM both; + """, + out=Csv(""" + "good","bad" + 28,"[NULL]" + """)) + def test_sanity_check(self): return DiffTestBlueprint( trace=DataPath('example_android_trace_30s.pb'), @@ -330,7 +532,7 @@ def test_sanity_check(self): WITH ii AS ( SELECT * - FROM _interval_intersect!(trace_interval, non_overlapping) + FROM _interval_intersect!((trace_interval, non_overlapping), ()) ) SELECT (SELECT count(*) FROM ii) AS ii_count, @@ -372,3 +574,205 @@ def test_sanity_check_single_interval(self): "ii_count","thread_count","ii_sum","thread_sum" 313,313,27540674879,27540674879 """)) + + def test_sanity_multiple_partitions(self): + return DiffTestBlueprint( + trace=DataPath('example_android_trace_30s.pb'), + query=""" + INCLUDE PERFETTO MODULE intervals.intersect; + + SELECT * FROM _interval_intersect!( + ((SELECT id, ts, dur, utid, cpu FROM sched WHERE dur > 0 LIMIT 10), + (SELECT id, ts, dur, utid, cpu FROM sched WHERE dur > 0 LIMIT 10)), + (utid, cpu) + ); + """, + out=Csv(""" + "ts","dur","id_0","id_1","utid","cpu" + 70730062200,125364,0,0,1,0 + 70730187564,20297242,1,1,0,0 + 70731483398,24583,9,9,10,3 + 70731458606,24792,8,8,9,3 + 70731393294,42396,5,5,6,3 + 70731435690,22916,6,6,7,3 + 70731161731,35000,3,3,4,3 + 70731196731,196563,4,4,5,3 + 70731438502,55261,7,7,8,6 + 70731135898,25833,2,2,2,3 + """)) + + def test_sanity_single_partitions(self): + return DiffTestBlueprint( + trace=DataPath('example_android_trace_30s.pb'), + query=""" + INCLUDE PERFETTO MODULE intervals.intersect; + + SELECT * FROM _interval_intersect!( + ((SELECT id, ts, dur, utid, cpu FROM sched WHERE dur > 0 LIMIT 10), + (SELECT id, ts, dur, utid, cpu FROM sched WHERE dur > 0 LIMIT 10)), + (utid) + ); + """, + out=Csv(""" + "ts","dur","id_0","id_1","utid" + 70731458606,24792,8,8,9 + 70731161731,35000,3,3,4 + 70731393294,42396,5,5,6 + 70730187564,20297242,1,1,0 + 70731135898,25833,2,2,2 + 70731438502,55261,7,7,8 + 70731483398,24583,9,9,10 + 70731196731,196563,4,4,5 + 70731435690,22916,6,6,7 + 70730062200,125364,0,0,1 + """)) + + def test_sanity_multiple_tables_and_partitions(self): + return DiffTestBlueprint( + trace=DataPath('example_android_trace_30s.pb'), + query=""" + INCLUDE PERFETTO MODULE intervals.intersect; + + SELECT * FROM _interval_intersect!( + ( + (SELECT id, ts, dur, utid, cpu FROM sched WHERE dur > 0 LIMIT 10), + (SELECT id, ts, dur, utid, cpu FROM sched WHERE dur > 0 LIMIT 10), + (SELECT id, ts, dur, utid, cpu FROM sched WHERE dur > 0 LIMIT 10) + ), + (utid, cpu) + ); + """, + out=Csv(""" + "ts","dur","id_0","id_1","id_2","utid","cpu" + 70730062200,125364,0,0,0,1,0 + 70730187564,20297242,1,1,1,0,0 + 70731483398,24583,9,9,9,10,3 + 70731458606,24792,8,8,8,9,3 + 70731393294,42396,5,5,5,6,3 + 70731435690,22916,6,6,6,7,3 + 70731161731,35000,3,3,3,4,3 + 70731196731,196563,4,4,4,5,3 + 70731438502,55261,7,7,7,8,6 + 70731135898,25833,2,2,2,2,3 + """)) + + def test_sanity_multiple_tables_and_partitions(self): + return DiffTestBlueprint( + trace=DataPath('example_android_trace_30s.pb'), + query=""" + INCLUDE PERFETTO MODULE intervals.intersect; + + SELECT * FROM _interval_intersect!( + ( + (SELECT id, ts, dur, utid, cpu FROM sched WHERE dur > 0 LIMIT 10), + (SELECT id, ts, dur, utid, cpu FROM sched WHERE dur > 0 LIMIT 10), + (SELECT id, ts, dur, utid, cpu FROM sched WHERE dur > 0 LIMIT 10) + ), + (utid, cpu) + ); + """, + out=Csv(""" + "ts","dur","id_0","id_1","id_2","utid","cpu" + 70730062200,125364,0,0,0,1,0 + 70730187564,20297242,1,1,1,0,0 + 70731483398,24583,9,9,9,10,3 + 70731458606,24792,8,8,8,9,3 + 70731393294,42396,5,5,5,6,3 + 70731435690,22916,6,6,6,7,3 + 70731161731,35000,3,3,3,4,3 + 70731196731,196563,4,4,4,5,3 + 70731438502,55261,7,7,7,8,6 + 70731135898,25833,2,2,2,2,3 + """)) + + def test_multiple_tables_against_ii(self): + return DiffTestBlueprint( + trace=DataPath('example_android_trace_30s.pb'), + query=""" + INCLUDE PERFETTO MODULE intervals.intersect; + + CREATE PERFETTO TABLE foo AS + SELECT id, ts, dur FROM sched + WHERE dur > 0 AND cpu = 0 + ORDER BY ts; + + CREATE PERFETTO TABLE bar AS + SELECT id, ts, dur FROM sched + WHERE dur > 0 AND cpu = 1 + ORDER BY ts; + + CREATE PERFETTO TABLE baz AS + SELECT id, ts, dur FROM sched + WHERE dur > 0 AND cpu = 2 + ORDER BY ts; + + CREATE PERFETTO TABLE ii_foo_and_bar AS + SELECT + ROW_NUMBER() OVER (ORDER BY ts) AS id, + ts, dur, id_0 AS id_foo, id_1 AS id_bar + FROM _interval_intersect!((foo, bar), ()) + ORDER BY ts; + + CREATE PERFETTO TABLE ii_foo_bar_baz AS + SELECT id_foo, id_bar, id_1 AS id_baz, ii.ts, ii.dur + FROM _interval_intersect!((ii_foo_and_bar, baz), ()) ii + JOIN ii_foo_and_bar ON ii_foo_and_bar.id = ii.id_0; + + WITH unioned AS ( + SELECT id_foo, id_bar, id_baz, ts, dur, "std" AS cat + FROM ii_foo_bar_baz + UNION + SELECT id_0 AS id_foo, id_1 AS id_bar, id_2 AS id_baz, ts, dur, "triple" AS cat + FROM _interval_intersect!((foo, bar, baz), ()) + ), + counted AS ( + SELECT *, count() c FROM unioned GROUP BY ts, dur, id_foo, id_bar, id_baz + ) + SELECT + SUM(c) FILTER (WHERE c == 2) AS good, + SUM(c) FILTER (WHERE c != 2) AS bad + FROM counted; + """, + out=Csv(""" + "good","bad" + 303178,"[NULL]" + """)) + + def test_multiple_tables_big(self): + return DiffTestBlueprint( + trace=DataPath('example_android_trace_30s.pb'), + query=""" + INCLUDE PERFETTO MODULE intervals.intersect; + + CREATE PERFETTO TABLE foo AS + SELECT id, ts, dur FROM sched + WHERE dur > 0 AND cpu = 0 + ORDER BY ts; + + CREATE PERFETTO TABLE bar AS + SELECT id, ts, dur FROM sched + WHERE dur > 0 AND cpu = 1 + ORDER BY ts; + + CREATE PERFETTO TABLE baz AS + SELECT id, ts, dur FROM sched + WHERE dur > 0 AND cpu = 2 + ORDER BY ts; + + SELECT * FROM _interval_intersect!((foo, bar, baz), ()) + ORDER BY ts + LIMIT 10; + """, + out=Csv(""" + "ts","dur","id_0","id_1","id_2" + 70799077155,1187135,44,133,132 + 70800264290,473386,44,138,132 + 70800737676,352500,44,139,132 + 70801090176,643906,140,139,132 + 70801734082,121615,141,139,132 + 70801855697,68073,141,139,142 + 70801923770,61354,141,139,143 + 70801985124,5054323,141,139,144 + 70807039447,65261,141,139,145 + 70807104708,50572,141,139,146 + """)) diff --git a/test/trace_processor/diff_tests/stdlib/intervals/tests.py b/test/trace_processor/diff_tests/stdlib/intervals/tests.py index c00eca8f83..76810bf1e4 100644 --- a/test/trace_processor/diff_tests/stdlib/intervals/tests.py +++ b/test/trace_processor/diff_tests/stdlib/intervals/tests.py @@ -81,4 +81,328 @@ def test_intervals_overlap_in_table(self): "has_overlaps" 0 1 - """)) \ No newline at end of file + """)) + + def test_intervals_flatten(self): + return DiffTestBlueprint( + trace=TextProto(""), + query=""" + INCLUDE PERFETTO MODULE intervals.overlap; + + WITH roots_data (id, ts, dur) AS ( + VALUES + (0, 0, 9), + (1, 9, 1) + ), children_data (root_id, id, parent_id, ts, dur) AS ( + VALUES + (0, 2, 0, 1, 3), + (0, 3, 0, 5, 1), + (0, 4, 0, 6, 1), + (0, 5, 0, 7, 0), + (0, 6, 0, 7, 1), + (0, 7, 2, 2, 1) + ) + SELECT ts, dur, id, root_id + FROM _intervals_flatten!(_intervals_merge_root_and_children!(roots_data, children_data)) ORDER BY ts + """, + out=Csv(""" + "ts","dur","id","root_id" + 0,1,0,0 + 1,1,2,0 + 2,1,7,0 + 3,1,2,0 + 4,1,0,0 + 5,1,3,0 + 6,1,4,0 + 7,1,6,0 + 8,1,0,0 + 9,1,1,1 + """)) + + def test_simple_ii_operator(self): + return DiffTestBlueprint( + trace=TextProto(""), + query=""" + + CREATE PERFETTO TABLE A AS + WITH data(id, ts, ts_end, c0, c1) AS ( + VALUES + (0, 1, 7, 10, 3) + ) + SELECT * FROM data; + + CREATE PERFETTO TABLE B AS + WITH data(id, ts, ts_end, c0, c2) AS ( + VALUES + (0, 0, 2, 10, 100), + (1, 3, 5, 10, 200), + (2, 6, 8, 20, 300) + ) + SELECT * FROM data; + + SELECT a.id AS a_id, b.id AS b_id + FROM __intrinsic_ii_with_interval_tree('A', 'c0, c1') a + JOIN __intrinsic_ii_with_interval_tree('B', 'c0, c2') b + WHERE a.ts < b.ts_end AND a.ts_end > b.ts + """, + out=Csv(""" + "a_id","b_id" + 0,1 + 0,0 + 0,2 + """)) + + def test_ii_operator_big(self): + return DiffTestBlueprint( + trace=DataPath('example_android_trace_30s.pb'), + query=""" + CREATE PERFETTO TABLE big_foo AS + SELECT + id, + ts, + ts+dur AS ts_end + FROM sched + WHERE dur != -1 + ORDER BY ts; + + CREATE PERFETTO TABLE small_foo AS + SELECT + id * 10 AS id, + ts + 1000 AS ts, + ts_end + 1000 AS ts_end + FROM big_foo + LIMIT 10 + OFFSET 5; + + CREATE PERFETTO TABLE res AS + SELECT a.id AS a_id, b.id AS b_id + FROM __intrinsic_ii_with_interval_tree('small_foo', '') a + JOIN __intrinsic_ii_with_interval_tree('big_foo', '') b + WHERE a.ts < b.ts_end AND a.ts_end > b.ts; + + SELECT * FROM res + ORDER BY a_id, b_id + LIMIT 10; + """, + out=Csv(""" + "a_id","b_id" + 50,1 + 50,5 + 50,6 + 60,1 + 60,6 + 60,7 + 60,8 + 70,1 + 70,6 + 70,7 + """)) + + def test_ii_with_ii_operator(self): + return DiffTestBlueprint( + trace=DataPath('example_android_trace_30s.pb'), + query=""" + INCLUDE PERFETTO MODULE intervals.intersect; + + CREATE PERFETTO TABLE big_foo AS + SELECT + ts, + ts + dur as ts_end, + id * 10 AS id + FROM sched + WHERE utid == 1 AND dur > 0; + + CREATE PERFETTO TABLE small_foo AS + SELECT + ts + 1000 AS ts, + ts + dur + 1000 AS ts_end, + id + FROM sched + WHERE utid == 1 AND dur > 0; + + CREATE PERFETTO TABLE small_foo_for_ii AS + SELECT id, ts, ts_end - ts AS dur + FROM small_foo; + + CREATE PERFETTO TABLE big_foo_for_ii AS + SELECT id, ts, ts_end - ts AS dur + FROM big_foo; + + CREATE PERFETTO TABLE both AS + SELECT + id_0, + id_1, + cat, + count() AS c, + MAX(ts) AS max_ts, MAX(dur) AS max_dur + FROM ( + SELECT a.id AS id_0, b.id AS id_1, 0 AS ts, 0 AS dur, "it" AS cat + FROM __intrinsic_ii_with_interval_tree('big_foo', '') a + JOIN __intrinsic_ii_with_interval_tree('small_foo', '') b + WHERE a.ts < b.ts_end AND a.ts_end > b.ts + UNION + SELECT id_0, id_1, ts, dur, "ii" AS cat + FROM _interval_intersect!((big_foo_for_ii, small_foo_for_ii), ()) + WHERE dur != 0 + ) + GROUP BY id_0, id_1; + + SELECT + SUM(c) FILTER (WHERE c == 2) AS good, + SUM(c) FILTER (WHERE c != 2) AS bad + FROM both; + """, + out=Csv(""" + "good","bad" + 314,"[NULL]" + """)) + + def test_ii_operator_partitioned_big(self): + return DiffTestBlueprint( + trace=DataPath('example_android_trace_30s.pb'), + query=""" + INCLUDE PERFETTO MODULE intervals.intersect; + + CREATE PERFETTO TABLE big_foo AS + SELECT + ts, + ts + dur as ts_end, + id * 10 AS id, + cpu AS c0 + FROM sched + WHERE dur != -1; + + CREATE PERFETTO TABLE small_foo AS + SELECT + ts + 1000 AS ts, + ts + dur + 1000 AS ts_end, + id, + cpu AS c0 + FROM sched + WHERE dur != -1; + + CREATE PERFETTO TABLE res AS + SELECT a.id AS a_id, b.id AS b_id + FROM __intrinsic_ii_with_interval_tree('small_foo', 'c0') a + JOIN __intrinsic_ii_with_interval_tree('big_foo', 'c0') b + USING (c0) + WHERE a.ts < b.ts_end AND a.ts_end > b.ts; + + SELECT * FROM res + ORDER BY a_id, b_id + LIMIT 10; + """, + out=Csv(""" + "a_id","b_id" + 0,0 + 0,10 + 1,10 + 1,430 + 2,20 + 2,30 + 3,30 + 3,40 + 4,40 + 4,50 + """)) + + def test_compare_ii_operator_with_span_join(self): + return DiffTestBlueprint( + trace=DataPath('example_android_trace_30s.pb'), + query=""" + INCLUDE PERFETTO MODULE intervals.intersect; + + CREATE PERFETTO TABLE big_foo AS + SELECT + ts, + ts + dur as ts_end, + id * 10 AS id, + cpu AS c0 + FROM sched + WHERE dur != -1; + + CREATE PERFETTO TABLE small_foo AS + SELECT + ts + 1000 AS ts, + ts + dur + 1000 AS ts_end, + id, + cpu AS c0 + FROM sched + WHERE dur != -1; + + CREATE PERFETTO TABLE small_foo_for_sj AS + SELECT + id AS small_id, + ts, + ts_end - ts AS dur, + c0 + FROM small_foo + WHERE dur != 0; + + CREATE PERFETTO TABLE big_foo_for_sj AS + SELECT + id AS big_id, + ts, + ts_end - ts AS dur, + c0 + FROM big_foo + WHERE dur != 0; + + CREATE VIRTUAL TABLE sj_res + USING SPAN_JOIN( + small_foo_for_sj PARTITIONED c0, + big_foo_for_sj PARTITIONED c0); + + CREATE PERFETTO TABLE both AS + SELECT + id_0, + id_1, + cat, + count() AS c, + MAX(ts) AS max_ts, MAX(dur) AS max_dur + FROM ( + SELECT a.id AS id_0, b.id AS id_1, 0 AS ts, 0 AS dur, "it" AS cat + FROM __intrinsic_ii_with_interval_tree('big_foo', 'c0') a + JOIN __intrinsic_ii_with_interval_tree('small_foo', 'c0') b + USING (c0) + WHERE a.ts < b.ts_end AND a.ts_end > b.ts + UNION + SELECT big_id AS id_0, small_id AS id_1, ts, dur, "sj" AS cat FROM sj_res + ) + GROUP BY id_0, id_1; + + SELECT + SUM(c) FILTER (WHERE c == 2) AS good, + SUM(c) FILTER (WHERE c != 2) AS bad + FROM both; + """, + out=Csv(""" + "good","bad" + 1538288,"[NULL]" + """)) + + def test_ii_operator_wrong_partition(self): + return DiffTestBlueprint( + trace=TextProto(''), + query=""" + CREATE PERFETTO TABLE A + AS + WITH x(id, ts, ts_end, c0) AS (VALUES(1, 1, 2, 1), (2, 3, 4, 2)) + SELECT * FROM x; + + CREATE PERFETTO TABLE B + AS + WITH x(id, ts, ts_end, c0) AS (VALUES(1, 5, 6, 3)) + SELECT * FROM x; + + SELECT + a.id AS a_id, + b.id AS b_id + FROM __intrinsic_ii_with_interval_tree('A', 'c0') a + JOIN __intrinsic_ii_with_interval_tree('B', 'c0') b + USING (c0) + WHERE a.ts < b.ts_end AND a.ts_end > b.ts; + """, + out=Csv(""" + "a_id","b_id" + """)) diff --git a/test/trace_processor/diff_tests/stdlib/linux/cpu.py b/test/trace_processor/diff_tests/stdlib/linux/cpu.py new file mode 100644 index 0000000000..b1a4a074e2 --- /dev/null +++ b/test/trace_processor/diff_tests/stdlib/linux/cpu.py @@ -0,0 +1,326 @@ +#!/usr/bin/env python3 +# Copyright (C) 2024 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License a +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from python.generators.diff_tests.testing import Csv, DataPath, TextProto +from python.generators.diff_tests.testing import DiffTestBlueprint +from python.generators.diff_tests.testing import TestSuite + + +class LinuxCpu(TestSuite): + + def test_cpu_utilization_per_second(self): + return DiffTestBlueprint( + trace=DataPath('example_android_trace_30s.pb'), + query=""" + INCLUDE PERFETTO MODULE linux.cpu.utilization.system; + + SELECT * FROM cpu_utilization_per_second; + """, + out=Csv(""" + "ts","utilization","unnormalized_utilization" + 70000000000,0.004545,0.036362 + 71000000000,0.022596,0.180764 + 72000000000,0.163393,1.307146 + 73000000000,0.452122,3.616972 + 74000000000,0.525557,4.204453 + 75000000000,0.388632,3.109057 + 76000000000,0.425447,3.403579 + 77000000000,0.201112,1.608896 + 78000000000,0.280247,2.241977 + 79000000000,0.345228,2.761827 + 80000000000,0.303258,2.426064 + 81000000000,0.487522,3.900172 + 82000000000,0.080542,0.644336 + 83000000000,0.362450,2.899601 + 84000000000,0.076438,0.611501 + 85000000000,0.110689,0.885514 + 86000000000,0.681488,5.451901 + 87000000000,0.808331,6.466652 + 88000000000,0.941768,7.534142 + 89000000000,0.480556,3.844446 + 90000000000,0.453268,3.626142 + 91000000000,0.280310,2.242478 + 92000000000,0.006381,0.051049 + 93000000000,0.030991,0.247932 + 94000000000,0.031981,0.255845 + 95000000000,0.027931,0.223446 + 96000000000,0.063066,0.504529 + 97000000000,0.023847,0.190773 + 98000000000,0.011291,0.090328 + 99000000000,0.024065,0.192518 + 100000000000,0.001964,0.015711 + """)) + + def test_cpu_process_utilization_per_second(self): + return DiffTestBlueprint( + trace=DataPath('example_android_trace_30s.pb'), + query=""" + INCLUDE PERFETTO MODULE linux.cpu.utilization.process; + + SELECT * + FROM cpu_process_utilization_per_second(10); + """, + out=Csv(""" + "ts","utilization","unnormalized_utilization" + 72000000000,0.000187,0.001495 + 73000000000,0.000182,0.001460 + 77000000000,0.000072,0.000579 + 78000000000,0.000275,0.002204 + 82000000000,0.000300,0.002404 + 83000000000,0.000004,0.000034 + 87000000000,0.000133,0.001065 + 88000000000,0.000052,0.000416 + 89000000000,0.000212,0.001697 + 92000000000,0.000207,0.001658 + 97000000000,0.000353,0.002823 + """)) + + def test_cpu_thread_utilization_per_second(self): + return DiffTestBlueprint( + trace=DataPath('example_android_trace_30s.pb'), + query=""" + INCLUDE PERFETTO MODULE linux.cpu.utilization.thread; + + SELECT * + FROM cpu_thread_utilization_per_second(10); + """, + out=Csv(""" + "ts","utilization","unnormalized_utilization" + 70000000000,0.000024,0.000195 + 72000000000,0.000025,0.000200 + 73000000000,0.000053,0.000420 + 74000000000,0.000044,0.000352 + 75000000000,0.000058,0.000461 + 76000000000,0.000075,0.000603 + 77000000000,0.000051,0.000407 + 78000000000,0.000047,0.000374 + 79000000000,0.000049,0.000396 + 80000000000,0.000084,0.000673 + 81000000000,0.000041,0.000329 + 82000000000,0.000048,0.000383 + 83000000000,0.000040,0.000323 + 84000000000,0.000018,0.000145 + 85000000000,0.000053,0.000421 + 86000000000,0.000121,0.000972 + 87000000000,0.000049,0.000392 + 88000000000,0.000036,0.000285 + 89000000000,0.000033,0.000266 + 90000000000,0.000050,0.000401 + 91000000000,0.000025,0.000201 + 92000000000,0.000009,0.000071 + """)) + + def test_cpu_cycles_per_cpu(self): + return DiffTestBlueprint( + trace=DataPath('android_postboot_unlock.pftrace'), + query=(""" + INCLUDE PERFETTO MODULE linux.cpu.utilization.system; + + SELECT + * + FROM cpu_cycles_per_cpu; + """), + out=Csv(""" + "cpu","millicycles","megacycles","runtime","min_freq","max_freq","avg_freq" + 0,4007488375822,4007,2260291804,930000,1803000,1775516 + 1,3985923237512,3985,2247149674,930000,1803000,1776869 + 2,4047926756581,4047,2276274170,930000,1803000,1781496 + 3,3992276081242,3992,2248956757,930000,1803000,1778975 + 4,5134318459625,5134,2203887266,553000,2348000,2335531 + 5,5615703220380,5615,2438499077,553000,2348000,2308698 + 6,4715590442538,4715,1737264802,500000,2850000,2725191 + 7,4594701918170,4594,1719270548,500000,2850000,2685290 + """)) + + def test_cpu_cycles_per_thread(self): + return DiffTestBlueprint( + trace=DataPath('android_cpu_eos.pb'), + query=(""" + INCLUDE PERFETTO MODULE linux.cpu.utilization.thread; + + SELECT + AVG(millicycles) AS millicycles, + AVG(megacycles) AS megacycles, + AVG(runtime) AS runtime, + AVG(min_freq) AS min_freq, + AVG(max_freq) AS max_freq, + AVG(avg_freq) AS avg_freq + FROM cpu_cycles_per_thread; + """), + out=Csv(""" + "millicycles","megacycles","runtime","min_freq","max_freq","avg_freq" + 25048302186.035053,24.624742,16080173.697531,1402708.453608,1648468.453608,1582627.707216 + """)) + + def test_cpu_cycles_per_process(self): + return DiffTestBlueprint( + trace=DataPath('android_cpu_eos.pb'), + query=(""" + INCLUDE PERFETTO MODULE linux.cpu.utilization.process; + + SELECT + AVG(millicycles) AS millicycles, + AVG(megacycles) AS megacycles, + AVG(runtime) AS runtime, + AVG(min_freq) AS min_freq, + AVG(max_freq) AS max_freq, + AVG(avg_freq) AS avg_freq + FROM cpu_cycles_per_process; + """), + out=Csv(""" + "millicycles","megacycles","runtime","min_freq","max_freq","avg_freq" + 83208401098.424652,82.753425,53163023.244898,1189742.465753,1683945.205479,1534667.547945 + """)) + + # Test CPU frequency counter grouping. + def test_cpu_eos_counters_freq(self): + return DiffTestBlueprint( + trace=DataPath('android_cpu_eos.pb'), + query=(""" + INCLUDE PERFETTO MODULE linux.cpu.frequency; + select + track_id, + freq, + cpu, + sum(dur) as dur + from cpu_frequency_counters + GROUP BY freq, cpu + """), + out=Csv(""" + "track_id","freq","cpu","dur" + 33,614400,0,4755967239 + 34,614400,1,4755971561 + 35,614400,2,4755968228 + 36,614400,3,4755964320 + 33,864000,0,442371195 + 34,864000,1,442397134 + 35,864000,2,442417916 + 36,864000,3,442434530 + 33,1363200,0,897122398 + 34,1363200,1,897144167 + 35,1363200,2,897180154 + 36,1363200,3,897216772 + 33,1708800,0,2553979530 + 34,1708800,1,2553923073 + 35,1708800,2,2553866772 + 36,1708800,3,2553814688 + """)) + + # Test CPU idle state counter grouping. + def test_cpu_eos_counters_idle(self): + return DiffTestBlueprint( + trace=DataPath('android_cpu_eos.pb'), + query=(""" + INCLUDE PERFETTO MODULE linux.cpu.idle; + select + track_id, + idle, + cpu, + sum(dur) as dur + from cpu_idle_counters + GROUP BY idle, cpu + """), + out=Csv(""" + "track_id","idle","cpu","dur" + 0,-1,0,2839828332 + 37,-1,1,1977033843 + 32,-1,2,1800498713 + 1,-1,3,1884366297 + 0,0,0,1833971336 + 37,0,1,2285260950 + 32,0,2,1348416182 + 1,0,3,1338508968 + 0,1,0,4013820433 + 37,1,1,4386917600 + 32,1,2,5532102915 + 1,1,3,5462026920 + """)) + + def test_linux_cpu_idle_stats(self): + return DiffTestBlueprint( + trace=TextProto(r""" + packet { + ftrace_events { + cpu: 0 + event: { + timestamp: 200000000000 + pid: 2 + cpu_frequency: { + state : 1704000 + cpu_id: 0 + } + } + event: { + timestamp: 200000000000 + pid: 2 + cpu_idle: { + state: 4294967295 + cpu_id: 0 + } + } + event { + timestamp: 200001000000 + pid: 2 + cpu_idle: { + state : 1 + cpu_id: 0 + } + } + event: { + timestamp: 200002000000 + pid : 2 + cpu_idle: { + state : 4294967295 + cpu_id: 0 + } + } + event { + timestamp: 200003000000 + pid: 2 + cpu_idle: { + state : 1 + cpu_id: 0 + } + } + event: { + timestamp: 200004000000 + pid: 2 + cpu_idle: { + state : 4294967295 + cpu_id: 0 + } + } + event: { + timestamp: 200005000000 + pid: 2 + cpu_frequency: { + state: 300000 + cpu_id: 0 + } + } + } + trusted_uid: 9999 + trusted_packet_sequence_id: 2 + } + """), + query=""" + INCLUDE PERFETTO MODULE linux.cpu.idle; + SELECT * FROM cpu_idle_stats; + """, + out=Csv(""" + "cpu","state","count","dur","avg_dur","idle_percent" + 0,2,2,2000000,1000000,40.000000 + """)) + diff --git a/test/trace_processor/diff_tests/stdlib/linux/memory.py b/test/trace_processor/diff_tests/stdlib/linux/memory.py new file mode 100644 index 0000000000..be5d8ab2b1 --- /dev/null +++ b/test/trace_processor/diff_tests/stdlib/linux/memory.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +# Copyright (C) 2024 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License a +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from python.generators.diff_tests.testing import Path, DataPath, Metric, Systrace +from python.generators.diff_tests.testing import Csv, Json, TextProto, BinaryProto +from python.generators.diff_tests.testing import DiffTestBlueprint +from python.generators.diff_tests.testing import TestSuite +from python.generators.diff_tests.testing import PrintProfileProto + + +class Memory(TestSuite): + + def test_memory_rss_and_swap_per_process(self): + return DiffTestBlueprint( + trace=DataPath('android_postboot_unlock.pftrace'), + query=""" + INCLUDE PERFETTO MODULE linux.memory.process; + + SELECT * + FROM memory_rss_and_swap_per_process + WHERE upid = 1 + LIMIT 5 + """, + out=Csv(""" + "ts","dur","upid","pid","process_name","anon_rss","file_rss","shmem_rss","rss","swap","anon_rss_and_swap","rss_and_swap" + 37592474220,12993896,1,1982,"com.android.systemui",125865984,"[NULL]","[NULL]","[NULL]","[NULL]",125865984,"[NULL]" + 37605468116,1628,1,1982,"com.android.systemui",126050304,"[NULL]","[NULL]","[NULL]","[NULL]",126050304,"[NULL]" + 37605469744,1302,1,1982,"com.android.systemui",126050304,"[NULL]",2990080,"[NULL]","[NULL]",126050304,"[NULL]" + 37605471046,685791,1,1982,"com.android.systemui",126046208,"[NULL]",2990080,"[NULL]","[NULL]",126046208,"[NULL]" + 37606156837,6510,1,1982,"com.android.systemui",126042112,"[NULL]",2990080,"[NULL]","[NULL]",126042112,"[NULL]" + """)) + + def test_memory_rss_high_watermark_per_process(self): + return DiffTestBlueprint( + trace=DataPath('android_postboot_unlock.pftrace'), + query=""" + INCLUDE PERFETTO MODULE linux.memory.high_watermark; + + SELECT * + FROM memory_rss_high_watermark_per_process + WHERE upid = 1 + LIMIT 10; + """, + out=Csv(""" + "ts","dur","upid","pid","process_name","rss_high_watermark" + 37592474220,12993896,1,1982,"com.android.systemui",125865984 + 37605468116,1628,1,1982,"com.android.systemui",126050304 + 37605469744,333774129,1,1982,"com.android.systemui",129040384 + 37939243873,120479574,1,1982,"com.android.systemui",372977664 + 38059723447,936,1,1982,"com.android.systemui",373043200 + 38059724383,6749186,1,1982,"com.android.systemui",373174272 + 38066473569,7869426,1,1982,"com.android.systemui",373309440 + 38074342995,11596761,1,1982,"com.android.systemui",373444608 + 38085939756,4877848,1,1982,"com.android.systemui",373579776 + 38090817604,11930827,1,1982,"com.android.systemui",373714944 + """)) diff --git a/test/trace_processor/diff_tests/stdlib/linux/tests.py b/test/trace_processor/diff_tests/stdlib/linux/tests.py deleted file mode 100644 index b2e813554c..0000000000 --- a/test/trace_processor/diff_tests/stdlib/linux/tests.py +++ /dev/null @@ -1,99 +0,0 @@ -#!/usr/bin/env python3 -# 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 a -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from python.generators.diff_tests.testing import Path, DataPath, Metric -from python.generators.diff_tests.testing import Csv, Json, TextProto -from python.generators.diff_tests.testing import DiffTestBlueprint -from python.generators.diff_tests.testing import TestSuite - - -class LinuxStdlib(TestSuite): - - def test_linux_cpu_idle_stats(self): - return DiffTestBlueprint( - trace=TextProto(r""" - packet { - ftrace_events { - cpu: 0 - event: { - timestamp: 200000000000 - pid: 2 - cpu_frequency: { - state : 1704000 - cpu_id: 0 - } - } - event: { - timestamp: 200000000000 - pid: 2 - cpu_idle: { - state: 4294967295 - cpu_id: 0 - } - } - event { - timestamp: 200001000000 - pid: 2 - cpu_idle: { - state : 1 - cpu_id: 0 - } - } - event: { - timestamp: 200002000000 - pid : 2 - cpu_idle: { - state : 4294967295 - cpu_id: 0 - } - } - event { - timestamp: 200003000000 - pid: 2 - cpu_idle: { - state : 1 - cpu_id: 0 - } - } - event: { - timestamp: 200004000000 - pid: 2 - cpu_idle: { - state : 4294967295 - cpu_id: 0 - } - } - event: { - timestamp: 200005000000 - pid: 2 - cpu_frequency: { - state: 300000 - cpu_id: 0 - } - } - } - trusted_uid: 9999 - trusted_packet_sequence_id: 2 - } - """), - query=""" - INCLUDE PERFETTO MODULE linux.cpu_idle; - SELECT * FROM linux_cpu_idle_stats; - """, - out=Csv(""" - "cpu","state","count","dur","avg_dur","idle_percent" - 0,2,2,2000000,1000000,50.000013 - """)) - diff --git a/test/trace_processor/diff_tests/stdlib/metasql/column_list.py b/test/trace_processor/diff_tests/stdlib/metasql/column_list.py new file mode 100644 index 0000000000..5c4fe6c22f --- /dev/null +++ b/test/trace_processor/diff_tests/stdlib/metasql/column_list.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +# Copyright (C) 2024 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License a +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from python.generators.diff_tests.testing import Csv, TextProto +from python.generators.diff_tests.testing import DiffTestBlueprint +from python.generators.diff_tests.testing import TestSuite + + +class ColumnListTests(TestSuite): + + def test_column_list_result_columns(self): + return DiffTestBlueprint( + trace=TextProto(''), + query=""" + INCLUDE PERFETTO MODULE metasql.column_list; + + WITH data(foo, bar) AS ( + VALUES (0, 1) + ) + SELECT _metasql_unparenthesize_column_list!((foo, bar)) + FROM data + """, + out=Csv(""" + "foo","bar" + 0,1 + """)) diff --git a/test/trace_processor/diff_tests/stdlib/metasql/table_list.py b/test/trace_processor/diff_tests/stdlib/metasql/table_list.py new file mode 100644 index 0000000000..503f84cda6 --- /dev/null +++ b/test/trace_processor/diff_tests/stdlib/metasql/table_list.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +# Copyright (C) 2024 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License a +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from python.generators.diff_tests.testing import Csv, TextProto +from python.generators.diff_tests.testing import DiffTestBlueprint +from python.generators.diff_tests.testing import TestSuite + + +class TableListTests(TestSuite): + + def test_table_list_result_columns(self): + return DiffTestBlueprint( + trace=TextProto(''), + query=""" + INCLUDE PERFETTO MODULE metasql.table_list; + + CREATE PERFETTO MACRO mac(t TableOrSubquery) + RETURNS TableOrSubquery AS + (SELECT * FROM $t); + + WITH foo AS ( + SELECT 0 AS a + ), + bar AS ( + SELECT 1 AS b + ), + baz AS ( + SELECT 2 AS c + ) + SELECT a + b + c + FROM _metasql_map_join_table_list!((foo, bar, baz), mac); + """, + out=Csv(""" + "a + b + c" + 3 + """)) + + def test_table_list_with_capture(self): + return DiffTestBlueprint( + trace=TextProto(''), + query=""" + INCLUDE PERFETTO MODULE metasql.table_list; + + CREATE PERFETTO MACRO mac(t TableOrSubquery, x Expr) + RETURNS TableOrSubquery AS + (SELECT *, $x AS bla FROM $t); + + WITH foo AS ( + SELECT 0 AS a + ), + bar AS ( + SELECT 1 AS b + ), + baz AS ( + SELECT 2 AS c + ) + SELECT + a + b + c + FROM _metasql_map_join_table_list_with_capture!( + (foo, bar, baz), + mac, + (3) + ); + """, + out=Csv(""" + "a + b + c" + 3 + """)) diff --git a/test/trace_processor/diff_tests/stdlib/sched/tests.py b/test/trace_processor/diff_tests/stdlib/sched/tests.py index 3527185768..1de62b5dc2 100644 --- a/test/trace_processor/diff_tests/stdlib/sched/tests.py +++ b/test/trace_processor/diff_tests/stdlib/sched/tests.py @@ -60,108 +60,6 @@ def test_active_cpu_count(self): 390,2 """)) - def test_sched_utilization_per_second(self): - return DiffTestBlueprint( - trace=DataPath('example_android_trace_30s.pb'), - query=""" - INCLUDE PERFETTO MODULE sched.utilization.system; - - SELECT * FROM sched_utilization_per_second; - """, - out=Csv(""" - "ts","utilization","unnormalized_utilization" - 70000000000,0.004545,0.036362 - 71000000000,0.022596,0.180764 - 72000000000,0.163393,1.307146 - 73000000000,0.452122,3.616972 - 74000000000,0.525557,4.204453 - 75000000000,0.388632,3.109057 - 76000000000,0.425447,3.403579 - 77000000000,0.201112,1.608896 - 78000000000,0.280247,2.241977 - 79000000000,0.345228,2.761827 - 80000000000,0.303258,2.426064 - 81000000000,0.487522,3.900172 - 82000000000,0.080542,0.644336 - 83000000000,0.362450,2.899601 - 84000000000,0.076438,0.611501 - 85000000000,0.110689,0.885514 - 86000000000,0.681488,5.451901 - 87000000000,0.808331,6.466652 - 88000000000,0.941768,7.534142 - 89000000000,0.480556,3.844446 - 90000000000,0.453268,3.626142 - 91000000000,0.280310,2.242478 - 92000000000,0.006381,0.051049 - 93000000000,0.030991,0.247932 - 94000000000,0.031981,0.255845 - 95000000000,0.027931,0.223446 - 96000000000,0.063066,0.504529 - 97000000000,0.023847,0.190773 - 98000000000,0.011291,0.090328 - 99000000000,0.024065,0.192518 - 100000000000,0.001964,0.015711 - """)) - - def test_sched_process_utilization_per_second(self): - return DiffTestBlueprint( - trace=DataPath('example_android_trace_30s.pb'), - query=""" - INCLUDE PERFETTO MODULE sched.utilization.process; - - SELECT * - FROM sched_process_utilization_per_second(10); - """, - out=Csv(""" - "ts","utilization","unnormalized_utilization" - 72000000000,0.000187,0.001495 - 73000000000,0.000182,0.001460 - 77000000000,0.000072,0.000579 - 78000000000,0.000275,0.002204 - 82000000000,0.000300,0.002404 - 83000000000,0.000004,0.000034 - 87000000000,0.000133,0.001065 - 88000000000,0.000052,0.000416 - 89000000000,0.000212,0.001697 - 92000000000,0.000207,0.001658 - 97000000000,0.000353,0.002823 - """)) - - def test_sched_thread_utilization_per_second(self): - return DiffTestBlueprint( - trace=DataPath('example_android_trace_30s.pb'), - query=""" - INCLUDE PERFETTO MODULE sched.utilization.thread; - - SELECT * - FROM sched_thread_utilization_per_second(10); - """, - out=Csv(""" - "ts","utilization","unnormalized_utilization" - 70000000000,0.000024,0.000195 - 72000000000,0.000025,0.000200 - 73000000000,0.000053,0.000420 - 74000000000,0.000044,0.000352 - 75000000000,0.000058,0.000461 - 76000000000,0.000075,0.000603 - 77000000000,0.000051,0.000407 - 78000000000,0.000047,0.000374 - 79000000000,0.000049,0.000396 - 80000000000,0.000084,0.000673 - 81000000000,0.000041,0.000329 - 82000000000,0.000048,0.000383 - 83000000000,0.000040,0.000323 - 84000000000,0.000018,0.000145 - 85000000000,0.000053,0.000421 - 86000000000,0.000121,0.000972 - 87000000000,0.000049,0.000392 - 88000000000,0.000036,0.000285 - 89000000000,0.000033,0.000266 - 90000000000,0.000050,0.000401 - 91000000000,0.000025,0.000201 - 92000000000,0.000009,0.000071 - """)) - def test_sched_time_in_state_for_thread(self): return DiffTestBlueprint( trace=DataPath('example_android_trace_30s.pb'), diff --git a/test/trace_processor/diff_tests/stdlib/slices/tests.py b/test/trace_processor/diff_tests/stdlib/slices/tests.py index 747ffb2e36..5555c88a78 100644 --- a/test/trace_processor/diff_tests/stdlib/slices/tests.py +++ b/test/trace_processor/diff_tests/stdlib/slices/tests.py @@ -33,7 +33,8 @@ def test_thread_slice(self): """, out=Csv(""" "name","ts","dur","depth","thread_name","tid","process_name","pid" - "ThreadSlice",5,6,0,"Thread",5,"Process",3 + "ThreadSlice",5,8,0,"Thread",5,"Process",3 + "NestedThreadSlice",6,1,1,"Thread",5,"Process",3 """)) def test_process_slice(self): @@ -63,7 +64,44 @@ def test_slice_with_process_and_thread_info(self): "name","ts","dur","depth","thread_name","tid","process_name","pid" "AsyncSlice",1,2,0,"[NULL]","[NULL]","[NULL]","[NULL]" "ProcessSlice",3,4,0,"[NULL]","[NULL]","Process",3 - "ThreadSlice",5,6,0,"Thread",5,"Process",3 + "ThreadSlice",5,8,0,"Thread",5,"Process",3 + "NestedThreadSlice",6,1,1,"Thread",5,"Process",3 + """)) + + # Ancestor / descendant wrappers. + + def test_slice_ancestor_and_self(self): + return DiffTestBlueprint( + trace=Path('trace.py'), + query=""" + INCLUDE PERFETTO MODULE slices.hierarchy; + + SELECT name, ts, dur, depth + FROM _slice_ancestor_and_self( + (SELECT id FROM slice WHERE name = 'NestedThreadSlice') + ); + """, + out=Csv(""" + "name","ts","dur","depth" + "NestedThreadSlice",6,1,1 + "ThreadSlice",5,8,0 + """)) + + def test_slice_descendant_and_self(self): + return DiffTestBlueprint( + trace=Path('trace.py'), + query=""" + INCLUDE PERFETTO MODULE slices.hierarchy; + + SELECT name, ts, dur, depth + FROM _slice_descendant_and_self( + (SELECT id FROM slice WHERE name = 'ThreadSlice') + ); + """, + out=Csv(""" + "name","ts","dur","depth" + "ThreadSlice",5,8,0 + "NestedThreadSlice",6,1,1 """)) # Common functions @@ -79,20 +117,21 @@ def test_slice_flattened(self): JOIN thread_track ON e.track_id = thread_track.id JOIN thread USING(utid) WHERE thread.tid = 30196 + ORDER BY ts LIMIT 10; """, out=Csv(""" "name","ts","dur","depth" "EventForwarder::OnTouchEvent",1035865509936036,211000,0 - "EventForwarder::OnTouchEvent",1035865510234036,48000,0 - "EventForwarder::OnTouchEvent",1035865510673036,10000,0 "GestureProvider::OnTouchEvent",1035865510147036,87000,1 + "EventForwarder::OnTouchEvent",1035865510234036,48000,0 "RenderWidgetHostImpl::ForwardTouchEvent",1035865510282036,41000,1 - "RenderWidgetHostImpl::ForwardTouchEvent",1035865510331036,16000,1 - "RenderWidgetHostImpl::ForwardTouchEvent",1035865510670036,3000,1 "LatencyInfo.Flow",1035865510323036,8000,2 + "RenderWidgetHostImpl::ForwardTouchEvent",1035865510331036,16000,1 "PassthroughTouchEventQueue::QueueEvent",1035865510347036,30000,2 - "PassthroughTouchEventQueue::QueueEvent",1035865510666036,4000,2 + "InputRouterImpl::FilterAndSendWebInputEvent",1035865510377036,8000,3 + "LatencyInfo.Flow",1035865510385036,126000,4 + "RenderWidgetHostImpl::UserInputStarted",1035865510511036,7000,5 """)) def test_thread_slice_cpu_time(self): diff --git a/test/trace_processor/diff_tests/stdlib/slices/trace.py b/test/trace_processor/diff_tests/stdlib/slices/trace.py index 395051ca84..79991d165b 100644 --- a/test/trace_processor/diff_tests/stdlib/slices/trace.py +++ b/test/trace_processor/diff_tests/stdlib/slices/trace.py @@ -36,6 +36,8 @@ trace.add_track_event_slice("AsyncSlice", ts=1, dur=2, track=async_track_id) trace.add_track_event_slice("ProcessSlice", ts=3, dur=4, track=process_track) -trace.add_track_event_slice("ThreadSlice", ts=5, dur=6, track=thread_track) +trace.add_track_event_slice("ThreadSlice", ts=5, dur=8, track=thread_track) +trace.add_track_event_slice( + "NestedThreadSlice", ts=6, dur=1, track=thread_track) sys.stdout.buffer.write(trace.trace.SerializeToString()) diff --git a/test/trace_processor/diff_tests/stdlib/wattson/tests.py b/test/trace_processor/diff_tests/stdlib/wattson/tests.py index 94c9bd797e..1ba9960b70 100644 --- a/test/trace_processor/diff_tests/stdlib/wattson/tests.py +++ b/test/trace_processor/diff_tests/stdlib/wattson/tests.py @@ -36,6 +36,40 @@ class WattsonStdlib(TestSuite): LIMIT 20; ''') + # Test raw system state before any grouping + def test_wattson_system_state(self): + return DiffTestBlueprint( + trace=DataPath('wattson_dsu_pmu.pb'), + query=""" + INCLUDE PERFETTO MODULE wattson.system_state; + SELECT * from wattson_system_states + ORDER by ts DESC + LIMIT 20 + """, + out=Csv(""" + "ts","dur","l3_hit_count","l3_miss_count","freq_0","idle_0","freq_1","idle_1","freq_2","idle_2","freq_3","idle_3","freq_4","idle_4","freq_5","idle_5","freq_6","idle_6","freq_7","idle_7","suspended" + 370103436540,339437,"[NULL]","[NULL]",738000,-1,738000,1,738000,-1,738000,-1,400000,-1,400000,1,1106000,1,1106000,1,0 + 370103419857,16683,"[NULL]","[NULL]",738000,-1,738000,1,738000,1,738000,-1,400000,-1,400000,1,1106000,1,1106000,1,0 + 370103413314,6543,"[NULL]","[NULL]",738000,1,738000,1,738000,1,738000,-1,400000,-1,400000,1,1106000,1,1106000,1,0 + 370103079729,333585,"[NULL]","[NULL]",738000,1,738000,1,738000,1,738000,-1,400000,-1,400000,1,1106000,-1,1106000,1,0 + 370103037378,42351,"[NULL]","[NULL]",738000,1,738000,1,738000,1,738000,-1,400000,1,400000,1,1106000,-1,1106000,1,0 + 370102869076,168302,"[NULL]","[NULL]",738000,1,738000,1,738000,1,738000,-1,400000,1,400000,1,1106000,-1,1106000,-1,0 + 370102832862,36214,"[NULL]","[NULL]",738000,1,738000,1,738000,-1,738000,-1,400000,1,400000,1,1106000,-1,1106000,-1,0 + 370102831844,1018,"[NULL]","[NULL]",738000,1,738000,1,738000,-1,738000,-1,400000,1,400000,1,1106000,-1,984000,-1,0 + 370102819475,12369,"[NULL]","[NULL]",738000,1,738000,1,738000,-1,738000,-1,400000,1,400000,1,984000,-1,984000,-1,0 + 370102816586,1098,"[NULL]","[NULL]",738000,1,574000,1,574000,-1,574000,-1,400000,1,400000,1,984000,-1,984000,-1,0 + 370102669043,147543,"[NULL]","[NULL]",574000,1,574000,1,574000,-1,574000,-1,400000,1,400000,1,984000,-1,984000,-1,0 + 370102244564,424479,"[NULL]","[NULL]",574000,1,574000,1,574000,-1,574000,1,400000,1,400000,1,984000,-1,984000,-1,0 + 370100810360,1434204,"[NULL]","[NULL]",574000,1,574000,1,574000,-1,574000,1,400000,1,400000,1,984000,1,984000,-1,0 + 370100731096,79264,"[NULL]","[NULL]",574000,1,574000,1,574000,-1,574000,-1,400000,1,400000,1,984000,1,984000,-1,0 + 370100411312,319784,"[NULL]","[NULL]",574000,1,574000,1,574000,1,574000,-1,400000,1,400000,1,984000,1,984000,-1,0 + 370100224219,187093,"[NULL]","[NULL]",574000,-1,574000,1,574000,1,574000,-1,400000,1,400000,1,984000,1,984000,-1,0 + 370100171729,52490,"[NULL]","[NULL]",574000,1,574000,1,574000,1,574000,-1,400000,1,400000,1,984000,1,984000,-1,0 + 370096612858,3558871,"[NULL]","[NULL]",574000,1,574000,1,574000,1,574000,1,400000,1,400000,1,984000,1,984000,-1,0 + 370096452775,160083,"[NULL]","[NULL]",574000,1,574000,1,574000,1,574000,1,400000,1,400000,1,984000,-1,984000,-1,0 + 370096347307,105468,"[NULL]","[NULL]",574000,-1,574000,1,574000,1,574000,1,400000,1,400000,1,984000,-1,984000,-1,0 + """)) + # Test fixup of deep idle offset and time marker window. def test_wattson_time_window(self): return DiffTestBlueprint( @@ -148,85 +182,175 @@ def test_wattson_suspend(self): 375051979,864000,1,864000,1,864000,1,864000,1,0 """)) - # Test cpu_idle.sql module to make sure final CPU idle system state is proper. - def test_wattson_cpu_idle(self): + # Test that the device name can be extracted from the trace's metadata. + def test_wattson_device_name(self): return DiffTestBlueprint( trace=DataPath('android_cpu_eos.pb'), - query=""" - INCLUDE PERFETTO MODULE wattson.cpu_idle; - SELECT ts, dur, idle_0, idle_1, idle_2, idle_3 - FROM _cpu_idle_all - ORDER BY ts DESC - LIMIT 20; - """, + query=(""" + INCLUDE PERFETTO MODULE wattson.device_infos; + select name from _wattson_device + """), out=Csv(""" - "ts","dur","idle_0","idle_1","idle_2","idle_3" - 183589213730,322865,-1,-1,-1,-1 - 183589083105,130625,-1,0,-1,-1 - 183587834095,1249010,-1,-1,-1,-1 - 183586743417,1090678,0,-1,-1,-1 - 183584248209,2495208,0,-1,-1,0 - 183584002220,245989,-1,-1,-1,0 - 183580262740,3739480,0,-1,-1,0 - 183580247324,15416,0,-1,-1,-1 - 183580135813,111511,-1,-1,-1,-1 - 183580002792,133021,-1,-1,-1,0 - 183576199459,3803333,0,-1,-1,0 - 183576002376,197083,-1,-1,-1,0 - 183575781386,220990,0,-1,-1,0 - 183575764355,17031,0,-1,-1,-1 - 183575719824,44531,-1,-1,-1,-1 - 183575706490,13334,-1,-1,-1,0 - 183575630865,75625,-1,-1,-1,-1 - 183575503886,126979,0,-1,-1,-1 - 183573856542,1647344,0,-1,-1,0 - 183573750032,106510,-1,-1,-1,0 + "name" + "monaco" """)) - # Test cpu_freq.sql module to make sure final CPU frequency states are proper. - def test_wattson_cpu_freq(self): + # Tests intermediate table + def test_wattson_intermediate_table(self): return DiffTestBlueprint( - trace=DataPath('android_cpu_eos.pb'), - query=""" - INCLUDE PERFETTO MODULE wattson.cpu_freq; - SELECT ts, dur, freq_0, freq_1, freq_2, freq_3 - FROM _cpu_freq_all - ORDER BY ts DESC - LIMIT 20; - """, + trace=DataPath('wattson_dsu_pmu.pb'), + query=(""" + INCLUDE PERFETTO MODULE wattson.curves.ungrouped; + select * from _w_independent_cpus_calc + WHERE ts > 359661672577 + ORDER by ts ASC + LIMIT 10 + """), out=Csv(""" - "ts","dur","freq_0","freq_1","freq_2","freq_3" - 183219975813,369560782,1708800,1708800,1708800,1708800 - 183219971595,4218,1708800,1708800,1708800,1363200 - 183219967324,4271,1708800,1708800,1363200,1363200 - 183219961334,5990,1708800,1363200,1363200,1363200 - 183217603313,2358021,1363200,1363200,1363200,1363200 - 183217599147,4166,1363200,1363200,1363200,614400 - 183217594720,4427,1363200,1363200,614400,614400 - 183217584459,10261,1363200,614400,614400,614400 - 183099692324,117892135,614400,614400,614400,614400 - 183099688001,4323,614400,614400,614400,1708800 - 183099683418,4583,614400,614400,1708800,1708800 - 183099677584,5834,614400,1708800,1708800,1708800 - 183000228105,99449479,1708800,1708800,1708800,1708800 - 183000223938,4167,1708800,1708800,1708800,864000 - 183000219303,4635,1708800,1708800,864000,864000 - 183000213313,5990,1708800,864000,864000,864000 - 182984137428,16075885,864000,864000,864000,864000 - 182984133522,3906,864000,864000,864000,1363200 - 182984129355,4167,864000,864000,1363200,1363200 - 182984123678,5677,864000,1363200,1363200,1363200 + "ts","dur","l3_hit_count","l3_miss_count","freq_0","idle_0","freq_1","idle_1","freq_2","idle_2","freq_3","idle_3","freq_4","idle_4","freq_5","idle_5","freq_6","idle_6","freq_7","idle_7","policy_4","policy_5","policy_6","policy_7","no_static","cpu0_curve","cpu1_curve","cpu2_curve","cpu3_curve","cpu4_curve","cpu5_curve","cpu6_curve","cpu7_curve","static_4","static_5","static_6","static_7" + 359661672578,75521,8326,9689,1401000,0,1401000,0,1401000,0,1401000,0,2253000,-1,2253000,0,2802000,-1,2802000,0,4,4,6,6,0,"[NULL]","[NULL]","[NULL]","[NULL]",527.050000,23.500000,1942.890000,121.430000,35.660000,-1.000000,35.640000,-1.000000 + 359661748099,2254517,248577,289258,1401000,0,1401000,0,1401000,0,1401000,0,2253000,0,2253000,0,2802000,-1,2802000,0,4,4,6,6,0,"[NULL]","[NULL]","[NULL]","[NULL]",23.500000,23.500000,1942.890000,121.430000,-1.000000,-1.000000,35.640000,-1.000000 + 359664003674,11596,1278,1487,1401000,-1,1401000,-1,1401000,-1,1401000,-1,2253000,-1,2253000,-1,2802000,-1,2802000,-1,4,4,6,6,-1,"[NULL]","[NULL]","[NULL]","[NULL]",527.050000,527.050000,1942.890000,1942.890000,35.660000,35.660000,35.640000,35.640000 + 359664015270,4720,520,605,1401000,-1,1401000,-1,1401000,-1,1401000,-1,2253000,-1,2253000,-1,2802000,-1,2802000,0,4,4,6,6,-1,"[NULL]","[NULL]","[NULL]","[NULL]",527.050000,527.050000,1942.890000,121.430000,35.660000,35.660000,35.640000,-1.000000 + 359664019990,18921,2086,2427,1401000,-1,1401000,-1,1401000,-1,1401000,-1,2253000,0,2253000,-1,2802000,-1,2802000,0,4,4,6,6,-1,"[NULL]","[NULL]","[NULL]","[NULL]",23.500000,527.050000,1942.890000,121.430000,-1.000000,35.660000,35.640000,-1.000000 + 359664038911,8871,978,1138,1401000,-1,1401000,-1,1401000,0,1401000,-1,2253000,0,2253000,-1,2802000,-1,2802000,0,4,4,6,6,-1,"[NULL]","[NULL]","[NULL]","[NULL]",23.500000,527.050000,1942.890000,121.430000,-1.000000,35.660000,35.640000,-1.000000 + 359664047782,1343,148,172,1401000,-1,1401000,0,1401000,0,1401000,-1,2253000,0,2253000,-1,2802000,-1,2802000,0,4,4,6,6,-1,"[NULL]","[NULL]","[NULL]","[NULL]",23.500000,527.050000,1942.890000,121.430000,-1.000000,35.660000,35.640000,-1.000000 + 359664049491,1383,152,177,1401000,0,1401000,0,1401000,0,1401000,-1,2253000,0,2253000,0,2802000,-1,2802000,0,4,4,6,6,-1,"[NULL]","[NULL]","[NULL]","[NULL]",23.500000,23.500000,1942.890000,121.430000,-1.000000,-1.000000,35.640000,-1.000000 + 359664050874,2409912,265711,309195,1401000,0,1401000,0,1401000,0,1401000,0,2253000,0,2253000,0,2802000,-1,2802000,0,4,4,6,6,0,"[NULL]","[NULL]","[NULL]","[NULL]",23.500000,23.500000,1942.890000,121.430000,-1.000000,-1.000000,35.640000,-1.000000 + 359666460786,13754,1516,1764,1401000,0,1401000,0,1401000,0,1401000,0,2253000,-1,2253000,0,2802000,-1,2802000,0,4,4,6,6,0,"[NULL]","[NULL]","[NULL]","[NULL]",527.050000,23.500000,1942.890000,121.430000,35.660000,-1.000000,35.640000,-1.000000 """)) - # Test that the device name can be extracted from the trace's metadata. - def test_wattson_device_name(self): + # Tests that device static curve selection is only when CPUs are active + def test_wattson_static_curve_selection(self): return DiffTestBlueprint( - trace=DataPath('android_cpu_eos.pb'), + trace=DataPath('wattson_dsu_pmu.pb'), query=(""" - INCLUDE PERFETTO MODULE android.device; - select name from android_device_name + INCLUDE PERFETTO MODULE wattson.curves.ungrouped; + select * from _system_state_curves + ORDER by ts ASC + LIMIT 5 """), out=Csv(""" - "name" - "eos" + "ts","dur","cpu0_curve","cpu1_curve","cpu2_curve","cpu3_curve","cpu4_curve","cpu5_curve","cpu6_curve","cpu7_curve","static_curve","l3_hit_value","l3_miss_value" + 359085636893,23030,0.000000,"[NULL]",0.000000,0.000000,0.000000,28.510000,0.000000,0.000000,0.000000,"[NULL]","[NULL]" + 359085659923,6664673,0.000000,"[NULL]",0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000 + 359092324596,1399699,0.000000,"[NULL]",0.000000,21.840000,0.000000,0.000000,0.000000,0.000000,3.730000,"[NULL]","[NULL]" + 359093724295,6959391,0.000000,"[NULL]",0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000 + 359100683686,375122,0.000000,"[NULL]",0.000000,0.000000,28.510000,0.000000,0.000000,0.000000,0.000000,"[NULL]","[NULL]" + """)) + + # Tests that L3 cache calculations are being done correctly + def test_wattson_l3_calculations(self): + return DiffTestBlueprint( + trace=DataPath('wattson_dsu_pmu.pb'), + query=(""" + INCLUDE PERFETTO MODULE wattson.curves.ungrouped; + select * from _system_state_curves + WHERE ts > 359661672577 + ORDER by ts ASC + LIMIT 5 + """), + out=Csv(""" + "ts","dur","cpu0_curve","cpu1_curve","cpu2_curve","cpu3_curve","cpu4_curve","cpu5_curve","cpu6_curve","cpu7_curve","static_curve","l3_hit_value","l3_miss_value" + 359661672578,75521,3.410000,3.410000,3.410000,3.410000,527.050000,23.500000,1942.890000,121.430000,35.660000,16836.004600,1215.000600 + 359661748099,2254517,3.450000,3.450000,3.450000,3.450000,23.500000,23.500000,1942.890000,121.430000,35.640000,578637.540600,262212.377000 + 359664003674,11596,248.900000,248.900000,248.900000,248.900000,527.050000,527.050000,1942.890000,1942.890000,35.660000,2584.243800,186.469800 + 359664015270,4720,248.900000,248.900000,248.900000,248.900000,527.050000,527.050000,1942.890000,121.430000,35.660000,1051.492000,75.867000 + 359664019990,18921,248.900000,248.900000,248.900000,248.900000,23.500000,527.050000,1942.890000,121.430000,35.660000,4218.100600,304.345800 + """)) + + # Tests calculations when everything in system state is converted to mW + def test_wattson_system_state_mw_calculations(self): + return DiffTestBlueprint( + trace=DataPath('wattson_dsu_pmu.pb'), + query=(""" + INCLUDE PERFETTO MODULE wattson.curves.ungrouped; + select * from _system_state_mw + WHERE ts > 359661672577 + ORDER by ts ASC + LIMIT 10 + """), + out=Csv(""" + "ts","dur","cpu0_mw","cpu1_mw","cpu2_mw","cpu3_mw","cpu4_mw","cpu5_mw","cpu6_mw","cpu7_mw","dsu_scu_mw" + 359661672578,75521,3.410000,3.410000,3.410000,3.410000,527.050000,23.500000,1942.890000,121.430000,274.679679 + 359661748099,2254517,3.450000,3.450000,3.450000,3.450000,23.500000,23.500000,1942.890000,121.430000,408.602332 + 359664003674,11596,248.900000,248.900000,248.900000,248.900000,527.050000,527.050000,1942.890000,1942.890000,274.597013 + 359664015270,4720,248.900000,248.900000,248.900000,248.900000,527.050000,527.050000,1942.890000,121.430000,274.507246 + 359664019990,18921,248.900000,248.900000,248.900000,248.900000,23.500000,527.050000,1942.890000,121.430000,274.677304 + 359664038911,8871,248.900000,248.900000,3.410000,248.900000,23.500000,527.050000,1942.890000,121.430000,274.676909 + 359664047782,1343,248.900000,3.410000,3.410000,248.900000,23.500000,527.050000,1942.890000,121.430000,274.557692 + 359664049491,1383,3.450000,3.450000,3.450000,208.140000,23.500000,23.500000,1942.890000,121.430000,407.495459 + 359664050874,2409912,3.450000,3.450000,3.450000,3.450000,23.500000,23.500000,1942.890000,121.430000,408.602720 + 359666460786,13754,3.410000,3.410000,3.410000,3.410000,527.050000,23.500000,1942.890000,121.430000,274.623880 + """)) + + # Tests that suspend values are being skipped + def test_wattson_suspend_calculations(self): + return DiffTestBlueprint( + trace=DataPath('wattson_eos_suspend.pb'), + query=(""" + INCLUDE PERFETTO MODULE wattson.curves.ungrouped; + select * from _system_state_curves + WHERE ts > 24790009884888 + ORDER by ts ASC + LIMIT 5 + """), + out=Csv(""" + "ts","dur","cpu0_curve","cpu1_curve","cpu2_curve","cpu3_curve","cpu4_curve","cpu5_curve","cpu6_curve","cpu7_curve","static_curve","l3_hit_value","l3_miss_value" + 24790009886451,21406,39.690000,39.690000,39.690000,39.690000,0.000000,0.000000,0.000000,0.000000,18.390000,"[NULL]","[NULL]" + 24790009907857,2784616769,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0,0 + 24792794524626,654584,39.690000,39.690000,39.690000,39.690000,0.000000,0.000000,0.000000,0.000000,18.390000,"[NULL]","[NULL]" + 24792795179210,38854,39.690000,39.690000,39.690000,39.690000,0.000000,0.000000,0.000000,0.000000,18.390000,"[NULL]","[NULL]" + 24792795218064,164583,39.690000,39.690000,39.690000,39.690000,0.000000,0.000000,0.000000,0.000000,18.390000,"[NULL]","[NULL]" + """)) + + # Tests that device curve table is being looked up correctly + def test_wattson_device_curve_per_policy(self): + return DiffTestBlueprint( + trace=DataPath('wattson_dsu_pmu.pb'), + query=(""" + INCLUDE PERFETTO MODULE wattson.curves.grouped; + select * from wattson_estimate_per_component + WHERE ts > 359661672577 + ORDER by ts ASC + LIMIT 10 + """), + out=Csv(""" + "ts","dur","l3","little_cpus","mid_cpus","big_cpus" + 359661672578,75521,18051.005200,49.300000,550.550000,2064.320000 + 359661748099,2254517,840849.917600,49.440000,47.000000,2064.320000 + 359664003674,11596,2770.713600,1031.260000,1054.100000,3885.780000 + 359664015270,4720,1127.359000,1031.260000,1054.100000,2064.320000 + 359664019990,18921,4522.446400,1031.260000,550.550000,2064.320000 + 359664038911,8871,2120.319000,785.770000,550.550000,2064.320000 + 359664047782,1343,320.839600,540.280000,550.550000,2064.320000 + 359664049491,1383,514.276100,254.130000,47.000000,2064.320000 + 359664050874,2409912,898807.333300,49.440000,47.000000,2064.320000 + 359666460786,13754,3286.709200,49.300000,550.550000,2064.320000 + """)) + + # Tests that total calculations are correct + def test_wattson_total_raven_calc(self): + return DiffTestBlueprint( + trace=DataPath('wattson_dsu_pmu.pb'), + query=(""" + INCLUDE PERFETTO MODULE wattson.curves.grouped; + select * from _wattson_entire_trace + """), + out=Csv(""" + "total_l3","total_little_cpus","total_mid_cpus","total_big_cpus","total" + 500.120000,661.980000,370.730000,1490.970000,3023.800000 + """)) + + # Tests that total calculations are correct + def test_wattson_total_eos_calc(self): + return DiffTestBlueprint( + trace=DataPath('wattson_eos_suspend.pb'), + query=(""" + INCLUDE PERFETTO MODULE wattson.curves.grouped; + select * from _wattson_entire_trace + """), + out=Csv(""" + "total_l3","total_little_cpus","total_mid_cpus","total_big_cpus","total" + 0.000000,2603.100000,0.000000,0.000000,2603.100000 """)) diff --git a/test/trace_processor/diff_tests/syntax/filtering_tests.py b/test/trace_processor/diff_tests/syntax/filtering_tests.py index 22b41114eb..71af69cdba 100644 --- a/test/trace_processor/diff_tests/syntax/filtering_tests.py +++ b/test/trace_processor/diff_tests/syntax/filtering_tests.py @@ -235,3 +235,46 @@ def test_string_null_vs_empty_desc(self): 0,"[NULL]" 2,"[NULL]" """)) + + def test_like_limit_one(self): + return DiffTestBlueprint( + trace=TextProto(""), + query=""" + CREATE PERFETTO TABLE foo AS + SELECT 'foo' AS strings + UNION ALL + SELECT 'binder x' + UNION ALL + SELECT 'binder y' + UNION ALL + SELECT 'bar'; + + SELECT * FROM foo WHERE strings LIKE '%binder%' LIMIT 1; + """, + out=Csv(""" + "strings" + "binder x" + """)) + + def test_like_limit_multiple(self): + return DiffTestBlueprint( + trace=TextProto(""), + query=""" + CREATE PERFETTO TABLE foo AS + SELECT 'foo' AS strings + UNION ALL + SELECT 'binder x' + UNION ALL + SELECT 'binder y' + UNION ALL + SELECT 'bar' + UNION ALL + SELECT 'binder z'; + + SELECT * FROM foo WHERE strings LIKE '%binder%' LIMIT 2; + """, + out=Csv(""" + "strings" + "binder x" + "binder y" + """)) diff --git a/test/trace_processor/diff_tests/syntax/macro_tests.py b/test/trace_processor/diff_tests/syntax/macro_tests.py index f34aaa0ae8..5b7271e9aa 100644 --- a/test/trace_processor/diff_tests/syntax/macro_tests.py +++ b/test/trace_processor/diff_tests/syntax/macro_tests.py @@ -62,3 +62,23 @@ def test_replace_macro(self): "res" 2 """)) + + def test_stringify(self): + return DiffTestBlueprint( + trace=TextProto(''), + query=''' + SELECT __intrinsic_stringify!(foo) + UNION ALL + SELECT __intrinsic_stringify!(foo bar baz) + UNION ALL + SELECT __intrinsic_stringify!(foo'') + UNION ALL + SELECT __intrinsic_stringify!(bar()) + ''', + out=Csv(""" + "'foo'" + "foo" + "foo bar baz" + "foo'" + "bar()" + """)) diff --git a/test/trace_processor/diff_tests/syntax/table_tests.py b/test/trace_processor/diff_tests/syntax/table_tests.py index 7de63e9d49..2f719af8ab 100644 --- a/test/trace_processor/diff_tests/syntax/table_tests.py +++ b/test/trace_processor/diff_tests/syntax/table_tests.py @@ -78,7 +78,6 @@ def test_perfetto_table_info_static_table(self): 3,"perfetto_table_info","track_id","uint32",0,0 4,"perfetto_table_info","value","double",0,0 5,"perfetto_table_info","arg_set_id","uint32",1,0 - 6,"perfetto_table_info","machine_id","uint32",1,0 """)) def test_perfetto_table_info_runtime_table(self): @@ -178,6 +177,37 @@ def test_distinct_trivial(self): 3073,8,4529,8 """)) + def test_distinct_multi_column(self): + return DiffTestBlueprint( + trace=TextProto(''), + query=""" + CREATE PERFETTO TABLE foo AS + WITH data(a, b) AS ( + VALUES + -- Needed to defeat any id/sorted detection. + (2, 3), + (0, 2), + (0, 1) + ) + SELECT * FROM data; + + CREATE TABLE bar AS + SELECT 1 AS b; + + WITH multi_col_distinct AS ( + SELECT DISTINCT a FROM foo CROSS JOIN bar USING (b) + ), multi_col_group_by AS ( + SELECT a FROM foo CROSS JOIN bar USING (b) GROUP BY a + ) + SELECT + (SELECT COUNT(*) FROM multi_col_distinct) AS cnt_distinct, + (SELECT COUNT(*) FROM multi_col_group_by) AS cnt_group_by + """, + out=Csv(""" + "cnt_distinct","cnt_group_by" + 1,1 + """)) + def test_limit(self): return DiffTestBlueprint( trace=TextProto(''), @@ -297,3 +327,88 @@ def test_min(self): "id","numeric","string","nullable" 0,3111,460,0 """)) + + def test_create_perfetto_index(self): + return DiffTestBlueprint( + trace=DataPath('example_android_trace_30s.pb'), + query=""" + CREATE PERFETTO INDEX foo ON internal_slice(track_id); + CREATE PERFETTO INDEX foo_name ON internal_slice(name); + + SELECT + COUNT() FILTER (WHERE track_id > 10) AS track_idx, + COUNT() FILTER (WHERE name > "g") AS name_idx + FROM internal_slice; + """, + out=Csv(""" + "track_idx","name_idx" + 20717,7098 + """)) + + def test_create_perfetto_index_multiple_cols(self): + return DiffTestBlueprint( + trace=DataPath('example_android_trace_30s.pb'), + query=""" + CREATE PERFETTO INDEX foo ON internal_slice(track_id, name); + + SELECT + MIN(track_id) AS min_track_id, + MAX(name) AS min_name + FROM internal_slice + WHERE track_id = 13 AND name > "c" + """, + out=Csv(""" + "min_track_id","min_name" + 13,"virtual bool art::ElfOatFile::Load(const std::string &, bool, bool, bool, art::MemMap *, std::string *)" + """)) + + def test_create_perfetto_index_multiple_smoke(self): + return DiffTestBlueprint( + trace=DataPath('example_android_trace_30s.pb'), + query=""" + CREATE PERFETTO INDEX idx ON internal_slice(track_id, name); + CREATE PERFETTO TABLE bar AS SELECT * FROM slice; + + SELECT ( + SELECT count() + FROM bar + WHERE track_id = 13 AND dur > 1000 AND name > "b" + ) AS non_indexes_stats, + ( + SELECT count() + FROM slice + WHERE track_id = 13 AND dur > 1000 AND name > "b" + ) AS indexed_stats + """, + out=Csv(""" + "non_indexes_stats","indexed_stats" + 39,39 + """)) + + def test_create_or_replace_perfetto_index(self): + return DiffTestBlueprint( + trace=DataPath('example_android_trace_30s.pb'), + query=""" + CREATE PERFETTO INDEX idx ON internal_slice(track_id, name); + CREATE OR REPLACE PERFETTO INDEX idx ON internal_slice(name); + + SELECT MAX(id) FROM slice WHERE track_id = 13; + """, + out=Csv(""" + "MAX(id)" + 20745 + """)) + + def test_create_or_replace_perfetto_index(self): + return DiffTestBlueprint( + trace=DataPath('example_android_trace_30s.pb'), + query=""" + CREATE PERFETTO INDEX idx ON internal_slice(track_id, name); + DROP PERFETTO INDEX idx ON internal_slice; + + SELECT MAX(id) FROM slice WHERE track_id = 13; + """, + out=Csv(""" + "MAX(id)" + 20745 + """)) diff --git a/test/trace_processor/diff_tests/tables/tests.py b/test/trace_processor/diff_tests/tables/tests.py index eef833ca54..e6ab9a4f66 100644 --- a/test/trace_processor/diff_tests/tables/tests.py +++ b/test/trace_processor/diff_tests/tables/tests.py @@ -36,6 +36,8 @@ def test_android_sched_and_ps_smoke_window(self): 0,9223372036854775807,0 """)) + + # Null printing def test_nulls(self): return DiffTestBlueprint( @@ -295,8 +297,8 @@ def test_cpu_track_table(self): """, out=Csv(""" "type","cpu" - "cpu_track",0 - "cpu_track",1 + "__intrinsic_cpu_track",0 + "__intrinsic_cpu_track",1 """)) def test_thread_state_flattened_aggregated(self): @@ -458,14 +460,16 @@ def test_cpu_track_table_machine_id(self): """), query=""" SELECT - type, - cpu, - machine_id - FROM cpu_track - ORDER BY type, cpu + ct.type, + ct.ucpu, + c.cpu, + ct.machine_id + FROM cpu_track AS ct + JOIN cpu AS c ON ct.ucpu = c.id + ORDER BY ct.type, c.cpu """, out=Csv(""" - "type","cpu","machine_id" - "cpu_track",0,1 - "cpu_track",1,1 + "type","ucpu","cpu","machine_id" + "__intrinsic_cpu_track",4096,0,1 + "__intrinsic_cpu_track",4097,1,1 """)) diff --git a/test/trace_processor/diff_tests/tables/tests_counters.py b/test/trace_processor/diff_tests/tables/tests_counters.py index 4cc79a014c..d8d08b333c 100644 --- a/test/trace_processor/diff_tests/tables/tests_counters.py +++ b/test/trace_processor/diff_tests/tables/tests_counters.py @@ -133,23 +133,25 @@ def test_counter_dur_example_android_trace_30s_machine_id(self): ['ftrace_events', 'sys_stats', 'process_stats', 'process_tree'], {'machine_id': 1001}), query=""" - SELECT ts, dur, machine_id - FROM experimental_counter_dur + SELECT ts, dur, m.raw_id as raw_machine_id + FROM experimental_counter_dur c + JOIN counter_track t on c.track_id = t.id + JOIN machine m on t.machine_id = m.id WHERE track_id IN (1, 2, 3) ORDER BY dur LIMIT 10; """, out=Csv(""" - "ts","dur","machine_id" - 100351738640,-1,1 - 100351738640,-1,1 - 100351738640,-1,1 - 70731059648,19510835,1 - 70731059648,19510835,1 - 70731059648,19510835,1 - 73727335051,23522762,1 - 73727335051,23522762,1 - 73727335051,23522762,1 - 86726132752,24487554,1 + "ts","dur","raw_machine_id" + 100351738640,-1,1001 + 100351738640,-1,1001 + 100351738640,-1,1001 + 70731059648,19510835,1001 + 70731059648,19510835,1001 + 70731059648,19510835,1001 + 73727335051,23522762,1001 + 73727335051,23522762,1001 + 73727335051,23522762,1001 + 86726132752,24487554,1001 """)) # Tests counter.machine_id and process_counter_track.machine. @@ -174,7 +176,6 @@ def test_filter_row_vector_example_android_trace_30s_machine_id(self): AND t.machine_id is not NULL ) AND value != 17952.000000 - AND counter.machine_id is not NULL LIMIT 20; """, out=Path('filter_row_vector_example_android_trace_30s.out')) @@ -186,16 +187,17 @@ def test_counters_where_cpu_counters_where_cpu_machine_id(self): query=""" SELECT ts, - lead(ts, 1, ts) OVER (PARTITION BY name ORDER BY ts) - ts AS dur, - value, c.machine_id + lead(ts, 1, ts) OVER (PARTITION BY track_id ORDER BY ts) - ts AS dur, + value FROM counter c - JOIN cpu_counter_track t ON t.id = c.track_id - WHERE cpu = 1; + JOIN cpu_counter_track t ON c.track_id = t.id + JOIN cpu ON t.ucpu = cpu.id + WHERE cpu.cpu = 1; """, out=Csv(""" - "ts","dur","value","machine_id" - 1000,1,3000.000000,1 - 1001,0,4000.000000,1 + "ts","dur","value" + 1000,1,3000.000000 + 1001,0,4000.000000 """)) def test_synth_1_filter_counter_machine_id(self): @@ -205,14 +207,14 @@ def test_synth_1_filter_counter_machine_id(self): ['ftrace_events', 'process_stats', 'process_tree'], {'machine_id': 1001}), query=""" - SELECT COUNT(*), machine_id + SELECT COUNT(*) FROM counter WHERE track_id = 0; """, out=Csv(""" - "COUNT(*)","machine_id" - 2,1 + "COUNT(*)" + 2 """)) def test_memory_counters_machine_id(self): @@ -222,9 +224,11 @@ def test_memory_counters_machine_id(self): ['ftrace_events', 'sys_stats', 'process_stats', 'process_tree'], {'machine_id': 1001}), query=""" - SELECT count(*), machine_id FROM counters WHERE -1 < ts group by machine_id; + SELECT count(*) + FROM counters + WHERE -1 < ts group by machine_id; """, out=Csv(""" - "count(*)","machine_id" - 98688,1 + "count(*)" + 98688 """)) diff --git a/test/trace_processor/diff_tests/tables/tests_sched.py b/test/trace_processor/diff_tests/tables/tests_sched.py index ad23a5d800..152769a2db 100644 --- a/test/trace_processor/diff_tests/tables/tests_sched.py +++ b/test/trace_processor/diff_tests/tables/tests_sched.py @@ -129,16 +129,16 @@ def test_raw_common_flags(self): """, out=Csv(""" "id","type","ts","name","cpu","utid","arg_set_id","common_flags" - 3,"ftrace_event",1735489788930,"sched_waking",0,300,4,1 - 4,"ftrace_event",1735489812571,"sched_waking",0,300,5,1 - 5,"ftrace_event",1735489833977,"sched_waking",1,305,6,1 - 8,"ftrace_event",1735489876788,"sched_waking",1,297,9,1 - 9,"ftrace_event",1735489879097,"sched_waking",0,304,10,1 - 12,"ftrace_event",1735489933912,"sched_waking",0,428,13,1 - 14,"ftrace_event",1735489972385,"sched_waking",1,232,15,1 - 17,"ftrace_event",1735489999987,"sched_waking",1,232,15,1 - 19,"ftrace_event",1735490039439,"sched_waking",1,298,18,1 - 20,"ftrace_event",1735490042084,"sched_waking",1,298,19,1 + 3,"__intrinsic_ftrace_event",1735489788930,"sched_waking",0,300,4,1 + 4,"__intrinsic_ftrace_event",1735489812571,"sched_waking",0,300,5,1 + 5,"__intrinsic_ftrace_event",1735489833977,"sched_waking",1,305,6,1 + 8,"__intrinsic_ftrace_event",1735489876788,"sched_waking",1,297,9,1 + 9,"__intrinsic_ftrace_event",1735489879097,"sched_waking",0,304,10,1 + 12,"__intrinsic_ftrace_event",1735489933912,"sched_waking",0,428,13,1 + 14,"__intrinsic_ftrace_event",1735489972385,"sched_waking",1,232,15,1 + 17,"__intrinsic_ftrace_event",1735489999987,"sched_waking",1,232,15,1 + 19,"__intrinsic_ftrace_event",1735490039439,"sched_waking",1,298,18,1 + 20,"__intrinsic_ftrace_event",1735490042084,"sched_waking",1,298,19,1 """)) def test_thread_executing_span_graph(self): @@ -149,30 +149,30 @@ def test_thread_executing_span_graph(self): SELECT waker_id, prev_id, - prev_end_ts, + ts - idle_dur AS idle_ts, id, ts, - end_ts, - is_kernel, + ts + dur AS next_idle_ts , + is_idle_reason_self, utid, - state, - blocked_function + idle_state, + idle_reason FROM _wakeup_graph ORDER BY ts LIMIT 10 """, out=Csv(""" - "waker_id","prev_id","prev_end_ts","id","ts","end_ts","is_kernel","utid","state","blocked_function" - "[NULL]","[NULL]","[NULL]",5,1735489812571,1735489896509,0,304,"[NULL]","[NULL]" + "waker_id","prev_id","idle_ts","id","ts","next_idle_ts","is_idle_reason_self","utid","idle_state","idle_reason" + "[NULL]","[NULL]","[NULL]",5,1735489812571,1735489896509,1,304,"[NULL]","[NULL]" + "[NULL]","[NULL]","[NULL]",6,1735489833977,1735489886440,1,297,"[NULL]","[NULL]" 6,"[NULL]","[NULL]",11,1735489876788,1735489953773,0,428,"[NULL]","[NULL]" 5,"[NULL]","[NULL]",12,1735489879097,1735490217277,0,243,"[NULL]","[NULL]" 11,"[NULL]","[NULL]",17,1735489933912,1735490587658,0,230,"[NULL]","[NULL]" - "[NULL]","[NULL]","[NULL]",20,1735489972385,1735489995809,0,298,"[NULL]","[NULL]" - "[NULL]",20,1735489995809,25,1735489999987,1735490055966,0,298,"S","[NULL]" + "[NULL]","[NULL]","[NULL]",20,1735489972385,1735489995809,1,298,"[NULL]","[NULL]" + "[NULL]",20,1735489995809,25,1735489999987,1735490055966,1,298,"S","[NULL]" 25,"[NULL]","[NULL]",28,1735490039439,1735490610238,0,421,"[NULL]","[NULL]" 25,"[NULL]","[NULL]",29,1735490042084,1735490068213,0,420,"[NULL]","[NULL]" 25,"[NULL]","[NULL]",30,1735490045825,1735491418790,0,1,"[NULL]","[NULL]" - 17,"[NULL]","[NULL]",41,1735490544063,1735490598211,0,427,"[NULL]","[NULL]" """)) def test_thread_executing_span_graph_contains_forked_states(self): @@ -185,7 +185,7 @@ def test_thread_executing_span_graph_contains_forked_states(self): waker_id, prev_id FROM _wakeup_graph - WHERE ts = 1735842081507 AND end_ts = 1735842081507 + 293868 + WHERE ts = 1735842081507 AND ts + dur = 1735842081507 + 293868 """, out=Csv(""" "id","waker_id","prev_id" @@ -209,11 +209,11 @@ def test_thread_executing_span_graph_has_no_null_dur(self): trace=DataPath('sched_wakeup_trace.atr'), query=""" INCLUDE PERFETTO MODULE sched.thread_executing_span; - SELECT ts,end_ts FROM _wakeup_graph - WHERE end_ts IS NULL OR ts IS NULL + SELECT ts,dur FROM _wakeup_graph + WHERE dur IS NULL OR ts IS NULL """, out=Csv(""" - "ts","end_ts" + "ts","dur" """)) def test_thread_executing_span_graph_accepts_null_irq_context(self): @@ -225,50 +225,7 @@ def test_thread_executing_span_graph_accepts_null_irq_context(self): """, out=Csv(""" "count" - 17 - """)) - - def test_thread_executing_span_flatten_critical_path_tasks(self): - return DiffTestBlueprint( - trace=DataPath('sched_switch_original.pb'), - query=""" - INCLUDE PERFETTO MODULE sched.thread_executing_span; - - CREATE PERFETTO TABLE graph AS - SELECT - id AS source_node_id, - COALESCE(waker_id, id) AS dest_node_id, - id - COALESCE(waker_id, id) AS edge_weight - FROM _wakeup_graph; - - CREATE PERFETTO TABLE roots AS - SELECT - _wakeup_graph.id AS root_node_id, - _wakeup_graph.id - COALESCE(prev_id, _wakeup_graph.id) AS root_target_weight, - id, - ts, - end_ts, - utid - FROM _wakeup_graph LIMIT 10; - - CREATE PERFETTO TABLE critical_path AS - SELECT * FROM graph_reachable_weight_bounded_dfs!(graph, roots, 1); - - SELECT * FROM _flatten_critical_path_tasks!(critical_path); - """, - out=Csv(""" - "ts","root_node_id","node_id","dur","node_utid","prev_end_ts" - 807082868359903,29,29,"[NULL]",8,"[NULL]" - 807082871734539,35,35,"[NULL]",9,"[NULL]" - 807082871734539,38,35,45052,9,"[NULL]" - 807082871779591,38,38,"[NULL]",5,807082871764903 - 807082878623081,45,45,"[NULL]",9,807082871805424 - 807082947156994,57,57,"[NULL]",9,807082878865945 - 807082947246838,62,62,"[NULL]",6,807082879179539 - 807082947261525,63,63,"[NULL]",12,"[NULL]" - 807082947267463,64,64,"[NULL]",13,"[NULL]" - 807082947278140,65,65,"[NULL]",14,"[NULL]" - 807082947288765,66,66,"[NULL]",15,"[NULL]" + 30 """)) def test_thread_executing_span_intervals_to_roots_edge_case(self): @@ -278,21 +235,22 @@ def test_thread_executing_span_intervals_to_roots_edge_case(self): INCLUDE PERFETTO MODULE sched.thread_executing_span; SELECT * FROM - _intervals_to_roots!((SELECT 1477 AS utid, trace_start() AS ts, trace_end() - trace_start() AS dur)) + _intervals_to_roots!((SELECT 1477 AS utid, trace_start() AS ts, trace_end() - trace_start() AS dur), _wakeup_graph) + ORDER BY root_node_id LIMIT 10; """, out=Csv(""" - "id" - 11889 - 11892 - 11893 - 11896 - 11897 - 11900 - 11911 - 11916 - 11917 - 11921 + "root_node_id","capacity" + 11889,0 + 11980,91 + 12057,77 + 12254,197 + 12521,267 + 12672,151 + 12796,124 + 12802,6 + 12827,25 + 12833,6 """)) def test_thread_executing_span_intervals_to_roots(self): @@ -302,21 +260,15 @@ def test_thread_executing_span_intervals_to_roots(self): INCLUDE PERFETTO MODULE sched.thread_executing_span; SELECT * FROM - _intervals_to_roots!((SELECT 1477 AS utid, 1737362149192 AS ts, CAST(2e7 AS INT) AS dur)) + _intervals_to_roots!((SELECT 1477 AS utid, 1737362149192 AS ts, CAST(2e7 AS INT) AS dur), _wakeup_graph) + ORDER BY root_node_id LIMIT 10; """, out=Csv(""" - "id" - 11980 - 11983 - 11984 - 11989 - 11990 - 11991 - 11992 - 11993 - 12001 - 12006 + "root_node_id","capacity" + 11980,91 + 12057,77 + 12254,197 """)) def test_thread_executing_span_flatten_critical_paths(self): @@ -338,20 +290,26 @@ def test_thread_executing_span_flatten_critical_paths(self): _wakeup_graph.id - COALESCE(prev_id, _wakeup_graph.id) AS root_target_weight, id, ts, - end_ts, + dur, utid FROM _wakeup_graph; CREATE PERFETTO TABLE critical_path AS - SELECT * FROM graph_reachable_weight_bounded_dfs!(graph, roots, 1); + SELECT root_node_id AS root_id, node_id AS id, root_node_id AS parent_id FROM graph_reachable_weight_bounded_dfs!(graph, roots, 1); - SELECT * FROM _flatten_critical_paths!(critical_path, _sleep); + SELECT * FROM _critical_path_to_intervals!(critical_path, _wakeup_graph); """, out=Csv(""" - "ts","dur","utid","id","root_id","prev_end_ts","critical_path_utid","critical_path_id","critical_path_blocked_dur","critical_path_blocked_state","critical_path_blocked_function" - 807082871764903,14688,9,35,38,"[NULL]",5,38,14688,"S","[NULL]" - 807082947156994,351302,9,57,76,807082878865945,5,76,68858913,"S","[NULL]" - 807083031589763,324114,21,127,130,"[NULL]",5,130,80026987,"S","[NULL]" + "ts","dur","id","root_id" + 807082871764903,14688,35,38 + 807082871805424,6817657,38,45 + 807082947223556,23282,60,62 + 807082947156994,351302,57,76 + 807082947593348,4229115,76,96 + 807082959078401,95105,105,107 + 807082951886890,79702873,1,130 + 807083031589763,324114,127,130 + 807082947219546,85059279,1,135 """)) def test_thread_executing_span_critical_path(self): @@ -360,47 +318,49 @@ def test_thread_executing_span_critical_path(self): query=""" INCLUDE PERFETTO MODULE sched.thread_executing_span; - CREATE PERFETTO TABLE graph AS - SELECT - id AS source_node_id, - COALESCE(waker_id, id) AS dest_node_id, - id - COALESCE(waker_id, id) AS edge_weight - FROM _wakeup_graph; - - CREATE PERFETTO TABLE roots AS - SELECT - _wakeup_graph.id AS root_node_id, - _wakeup_graph.id - COALESCE(prev_id, _wakeup_graph.id) AS root_target_weight, - id, - ts, - end_ts, - utid - FROM _wakeup_graph; - - SELECT * FROM _critical_path!(graph, roots, _sleep); + SELECT * FROM _critical_path_intervals!(_wakeup_kernel_edges, (SELECT id AS root_node_id, id - COALESCE(prev_id, id) AS capacity FROM _wakeup_graph), _wakeup_graph) ORDER BY root_id; """, out=Csv(""" - "ts","dur","root_id","id","utid","critical_path_utid","critical_path_id","critical_path_blocked_dur","critical_path_blocked_state","critical_path_blocked_function" - 807082868359903,81302,29,29,8,8,"[NULL]","[NULL]","[NULL]","[NULL]" - 807082871734539,70885,35,35,9,9,"[NULL]","[NULL]","[NULL]","[NULL]" - 807082871764903,14688,38,35,9,5,38,14688,"S","[NULL]" - 807082871779591,55729,38,38,5,5,"[NULL]","[NULL]","[NULL]","[NULL]" - 807082878623081,242864,45,45,9,9,"[NULL]","[NULL]","[NULL]","[NULL]" - 807082947156994,436354,57,57,9,9,"[NULL]","[NULL]","[NULL]","[NULL]" - 807082947246838,1038854,62,62,6,6,"[NULL]","[NULL]","[NULL]","[NULL]" - 807082947261525,293594,63,63,12,12,"[NULL]","[NULL]","[NULL]","[NULL]" - 807082947267463,228958,64,64,13,13,"[NULL]","[NULL]","[NULL]","[NULL]" - 807082947278140,54114,65,65,14,14,"[NULL]","[NULL]","[NULL]","[NULL]" - 807082947288765,338802,66,66,15,15,"[NULL]","[NULL]","[NULL]","[NULL]" - 807082947294182,296875,67,67,16,16,"[NULL]","[NULL]","[NULL]","[NULL]" - 807082947156994,351302,76,57,9,5,76,68858913,"S","[NULL]" - 807082947508296,122083,76,76,5,5,"[NULL]","[NULL]","[NULL]","[NULL]" - 807082951822463,104427,96,96,9,9,"[NULL]","[NULL]","[NULL]","[NULL]" - 807082959173506,215104,107,107,6,6,"[NULL]","[NULL]","[NULL]","[NULL]" - 807083031589763,436198,127,127,21,21,"[NULL]","[NULL]","[NULL]","[NULL]" - 807083031589763,324114,130,127,21,5,130,80026987,"S","[NULL]" - 807083031913877,166302,130,130,5,5,"[NULL]","[NULL]","[NULL]","[NULL]" - 807083032278825,208490,135,135,2,2,"[NULL]","[NULL]","[NULL]","[NULL]" + "root_id","id","ts","dur" + 1,1,807082862885423,169601892 + 2,2,807082862913183,280521 + 13,13,807082864992767,6772136 + 14,14,807082865019382,14160157 + 17,17,807082865084902,272865 + 29,29,807082868359903,81302 + 35,35,807082871734539,70885 + 38,35,807082871764903,14688 + 38,38,807082871779591,6869792 + 45,38,807082871805424,6817657 + 45,45,807082878623081,242864 + 55,55,807082946856213,609219 + 57,57,807082947156994,436354 + 60,60,807082947223556,83577300 + 62,60,807082947223556,23282 + 62,62,807082947246838,2000260 + 63,63,807082947261525,293594 + 64,64,807082947267463,228958 + 65,65,807082947278140,54114 + 66,66,807082947288765,338802 + 67,67,807082947294182,296875 + 76,57,807082947156994,351302 + 76,76,807082947508296,4378594 + 93,93,807082951711161,2494011 + 96,76,807082947593348,4229115 + 96,96,807082951822463,104427 + 105,105,807082959078401,184115 + 107,105,807082959078401,95105 + 107,107,807082959173506,73362507 + 111,111,807082962662412,149011 + 114,114,807082967942309,334114 + 127,127,807083031589763,436198 + 130,1,807082951886890,79702873 + 130,127,807083031589763,324114 + 130,130,807083031913877,166302 + 135,1,807082947219546,85059279 + 135,135,807083032278825,208490 + 139,139,807083032634138,340625 + 142,142,807083032991378,89218 """)) def test_thread_executing_span_critical_path_by_roots(self): @@ -409,20 +369,24 @@ def test_thread_executing_span_critical_path_by_roots(self): query=""" INCLUDE PERFETTO MODULE sched.thread_executing_span; - SELECT * FROM _critical_path_by_roots!(_intervals_to_roots!((SELECT 6 AS utid, trace_start() AS ts, trace_end() - trace_start() AS dur))); + SELECT * + FROM _critical_path_by_roots!( + _intervals_to_roots!( + (SELECT 6 AS utid, trace_start() AS ts, trace_end() - trace_start() AS dur), + _wakeup_graph), + _wakeup_graph); """, out=Csv(""" - "id","ts","dur","utid","critical_path_id","critical_path_blocked_dur","critical_path_blocked_state","critical_path_blocked_function","critical_path_utid" - 62,807082947246838,1038854,6,"[NULL]","[NULL]","[NULL]","[NULL]",6 - 63,807082947261525,293594,12,"[NULL]","[NULL]","[NULL]","[NULL]",12 - 64,807082947267463,228958,13,"[NULL]","[NULL]","[NULL]","[NULL]",13 - 65,807082947278140,54114,14,"[NULL]","[NULL]","[NULL]","[NULL]",14 - 66,807082947288765,338802,15,"[NULL]","[NULL]","[NULL]","[NULL]",15 - 67,807082947294182,296875,16,"[NULL]","[NULL]","[NULL]","[NULL]",16 - 57,807082947156994,351302,9,76,68858913,"S","[NULL]",5 - 76,807082947508296,122083,5,"[NULL]","[NULL]","[NULL]","[NULL]",5 - 96,807082951822463,104427,9,"[NULL]","[NULL]","[NULL]","[NULL]",9 - 107,807082959173506,215104,6,"[NULL]","[NULL]","[NULL]","[NULL]",6 + "root_id","id","ts","dur" + 14,14,807082865019382,14160157 + 62,60,807082947223556,23282 + 62,62,807082947246838,2000260 + 107,105,807082959078401,95105 + 107,139,807082959173506,73362507 + 139,139,807083032536013,98125 + 139,142,807083032634138,340625 + 142,142,807083032974763,16615 + 142,142,807083032991378,89218 """)) def test_thread_executing_span_critical_path_by_intervals(self): @@ -431,12 +395,23 @@ def test_thread_executing_span_critical_path_by_intervals(self): query=""" INCLUDE PERFETTO MODULE sched.thread_executing_span; - SELECT * FROM _critical_path_by_intervals!((SELECT 6 AS utid, trace_start() AS ts, trace_end() - trace_start() AS dur)); + SELECT root_utid, root_id, id, ts, dur, utid + FROM _critical_path_by_intervals!( + (SELECT 6 AS utid, trace_start() AS ts, trace_end() - trace_start() AS dur), + _wakeup_graph) + ORDER BY root_id, ts; """, out=Csv(""" - "id","ts","dur","utid","critical_path_id","critical_path_blocked_dur","critical_path_blocked_state","critical_path_blocked_function","critical_path_utid" - 62,807082947246838,1038854,6,"[NULL]","[NULL]","[NULL]","[NULL]",6 - 107,807082959173506,215104,6,"[NULL]","[NULL]","[NULL]","[NULL]",6 + "root_utid","root_id","id","ts","dur","utid" + 6,14,14,807082865019382,14160157,6 + 6,62,60,807082947223556,23282,11 + 6,62,62,807082947246838,2000260,6 + 6,107,105,807082959078401,95105,18 + 6,107,139,807082959173506,73362507,6 + 6,139,139,807083032536013,98125,6 + 6,139,142,807083032634138,340625,6 + 6,142,142,807083032974763,16615,6 + 6,142,142,807083032991378,89218,6 """)) def test_thread_executing_span_critical_path_utid(self): @@ -445,31 +420,28 @@ def test_thread_executing_span_critical_path_utid(self): query=""" INCLUDE PERFETTO MODULE sched.thread_executing_span; SELECT + root_id, + root_utid, id, ts, dur, - utid, - critical_path_id, - critical_path_blocked_dur, - critical_path_blocked_state, - critical_path_blocked_function, - critical_path_utid + utid FROM _thread_executing_span_critical_path((select utid from thread where tid = 3487), start_ts, end_ts), trace_bounds ORDER BY ts LIMIT 10 """, out=Csv(""" - "id","ts","dur","utid","critical_path_id","critical_path_blocked_dur","critical_path_blocked_state","critical_path_blocked_function","critical_path_utid" - 11889,1737349401439,7705561,1477,"[NULL]","[NULL]","[NULL]","[NULL]",1477 - 11952,1737357107000,547583,1480,11980,547583,"S","[NULL]",1477 - 11980,1737357654583,8430762,1477,"[NULL]","[NULL]","[NULL]","[NULL]",1477 - 12052,1737366085345,50400,91,12057,50400,"S","[NULL]",1477 - 12057,1737366135745,6635927,1477,"[NULL]","[NULL]","[NULL]","[NULL]",1477 - 12081,1737372771672,12798314,1488,12254,12798314,"S","[NULL]",1477 - 12254,1737385569986,21830622,1477,"[NULL]","[NULL]","[NULL]","[NULL]",1477 - 12517,1737407400608,241267,91,12521,241267,"S","[NULL]",1477 - 12521,1737407641875,1830015,1477,"[NULL]","[NULL]","[NULL]","[NULL]",1477 - 12669,1737409471890,68590,91,12672,68590,"S","[NULL]",1477 + "root_id","root_utid","id","ts","dur","utid" + 11889,1477,11889,1737349401439,7705561,1477 + 11980,1477,11952,1737357107000,547583,1480 + 11980,1477,11980,1737357654583,8430762,1477 + 12057,1477,12052,1737366085345,50400,91 + 12057,1477,12057,1737366135745,6635927,1477 + 12254,1477,12251,1737372771672,12594070,1488 + 12254,1477,12251,1737385365742,204244,1488 + 12254,1477,12254,1737385569986,21830622,1477 + 12521,1477,12517,1737407400608,241267,91 + 12521,1477,12521,1737407641875,1830015,1477 """)) def test_thread_executing_span_critical_path_stack(self): @@ -485,24 +457,26 @@ def test_thread_executing_span_critical_path_stack(self): stack_depth, name, table_name, - critical_path_utid + root_utid FROM _thread_executing_span_critical_path_stack((select utid from thread where tid = 3487), start_ts, end_ts), trace_bounds - ORDER BY ts - LIMIT 11 + WHERE ts = 1737500355691 + ORDER BY utid, id """, out=Csv(""" - "id","ts","dur","utid","stack_depth","name","table_name","critical_path_utid" - 11889,1737349401439,57188,1477,0,"thread_state: R","thread_state",1477 - 11889,1737349401439,57188,1477,1,"[NULL]","thread_state",1477 - 11889,1737349401439,57188,1477,2,"[NULL]","thread_state",1477 - 11889,1737349401439,57188,1477,3,"process_name: com.android.providers.media.module","thread_state",1477 - 11889,1737349401439,57188,1477,4,"thread_name: rs.media.module","thread_state",1477 - 11891,1737349458627,1884896,1477,0,"thread_state: Running","thread_state",1477 - 11891,1737349458627,1884896,1477,1,"[NULL]","thread_state",1477 - 11891,1737349458627,1884896,1477,2,"[NULL]","thread_state",1477 - 11891,1737349458627,1884896,1477,3,"process_name: com.android.providers.media.module","thread_state",1477 - 11891,1737349458627,1884896,1477,4,"thread_name: rs.media.module","thread_state",1477 - 11891,1737349458627,1884896,1477,5,"cpu: 0","thread_state",1477 + "id","ts","dur","utid","stack_depth","name","table_name","root_utid" + 4271,1737500355691,1456753,1477,5,"bindApplication","slice",1477 + 13120,1737500355691,1456753,1477,0,"thread_state: S","thread_state",1477 + 13120,1737500355691,1456753,1477,1,"[NULL]","thread_state",1477 + 13120,1737500355691,1456753,1477,2,"[NULL]","thread_state",1477 + 13120,1737500355691,1456753,1477,3,"process_name: com.android.providers.media.module","thread_state",1477 + 13120,1737500355691,1456753,1477,4,"thread_name: rs.media.module","thread_state",1477 + 4800,1737500355691,1456753,1498,11,"HIDL::IComponentStore::getStructDescriptors::client","slice",1477 + 4801,1737500355691,1456753,1498,12,"binder transaction","slice",1477 + 13648,1737500355691,1456753,1498,6,"blocking thread_state: R+","thread_state",1477 + 13648,1737500355691,1456753,1498,7,"blocking process_name: com.android.providers.media.module","thread_state",1477 + 13648,1737500355691,1456753,1498,8,"blocking thread_name: CodecLooper","thread_state",1477 + 13648,1737500355691,1456753,1498,9,"[NULL]","thread_state",1477 + 13648,1737500355691,1456753,1498,10,"[NULL]","thread_state",1477 """)) def test_thread_executing_span_critical_path_graph(self): @@ -516,164 +490,164 @@ def test_thread_executing_span_critical_path_graph(self): message_type="perfetto.third_party.perftools.profiles.Profile", post_processing=PrintProfileProto, contents=""" - Sample: - Values: 0 - Stack: - bindApplication (0x0) - thread_name: rs.media.module (0x0) - process_name: com.android.providers.media.module (0x0) - thread_state: R (0x0) - critical path (0x0) - - Sample: - Values: 0 - Stack: - bindApplication (0x0) - thread_name: rs.media.module (0x0) - process_name: com.android.providers.media.module (0x0) - thread_state: S (0x0) - critical path (0x0) - - Sample: - Values: 0 - Stack: - binder reply (0x0) - blocking thread_name: binder:553_3 (0x0) - blocking process_name: /system/bin/mediaserver (0x0) - blocking thread_state: Running (0x0) - binder transaction (0x0) - bindApplication (0x0) - thread_name: rs.media.module (0x0) - process_name: com.android.providers.media.module (0x0) - thread_state: S (0x0) - critical path (0x0) - - Sample: - Values: 0 - Stack: - binder transaction (0x0) - bindApplication (0x0) - thread_name: rs.media.module (0x0) - process_name: com.android.providers.media.module (0x0) - thread_state: S (0x0) - critical path (0x0) - - Sample: - Values: 0 - Stack: - blocking process_name: /system/bin/mediaserver (0x0) - blocking thread_state: Running (0x0) - binder transaction (0x0) - bindApplication (0x0) - thread_name: rs.media.module (0x0) - process_name: com.android.providers.media.module (0x0) - thread_state: S (0x0) - critical path (0x0) - - Sample: - Values: 0 - Stack: - blocking thread_name: binder:553_3 (0x0) - blocking process_name: /system/bin/mediaserver (0x0) - blocking thread_state: Running (0x0) - binder transaction (0x0) - bindApplication (0x0) - thread_name: rs.media.module (0x0) - process_name: com.android.providers.media.module (0x0) - thread_state: S (0x0) - critical path (0x0) - - Sample: - Values: 0 - Stack: - blocking thread_state: Running (0x0) - binder transaction (0x0) - bindApplication (0x0) - thread_name: rs.media.module (0x0) - process_name: com.android.providers.media.module (0x0) - thread_state: S (0x0) - critical path (0x0) - - Sample: - Values: 0 - Stack: - process_name: com.android.providers.media.module (0x0) - thread_state: R (0x0) - critical path (0x0) - - Sample: - Values: 0 - Stack: - process_name: com.android.providers.media.module (0x0) - thread_state: S (0x0) - critical path (0x0) - - Sample: - Values: 0 - Stack: - thread_name: rs.media.module (0x0) - process_name: com.android.providers.media.module (0x0) - thread_state: R (0x0) - critical path (0x0) - - Sample: - Values: 0 - Stack: - thread_name: rs.media.module (0x0) - process_name: com.android.providers.media.module (0x0) - thread_state: S (0x0) - critical path (0x0) - - Sample: - Values: 0 - Stack: - thread_state: R (0x0) - critical path (0x0) - - Sample: - Values: 0 - Stack: - thread_state: S (0x0) - critical path (0x0) - - Sample: - Values: 1101 - Stack: - binder transaction (0x0) - bindApplication (0x0) - thread_name: rs.media.module (0x0) - process_name: com.android.providers.media.module (0x0) - thread_state: R (0x0) - critical path (0x0) - - Sample: - Values: 13010 - Stack: - cpu: 0 (0x0) - binder reply (0x0) - blocking thread_name: binder:553_3 (0x0) - blocking process_name: /system/bin/mediaserver (0x0) - blocking thread_state: Running (0x0) - binder transaction (0x0) - bindApplication (0x0) - thread_name: rs.media.module (0x0) - process_name: com.android.providers.media.module (0x0) - thread_state: S (0x0) - critical path (0x0) - - Sample: - Values: 1889 - Stack: - cpu: 0 (0x0) - blocking thread_name: binder:553_3 (0x0) - blocking process_name: /system/bin/mediaserver (0x0) - blocking thread_state: Running (0x0) - binder transaction (0x0) - bindApplication (0x0) - thread_name: rs.media.module (0x0) - process_name: com.android.providers.media.module (0x0) - thread_state: S (0x0) - critical path (0x0) + Sample: + Values: 0 + Stack: + bindApplication (0x0) + thread_name: rs.media.module (0x0) + process_name: com.android.providers.media.module (0x0) + thread_state: R (0x0) + critical path (0x0) + + Sample: + Values: 0 + Stack: + bindApplication (0x0) + thread_name: rs.media.module (0x0) + process_name: com.android.providers.media.module (0x0) + thread_state: S (0x0) + critical path (0x0) + + Sample: + Values: 0 + Stack: + binder reply (0x0) + blocking thread_name: binder:553_3 (0x0) + blocking process_name: /system/bin/mediaserver (0x0) + blocking thread_state: Running (0x0) + binder transaction (0x0) + bindApplication (0x0) + thread_name: rs.media.module (0x0) + process_name: com.android.providers.media.module (0x0) + thread_state: S (0x0) + critical path (0x0) + + Sample: + Values: 0 + Stack: + binder transaction (0x0) + bindApplication (0x0) + thread_name: rs.media.module (0x0) + process_name: com.android.providers.media.module (0x0) + thread_state: S (0x0) + critical path (0x0) + + Sample: + Values: 0 + Stack: + blocking process_name: /system/bin/mediaserver (0x0) + blocking thread_state: Running (0x0) + binder transaction (0x0) + bindApplication (0x0) + thread_name: rs.media.module (0x0) + process_name: com.android.providers.media.module (0x0) + thread_state: S (0x0) + critical path (0x0) + + Sample: + Values: 0 + Stack: + blocking thread_name: binder:553_3 (0x0) + blocking process_name: /system/bin/mediaserver (0x0) + blocking thread_state: Running (0x0) + binder transaction (0x0) + bindApplication (0x0) + thread_name: rs.media.module (0x0) + process_name: com.android.providers.media.module (0x0) + thread_state: S (0x0) + critical path (0x0) + + Sample: + Values: 0 + Stack: + blocking thread_state: Running (0x0) + binder transaction (0x0) + bindApplication (0x0) + thread_name: rs.media.module (0x0) + process_name: com.android.providers.media.module (0x0) + thread_state: S (0x0) + critical path (0x0) + + Sample: + Values: 0 + Stack: + process_name: com.android.providers.media.module (0x0) + thread_state: R (0x0) + critical path (0x0) + + Sample: + Values: 0 + Stack: + process_name: com.android.providers.media.module (0x0) + thread_state: S (0x0) + critical path (0x0) + + Sample: + Values: 0 + Stack: + thread_name: rs.media.module (0x0) + process_name: com.android.providers.media.module (0x0) + thread_state: R (0x0) + critical path (0x0) + + Sample: + Values: 0 + Stack: + thread_name: rs.media.module (0x0) + process_name: com.android.providers.media.module (0x0) + thread_state: S (0x0) + critical path (0x0) + + Sample: + Values: 0 + Stack: + thread_state: R (0x0) + critical path (0x0) + + Sample: + Values: 0 + Stack: + thread_state: S (0x0) + critical path (0x0) + + Sample: + Values: 1101 + Stack: + binder transaction (0x0) + bindApplication (0x0) + thread_name: rs.media.module (0x0) + process_name: com.android.providers.media.module (0x0) + thread_state: R (0x0) + critical path (0x0) + + Sample: + Values: 13010 + Stack: + cpu: 0 (0x0) + binder reply (0x0) + blocking thread_name: binder:553_3 (0x0) + blocking process_name: /system/bin/mediaserver (0x0) + blocking thread_state: Running (0x0) + binder transaction (0x0) + bindApplication (0x0) + thread_name: rs.media.module (0x0) + process_name: com.android.providers.media.module (0x0) + thread_state: S (0x0) + critical path (0x0) + + Sample: + Values: 1889 + Stack: + cpu: 0 (0x0) + blocking thread_name: binder:553_3 (0x0) + blocking process_name: /system/bin/mediaserver (0x0) + blocking thread_state: Running (0x0) + binder transaction (0x0) + bindApplication (0x0) + thread_name: rs.media.module (0x0) + process_name: com.android.providers.media.module (0x0) + thread_state: S (0x0) + critical path (0x0) """)) # Test machine_id ID of the sched table. @@ -682,7 +656,9 @@ def test_android_sched_and_ps_machine_id(self): trace=DataPath('android_sched_and_ps.pb'), trace_modifier=TraceInjector(['ftrace_events'], {'machine_id': 1001}), query=""" - SELECT ts, cpu, machine_id FROM sched WHERE ts >= 81473797418963 LIMIT 10; + SELECT ts, cpu.cpu, machine_id + FROM sched LEFT JOIN cpu USING (ucpu) + WHERE ts >= 81473797418963 LIMIT 10; """, out=Csv(""" "ts","cpu","machine_id" @@ -704,9 +680,47 @@ def test_raw_machine_id(self): trace=DataPath('android_sched_and_ps.pb'), trace_modifier=TraceInjector(['ftrace_events'], {'machine_id': 1001}), query=""" - SELECT count(*) FROM raw WHERE machine_id is NULL; + SELECT count(*) + FROM raw LEFT JOIN cpu USING (ucpu) + WHERE machine_id is NULL; """, out=Csv(""" "count(*)" 0 """)) + + def test_sched_cpu_id(self): + return DiffTestBlueprint( + trace=DataPath('sched_switch_original.pb'), + query=""" + SELECT cpu, cluster_id + FROM cpu + """, + out=Csv(""" + "cpu","cluster_id" + 0,0 + 1,0 + 2,0 + 3,0 + 4,0 + 7,0 + """)) + + def test_sched_cpu_id_machine_id(self): + return DiffTestBlueprint( + trace=DataPath('sched_switch_original.pb'), + trace_modifier=TraceInjector(['ftrace_events'], {'machine_id': 1001}), + query=""" + SELECT cpu, cluster_id, machine.raw_id as raw_machine_id + FROM cpu + JOIN machine ON cpu.machine_id = machine.id + """, + out=Csv(""" + "cpu","cluster_id","raw_machine_id" + 0,0,1001 + 1,0,1001 + 2,0,1001 + 3,0,1001 + 4,0,1001 + 7,0,1001 + """)) diff --git a/tools/bisect_ui_releases b/tools/bisect_ui_releases new file mode 100644 index 0000000000..ca0679aa91 --- /dev/null +++ b/tools/bisect_ui_releases @@ -0,0 +1,116 @@ +#!/usr/bin/env python3 +# Copyright (C) 2024 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +Runs a bisection on the autopush ui.perfetto.dev builds + +Similar to git bisect, but bisects UI releases rather than commits. +This works only for autopush builds from main, ignores canary and stable +channels, as they make the history non-linear. + +How it works: +- It first obtains an unordered list of versions from gs://ui.perfetto.dev +- Then obtains the list of ordered commits from git +- Intersects the two lists, keeping only git commits that have a corresponding + ui autopush release. +- Proceeds with a guided bisect in the range. +""" + +import argparse +import sys + +from subprocess import check_output + +COMMIT_ABBR_LEN = 9 # UI truncates commitish to 9 chars, e.g. v45.0-38b7c2b12. + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument( + '--good', + default=None, + help='Last good release (e.g. v44.0-257a02990).' + + 'Defaults to the first verion available') + parser.add_argument( + '--bad', + default=None, + help='First bad release. Defaults to the latest version available') + args = parser.parse_args() + + print('Fetching list of UI releases from GCS...') + rev_list = check_output(['gsutil.py', 'ls', 'gs://ui.perfetto.dev/']).decode() + ui_map = {} # maps '38b7c2b12' -> 'v45.0-38b7c2b12' + for line in rev_list.split(): + version = line.split('/')[3] + if '-' not in version: + continue + ver_hash = version.split('-')[1] + ui_map[ver_hash] = version + print('Found %d UI versions' % len(ui_map)) + + # Get the linear history of all commits. + print('Fetching revision history from git...') + ui_versions = [] + git_out = check_output(['git', 'rev-list', '--left-only', + 'origin/main']).decode() + for line in git_out.split(): + line = line.strip() + rev = line[0:COMMIT_ABBR_LEN] + if rev not in ui_map: + continue # Not all perfetto commits have a UI autopush build. + ui_versions.append(ui_map[rev]) + + # git rev-list emits entries in recent -> older versions. Reverse it. + ui_versions.reverse() + + # Note that not all the entries in ui_map will be present in ui_versions. + # This is because ui_map contains also builds coming from canary and stable + # branches, that we ignore here. + + start = ui_versions.index(args.good) if args.good else 0 + end = ui_versions.index(args.bad) if args.bad else len(ui_versions) - 1 + while end - start > 1: + print('\033c', end='') # clear terminal. + print( + 'Bisecting from %s (last good) to %s (first bad), %d revisions to go' % + (ui_versions[start], ui_versions[end], end - start + 1)) + mid = (end + start) // 2 + + # Print a visual indication of where we are in the bisect. + for i in reversed(range(start, end + 1)): + sfx = '' + if i == start: + sfx = ' GOOD --------------' + elif i == end: + sfx = ' BAD ---------------' + elif i == mid: + sfx = ' <- version to test' + print(ui_versions[i] + sfx) + + user_feedback = input( + 'https://ui.perfetto.dev/%s/. Type g for good and b for bad: ' % + ui_versions[mid]) + if user_feedback == 'b': + end = mid + elif user_feedback == 'g': + start = mid + else: + print('Unrecognised key "%d", try again' % user_feedback) + + print('First bad UI release %s' % ui_versions[end]) + print('You should now inspect the individual commits via git log good..bad') + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/tools/check_sql_metrics.py b/tools/check_sql_metrics.py index f025f47c86..cd82c6df69 100755 --- a/tools/check_sql_metrics.py +++ b/tools/check_sql_metrics.py @@ -27,12 +27,36 @@ ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.append(os.path.join(ROOT_DIR)) -from python.generators.sql_processing.utils import check_banned_create_table_as from python.generators.sql_processing.utils import check_banned_create_view_as from python.generators.sql_processing.utils import check_banned_words from python.generators.sql_processing.utils import match_pattern from python.generators.sql_processing.utils import DROP_TABLE_VIEW_PATTERN from python.generators.sql_processing.utils import CREATE_TABLE_VIEW_PATTERN +from python.generators.sql_processing.utils import CREATE_TABLE_AS_PATTERN + + +def check_if_create_table_allowlisted( + sql: str, filename: str, stdlib_path: str, + allowlist: Dict[str, List[str]]) -> List[str]: + errors = [] + for _, matches in match_pattern(CREATE_TABLE_AS_PATTERN, sql).items(): + name = matches[0] + # Normalize paths before checking presence in the allowlist so it will + # work on Windows for the Chrome stdlib presubmit. + allowlist_normpath = dict( + (os.path.normpath(path), tables) for path, tables in allowlist.items()) + allowlist_key = os.path.normpath(filename[len(stdlib_path):]) + if allowlist_key not in allowlist_normpath: + errors.append(f"CREATE TABLE '{name}' is deprecated. " + "Use CREATE PERFETTO TABLE instead.\n" + f"Offending file: {filename}\n") + continue + if name not in allowlist_normpath[allowlist_key]: + errors.append( + f"Table '{name}' uses CREATE TABLE which is deprecated " + "and this table is not allowlisted. Use CREATE PERFETTO TABLE.\n" + f"Offending file: {filename}\n") + return errors # Allowlist path are relative to the metrics root. CREATE_TABLE_ALLOWLIST = { @@ -42,9 +66,9 @@ 'android_blocking_calls_cuj_calls' ], ('/android' - '/android_blocking_calls_unagg.sql'): [ - 'filtered_processes_with_non_zero_blocking_calls', - 'process_info', 'android_blocking_calls_unagg_calls' + '/android_blocking_calls_unagg.sql'): [ + 'filtered_processes_with_non_zero_blocking_calls', 'process_info', + 'android_blocking_calls_unagg_calls' ], '/android/jank/cujs.sql': ['android_jank_cuj'], '/chrome/gesture_flow_event.sql': [ @@ -74,6 +98,7 @@ def match_drop_view_pattern_to_dict(sql: str, def check(path: str, metrics_sources: str) -> List[str]: + errors = [] with open(path) as f: sql = f.read() @@ -93,17 +118,15 @@ def check(path: str, metrics_sources: str) -> List[str]: f'prevent the file from crashing if the metric is rerun.\n' f'Offending file: {path}\n') - - # Check that CREATE VIEW/TABLE has a matching DROP VIEW/TABLE before it. create_table_view_dir = match_create_table_pattern_to_dict( sql, CREATE_TABLE_VIEW_PATTERN) drop_table_view_dir = match_drop_view_pattern_to_dict( sql, DROP_TABLE_VIEW_PATTERN) - errors = check_banned_create_table_as(sql, - path.split(ROOT_DIR)[1], - metrics_sources.split(ROOT_DIR)[1], - CREATE_TABLE_ALLOWLIST) + errors += check_if_create_table_allowlisted( + sql, + path.split(ROOT_DIR)[1], + metrics_sources.split(ROOT_DIR)[1], CREATE_TABLE_ALLOWLIST) errors += check_banned_create_view_as(sql, path.split(ROOT_DIR)[1]) for name, [line, type] in create_table_view_dir.items(): if name not in drop_table_view_dir: diff --git a/tools/check_sql_modules.py b/tools/check_sql_modules.py index 935bf9e64a..77656976f9 100755 --- a/tools/check_sql_modules.py +++ b/tools/check_sql_modules.py @@ -32,23 +32,6 @@ from python.generators.sql_processing.utils import check_banned_words from python.generators.sql_processing.utils import check_banned_include_all -# Allowlist path are relative to the stdlib root. -CREATE_TABLE_ALLOWLIST = { - '/prelude/trace_bounds.sql': ['trace_bounds'], - '/android/binder.sql': ['_oom_score'], - '/android/monitor_contention.sql': [ - '_isolated', 'android_monitor_contention_chain', - 'android_monitor_contention' - ], - '/chrome/tasks.sql': [ - '_chrome_mojo_slices', '_chrome_java_views', '_chrome_scheduler_tasks', - '_chrome_tasks' - ], - '/sched/thread_executing_span.sql': ['_wakeup_graph', '_thread_executing_span_graph', - '_critical_path'], - '/slices/flat_slices.sql': ['_slice_flattened'] -} - def main(): parser = argparse.ArgumentParser() @@ -122,7 +105,7 @@ def main(): errors += check_banned_create_table_as( sql, path.split(ROOT_DIR)[1], - args.stdlib_sources.split(ROOT_DIR)[1], CREATE_TABLE_ALLOWLIST) + args.stdlib_sources.split(ROOT_DIR)[1]) errors += check_banned_create_view_as(sql, path.split(ROOT_DIR)[1]) errors += check_banned_include_all(sql, path.split(ROOT_DIR)[1]) diff --git a/tools/cpu_profile b/tools/cpu_profile index c4198d392f..0cb493bf59 100755 --- a/tools/cpu_profile +++ b/tools/cpu_profile @@ -37,18 +37,18 @@ import uuid # ----- Amalgamator: begin of python/perfetto/prebuilts/manifests/traceconv.py -# This file has been generated by: tools/roll-prebuilts v45.0 +# This file has been generated by: tools/roll-prebuilts v46.0 TRACECONV_MANIFEST = [{ 'arch': 'mac-amd64', 'file_name': 'traceconv', 'file_size': - 8299680, + 8743272, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/mac-amd64/traceconv', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/mac-amd64/traceconv', 'sha256': - '80291c46aea5bf3b1007a385fc6442a99865bfdd70ea627b3e9a2e19d6d62de1', + 'e0abc72fc69d3be68f16b198253ea9a802c266f7c2609debe561976b627d5d67', 'platform': 'darwin', 'machine': ['x86_64'] @@ -58,11 +58,11 @@ TRACECONV_MANIFEST = [{ 'file_name': 'traceconv', 'file_size': - 7761304, + 8158456, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/mac-arm64/traceconv', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/mac-arm64/traceconv', 'sha256': - '53b78c7f9ee26a766b2d8f28e8033367f697d669b133383dfb116fd44f7ffa8a', + '9cd1635898536bcdcff8b944b05bf2ffc84fc0c55ba270746203a4fa0bae9f01', 'platform': 'darwin', 'machine': ['arm64'] @@ -72,11 +72,11 @@ TRACECONV_MANIFEST = [{ 'file_name': 'traceconv', 'file_size': - 8353608, + 8809008, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/linux-amd64/traceconv', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/linux-amd64/traceconv', 'sha256': - '57e1cf04dc25fa6a6f7ae3482158f2dfb363269baeab1673343974312551a753', + '63e8599c19125db25bb1d1d39b0bba6b30ea30760ebfce87d0d9508458cad8ba', 'platform': 'linux', 'machine': ['x86_64'] @@ -86,11 +86,11 @@ TRACECONV_MANIFEST = [{ 'file_name': 'traceconv', 'file_size': - 6317844, + 6665536, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/linux-arm/traceconv', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/linux-arm/traceconv', 'sha256': - 'e361944a78b55b83bc072043dd983b8f5debcf5a875b79accfcb02d864f97a85', + 'c60d071015bed4da8b9411727e5c35c8429dd4698c92533bea82bf927f1f6966', 'platform': 'linux', 'machine': ['armv6l', 'armv7l', 'armv8l'] @@ -100,11 +100,11 @@ TRACECONV_MANIFEST = [{ 'file_name': 'traceconv', 'file_size': - 8061520, + 8477496, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/linux-arm64/traceconv', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/linux-arm64/traceconv', 'sha256': - '80e8842b22e9c92b4d44f0e16d6b65b0d013d7c769e853961a0f222287898850', + 'd675922ce9900d4f50105ea8bfcd1edd7fc92ffc56c3e0e7f86bf2af41c5b3db', 'platform': 'linux', 'machine': ['aarch64'] @@ -114,55 +114,55 @@ TRACECONV_MANIFEST = [{ 'file_name': 'traceconv', 'file_size': - 6320968, + 6680736, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/android-arm/traceconv', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/android-arm/traceconv', 'sha256': - '4d17f00e48f256cc9a725db1e4ba7eddd1fc4ab96e7e67cade07338ac74eab88' + '7148b59e5328ab3d6200d52ceb7dd97fe4d82e873dfabd2da3f2f5be54e8e726' }, { 'arch': 'android-arm64', 'file_name': 'traceconv', 'file_size': - 7998792, + 8424248, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/android-arm64/traceconv', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/android-arm64/traceconv', 'sha256': - '230e91c8bfa59094d69b0dad0fb3328b9c2134a3d34653d6d4fa2a05ed2db4b2' + 'fc7830c47a662a38cee0a11b3c0e9959cffcd4b9b6260e38ef45284371f6e4df' }, { 'arch': 'android-x86', 'file_name': 'traceconv', 'file_size': - 8670248, + 9115416, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/android-x86/traceconv', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/android-x86/traceconv', 'sha256': - 'e59e7a34fd0af9cd0db84ae104bdb79cbf7efb5d71ab11f3b31f6045c1621c5b' + '4f7829506c6bcb249615b87aa40440e45824d4ce378c5f086c317e55241b9b7c' }, { 'arch': 'android-x64', 'file_name': 'traceconv', 'file_size': - 8229048, + 8657048, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/android-x64/traceconv', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/android-x64/traceconv', 'sha256': - '277eab28583824c02159b4b0611d4cb4480305da477f607a90b161c834a21e6b' + '1799a71ad7a743989456ad50b25fdfc01324d4a8f3d9eb97e0799494a3d7ed1c' }, { 'arch': 'windows-amd64', 'file_name': 'traceconv.exe', 'file_size': - 8119296, + 8527872, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/windows-amd64/traceconv.exe', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/windows-amd64/traceconv.exe', 'sha256': - '3fffac0a1d3bea94924e219a8390c96aef729cd0dfa9f216a2c20eb56ece9e86', + '9e7afe063caefe8ae5164014d0180933c66688f0af6618ed3ac1179454d2c23c', 'platform': 'win32', 'machine': ['amd64'] @@ -217,11 +217,9 @@ The intended usage is: import hashlib import os import platform +import random import subprocess import sys -import threading - -DOWNLOAD_LOCK = threading.Lock() def download_or_get_cached(file_name, url, sha256): @@ -238,36 +236,30 @@ def download_or_get_cached(file_name, url, sha256): sha256_path = os.path.join(dir, file_name + '.sha256') needs_download = True - 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() + # 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: # The file doesn't exist or the SHA256 doesn't match. + # Use a unique random file to guard against concurrent executions. + # See https://github.com/google/perfetto/issues/786 . + tmp_path = '%s.%d.tmp' % (bin_path, random.randint(0, 100000)) + 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(tmp_path, 'w') as f: + f.write(sha256) + os.replace(tmp_path, sha256_path) return bin_path diff --git a/tools/diff_test_trace_processor.py b/tools/diff_test_trace_processor.py index 369cde4389..a4d3261a8b 100755 --- a/tools/diff_test_trace_processor.py +++ b/tools/diff_test_trace_processor.py @@ -74,8 +74,9 @@ def main(): args.test_extensions = os.path.join(out_path, 'gen', 'protos', 'perfetto', 'trace', 'test_extensions.descriptor') if args.winscope_extensions is None: - args.winscope_extensions = os.path.join(out_path, 'gen', 'protos', 'perfetto', 'trace', - 'android', 'winscope.descriptor') + args.winscope_extensions = os.path.join(out_path, 'gen', 'protos', + 'perfetto', 'trace', 'android', + 'winscope.descriptor') test_runner = DiffTestsRunner(args.name_filter, args.trace_processor, args.trace_descriptor, args.no_colors, @@ -83,8 +84,10 @@ def main(): sys.stderr.write(f"[==========] Running {len(test_runner.tests)} tests.\n") results = test_runner.run_all_tests(args.metrics_descriptor, - args.chrome_track_event_descriptor, args.test_extensions, - args.winscope_extensions, args.keep_input, args.rebase) + args.chrome_track_event_descriptor, + args.test_extensions, + args.winscope_extensions, args.keep_input, + args.rebase) sys.stderr.write(results.str(args.no_colors, len(test_runner.tests))) if args.rebase: diff --git a/tools/gen_android_bp b/tools/gen_android_bp index a0d92edce8..9dbeac799a 100755 --- a/tools/gen_android_bp +++ b/tools/gen_android_bp @@ -57,6 +57,7 @@ default_targets = [ '//:perfetto_integrationtests', '//:perfetto_unittests', '//protos/perfetto/trace:perfetto_trace_protos', + '//protos/perfetto/trace/android:perfetto_winscope_extensions_zero', '//src/android_internal:libperfetto_android_internal', '//src/base:perfetto_base_default_platform', '//src/shared_lib:libperfetto_c', @@ -105,6 +106,7 @@ target_host_supported = [ '//:libperfetto', '//:libperfetto_client_experimental', '//protos/perfetto/trace:perfetto_trace_protos', + '//protos/perfetto/trace/android:perfetto_winscope_extensions_zero', '//src/shared_lib:libperfetto_c', '//src/trace_processor:demangle', '//src/trace_processor:trace_processor_shell', @@ -137,7 +139,7 @@ proto_groups = { ] }, 'config': { - 'types': ['lite'], + 'types': ['filegroup'], 'targets': [ '//protos/perfetto/config:source_set', ] @@ -341,6 +343,7 @@ def enable_libunwindstack(module): module.static_libs.add('liblzma') module.static_libs.add('libdexfile_support') module.runtime_libs.add('libdexfile') # libdexfile_support dependency + module.shared_libs.add('libz') # libunwindstack dependency def enable_libunwind(module): diff --git a/tools/gen_bigtrace_grpc_protos.py b/tools/gen_bigtrace_grpc_protos.py new file mode 100644 index 0000000000..a6ea37d239 --- /dev/null +++ b/tools/gen_bigtrace_grpc_protos.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +# Copyright (C) 2024 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import subprocess +""" +Compile the gRPC python code for Bigtrace +and modify the include paths to point to the correct file paths + +""" + + +def main(): + subprocess.run([ + "python", + "-m", + "grpc_tools.protoc", + "-I.", + "--python_out=python/perfetto/bigtrace", + "--pyi_out=python/perfetto/bigtrace", + "protos/perfetto/bigtrace/orchestrator.proto", + "protos/perfetto/trace_processor/trace_processor.proto", + "protos/perfetto/common/descriptor.proto", + "protos/perfetto/trace_processor/metatrace_categories.proto", + ]) + subprocess.run([ + "python", + "-m", + "grpc_tools.protoc", + "-I.", + "--python_out=python/perfetto/bigtrace", + "--pyi_out=python/perfetto/bigtrace", + "--grpc_python_out=python/perfetto/bigtrace", + "protos/perfetto/bigtrace/orchestrator.proto", + ]) + subprocess.run([ + "sed", + "-i", + "-e", + "s/protos\.perfetto/perfetto\.bigtrace\.protos\.perfetto/", + "python/perfetto/bigtrace/protos/perfetto/bigtrace/orchestrator_pb2_grpc.py", + "python/perfetto/bigtrace/protos/perfetto/bigtrace/orchestrator_pb2.py", + "python/perfetto/bigtrace/protos/perfetto/bigtrace/orchestrator_pb2.pyi", + "python/perfetto/bigtrace/protos/perfetto/trace_processor/trace_processor_pb2.py", + "python/perfetto/bigtrace/protos/perfetto/trace_processor/trace_processor_pb2.pyi", + ]) + return 0 + + +if __name__ == "__main__": + main() diff --git a/tools/gen_c_protos b/tools/gen_c_protos index 370e59abbb..fd12cdd97c 100755 --- a/tools/gen_c_protos +++ b/tools/gen_c_protos @@ -30,6 +30,8 @@ SOURCE_FILES = [ 'protos/perfetto/config/data_source_config.proto', 'protos/perfetto/config/trace_config.proto', 'protos/perfetto/config/track_event/track_event_config.proto', + 'protos/perfetto/trace/android/android_track_event.proto', + 'protos/perfetto/trace/clock_snapshot.proto', 'protos/perfetto/trace/interned_data/interned_data.proto', 'protos/perfetto/trace/test_event.proto', 'protos/perfetto/trace/trace.proto', @@ -47,6 +49,7 @@ SOURCE_FILES = [ }, { 'files': [ + 'src/protozero/test/example_proto/extensions.proto', 'src/protozero/test/example_proto/library.proto', 'src/protozero/test/example_proto/library_internals/galaxies.proto', 'src/protozero/test/example_proto/other_package/test_messages.proto', diff --git a/tools/gen_grpc_build_gn.py b/tools/gen_grpc_build_gn.py index 98e90839fb..f921e391f9 100755 --- a/tools/gen_grpc_build_gn.py +++ b/tools/gen_grpc_build_gn.py @@ -46,6 +46,7 @@ public_deps = {deps} public_configs = ["..:{config_name}"] configs -= [ "//gn/standalone:extra_warnings" ] + check_includes = {check_includes} }}""" LIBRARY_IGNORE_LIST = set([ @@ -53,6 +54,8 @@ 'grpc++_reflection', 'benchmark_helpers', 'boringssl_test_util', + 'grpcpp_otel_plugin', + 'otel_plugin_test', ]) TARGET_ALLOW_LIST = set([ @@ -64,6 +67,12 @@ 're2', 'boringssl', 'grpc++', + 'upb_json_lib', + 'upb_textformat_lib', +]) + +DEP_DENYLIST = set([ + 'cares', ]) @@ -74,8 +83,6 @@ def grpc_relpath(*segments: str) -> str: GRPC_BUILD_YAML = grpc_relpath('build_autogenerated.yaml') ABSL_GEN_BUILD_YAML = grpc_relpath('src', 'abseil-cpp', 'gen_build_yaml.py') -UPB_GEN_BUILD_YAML = grpc_relpath('src', 'upb', 'gen_build_yaml.py') -RE2_GEN_BUILD_YAML = grpc_relpath('src', 're2', 'gen_build_yaml.py') BSSL_GEN_BUILD_YAML = grpc_relpath('src', 'boringssl', 'gen_build_yaml.py') @@ -91,14 +98,14 @@ def bazel_label_to_gn_target(dep: str) -> str: return dep.replace('/', '_').replace(':', '_') -def get_deps_for_target(target: str) -> List[str]: - if target == 'grpc_plugin_support': - return ['..:protoc_lib'] - if target == 'grpc': - return [':re2', '../../gn:zlib'] - if target == 'grpc_authorization_provider': - return [':re2'] - return [] +def bazel_label_to_gn_dep(dep: str) -> str: + if dep == 'protobuf': + return '..:protobuf_full' + if dep == 'protoc': + return '..:protoc_lib' + if dep == 'z': + return '..:zlib' + return ':' + bazel_label_to_gn_target(dep) def get_library_target_type(target: str) -> str: @@ -118,15 +125,20 @@ def yaml_to_gn_targets(desc: Dict[str, Any], build_types: list[str], continue srcs = [f'src/{file}' for file in lib['src'] + lib['headers']] if 'asm_src' in lib: - srcs += [f'src/{file}' for file in lib['asm_src']['crypto_linux_x86_64']] - deps = [f':{bazel_label_to_gn_target(dep)}' for dep in lib.get('deps', []) - ] + get_deps_for_target(lib['name']) + srcs += [f'src/{file}' for file in lib['asm_src']['crypto_asm']] + deps = [ + bazel_label_to_gn_dep(dep) + for dep in lib.get('deps', []) + if dep not in DEP_DENYLIST + ] library_target = TARGET_TEMPLATE.format( name=bazel_label_to_gn_target(lib['name']), config_name=config_name, srcs=json.dumps(srcs), deps=json.dumps(deps), - target_type=get_library_target_type(lib['name'])) + target_type=get_library_target_type(lib['name']), + check_includes='false' if lib['name'] == 'upb_json_lib' or + lib['name'] == 'upb_textformat_lib' else 'true') out.append(library_target) for bin in desc.get('targets', []): @@ -135,14 +147,18 @@ def yaml_to_gn_targets(desc: Dict[str, Any], build_types: list[str], if bin['name'] not in TARGET_ALLOW_LIST: continue srcs = json.dumps([f'src/{file}' for file in bin['src'] + bin['headers']]) - deps = [f':{bazel_label_to_gn_target(dep)}' for dep in bin.get('deps', []) - ] + get_deps_for_target(bin['name']) + deps = [ + bazel_label_to_gn_dep(dep) + for dep in bin.get('deps', []) + if dep not in DEP_DENYLIST + ] binary_target = TARGET_TEMPLATE.format( name=bazel_label_to_gn_target(bin['name']), config_name=config_name, srcs=srcs, deps=json.dumps(deps), - target_type='executable') + target_type='executable', + check_includes='true') out.append(binary_target) return out @@ -154,14 +170,6 @@ def main(): absl_yaml = gen_grpc_dep_yaml(ABSL_GEN_BUILD_YAML) out.extend(yaml_to_gn_targets(absl_yaml, ['private'], 'grpc_absl_config')) - # Generate upb rules - upb_yaml = gen_grpc_dep_yaml(UPB_GEN_BUILD_YAML) - out.extend(yaml_to_gn_targets(upb_yaml, ['all'], 'grpc_upb_config')) - - # Generate re2 rules - re2_yaml = gen_grpc_dep_yaml(RE2_GEN_BUILD_YAML) - out.extend(yaml_to_gn_targets(re2_yaml, ['private'], 'grpc_re2_config')) - # Generate boringssl rules boringssl_yaml = gen_grpc_dep_yaml(BSSL_GEN_BUILD_YAML) out.extend( diff --git a/tools/gn_utils.py b/tools/gn_utils.py index 904760ec59..33a79ff8d7 100644 --- a/tools/gn_utils.py +++ b/tools/gn_utils.py @@ -385,12 +385,13 @@ def __lt__(self, other): (type(self).__name__, type(other).__name__)) def __repr__(self): - return json.dumps({ - k: (list(sorted(v)) if isinstance(v, set) else v) - for (k, v) in iteritems(self.__dict__) - }, - indent=4, - sort_keys=True) + return json.dumps( + { + k: (list(sorted(v)) if isinstance(v, set) else v) + for (k, v) in iteritems(self.__dict__) + }, + indent=4, + sort_keys=True) def update(self, other): for key in ('cflags', 'data', 'defines', 'deps', 'include_dirs', @@ -532,8 +533,8 @@ def get_proto_paths(self, proto_desc): metadata = proto_desc.get('metadata', {}) return metadata.get('proto_import_dirs', []) - def get_proto_target_type(self, target: Target - ) -> Tuple[Optional[str], Optional[Dict]]: + def get_proto_target_type( + self, target: Target) -> Tuple[Optional[str], Optional[Dict]]: """ Checks if the target is a proto library and return the plugin. Returns: diff --git a/tools/heap_profile b/tools/heap_profile index feaff58ebc..9a28b19df5 100755 --- a/tools/heap_profile +++ b/tools/heap_profile @@ -34,18 +34,18 @@ import uuid # ----- Amalgamator: begin of python/perfetto/prebuilts/manifests/traceconv.py -# This file has been generated by: tools/roll-prebuilts v45.0 +# This file has been generated by: tools/roll-prebuilts v46.0 TRACECONV_MANIFEST = [{ 'arch': 'mac-amd64', 'file_name': 'traceconv', 'file_size': - 8299680, + 8743272, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/mac-amd64/traceconv', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/mac-amd64/traceconv', 'sha256': - '80291c46aea5bf3b1007a385fc6442a99865bfdd70ea627b3e9a2e19d6d62de1', + 'e0abc72fc69d3be68f16b198253ea9a802c266f7c2609debe561976b627d5d67', 'platform': 'darwin', 'machine': ['x86_64'] @@ -55,11 +55,11 @@ TRACECONV_MANIFEST = [{ 'file_name': 'traceconv', 'file_size': - 7761304, + 8158456, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/mac-arm64/traceconv', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/mac-arm64/traceconv', 'sha256': - '53b78c7f9ee26a766b2d8f28e8033367f697d669b133383dfb116fd44f7ffa8a', + '9cd1635898536bcdcff8b944b05bf2ffc84fc0c55ba270746203a4fa0bae9f01', 'platform': 'darwin', 'machine': ['arm64'] @@ -69,11 +69,11 @@ TRACECONV_MANIFEST = [{ 'file_name': 'traceconv', 'file_size': - 8353608, + 8809008, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/linux-amd64/traceconv', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/linux-amd64/traceconv', 'sha256': - '57e1cf04dc25fa6a6f7ae3482158f2dfb363269baeab1673343974312551a753', + '63e8599c19125db25bb1d1d39b0bba6b30ea30760ebfce87d0d9508458cad8ba', 'platform': 'linux', 'machine': ['x86_64'] @@ -83,11 +83,11 @@ TRACECONV_MANIFEST = [{ 'file_name': 'traceconv', 'file_size': - 6317844, + 6665536, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/linux-arm/traceconv', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/linux-arm/traceconv', 'sha256': - 'e361944a78b55b83bc072043dd983b8f5debcf5a875b79accfcb02d864f97a85', + 'c60d071015bed4da8b9411727e5c35c8429dd4698c92533bea82bf927f1f6966', 'platform': 'linux', 'machine': ['armv6l', 'armv7l', 'armv8l'] @@ -97,11 +97,11 @@ TRACECONV_MANIFEST = [{ 'file_name': 'traceconv', 'file_size': - 8061520, + 8477496, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/linux-arm64/traceconv', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/linux-arm64/traceconv', 'sha256': - '80e8842b22e9c92b4d44f0e16d6b65b0d013d7c769e853961a0f222287898850', + 'd675922ce9900d4f50105ea8bfcd1edd7fc92ffc56c3e0e7f86bf2af41c5b3db', 'platform': 'linux', 'machine': ['aarch64'] @@ -111,55 +111,55 @@ TRACECONV_MANIFEST = [{ 'file_name': 'traceconv', 'file_size': - 6320968, + 6680736, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/android-arm/traceconv', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/android-arm/traceconv', 'sha256': - '4d17f00e48f256cc9a725db1e4ba7eddd1fc4ab96e7e67cade07338ac74eab88' + '7148b59e5328ab3d6200d52ceb7dd97fe4d82e873dfabd2da3f2f5be54e8e726' }, { 'arch': 'android-arm64', 'file_name': 'traceconv', 'file_size': - 7998792, + 8424248, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/android-arm64/traceconv', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/android-arm64/traceconv', 'sha256': - '230e91c8bfa59094d69b0dad0fb3328b9c2134a3d34653d6d4fa2a05ed2db4b2' + 'fc7830c47a662a38cee0a11b3c0e9959cffcd4b9b6260e38ef45284371f6e4df' }, { 'arch': 'android-x86', 'file_name': 'traceconv', 'file_size': - 8670248, + 9115416, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/android-x86/traceconv', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/android-x86/traceconv', 'sha256': - 'e59e7a34fd0af9cd0db84ae104bdb79cbf7efb5d71ab11f3b31f6045c1621c5b' + '4f7829506c6bcb249615b87aa40440e45824d4ce378c5f086c317e55241b9b7c' }, { 'arch': 'android-x64', 'file_name': 'traceconv', 'file_size': - 8229048, + 8657048, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/android-x64/traceconv', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/android-x64/traceconv', 'sha256': - '277eab28583824c02159b4b0611d4cb4480305da477f607a90b161c834a21e6b' + '1799a71ad7a743989456ad50b25fdfc01324d4a8f3d9eb97e0799494a3d7ed1c' }, { 'arch': 'windows-amd64', 'file_name': 'traceconv.exe', 'file_size': - 8119296, + 8527872, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/windows-amd64/traceconv.exe', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/windows-amd64/traceconv.exe', 'sha256': - '3fffac0a1d3bea94924e219a8390c96aef729cd0dfa9f216a2c20eb56ece9e86', + '9e7afe063caefe8ae5164014d0180933c66688f0af6618ed3ac1179454d2c23c', 'platform': 'win32', 'machine': ['amd64'] @@ -214,11 +214,9 @@ The intended usage is: import hashlib import os import platform +import random import subprocess import sys -import threading - -DOWNLOAD_LOCK = threading.Lock() def download_or_get_cached(file_name, url, sha256): @@ -235,36 +233,30 @@ def download_or_get_cached(file_name, url, sha256): sha256_path = os.path.join(dir, file_name + '.sha256') needs_download = True - 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() + # 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: # The file doesn't exist or the SHA256 doesn't match. + # Use a unique random file to guard against concurrent executions. + # See https://github.com/google/perfetto/issues/786 . + tmp_path = '%s.%d.tmp' % (bin_path, random.randint(0, 100000)) + 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(tmp_path, 'w') as f: + f.write(sha256) + os.replace(tmp_path, sha256_path) return bin_path @@ -430,7 +422,10 @@ def print_options(parser): def main(argv): - parser = argparse.ArgumentParser() + parser = argparse.ArgumentParser(description="""Collect a heap profile + + The PERFETTO_PROGUARD_MAP=packagename=map_filename.txt[:packagename=map_filename.txt...] environment variable can be used to pass proguard deobfuscation maps for different packages""", formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument( "-i", "--interval", diff --git a/tools/install-build-deps b/tools/install-build-deps index 8e1d88f1a2..3367a7a234 100755 --- a/tools/install-build-deps +++ b/tools/install-build-deps @@ -185,8 +185,13 @@ BUILD_DEPS_HOST = [ # Keep in sync with Chromium's //third_party/protobuf. Dependency( 'buildtools/protobuf', + # If you revert the below version back to an earlier version of + # protobuf, make sure to revert the changes to + # //gn/standalone/protoc.py as well. + # + # This comment can be removed with protobuf is next upreved. 'https://chromium.googlesource.com/external/github.com/protocolbuffers/protobuf.git', - 'fe271ab76f2ad2b2b28c10443865d2af21e27e0e', # refs/tags/v3.20.3 + 'f0dc78d7e6e331b8c6bb2d5283e06aa26883ca7c', # refs/tags/v21.12 'all', 'all'), @@ -366,18 +371,18 @@ TYPEFACES_SHA256 = '1065172aaf0e9c22bc4f206ed9fdf5f1b4355d233fb21f9f26a89cd66c94 UI_DEPS = [ Dependency( 'buildtools/mac/nodejs.tgz', - 'https://storage.googleapis.com/chromium-nodejs/16.13.0/31859fc1fa0994a95f44f09c367d6ff63607cfde', - 'd9cf1f36b16e08ce52cfbdef427c6596eed2bd2f3608a79112d25b4ec8f75753', + 'https://storage.googleapis.com/chromium-nodejs/20.11.0/5b5681e12a21cda986410f69e03e6220a21dd4d2', + 'cecb99fbb369a9090dddc27e228b66335cd72555b44fa8839ef78e56c51682c5', 'darwin', 'arm64'), Dependency( 'buildtools/mac/nodejs.tgz', - 'https://storage.googleapis.com/chromium-nodejs/16.13.0/16dfd094763b71988933a31735f9dea966f9abd6', - '065ced7e93f0e26276cb74d9688706674323865c4a8fc89e4adfa6868d4ebb4d', + 'https://storage.googleapis.com/chromium-nodejs/20.11.0/e3c0fd53caae857309815f3f8de7c2dce49d7bca', + '20affacca2480c368b75a1d91ec1a2720604b325207ef0cf39cfef3c235dad19', 'darwin', 'x64'), Dependency( 'buildtools/linux64/nodejs.tgz', - 'https://storage.googleapis.com/chromium-nodejs/16.13.0/ab9544e24e752d3d17f335fb7b2055062e582d11', - '3b5ca150a55f3aadfa18f3a10a4495aaf9653614ba3e460647170fef5287ec4f', + 'https://storage.googleapis.com/chromium-nodejs/20.11.0/f9a337cfa0e2b92d3e5c671c26b454bd8e99769e', + '0ba9cc91698c1f833a1fdc1fe0cb392d825ad484c71b0d84388ac80bfd3d6079', 'linux', 'x64'), Dependency( 'buildtools/mac/emsdk.tgz', @@ -425,7 +430,7 @@ GRPC_DEPS = [ Dependency( 'buildtools/grpc/src', 'https://chromium.googlesource.com/external/github.com/grpc/grpc.git', - '6943c1841f57cac4666b165aea4f618fe73b3ff1', 'all', 'all', True), + '4795c5e69b25e8c767b498bea784da0ef8c96fd5', 'all', 'all', True), ] # Sysroots required to cross-compile Linux targets (linux-arm{,64}). diff --git a/tools/install_test_reporter_app b/tools/install_test_reporter_app index 3a2d046089..382f395db1 100755 --- a/tools/install_test_reporter_app +++ b/tools/install_test_reporter_app @@ -72,11 +72,9 @@ The intended usage is: import hashlib import os import platform +import random import subprocess import sys -import threading - -DOWNLOAD_LOCK = threading.Lock() def download_or_get_cached(file_name, url, sha256): @@ -93,36 +91,30 @@ def download_or_get_cached(file_name, url, sha256): sha256_path = os.path.join(dir, file_name + '.sha256') needs_download = True - 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() + # 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: # The file doesn't exist or the SHA256 doesn't match. + # Use a unique random file to guard against concurrent executions. + # See https://github.com/google/perfetto/issues/786 . + tmp_path = '%s.%d.tmp' % (bin_path, random.randint(0, 100000)) + 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(tmp_path, 'w') as f: + f.write(sha256) + os.replace(tmp_path, sha256_path) return bin_path diff --git a/tools/pnpm b/tools/pnpm index 34fce7f14c..3c4d30ef24 100755 --- a/tools/pnpm +++ b/tools/pnpm @@ -16,7 +16,10 @@ import os import sys -ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +TOOLS_DIR = os.path.dirname(os.path.abspath(__file__)) +ROOT_DIR = os.path.dirname(TOOLS_DIR) +# Add the /tools dir to the front of the PATH so our hermetic node is used. +os.environ['PATH'] = TOOLS_DIR + os.pathsep + os.environ['PATH'] sys.path.append(ROOT_DIR) __package__ = 'tools' from .run_buildtools_binary import run_buildtools_binary diff --git a/tools/record_android_trace b/tools/record_android_trace index 13eabca133..471fbe5fd0 100755 --- a/tools/record_android_trace +++ b/tools/record_android_trace @@ -33,18 +33,18 @@ import webbrowser # ----- Amalgamator: begin of python/perfetto/prebuilts/manifests/tracebox.py -# This file has been generated by: tools/roll-prebuilts v45.0 +# This file has been generated by: tools/roll-prebuilts v46.0 TRACEBOX_MANIFEST = [{ 'arch': 'mac-amd64', 'file_name': 'tracebox', 'file_size': - 1581024, + 1597432, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/mac-amd64/tracebox', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/mac-amd64/tracebox', 'sha256': - '79661ca8986fb81a1ed8b5759b5b5259d581ec3324c9e9ff7ccd0d7d06b7dd80', + 'fda9aa1a57fc6bd85a7f332a436ae0ba8629eac81f5fd0e21a72fe3673b2d609', 'platform': 'darwin', 'machine': ['x86_64'] @@ -54,11 +54,11 @@ TRACEBOX_MANIFEST = [{ 'file_name': 'tracebox', 'file_size': - 1459096, + 1459128, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/mac-arm64/tracebox', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/mac-arm64/tracebox', 'sha256': - '232709e7fd1afd745543880edc721eed3817c2ca61dcfbd5083a812082e651a2', + '9a7ee198c0b2ca41edd73afc313193e6f643aaa1a88cafb1e515b76d6dcc47dd', 'platform': 'darwin', 'machine': ['arm64'] @@ -68,11 +68,11 @@ TRACEBOX_MANIFEST = [{ 'file_name': 'tracebox', 'file_size': - 2308416, + 2333576, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/linux-amd64/tracebox', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/linux-amd64/tracebox', 'sha256': - '4e07b5cf49e61d3c8c323eaa1c3b4b6da93f1c57c8470cad6fb64536c46cb0a5', + '3567682e999c9bc36c9c757fe1fc56067963e226573377da21747b7686238012', 'platform': 'linux', 'machine': ['x86_64'] @@ -82,11 +82,11 @@ TRACEBOX_MANIFEST = [{ 'file_name': 'tracebox', 'file_size': - 1411304, + 1422204, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/linux-arm/tracebox', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/linux-arm/tracebox', 'sha256': - '5b5b2bed6c7893c2ff868ced6f5de939f860a807bc45cf3705a78e1cacc9ea0b', + '66890d26ab8f88241b3608ce099e45dee7179ddeca3966dab6e7c1ade78963e3', 'platform': 'linux', 'machine': ['armv6l', 'armv7l', 'armv8l'] @@ -96,11 +96,11 @@ TRACEBOX_MANIFEST = [{ 'file_name': 'tracebox', 'file_size': - 2215800, + 2229176, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/linux-arm64/tracebox', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/linux-arm64/tracebox', 'sha256': - 'd86b27f9359862ae38cb9964938d87fc189c2f61415a5d8136ab00d987fc736c', + '425d7a8e88054b5b314bea3db884722a6646d9d7e8230b8ebebc044e57207739', 'platform': 'linux', 'machine': ['aarch64'] @@ -110,44 +110,44 @@ TRACEBOX_MANIFEST = [{ 'file_name': 'tracebox', 'file_size': - 1301920, + 1314720, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/android-arm/tracebox', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/android-arm/tracebox', 'sha256': - 'd8221b091a5f0b41168aa922cc12ea9cda9e3b85183086d84eecfc5b904fe494' + 'e3a6905bf8db5af2bd2b83512a745cfd3d86cf842fc81b1d1b3a05f4fb9f15d0' }, { 'arch': 'android-arm64', 'file_name': 'tracebox', 'file_size': - 2071440, + 2086288, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/android-arm64/tracebox', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/android-arm64/tracebox', 'sha256': - '34de36eb41091d72c5bc6dcba6886f57f7d7d3a6a1d10d38c9516f2517ff2437' + 'b506b076e19470afa4ca3f625d596f894a3778b3fe2fd7ad9f97f9e136f25542' }, { 'arch': 'android-x86', 'file_name': 'tracebox', 'file_size': - 2245480, + 2264088, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/android-x86/tracebox', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/android-x86/tracebox', 'sha256': - '5b4bc326f6dcf3b6a0ea970a6ba8da88f053f93f666ca20e15f1d8a35ac7b3fb' + '953cb01053d8094a5e39e05acc2f5b81a73836e699e4e0a7469c0cfa7c820364' }, { 'arch': 'android-x64', 'file_name': 'tracebox', 'file_size': - 2097064, + 2114328, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/android-x64/tracebox', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/android-x64/tracebox', 'sha256': - '444d0da4e68b4440f3be114152f8942d29c938dacc1cdf1a497d59d37cd2a203' + '570d475684bcc93ae8b6b8a5566887337d94aff91dea2270a0feb1c0bec00a8b' }] # ----- Amalgamator: end of python/perfetto/prebuilts/manifests/tracebox.py @@ -199,11 +199,9 @@ The intended usage is: import hashlib import os import platform +import random import subprocess import sys -import threading - -DOWNLOAD_LOCK = threading.Lock() def download_or_get_cached(file_name, url, sha256): @@ -220,36 +218,30 @@ def download_or_get_cached(file_name, url, sha256): sha256_path = os.path.join(dir, file_name + '.sha256') needs_download = True - 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() + # 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: # The file doesn't exist or the SHA256 doesn't match. + # Use a unique random file to guard against concurrent executions. + # See https://github.com/google/perfetto/issues/786 . + tmp_path = '%s.%d.tmp' % (bin_path, random.randint(0, 100000)) + 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(tmp_path, 'w') as f: + f.write(sha256) + os.replace(tmp_path, sha256_path) return bin_path @@ -378,7 +370,7 @@ class HttpHandler(http.server.SimpleHTTPRequestHandler): self.send_error(404, "File not found") -def main(): +def setup_arguments(): atexit.register(kill_all_subprocs_on_exit) default_out_dir_str = '~/traces/' default_out_dir = os.path.expanduser(default_out_dir_str) @@ -507,6 +499,10 @@ def main(): parser.print_help() sys.exit(1) + return args + + +def start_trace(args, print_log=True): perfetto_cmd = 'perfetto' device_dir = '/data/misc/perfetto-traces/' @@ -608,7 +604,8 @@ def main(): shutil.os.makedirs(host_dir) with open(on_host_config or os.devnull, 'rb') as f: - print('Running ' + ' '.join(cmd)) + if print_log: + print('Running ' + ' '.join(cmd)) proc = adb('shell', *cmd, stdin=f, stdout=subprocess.PIPE) proc_out = proc.communicate()[0].decode().strip() if on_device_config is not None: @@ -631,8 +628,11 @@ def main(): sys.exit(1) prt('Trace started. Press CTRL+C to stop', ANSI.BLACK + ANSI.BG_BLUE) - logcat = adb('logcat', '-v', 'brief', '-s', 'perfetto', '-b', 'main', '-T', - '1') + log_level = "-v" + if not print_log: + log_level = "-e" + logcat = adb('logcat', log_level, 'brief', '-s', 'perfetto', '-b', 'main', + '-T', '1') ctrl_c_count = 0 adb_failure_count = 0 @@ -666,14 +666,16 @@ def main(): except KeyboardInterrupt: sig = 'TERM' if ctrl_c_count == 0 else 'KILL' ctrl_c_count += 1 - prt('Stopping the trace (SIG%s)' % sig, ANSI.BLACK + ANSI.BG_YELLOW) + if print_log: + prt('Stopping the trace (SIG%s)' % sig, ANSI.BLACK + ANSI.BG_YELLOW) adb('shell', 'kill -%s %s' % (sig, bg_pid)).wait() logcat.kill() logcat.wait() if args.reporter_api: - prt('Waiting a few seconds to allow reporter to copy trace') + if print_log: + prt('Waiting a few seconds to allow reporter to copy trace') time.sleep(5) ret = adb( @@ -684,17 +686,26 @@ def main(): prt('Failed to extract reporter trace', ANSI.RED) sys.exit(1) - prt('\n') - prt('Pulling into %s' % host_file, ANSI.BOLD) + if print_log: + prt('\n') + prt('Pulling into %s' % host_file, ANSI.BOLD) adb('pull', device_file, host_file).wait() adb('shell', 'rm -f ' + device_file).wait() if not args.no_open: - prt('\n') - prt('Opening the trace (%s) in the browser' % host_file) + if print_log: + prt('\n') + prt('Opening the trace (%s) in the browser' % host_file) open_browser = not args.no_open_browser open_trace_in_browser(host_file, open_browser, args.origin) + return host_file + + +def main(): + args = setup_arguments() + start_trace(args) + def prt(msg, colors=ANSI.END): print(colors + msg + ANSI.END) @@ -742,14 +753,15 @@ def open_trace_in_browser(path, open_browser, origin): httpd.handle_request() -def adb(*args, stdin=devnull, stdout=None): +def adb(*args, stdin=devnull, stdout=None, stderr=None): cmd = [adb_path, *args] setpgrp = None if os.name != 'nt': # On Linux/Mac, start a new process group so all child processes are killed # on exit. Unsupported on Windows. setpgrp = lambda: os.setpgrp() - proc = subprocess.Popen(cmd, stdin=stdin, stdout=stdout, preexec_fn=setpgrp) + proc = subprocess.Popen( + cmd, stdin=stdin, stdout=stdout, stderr=stderr, preexec_fn=setpgrp) procs.append(proc) return proc diff --git a/tools/run_buildtools_binary.py b/tools/run_buildtools_binary.py index 1ce25a748f..9b9cb15144 100644 --- a/tools/run_buildtools_binary.py +++ b/tools/run_buildtools_binary.py @@ -20,7 +20,7 @@ import subprocess import sys -from platform import system, machine +from platform import system ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) @@ -56,10 +56,10 @@ def run_buildtools_binary(args): if sys_name == 'windows': # execl() behaves oddly on Windows: the spawned process doesn't seem to # receive CTRL+C. Use subprocess instead. - return subprocess.call([exe_path] + args) + sys.exit(subprocess.call([exe_path] + args)) else: os.execl(exe_path, os.path.basename(exe_path), *args) if __name__ == '__main__': - sys.exit(run_buildtools_binary(sys.argv[1:])) + run_buildtools_binary(sys.argv[1:]) diff --git a/tools/serialize_test_trace.py b/tools/serialize_test_trace.py index f207101430..f6b0733c0a 100755 --- a/tools/serialize_test_trace.py +++ b/tools/serialize_test_trace.py @@ -45,11 +45,11 @@ def main(): trace_descriptor_path = os.path.join(trace_protos_path, 'trace.descriptor') test_extensions_descriptor_path = os.path.join( trace_protos_path, 'test_extensions.descriptor') - winscope_extensions_descriptor_path = os.path.join( - trace_protos_path, 'android', 'winscope.descriptor') + winscope_extensions_descriptor_path = os.path.join(trace_protos_path, + 'android', + 'winscope.descriptor') extension_descriptors = [ - chrome_extension_descriptor_path, - test_extensions_descriptor_path, + chrome_extension_descriptor_path, test_extensions_descriptor_path, winscope_extensions_descriptor_path ] elif args.descriptor and not args.out: diff --git a/tools/setup_minikube_cluster.sh b/tools/setup_minikube_cluster.sh new file mode 100644 index 0000000000..c2549c79ec --- /dev/null +++ b/tools/setup_minikube_cluster.sh @@ -0,0 +1,29 @@ +#!/bin/bash +# 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. + +cd $PROJECT_ROOT/src/bigtrace + +minikube start +eval $(minikube docker-env) + +docker build -t orchestrator_image ./orchestrator +docker build -t worker_image ./worker + +minikube kubectl -- apply -f worker-deployment.yaml +minikube kubectl -- apply -f worker-service.yaml +minikube kubectl -- apply -f orchestrator-deployment.yaml +minikube kubectl -- apply -f orchestrator-service.yaml + +eval $(minikube docker-env -u) \ No newline at end of file diff --git a/tools/ssh_into_gce_vm b/tools/ssh_into_gce_vm new file mode 100644 index 0000000000..008f8b652e --- /dev/null +++ b/tools/ssh_into_gce_vm @@ -0,0 +1,88 @@ +#!/usr/bin/env python3 +# Copyright (C) 2024 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the 'License'); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an 'AS IS' BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import argparse +import os +import subprocess +import sys + + +def list_instances(project_id): + try: + result = subprocess.run([ + 'gcloud', 'compute', 'instances', 'list', '--project', project_id, + '--format', 'table(name,zone)' + ], + check=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True) + lines = result.stdout.strip().split('\n') + instances = [tuple(line.split()) for line in lines[1:]] # Skip the header + return instances + except subprocess.CalledProcessError as e: + print(f'Error retrieving instances: {e.stderr}') + sys.exit(1) + + +def main(): + DEFAULT_PROJECT_ID = 'perfetto-ci' + # project_id = os.getenv('CLOUDSDK_CORE_PROJECT', DEFAULT_PROJECT_ID) + + parser = argparse.ArgumentParser() + parser.add_argument( + '-p', + '--project-id', + metavar='PROJECT_ID', + required=False, + help='The Cloud project id. Defaults to CLOUDSDK_CORE_PROJECT', + default=os.getenv('CLOUDSDK_CORE_PROJECT', DEFAULT_PROJECT_ID)) + args = parser.parse_args() + project_id = args.project_id + + print('Using Cloud project: %s' % project_id) + print('If this script fail ensure that:') + print(' - The cloud project has been configured as per go/gce-beyondcorp-ssh') + print(' - Register your key as per "Ensure that you are registered with OS') + + instances = list_instances(project_id) + if not instances: + print('No GCE instances found.') + sys.exit(0) + + print('Available VMs:') + for idx, (name, zone) in enumerate(instances, start=1): + print(f'{idx}. {name} ({zone})') + + try: + vm_number = int(input('Enter the number of the VM you want to ssh into: ')) + if vm_number < 1 or vm_number > len(instances): + raise ValueError + except ValueError: + print('Invalid selection. Please run the script again.') + sys.exit(1) + + # Get the selected VM's name and zone + selected_instance = instances[vm_number - 1] + vm_name, vm_zone = selected_instance + user = os.getenv('USER', 'username') + ssh_arg = '%s_google_com@nic0.%s.%s.c.%s.internal.gcpnode.com' % ( + user, vm_name, vm_zone, project_id) + print('ssh ' + ssh_arg) + os.execvp('ssh', ['ssh', ssh_arg]) + + +if __name__ == '__main__': + main() diff --git a/tools/trace_processor b/tools/trace_processor index 06e26a547d..0ee1e788d2 100755 --- a/tools/trace_processor +++ b/tools/trace_processor @@ -30,18 +30,18 @@ exec python3 - "$@" <<'#'EOF # ----- Amalgamator: begin of python/perfetto/prebuilts/manifests/trace_processor_shell.py -# This file has been generated by: tools/roll-prebuilts v45.0 +# This file has been generated by: tools/roll-prebuilts v46.0 TRACE_PROCESSOR_SHELL_MANIFEST = [{ 'arch': 'mac-amd64', 'file_name': 'trace_processor_shell', 'file_size': - 9092856, + 9503800, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/mac-amd64/trace_processor_shell', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/mac-amd64/trace_processor_shell', 'sha256': - '13b45c29743b296221ebdda630913a8630f66c1848b6d894ba489545c8098575', + 'c979a616c2aeada9ff5e0807f15089c7415de7e1f19670945792be5953c05f87', 'platform': 'darwin', 'machine': ['x86_64'] @@ -51,11 +51,11 @@ TRACE_PROCESSOR_SHELL_MANIFEST = [{ 'file_name': 'trace_processor_shell', 'file_size': - 8476632, + 8873912, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/mac-arm64/trace_processor_shell', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/mac-arm64/trace_processor_shell', 'sha256': - 'aefb6a85618716b172c6d81cf7d35b0f71e6e67f6a3601685548a378bb9278f0', + '8f88e9ec002fb0e95bcfdd655e1a9632487010876a234bc545c85c44d0019294', 'platform': 'darwin', 'machine': ['arm64'] @@ -65,11 +65,11 @@ TRACE_PROCESSOR_SHELL_MANIFEST = [{ 'file_name': 'trace_processor_shell', 'file_size': - 9253528, + 9675688, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/linux-amd64/trace_processor_shell', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/linux-amd64/trace_processor_shell', 'sha256': - 'e8b2e9507ec80145121014c9f3e50c1e0e5a896e8d925dbe7b95f69b1d1e9214', + '70adf26bc45d6de8327b1e46e98f64d4fe8ccf9fd31a1391bb0916c31aaa9df8', 'platform': 'linux', 'machine': ['x86_64'] @@ -79,11 +79,11 @@ TRACE_PROCESSOR_SHELL_MANIFEST = [{ 'file_name': 'trace_processor_shell', 'file_size': - 6781500, + 7098492, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/linux-arm/trace_processor_shell', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/linux-arm/trace_processor_shell', 'sha256': - '9ecd4f09f22270aff1926c80518b62425466c57c95ce94f912cfe3b325d1d0a9', + 'b9d5abb196a99d1ade08db3d97bff3c71e8ee4ccd6fedb6332effa4d5d50c21d', 'platform': 'linux', 'machine': ['armv6l', 'armv7l', 'armv8l'] @@ -93,11 +93,11 @@ TRACE_PROCESSOR_SHELL_MANIFEST = [{ 'file_name': 'trace_processor_shell', 'file_size': - 8912968, + 9297416, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/linux-arm64/trace_processor_shell', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/linux-arm64/trace_processor_shell', 'sha256': - 'd3165c02bf665fe7513acb7de3b0186a91610328cf01afa5cfce437fdf589e36', + 'b0495e1ba864f3a315a31353e02f25fe8bcfc993ec6cdae1545f12f33f008c8c', 'platform': 'linux', 'machine': ['aarch64'] @@ -107,55 +107,55 @@ TRACE_PROCESSOR_SHELL_MANIFEST = [{ 'file_name': 'trace_processor_shell', 'file_size': - 6786364, + 7111084, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/android-arm/trace_processor_shell', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/android-arm/trace_processor_shell', 'sha256': - 'f25fdc4021c34bd20248359cdfe974c84413b877d7c55886eb37310028b950d3' + 'd8aa4b6460aca2e166755952fa14010d1f80ddb6b6dc406ca2b5246f9885b153' }, { 'arch': 'android-arm64', 'file_name': 'trace_processor_shell', 'file_size': - 8809584, + 9200536, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/android-arm64/trace_processor_shell', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/android-arm64/trace_processor_shell', 'sha256': - '458501bf14813679333bc40e1bf6275d54bbba20b4ae18dbc7aa212d7d3ef3ec' + '1c1843ec2e127b5f7aa8eb9e8d2c216e4cc99f681ecf437ff09f7abc56191cef' }, { 'arch': 'android-x86', 'file_name': 'trace_processor_shell', 'file_size': - 9682456, + 10094664, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/android-x86/trace_processor_shell', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/android-x86/trace_processor_shell', 'sha256': - '3ff5752895dc89bf245e8541dda809b7de27cebb8854835041f00fc549e685c1' + '4501c9aa2eaaec62155aac914a35b3c61e9d434e765b20fcefa49b7d01f978f0' }, { 'arch': 'android-x64', 'file_name': 'trace_processor_shell', 'file_size': - 9070288, + 9464288, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/android-x64/trace_processor_shell', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/android-x64/trace_processor_shell', 'sha256': - '2f431c7bc515d7d0d5fb49fdead6628c7133e0b03b2b2397a874009a80739ba0' + 'fb54ee018ca1d4895ad3e1192e2e585a3709b58864ef3beab4368af3ae17e5fb' }, { 'arch': 'windows-amd64', 'file_name': 'trace_processor_shell.exe', 'file_size': - 9165312, + 9548288, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/windows-amd64/trace_processor_shell.exe', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/windows-amd64/trace_processor_shell.exe', 'sha256': - '3a5432bebe30520ddceeafd2f77b2d44608e5942c06d1d50f908c5921375944a', + '41a3353caf59d4df14e7d5ba185861fdc8e41776c1720a6fc1eddc6a99afb6c0', 'platform': 'win32', 'machine': ['amd64'] @@ -210,11 +210,9 @@ The intended usage is: import hashlib import os import platform +import random import subprocess import sys -import threading - -DOWNLOAD_LOCK = threading.Lock() def download_or_get_cached(file_name, url, sha256): @@ -231,36 +229,30 @@ def download_or_get_cached(file_name, url, sha256): sha256_path = os.path.join(dir, file_name + '.sha256') needs_download = True - 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 + # 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() + if needs_download: # The file doesn't exist or the SHA256 doesn't match. + # Use a unique random file to guard against concurrent executions. + # See https://github.com/google/perfetto/issues/786 . + tmp_path = '%s.%d.tmp' % (bin_path, random.randint(0, 100000)) + 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(tmp_path, 'w') as f: + f.write(sha256) + os.replace(tmp_path, sha256_path) return bin_path diff --git a/tools/tracebox b/tools/tracebox index fb7b4c56c3..28323b8861 100755 --- a/tools/tracebox +++ b/tools/tracebox @@ -30,18 +30,18 @@ exec python3 - "$@" <<'#'EOF # ----- Amalgamator: begin of python/perfetto/prebuilts/manifests/tracebox.py -# This file has been generated by: tools/roll-prebuilts v45.0 +# This file has been generated by: tools/roll-prebuilts v46.0 TRACEBOX_MANIFEST = [{ 'arch': 'mac-amd64', 'file_name': 'tracebox', 'file_size': - 1581024, + 1597432, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/mac-amd64/tracebox', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/mac-amd64/tracebox', 'sha256': - '79661ca8986fb81a1ed8b5759b5b5259d581ec3324c9e9ff7ccd0d7d06b7dd80', + 'fda9aa1a57fc6bd85a7f332a436ae0ba8629eac81f5fd0e21a72fe3673b2d609', 'platform': 'darwin', 'machine': ['x86_64'] @@ -51,11 +51,11 @@ TRACEBOX_MANIFEST = [{ 'file_name': 'tracebox', 'file_size': - 1459096, + 1459128, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/mac-arm64/tracebox', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/mac-arm64/tracebox', 'sha256': - '232709e7fd1afd745543880edc721eed3817c2ca61dcfbd5083a812082e651a2', + '9a7ee198c0b2ca41edd73afc313193e6f643aaa1a88cafb1e515b76d6dcc47dd', 'platform': 'darwin', 'machine': ['arm64'] @@ -65,11 +65,11 @@ TRACEBOX_MANIFEST = [{ 'file_name': 'tracebox', 'file_size': - 2308416, + 2333576, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/linux-amd64/tracebox', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/linux-amd64/tracebox', 'sha256': - '4e07b5cf49e61d3c8c323eaa1c3b4b6da93f1c57c8470cad6fb64536c46cb0a5', + '3567682e999c9bc36c9c757fe1fc56067963e226573377da21747b7686238012', 'platform': 'linux', 'machine': ['x86_64'] @@ -79,11 +79,11 @@ TRACEBOX_MANIFEST = [{ 'file_name': 'tracebox', 'file_size': - 1411304, + 1422204, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/linux-arm/tracebox', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/linux-arm/tracebox', 'sha256': - '5b5b2bed6c7893c2ff868ced6f5de939f860a807bc45cf3705a78e1cacc9ea0b', + '66890d26ab8f88241b3608ce099e45dee7179ddeca3966dab6e7c1ade78963e3', 'platform': 'linux', 'machine': ['armv6l', 'armv7l', 'armv8l'] @@ -93,11 +93,11 @@ TRACEBOX_MANIFEST = [{ 'file_name': 'tracebox', 'file_size': - 2215800, + 2229176, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/linux-arm64/tracebox', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/linux-arm64/tracebox', 'sha256': - 'd86b27f9359862ae38cb9964938d87fc189c2f61415a5d8136ab00d987fc736c', + '425d7a8e88054b5b314bea3db884722a6646d9d7e8230b8ebebc044e57207739', 'platform': 'linux', 'machine': ['aarch64'] @@ -107,44 +107,44 @@ TRACEBOX_MANIFEST = [{ 'file_name': 'tracebox', 'file_size': - 1301920, + 1314720, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/android-arm/tracebox', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/android-arm/tracebox', 'sha256': - 'd8221b091a5f0b41168aa922cc12ea9cda9e3b85183086d84eecfc5b904fe494' + 'e3a6905bf8db5af2bd2b83512a745cfd3d86cf842fc81b1d1b3a05f4fb9f15d0' }, { 'arch': 'android-arm64', 'file_name': 'tracebox', 'file_size': - 2071440, + 2086288, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/android-arm64/tracebox', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/android-arm64/tracebox', 'sha256': - '34de36eb41091d72c5bc6dcba6886f57f7d7d3a6a1d10d38c9516f2517ff2437' + 'b506b076e19470afa4ca3f625d596f894a3778b3fe2fd7ad9f97f9e136f25542' }, { 'arch': 'android-x86', 'file_name': 'tracebox', 'file_size': - 2245480, + 2264088, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/android-x86/tracebox', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/android-x86/tracebox', 'sha256': - '5b4bc326f6dcf3b6a0ea970a6ba8da88f053f93f666ca20e15f1d8a35ac7b3fb' + '953cb01053d8094a5e39e05acc2f5b81a73836e699e4e0a7469c0cfa7c820364' }, { 'arch': 'android-x64', 'file_name': 'tracebox', 'file_size': - 2097064, + 2114328, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/android-x64/tracebox', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/android-x64/tracebox', 'sha256': - '444d0da4e68b4440f3be114152f8942d29c938dacc1cdf1a497d59d37cd2a203' + '570d475684bcc93ae8b6b8a5566887337d94aff91dea2270a0feb1c0bec00a8b' }] # ----- Amalgamator: end of python/perfetto/prebuilts/manifests/tracebox.py @@ -196,11 +196,9 @@ The intended usage is: import hashlib import os import platform +import random import subprocess import sys -import threading - -DOWNLOAD_LOCK = threading.Lock() def download_or_get_cached(file_name, url, sha256): @@ -217,36 +215,30 @@ def download_or_get_cached(file_name, url, sha256): sha256_path = os.path.join(dir, file_name + '.sha256') needs_download = True - 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 + # 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() + if needs_download: # The file doesn't exist or the SHA256 doesn't match. + # Use a unique random file to guard against concurrent executions. + # See https://github.com/google/perfetto/issues/786 . + tmp_path = '%s.%d.tmp' % (bin_path, random.randint(0, 100000)) + 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(tmp_path, 'w') as f: + f.write(sha256) + os.replace(tmp_path, sha256_path) return bin_path diff --git a/tools/traceconv b/tools/traceconv index 6d1bbbafc5..3259389a4c 100755 --- a/tools/traceconv +++ b/tools/traceconv @@ -30,18 +30,18 @@ exec python3 - "$@" <<'#'EOF # ----- Amalgamator: begin of python/perfetto/prebuilts/manifests/traceconv.py -# This file has been generated by: tools/roll-prebuilts v45.0 +# This file has been generated by: tools/roll-prebuilts v46.0 TRACECONV_MANIFEST = [{ 'arch': 'mac-amd64', 'file_name': 'traceconv', 'file_size': - 8299680, + 8743272, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/mac-amd64/traceconv', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/mac-amd64/traceconv', 'sha256': - '80291c46aea5bf3b1007a385fc6442a99865bfdd70ea627b3e9a2e19d6d62de1', + 'e0abc72fc69d3be68f16b198253ea9a802c266f7c2609debe561976b627d5d67', 'platform': 'darwin', 'machine': ['x86_64'] @@ -51,11 +51,11 @@ TRACECONV_MANIFEST = [{ 'file_name': 'traceconv', 'file_size': - 7761304, + 8158456, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/mac-arm64/traceconv', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/mac-arm64/traceconv', 'sha256': - '53b78c7f9ee26a766b2d8f28e8033367f697d669b133383dfb116fd44f7ffa8a', + '9cd1635898536bcdcff8b944b05bf2ffc84fc0c55ba270746203a4fa0bae9f01', 'platform': 'darwin', 'machine': ['arm64'] @@ -65,11 +65,11 @@ TRACECONV_MANIFEST = [{ 'file_name': 'traceconv', 'file_size': - 8353608, + 8809008, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/linux-amd64/traceconv', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/linux-amd64/traceconv', 'sha256': - '57e1cf04dc25fa6a6f7ae3482158f2dfb363269baeab1673343974312551a753', + '63e8599c19125db25bb1d1d39b0bba6b30ea30760ebfce87d0d9508458cad8ba', 'platform': 'linux', 'machine': ['x86_64'] @@ -79,11 +79,11 @@ TRACECONV_MANIFEST = [{ 'file_name': 'traceconv', 'file_size': - 6317844, + 6665536, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/linux-arm/traceconv', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/linux-arm/traceconv', 'sha256': - 'e361944a78b55b83bc072043dd983b8f5debcf5a875b79accfcb02d864f97a85', + 'c60d071015bed4da8b9411727e5c35c8429dd4698c92533bea82bf927f1f6966', 'platform': 'linux', 'machine': ['armv6l', 'armv7l', 'armv8l'] @@ -93,11 +93,11 @@ TRACECONV_MANIFEST = [{ 'file_name': 'traceconv', 'file_size': - 8061520, + 8477496, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/linux-arm64/traceconv', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/linux-arm64/traceconv', 'sha256': - '80e8842b22e9c92b4d44f0e16d6b65b0d013d7c769e853961a0f222287898850', + 'd675922ce9900d4f50105ea8bfcd1edd7fc92ffc56c3e0e7f86bf2af41c5b3db', 'platform': 'linux', 'machine': ['aarch64'] @@ -107,55 +107,55 @@ TRACECONV_MANIFEST = [{ 'file_name': 'traceconv', 'file_size': - 6320968, + 6680736, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/android-arm/traceconv', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/android-arm/traceconv', 'sha256': - '4d17f00e48f256cc9a725db1e4ba7eddd1fc4ab96e7e67cade07338ac74eab88' + '7148b59e5328ab3d6200d52ceb7dd97fe4d82e873dfabd2da3f2f5be54e8e726' }, { 'arch': 'android-arm64', 'file_name': 'traceconv', 'file_size': - 7998792, + 8424248, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/android-arm64/traceconv', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/android-arm64/traceconv', 'sha256': - '230e91c8bfa59094d69b0dad0fb3328b9c2134a3d34653d6d4fa2a05ed2db4b2' + 'fc7830c47a662a38cee0a11b3c0e9959cffcd4b9b6260e38ef45284371f6e4df' }, { 'arch': 'android-x86', 'file_name': 'traceconv', 'file_size': - 8670248, + 9115416, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/android-x86/traceconv', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/android-x86/traceconv', 'sha256': - 'e59e7a34fd0af9cd0db84ae104bdb79cbf7efb5d71ab11f3b31f6045c1621c5b' + '4f7829506c6bcb249615b87aa40440e45824d4ce378c5f086c317e55241b9b7c' }, { 'arch': 'android-x64', 'file_name': 'traceconv', 'file_size': - 8229048, + 8657048, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/android-x64/traceconv', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/android-x64/traceconv', 'sha256': - '277eab28583824c02159b4b0611d4cb4480305da477f607a90b161c834a21e6b' + '1799a71ad7a743989456ad50b25fdfc01324d4a8f3d9eb97e0799494a3d7ed1c' }, { 'arch': 'windows-amd64', 'file_name': 'traceconv.exe', 'file_size': - 8119296, + 8527872, 'url': - 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v45.0/windows-amd64/traceconv.exe', + 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v46.0/windows-amd64/traceconv.exe', 'sha256': - '3fffac0a1d3bea94924e219a8390c96aef729cd0dfa9f216a2c20eb56ece9e86', + '9e7afe063caefe8ae5164014d0180933c66688f0af6618ed3ac1179454d2c23c', 'platform': 'win32', 'machine': ['amd64'] @@ -210,11 +210,9 @@ The intended usage is: import hashlib import os import platform +import random import subprocess import sys -import threading - -DOWNLOAD_LOCK = threading.Lock() def download_or_get_cached(file_name, url, sha256): @@ -231,36 +229,30 @@ def download_or_get_cached(file_name, url, sha256): sha256_path = os.path.join(dir, file_name + '.sha256') needs_download = True - 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 + # 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() + if needs_download: # The file doesn't exist or the SHA256 doesn't match. + # Use a unique random file to guard against concurrent executions. + # See https://github.com/google/perfetto/issues/786 . + tmp_path = '%s.%d.tmp' % (bin_path, random.randint(0, 100000)) + 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(tmp_path, 'w') as f: + f.write(sha256) + os.replace(tmp_path, sha256_path) return bin_path diff --git a/ui/.eslintignore b/ui/.eslintignore deleted file mode 100644 index ce6c8c280a..0000000000 --- a/ui/.eslintignore +++ /dev/null @@ -1,8 +0,0 @@ -node_modules -src/service_worker -src/bigtrace -src/gen -out -config -build.js -.eslintrc.js diff --git a/ui/.eslintrc.js b/ui/.eslintrc.js index db775ea8be..29392beb44 100644 --- a/ui/.eslintrc.js +++ b/ui/.eslintrc.js @@ -28,14 +28,14 @@ module.exports = { }], // Formatting handled by prettier - "indent": "off", - 'max-len': "off", - "operator-linebreak": "off", - "quotes": "off", - "brace-style": "off", - "space-before-function-paren": "off", - "generator-star-spacing": "off", - "semi-spacing": "off", + 'indent': 'off', + 'max-len': 'off', + 'operator-linebreak': 'off', + 'quotes': 'off', + 'brace-style': 'off', + 'space-before-function-paren': 'off', + 'generator-star-spacing': 'off', + 'semi-spacing': 'off', // clang-format --js used to format EOL comments after (e.g.) an if like: // if (foo) { // insightful comment diff --git a/ui/.prettierignore b/ui/.prettierignore index ce6c8c280a..d9b2fe446a 100644 --- a/ui/.prettierignore +++ b/ui/.prettierignore @@ -1,8 +1,11 @@ +# NOTE: eslint.config.js and ui/format-sources also depend on this file. node_modules src/service_worker src/bigtrace src/gen +src/test/diff_viewer out config build.js -.eslintrc.js +eslint.config.js + diff --git a/ui/PRESUBMIT.py b/ui/PRESUBMIT.py index be0ad247af..7f0c469e0a 100644 --- a/ui/PRESUBMIT.py +++ b/ui/PRESUBMIT.py @@ -17,7 +17,6 @@ import subprocess from os.path import relpath, dirname, join - USE_PYTHON3 = True @@ -25,7 +24,7 @@ def RunAndReportIfLong(func, *args, **kargs): start = time.time() results = func(*args, **kargs) end = time.time() - limit = 0.5 # seconds + limit = 3.0 # seconds name = func.__name__ runtime = end - start if runtime > limit: @@ -35,8 +34,7 @@ def RunAndReportIfLong(func, *args, **kargs): def CheckChange(input, output): results = [] - results += RunAndReportIfLong(CheckEslint, input, output) - results += RunAndReportIfLong(CheckPrettier, input, output) + results += RunAndReportIfLong(CheckPrettierAndEslint, input, output) results += RunAndReportIfLong(CheckImports, input, output) results += RunAndReportIfLong(CheckAnyRachet, input, output) return results @@ -50,67 +48,16 @@ def CheckChangeOnCommit(input_api, output_api): return CheckChange(input_api, output_api) -def CheckEslint(input_api, output_api): - path = input_api.os_path +def CheckPrettierAndEslint(input_api, output_api): ui_path = input_api.PresubmitLocalPath() - module_path = path.join(ui_path, 'node_modules', '.bin', 'eslint') - lint_path = path.join(ui_path, 'eslint') - - if not path.exists(module_path): - repo_root = input_api.change.RepositoryRoot() - install_path = path.join(repo_root, 'tools', 'install-build-deps') - return [ - output_api.PresubmitError( - f"eslint not found. Please first run\n $ {install_path} --ui") - ] - - def file_filter(x): - return input_api.FilterSourceFile( - x, - files_to_check=[r'.*\.ts$', r'.*\.js$'], - files_to_skip=[r'.*\.eslintrc\.js']) - - files = input_api.AffectedSourceFiles(file_filter) - - if not files: - return [] - paths = [f.AbsoluteLocalPath() for f in files] - - cmd = [lint_path] + paths + format_sources_path = join(ui_path, 'format-sources') + cmd = [format_sources_path, '--check-only'] if subprocess.call(cmd): s = ' '.join(cmd) - return [output_api.PresubmitError(f"eslint errors. Run: $ {s}")] - return [] - - -def CheckPrettier(input_api, output_api): - path = input_api.os_path - ui_path = input_api.PresubmitLocalPath() - module_path = path.join(ui_path, 'node_modules', '.bin', 'prettier') - prettier_path = path.join(ui_path, 'prettier') - - if not path.exists(module_path): - repo_root = input_api.change.RepositoryRoot() - install_path = path.join(repo_root, 'tools', 'install-build-deps') return [ - output_api.PresubmitError( - f"prettier not found. Please first run\n $ {install_path} --ui") + output_api.PresubmitError(f"""Prettier/Eslint errors. To fix, run: +{format_sources_path}""") ] - - def file_filter(x): - return input_api.FilterSourceFile( - x, files_to_check=[r'.*\.ts$', r'.*\.js$', r'.*\.scss$']) - - files = input_api.AffectedSourceFiles(file_filter) - - if not files: - return [] - paths = [f.AbsoluteLocalPath() for f in files] - - cmd = [prettier_path, '--check'] + paths - if subprocess.call(cmd): - s = ' '.join(cmd) - return [output_api.PresubmitError(f"prettier errors. Run: $ {s}")] return [] diff --git a/ui/build.js b/ui/build.js index 56745df410..2df02c3833 100644 --- a/ui/build.js +++ b/ui/build.js @@ -26,11 +26,11 @@ // and the rollup bundler in --watch mode. Any other attempt, leads to O(10s) // incremental-build times. // This script allows mixing build tools that support --watch mode (tsc and -// rollup) and auto-triggering-on-file-change rules via node-watch. +// rollup) and auto-triggering-on-file-change rules via fs.watch. // When invoked without any argument (e.g., for production builds), this script // just runs all the build tasks serially. It doesn't to do any mtime-based // check, it always re-runs all the tasks. -// When invoked with --watch, it mounts a pipeline of tasks based on node-watch +// When invoked with --watch, it mounts a pipeline of tasks based on fs.watch // and runs them together with tsc --watch and rollup --watch. // The output directory structure is carefully crafted so that any change to UI // sources causes cascading triggers of the next steps. @@ -71,7 +71,6 @@ const crypto = require('crypto'); const fs = require('fs'); const http = require('http'); const path = require('path'); -const fswatch = require('node-watch'); // Like fs.watch(), but works on Linux. const pjoin = path.join; const ROOT_DIR = path.dirname(__dirname); // The repo root. @@ -79,6 +78,7 @@ const VERSION_SCRIPT = pjoin(ROOT_DIR, 'tools/write_version_header.py'); const GEN_IMPORTS_SCRIPT = pjoin(ROOT_DIR, 'tools/gen_ui_imports'); const cfg = { + minifyJs: '', watch: false, verbose: false, debug: false, @@ -127,7 +127,7 @@ const RULES = [ f: copyUiTestArtifactsAssets, }, {r: /.*\/dist\/.+\/(?!manifest\.json).*/, f: genServiceWorkerManifestJson}, - {r: /.*\/dist\/.*/, f: notifyLiveServer}, + {r: /.*\/dist\/.*[.](js|html|css|wasm)$/, f: notifyLiveServer}, ]; const tasks = []; @@ -140,6 +140,10 @@ const subprocesses = []; async function main() { const parser = new argparse.ArgumentParser(); parser.add_argument('--out', {help: 'Output directory'}); + parser.add_argument('--minify-js', { + help: 'Minify js files', + choices: ['preserve_comments', 'all'], + }); parser.add_argument('--watch', '-w', {action: 'store_true'}); parser.add_argument('--serve', '-s', {action: 'store_true'}); parser.add_argument('--serve-host', {help: '--serve bind host'}); @@ -181,6 +185,9 @@ async function main() { cfg.openPerfettoTrace = !!args.open_perfetto_trace; cfg.startHttpServer = args.serve; cfg.noOverrideGnArgs = !!args.no_override_gn_args; + if (args.minify_js) { + cfg.minifyJs = args.minify_js; + } if (args.bigtrace) { cfg.outBigtraceDistDir = ensureDir(pjoin(cfg.outDistDir, 'bigtrace')); } @@ -402,20 +409,10 @@ function compileScss() { function compileProtos() { const dstJs = pjoin(cfg.outGenDir, 'protos.js'); const dstTs = pjoin(cfg.outGenDir, 'protos.d.ts'); - // We've ended up pulling in all the protos (via trace.proto, - // trace_packet.proto) below which means |dstJs| ends up being - // 23k lines/12mb. We should probably not do that. - // TODO(hjd): Figure out how to use lazy with pbjs/pbts. const inputs = [ - 'protos/perfetto/common/trace_stats.proto', - 'protos/perfetto/common/tracing_service_capabilities.proto', - 'protos/perfetto/config/perfetto_config.proto', 'protos/perfetto/ipc/consumer_port.proto', 'protos/perfetto/ipc/wire_protocol.proto', - 'protos/perfetto/metrics/metrics.proto', 'protos/perfetto/trace/perfetto/perfetto_metatrace.proto', - 'protos/perfetto/trace/trace.proto', - 'protos/perfetto/trace/trace_packet.proto', 'protos/perfetto/trace_processor/trace_processor.proto', ]; // Can't put --no-comments here - The comments are load bearing for @@ -544,6 +541,9 @@ function bundleJs(cfgName) { if (cfg.openPerfettoTrace) { args.push('--environment', 'ENABLE_OPEN_PERFETTO_TRACE:true'); } + if (cfg.minifyJs) { + args.push('--environment', `MINIFY_JS:${cfg.minifyJs}`); + } args.push(...(cfg.verbose ? [] : ['--silent'])); if (cfg.watch) { // --waitForBundleInput is sadly quite busted so it is required ts @@ -740,7 +740,8 @@ function scanDir(dir, regex) { const absDir = path.isAbsolute(dir) ? dir : pjoin(ROOT_DIR, dir); // Add a fs watch if in watch mode. if (cfg.watch) { - fswatch(absDir, {recursive: true}, (_eventType, filePath) => { + fs.watch(absDir, {recursive: true}, (_eventType, relFilePath) => { + const filePath = pjoin(absDir, relFilePath); if (!filterFn(filePath)) return; if (cfg.verbose) { console.log('File change detected', _eventType, filePath); diff --git a/ui/config/JestJsdomEnv.js b/ui/config/JestJsdomEnv.js new file mode 100644 index 0000000000..e7425f885e --- /dev/null +++ b/ui/config/JestJsdomEnv.js @@ -0,0 +1,30 @@ +// 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. + +const JSDOMEnvironment = require('jest-environment-jsdom').default; + +module.exports = class JestJsdomEnv extends JSDOMEnvironment { + constructor(...args) { + super(...args); + + // vega-lite, which is pulled in by tests, depends on structuredClone. + // The jsdom envinronment doesn't emulate it yet. So here we create a wrapper + // around it that fills the gap. + // See https://github.com/jsdom/jsdom/issues/3363 . + this.global.structuredClone = structuredClone; + + this.global.TextDecoder = TextDecoder; + this.global.TextEncoder = TextEncoder; + } +}; diff --git a/ui/config/integrationtest_env.js b/ui/config/integrationtest_env.js index 830025ec85..01082782fe 100644 --- a/ui/config/integrationtest_env.js +++ b/ui/config/integrationtest_env.js @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -const NodeEnvironment = require('jest-environment-node'); +const NodeEnvironment = require('jest-environment-node').default; const puppeteer = require('puppeteer'); module.exports = class IntegrationtestEnvironment extends NodeEnvironment { diff --git a/ui/config/jest.unittest.config.js b/ui/config/jest.unittest.config.js index e8efad3eed..75878be623 100644 --- a/ui/config/jest.unittest.config.js +++ b/ui/config/jest.unittest.config.js @@ -15,5 +15,9 @@ module.exports = { transform: {}, testRegex: '_(unittest|jsdomtest)[.]js$', - testEnvironment: 'jsdom', + testEnvironment: __dirname + '/JestJsdomEnv.js', + setupFiles: [ + 'jest-canvas-mock', + 'jest-localstorage-mock', + ], }; diff --git a/ui/config/rollup.config.js b/ui/config/rollup.config.js index 144f5d9faa..daec62eb3c 100644 --- a/ui/config/rollup.config.js +++ b/ui/config/rollup.config.js @@ -12,13 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -import commonjs from '@rollup/plugin-commonjs'; -import nodeResolve from '@rollup/plugin-node-resolve'; -import replace from 'rollup-plugin-re'; -import sourcemaps from 'rollup-plugin-sourcemaps'; +const {uglify} = require('rollup-plugin-uglify') +const commonjs = require('@rollup/plugin-commonjs'); +const nodeResolve = require('@rollup/plugin-node-resolve'); const path = require('path'); -const ROOT_DIR = path.dirname(path.dirname(__dirname)); // The repo root. +const replace = require('rollup-plugin-re'); +const sourcemaps = require('rollup-plugin-sourcemaps'); + +const ROOT_DIR = path.dirname(path.dirname(__dirname)); // The repo root. const OUT_SYMLINK = path.join(ROOT_DIR, 'ui/out'); function defBundle(tsRoot, bundle, distDir) { @@ -51,17 +53,19 @@ function defBundle(tsRoot, bundle, distDir) { // Immer entry point has a if (process.env.NODE_ENV === 'production') // but |process| is not defined in the browser. Bypass. // https://github.com/immerjs/immer/issues/557 - {test: /process\.env\.NODE_ENV/g, replace: '\'production\''}, + {test: /process\.env\.NODE_ENV/g, replace: "'production'"}, ], }), // Translate source maps to point back to the .ts sources. sourcemaps(), - ], - onwarn: function(warning, warn) { + ].concat(maybeUglify()), + onwarn: function (warning, warn) { // Ignore circular dependency warnings coming from third party code. - if (warning.code === 'CIRCULAR_DEPENDENCY' && - warning.importer.includes('node_modules')) { + if ( + warning.code === 'CIRCULAR_DEPENDENCY' && + warning.message.includes('node_modules') + ) { return; } @@ -93,19 +97,28 @@ function defServiceWorkerBundle() { }; } -const maybeBigtrace = process.env['ENABLE_BIGTRACE'] ? - [defBundle('tsc/bigtrace', 'bigtrace', 'dist_version/bigtrace')] : - []; +function maybeUglify() { + const minifyEnv = process.env['MINIFY_JS']; + if (!minifyEnv) return []; + const opts = + minifyEnv === 'preserve_comments' ? {output: {comments: 'all'}} : undefined; + return [uglify(opts)]; +} -const maybeOpenPerfettoTrace = process.env['ENABLE_OPEN_PERFETTO_TRACE'] ? - [defBundle('tsc', 'open_perfetto_trace', 'dist/open_perfetto_trace')] : - []; +const maybeBigtrace = process.env['ENABLE_BIGTRACE'] + ? [defBundle('tsc/bigtrace', 'bigtrace', 'dist_version/bigtrace')] + : []; +const maybeOpenPerfettoTrace = process.env['ENABLE_OPEN_PERFETTO_TRACE'] + ? [defBundle('tsc', 'open_perfetto_trace', 'dist/open_perfetto_trace')] + : []; -export default [ +module.exports = [ defBundle('tsc', 'frontend', 'dist_version'), defBundle('tsc', 'engine', 'dist_version'), defBundle('tsc', 'traceconv', 'dist_version'), defBundle('tsc', 'chrome_extension', 'chrome_extension'), defServiceWorkerBundle(), -].concat(maybeBigtrace).concat(maybeOpenPerfettoTrace); +] + .concat(maybeBigtrace) + .concat(maybeOpenPerfettoTrace); diff --git a/ui/eslint.config.js b/ui/eslint.config.js new file mode 100644 index 0000000000..1e67d515ec --- /dev/null +++ b/ui/eslint.config.js @@ -0,0 +1,132 @@ +// Copyright (C) 2024 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +const {FlatCompat} = require('@eslint/eslintrc'); +const fs = require('fs'); +const globals = require('globals'); +const js = require('@eslint/js'); +const jsdoc = require('eslint-plugin-jsdoc'); +const path = require('node:path'); +const tsParser = require('@typescript-eslint/parser'); +const typescriptEslint = require('@typescript-eslint/eslint-plugin'); + +const compat = new FlatCompat({ + baseDirectory: __dirname, + recommendedConfig: js.configs.recommended, + allConfig: js.configs.all, +}); + +// The eslint-config-google uses deprecated jsdoc options that break with the +// latest version of eslint. This has been fixed upstram [1] but no npm package +// has been released since then. Hence patching the config manually. +// [1] https://github.com/google/eslint-config-google/pull/72. +const googleCfg = compat.extends('google'); +delete googleCfg[0].rules['valid-jsdoc']; +delete googleCfg[0].rules['require-jsdoc']; + +const ignorePath = path.resolve(__dirname, '.prettierignore'); +const ignores = fs + .readFileSync(ignorePath, {encoding: 'utf8'}) + .split('\n') + .filter((l) => l !== '' && !l.startsWith('#')); + +module.exports = [ + // `ignores` has to go on a standalone block at the start otherwise gets + // overridden by the googleCfg and jsdoc.configs, because the new eslint + // flat config is so clever. + {ignores: ignores}, + + ...googleCfg, + + jsdoc.configs['flat/recommended'], + + { + files: ['src/**/*.ts'], + plugins: { + '@typescript-eslint': typescriptEslint, + jsdoc, + }, + + languageOptions: { + globals: { + ...globals.browser, + ...globals.node, + }, + ecmaVersion: 'latest', + sourceType: 'module', + parser: tsParser, + parserOptions: { + project: './tsconfig.json', + }, + }, + + rules: { + 'indent': 'off', + 'max-len': 'off', + 'operator-linebreak': 'off', + 'quotes': 'off', + 'brace-style': 'off', + 'space-before-function-paren': 'off', + 'generator-star-spacing': 'off', + 'semi-spacing': 'off', + + 'no-multi-spaces': [ + 'error', + { + ignoreEOLComments: true, + }, + ], + + 'no-unused-vars': 'off', + + '@typescript-eslint/no-unused-vars': [ + 'error', + { + argsIgnorePattern: '^_.*', + varsIgnorePattern: '^_.*', + }, + ], + + 'no-array-constructor': 'off', + '@typescript-eslint/no-array-constructor': ['error'], + 'prefer-rest-params': 'off', + + 'new-cap': [ + 'error', + { + capIsNew: false, + properties: false, + }, + ], + + 'jsdoc/require-jsdoc': 'off', + 'jsdoc/require-param': 'off', + 'jsdoc/require-param-type': 'off', + 'jsdoc/require-returns': 'off', + 'jsdoc/require-returns-type': 'off', + 'jsdoc/tag-lines': 'off', + + '@typescript-eslint/no-explicit-any': 'error', + + '@typescript-eslint/strict-boolean-expressions': [ + 'error', + { + allowNullableBoolean: true, + allowNullableObject: true, + allowNullableString: true, + }, + ], + }, + }, +]; diff --git a/ui/format-sources b/ui/format-sources new file mode 100644 index 0000000000..6aae84ce4e --- /dev/null +++ b/ui/format-sources @@ -0,0 +1,131 @@ +#!/usr/bin/env python3 +# Copyright (C) 2024 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import argparse +import os +import sys +import subprocess + +UI_DIR = os.path.abspath(os.path.dirname(__file__)) +ROOT_DIR = os.path.dirname(UI_DIR) +PRETTIER_PATH = os.path.join(ROOT_DIR, 'ui/node_modules/.bin/prettier') +ESLINT_PATH = os.path.join(ROOT_DIR, 'ui/node_modules/.bin/eslint') + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('--check-only', action='store_true') + parser.add_argument('--no-prettier', action='store_true') + parser.add_argument('--no-eslint', action='store_true') + parser.add_argument( + '--all', + action='store_true', + help='Prettify all .ts sources, not just the changed ones') + parser.add_argument('filelist', nargs='*') + args = parser.parse_args() + + # We want to execute all the commands relative to UI_DIR, because eslint looks + # for eslintrc in cwd. However, the user might pass paths that are relative to + # the current cwd, which might be != UI_DIR. + # So before running chdir relativize all the passed paths to UI_DIR + filelist = [ + os.path.relpath(os.path.abspath(x), UI_DIR) for x in args.filelist + ] + os.chdir(UI_DIR) + + # Need to add 'node' to the search path. + os.environ['PATH'] += os.pathsep + os.path.join(ROOT_DIR, 'tools') + + if not os.path.exists(PRETTIER_PATH): + print('Cannot find %s' % PRETTIER_PATH) + print('Run tools/install-build-deps --ui') + return 1 + + with open('.prettierignore', 'r') as f: + ignorelist = set(f.read().strip().split('\n')) + + all_files = set() + for root, _dirs, files in os.walk('src'): + if root in ignorelist: + continue + for file in files: + file_path = os.path.join(root, file) + if os.path.splitext(file)[1].lower() in ['.ts', '.js', '.scss']: + all_files.add(file_path) + + files_to_check = [] + git_cmd = [] + if args.all: + files_to_check = list(all_files) + elif filelist: + files_to_check = filelist + else: + upstream_branch = get_upstream_branch() + git_cmd = ['git', 'diff', '--name-only', upstream_branch] + git_output = subprocess.check_output(git_cmd, text=True).strip() + changed_files = set(git_output.split('\n') if git_output else []) + changed_files = [os.path.relpath(x, 'ui') for x in changed_files] + files_to_check = all_files.intersection(changed_files) + + prettier_args = ['--log-level=warn'] + eslint_args = [] + if args.check_only: + prettier_args += ['--check'] + else: + eslint_args += ['--fix'] + prettier_args += ['--write'] + + if len(files_to_check) == 0: + if not args.check_only: + # Be quiet when invoked by git cl presubmit. + print('No changed files detected by `%s`' % ' '.join(git_cmd)) + print('Pass --all to prettify all ts/js/scss files in the repo') + return 0 + + # Run prettier first + if not args.no_prettier: + print('Running prettier on %d files' % len(files_to_check)) + call_or_die([PRETTIER_PATH] + prettier_args + list(files_to_check)) + + # Then run eslint (but not on .scss) + if not args.no_eslint: + ts_js_only = lambda f: f.endswith('.ts') or f.endswith('.js') + files_to_check = list(filter(ts_js_only, files_to_check)) + if len(files_to_check) > 0: + print('Running eslint on %d files' % len(files_to_check)) + call_or_die([ESLINT_PATH] + eslint_args + files_to_check) + + +# Like subprocess.check_call, but in case of errors dies without printing a +# useless stacktrace that just muddies the stdout. +def call_or_die(cmd): + try: + subprocess.check_call(cmd) + except subprocess.CalledProcessError as ex: + print('`%s` returned %d' % (' '.join(cmd)[:128], ex.returncode)) + sys.exit(ex.returncode) + + +def get_upstream_branch(): + try: + cmd = ['git', 'rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{u}'] + res = subprocess.check_output(cmd, text=True, stderr=subprocess.DEVNULL) + return res.strip() + except subprocess.CalledProcessError: + return 'origin/main' + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/ui/npm b/ui/npm index e703031850..32944af11f 100755 --- a/ui/npm +++ b/ui/npm @@ -15,4 +15,7 @@ set -e -u ROOT_DIR="$(dirname $(cd -P ${BASH_SOURCE[0]%/*}; pwd))" +# Need to add node to the search path. +TOOLS_PATH="$ROOT_DIR/tools" +PATH=$TOOLS_PATH:$PATH exec "$ROOT_DIR/tools/npm" "$@" diff --git a/ui/package.json b/ui/package.json index 8f1e15d4c4..8a0a28985c 100644 --- a/ui/package.json +++ b/ui/package.json @@ -7,73 +7,78 @@ "author": "Perfetto Team", "license": "Apache-2.0", "dependencies": { - "@codemirror/autocomplete": "6.8.1", - "@codemirror/commands": "6.2.4", - "@codemirror/lint": "6.3.0", - "@codemirror/search": "6.5.0", - "@codemirror/state": "6.2.1", + "@codemirror/autocomplete": "^6.16.3", + "@codemirror/commands": "^6.6.0", + "@codemirror/lint": "^6.8.1", + "@codemirror/search": "^6.5.6", + "@codemirror/state": "^6.4.1", "@codemirror/theme-one-dark": "^6.1.2", + "@codemirror/view": "^6.28.2", "@popperjs/core": "^2.11.8", "@protobufjs/base64": "^1.1.2", "@protobufjs/utf8": "^1.1.0", - "@types/chrome": "0.0.186", - "@types/color-convert": "^1.9.0", - "@types/filesystem": "^0.0.32", - "@types/mithril": "^2.0.12", - "@types/node": "^14.18.51", - "@types/pako": "^1.0.4", - "@types/pngjs": "^6.0.1", - "@types/uuid": "^8.3.4", - "@types/w3c-web-usb": "^1.0.6", - "argparse": "^2.0.1", + "@types/chrome": "0.0.268", + "@types/color-convert": "^2.0.3", + "@types/filesystem": "^0.0.36", + "@types/mithril": "^2.2.6", + "@types/node": "^20.14.9", + "@types/pako": "^2.0.3", + "@types/pngjs": "^6.0.5", + "@types/uuid": "^10.0.0", + "@types/w3c-web-usb": "^1.0.10", "codemirror": "6.0.1", "color-convert": "^2.0.1", - "custom_utils": "file:src/base/utils", - "devtools-protocol": "0.0.1159816", - "esbuild": "^0.15.18", + "devtools-protocol": "0.0.1319565", + "esbuild": "^0.21.5", "events": "^3.3.0", "hsluv": "^0.1.0", - "immer": "^9.0.21", + "immer": "^10.1.1", "jsbn-rsa": "^1.0.4", "mithril": "^2.2.2", "noice-json-rpc": "^1.2.0", - "pako": "^1.0.11", - "protobufjs": "^7.2.5", + "pako": "^2.1.0", + "protobufjs": "^7.3.2", "protobufjs-cli": "^1.1.2", - "sass": "^1.63.6", "util": "^0.12.5", - "uuid": "^9.0.0", - "vega": "^5.25.0", - "vega-lite": "^5.11.0" + "uuid": "^10.0.0", + "vega": "^5.30.0", + "vega-lite": "^5.19.0", + "zod": "^3.23.8" }, "devDependencies": { - "@rollup/plugin-commonjs": "^24.1.0", - "@rollup/plugin-node-resolve": "^15.1.0", - "@types/jest": "^26.0.24", - "@types/pixelmatch": "^5.2.4", - "@typescript-eslint/eslint-plugin": "^5.60.0", - "@typescript-eslint/parser": "^5.60.0", + "@eslint/eslintrc": "^3.1.0", + "@eslint/js": "^9.6.0", + "@rollup/plugin-commonjs": "^26.0.1", + "@rollup/plugin-node-resolve": "^15.2.3", + "@types/jest": "^29.5.12", + "@types/pixelmatch": "^5.2.6", + "@typescript-eslint/eslint-plugin": "^7.14.1", + "@typescript-eslint/parser": "^7.14.1", "dingusjs": "^0.0.3", - "eslint": "^8.43.0", + "eslint": "^9.6.0", "eslint-config-google": "^0.14.0", - "jest": "^26.6.3", - "node-watch": "^0.7.3", + "eslint-plugin-compat": "^5.0.0", + "eslint-plugin-jsdoc": "^48.5.0", + "globals": "^15.6.0", + "jest": "^29.7.0", + "jest-canvas-mock": "^2.5.2", + "jest-environment-jsdom": "^29.7.0", + "jest-localstorage-mock": "^2.4.26", "pixelmatch": "^5.3.0", - "pngjs": "^6.0.0", - "prettier": "^2.8.8", - "puppeteer": "^22.6.0", + "pngjs": "^7.0.0", + "prettier": "^3.3.2", + "puppeteer": "^22.12.1", "rollup": "^2.79.1", "rollup-plugin-re": "^1.0.7", "rollup-plugin-sourcemaps": "^0.6.3", - "tslib": "^2.5.3", - "typescript": "5.0.4" + "rollup-plugin-uglify": "^6.0.4", + "sass": "^1.77.6", + "tslib": "^2.6.3", + "typescript": "5.5.2" }, "scripts": { "build": "node build.js", "test": "node build.js --run-unittests", "lint": "npx eslint . --ext .js,.ts" - }, - "jest": { - "setupFiles": ["jest-localstorage-mock"] } } diff --git a/ui/pnpm b/ui/pnpm index e10f045793..7f3130324d 100755 --- a/ui/pnpm +++ b/ui/pnpm @@ -15,4 +15,7 @@ set -e -u ROOT_DIR="$(dirname $(cd -P ${BASH_SOURCE[0]%/*}; pwd))" +# Need to add node to the search path. +TOOLS_PATH="$ROOT_DIR/tools" +PATH=$TOOLS_PATH:$PATH exec "$ROOT_DIR/tools/pnpm" "$@" diff --git a/ui/pnpm-lock.yaml b/ui/pnpm-lock.yaml index e2beb57fe2..1feed7a6bf 100644 --- a/ui/pnpm-lock.yaml +++ b/ui/pnpm-lock.yaml @@ -6,23 +6,26 @@ settings: dependencies: '@codemirror/autocomplete': - specifier: 6.8.1 - version: 6.8.1(@codemirror/language@6.9.0)(@codemirror/state@6.2.1)(@codemirror/view@6.18.1)(@lezer/common@1.0.4) + specifier: ^6.16.3 + version: 6.16.3(@codemirror/language@6.9.0)(@codemirror/state@6.4.1)(@codemirror/view@6.28.2)(@lezer/common@1.2.1) '@codemirror/commands': - specifier: 6.2.4 - version: 6.2.4 + specifier: ^6.6.0 + version: 6.6.0 '@codemirror/lint': - specifier: 6.3.0 - version: 6.3.0 + specifier: ^6.8.1 + version: 6.8.1 '@codemirror/search': - specifier: 6.5.0 - version: 6.5.0 + specifier: ^6.5.6 + version: 6.5.6 '@codemirror/state': - specifier: 6.2.1 - version: 6.2.1 + specifier: ^6.4.1 + version: 6.4.1 '@codemirror/theme-one-dark': specifier: ^6.1.2 version: 6.1.2 + '@codemirror/view': + specifier: ^6.28.2 + version: 6.28.2 '@popperjs/core': specifier: ^2.11.8 version: 2.11.8 @@ -33,50 +36,44 @@ dependencies: specifier: ^1.1.0 version: 1.1.0 '@types/chrome': - specifier: 0.0.186 - version: 0.0.186 + specifier: 0.0.268 + version: 0.0.268 '@types/color-convert': - specifier: ^1.9.0 - version: 1.9.0 + specifier: ^2.0.3 + version: 2.0.3 '@types/filesystem': - specifier: ^0.0.32 - version: 0.0.32 + specifier: ^0.0.36 + version: 0.0.36 '@types/mithril': - specifier: ^2.0.12 - version: 2.0.12 + specifier: ^2.2.6 + version: 2.2.6 '@types/node': - specifier: ^14.18.51 - version: 14.18.51 + specifier: ^20.14.9 + version: 20.14.9 '@types/pako': - specifier: ^1.0.4 - version: 1.0.4 + specifier: ^2.0.3 + version: 2.0.3 '@types/pngjs': - specifier: ^6.0.1 - version: 6.0.1 + specifier: ^6.0.5 + version: 6.0.5 '@types/uuid': - specifier: ^8.3.4 - version: 8.3.4 + specifier: ^10.0.0 + version: 10.0.0 '@types/w3c-web-usb': - specifier: ^1.0.6 - version: 1.0.6 - argparse: - specifier: ^2.0.1 - version: 2.0.1 + specifier: ^1.0.10 + version: 1.0.10 codemirror: specifier: 6.0.1 - version: 6.0.1(@lezer/common@1.0.4) + version: 6.0.1(@lezer/common@1.2.1) color-convert: specifier: ^2.0.1 version: 2.0.1 - custom_utils: - specifier: file:src/base/utils - version: file:src/base/utils devtools-protocol: - specifier: 0.0.1159816 - version: 0.0.1159816 + specifier: 0.0.1319565 + version: 0.0.1319565 esbuild: - specifier: ^0.15.18 - version: 0.15.18 + specifier: ^0.21.5 + version: 0.21.5 events: specifier: ^3.3.0 version: 3.3.0 @@ -84,8 +81,8 @@ dependencies: specifier: ^0.1.0 version: 0.1.0 immer: - specifier: ^9.0.21 - version: 9.0.21 + specifier: ^10.1.1 + version: 10.1.1 jsbn-rsa: specifier: ^1.0.4 version: 1.0.4 @@ -96,76 +93,97 @@ dependencies: specifier: ^1.2.0 version: 1.2.0 pako: - specifier: ^1.0.11 - version: 1.0.11 + specifier: ^2.1.0 + version: 2.1.0 protobufjs: - specifier: ^7.2.5 - version: 7.2.5 + specifier: ^7.3.2 + version: 7.3.2 protobufjs-cli: specifier: ^1.1.2 - version: 1.1.2(protobufjs@7.2.5) - sass: - specifier: ^1.63.6 - version: 1.63.6 + version: 1.1.2(protobufjs@7.3.2) util: specifier: ^0.12.5 version: 0.12.5 uuid: - specifier: ^9.0.0 - version: 9.0.0 + specifier: ^10.0.0 + version: 10.0.0 vega: - specifier: ^5.25.0 - version: 5.25.0 + specifier: ^5.30.0 + version: 5.30.0 vega-lite: - specifier: ^5.11.0 - version: 5.11.0(vega@5.25.0) + specifier: ^5.19.0 + version: 5.19.0(vega@5.30.0) + zod: + specifier: ^3.23.8 + version: 3.23.8 devDependencies: + '@eslint/eslintrc': + specifier: ^3.1.0 + version: 3.1.0 + '@eslint/js': + specifier: ^9.6.0 + version: 9.6.0 '@rollup/plugin-commonjs': - specifier: ^24.1.0 - version: 24.1.0(rollup@2.79.1) + specifier: ^26.0.1 + version: 26.0.1(rollup@2.79.1) '@rollup/plugin-node-resolve': - specifier: ^15.1.0 - version: 15.1.0(rollup@2.79.1) + specifier: ^15.2.3 + version: 15.2.3(rollup@2.79.1) '@types/jest': - specifier: ^26.0.24 - version: 26.0.24 + specifier: ^29.5.12 + version: 29.5.12 '@types/pixelmatch': - specifier: ^5.2.4 - version: 5.2.4 + specifier: ^5.2.6 + version: 5.2.6 '@typescript-eslint/eslint-plugin': - specifier: ^5.60.0 - version: 5.60.0(@typescript-eslint/parser@5.60.0)(eslint@8.43.0)(typescript@5.0.4) + specifier: ^7.14.1 + version: 7.14.1(@typescript-eslint/parser@7.14.1)(eslint@9.6.0)(typescript@5.5.2) '@typescript-eslint/parser': - specifier: ^5.60.0 - version: 5.60.0(eslint@8.43.0)(typescript@5.0.4) + specifier: ^7.14.1 + version: 7.14.1(eslint@9.6.0)(typescript@5.5.2) dingusjs: specifier: ^0.0.3 version: 0.0.3 eslint: - specifier: ^8.43.0 - version: 8.43.0 + specifier: ^9.6.0 + version: 9.6.0 eslint-config-google: specifier: ^0.14.0 - version: 0.14.0(eslint@8.43.0) + version: 0.14.0(eslint@9.6.0) + eslint-plugin-compat: + specifier: ^5.0.0 + version: 5.0.0(eslint@9.6.0) + eslint-plugin-jsdoc: + specifier: ^48.5.0 + version: 48.5.0(eslint@9.6.0) + globals: + specifier: ^15.6.0 + version: 15.6.0 jest: - specifier: ^26.6.3 - version: 26.6.3 - node-watch: - specifier: ^0.7.3 - version: 0.7.3 + specifier: ^29.7.0 + version: 29.7.0(@types/node@20.14.9) + jest-canvas-mock: + specifier: ^2.5.2 + version: 2.5.2 + jest-environment-jsdom: + specifier: ^29.7.0 + version: 29.7.0 + jest-localstorage-mock: + specifier: ^2.4.26 + version: 2.4.26 pixelmatch: specifier: ^5.3.0 version: 5.3.0 pngjs: - specifier: ^6.0.0 - version: 6.0.0 + specifier: ^7.0.0 + version: 7.0.0 prettier: - specifier: ^2.8.8 - version: 2.8.8 + specifier: ^3.3.2 + version: 3.3.2 puppeteer: - specifier: ^22.6.0 - version: 22.6.0(typescript@5.0.4) + specifier: ^22.12.1 + version: 22.12.1(typescript@5.5.2) rollup: specifier: ^2.79.1 version: 2.79.1 @@ -174,13 +192,19 @@ devDependencies: version: 1.0.7 rollup-plugin-sourcemaps: specifier: ^0.6.3 - version: 0.6.3(@types/node@14.18.51)(rollup@2.79.1) + version: 0.6.3(@types/node@20.14.9)(rollup@2.79.1) + rollup-plugin-uglify: + specifier: ^6.0.4 + version: 6.0.4(rollup@2.79.1) + sass: + specifier: ^1.77.6 + version: 1.77.6 tslib: - specifier: ^2.5.3 - version: 2.5.3 + specifier: ^2.6.3 + version: 2.6.3 typescript: - specifier: 5.0.4 - version: 5.0.4 + specifier: 5.5.2 + version: 5.5.2 packages: @@ -199,17 +223,30 @@ packages: '@babel/highlight': 7.22.5 dev: true + /@babel/code-frame@7.24.7: + resolution: {integrity: sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/highlight': 7.24.7 + picocolors: 1.0.1 + dev: true + /@babel/compat-data@7.22.5: resolution: {integrity: sha512-4Jc/YuIaYqKnDDz892kPIledykKg12Aw1PYX5i/TY28anJtacvM1Rrr8wbieB9GfEJwlzqT0hUEao0CxEebiDA==} engines: {node: '>=6.9.0'} dev: true + /@babel/compat-data@7.24.7: + resolution: {integrity: sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw==} + engines: {node: '>=6.9.0'} + dev: true + /@babel/core@7.22.5: resolution: {integrity: sha512-SBuTAjg91A3eKOvD+bPEz3LlhHZRNu1nFOVts9lzDJTXshHTjII0BAtDS3Y2DAkdZdDKWVZGVwkDfc4Clxn1dg==} engines: {node: '>=6.9.0'} dependencies: '@ampproject/remapping': 2.2.1 - '@babel/code-frame': 7.22.5 + '@babel/code-frame': 7.24.7 '@babel/generator': 7.22.5 '@babel/helper-compilation-targets': 7.22.5(@babel/core@7.22.5) '@babel/helper-module-transforms': 7.22.5 @@ -219,10 +256,33 @@ packages: '@babel/traverse': 7.22.5 '@babel/types': 7.22.5 convert-source-map: 1.9.0 - debug: 4.3.4 + debug: 4.3.5 gensync: 1.0.0-beta.2 json5: 2.2.3 - semver: 6.3.0 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/core@7.24.7: + resolution: {integrity: sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g==} + engines: {node: '>=6.9.0'} + dependencies: + '@ampproject/remapping': 2.2.1 + '@babel/code-frame': 7.24.7 + '@babel/generator': 7.24.7 + '@babel/helper-compilation-targets': 7.24.7 + '@babel/helper-module-transforms': 7.24.7(@babel/core@7.24.7) + '@babel/helpers': 7.24.7 + '@babel/parser': 7.24.7 + '@babel/template': 7.24.7 + '@babel/traverse': 7.24.7 + '@babel/types': 7.24.7 + convert-source-map: 2.0.0 + debug: 4.3.5 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 transitivePeerDependencies: - supports-color dev: true @@ -237,6 +297,16 @@ packages: jsesc: 2.5.2 dev: true + /@babel/generator@7.24.7: + resolution: {integrity: sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.24.7 + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + jsesc: 2.5.2 + dev: true + /@babel/helper-compilation-targets@7.22.5(@babel/core@7.22.5): resolution: {integrity: sha512-Ji+ywpHeuqxB8WDxraCiqR0xfhYjiDE/e6k7FuIaANnoOFxAHskHChz4vA1mJC9Lbm01s1PVAGhQY4FUKSkGZw==} engines: {node: '>=6.9.0'} @@ -246,9 +316,20 @@ packages: '@babel/compat-data': 7.22.5 '@babel/core': 7.22.5 '@babel/helper-validator-option': 7.22.5 - browserslist: 4.21.9 + browserslist: 4.23.1 + lru-cache: 5.1.1 + semver: 6.3.1 + dev: true + + /@babel/helper-compilation-targets@7.24.7: + resolution: {integrity: sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/compat-data': 7.24.7 + '@babel/helper-validator-option': 7.24.7 + browserslist: 4.23.1 lru-cache: 5.1.1 - semver: 6.3.0 + semver: 6.3.1 dev: true /@babel/helper-environment-visitor@7.22.5: @@ -256,26 +337,58 @@ packages: engines: {node: '>=6.9.0'} dev: true + /@babel/helper-environment-visitor@7.24.7: + resolution: {integrity: sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.24.7 + dev: true + /@babel/helper-function-name@7.22.5: resolution: {integrity: sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==} engines: {node: '>=6.9.0'} dependencies: - '@babel/template': 7.22.5 - '@babel/types': 7.22.5 + '@babel/template': 7.24.7 + '@babel/types': 7.24.7 + dev: true + + /@babel/helper-function-name@7.24.7: + resolution: {integrity: sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/template': 7.24.7 + '@babel/types': 7.24.7 dev: true /@babel/helper-hoist-variables@7.22.5: resolution: {integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.22.5 + '@babel/types': 7.24.7 + dev: true + + /@babel/helper-hoist-variables@7.24.7: + resolution: {integrity: sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.24.7 dev: true /@babel/helper-module-imports@7.22.5: resolution: {integrity: sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.22.5 + '@babel/types': 7.24.7 + dev: true + + /@babel/helper-module-imports@7.24.7: + resolution: {integrity: sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/traverse': 7.24.7 + '@babel/types': 7.24.7 + transitivePeerDependencies: + - supports-color dev: true /@babel/helper-module-transforms@7.22.5: @@ -286,7 +399,7 @@ packages: '@babel/helper-module-imports': 7.22.5 '@babel/helper-simple-access': 7.22.5 '@babel/helper-split-export-declaration': 7.22.5 - '@babel/helper-validator-identifier': 7.22.5 + '@babel/helper-validator-identifier': 7.24.7 '@babel/template': 7.22.5 '@babel/traverse': 7.22.5 '@babel/types': 7.22.5 @@ -294,31 +407,74 @@ packages: - supports-color dev: true + /@babel/helper-module-transforms@7.24.7(@babel/core@7.24.7): + resolution: {integrity: sha512-1fuJEwIrp+97rM4RWdO+qrRsZlAeL1lQJoPqtCYWv0NL115XM93hIH4CSRln2w52SqvmY5hqdtauB6QFCDiZNQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-environment-visitor': 7.24.7 + '@babel/helper-module-imports': 7.24.7 + '@babel/helper-simple-access': 7.24.7 + '@babel/helper-split-export-declaration': 7.24.7 + '@babel/helper-validator-identifier': 7.24.7 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/helper-plugin-utils@7.22.5: resolution: {integrity: sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==} engines: {node: '>=6.9.0'} dev: true + /@babel/helper-plugin-utils@7.24.7: + resolution: {integrity: sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg==} + engines: {node: '>=6.9.0'} + dev: true + /@babel/helper-simple-access@7.22.5: resolution: {integrity: sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.22.5 + '@babel/types': 7.24.7 + dev: true + + /@babel/helper-simple-access@7.24.7: + resolution: {integrity: sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/traverse': 7.24.7 + '@babel/types': 7.24.7 + transitivePeerDependencies: + - supports-color dev: true /@babel/helper-split-export-declaration@7.22.5: resolution: {integrity: sha512-thqK5QFghPKWLhAV321lxF95yCg2K3Ob5yw+M3VHWfdia0IkPXUtoLH8x/6Fh486QUvzhb8YOWHChTVen2/PoQ==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.22.5 + '@babel/types': 7.24.7 + dev: true + + /@babel/helper-split-export-declaration@7.24.7: + resolution: {integrity: sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.24.7 dev: true /@babel/helper-string-parser@7.22.5: resolution: {integrity: sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==} engines: {node: '>=6.9.0'} - /@babel/helper-validator-identifier@7.22.5: - resolution: {integrity: sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==} + /@babel/helper-string-parser@7.24.7: + resolution: {integrity: sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==} + engines: {node: '>=6.9.0'} + dev: true + + /@babel/helper-validator-identifier@7.24.7: + resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==} engines: {node: '>=6.9.0'} /@babel/helper-validator-option@7.22.5: @@ -326,6 +482,11 @@ packages: engines: {node: '>=6.9.0'} dev: true + /@babel/helper-validator-option@7.24.7: + resolution: {integrity: sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw==} + engines: {node: '>=6.9.0'} + dev: true + /@babel/helpers@7.22.5: resolution: {integrity: sha512-pSXRmfE1vzcUIDFQcSGA5Mr+GxBV9oiRKDuDxXvWQQBCh8HoIjs/2DlDB7H8smac1IVrB9/xdXj2N3Wol9Cr+Q==} engines: {node: '>=6.9.0'} @@ -337,15 +498,33 @@ packages: - supports-color dev: true + /@babel/helpers@7.24.7: + resolution: {integrity: sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/template': 7.24.7 + '@babel/types': 7.24.7 + dev: true + /@babel/highlight@7.22.5: resolution: {integrity: sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==} engines: {node: '>=6.9.0'} dependencies: - '@babel/helper-validator-identifier': 7.22.5 + '@babel/helper-validator-identifier': 7.24.7 chalk: 2.4.2 js-tokens: 4.0.0 dev: true + /@babel/highlight@7.24.7: + resolution: {integrity: sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-validator-identifier': 7.24.7 + chalk: 2.4.2 + js-tokens: 4.0.0 + picocolors: 1.0.1 + dev: true + /@babel/parser@7.22.5: resolution: {integrity: sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q==} engines: {node: '>=6.0.0'} @@ -353,13 +532,21 @@ packages: dependencies: '@babel/types': 7.22.5 + /@babel/parser@7.24.7: + resolution: {integrity: sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==} + engines: {node: '>=6.0.0'} + hasBin: true + dependencies: + '@babel/types': 7.22.5 + dev: true + /@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.22.5): resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.22.5 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-plugin-utils': 7.24.7 dev: true /@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.22.5): @@ -368,7 +555,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.22.5 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-plugin-utils': 7.24.7 dev: true /@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.22.5): @@ -377,7 +564,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.22.5 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-plugin-utils': 7.24.7 dev: true /@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.22.5): @@ -386,7 +573,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.22.5 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-plugin-utils': 7.24.7 dev: true /@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.22.5): @@ -395,7 +582,17 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.22.5 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-plugin-utils': 7.24.7 + dev: true + + /@babel/plugin-syntax-jsx@7.24.7(@babel/core@7.22.5): + resolution: {integrity: sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.5 + '@babel/helper-plugin-utils': 7.24.7 dev: true /@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.22.5): @@ -404,7 +601,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.22.5 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-plugin-utils': 7.24.7 dev: true /@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.22.5): @@ -413,7 +610,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.22.5 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-plugin-utils': 7.24.7 dev: true /@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.22.5): @@ -422,7 +619,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.22.5 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-plugin-utils': 7.24.7 dev: true /@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.22.5): @@ -431,7 +628,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.22.5 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-plugin-utils': 7.24.7 dev: true /@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.22.5): @@ -440,7 +637,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.22.5 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-plugin-utils': 7.24.7 dev: true /@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.22.5): @@ -449,7 +646,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.22.5 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-plugin-utils': 7.24.7 dev: true /@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.22.5): @@ -459,31 +656,68 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.22.5 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-plugin-utils': 7.24.7 + dev: true + + /@babel/plugin-syntax-typescript@7.24.7(@babel/core@7.22.5): + resolution: {integrity: sha512-c/+fVeJBB0FeKsFvwytYiUD+LBvhHjGSI0g446PRGdSVGZLRNArBUno2PETbAly3tpiNAQR5XaZ+JslxkotsbA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.22.5 + '@babel/helper-plugin-utils': 7.24.7 dev: true /@babel/template@7.22.5: resolution: {integrity: sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==} engines: {node: '>=6.9.0'} dependencies: - '@babel/code-frame': 7.22.5 - '@babel/parser': 7.22.5 + '@babel/code-frame': 7.24.7 + '@babel/parser': 7.24.7 '@babel/types': 7.22.5 dev: true + /@babel/template@7.24.7: + resolution: {integrity: sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/code-frame': 7.24.7 + '@babel/parser': 7.24.7 + '@babel/types': 7.24.7 + dev: true + /@babel/traverse@7.22.5: resolution: {integrity: sha512-7DuIjPgERaNo6r+PZwItpjCZEa5vyw4eJGufeLxrPdBXBoLcCJCIasvK6pK/9DVNrLZTLFhUGqaC6X/PA007TQ==} engines: {node: '>=6.9.0'} dependencies: - '@babel/code-frame': 7.22.5 + '@babel/code-frame': 7.24.7 '@babel/generator': 7.22.5 '@babel/helper-environment-visitor': 7.22.5 '@babel/helper-function-name': 7.22.5 '@babel/helper-hoist-variables': 7.22.5 '@babel/helper-split-export-declaration': 7.22.5 - '@babel/parser': 7.22.5 + '@babel/parser': 7.24.7 '@babel/types': 7.22.5 - debug: 4.3.4 + debug: 4.3.5 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/traverse@7.24.7: + resolution: {integrity: sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/code-frame': 7.24.7 + '@babel/generator': 7.24.7 + '@babel/helper-environment-visitor': 7.24.7 + '@babel/helper-function-name': 7.24.7 + '@babel/helper-hoist-variables': 7.24.7 + '@babel/helper-split-export-declaration': 7.24.7 + '@babel/parser': 7.24.7 + '@babel/types': 7.24.7 + debug: 4.3.5 globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -494,38 +728,24 @@ packages: engines: {node: '>=6.9.0'} dependencies: '@babel/helper-string-parser': 7.22.5 - '@babel/helper-validator-identifier': 7.22.5 + '@babel/helper-validator-identifier': 7.24.7 to-fast-properties: 2.0.0 - /@bcoe/v8-coverage@0.2.3: - resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} - dev: true - - /@cnakazawa/watch@1.0.4: - resolution: {integrity: sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ==} - engines: {node: '>=0.1.95'} - hasBin: true + /@babel/types@7.24.7: + resolution: {integrity: sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==} + engines: {node: '>=6.9.0'} dependencies: - exec-sh: 0.3.6 - minimist: 1.2.8 + '@babel/helper-string-parser': 7.24.7 + '@babel/helper-validator-identifier': 7.24.7 + to-fast-properties: 2.0.0 dev: true - /@codemirror/autocomplete@6.8.1(@codemirror/language@6.8.0)(@codemirror/state@6.2.1)(@codemirror/view@6.18.1)(@lezer/common@1.0.4): - resolution: {integrity: sha512-HpphvDcTdOx+9R3eUw9hZK9JA77jlaBF0kOt2McbyfvY0rX9pnMoO8rkkZc0GzSbzhIY4m5xJ0uHHgjfqHNmXQ==} - peerDependencies: - '@codemirror/language': ^6.0.0 - '@codemirror/state': ^6.0.0 - '@codemirror/view': ^6.0.0 - '@lezer/common': ^1.0.0 - dependencies: - '@codemirror/language': 6.8.0 - '@codemirror/state': 6.2.1 - '@codemirror/view': 6.18.1 - '@lezer/common': 1.0.4 - dev: false + /@bcoe/v8-coverage@0.2.3: + resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + dev: true - /@codemirror/autocomplete@6.8.1(@codemirror/language@6.9.0)(@codemirror/state@6.2.1)(@codemirror/view@6.18.1)(@lezer/common@1.0.4): - resolution: {integrity: sha512-HpphvDcTdOx+9R3eUw9hZK9JA77jlaBF0kOt2McbyfvY0rX9pnMoO8rkkZc0GzSbzhIY4m5xJ0uHHgjfqHNmXQ==} + /@codemirror/autocomplete@6.16.3(@codemirror/language@6.9.0)(@codemirror/state@6.4.1)(@codemirror/view@6.28.2)(@lezer/common@1.2.1): + resolution: {integrity: sha512-Vl/tIeRVVUCRDuOG48lttBasNQu8usGgXQawBXI7WJAiUDSFOfzflmEsZFZo48mAvAaa4FZ/4/yLLxFtdJaKYA==} peerDependencies: '@codemirror/language': ^6.0.0 '@codemirror/state': ^6.0.0 @@ -533,81 +753,100 @@ packages: '@lezer/common': ^1.0.0 dependencies: '@codemirror/language': 6.9.0 - '@codemirror/state': 6.2.1 - '@codemirror/view': 6.18.1 - '@lezer/common': 1.0.4 - dev: false - - /@codemirror/commands@6.2.4: - resolution: {integrity: sha512-42lmDqVH0ttfilLShReLXsDfASKLXzfyC36bzwcqzox9PlHulMcsUOfHXNo2X2aFMVNUoQ7j+d4q5bnfseYoOA==} - dependencies: - '@codemirror/language': 6.8.0 - '@codemirror/state': 6.2.1 - '@codemirror/view': 6.18.1 - '@lezer/common': 1.0.3 + '@codemirror/state': 6.4.1 + '@codemirror/view': 6.28.2 + '@lezer/common': 1.2.1 dev: false - /@codemirror/language@6.8.0: - resolution: {integrity: sha512-r1paAyWOZkfY0RaYEZj3Kul+MiQTEbDvYqf8gPGaRvNneHXCmfSaAVFjwRUPlgxS8yflMxw2CTu6uCMp8R8A2g==} + /@codemirror/commands@6.6.0: + resolution: {integrity: sha512-qnY+b7j1UNcTS31Eenuc/5YJB6gQOzkUoNmJQc0rznwqSRpeaWWpjkWy2C/MPTcePpsKJEM26hXrOXl1+nceXg==} dependencies: - '@codemirror/state': 6.2.1 - '@codemirror/view': 6.18.1 - '@lezer/common': 1.0.3 - '@lezer/highlight': 1.1.6 - '@lezer/lr': 1.3.7 - style-mod: 4.0.3 + '@codemirror/language': 6.9.0 + '@codemirror/state': 6.4.1 + '@codemirror/view': 6.28.2 + '@lezer/common': 1.2.1 dev: false /@codemirror/language@6.9.0: resolution: {integrity: sha512-nFu311/0ne/qGuGCL3oKuktBgzVOaxCHZPZv1tLSZkNjPYxxvkjSbzno3MlErG2tgw1Yw1yF8BxMCegeMXqpiw==} dependencies: - '@codemirror/state': 6.2.1 - '@codemirror/view': 6.18.1 - '@lezer/common': 1.0.4 + '@codemirror/state': 6.4.1 + '@codemirror/view': 6.28.2 + '@lezer/common': 1.2.1 '@lezer/highlight': 1.1.6 '@lezer/lr': 1.3.10 style-mod: 4.1.0 dev: false - /@codemirror/lint@6.3.0: - resolution: {integrity: sha512-tzxOVQNoDhhwFNfcTO2IB74wQoWarARcH6gv3YufPpiJ9yhcb7zD6JCkO5+FWARskqRFc8GFa6E+wUyOvADl5A==} + /@codemirror/lint@6.8.1: + resolution: {integrity: sha512-IZ0Y7S4/bpaunwggW2jYqwLuHj0QtESf5xcROewY6+lDNwZ/NzvR4t+vpYgg9m7V8UXLPYqG+lu3DF470E5Oxg==} dependencies: - '@codemirror/state': 6.2.1 - '@codemirror/view': 6.18.1 + '@codemirror/state': 6.4.1 + '@codemirror/view': 6.28.2 crelt: 1.0.6 dev: false - /@codemirror/search@6.5.0: - resolution: {integrity: sha512-64/M40YeJPToKvGO6p3fijo2vwUEj4nACEAXElCaYQ50HrXSvRaK+NHEhSh73WFBGdvIdhrV+lL9PdJy2RfCYA==} + /@codemirror/search@6.5.6: + resolution: {integrity: sha512-rpMgcsh7o0GuCDUXKPvww+muLA1pDJaFrpq/CCHtpQJYz8xopu4D1hPcKRoDD0YlF8gZaqTNIRa4VRBWyhyy7Q==} dependencies: - '@codemirror/state': 6.2.1 - '@codemirror/view': 6.18.1 + '@codemirror/state': 6.4.1 + '@codemirror/view': 6.28.2 crelt: 1.0.6 dev: false - /@codemirror/state@6.2.1: - resolution: {integrity: sha512-RupHSZ8+OjNT38zU9fKH2sv+Dnlr8Eb8sl4NOnnqz95mCFTZUaiRP8Xv5MeeaG0px2b8Bnfe7YGwCV3nsBhbuw==} + /@codemirror/state@6.4.1: + resolution: {integrity: sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A==} dev: false /@codemirror/theme-one-dark@6.1.2: resolution: {integrity: sha512-F+sH0X16j/qFLMAfbciKTxVOwkdAS336b7AXTKOZhy8BR3eH/RelsnLgLFINrpST63mmN2OuwUt0W2ndUgYwUA==} dependencies: - '@codemirror/language': 6.8.0 - '@codemirror/state': 6.2.1 - '@codemirror/view': 6.18.1 + '@codemirror/language': 6.9.0 + '@codemirror/state': 6.4.1 + '@codemirror/view': 6.28.2 '@lezer/highlight': 1.1.6 dev: false - /@codemirror/view@6.18.1: - resolution: {integrity: sha512-xcsXcMkIMd7l3WZEWoc4ljteAiqzxb5gVerRxk5132p5cLix6rTydWTQjsj2oxORepfsrwy1fC4r20iMa9plrg==} + /@codemirror/view@6.28.2: + resolution: {integrity: sha512-A3DmyVfjgPsGIjiJqM/zvODUAPQdQl3ci0ghehYNnbt5x+o76xq+dL5+mMBuysDXnI3kapgOkoeJ0sbtL/3qPw==} dependencies: - '@codemirror/state': 6.2.1 + '@codemirror/state': 6.4.1 style-mod: 4.1.0 w3c-keyname: 2.2.8 dev: false - /@esbuild/android-arm@0.15.18: - resolution: {integrity: sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==} + /@es-joy/jsdoccomment@0.43.1: + resolution: {integrity: sha512-I238eDtOolvCuvtxrnqtlBaw0BwdQuYqK7eA6XIonicMdOOOb75mqdIzkGDUbS04+1Di007rgm9snFRNeVrOog==} + engines: {node: '>=16'} + dependencies: + '@types/eslint': 8.56.10 + '@types/estree': 1.0.5 + '@typescript-eslint/types': 7.14.1 + comment-parser: 1.4.1 + esquery: 1.5.0 + jsdoc-type-pratt-parser: 4.0.0 + dev: true + + /@esbuild/aix-ppc64@0.21.5: + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + requiresBuild: true + dev: false + optional: true + + /@esbuild/android-arm64@0.21.5: + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: false + optional: true + + /@esbuild/android-arm@0.21.5: + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} engines: {node: '>=12'} cpu: [arm] os: [android] @@ -615,8 +854,80 @@ packages: dev: false optional: true - /@esbuild/linux-loong64@0.15.18: - resolution: {integrity: sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==} + /@esbuild/android-x64@0.21.5: + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + dev: false + optional: true + + /@esbuild/darwin-arm64@0.21.5: + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /@esbuild/darwin-x64@0.21.5: + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /@esbuild/freebsd-arm64@0.21.5: + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: false + optional: true + + /@esbuild/freebsd-x64@0.21.5: + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: false + optional: true + + /@esbuild/linux-arm64@0.21.5: + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@esbuild/linux-arm@0.21.5: + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@esbuild/linux-ia32@0.21.5: + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@esbuild/linux-loong64@0.21.5: + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} engines: {node: '>=12'} cpu: [loong64] os: [linux] @@ -624,29 +935,139 @@ packages: dev: false optional: true - /@eslint-community/eslint-utils@4.4.0(eslint@8.43.0): + /@esbuild/linux-mips64el@0.21.5: + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@esbuild/linux-ppc64@0.21.5: + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@esbuild/linux-riscv64@0.21.5: + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@esbuild/linux-s390x@0.21.5: + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@esbuild/linux-x64@0.21.5: + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@esbuild/netbsd-x64@0.21.5: + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: false + optional: true + + /@esbuild/openbsd-x64@0.21.5: + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: false + optional: true + + /@esbuild/sunos-x64@0.21.5: + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: false + optional: true + + /@esbuild/win32-arm64@0.21.5: + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: false + optional: true + + /@esbuild/win32-ia32@0.21.5: + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: false + optional: true + + /@esbuild/win32-x64@0.21.5: + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: false + optional: true + + /@eslint-community/eslint-utils@4.4.0(eslint@9.6.0): resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 dependencies: - eslint: 8.43.0 - eslint-visitor-keys: 3.4.1 + eslint: 9.6.0 + eslint-visitor-keys: 3.4.3 dev: true - /@eslint-community/regexpp@4.5.1: - resolution: {integrity: sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==} + /@eslint-community/regexpp@4.11.0: + resolution: {integrity: sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} dev: true - /@eslint/eslintrc@2.0.3: - resolution: {integrity: sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + /@eslint/config-array@0.17.0: + resolution: {integrity: sha512-A68TBu6/1mHHuc5YJL0U0VVeGNiklLAL6rRmhTCP2B5XjWLMnrX+HkO+IAXyHvks5cyyY1jjK5ITPQ1HGS2EVA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + dependencies: + '@eslint/object-schema': 2.1.4 + debug: 4.3.4 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + dev: true + + /@eslint/eslintrc@3.1.0: + resolution: {integrity: sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dependencies: ajv: 6.12.6 debug: 4.3.4 - espree: 9.5.2 - globals: 13.20.0 + espree: 10.1.0 + globals: 14.0.0 ignore: 5.2.4 import-fresh: 3.3.0 js-yaml: 4.1.0 @@ -656,20 +1077,14 @@ packages: - supports-color dev: true - /@eslint/js@8.43.0: - resolution: {integrity: sha512-s2UHCoiXfxMvmfzqoN+vrQ84ahUSYde9qNO1MdxmoEhyHWsfmwOpFlwYV+ePJEVc7gFnATGUi376WowX1N7tFg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + /@eslint/js@9.6.0: + resolution: {integrity: sha512-D9B0/3vNg44ZeWbYMpBoXqNP4j6eQD5vNwIlGAuFRRzK/WtT/jvDQW3Bi9kkf3PMDMlM7Yi+73VLUsn5bJcl8A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dev: true - /@humanwhocodes/config-array@0.11.10: - resolution: {integrity: sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==} - engines: {node: '>=10.10.0'} - dependencies: - '@humanwhocodes/object-schema': 1.2.1 - debug: 4.3.4 - minimatch: 3.1.2 - transitivePeerDependencies: - - supports-color + /@eslint/object-schema@2.1.4: + resolution: {integrity: sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dev: true /@humanwhocodes/module-importer@1.0.1: @@ -677,8 +1092,21 @@ packages: engines: {node: '>=12.22'} dev: true - /@humanwhocodes/object-schema@1.2.1: - resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} + /@humanwhocodes/retry@0.3.0: + resolution: {integrity: sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==} + engines: {node: '>=18.18'} + dev: true + + /@isaacs/cliui@8.0.2: + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + dependencies: + string-width: 5.1.2 + string-width-cjs: /string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: /strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: /wrap-ansi@7.0.0 dev: true /@istanbuljs/load-nyc-config@1.1.0: @@ -697,190 +1125,217 @@ packages: engines: {node: '>=8'} dev: true - /@jest/console@26.6.2: - resolution: {integrity: sha512-IY1R2i2aLsLr7Id3S6p2BA82GNWryt4oSvEXLAKc+L2zdi89dSkE8xC1C+0kpATG4JhBJREnQOH7/zmccM2B0g==} - engines: {node: '>= 10.14.2'} + /@jest/console@29.7.0: + resolution: {integrity: sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jest/types': 26.6.2 - '@types/node': 14.18.51 + '@jest/types': 29.6.3 + '@types/node': 20.14.9 chalk: 4.1.2 - jest-message-util: 26.6.2 - jest-util: 26.6.2 + jest-message-util: 29.7.0 + jest-util: 29.7.0 slash: 3.0.0 dev: true - /@jest/core@26.6.3: - resolution: {integrity: sha512-xvV1kKbhfUqFVuZ8Cyo+JPpipAHHAV3kcDBftiduK8EICXmTFddryy3P7NfZt8Pv37rA9nEJBKCCkglCPt/Xjw==} - engines: {node: '>= 10.14.2'} + /@jest/core@29.7.0: + resolution: {integrity: sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true dependencies: - '@jest/console': 26.6.2 - '@jest/reporters': 26.6.2 - '@jest/test-result': 26.6.2 - '@jest/transform': 26.6.2 - '@jest/types': 26.6.2 - '@types/node': 14.18.51 + '@jest/console': 29.7.0 + '@jest/reporters': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.14.9 ansi-escapes: 4.3.2 chalk: 4.1.2 + ci-info: 3.9.0 exit: 0.1.2 graceful-fs: 4.2.11 - jest-changed-files: 26.6.2 - jest-config: 26.6.3 - jest-haste-map: 26.6.2 - jest-message-util: 26.6.2 - jest-regex-util: 26.0.0 - jest-resolve: 26.6.2 - jest-resolve-dependencies: 26.6.3 - jest-runner: 26.6.3 - jest-runtime: 26.6.3 - jest-snapshot: 26.6.2 - jest-util: 26.6.2 - jest-validate: 26.6.2 - jest-watcher: 26.6.2 + jest-changed-files: 29.7.0 + jest-config: 29.7.0(@types/node@20.14.9) + jest-haste-map: 29.7.0 + jest-message-util: 29.7.0 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-resolve-dependencies: 29.7.0 + jest-runner: 29.7.0 + jest-runtime: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + jest-watcher: 29.7.0 micromatch: 4.0.5 - p-each-series: 2.2.0 - rimraf: 3.0.2 + pretty-format: 29.7.0 slash: 3.0.0 strip-ansi: 6.0.1 transitivePeerDependencies: - - bufferutil - - canvas + - babel-plugin-macros - supports-color - ts-node - - utf-8-validate dev: true - /@jest/environment@26.6.2: - resolution: {integrity: sha512-nFy+fHl28zUrRsCeMB61VDThV1pVTtlEokBRgqPrcT1JNq4yRNIyTHfyht6PqtUvY9IsuLGTrbG8kPXjSZIZwA==} - engines: {node: '>= 10.14.2'} + /@jest/environment@29.7.0: + resolution: {integrity: sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jest/fake-timers': 26.6.2 - '@jest/types': 26.6.2 - '@types/node': 14.18.51 - jest-mock: 26.6.2 + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.14.9 + jest-mock: 29.7.0 dev: true - /@jest/fake-timers@26.6.2: - resolution: {integrity: sha512-14Uleatt7jdzefLPYM3KLcnUl1ZNikaKq34enpb5XG9i81JpppDb5muZvonvKyrl7ftEHkKS5L5/eB/kxJ+bvA==} - engines: {node: '>= 10.14.2'} + /@jest/expect-utils@29.7.0: + resolution: {integrity: sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jest/types': 26.6.2 - '@sinonjs/fake-timers': 6.0.1 - '@types/node': 14.18.51 - jest-message-util: 26.6.2 - jest-mock: 26.6.2 - jest-util: 26.6.2 + jest-get-type: 29.6.3 dev: true - /@jest/globals@26.6.2: - resolution: {integrity: sha512-85Ltnm7HlB/KesBUuALwQ68YTU72w9H2xW9FjZ1eL1U3lhtefjjl5c2MiUbpXt/i6LaPRvoOFJ22yCBSfQ0JIA==} - engines: {node: '>= 10.14.2'} + /@jest/expect@29.7.0: + resolution: {integrity: sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jest/environment': 26.6.2 - '@jest/types': 26.6.2 - expect: 26.6.2 + expect: 29.7.0 + jest-snapshot: 29.7.0 + transitivePeerDependencies: + - supports-color dev: true - /@jest/reporters@26.6.2: - resolution: {integrity: sha512-h2bW53APG4HvkOnVMo8q3QXa6pcaNt1HkwVsOPMBV6LD/q9oSpxNSYZQYkAnjdMjrJ86UuYeLo+aEZClV6opnw==} - engines: {node: '>= 10.14.2'} + /@jest/fake-timers@29.7.0: + resolution: {integrity: sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/types': 29.6.3 + '@sinonjs/fake-timers': 10.3.0 + '@types/node': 20.14.9 + jest-message-util: 29.7.0 + jest-mock: 29.7.0 + jest-util: 29.7.0 + dev: true + + /@jest/globals@29.7.0: + resolution: {integrity: sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/environment': 29.7.0 + '@jest/expect': 29.7.0 + '@jest/types': 29.6.3 + jest-mock: 29.7.0 + transitivePeerDependencies: + - supports-color + dev: true + + /@jest/reporters@29.7.0: + resolution: {integrity: sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true dependencies: '@bcoe/v8-coverage': 0.2.3 - '@jest/console': 26.6.2 - '@jest/test-result': 26.6.2 - '@jest/transform': 26.6.2 - '@jest/types': 26.6.2 + '@jest/console': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@jridgewell/trace-mapping': 0.3.18 + '@types/node': 20.14.9 chalk: 4.1.2 collect-v8-coverage: 1.0.1 exit: 0.1.2 glob: 7.2.3 graceful-fs: 4.2.11 istanbul-lib-coverage: 3.2.0 - istanbul-lib-instrument: 4.0.3 + istanbul-lib-instrument: 6.0.2 istanbul-lib-report: 3.0.0 istanbul-lib-source-maps: 4.0.1 istanbul-reports: 3.1.5 - jest-haste-map: 26.6.2 - jest-resolve: 26.6.2 - jest-util: 26.6.2 - jest-worker: 26.6.2 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + jest-worker: 29.7.0 slash: 3.0.0 - source-map: 0.6.1 string-length: 4.0.2 - terminal-link: 2.1.1 - v8-to-istanbul: 7.1.2 - optionalDependencies: - node-notifier: 8.0.2 + strip-ansi: 6.0.1 + v8-to-istanbul: 9.3.0 transitivePeerDependencies: - supports-color dev: true - /@jest/source-map@26.6.2: - resolution: {integrity: sha512-YwYcCwAnNmOVsZ8mr3GfnzdXDAl4LaenZP5z+G0c8bzC9/dugL8zRmxZzdoTl4IaS3CryS1uWnROLPFmb6lVvA==} - engines: {node: '>= 10.14.2'} + /@jest/schemas@29.6.3: + resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: + '@sinclair/typebox': 0.27.8 + dev: true + + /@jest/source-map@29.6.3: + resolution: {integrity: sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jridgewell/trace-mapping': 0.3.18 callsites: 3.1.0 graceful-fs: 4.2.11 - source-map: 0.6.1 dev: true - /@jest/test-result@26.6.2: - resolution: {integrity: sha512-5O7H5c/7YlojphYNrK02LlDIV2GNPYisKwHm2QTKjNZeEzezCbwYs9swJySv2UfPMyZ0VdsmMv7jIlD/IKYQpQ==} - engines: {node: '>= 10.14.2'} + /@jest/test-result@29.7.0: + resolution: {integrity: sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jest/console': 26.6.2 - '@jest/types': 26.6.2 + '@jest/console': 29.7.0 + '@jest/types': 29.6.3 '@types/istanbul-lib-coverage': 2.0.4 collect-v8-coverage: 1.0.1 dev: true - /@jest/test-sequencer@26.6.3: - resolution: {integrity: sha512-YHlVIjP5nfEyjlrSr8t/YdNfU/1XEt7c5b4OxcXCjyRhjzLYu/rO69/WHPuYcbCWkz8kAeZVZp2N2+IOLLEPGw==} - engines: {node: '>= 10.14.2'} + /@jest/test-sequencer@29.7.0: + resolution: {integrity: sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jest/test-result': 26.6.2 + '@jest/test-result': 29.7.0 graceful-fs: 4.2.11 - jest-haste-map: 26.6.2 - jest-runner: 26.6.3 - jest-runtime: 26.6.3 - transitivePeerDependencies: - - bufferutil - - canvas - - supports-color - - ts-node - - utf-8-validate + jest-haste-map: 29.7.0 + slash: 3.0.0 dev: true - /@jest/transform@26.6.2: - resolution: {integrity: sha512-E9JjhUgNzvuQ+vVAL21vlyfy12gP0GhazGgJC4h6qUt1jSdUXGWJ1wfu/X7Sd8etSgxV4ovT1pb9v5D6QW4XgA==} - engines: {node: '>= 10.14.2'} + /@jest/transform@29.7.0: + resolution: {integrity: sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@babel/core': 7.22.5 - '@jest/types': 26.6.2 + '@jest/types': 29.6.3 + '@jridgewell/trace-mapping': 0.3.18 babel-plugin-istanbul: 6.1.1 chalk: 4.1.2 - convert-source-map: 1.9.0 + convert-source-map: 2.0.0 fast-json-stable-stringify: 2.1.0 graceful-fs: 4.2.11 - jest-haste-map: 26.6.2 - jest-regex-util: 26.0.0 - jest-util: 26.6.2 + jest-haste-map: 29.7.0 + jest-regex-util: 29.6.3 + jest-util: 29.7.0 micromatch: 4.0.5 pirates: 4.0.6 slash: 3.0.0 - source-map: 0.6.1 - write-file-atomic: 3.0.3 + write-file-atomic: 4.0.2 transitivePeerDependencies: - supports-color dev: true - /@jest/types@26.6.2: - resolution: {integrity: sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==} - engines: {node: '>= 10.14.2'} + /@jest/types@29.6.3: + resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: + '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.4 '@types/istanbul-reports': 3.0.1 - '@types/node': 14.18.51 - '@types/yargs': 15.0.15 + '@types/node': 20.14.9 + '@types/yargs': 17.0.32 chalk: 4.1.2 dev: true @@ -893,6 +1348,15 @@ packages: '@jridgewell/trace-mapping': 0.3.18 dev: true + /@jridgewell/gen-mapping@0.3.5: + resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} + engines: {node: '>=6.0.0'} + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/trace-mapping': 0.3.25 + dev: true + /@jridgewell/resolve-uri@3.1.0: resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==} engines: {node: '>=6.0.0'} @@ -903,6 +1367,11 @@ packages: engines: {node: '>=6.0.0'} dev: true + /@jridgewell/set-array@1.2.1: + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} + dev: true + /@jridgewell/sourcemap-codec@1.4.14: resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==} dev: true @@ -918,6 +1387,13 @@ packages: '@jridgewell/sourcemap-codec': 1.4.14 dev: true + /@jridgewell/trace-mapping@0.3.25: + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + dependencies: + '@jridgewell/resolve-uri': 3.1.0 + '@jridgewell/sourcemap-codec': 1.4.15 + dev: true + /@jsdoc/salty@0.2.5: resolution: {integrity: sha512-TfRP53RqunNe2HBobVBJ0VLhK1HbfvBYeTC1ahnN64PWvyYyGebmMiPkuwvD9fpw2ZbkoPb8Q7mwy0aR8Z9rvw==} engines: {node: '>=v12.0.0'} @@ -925,31 +1401,25 @@ packages: lodash: 4.17.21 dev: false - /@lezer/common@1.0.3: - resolution: {integrity: sha512-JH4wAXCgUOcCGNekQPLhVeUtIqjH0yPBs7vvUdSjyQama9618IOKFJwkv2kcqdhF0my8hQEgCTEJU0GIgnahvA==} - dev: false - - /@lezer/common@1.0.4: - resolution: {integrity: sha512-lZHlk8p67x4aIDtJl6UQrXSOP6oi7dQR3W/geFVrENdA1JDaAJWldnVqVjPMJupbTKbzDfFcePfKttqVidS/dg==} + /@lezer/common@1.2.1: + resolution: {integrity: sha512-yemX0ZD2xS/73llMZIK6KplkjIjf2EvAHcinDi/TfJ9hS25G0388+ClHt6/3but0oOxinTcQHJLDXh6w1crzFQ==} dev: false /@lezer/highlight@1.1.6: resolution: {integrity: sha512-cmSJYa2us+r3SePpRCjN5ymCqCPv+zyXmDl0ciWtVaNiORT/MxM7ZgOMQZADD0o51qOaOg24qc/zBViOIwAjJg==} dependencies: - '@lezer/common': 1.0.3 + '@lezer/common': 1.2.1 dev: false /@lezer/lr@1.3.10: resolution: {integrity: sha512-BZfVvf7Re5BIwJHlZXbJn9L8lus5EonxQghyn+ih8Wl36XMFBPTXC0KM0IdUtj9w/diPHsKlXVgL+AlX2jYJ0Q==} dependencies: - '@lezer/common': 1.0.4 + '@lezer/common': 1.2.1 dev: false - /@lezer/lr@1.3.7: - resolution: {integrity: sha512-ssHKb3p0MxhJXT2i7UBmgAY1BIM3Uq/D772Qutu3EVmxWIyNMU12nQ0rL3Fhu+MiFtiTzyTmd3xGwEf3ON5PSA==} - dependencies: - '@lezer/common': 1.0.3 - dev: false + /@mdn/browser-compat-data@5.5.35: + resolution: {integrity: sha512-APtxt3S+a64EcXpG7E3a0bLx+CPqEcgN45FY/GEmbBBgX51AGIPkkYFy0JQDuOR0MFCozjo50q5Im74jflrkiQ==} + dev: true /@nodelib/fs.scandir@2.1.5: resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} @@ -972,6 +1442,18 @@ packages: fastq: 1.15.0 dev: true + /@pkgjs/parseargs@0.11.0: + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + requiresBuild: true + dev: true + optional: true + + /@pkgr/core@0.1.1: + resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + dev: true + /@popperjs/core@2.11.8: resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} dev: false @@ -1019,8 +1501,8 @@ packages: resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} dev: false - /@puppeteer/browsers@2.2.0: - resolution: {integrity: sha512-MC7LxpcBtdfTbzwARXIkqGZ1Osn3nnZJlm+i0+VqHl72t//Xwl9wICrXT8BwtgC6s1xJNHsxOpvzISUqe92+sw==} + /@puppeteer/browsers@2.2.3: + resolution: {integrity: sha512-bJ0UBsk0ESOs6RFcLXOt99a3yTDcOKlzfjad+rhFwdaG1Lu/Wzq58GHYCDTlZ9z6mldf4g+NTb+TXEfe0PpnsQ==} engines: {node: '>=18'} hasBin: true dependencies: @@ -1036,11 +1518,11 @@ packages: - supports-color dev: true - /@rollup/plugin-commonjs@24.1.0(rollup@2.79.1): - resolution: {integrity: sha512-eSL45hjhCWI0jCCXcNtLVqM5N1JlBGvlFfY0m6oOYnLCJ6N0qEXoZql4sY2MOUArzhH4SA/qBpTxvvZp2Sc+DQ==} - engines: {node: '>=14.0.0'} + /@rollup/plugin-commonjs@26.0.1(rollup@2.79.1): + resolution: {integrity: sha512-UnsKoZK6/aGIH6AdkptXhNvhaqftcjq3zZdT+LY5Ftms6JR06nADcDsYp5hTU9E2lbJUEOhdlY5J4DNTneM+jQ==} + engines: {node: '>=16.0.0 || 14 >= 14.17'} peerDependencies: - rollup: ^2.68.0||^3.0.0 + rollup: ^2.68.0||^3.0.0||^4.0.0 peerDependenciesMeta: rollup: optional: true @@ -1048,17 +1530,17 @@ packages: '@rollup/pluginutils': 5.0.2(rollup@2.79.1) commondir: 1.0.1 estree-walker: 2.0.2 - glob: 8.1.0 + glob: 10.4.2 is-reference: 1.2.1 - magic-string: 0.27.0 + magic-string: 0.30.10 rollup: 2.79.1 dev: true - /@rollup/plugin-node-resolve@15.1.0(rollup@2.79.1): - resolution: {integrity: sha512-xeZHCgsiZ9pzYVgAo9580eCGqwh/XCEUM9q6iQfGNocjgkufHAqC3exA+45URvhiYV8sBF9RlBai650eNs7AsA==} + /@rollup/plugin-node-resolve@15.2.3(rollup@2.79.1): + resolution: {integrity: sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==} engines: {node: '>=14.0.0'} peerDependencies: - rollup: ^2.78.0||^3.0.0 + rollup: ^2.78.0||^3.0.0||^4.0.0 peerDependenciesMeta: rollup: optional: true @@ -1093,27 +1575,31 @@ packages: rollup: optional: true dependencies: - '@types/estree': 1.0.1 + '@types/estree': 1.0.5 estree-walker: 2.0.2 picomatch: 2.3.1 rollup: 2.79.1 dev: true - /@sinonjs/commons@1.8.6: - resolution: {integrity: sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==} + /@sinclair/typebox@0.27.8: + resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + dev: true + + /@sinonjs/commons@3.0.1: + resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} dependencies: type-detect: 4.0.8 dev: true - /@sinonjs/fake-timers@6.0.1: - resolution: {integrity: sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==} + /@sinonjs/fake-timers@10.3.0: + resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} dependencies: - '@sinonjs/commons': 1.8.6 + '@sinonjs/commons': 3.0.1 dev: true - /@tootallnate/once@1.1.2: - resolution: {integrity: sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==} - engines: {node: '>= 6'} + /@tootallnate/once@2.0.0: + resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} + engines: {node: '>= 10'} dev: true /@tootallnate/quickjs-emscripten@0.23.0: @@ -1123,7 +1609,7 @@ packages: /@types/babel__core@7.20.1: resolution: {integrity: sha512-aACu/U/omhdk15O4Nfb+fHgH/z3QsfQzpnvRZhYhThms83ZnAOZz7zZAWO7mn2yyNQaA4xTO8GLK3uqFU4bYYw==} dependencies: - '@babel/parser': 7.22.5 + '@babel/parser': 7.24.7 '@babel/types': 7.22.5 '@types/babel__generator': 7.6.4 '@types/babel__template': 7.4.1 @@ -1133,35 +1619,31 @@ packages: /@types/babel__generator@7.6.4: resolution: {integrity: sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==} dependencies: - '@babel/types': 7.22.5 + '@babel/types': 7.24.7 dev: true /@types/babel__template@7.4.1: resolution: {integrity: sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==} dependencies: - '@babel/parser': 7.22.5 - '@babel/types': 7.22.5 + '@babel/parser': 7.24.7 + '@babel/types': 7.24.7 dev: true /@types/babel__traverse@7.20.1: resolution: {integrity: sha512-MitHFXnhtgwsGZWtT68URpOvLN4EREih1u3QtQiN4VdAxWKRVvGCSvw/Qth0M0Qq3pJpnGOu5JaM/ydK7OGbqg==} dependencies: - '@babel/types': 7.22.5 + '@babel/types': 7.24.7 dev: true - /@types/chrome@0.0.186: - resolution: {integrity: sha512-Ykpf95dbv0resO/PcRF/9vKETOKma5D2sSUKo8mSL1vz03IgVhyHuCrlzbDYMLrXIl9CcyGnYTMG2Zg0WAk62w==} + /@types/chrome@0.0.268: + resolution: {integrity: sha512-7N1QH9buudSJ7sI8Pe4mBHJr5oZ48s0hcanI9w3wgijAlv1OZNUZve9JR4x42dn5lJ5Sm87V1JNfnoh10EnQlA==} dependencies: - '@types/filesystem': 0.0.32 + '@types/filesystem': 0.0.36 '@types/har-format': 1.2.11 dev: false - /@types/clone@2.1.1: - resolution: {integrity: sha512-BZIU34bSYye0j/BFcPraiDZ5ka6MJADjcDVELGf7glr9K+iE8NYVjFslJFVWzskSxkLLyCrSPScE82/UUoBSvg==} - dev: false - - /@types/color-convert@1.9.0: - resolution: {integrity: sha512-OKGEfULrvSL2VRbkl/gnjjgbbF7ycIlpSsX7Nkab4MOWi5XxmgBYvuiQ7lcCFY5cPDz7MUNaKgxte2VRmtr4Fg==} + /@types/color-convert@2.0.3: + resolution: {integrity: sha512-2Q6wzrNiuEvYxVQqhh7sXM2mhIhvZR/Paq4FdsQkOMgWsCIkKvSGj8Le1/XalulrmgOzPMqNa0ix+ePY4hTrfg==} dependencies: '@types/color-name': 1.1.1 dev: false @@ -1170,15 +1652,22 @@ packages: resolution: {integrity: sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==} dev: false + /@types/eslint@8.56.10: + resolution: {integrity: sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==} + dependencies: + '@types/estree': 1.0.5 + '@types/json-schema': 7.0.12 + dev: true + /@types/estree@0.0.39: resolution: {integrity: sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==} dev: true - /@types/estree@1.0.1: - resolution: {integrity: sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==} + /@types/estree@1.0.5: + resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} - /@types/filesystem@0.0.32: - resolution: {integrity: sha512-Yuf4jR5YYMR2DVgwuCiP11s0xuVRyPKmz8vo6HBY3CGdeMj8af93CFZX+T82+VD1+UqHOxTq31lO7MI7lepBtQ==} + /@types/filesystem@0.0.36: + resolution: {integrity: sha512-vPDXOZuannb9FZdxgHnqSwAG/jvdGM8Wq+6N4D/d80z+D4HWH+bItqsZaVRQykAn6WEVeEkLm2oQigyHtgb0RA==} dependencies: '@types/filewriter': 0.0.29 dev: false @@ -1194,7 +1683,7 @@ packages: /@types/graceful-fs@4.1.6: resolution: {integrity: sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==} dependencies: - '@types/node': 14.18.51 + '@types/node': 20.14.9 dev: true /@types/har-format@1.2.11: @@ -1217,11 +1706,19 @@ packages: '@types/istanbul-lib-report': 3.0.0 dev: true - /@types/jest@26.0.24: - resolution: {integrity: sha512-E/X5Vib8BWqZNRlDxj9vYXhsDwPYbPINqKF9BsnSoon4RQ0D9moEuLD8txgyypFLH7J4+Lho9Nr/c8H0Fi+17w==} + /@types/jest@29.5.12: + resolution: {integrity: sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==} dependencies: - jest-diff: 26.6.2 - pretty-format: 26.6.2 + expect: 29.7.0 + pretty-format: 29.7.0 + dev: true + + /@types/jsdom@20.0.1: + resolution: {integrity: sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==} + dependencies: + '@types/node': 20.14.9 + '@types/tough-cookie': 4.0.5 + parse5: 7.1.2 dev: true /@types/json-schema@7.0.12: @@ -1243,63 +1740,57 @@ packages: resolution: {integrity: sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==} dev: false - /@types/mithril@2.0.12: - resolution: {integrity: sha512-vedzt04n3EB7rcnfSLCv3+w3qJLkGWdsNRBKvelTqhSJSfg73Roq9b+rcnn9zeqGYtQAMqNcO6vNBR/w0OzipQ==} + /@types/mithril@2.2.6: + resolution: {integrity: sha512-cU142pS/rDHpE0NlC2XJpejD2ihzMQ+jI7R0cOLCVWRQJFxwD9CUxJGdRmWu9DrGLr52G2QjMRCfXs+ew4LD2Q==} dev: false - /@types/node@14.18.51: - resolution: {integrity: sha512-P9bsdGFPpVtofEKlhWMVS2qqx1A/rt9QBfihWlklfHHpUpjtYse5AzFz6j4DWrARLYh6gRnw9+5+DJcrq3KvBA==} - - /@types/normalize-package-data@2.4.1: - resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==} - dev: true + /@types/node@20.14.9: + resolution: {integrity: sha512-06OCtnTXtWOZBJlRApleWndH4JsRVs1pDCc8dLSQp+7PpUpX3ePdHyeNSFTeSe7FtKyQkrlPvHwJOW3SLd8Oyg==} + dependencies: + undici-types: 5.26.5 - /@types/pako@1.0.4: - resolution: {integrity: sha512-Z+5bJSm28EXBSUJEgx29ioWeEEHUh6TiMkZHDhLwjc9wVFH+ressbkmX6waUZc5R3Gobn4Qu5llGxaoflZ+yhA==} + /@types/pako@2.0.3: + resolution: {integrity: sha512-bq0hMV9opAcrmE0Byyo0fY3Ew4tgOevJmQ9grUhpXQhYfyLJ1Kqg3P33JT5fdbT2AjeAjR51zqqVjAL/HMkx7Q==} dev: false - /@types/pixelmatch@5.2.4: - resolution: {integrity: sha512-HDaSHIAv9kwpMN7zlmwfTv6gax0PiporJOipcrGsVNF3Ba+kryOZc0Pio5pn6NhisgWr7TaajlPEKTbTAypIBQ==} + /@types/pixelmatch@5.2.6: + resolution: {integrity: sha512-wC83uexE5KGuUODn6zkm9gMzTwdY5L0chiK+VrKcDfEjzxh1uadlWTvOmAbCpnM9zx/Ww3f8uKlYQVnO/TrqVg==} dependencies: - '@types/node': 14.18.51 + '@types/node': 20.14.9 dev: true - /@types/pngjs@6.0.1: - resolution: {integrity: sha512-J39njbdW1U/6YyVXvC9+1iflZghP8jgRf2ndYghdJb5xL49LYDB+1EuAxfbuJ2IBbWIL3AjHPQhgaTxT3YaYeg==} + /@types/pngjs@6.0.5: + resolution: {integrity: sha512-0k5eKfrA83JOZPppLtS2C7OUtyNAl2wKNxfyYl9Q5g9lPkgBl/9hNyAu6HuEH2J4XmIv2znEpkDd0SaZVxW6iQ==} dependencies: - '@types/node': 14.18.51 + '@types/node': 20.14.9 dev: false - /@types/prettier@2.7.3: - resolution: {integrity: sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==} - dev: true - /@types/resolve@1.20.2: resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} dev: true - /@types/semver@7.5.0: - resolution: {integrity: sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==} - dev: true - /@types/stack-utils@2.0.1: resolution: {integrity: sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==} dev: true - /@types/uuid@8.3.4: - resolution: {integrity: sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==} + /@types/tough-cookie@4.0.5: + resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} + dev: true + + /@types/uuid@10.0.0: + resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==} dev: false - /@types/w3c-web-usb@1.0.6: - resolution: {integrity: sha512-cSjhgrr8g4KbPnnijAr/KJDNKa/bBa+ixYkywFRvrhvi9n1WEl7yYbtRyzE6jqNQiSxxJxoAW3STaOQwJHndaw==} + /@types/w3c-web-usb@1.0.10: + resolution: {integrity: sha512-CHgUI5kTc/QLMP8hODUHhge0D4vx+9UiAwIGiT0sTy/B2XpdX1U5rJt6JSISgr6ikRT7vxV9EVAFeYZqUnl1gQ==} dev: false /@types/yargs-parser@21.0.0: resolution: {integrity: sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==} dev: true - /@types/yargs@15.0.15: - resolution: {integrity: sha512-IziEYMU9XoVj8hWg7k+UJrXALkGFjWJhn5QFEv9q4p+v40oZhSuC135M38st8XPjICL7Ey4TV64ferBGUoJhBg==} + /@types/yargs@17.0.32: + resolution: {integrity: sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==} dependencies: '@types/yargs-parser': 21.0.0 dev: true @@ -1308,149 +1799,155 @@ packages: resolution: {integrity: sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==} requiresBuild: true dependencies: - '@types/node': 14.18.51 + '@types/node': 20.14.9 dev: true optional: true - /@typescript-eslint/eslint-plugin@5.60.0(@typescript-eslint/parser@5.60.0)(eslint@8.43.0)(typescript@5.0.4): - resolution: {integrity: sha512-78B+anHLF1TI8Jn/cD0Q00TBYdMgjdOn980JfAVa9yw5sop8nyTfVOQAv6LWywkOGLclDBtv5z3oxN4w7jxyNg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + /@typescript-eslint/eslint-plugin@7.14.1(@typescript-eslint/parser@7.14.1)(eslint@9.6.0)(typescript@5.5.2): + resolution: {integrity: sha512-aAJd6bIf2vvQRjUG3ZkNXkmBpN+J7Wd0mfQiiVCJMu9Z5GcZZdcc0j8XwN/BM97Fl7e3SkTXODSk4VehUv7CGw==} + engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: - '@typescript-eslint/parser': ^5.0.0 - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + '@typescript-eslint/parser': ^7.0.0 + eslint: ^8.56.0 typescript: '*' peerDependenciesMeta: typescript: optional: true dependencies: - '@eslint-community/regexpp': 4.5.1 - '@typescript-eslint/parser': 5.60.0(eslint@8.43.0)(typescript@5.0.4) - '@typescript-eslint/scope-manager': 5.60.0 - '@typescript-eslint/type-utils': 5.60.0(eslint@8.43.0)(typescript@5.0.4) - '@typescript-eslint/utils': 5.60.0(eslint@8.43.0)(typescript@5.0.4) - debug: 4.3.4 - eslint: 8.43.0 - grapheme-splitter: 1.0.4 - ignore: 5.2.4 - natural-compare-lite: 1.4.0 - semver: 7.5.3 - tsutils: 3.21.0(typescript@5.0.4) - typescript: 5.0.4 + '@eslint-community/regexpp': 4.11.0 + '@typescript-eslint/parser': 7.14.1(eslint@9.6.0)(typescript@5.5.2) + '@typescript-eslint/scope-manager': 7.14.1 + '@typescript-eslint/type-utils': 7.14.1(eslint@9.6.0)(typescript@5.5.2) + '@typescript-eslint/utils': 7.14.1(eslint@9.6.0)(typescript@5.5.2) + '@typescript-eslint/visitor-keys': 7.14.1 + eslint: 9.6.0 + graphemer: 1.4.0 + ignore: 5.3.1 + natural-compare: 1.4.0 + ts-api-utils: 1.3.0(typescript@5.5.2) + typescript: 5.5.2 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/parser@5.60.0(eslint@8.43.0)(typescript@5.0.4): - resolution: {integrity: sha512-jBONcBsDJ9UoTWrARkRRCgDz6wUggmH5RpQVlt7BimSwaTkTjwypGzKORXbR4/2Hqjk9hgwlon2rVQAjWNpkyQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + /@typescript-eslint/parser@7.14.1(eslint@9.6.0)(typescript@5.5.2): + resolution: {integrity: sha512-8lKUOebNLcR0D7RvlcloOacTOWzOqemWEWkKSVpMZVF/XVcwjPR+3MD08QzbW9TCGJ+DwIc6zUSGZ9vd8cO1IA==} + engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + eslint: ^8.56.0 typescript: '*' peerDependenciesMeta: typescript: optional: true dependencies: - '@typescript-eslint/scope-manager': 5.60.0 - '@typescript-eslint/types': 5.60.0 - '@typescript-eslint/typescript-estree': 5.60.0(typescript@5.0.4) + '@typescript-eslint/scope-manager': 7.14.1 + '@typescript-eslint/types': 7.14.1 + '@typescript-eslint/typescript-estree': 7.14.1(typescript@5.5.2) + '@typescript-eslint/visitor-keys': 7.14.1 debug: 4.3.4 - eslint: 8.43.0 - typescript: 5.0.4 + eslint: 9.6.0 + typescript: 5.5.2 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/scope-manager@5.60.0: - resolution: {integrity: sha512-hakuzcxPwXi2ihf9WQu1BbRj1e/Pd8ZZwVTG9kfbxAMZstKz8/9OoexIwnmLzShtsdap5U/CoQGRCWlSuPbYxQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + /@typescript-eslint/scope-manager@7.14.1: + resolution: {integrity: sha512-gPrFSsoYcsffYXTOZ+hT7fyJr95rdVe4kGVX1ps/dJ+DfmlnjFN/GcMxXcVkeHDKqsq6uAcVaQaIi3cFffmAbA==} + engines: {node: ^18.18.0 || >=20.0.0} dependencies: - '@typescript-eslint/types': 5.60.0 - '@typescript-eslint/visitor-keys': 5.60.0 + '@typescript-eslint/types': 7.14.1 + '@typescript-eslint/visitor-keys': 7.14.1 dev: true - /@typescript-eslint/type-utils@5.60.0(eslint@8.43.0)(typescript@5.0.4): - resolution: {integrity: sha512-X7NsRQddORMYRFH7FWo6sA9Y/zbJ8s1x1RIAtnlj6YprbToTiQnM6vxcMu7iYhdunmoC0rUWlca13D5DVHkK2g==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + /@typescript-eslint/type-utils@7.14.1(eslint@9.6.0)(typescript@5.5.2): + resolution: {integrity: sha512-/MzmgNd3nnbDbOi3LfasXWWe292+iuo+umJ0bCCMCPc1jLO/z2BQmWUUUXvXLbrQey/JgzdF/OV+I5bzEGwJkQ==} + engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: - eslint: '*' + eslint: ^8.56.0 typescript: '*' peerDependenciesMeta: typescript: optional: true dependencies: - '@typescript-eslint/typescript-estree': 5.60.0(typescript@5.0.4) - '@typescript-eslint/utils': 5.60.0(eslint@8.43.0)(typescript@5.0.4) + '@typescript-eslint/typescript-estree': 7.14.1(typescript@5.5.2) + '@typescript-eslint/utils': 7.14.1(eslint@9.6.0)(typescript@5.5.2) debug: 4.3.4 - eslint: 8.43.0 - tsutils: 3.21.0(typescript@5.0.4) - typescript: 5.0.4 + eslint: 9.6.0 + ts-api-utils: 1.3.0(typescript@5.5.2) + typescript: 5.5.2 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/types@5.60.0: - resolution: {integrity: sha512-ascOuoCpNZBccFVNJRSC6rPq4EmJ2NkuoKnd6LDNyAQmdDnziAtxbCGWCbefG1CNzmDvd05zO36AmB7H8RzKPA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + /@typescript-eslint/types@7.14.1: + resolution: {integrity: sha512-mL7zNEOQybo5R3AavY+Am7KLv8BorIv7HCYS5rKoNZKQD9tsfGUpO4KdAn3sSUvTiS4PQkr2+K0KJbxj8H9NDg==} + engines: {node: ^18.18.0 || >=20.0.0} dev: true - /@typescript-eslint/typescript-estree@5.60.0(typescript@5.0.4): - resolution: {integrity: sha512-R43thAuwarC99SnvrBmh26tc7F6sPa2B3evkXp/8q954kYL6Ro56AwASYWtEEi+4j09GbiNAHqYwNNZuNlARGQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + /@typescript-eslint/typescript-estree@7.14.1(typescript@5.5.2): + resolution: {integrity: sha512-k5d0VuxViE2ulIO6FbxxSZaxqDVUyMbXcidC8rHvii0I56XZPv8cq+EhMns+d/EVIL41sMXqRbK3D10Oza1bbA==} + engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: typescript: '*' peerDependenciesMeta: typescript: optional: true dependencies: - '@typescript-eslint/types': 5.60.0 - '@typescript-eslint/visitor-keys': 5.60.0 + '@typescript-eslint/types': 7.14.1 + '@typescript-eslint/visitor-keys': 7.14.1 debug: 4.3.4 globby: 11.1.0 is-glob: 4.0.3 - semver: 7.5.3 - tsutils: 3.21.0(typescript@5.0.4) - typescript: 5.0.4 + minimatch: 9.0.5 + semver: 7.6.0 + ts-api-utils: 1.3.0(typescript@5.5.2) + typescript: 5.5.2 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/utils@5.60.0(eslint@8.43.0)(typescript@5.0.4): - resolution: {integrity: sha512-ba51uMqDtfLQ5+xHtwlO84vkdjrqNzOnqrnwbMHMRY8Tqeme8C2Q8Fc7LajfGR+e3/4LoYiWXUM6BpIIbHJ4hQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + /@typescript-eslint/utils@7.14.1(eslint@9.6.0)(typescript@5.5.2): + resolution: {integrity: sha512-CMmVVELns3nak3cpJhZosDkm63n+DwBlDX8g0k4QUa9BMnF+lH2lr3d130M1Zt1xxmB3LLk3NV7KQCq86ZBBhQ==} + engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + eslint: ^8.56.0 dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.43.0) - '@types/json-schema': 7.0.12 - '@types/semver': 7.5.0 - '@typescript-eslint/scope-manager': 5.60.0 - '@typescript-eslint/types': 5.60.0 - '@typescript-eslint/typescript-estree': 5.60.0(typescript@5.0.4) - eslint: 8.43.0 - eslint-scope: 5.1.1 - semver: 7.5.3 + '@eslint-community/eslint-utils': 4.4.0(eslint@9.6.0) + '@typescript-eslint/scope-manager': 7.14.1 + '@typescript-eslint/types': 7.14.1 + '@typescript-eslint/typescript-estree': 7.14.1(typescript@5.5.2) + eslint: 9.6.0 transitivePeerDependencies: - supports-color - typescript dev: true - /@typescript-eslint/visitor-keys@5.60.0: - resolution: {integrity: sha512-wm9Uz71SbCyhUKgcaPRauBdTegUyY/ZWl8gLwD/i/ybJqscrrdVSFImpvUz16BLPChIeKBK5Fa9s6KDQjsjyWw==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + /@typescript-eslint/visitor-keys@7.14.1: + resolution: {integrity: sha512-Crb+F75U1JAEtBeQGxSKwI60hZmmzaqA3z9sYsVm8X7W5cwLEm5bRe0/uXS6+MR/y8CVpKSR/ontIAIEPFcEkA==} + engines: {node: ^18.18.0 || >=20.0.0} dependencies: - '@typescript-eslint/types': 5.60.0 - eslint-visitor-keys: 3.4.1 + '@typescript-eslint/types': 7.14.1 + eslint-visitor-keys: 3.4.3 dev: true /abab@2.0.6: resolution: {integrity: sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==} + deprecated: Use your platform's native atob() and btoa() methods instead dev: true - /acorn-globals@6.0.0: - resolution: {integrity: sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==} + /acorn-globals@7.0.1: + resolution: {integrity: sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==} dependencies: - acorn: 7.4.1 - acorn-walk: 7.2.0 + acorn: 8.12.0 + acorn-walk: 8.3.3 + dev: true + + /acorn-jsx@5.3.2(acorn@8.12.0): + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + dependencies: + acorn: 8.12.0 dev: true /acorn-jsx@5.3.2(acorn@8.9.0): @@ -1459,14 +1956,17 @@ packages: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: acorn: 8.9.0 + dev: false - /acorn-walk@7.2.0: - resolution: {integrity: sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==} + /acorn-walk@8.3.3: + resolution: {integrity: sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==} engines: {node: '>=0.4.0'} + dependencies: + acorn: 8.12.0 dev: true - /acorn@7.4.1: - resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==} + /acorn@8.12.0: + resolution: {integrity: sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==} engines: {node: '>=0.4.0'} hasBin: true dev: true @@ -1475,12 +1975,13 @@ packages: resolution: {integrity: sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==} engines: {node: '>=0.4.0'} hasBin: true + dev: false /agent-base@6.0.2: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} engines: {node: '>= 6.0.0'} dependencies: - debug: 4.3.4 + debug: 4.3.5 transitivePeerDependencies: - supports-color dev: true @@ -1489,7 +1990,7 @@ packages: resolution: {integrity: sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==} engines: {node: '>= 14'} dependencies: - debug: 4.3.4 + debug: 4.3.5 transitivePeerDependencies: - supports-color dev: true @@ -1514,6 +2015,11 @@ packages: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} + /ansi-regex@6.0.1: + resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} + engines: {node: '>=12'} + dev: true + /ansi-styles@3.2.1: resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} engines: {node: '>=4'} @@ -1527,13 +2033,14 @@ packages: dependencies: color-convert: 2.0.1 - /anymatch@2.0.0: - resolution: {integrity: sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==} - dependencies: - micromatch: 3.1.10 - normalize-path: 2.1.1 - transitivePeerDependencies: - - supports-color + /ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + dev: true + + /ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} dev: true /anymatch@3.1.3: @@ -1542,6 +2049,12 @@ packages: dependencies: normalize-path: 3.0.0 picomatch: 2.3.1 + dev: true + + /are-docs-informative@0.0.2: + resolution: {integrity: sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==} + engines: {node: '>=14'} + dev: true /argparse@1.0.10: resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} @@ -1552,41 +2065,22 @@ packages: /argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - /arr-diff@4.0.0: - resolution: {integrity: sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==} - engines: {node: '>=0.10.0'} - dev: true - - /arr-flatten@1.1.0: - resolution: {integrity: sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==} - engines: {node: '>=0.10.0'} - dev: true - - /arr-union@3.1.0: - resolution: {integrity: sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==} - engines: {node: '>=0.10.0'} - dev: true - /array-union@2.1.0: resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} engines: {node: '>=8'} dev: true - /array-unique@0.3.2: - resolution: {integrity: sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==} - engines: {node: '>=0.10.0'} - dev: true - - /assign-symbols@1.0.0: - resolution: {integrity: sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==} - engines: {node: '>=0.10.0'} + /ast-metadata-inferer@0.8.0: + resolution: {integrity: sha512-jOMKcHht9LxYIEQu+RVd22vtgrPaVCtDRQ/16IGmurdzxvYbDd5ynxjnyrzLnieG96eTcAyaoj/wN/4/1FyyeA==} + dependencies: + '@mdn/browser-compat-data': 5.5.35 dev: true /ast-types@0.13.4: resolution: {integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==} engines: {node: '>=4'} dependencies: - tslib: 2.5.3 + tslib: 2.6.3 dev: true /asynckit@0.4.0: @@ -1608,18 +2102,17 @@ packages: resolution: {integrity: sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw==} dev: true - /babel-jest@26.6.3(@babel/core@7.22.5): - resolution: {integrity: sha512-pl4Q+GAVOHwvjrck6jKjvmGhnO3jHX/xuB9d27f+EJZ/6k+6nMuPjorrYp7s++bKKdANwzElBWnLWaObvTnaZA==} - engines: {node: '>= 10.14.2'} + /babel-jest@29.7.0(@babel/core@7.22.5): + resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: - '@babel/core': ^7.0.0 + '@babel/core': ^7.8.0 dependencies: '@babel/core': 7.22.5 - '@jest/transform': 26.6.2 - '@jest/types': 26.6.2 + '@jest/transform': 29.7.0 '@types/babel__core': 7.20.1 babel-plugin-istanbul: 6.1.1 - babel-preset-jest: 26.6.2(@babel/core@7.22.5) + babel-preset-jest: 29.6.3(@babel/core@7.22.5) chalk: 4.1.2 graceful-fs: 4.2.11 slash: 3.0.0 @@ -1640,12 +2133,12 @@ packages: - supports-color dev: true - /babel-plugin-jest-hoist@26.6.2: - resolution: {integrity: sha512-PO9t0697lNTmcEHH69mdtYiOIkkOlj9fySqfO3K1eCcdISevLAE0xY59VLLUj0SoiPiTX/JU2CYFpILydUa5Lw==} - engines: {node: '>= 10.14.2'} + /babel-plugin-jest-hoist@29.6.3: + resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@babel/template': 7.22.5 - '@babel/types': 7.22.5 + '@babel/template': 7.24.7 + '@babel/types': 7.24.7 '@types/babel__core': 7.20.1 '@types/babel__traverse': 7.20.1 dev: true @@ -1670,14 +2163,14 @@ packages: '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.22.5) dev: true - /babel-preset-jest@26.6.2(@babel/core@7.22.5): - resolution: {integrity: sha512-YvdtlVm9t3k777c5NPQIv6cxFFFapys25HiUmuSgHwIZhfifweR5c5Sf5nwE3MAbfu327CYSvps8Yx6ANLyleQ==} - engines: {node: '>= 10.14.2'} + /babel-preset-jest@29.6.3(@babel/core@7.22.5): + resolution: {integrity: sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: '@babel/core': ^7.0.0 dependencies: '@babel/core': 7.22.5 - babel-plugin-jest-hoist: 26.6.2 + babel-plugin-jest-hoist: 29.6.3 babel-preset-current-node-syntax: 1.0.1(@babel/core@7.22.5) dev: true @@ -1719,19 +2212,6 @@ packages: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} dev: true - /base@0.11.2: - resolution: {integrity: sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==} - engines: {node: '>=0.10.0'} - dependencies: - cache-base: 1.0.1 - class-utils: 0.3.6 - component-emitter: 1.3.0 - define-property: 1.0.0 - isobject: 3.0.1 - mixin-deep: 1.3.2 - pascalcase: 0.1.1 - dev: true - /basic-ftp@5.0.3: resolution: {integrity: sha512-QHX8HLlncOLpy54mh+k/sWIFd0ThmRqwe9ZjELybGZK+tZ8rUb9VO0saKJUROTbE+KhzDUT7xziGpGrW8Kmd+g==} engines: {node: '>=10.0.0'} @@ -1740,7 +2220,7 @@ packages: /binary-extensions@2.2.0: resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} engines: {node: '>=8'} - dev: false + dev: true /bluebird@3.7.2: resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} @@ -1757,43 +2237,22 @@ packages: dependencies: balanced-match: 1.0.2 - /braces@2.3.2: - resolution: {integrity: sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==} - engines: {node: '>=0.10.0'} - dependencies: - arr-flatten: 1.1.0 - array-unique: 0.3.2 - extend-shallow: 2.0.1 - fill-range: 4.0.0 - isobject: 3.0.1 - repeat-element: 1.1.4 - snapdragon: 0.8.2 - snapdragon-node: 2.1.1 - split-string: 3.1.0 - to-regex: 3.0.2 - transitivePeerDependencies: - - supports-color - dev: true - /braces@3.0.2: resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} engines: {node: '>=8'} dependencies: fill-range: 7.0.1 - - /browser-process-hrtime@1.0.0: - resolution: {integrity: sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==} dev: true - /browserslist@4.21.9: - resolution: {integrity: sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg==} + /browserslist@4.23.1: + resolution: {integrity: sha512-TUfofFo/KsK/bWZ9TWQ5O26tsWW4Uhmt8IYklbnUa70udB6P2wA7w7o4PY4muaEPBQaAX+CEnmmIA41NVHtPVw==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true dependencies: - caniuse-lite: 1.0.30001507 - electron-to-chromium: 1.4.440 - node-releases: 2.0.12 - update-browserslist-db: 1.0.11(browserslist@4.21.9) + caniuse-lite: 1.0.30001638 + electron-to-chromium: 1.4.814 + node-releases: 2.0.14 + update-browserslist-db: 1.0.16(browserslist@4.23.1) dev: true /bser@2.1.1: @@ -1822,21 +2281,6 @@ packages: engines: {node: '>=6'} dev: true - /cache-base@1.0.1: - resolution: {integrity: sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==} - engines: {node: '>=0.10.0'} - dependencies: - collection-visit: 1.0.0 - component-emitter: 1.3.0 - get-value: 2.0.6 - has-value: 1.0.0 - isobject: 3.0.1 - set-value: 2.0.1 - to-object-path: 0.3.0 - union-value: 1.0.1 - unset-value: 1.0.0 - dev: true - /call-bind@1.0.2: resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} dependencies: @@ -1859,15 +2303,8 @@ packages: engines: {node: '>=10'} dev: true - /caniuse-lite@1.0.30001507: - resolution: {integrity: sha512-SFpUDoSLCaE5XYL2jfqe9ova/pbQHEmbheDf5r4diNwbAgR3qxM9NQtfsiSscjqoya5K7kFcHPUQ+VsUkIJR4A==} - dev: true - - /capture-exit@2.0.0: - resolution: {integrity: sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g==} - engines: {node: 6.* || 8.* || >= 10.*} - dependencies: - rsvp: 4.8.5 + /caniuse-lite@1.0.30001638: + resolution: {integrity: sha512-5SuJUJ7cZnhPpeLHaH0c/HPAnAHZvS6ElWyHK9GSIbVOQABLzowiI2pjmpvZ1WEbkyz46iFd4UXlOHR5SqgfMQ==} dev: true /catharsis@0.9.0: @@ -1911,43 +2348,26 @@ packages: readdirp: 3.6.0 optionalDependencies: fsevents: 2.3.3 - dev: false + dev: true - /chromium-bidi@0.5.13(devtools-protocol@0.0.1262051): - resolution: {integrity: sha512-OHbYCetDxdW/xmlrafgOiLsIrw4Sp1BEeolbZ1UGJO5v/nekQOJBj/Kzyw6sqKcAVabUTo0GS3cTYgr6zIf00g==} + /chromium-bidi@0.5.24(devtools-protocol@0.0.1299070): + resolution: {integrity: sha512-5xQNN2SVBdZv4TxeMLaI+PelrnZsHDhn8h2JtyriLr+0qHcZS8BMuo93qN6J1VmtmrgYP+rmcLHcbpnA8QJh+w==} peerDependencies: devtools-protocol: '*' dependencies: - devtools-protocol: 0.0.1262051 + devtools-protocol: 0.0.1299070 mitt: 3.0.1 urlpattern-polyfill: 10.0.0 - zod: 3.22.4 - dev: true - - /ci-info@2.0.0: - resolution: {integrity: sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==} + zod: 3.23.8 dev: true - /cjs-module-lexer@0.6.0: - resolution: {integrity: sha512-uc2Vix1frTfnuzxxu1Hp4ktSvM3QaI4oXl4ZUqL1wjTu/BGki9TrCWoqLTg/drR1KwAEarXuRFCG2Svr1GxPFw==} - dev: true - - /class-utils@0.3.6: - resolution: {integrity: sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==} - engines: {node: '>=0.10.0'} - dependencies: - arr-union: 3.1.0 - define-property: 0.2.5 - isobject: 3.0.1 - static-extend: 0.1.2 + /ci-info@3.9.0: + resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} + engines: {node: '>=8'} dev: true - /cliui@6.0.0: - resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} - dependencies: - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrap-ansi: 6.2.0 + /cjs-module-lexer@1.3.1: + resolution: {integrity: sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q==} dev: true /cliui@8.0.1: @@ -1958,26 +2378,21 @@ packages: strip-ansi: 6.0.1 wrap-ansi: 7.0.0 - /clone@2.1.2: - resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==} - engines: {node: '>=0.8'} - dev: false - /co@4.6.0: resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} dev: true - /codemirror@6.0.1(@lezer/common@1.0.4): + /codemirror@6.0.1(@lezer/common@1.2.1): resolution: {integrity: sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==} dependencies: - '@codemirror/autocomplete': 6.8.1(@codemirror/language@6.8.0)(@codemirror/state@6.2.1)(@codemirror/view@6.18.1)(@lezer/common@1.0.4) - '@codemirror/commands': 6.2.4 - '@codemirror/language': 6.8.0 - '@codemirror/lint': 6.3.0 - '@codemirror/search': 6.5.0 - '@codemirror/state': 6.2.1 - '@codemirror/view': 6.18.1 + '@codemirror/autocomplete': 6.16.3(@codemirror/language@6.9.0)(@codemirror/state@6.4.1)(@codemirror/view@6.28.2)(@lezer/common@1.2.1) + '@codemirror/commands': 6.6.0 + '@codemirror/language': 6.9.0 + '@codemirror/lint': 6.8.1 + '@codemirror/search': 6.5.6 + '@codemirror/state': 6.4.1 + '@codemirror/view': 6.28.2 transitivePeerDependencies: - '@lezer/common' dev: false @@ -1986,14 +2401,6 @@ packages: resolution: {integrity: sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==} dev: true - /collection-visit@1.0.0: - resolution: {integrity: sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==} - engines: {node: '>=0.10.0'} - dependencies: - map-visit: 1.0.0 - object-visit: 1.0.1 - dev: true - /color-convert@1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} dependencies: @@ -2029,12 +2436,13 @@ packages: engines: {node: '>= 10'} dev: false - /commondir@1.0.1: - resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} + /comment-parser@1.4.1: + resolution: {integrity: sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==} + engines: {node: '>= 12.0.0'} dev: true - /component-emitter@1.3.0: - resolution: {integrity: sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==} + /commondir@1.0.1: + resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} dev: true /concat-map@0.0.1: @@ -2044,12 +2452,11 @@ packages: resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} dev: true - /copy-descriptor@0.1.1: - resolution: {integrity: sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==} - engines: {node: '>=0.10.0'} + /convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} dev: true - /cosmiconfig@9.0.0(typescript@5.0.4): + /cosmiconfig@9.0.0(typescript@5.5.2): resolution: {integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==} engines: {node: '>=14'} peerDependencies: @@ -2062,24 +2469,32 @@ packages: import-fresh: 3.3.0 js-yaml: 4.1.0 parse-json: 5.2.0 - typescript: 5.0.4 + typescript: 5.5.2 + dev: true + + /create-jest@29.7.0(@types/node@20.14.9): + resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + dependencies: + '@jest/types': 29.6.3 + chalk: 4.1.2 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-config: 29.7.0(@types/node@20.14.9) + jest-util: 29.7.0 + prompts: 2.4.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node dev: true /crelt@1.0.6: resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==} dev: false - /cross-spawn@6.0.5: - resolution: {integrity: sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==} - engines: {node: '>=4.8'} - dependencies: - nice-try: 1.0.5 - path-key: 2.0.1 - semver: 5.7.1 - shebang-command: 1.2.0 - which: 1.3.1 - dev: true - /cross-spawn@7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} @@ -2089,12 +2504,16 @@ packages: which: 2.0.2 dev: true + /cssfontparser@1.2.1: + resolution: {integrity: sha512-6tun4LoZnj7VN6YeegOVb67KBX/7JJsqvj+pv3ZA7F878/eN33AbGa5b/S/wXxS/tcp8nc40xRUrsPlxIyNUPg==} + dev: true + /cssom@0.3.8: resolution: {integrity: sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==} dev: true - /cssom@0.4.4: - resolution: {integrity: sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==} + /cssom@0.5.0: + resolution: {integrity: sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==} dev: true /cssstyle@2.3.0: @@ -2104,13 +2523,6 @@ packages: cssom: 0.3.8 dev: true - /d3-array@3.2.2: - resolution: {integrity: sha512-yEEyEAbDrF8C6Ob2myOBLjwBLck1Z89jMGFee0oPsn95GqjerpaOA4ch+vc2l0FNFFwMD5N7OCSEN5eAlsUbgQ==} - engines: {node: '>=12'} - dependencies: - internmap: 2.0.3 - dev: false - /d3-array@3.2.4: resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} engines: {node: '>=12'} @@ -2198,6 +2610,14 @@ packages: engines: {node: '>=12'} dev: false + /d3-scale-chromatic@3.1.0: + resolution: {integrity: sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==} + engines: {node: '>=12'} + dependencies: + d3-color: 3.1.0 + d3-interpolate: 3.0.1 + dev: false + /d3-scale@4.0.2: resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} engines: {node: '>=12'} @@ -2240,28 +2660,29 @@ packages: engines: {node: '>= 14'} dev: true - /data-urls@2.0.0: - resolution: {integrity: sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==} - engines: {node: '>=10'} + /data-urls@3.0.2: + resolution: {integrity: sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==} + engines: {node: '>=12'} dependencies: abab: 2.0.6 - whatwg-mimetype: 2.3.0 - whatwg-url: 8.7.0 + whatwg-mimetype: 3.0.0 + whatwg-url: 11.0.0 dev: true - /debug@2.6.9: - resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + /debug@4.3.4: + resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} + engines: {node: '>=6.0'} peerDependencies: supports-color: '*' peerDependenciesMeta: supports-color: optional: true dependencies: - ms: 2.0.0 + ms: 2.1.2 dev: true - /debug@4.3.4: - resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} + /debug@4.3.5: + resolution: {integrity: sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==} engines: {node: '>=6.0'} peerDependencies: supports-color: '*' @@ -2272,11 +2693,6 @@ packages: ms: 2.1.2 dev: true - /decamelize@1.2.0: - resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} - engines: {node: '>=0.10.0'} - dev: true - /decimal.js@10.4.3: resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==} dev: true @@ -2286,6 +2702,15 @@ packages: engines: {node: '>=0.10'} dev: true + /dedent@1.5.3: + resolution: {integrity: sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==} + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + dev: true + /deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -2294,28 +2719,6 @@ packages: engines: {node: '>=0.10.0'} dev: true - /define-property@0.2.5: - resolution: {integrity: sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==} - engines: {node: '>=0.10.0'} - dependencies: - is-descriptor: 0.1.6 - dev: true - - /define-property@1.0.0: - resolution: {integrity: sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==} - engines: {node: '>=0.10.0'} - dependencies: - is-descriptor: 1.0.2 - dev: true - - /define-property@2.0.2: - resolution: {integrity: sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==} - engines: {node: '>=0.10.0'} - dependencies: - is-descriptor: 1.0.2 - isobject: 3.0.1 - dev: true - /degenerator@5.0.1: resolution: {integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==} engines: {node: '>= 14'} @@ -2341,17 +2744,17 @@ packages: engines: {node: '>=8'} 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==} + /devtools-protocol@0.0.1299070: + resolution: {integrity: sha512-+qtL3eX50qsJ7c+qVyagqi7AWMoQCBGNfoyJZMwm/NSXVqLYbuitrWEEIzxfUmTNy7//Xe8yhMmQ+elj3uAqSg==} dev: true - /diff-sequences@26.6.2: - resolution: {integrity: sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==} - engines: {node: '>= 10.14.2'} + /devtools-protocol@0.0.1319565: + resolution: {integrity: sha512-Xc1Xrng6tSt5t0IIFCMeYUIqKEbvJVnKakfUCCq9WIk5m+9SbrPIXFtGcwwos8DucDyViEzwjy6PgIZWGUldyQ==} + dev: false + + /diff-sequences@29.6.3: + resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dev: true /dingusjs@0.0.3: @@ -2365,265 +2768,97 @@ packages: path-type: 4.0.0 dev: true - /doctrine@3.0.0: - resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} - engines: {node: '>=6.0.0'} + /domexception@4.0.0: + resolution: {integrity: sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==} + engines: {node: '>=12'} + deprecated: Use your platform's native DOMException instead dependencies: - esutils: 2.0.3 + webidl-conversions: 7.0.0 dev: true - /domexception@2.0.1: - resolution: {integrity: sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==} - engines: {node: '>=8'} - dependencies: - webidl-conversions: 5.0.0 + /eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} dev: true - /electron-to-chromium@1.4.440: - resolution: {integrity: sha512-r6dCgNpRhPwiWlxbHzZQ/d9swfPaEJGi8ekqRBwQYaR3WmA5VkqQfBWSDDjuJU1ntO+W9tHx8OHV/96Q8e0dVw==} + /electron-to-chromium@1.4.814: + resolution: {integrity: sha512-GVulpHjFu1Y9ZvikvbArHmAhZXtm3wHlpjTMcXNGKl4IQ4jMQjlnz8yMQYYqdLHKi/jEL2+CBC2akWVCoIGUdw==} dev: true - /emittery@0.7.2: - resolution: {integrity: sha512-A8OG5SR/ij3SsJdWDJdkkSYUjQdCUx6APQXem0SaEePBSRg4eymGYwBkKo1Y6DU+af/Jn2dBQqDBvjnr9Vi8nQ==} - engines: {node: '>=10'} + /emittery@0.13.1: + resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} + engines: {node: '>=12'} dev: true /emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + /emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + dev: true + /end-of-stream@1.4.4: resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} dependencies: once: 1.4.0 dev: true - /entities@2.1.0: - 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: - is-arrayish: 0.2.1 - dev: true - - /esbuild-android-64@0.15.18: - resolution: {integrity: sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==} - engines: {node: '>=12'} - cpu: [x64] - os: [android] - requiresBuild: true - dev: false - optional: true - - /esbuild-android-arm64@0.15.18: - resolution: {integrity: sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] - requiresBuild: true - dev: false - optional: true - - /esbuild-darwin-64@0.15.18: - resolution: {integrity: sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - requiresBuild: true - dev: false - optional: true - - /esbuild-darwin-arm64@0.15.18: - resolution: {integrity: sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - requiresBuild: true - dev: false - optional: true - - /esbuild-freebsd-64@0.15.18: - resolution: {integrity: sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==} - engines: {node: '>=12'} - cpu: [x64] - os: [freebsd] - requiresBuild: true - dev: false - optional: true - - /esbuild-freebsd-arm64@0.15.18: - resolution: {integrity: sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] - requiresBuild: true - dev: false - optional: true - - /esbuild-linux-32@0.15.18: - resolution: {integrity: sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==} - engines: {node: '>=12'} - cpu: [ia32] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /esbuild-linux-64@0.15.18: - resolution: {integrity: sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /esbuild-linux-arm64@0.15.18: - resolution: {integrity: sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /esbuild-linux-arm@0.15.18: - resolution: {integrity: sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==} - engines: {node: '>=12'} - cpu: [arm] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /esbuild-linux-mips64le@0.15.18: - resolution: {integrity: sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==} - engines: {node: '>=12'} - cpu: [mips64el] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /esbuild-linux-ppc64le@0.15.18: - resolution: {integrity: sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /esbuild-linux-riscv64@0.15.18: - resolution: {integrity: sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==} - engines: {node: '>=12'} - cpu: [riscv64] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /esbuild-linux-s390x@0.15.18: - resolution: {integrity: sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==} - engines: {node: '>=12'} - cpu: [s390x] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /esbuild-netbsd-64@0.15.18: - resolution: {integrity: sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==} - engines: {node: '>=12'} - cpu: [x64] - os: [netbsd] - requiresBuild: true - dev: false - optional: true - - /esbuild-openbsd-64@0.15.18: - resolution: {integrity: sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [openbsd] - requiresBuild: true - dev: false - optional: true - - /esbuild-sunos-64@0.15.18: - resolution: {integrity: sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==} - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] - requiresBuild: true - dev: false - optional: true - - /esbuild-windows-32@0.15.18: - resolution: {integrity: sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==} - engines: {node: '>=12'} - cpu: [ia32] - os: [win32] - requiresBuild: true - dev: false - optional: true - - /esbuild-windows-64@0.15.18: - resolution: {integrity: sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - requiresBuild: true - dev: false - optional: true - - /esbuild-windows-arm64@0.15.18: - resolution: {integrity: sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - requiresBuild: true + /entities@2.1.0: + resolution: {integrity: sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==} dev: false - optional: true - /esbuild@0.15.18: - resolution: {integrity: sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==} + /entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + dev: true + + /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: + is-arrayish: 0.2.1 + dev: true + + /es-module-lexer@1.5.4: + resolution: {integrity: sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==} + dev: true + + /esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} engines: {node: '>=12'} hasBin: true requiresBuild: true optionalDependencies: - '@esbuild/android-arm': 0.15.18 - '@esbuild/linux-loong64': 0.15.18 - esbuild-android-64: 0.15.18 - esbuild-android-arm64: 0.15.18 - esbuild-darwin-64: 0.15.18 - esbuild-darwin-arm64: 0.15.18 - esbuild-freebsd-64: 0.15.18 - esbuild-freebsd-arm64: 0.15.18 - esbuild-linux-32: 0.15.18 - esbuild-linux-64: 0.15.18 - esbuild-linux-arm: 0.15.18 - esbuild-linux-arm64: 0.15.18 - esbuild-linux-mips64le: 0.15.18 - esbuild-linux-ppc64le: 0.15.18 - esbuild-linux-riscv64: 0.15.18 - esbuild-linux-s390x: 0.15.18 - esbuild-netbsd-64: 0.15.18 - esbuild-openbsd-64: 0.15.18 - esbuild-sunos-64: 0.15.18 - esbuild-windows-32: 0.15.18 - esbuild-windows-64: 0.15.18 - esbuild-windows-arm64: 0.15.18 - dev: false - - /escalade@3.1.1: - resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + dev: false + + /escalade@3.1.2: + resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} engines: {node: '>=6'} /escape-string-regexp@1.0.5: @@ -2653,19 +2888,6 @@ packages: source-map: 0.6.1 dev: false - /escodegen@2.0.0: - resolution: {integrity: sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==} - engines: {node: '>=6.0'} - hasBin: true - dependencies: - esprima: 4.0.1 - estraverse: 5.3.0 - esutils: 2.0.3 - optionator: 0.8.3 - optionalDependencies: - source-map: 0.6.1 - dev: true - /escodegen@2.1.0: resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==} engines: {node: '>=6.0'} @@ -2678,90 +2900,130 @@ packages: source-map: 0.6.1 dev: true - /eslint-config-google@0.14.0(eslint@8.43.0): + /eslint-config-google@0.14.0(eslint@9.6.0): resolution: {integrity: sha512-WsbX4WbjuMvTdeVL6+J3rK1RGhCTqjsFjX7UMSMgZiyxxaNLkoJENbrGExzERFeoTpGw3F3FypTiWAP9ZXzkEw==} engines: {node: '>=0.10.0'} peerDependencies: eslint: '>=5.16.0' dependencies: - eslint: 8.43.0 + eslint: 9.6.0 dev: true - /eslint-scope@5.1.1: - resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} - engines: {node: '>=8.0.0'} + /eslint-plugin-compat@5.0.0(eslint@9.6.0): + resolution: {integrity: sha512-29KNWyFkUbNVf6TIKVe9SVCGCtHjML3HnUg9C8LG2GsXf7miAeBOgdMc1n2B5n0sHUzg1/A4IFly7Jyf1gSbgQ==} + engines: {node: '>=14.x'} + peerDependencies: + eslint: ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: - esrecurse: 4.3.0 - estraverse: 4.3.0 + '@mdn/browser-compat-data': 5.5.35 + ast-metadata-inferer: 0.8.0 + browserslist: 4.23.1 + caniuse-lite: 1.0.30001638 + eslint: 9.6.0 + find-up: 5.0.0 + globals: 13.24.0 + lodash.memoize: 4.1.2 + semver: 7.6.0 dev: true - /eslint-scope@7.2.0: - resolution: {integrity: sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + /eslint-plugin-jsdoc@48.5.0(eslint@9.6.0): + resolution: {integrity: sha512-ukXPNpGby3KjCveCizIS8t1EbuJEHYEu/tBg8GCbn/YbHcXwphyvYCdvRZ/oMRfTscGSSzfsWoZ+ZkAP0/6YMQ==} + engines: {node: '>=18'} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 || ^9.0.0 + dependencies: + '@es-joy/jsdoccomment': 0.43.1 + are-docs-informative: 0.0.2 + comment-parser: 1.4.1 + debug: 4.3.4 + escape-string-regexp: 4.0.0 + eslint: 9.6.0 + esquery: 1.5.0 + parse-imports: 2.1.0 + semver: 7.6.2 + spdx-expression-parse: 4.0.0 + synckit: 0.9.0 + transitivePeerDependencies: + - supports-color + dev: true + + /eslint-scope@8.0.1: + resolution: {integrity: sha512-pL8XjgP4ZOmmwfFE8mEhSxA7ZY4C+LWyqjQ3o4yWkkmD0qcMT9kkW3zWHOczhWcjTSgqycYAgwSlXvZltv65og==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dependencies: esrecurse: 4.3.0 estraverse: 5.3.0 dev: true - /eslint-visitor-keys@3.4.1: - resolution: {integrity: sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==} + /eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - /eslint@8.43.0: - resolution: {integrity: sha512-aaCpf2JqqKesMFGgmRPessmVKjcGXqdlAYLLC3THM8t5nBRZRQ+st5WM/hoJXkdioEXLLbXgclUpM0TXo5HX5Q==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + /eslint-visitor-keys@4.0.0: + resolution: {integrity: sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + dev: true + + /eslint@9.6.0: + resolution: {integrity: sha512-ElQkdLMEEqQNM9Njff+2Y4q2afHk7JpkPvrd7Xh7xefwgQynqPxwf55J7di9+MEibWUGdNjFF9ITG9Pck5M84w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.43.0) - '@eslint-community/regexpp': 4.5.1 - '@eslint/eslintrc': 2.0.3 - '@eslint/js': 8.43.0 - '@humanwhocodes/config-array': 0.11.10 + '@eslint-community/eslint-utils': 4.4.0(eslint@9.6.0) + '@eslint-community/regexpp': 4.11.0 + '@eslint/config-array': 0.17.0 + '@eslint/eslintrc': 3.1.0 + '@eslint/js': 9.6.0 '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.3.0 '@nodelib/fs.walk': 1.2.8 ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 debug: 4.3.4 - doctrine: 3.0.0 escape-string-regexp: 4.0.0 - eslint-scope: 7.2.0 - eslint-visitor-keys: 3.4.1 - espree: 9.5.2 + eslint-scope: 8.0.1 + eslint-visitor-keys: 4.0.0 + espree: 10.1.0 esquery: 1.5.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 - file-entry-cache: 6.0.1 + file-entry-cache: 8.0.0 find-up: 5.0.0 glob-parent: 6.0.2 - globals: 13.20.0 - graphemer: 1.4.0 - ignore: 5.2.4 - import-fresh: 3.3.0 + ignore: 5.3.1 imurmurhash: 0.1.4 is-glob: 4.0.3 is-path-inside: 3.0.3 - js-yaml: 4.1.0 json-stable-stringify-without-jsonify: 1.0.1 levn: 0.4.1 lodash.merge: 4.6.2 minimatch: 3.1.2 natural-compare: 1.4.0 - optionator: 0.9.1 + optionator: 0.9.4 strip-ansi: 6.0.1 - strip-json-comments: 3.1.1 text-table: 0.2.0 transitivePeerDependencies: - supports-color dev: true + /espree@10.1.0: + resolution: {integrity: sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + dependencies: + acorn: 8.12.0 + acorn-jsx: 5.3.2(acorn@8.12.0) + eslint-visitor-keys: 4.0.0 + dev: true + /espree@9.5.2: resolution: {integrity: sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: acorn: 8.9.0 acorn-jsx: 5.3.2(acorn@8.9.0) - eslint-visitor-keys: 3.4.1 + eslint-visitor-keys: 3.4.3 + dev: false /esprima@4.0.1: resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} @@ -2785,6 +3047,7 @@ packages: /estraverse@4.3.0: resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} engines: {node: '>=4.0'} + dev: false /estraverse@5.3.0: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} @@ -2811,30 +3074,13 @@ packages: engines: {node: '>=0.8.x'} dev: false - /exec-sh@0.3.6: - resolution: {integrity: sha512-nQn+hI3yp+oD0huYhKwvYI32+JFeq+XkNcD1GAo3Y/MjxsfVGmrrzrnzjWiNY6f+pUCP440fThsFh5gZrRAU/w==} - dev: true - - /execa@1.0.0: - resolution: {integrity: sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==} - engines: {node: '>=6'} - dependencies: - cross-spawn: 6.0.5 - get-stream: 4.1.0 - is-stream: 1.1.0 - npm-run-path: 2.0.2 - p-finally: 1.0.0 - signal-exit: 3.0.7 - strip-eof: 1.0.0 - dev: true - - /execa@4.1.0: - resolution: {integrity: sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==} + /execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} dependencies: cross-spawn: 7.0.3 - get-stream: 5.2.0 - human-signals: 1.1.1 + get-stream: 6.0.1 + human-signals: 2.1.0 is-stream: 2.0.1 merge-stream: 2.0.0 npm-run-path: 4.0.1 @@ -2848,62 +3094,15 @@ packages: engines: {node: '>= 0.8.0'} dev: true - /expand-brackets@2.1.4: - resolution: {integrity: sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==} - engines: {node: '>=0.10.0'} - dependencies: - debug: 2.6.9 - define-property: 0.2.5 - extend-shallow: 2.0.1 - posix-character-classes: 0.1.1 - regex-not: 1.0.2 - snapdragon: 0.8.2 - to-regex: 3.0.2 - transitivePeerDependencies: - - supports-color - dev: true - - /expect@26.6.2: - resolution: {integrity: sha512-9/hlOBkQl2l/PLHJx6JjoDF6xPKcJEsUlWKb23rKE7KzeDqUZKXKNMW27KIue5JMdBV9HgmoJPcc8HtO85t9IA==} - engines: {node: '>= 10.14.2'} - dependencies: - '@jest/types': 26.6.2 - ansi-styles: 4.3.0 - jest-get-type: 26.3.0 - jest-matcher-utils: 26.6.2 - jest-message-util: 26.6.2 - jest-regex-util: 26.0.0 - dev: true - - /extend-shallow@2.0.1: - resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} - engines: {node: '>=0.10.0'} - dependencies: - is-extendable: 0.1.1 - dev: true - - /extend-shallow@3.0.2: - resolution: {integrity: sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==} - engines: {node: '>=0.10.0'} - dependencies: - assign-symbols: 1.0.0 - is-extendable: 1.0.1 - dev: true - - /extglob@2.0.4: - resolution: {integrity: sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==} - engines: {node: '>=0.10.0'} + /expect@29.7.0: + resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - array-unique: 0.3.2 - define-property: 1.0.0 - expand-brackets: 2.1.4 - extend-shallow: 2.0.1 - fragment-cache: 0.2.1 - regex-not: 1.0.2 - snapdragon: 0.8.2 - to-regex: 3.0.2 - transitivePeerDependencies: - - supports-color + '@jest/expect-utils': 29.7.0 + jest-get-type: 29.6.3 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-util: 29.7.0 dev: true /extract-zip@2.0.1: @@ -2922,6 +3121,7 @@ packages: /fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + dev: true /fast-fifo@1.2.0: resolution: {integrity: sha512-NcvQXt7Cky1cNau15FWy64IjuO8X0JijhTBBrJj1YlxlDfRkJXNaK9RFUjwpfDPzMdv7wB38jr53l9tkNLxnWg==} @@ -2940,6 +3140,7 @@ packages: /fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + dev: true /fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} @@ -2962,21 +3163,11 @@ packages: pend: 1.2.0 dev: true - /file-entry-cache@6.0.1: - resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} - engines: {node: ^10.12.0 || >=12.0.0} + /file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} dependencies: - flat-cache: 3.0.4 - dev: true - - /fill-range@4.0.0: - resolution: {integrity: sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==} - engines: {node: '>=0.10.0'} - dependencies: - extend-shallow: 2.0.1 - is-number: 3.0.0 - repeat-string: 1.6.1 - to-regex-range: 2.1.1 + flat-cache: 4.0.1 dev: true /fill-range@7.0.1: @@ -2984,6 +3175,7 @@ packages: engines: {node: '>=8'} dependencies: to-regex-range: 5.0.1 + dev: true /find-up@4.1.0: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} @@ -3001,16 +3193,16 @@ packages: path-exists: 4.0.0 dev: true - /flat-cache@3.0.4: - resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==} - engines: {node: ^10.12.0 || >=12.0.0} + /flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} dependencies: - flatted: 3.2.7 - rimraf: 3.0.2 + flatted: 3.3.1 + keyv: 4.5.4 dev: true - /flatted@3.2.7: - resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==} + /flatted@3.3.1: + resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} dev: true /for-each@0.3.3: @@ -3019,13 +3211,16 @@ packages: is-callable: 1.2.7 dev: false - /for-in@1.0.2: - resolution: {integrity: sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==} - engines: {node: '>=0.10.0'} + /foreground-child@3.2.1: + resolution: {integrity: sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==} + engines: {node: '>=14'} + dependencies: + cross-spawn: 7.0.3 + signal-exit: 4.1.0 dev: true - /form-data@3.0.1: - resolution: {integrity: sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==} + /form-data@4.0.0: + resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} engines: {node: '>= 6'} dependencies: asynckit: 0.4.0 @@ -3033,13 +3228,6 @@ packages: mime-types: 2.1.35 dev: true - /fragment-cache@0.2.1: - resolution: {integrity: sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==} - engines: {node: '>=0.10.0'} - dependencies: - map-cache: 0.2.2 - dev: true - /fs-extra@8.1.0: resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} engines: {node: '>=6 <7 || >=8'} @@ -3057,6 +3245,7 @@ packages: engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] requiresBuild: true + dev: true optional: true /function-bind@1.1.1: @@ -3085,13 +3274,6 @@ packages: engines: {node: '>=8.0.0'} dev: true - /get-stream@4.1.0: - resolution: {integrity: sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==} - engines: {node: '>=6'} - dependencies: - pump: 3.0.0 - dev: true - /get-stream@5.2.0: resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} engines: {node: '>=8'} @@ -3099,28 +3281,29 @@ packages: pump: 3.0.0 dev: true + /get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + dev: true + /get-uri@6.0.1: resolution: {integrity: sha512-7ZqONUVqaabogsYNWlYj0t3YZaL6dhuEueZXGF+/YVmf6dHmaFg8/6psJKqhx9QykIDKzpGcy2cn4oV4YC7V/Q==} engines: {node: '>= 14'} dependencies: basic-ftp: 5.0.3 data-uri-to-buffer: 5.0.1 - debug: 4.3.4 + debug: 4.3.5 fs-extra: 8.1.0 transitivePeerDependencies: - supports-color dev: true - /get-value@2.0.6: - resolution: {integrity: sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==} - engines: {node: '>=0.10.0'} - dev: true - /glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} dependencies: is-glob: 4.0.3 + dev: true /glob-parent@6.0.2: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} @@ -3129,8 +3312,22 @@ packages: is-glob: 4.0.3 dev: true + /glob@10.4.2: + resolution: {integrity: sha512-GwMlUF6PkPo3Gk21UxkCohOv0PLcIXVtKyLlpEI28R/cO/4eNOdmLk3CMW1wROV/WR/EsZOWAfBbBOqYvs88/w==} + engines: {node: '>=16 || 14 >=14.18'} + hasBin: true + dependencies: + foreground-child: 3.2.1 + jackspeak: 3.4.0 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.0 + path-scurry: 1.11.1 + dev: true + /glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported dependencies: fs.realpath: 1.0.0 inflight: 1.0.6 @@ -3148,19 +3345,30 @@ packages: inherits: 2.0.4 minimatch: 5.1.6 once: 1.4.0 + dev: false /globals@11.12.0: resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} engines: {node: '>=4'} dev: true - /globals@13.20.0: - resolution: {integrity: sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==} + /globals@13.24.0: + resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} engines: {node: '>=8'} dependencies: type-fest: 0.20.2 dev: true + /globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + dev: true + + /globals@15.6.0: + resolution: {integrity: sha512-UzcJi88Hw//CurUIRa9Jxb0vgOCcuD/MNjwmXp633cyaRKkCWACkoqHCtfZv43b1kqXGg/fpOa8bwgacCeXsVg==} + engines: {node: '>=18'} + dev: true + /globby@11.1.0: resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} engines: {node: '>=10'} @@ -3168,7 +3376,7 @@ packages: array-union: 2.1.0 dir-glob: 3.0.1 fast-glob: 3.2.12 - ignore: 5.2.4 + ignore: 5.3.1 merge2: 1.4.1 slash: 3.0.0 dev: true @@ -3182,20 +3390,10 @@ packages: /graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - /grapheme-splitter@1.0.4: - resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} - dev: true - /graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} dev: true - /growly@1.3.0: - resolution: {integrity: sha512-+xGQY0YyAWCnqy7Cd++hc2JqMYzlm0dG30Jd0beaA64sROr8C4nt8Yc9V5Ro3avlSUDTN0ulqP/VBKi1/lLygw==} - requiresBuild: true - dev: true - optional: true - /has-flag@3.0.0: resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} engines: {node: '>=4'} @@ -3222,69 +3420,34 @@ packages: has-symbols: 1.0.3 dev: false - /has-value@0.3.1: - resolution: {integrity: sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==} - engines: {node: '>=0.10.0'} - dependencies: - get-value: 2.0.6 - has-values: 0.1.4 - isobject: 2.1.0 - dev: true - - /has-value@1.0.0: - resolution: {integrity: sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw==} - engines: {node: '>=0.10.0'} - dependencies: - get-value: 2.0.6 - has-values: 1.0.0 - isobject: 3.0.1 - dev: true - - /has-values@0.1.4: - resolution: {integrity: sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ==} - engines: {node: '>=0.10.0'} - dev: true - - /has-values@1.0.0: - resolution: {integrity: sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ==} - engines: {node: '>=0.10.0'} - dependencies: - is-number: 3.0.0 - kind-of: 4.0.0 - dev: true - /has@1.0.3: resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} engines: {node: '>= 0.4.0'} dependencies: function-bind: 1.1.1 - /hosted-git-info@2.8.9: - resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} - dev: true - /hsluv@0.1.0: resolution: {integrity: sha512-ERcanKLAszD2XN3Vh5r5Szkrv9q0oSTudmP0rkiKAGM/3NMc9FLmMZBB7TSqTaXJfSDBOreYTfjezCOYbRKqlw==} dev: false - /html-encoding-sniffer@2.0.1: - resolution: {integrity: sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==} - engines: {node: '>=10'} + /html-encoding-sniffer@3.0.0: + resolution: {integrity: sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==} + engines: {node: '>=12'} dependencies: - whatwg-encoding: 1.0.5 + whatwg-encoding: 2.0.0 dev: true /html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} dev: true - /http-proxy-agent@4.0.1: - resolution: {integrity: sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==} + /http-proxy-agent@5.0.0: + resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==} engines: {node: '>= 6'} dependencies: - '@tootallnate/once': 1.1.2 + '@tootallnate/once': 2.0.0 agent-base: 6.0.2 - debug: 4.3.4 + debug: 4.3.5 transitivePeerDependencies: - supports-color dev: true @@ -3294,7 +3457,7 @@ packages: engines: {node: '>= 14'} dependencies: agent-base: 7.1.0 - debug: 4.3.4 + debug: 4.3.5 transitivePeerDependencies: - supports-color dev: true @@ -3304,7 +3467,7 @@ packages: engines: {node: '>= 6'} dependencies: agent-base: 6.0.2 - debug: 4.3.4 + debug: 4.3.5 transitivePeerDependencies: - supports-color dev: true @@ -3314,21 +3477,14 @@ packages: engines: {node: '>= 14'} dependencies: agent-base: 7.1.0 - debug: 4.3.4 + debug: 4.3.5 transitivePeerDependencies: - supports-color dev: true - /human-signals@1.1.1: - resolution: {integrity: sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==} - engines: {node: '>=8.12.0'} - dev: true - - /iconv-lite@0.4.24: - resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} - engines: {node: '>=0.10.0'} - dependencies: - safer-buffer: 2.1.2 + /human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} dev: true /iconv-lite@0.6.3: @@ -3336,7 +3492,6 @@ packages: engines: {node: '>=0.10.0'} dependencies: safer-buffer: 2.1.2 - dev: false /ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} @@ -3347,13 +3502,18 @@ packages: engines: {node: '>= 4'} dev: true - /immer@9.0.21: - resolution: {integrity: sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==} + /ignore@5.3.1: + resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} + engines: {node: '>= 4'} + dev: true + + /immer@10.1.1: + resolution: {integrity: sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==} dev: false /immutable@4.3.0: resolution: {integrity: sha512-0AOCmOip+xgJwEVTQj1EfiDDOkPmuyllDuTuEX+DDXUgapLAsBIfkg3sxCYyCEA8mQqZrrxPUGjcOQ2JS3WLkg==} - dev: false + dev: true /import-fresh@3.3.0: resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} @@ -3395,20 +3555,6 @@ packages: resolution: {integrity: sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==} dev: true - /is-accessor-descriptor@0.1.6: - resolution: {integrity: sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==} - engines: {node: '>=0.10.0'} - dependencies: - kind-of: 3.2.2 - dev: true - - /is-accessor-descriptor@1.0.0: - resolution: {integrity: sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==} - engines: {node: '>=0.10.0'} - dependencies: - kind-of: 6.0.3 - dev: true - /is-arguments@1.1.1: resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==} engines: {node: '>= 0.4'} @@ -3426,10 +3572,6 @@ packages: engines: {node: '>=8'} dependencies: binary-extensions: 2.2.0 - dev: false - - /is-buffer@1.1.6: - resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==} dev: true /is-builtin-module@3.2.1: @@ -3444,74 +3586,16 @@ packages: engines: {node: '>= 0.4'} dev: false - /is-ci@2.0.0: - resolution: {integrity: sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==} - hasBin: true - dependencies: - ci-info: 2.0.0 - dev: true - /is-core-module@2.12.1: resolution: {integrity: sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==} dependencies: has: 1.0.3 dev: true - /is-data-descriptor@0.1.4: - resolution: {integrity: sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==} - engines: {node: '>=0.10.0'} - dependencies: - kind-of: 3.2.2 - dev: true - - /is-data-descriptor@1.0.0: - resolution: {integrity: sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==} - engines: {node: '>=0.10.0'} - dependencies: - kind-of: 6.0.3 - dev: true - - /is-descriptor@0.1.6: - resolution: {integrity: sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==} - engines: {node: '>=0.10.0'} - dependencies: - is-accessor-descriptor: 0.1.6 - is-data-descriptor: 0.1.4 - kind-of: 5.1.0 - dev: true - - /is-descriptor@1.0.2: - resolution: {integrity: sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==} - engines: {node: '>=0.10.0'} - dependencies: - is-accessor-descriptor: 1.0.0 - is-data-descriptor: 1.0.0 - kind-of: 6.0.3 - dev: true - - /is-docker@2.2.1: - resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} - engines: {node: '>=8'} - hasBin: true - requiresBuild: true - dev: true - optional: true - - /is-extendable@0.1.1: - resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==} - engines: {node: '>=0.10.0'} - dev: true - - /is-extendable@1.0.1: - resolution: {integrity: sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==} - engines: {node: '>=0.10.0'} - dependencies: - is-plain-object: 2.0.4 - dev: true - /is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} + dev: true /is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} @@ -3534,34 +3618,22 @@ packages: engines: {node: '>=0.10.0'} dependencies: is-extglob: 2.1.1 + dev: true /is-module@1.0.0: resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==} dev: true - /is-number@3.0.0: - resolution: {integrity: sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==} - engines: {node: '>=0.10.0'} - dependencies: - kind-of: 3.2.2 - dev: true - /is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} + dev: true /is-path-inside@3.0.3: resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} engines: {node: '>=8'} dev: true - /is-plain-object@2.0.4: - resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==} - engines: {node: '>=0.10.0'} - dependencies: - isobject: 3.0.1 - dev: true - /is-potential-custom-element-name@1.0.1: resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} dev: true @@ -3569,12 +3641,7 @@ packages: /is-reference@1.2.1: resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} dependencies: - '@types/estree': 1.0.1 - dev: true - - /is-stream@1.1.0: - resolution: {integrity: sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==} - engines: {node: '>=0.10.0'} + '@types/estree': 1.0.5 dev: true /is-stream@2.0.1: @@ -3593,42 +3660,8 @@ packages: has-tostringtag: 1.0.0 dev: false - /is-typedarray@1.0.0: - resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==} - dev: true - - /is-windows@1.0.2: - resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} - engines: {node: '>=0.10.0'} - dev: true - - /is-wsl@2.2.0: - resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} - engines: {node: '>=8'} - requiresBuild: true - dependencies: - is-docker: 2.2.1 - dev: true - optional: true - - /isarray@1.0.0: - resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} - dev: true - - /isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - dev: true - - /isobject@2.1.0: - resolution: {integrity: sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==} - engines: {node: '>=0.10.0'} - dependencies: - isarray: 1.0.0 - dev: true - - /isobject@3.0.1: - resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==} - engines: {node: '>=0.10.0'} + /isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} dev: true /istanbul-lib-coverage@3.2.0: @@ -3636,27 +3669,28 @@ packages: engines: {node: '>=8'} dev: true - /istanbul-lib-instrument@4.0.3: - resolution: {integrity: sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==} + /istanbul-lib-instrument@5.2.1: + resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} engines: {node: '>=8'} dependencies: - '@babel/core': 7.22.5 + '@babel/core': 7.24.7 + '@babel/parser': 7.24.7 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.0 - semver: 6.3.0 + semver: 6.3.1 transitivePeerDependencies: - supports-color dev: true - /istanbul-lib-instrument@5.2.1: - resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} - engines: {node: '>=8'} + /istanbul-lib-instrument@6.0.2: + resolution: {integrity: sha512-1WUsZ9R1lA0HtBSohTkm39WTPlNKSJ5iFk7UwqXkBLoHQT+hfqPsfsTDVuZdKGaBwn7din9bS7SsnoAr943hvw==} + engines: {node: '>=10'} dependencies: - '@babel/core': 7.22.5 - '@babel/parser': 7.22.5 + '@babel/core': 7.24.7 + '@babel/parser': 7.24.7 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.0 - semver: 6.3.0 + semver: 7.6.2 transitivePeerDependencies: - supports-color dev: true @@ -3674,7 +3708,7 @@ packages: resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} engines: {node: '>=10'} dependencies: - debug: 4.3.4 + debug: 4.3.5 istanbul-lib-coverage: 3.2.0 source-map: 0.6.1 transitivePeerDependencies: @@ -3689,233 +3723,263 @@ packages: istanbul-lib-report: 3.0.0 dev: true - /jest-changed-files@26.6.2: - resolution: {integrity: sha512-fDS7szLcY9sCtIip8Fjry9oGf3I2ht/QT21bAHm5Dmf0mD4X3ReNUf17y+bO6fR8WgbIZTlbyG1ak/53cbRzKQ==} - engines: {node: '>= 10.14.2'} + /jackspeak@3.4.0: + resolution: {integrity: sha512-JVYhQnN59LVPFCEcVa2C3CrEKYacvjRfqIQl+h8oi91aLYQVWRYbxjPcv1bUiUy/kLmQaANrYfNMCO3kuEDHfw==} + engines: {node: '>=14'} + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + dev: true + + /jest-canvas-mock@2.5.2: + resolution: {integrity: sha512-vgnpPupjOL6+L5oJXzxTxFrlGEIbHdZqFU+LFNdtLxZ3lRDCl17FlTMM7IatoRQkrcyOTMlDinjUguqmQ6bR2A==} + dependencies: + cssfontparser: 1.2.1 + moo-color: 1.0.3 + dev: true + + /jest-changed-files@29.7.0: + resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + execa: 5.1.1 + jest-util: 29.7.0 + p-limit: 3.1.0 + dev: true + + /jest-circus@29.7.0: + resolution: {integrity: sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jest/types': 26.6.2 - execa: 4.1.0 - throat: 5.0.0 + '@jest/environment': 29.7.0 + '@jest/expect': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.14.9 + chalk: 4.1.2 + co: 4.6.0 + dedent: 1.5.3 + is-generator-fn: 2.1.0 + jest-each: 29.7.0 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-runtime: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + p-limit: 3.1.0 + pretty-format: 29.7.0 + pure-rand: 6.1.0 + slash: 3.0.0 + stack-utils: 2.0.6 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color dev: true - /jest-cli@26.6.3: - resolution: {integrity: sha512-GF9noBSa9t08pSyl3CY4frMrqp+aQXFGFkf5hEPbh/pIUFYWMK6ZLTfbmadxJVcJrdRoChlWQsA2VkJcDFK8hg==} - engines: {node: '>= 10.14.2'} + /jest-cli@29.7.0(@types/node@20.14.9): + resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true dependencies: - '@jest/core': 26.6.3 - '@jest/test-result': 26.6.2 - '@jest/types': 26.6.2 + '@jest/core': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 chalk: 4.1.2 + create-jest: 29.7.0(@types/node@20.14.9) exit: 0.1.2 - graceful-fs: 4.2.11 import-local: 3.1.0 - is-ci: 2.0.0 - jest-config: 26.6.3 - jest-util: 26.6.2 - jest-validate: 26.6.2 - prompts: 2.4.2 - yargs: 15.4.1 + jest-config: 29.7.0(@types/node@20.14.9) + jest-util: 29.7.0 + jest-validate: 29.7.0 + yargs: 17.7.2 transitivePeerDependencies: - - bufferutil - - canvas + - '@types/node' + - babel-plugin-macros - supports-color - ts-node - - utf-8-validate dev: true - /jest-config@26.6.3: - resolution: {integrity: sha512-t5qdIj/bCj2j7NFVHb2nFB4aUdfucDn3JRKgrZnplb8nieAirAzRSHP8uDEd+qV6ygzg9Pz4YG7UTJf94LPSyg==} - engines: {node: '>= 10.14.2'} + /jest-config@29.7.0(@types/node@20.14.9): + resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: + '@types/node': '*' ts-node: '>=9.0.0' peerDependenciesMeta: + '@types/node': + optional: true ts-node: optional: true dependencies: '@babel/core': 7.22.5 - '@jest/test-sequencer': 26.6.3 - '@jest/types': 26.6.2 - babel-jest: 26.6.3(@babel/core@7.22.5) + '@jest/test-sequencer': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.14.9 + babel-jest: 29.7.0(@babel/core@7.22.5) chalk: 4.1.2 + ci-info: 3.9.0 deepmerge: 4.3.1 glob: 7.2.3 graceful-fs: 4.2.11 - jest-environment-jsdom: 26.6.2 - jest-environment-node: 26.6.2 - jest-get-type: 26.3.0 - jest-jasmine2: 26.6.3 - jest-regex-util: 26.0.0 - jest-resolve: 26.6.2 - jest-util: 26.6.2 - jest-validate: 26.6.2 + jest-circus: 29.7.0 + jest-environment-node: 29.7.0 + jest-get-type: 29.6.3 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-runner: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 micromatch: 4.0.5 - pretty-format: 26.6.2 + parse-json: 5.2.0 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-json-comments: 3.1.1 transitivePeerDependencies: - - bufferutil - - canvas + - babel-plugin-macros - supports-color - - utf-8-validate dev: true - /jest-diff@26.6.2: - resolution: {integrity: sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA==} - engines: {node: '>= 10.14.2'} + /jest-diff@29.7.0: + resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: chalk: 4.1.2 - diff-sequences: 26.6.2 - jest-get-type: 26.3.0 - pretty-format: 26.6.2 + diff-sequences: 29.6.3 + jest-get-type: 29.6.3 + pretty-format: 29.7.0 dev: true - /jest-docblock@26.0.0: - resolution: {integrity: sha512-RDZ4Iz3QbtRWycd8bUEPxQsTlYazfYn/h5R65Fc6gOfwozFhoImx+affzky/FFBuqISPTqjXomoIGJVKBWoo0w==} - engines: {node: '>= 10.14.2'} + /jest-docblock@29.7.0: + resolution: {integrity: sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: detect-newline: 3.1.0 dev: true - /jest-each@26.6.2: - resolution: {integrity: sha512-Mer/f0KaATbjl8MCJ+0GEpNdqmnVmDYqCTJYTvoo7rqmRiDllmp2AYN+06F93nXcY3ur9ShIjS+CO/uD+BbH4A==} - engines: {node: '>= 10.14.2'} + /jest-each@29.7.0: + resolution: {integrity: sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jest/types': 26.6.2 + '@jest/types': 29.6.3 chalk: 4.1.2 - jest-get-type: 26.3.0 - jest-util: 26.6.2 - pretty-format: 26.6.2 - dev: true - - /jest-environment-jsdom@26.6.2: - resolution: {integrity: sha512-jgPqCruTlt3Kwqg5/WVFyHIOJHsiAvhcp2qiR2QQstuG9yWox5+iHpU3ZrcBxW14T4fe5Z68jAfLRh7joCSP2Q==} - engines: {node: '>= 10.14.2'} - dependencies: - '@jest/environment': 26.6.2 - '@jest/fake-timers': 26.6.2 - '@jest/types': 26.6.2 - '@types/node': 14.18.51 - jest-mock: 26.6.2 - jest-util: 26.6.2 - jsdom: 16.7.0 + jest-get-type: 29.6.3 + jest-util: 29.7.0 + pretty-format: 29.7.0 + dev: true + + /jest-environment-jsdom@29.7.0: + resolution: {integrity: sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + canvas: ^2.5.0 + peerDependenciesMeta: + canvas: + optional: true + dependencies: + '@jest/environment': 29.7.0 + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/jsdom': 20.0.1 + '@types/node': 20.14.9 + jest-mock: 29.7.0 + jest-util: 29.7.0 + jsdom: 20.0.3 transitivePeerDependencies: - bufferutil - - canvas - supports-color - utf-8-validate dev: true - /jest-environment-node@26.6.2: - resolution: {integrity: sha512-zhtMio3Exty18dy8ee8eJ9kjnRyZC1N4C1Nt/VShN1apyXc8rWGtJ9lI7vqiWcyyXS4BVSEn9lxAM2D+07/Tag==} - engines: {node: '>= 10.14.2'} + /jest-environment-node@29.7.0: + resolution: {integrity: sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jest/environment': 26.6.2 - '@jest/fake-timers': 26.6.2 - '@jest/types': 26.6.2 - '@types/node': 14.18.51 - jest-mock: 26.6.2 - jest-util: 26.6.2 + '@jest/environment': 29.7.0 + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.14.9 + jest-mock: 29.7.0 + jest-util: 29.7.0 dev: true - /jest-get-type@26.3.0: - resolution: {integrity: sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==} - engines: {node: '>= 10.14.2'} + /jest-get-type@29.6.3: + resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dev: true - /jest-haste-map@26.6.2: - resolution: {integrity: sha512-easWIJXIw71B2RdR8kgqpjQrbMRWQBgiBwXYEhtGUTaX+doCjBheluShdDMeR8IMfJiTqH4+zfhtg29apJf/8w==} - engines: {node: '>= 10.14.2'} + /jest-haste-map@29.7.0: + resolution: {integrity: sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jest/types': 26.6.2 + '@jest/types': 29.6.3 '@types/graceful-fs': 4.1.6 - '@types/node': 14.18.51 + '@types/node': 20.14.9 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 - jest-regex-util: 26.0.0 - jest-serializer: 26.6.2 - jest-util: 26.6.2 - jest-worker: 26.6.2 + jest-regex-util: 29.6.3 + jest-util: 29.7.0 + jest-worker: 29.7.0 micromatch: 4.0.5 - sane: 4.1.0 walker: 1.0.8 optionalDependencies: fsevents: 2.3.3 - transitivePeerDependencies: - - supports-color dev: true - /jest-jasmine2@26.6.3: - resolution: {integrity: sha512-kPKUrQtc8aYwBV7CqBg5pu+tmYXlvFlSFYn18ev4gPFtrRzB15N2gW/Roew3187q2w2eHuu0MU9TJz6w0/nPEg==} - engines: {node: '>= 10.14.2'} + /jest-leak-detector@29.7.0: + resolution: {integrity: sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@babel/traverse': 7.22.5 - '@jest/environment': 26.6.2 - '@jest/source-map': 26.6.2 - '@jest/test-result': 26.6.2 - '@jest/types': 26.6.2 - '@types/node': 14.18.51 - chalk: 4.1.2 - co: 4.6.0 - expect: 26.6.2 - is-generator-fn: 2.1.0 - jest-each: 26.6.2 - jest-matcher-utils: 26.6.2 - jest-message-util: 26.6.2 - jest-runtime: 26.6.3 - jest-snapshot: 26.6.2 - jest-util: 26.6.2 - pretty-format: 26.6.2 - throat: 5.0.0 - transitivePeerDependencies: - - bufferutil - - canvas - - supports-color - - ts-node - - utf-8-validate + jest-get-type: 29.6.3 + pretty-format: 29.7.0 dev: true - /jest-leak-detector@26.6.2: - resolution: {integrity: sha512-i4xlXpsVSMeKvg2cEKdfhh0H39qlJlP5Ex1yQxwF9ubahboQYMgTtz5oML35AVA3B4Eu+YsmwaiKVev9KCvLxg==} - engines: {node: '>= 10.14.2'} - dependencies: - jest-get-type: 26.3.0 - pretty-format: 26.6.2 + /jest-localstorage-mock@2.4.26: + resolution: {integrity: sha512-owAJrYnjulVlMIXOYQIPRCCn3MmqI3GzgfZCXdD3/pmwrIvFMXcKVWZ+aMc44IzaASapg0Z4SEFxR+v5qxDA2w==} + engines: {node: '>=6.16.0'} dev: true - /jest-matcher-utils@26.6.2: - resolution: {integrity: sha512-llnc8vQgYcNqDrqRDXWwMr9i7rS5XFiCwvh6DTP7Jqa2mqpcCBBlpCbn+trkG0KNhPu/h8rzyBkriOtBstvWhw==} - engines: {node: '>= 10.14.2'} + /jest-matcher-utils@29.7.0: + resolution: {integrity: sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: chalk: 4.1.2 - jest-diff: 26.6.2 - jest-get-type: 26.3.0 - pretty-format: 26.6.2 + jest-diff: 29.7.0 + jest-get-type: 29.6.3 + pretty-format: 29.7.0 dev: true - /jest-message-util@26.6.2: - resolution: {integrity: sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA==} - engines: {node: '>= 10.14.2'} + /jest-message-util@29.7.0: + resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@babel/code-frame': 7.22.5 - '@jest/types': 26.6.2 + '@babel/code-frame': 7.24.7 + '@jest/types': 29.6.3 '@types/stack-utils': 2.0.1 chalk: 4.1.2 graceful-fs: 4.2.11 micromatch: 4.0.5 - pretty-format: 26.6.2 + pretty-format: 29.7.0 slash: 3.0.0 stack-utils: 2.0.6 dev: true - /jest-mock@26.6.2: - resolution: {integrity: sha512-YyFjePHHp1LzpzYcmgqkJ0nm0gg/lJx2aZFzFy1S6eUqNjXsOqTK10zNRff2dNfssgokjkG65OlWNcIlgd3zew==} - engines: {node: '>= 10.14.2'} + /jest-mock@29.7.0: + resolution: {integrity: sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jest/types': 26.6.2 - '@types/node': 14.18.51 + '@jest/types': 29.6.3 + '@types/node': 20.14.9 + jest-util: 29.7.0 dev: true - /jest-pnp-resolver@1.2.3(jest-resolve@26.6.2): + /jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==} engines: {node: '>=6'} peerDependencies: @@ -3924,203 +3988,201 @@ packages: jest-resolve: optional: true dependencies: - jest-resolve: 26.6.2 + jest-resolve: 29.7.0 dev: true - /jest-regex-util@26.0.0: - resolution: {integrity: sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A==} - engines: {node: '>= 10.14.2'} + /jest-regex-util@29.6.3: + resolution: {integrity: sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dev: true - /jest-resolve-dependencies@26.6.3: - resolution: {integrity: sha512-pVwUjJkxbhe4RY8QEWzN3vns2kqyuldKpxlxJlzEYfKSvY6/bMvxoFrYYzUO1Gx28yKWN37qyV7rIoIp2h8fTg==} - engines: {node: '>= 10.14.2'} + /jest-resolve-dependencies@29.7.0: + resolution: {integrity: sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jest/types': 26.6.2 - jest-regex-util: 26.0.0 - jest-snapshot: 26.6.2 + jest-regex-util: 29.6.3 + jest-snapshot: 29.7.0 transitivePeerDependencies: - supports-color dev: true - /jest-resolve@26.6.2: - resolution: {integrity: sha512-sOxsZOq25mT1wRsfHcbtkInS+Ek7Q8jCHUB0ZUTP0tc/c41QHriU/NunqMfCUWsL4H3MHpvQD4QR9kSYhS7UvQ==} - engines: {node: '>= 10.14.2'} + /jest-resolve@29.7.0: + resolution: {integrity: sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jest/types': 26.6.2 chalk: 4.1.2 graceful-fs: 4.2.11 - jest-pnp-resolver: 1.2.3(jest-resolve@26.6.2) - jest-util: 26.6.2 - read-pkg-up: 7.0.1 + jest-haste-map: 29.7.0 + jest-pnp-resolver: 1.2.3(jest-resolve@29.7.0) + jest-util: 29.7.0 + jest-validate: 29.7.0 resolve: 1.22.2 + resolve.exports: 2.0.2 slash: 3.0.0 dev: true - /jest-runner@26.6.3: - resolution: {integrity: sha512-atgKpRHnaA2OvByG/HpGA4g6CSPS/1LK0jK3gATJAoptC1ojltpmVlYC3TYgdmGp+GLuhzpH30Gvs36szSL2JQ==} - engines: {node: '>= 10.14.2'} + /jest-runner@29.7.0: + resolution: {integrity: sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jest/console': 26.6.2 - '@jest/environment': 26.6.2 - '@jest/test-result': 26.6.2 - '@jest/types': 26.6.2 - '@types/node': 14.18.51 + '@jest/console': 29.7.0 + '@jest/environment': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.14.9 chalk: 4.1.2 - emittery: 0.7.2 - exit: 0.1.2 + emittery: 0.13.1 graceful-fs: 4.2.11 - jest-config: 26.6.3 - jest-docblock: 26.0.0 - jest-haste-map: 26.6.2 - jest-leak-detector: 26.6.2 - jest-message-util: 26.6.2 - jest-resolve: 26.6.2 - jest-runtime: 26.6.3 - jest-util: 26.6.2 - jest-worker: 26.6.2 - source-map-support: 0.5.21 - throat: 5.0.0 + jest-docblock: 29.7.0 + jest-environment-node: 29.7.0 + jest-haste-map: 29.7.0 + jest-leak-detector: 29.7.0 + jest-message-util: 29.7.0 + jest-resolve: 29.7.0 + jest-runtime: 29.7.0 + jest-util: 29.7.0 + jest-watcher: 29.7.0 + jest-worker: 29.7.0 + p-limit: 3.1.0 + source-map-support: 0.5.13 transitivePeerDependencies: - - bufferutil - - canvas - supports-color - - ts-node - - utf-8-validate dev: true - /jest-runtime@26.6.3: - resolution: {integrity: sha512-lrzyR3N8sacTAMeonbqpnSka1dHNux2uk0qqDXVkMv2c/A3wYnvQ4EXuI013Y6+gSKSCxdaczvf4HF0mVXHRdw==} - engines: {node: '>= 10.14.2'} - hasBin: true + /jest-runtime@29.7.0: + resolution: {integrity: sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jest/console': 26.6.2 - '@jest/environment': 26.6.2 - '@jest/fake-timers': 26.6.2 - '@jest/globals': 26.6.2 - '@jest/source-map': 26.6.2 - '@jest/test-result': 26.6.2 - '@jest/transform': 26.6.2 - '@jest/types': 26.6.2 - '@types/yargs': 15.0.15 + '@jest/environment': 29.7.0 + '@jest/fake-timers': 29.7.0 + '@jest/globals': 29.7.0 + '@jest/source-map': 29.6.3 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.14.9 chalk: 4.1.2 - cjs-module-lexer: 0.6.0 + cjs-module-lexer: 1.3.1 collect-v8-coverage: 1.0.1 - exit: 0.1.2 glob: 7.2.3 graceful-fs: 4.2.11 - jest-config: 26.6.3 - jest-haste-map: 26.6.2 - jest-message-util: 26.6.2 - jest-mock: 26.6.2 - jest-regex-util: 26.0.0 - jest-resolve: 26.6.2 - jest-snapshot: 26.6.2 - jest-util: 26.6.2 - jest-validate: 26.6.2 + jest-haste-map: 29.7.0 + jest-message-util: 29.7.0 + jest-mock: 29.7.0 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 slash: 3.0.0 strip-bom: 4.0.0 - yargs: 15.4.1 transitivePeerDependencies: - - bufferutil - - canvas - supports-color - - ts-node - - utf-8-validate - dev: true - - /jest-serializer@26.6.2: - resolution: {integrity: sha512-S5wqyz0DXnNJPd/xfIzZ5Xnp1HrJWBczg8mMfMpN78OJ5eDxXyf+Ygld9wX1DnUWbIbhM1YDY95NjR4CBXkb2g==} - engines: {node: '>= 10.14.2'} - dependencies: - '@types/node': 14.18.51 - graceful-fs: 4.2.11 dev: true - /jest-snapshot@26.6.2: - resolution: {integrity: sha512-OLhxz05EzUtsAmOMzuupt1lHYXCNib0ECyuZ/PZOx9TrZcC8vL0x+DUG3TL+GLX3yHG45e6YGjIm0XwDc3q3og==} - engines: {node: '>= 10.14.2'} + /jest-snapshot@29.7.0: + resolution: {integrity: sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: + '@babel/core': 7.22.5 + '@babel/generator': 7.22.5 + '@babel/plugin-syntax-jsx': 7.24.7(@babel/core@7.22.5) + '@babel/plugin-syntax-typescript': 7.24.7(@babel/core@7.22.5) '@babel/types': 7.22.5 - '@jest/types': 26.6.2 - '@types/babel__traverse': 7.20.1 - '@types/prettier': 2.7.3 + '@jest/expect-utils': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + babel-preset-current-node-syntax: 1.0.1(@babel/core@7.22.5) chalk: 4.1.2 - expect: 26.6.2 + expect: 29.7.0 graceful-fs: 4.2.11 - jest-diff: 26.6.2 - jest-get-type: 26.3.0 - jest-haste-map: 26.6.2 - jest-matcher-utils: 26.6.2 - jest-message-util: 26.6.2 - jest-resolve: 26.6.2 + jest-diff: 29.7.0 + jest-get-type: 29.6.3 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-util: 29.7.0 natural-compare: 1.4.0 - pretty-format: 26.6.2 - semver: 7.5.3 + pretty-format: 29.7.0 + semver: 7.6.2 transitivePeerDependencies: - supports-color dev: true - /jest-util@26.6.2: - resolution: {integrity: sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q==} - engines: {node: '>= 10.14.2'} + /jest-util@29.7.0: + resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jest/types': 26.6.2 - '@types/node': 14.18.51 + '@jest/types': 29.6.3 + '@types/node': 20.14.9 chalk: 4.1.2 + ci-info: 3.9.0 graceful-fs: 4.2.11 - is-ci: 2.0.0 - micromatch: 4.0.5 + picomatch: 2.3.1 dev: true - /jest-validate@26.6.2: - resolution: {integrity: sha512-NEYZ9Aeyj0i5rQqbq+tpIOom0YS1u2MVu6+euBsvpgIme+FOfRmoC4R5p0JiAUpaFvFy24xgrpMknarR/93XjQ==} - engines: {node: '>= 10.14.2'} + /jest-validate@29.7.0: + resolution: {integrity: sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jest/types': 26.6.2 + '@jest/types': 29.6.3 camelcase: 6.3.0 chalk: 4.1.2 - jest-get-type: 26.3.0 + jest-get-type: 29.6.3 leven: 3.1.0 - pretty-format: 26.6.2 + pretty-format: 29.7.0 dev: true - /jest-watcher@26.6.2: - resolution: {integrity: sha512-WKJob0P/Em2csiVthsI68p6aGKTIcsfjH9Gsx1f0A3Italz43e3ho0geSAVsmj09RWOELP1AZ/DXyJgOgDKxXQ==} - engines: {node: '>= 10.14.2'} + /jest-watcher@29.7.0: + resolution: {integrity: sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jest/test-result': 26.6.2 - '@jest/types': 26.6.2 - '@types/node': 14.18.51 + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.14.9 ansi-escapes: 4.3.2 chalk: 4.1.2 - jest-util: 26.6.2 + emittery: 0.13.1 + jest-util: 29.7.0 string-length: 4.0.2 dev: true - /jest-worker@26.6.2: - resolution: {integrity: sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==} - engines: {node: '>= 10.13.0'} + /jest-worker@24.9.0: + resolution: {integrity: sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw==} + engines: {node: '>= 6'} dependencies: - '@types/node': 14.18.51 merge-stream: 2.0.0 - supports-color: 7.2.0 + supports-color: 6.1.0 + dev: true + + /jest-worker@29.7.0: + resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@types/node': 20.14.9 + jest-util: 29.7.0 + merge-stream: 2.0.0 + supports-color: 8.1.1 dev: true - /jest@26.6.3: - resolution: {integrity: sha512-lGS5PXGAzR4RF7V5+XObhqz2KZIDUA1yD0DG6pBVmy10eh0ZIXQImRuzocsI/N2XZ1GrLFwTS27In2i2jlpq1Q==} - engines: {node: '>= 10.14.2'} + /jest@29.7.0(@types/node@20.14.9): + resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true dependencies: - '@jest/core': 26.6.3 + '@jest/core': 29.7.0 + '@jest/types': 29.6.3 import-local: 3.1.0 - jest-cli: 26.6.3 + jest-cli: 29.7.0(@types/node@20.14.9) transitivePeerDependencies: - - bufferutil - - canvas + - '@types/node' + - babel-plugin-macros - supports-color - ts-node - - utf-8-validate dev: true /js-tokens@4.0.0: @@ -4152,6 +4214,11 @@ packages: resolution: {integrity: sha512-unHyEPFGjr6WCzrcMiwdNhYMlq4gXt6Hg5JuKOyE7OXJ7GbVMpottnqsUkPeZCAYqByAkn4N8gJwCpnacduOew==} dev: false + /jsdoc-type-pratt-parser@4.0.0: + resolution: {integrity: sha512-YtOli5Cmzy3q4dP26GraSOeAhqecewG04hoO8DY56CH4KJ9Fvv5qKWUCCo3HZob7esJQHCv6/+bnTy72xZZaVQ==} + engines: {node: '>=12.0.0'} + dev: true + /jsdoc@4.0.2: resolution: {integrity: sha512-e8cIg2z62InH7azBBi3EsSEqrKx+nUtAS5bBcYTSpZFA+vhNPyhv8PTFZ0WsjOPDj04/dOLlm08EDcQJDqaGQg==} engines: {node: '>=12.0.0'} @@ -4174,9 +4241,9 @@ packages: underscore: 1.13.6 dev: false - /jsdom@16.7.0: - resolution: {integrity: sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==} - engines: {node: '>=10'} + /jsdom@20.0.3: + resolution: {integrity: sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==} + engines: {node: '>=14'} peerDependencies: canvas: ^2.5.0 peerDependenciesMeta: @@ -4184,32 +4251,31 @@ packages: optional: true dependencies: abab: 2.0.6 - acorn: 8.9.0 - acorn-globals: 6.0.0 - cssom: 0.4.4 + acorn: 8.12.0 + acorn-globals: 7.0.1 + cssom: 0.5.0 cssstyle: 2.3.0 - data-urls: 2.0.0 + data-urls: 3.0.2 decimal.js: 10.4.3 - domexception: 2.0.1 - escodegen: 2.0.0 - form-data: 3.0.1 - html-encoding-sniffer: 2.0.1 - http-proxy-agent: 4.0.1 + domexception: 4.0.0 + escodegen: 2.1.0 + form-data: 4.0.0 + html-encoding-sniffer: 3.0.0 + http-proxy-agent: 5.0.0 https-proxy-agent: 5.0.1 is-potential-custom-element-name: 1.0.1 nwsapi: 2.2.5 - parse5: 6.0.1 - saxes: 5.0.1 + parse5: 7.1.2 + saxes: 6.0.0 symbol-tree: 3.2.4 tough-cookie: 4.1.3 - w3c-hr-time: 1.0.2 - w3c-xmlserializer: 2.0.0 - webidl-conversions: 6.1.0 - whatwg-encoding: 1.0.5 - whatwg-mimetype: 2.3.0 - whatwg-url: 8.7.0 - ws: 7.5.9 - xml-name-validator: 3.0.0 + w3c-xmlserializer: 4.0.0 + webidl-conversions: 7.0.0 + whatwg-encoding: 2.0.0 + whatwg-mimetype: 3.0.0 + whatwg-url: 11.0.0 + ws: 8.17.1 + xml-name-validator: 4.0.0 transitivePeerDependencies: - bufferutil - supports-color @@ -4222,6 +4288,10 @@ packages: hasBin: true dev: true + /json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + dev: true + /json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} dev: true @@ -4250,28 +4320,10 @@ packages: graceful-fs: 4.2.11 dev: true - /kind-of@3.2.2: - resolution: {integrity: sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==} - engines: {node: '>=0.10.0'} - dependencies: - is-buffer: 1.1.6 - dev: true - - /kind-of@4.0.0: - resolution: {integrity: sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw==} - engines: {node: '>=0.10.0'} + /keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} dependencies: - is-buffer: 1.1.6 - dev: true - - /kind-of@5.1.0: - resolution: {integrity: sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==} - engines: {node: '>=0.10.0'} - dev: true - - /kind-of@6.0.3: - resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} - engines: {node: '>=0.10.0'} + json-buffer: 3.0.1 dev: true /klaw@3.0.0: @@ -4296,6 +4348,7 @@ packages: dependencies: prelude-ls: 1.1.2 type-check: 0.3.2 + dev: false /levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} @@ -4329,17 +4382,27 @@ packages: p-locate: 5.0.0 dev: true + /lodash.memoize@4.1.2: + resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} + dev: true + /lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} dev: true /lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + dev: false /long@5.2.3: resolution: {integrity: sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==} dev: false + /lru-cache@10.3.0: + resolution: {integrity: sha512-CQl19J/g+Hbjbv4Y3mFNNXFEL/5t/KCg8POCuUqd4rMKjGG+j1ybER83hxV58zL+dFI1PTkt3GNFSHRt+d8qEQ==} + engines: {node: 14 || >=16.14} + dev: true + /lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} dependencies: @@ -4363,9 +4426,8 @@ packages: vlq: 0.2.3 dev: true - /magic-string@0.27.0: - resolution: {integrity: sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==} - engines: {node: '>=12'} + /magic-string@0.30.10: + resolution: {integrity: sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==} dependencies: '@jridgewell/sourcemap-codec': 1.4.15 dev: true @@ -4374,7 +4436,7 @@ packages: resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} engines: {node: '>=8'} dependencies: - semver: 6.3.0 + semver: 6.3.1 dev: true /makeerror@1.0.12: @@ -4383,18 +4445,6 @@ packages: tmpl: 1.0.5 dev: true - /map-cache@0.2.2: - resolution: {integrity: sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==} - engines: {node: '>=0.10.0'} - dev: true - - /map-visit@1.0.0: - resolution: {integrity: sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==} - engines: {node: '>=0.10.0'} - dependencies: - object-visit: 1.0.1 - dev: true - /markdown-it-anchor@8.6.7(@types/markdown-it@12.2.3)(markdown-it@12.3.2): resolution: {integrity: sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA==} peerDependencies: @@ -4435,27 +4485,6 @@ packages: engines: {node: '>= 8'} dev: true - /micromatch@3.1.10: - resolution: {integrity: sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==} - engines: {node: '>=0.10.0'} - dependencies: - arr-diff: 4.0.0 - array-unique: 0.3.2 - braces: 2.3.2 - define-property: 2.0.2 - extend-shallow: 3.0.2 - extglob: 2.0.4 - fragment-cache: 0.2.1 - kind-of: 6.0.3 - nanomatch: 1.2.13 - object.pick: 1.3.0 - regex-not: 1.0.2 - snapdragon: 0.8.2 - to-regex: 3.0.2 - transitivePeerDependencies: - - supports-color - dev: true - /micromatch@4.0.5: resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} engines: {node: '>=8.6'} @@ -4491,9 +4520,23 @@ packages: engines: {node: '>=10'} dependencies: brace-expansion: 2.0.1 + dev: false + + /minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + dependencies: + brace-expansion: 2.0.1 + dev: true /minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + dev: false + + /minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + dev: true /mithril@2.2.2: resolution: {integrity: sha512-YRm6eLv2UUaWaWHdH8L+desW9+DN7+oM34CxJv6tT2e1lNVue8bxQlknQeDRn9aKlO8sIujm2wqUHwM+Hb1wGQ==} @@ -4506,51 +4549,22 @@ packages: resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} dev: true - /mixin-deep@1.3.2: - resolution: {integrity: sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==} - engines: {node: '>=0.10.0'} - dependencies: - for-in: 1.0.2 - is-extendable: 1.0.1 - dev: true - /mkdirp@1.0.4: resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} engines: {node: '>=10'} hasBin: true dev: false - /ms@2.0.0: - resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + /moo-color@1.0.3: + resolution: {integrity: sha512-i/+ZKXMDf6aqYtBhuOcej71YSlbjT3wCO/4H1j8rPvxDJEifdwgg5MaFyu6iYAT8GBZJg2z0dkgK4YMzvURALQ==} + dependencies: + color-name: 1.1.4 dev: true /ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} dev: true - /nanomatch@1.2.13: - resolution: {integrity: sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==} - engines: {node: '>=0.10.0'} - dependencies: - arr-diff: 4.0.0 - array-unique: 0.3.2 - define-property: 2.0.2 - extend-shallow: 3.0.2 - fragment-cache: 0.2.1 - is-windows: 1.0.2 - kind-of: 6.0.3 - object.pick: 1.3.0 - regex-not: 1.0.2 - snapdragon: 0.8.2 - to-regex: 3.0.2 - transitivePeerDependencies: - - supports-color - dev: true - - /natural-compare-lite@1.4.0: - resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==} - dev: true - /natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} dev: true @@ -4560,10 +4574,6 @@ packages: engines: {node: '>= 0.4.0'} dev: true - /nice-try@1.0.5: - resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==} - dev: true - /node-fetch@2.6.11: resolution: {integrity: sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==} engines: {node: 4.x || >=6.0.0} @@ -4580,57 +4590,17 @@ packages: resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} dev: true - /node-notifier@8.0.2: - resolution: {integrity: sha512-oJP/9NAdd9+x2Q+rfphB2RJCHjod70RcRLjosiPMMu5gjIfwVnOUGq2nbTjTUbmy0DJ/tFIVT30+Qe3nzl4TJg==} - requiresBuild: true - dependencies: - growly: 1.3.0 - is-wsl: 2.2.0 - semver: 7.5.3 - shellwords: 0.1.1 - uuid: 8.3.2 - which: 2.0.2 - dev: true - optional: true - - /node-releases@2.0.12: - resolution: {integrity: sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ==} - dev: true - - /node-watch@0.7.3: - resolution: {integrity: sha512-3l4E8uMPY1HdMMryPRUAl+oIHtXtyiTlIiESNSVSNxcPfzAFzeTbXFQkZfAwBbo0B1qMSG8nUABx+Gd+YrbKrQ==} - engines: {node: '>=6'} + /node-releases@2.0.14: + resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==} dev: true /noice-json-rpc@1.2.0: resolution: {integrity: sha512-Wm+otW+drKzdqlSPoSwj34tUEq/Xj1gX6Cr2avrykvTW4IY7d3ngLmP+PErALzS0s9nYRokXvYDM54sbFvLlDA==} dev: false - /normalize-package-data@2.5.0: - resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} - dependencies: - hosted-git-info: 2.8.9 - resolve: 1.22.2 - semver: 5.7.1 - validate-npm-package-license: 3.0.4 - dev: true - - /normalize-path@2.1.1: - resolution: {integrity: sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==} - engines: {node: '>=0.10.0'} - dependencies: - remove-trailing-separator: 1.1.0 - dev: true - /normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} - - /npm-run-path@2.0.2: - resolution: {integrity: sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==} - engines: {node: '>=4'} - dependencies: - path-key: 2.0.1 dev: true /npm-run-path@4.0.1: @@ -4644,29 +4614,6 @@ packages: resolution: {integrity: sha512-6xpotnECFy/og7tKSBVmUNft7J3jyXAka4XvG6AUhFWRz+Q/Ljus7znJAA3bxColfQLdS+XsjoodtJfCgeTEFQ==} dev: true - /object-copy@0.1.0: - resolution: {integrity: sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ==} - engines: {node: '>=0.10.0'} - dependencies: - copy-descriptor: 0.1.1 - define-property: 0.2.5 - kind-of: 3.2.2 - dev: true - - /object-visit@1.0.1: - resolution: {integrity: sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA==} - engines: {node: '>=0.10.0'} - dependencies: - isobject: 3.0.1 - dev: true - - /object.pick@1.3.0: - resolution: {integrity: sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==} - engines: {node: '>=0.10.0'} - dependencies: - isobject: 3.0.1 - dev: true - /once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} dependencies: @@ -4688,10 +4635,11 @@ packages: levn: 0.3.0 prelude-ls: 1.1.2 type-check: 0.3.2 - word-wrap: 1.2.3 + word-wrap: 1.2.5 + dev: false - /optionator@0.9.1: - resolution: {integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==} + /optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} dependencies: deep-is: 0.1.4 @@ -4699,7 +4647,7 @@ packages: levn: 0.4.1 prelude-ls: 1.2.1 type-check: 0.4.0 - word-wrap: 1.2.3 + word-wrap: 1.2.5 dev: true /ospec@4.0.0: @@ -4709,16 +4657,6 @@ packages: glob: 7.2.3 dev: false - /p-each-series@2.2.0: - resolution: {integrity: sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA==} - engines: {node: '>=8'} - dev: true - - /p-finally@1.0.0: - resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} - engines: {node: '>=4'} - dev: true - /p-limit@2.3.0: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} engines: {node: '>=6'} @@ -4758,7 +4696,7 @@ packages: dependencies: '@tootallnate/quickjs-emscripten': 0.23.0 agent-base: 7.1.0 - debug: 4.3.4 + debug: 4.3.5 get-uri: 6.0.1 http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.4 @@ -4776,8 +4714,12 @@ packages: netmask: 2.0.2 dev: true - /pako@1.0.11: - resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} + /package-json-from-dist@1.0.0: + resolution: {integrity: sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==} + dev: true + + /pako@2.1.0: + resolution: {integrity: sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==} dev: false /parent-module@1.0.1: @@ -4787,23 +4729,28 @@ packages: callsites: 3.1.0 dev: true + /parse-imports@2.1.0: + resolution: {integrity: sha512-JQWgmK2o4w8leUkZeZPatWdAny6vXGU/3siIUvMF6J2rDCud9aTt8h/px9oZJ6U3EcfhngBJ635uPFI0q0VAeA==} + engines: {node: '>= 18'} + dependencies: + es-module-lexer: 1.5.4 + slashes: 3.0.12 + dev: true + /parse-json@5.2.0: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} dependencies: - '@babel/code-frame': 7.22.5 + '@babel/code-frame': 7.24.7 error-ex: 1.3.2 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 dev: true - /parse5@6.0.1: - resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==} - dev: true - - /pascalcase@0.1.1: - resolution: {integrity: sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==} - engines: {node: '>=0.10.0'} + /parse5@7.1.2: + resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==} + dependencies: + entities: 4.5.0 dev: true /path-exists@4.0.0: @@ -4815,11 +4762,6 @@ packages: resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} engines: {node: '>=0.10.0'} - /path-key@2.0.1: - resolution: {integrity: sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==} - engines: {node: '>=4'} - dev: true - /path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} @@ -4829,6 +4771,14 @@ packages: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} dev: true + /path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + dependencies: + lru-cache: 10.3.0 + minipass: 7.1.2 + dev: true + /path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} @@ -4838,13 +4788,14 @@ packages: resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} dev: true - /picocolors@1.0.0: - resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} + /picocolors@1.0.1: + resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} dev: true /picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} + dev: true /pirates@4.0.6: resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} @@ -4870,34 +4821,34 @@ packages: engines: {node: '>=12.13.0'} dev: true - /posix-character-classes@0.1.1: - resolution: {integrity: sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==} - engines: {node: '>=0.10.0'} + /pngjs@7.0.0: + resolution: {integrity: sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==} + engines: {node: '>=14.19.0'} dev: true /prelude-ls@1.1.2: resolution: {integrity: sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==} engines: {node: '>= 0.8.0'} + dev: false /prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} dev: true - /prettier@2.8.8: - resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} - engines: {node: '>=10.13.0'} + /prettier@3.3.2: + resolution: {integrity: sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==} + engines: {node: '>=14'} hasBin: true dev: true - /pretty-format@26.6.2: - resolution: {integrity: sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==} - engines: {node: '>= 10'} + /pretty-format@29.7.0: + resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jest/types': 26.6.2 - ansi-regex: 5.0.1 - ansi-styles: 4.3.0 - react-is: 17.0.2 + '@jest/schemas': 29.6.3 + ansi-styles: 5.2.0 + react-is: 18.3.1 dev: true /progress@2.0.3: @@ -4913,7 +4864,7 @@ packages: sisteransi: 1.0.5 dev: true - /protobufjs-cli@1.1.2(protobufjs@7.2.5): + /protobufjs-cli@1.1.2(protobufjs@7.3.2): resolution: {integrity: sha512-8ivXWxT39gZN4mm4ArQyJrRgnIwZqffBWoLDsE21TmMcKI3XwJMV4lEF2WU02C4JAtgYYc2SfJIltelD8to35g==} engines: {node: '>=12.0.0'} hasBin: true @@ -4927,14 +4878,14 @@ packages: glob: 8.1.0 jsdoc: 4.0.2 minimist: 1.2.8 - protobufjs: 7.2.5 + protobufjs: 7.3.2 semver: 7.5.3 tmp: 0.2.1 uglify-js: 3.17.4 dev: false - /protobufjs@7.2.5: - resolution: {integrity: sha512-gGXRSXvxQ7UiPgfw8gevrfRWcTlSbOFg+p/N+JVJEK5VhueL2miT6qTymqAmjr1Q5WbOCyJbyrk6JfWKwlFn6A==} + /protobufjs@7.3.2: + resolution: {integrity: sha512-RXyHaACeqXeqAKGLDl68rQKbmObRsTIn4TYVUUug1KfS47YWCo5MacGITEryugIgZqORCvJWEk4l449POg5Txg==} engines: {node: '>=12.0.0'} requiresBuild: true dependencies: @@ -4948,7 +4899,7 @@ packages: '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.0 - '@types/node': 14.18.51 + '@types/node': 20.14.9 long: 5.2.3 dev: false @@ -4988,31 +4939,31 @@ packages: engines: {node: '>=6'} dev: true - /puppeteer-core@22.6.0: - resolution: {integrity: sha512-xclyGFhxHfZ9l62uXFm+JpgtJHLIZ1qHc7iR4eaIqBNKA5Dg2sDr8yvylfCx5bMN89QWIaxpV6IHsy0qUynK/g==} + /puppeteer-core@22.12.1: + resolution: {integrity: sha512-XmqeDPVdC5/3nGJys1jbgeoZ02wP0WV1GBlPtr/ULRbGXJFuqgXMcKQ3eeNtFpBzGRbpeoCGWHge1ZWKWl0Exw==} engines: {node: '>=18'} dependencies: - '@puppeteer/browsers': 2.2.0 - chromium-bidi: 0.5.13(devtools-protocol@0.0.1262051) - debug: 4.3.4 - devtools-protocol: 0.0.1262051 - ws: 8.16.0 + '@puppeteer/browsers': 2.2.3 + chromium-bidi: 0.5.24(devtools-protocol@0.0.1299070) + debug: 4.3.5 + devtools-protocol: 0.0.1299070 + ws: 8.17.1 transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate dev: true - /puppeteer@22.6.0(typescript@5.0.4): - resolution: {integrity: sha512-TYeza4rl1YXfxqUVw/0hWUWYX5cicnf6qu5kkDV+t7QrESCjMoSNnva4ZA/MRGQ03HnB9BOFw9nxs/SKek5KDA==} + /puppeteer@22.12.1(typescript@5.5.2): + resolution: {integrity: sha512-1GxY8dnEnHr1SLzdSDr0FCjM6JQfAh2E2I/EqzeF8a58DbGVk9oVjj4lFdqNoVbpgFSpAbz7VER9St7S1wDpNg==} engines: {node: '>=18'} hasBin: true requiresBuild: true dependencies: - '@puppeteer/browsers': 2.2.0 - cosmiconfig: 9.0.0(typescript@5.0.4) - devtools-protocol: 0.0.1262051 - puppeteer-core: 22.6.0 + '@puppeteer/browsers': 2.2.3 + cosmiconfig: 9.0.0(typescript@5.5.2) + devtools-protocol: 0.0.1299070 + puppeteer-core: 22.12.1 transitivePeerDependencies: - bufferutil - supports-color @@ -5020,6 +4971,10 @@ packages: - utf-8-validate dev: true + /pure-rand@6.1.0: + resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} + dev: true + /querystringify@2.2.0: resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} dev: true @@ -5033,27 +4988,8 @@ packages: requiresBuild: true dev: true - /react-is@17.0.2: - resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} - dev: true - - /read-pkg-up@7.0.1: - resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} - engines: {node: '>=8'} - dependencies: - find-up: 4.1.0 - read-pkg: 5.2.0 - type-fest: 0.8.1 - dev: true - - /read-pkg@5.2.0: - resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==} - engines: {node: '>=8'} - dependencies: - '@types/normalize-package-data': 2.4.1 - normalize-package-data: 2.5.0 - parse-json: 5.2.0 - type-fest: 0.6.0 + /react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} dev: true /readdirp@3.6.0: @@ -5061,38 +4997,12 @@ packages: engines: {node: '>=8.10.0'} dependencies: picomatch: 2.3.1 - dev: false - - /regex-not@1.0.2: - resolution: {integrity: sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==} - engines: {node: '>=0.10.0'} - dependencies: - extend-shallow: 3.0.2 - safe-regex: 1.1.0 - dev: true - - /remove-trailing-separator@1.1.0: - resolution: {integrity: sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==} - dev: true - - /repeat-element@1.1.4: - resolution: {integrity: sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==} - engines: {node: '>=0.10.0'} - dev: true - - /repeat-string@1.6.1: - resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==} - engines: {node: '>=0.10'} dev: true /require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} - /require-main-filename@2.0.0: - resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} - dev: true - /requires-port@1.0.0: resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} dev: true @@ -5120,9 +5030,9 @@ packages: engines: {node: '>=8'} dev: true - /resolve-url@0.2.1: - resolution: {integrity: sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==} - deprecated: https://github.com/lydell/resolve-url#deprecated + /resolve.exports@2.0.2: + resolution: {integrity: sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==} + engines: {node: '>=10'} dev: true /resolve@1.22.2: @@ -5134,11 +5044,6 @@ packages: supports-preserve-symlinks-flag: 1.0.0 dev: true - /ret@0.1.15: - resolution: {integrity: sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==} - engines: {node: '>=0.12'} - dev: true - /reusify@1.0.4: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} @@ -5149,6 +5054,7 @@ packages: hasBin: true dependencies: glob: 7.2.3 + dev: false /robust-predicates@3.0.2: resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==} @@ -5161,7 +5067,7 @@ packages: rollup-pluginutils: 2.8.2 dev: true - /rollup-plugin-sourcemaps@0.6.3(@types/node@14.18.51)(rollup@2.79.1): + /rollup-plugin-sourcemaps@0.6.3(@types/node@20.14.9)(rollup@2.79.1): resolution: {integrity: sha512-paFu+nT1xvuO1tPFYXGe+XnQvg4Hjqv/eIhG8i5EspfYYPBKL57X7iVbfv55aNVASg3dzWvES9dmWsL2KhfByw==} engines: {node: '>=10.0.0'} peerDependencies: @@ -5172,11 +5078,23 @@ packages: optional: true dependencies: '@rollup/pluginutils': 3.1.0(rollup@2.79.1) - '@types/node': 14.18.51 + '@types/node': 20.14.9 rollup: 2.79.1 source-map-resolve: 0.6.0 dev: true + /rollup-plugin-uglify@6.0.4(rollup@2.79.1): + resolution: {integrity: sha512-ddgqkH02klveu34TF0JqygPwZnsbhHVI6t8+hGTcYHngPkQb5MIHI0XiztXIN/d6V9j+efwHAqEL7LspSxQXGw==} + peerDependencies: + rollup: '>=0.66.0 <2' + dependencies: + '@babel/code-frame': 7.22.5 + jest-worker: 24.9.0 + rollup: 2.79.1 + serialize-javascript: 2.1.2 + uglify-js: 3.17.4 + dev: true + /rollup-pluginutils@2.8.2: resolution: {integrity: sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==} dependencies: @@ -5191,11 +5109,6 @@ packages: fsevents: 2.3.3 dev: true - /rsvp@4.8.5: - resolution: {integrity: sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==} - engines: {node: 6.* || >= 7.*} - dev: true - /run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} dependencies: @@ -5206,58 +5119,28 @@ packages: resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==} dev: false - /safe-regex@1.1.0: - resolution: {integrity: sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==} - dependencies: - ret: 0.1.15 - dev: true - /safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - /sane@4.1.0: - resolution: {integrity: sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA==} - engines: {node: 6.* || 8.* || >= 10.*} - deprecated: some dependency vulnerabilities fixed, support for node < 10 dropped, and newer ECMAScript syntax/features added - hasBin: true - dependencies: - '@cnakazawa/watch': 1.0.4 - anymatch: 2.0.0 - capture-exit: 2.0.0 - exec-sh: 0.3.6 - execa: 1.0.0 - fb-watchman: 2.0.2 - micromatch: 3.1.10 - minimist: 1.2.8 - walker: 1.0.8 - transitivePeerDependencies: - - supports-color - dev: true - - /sass@1.63.6: - resolution: {integrity: sha512-MJuxGMHzaOW7ipp+1KdELtqKbfAWbH7OLIdoSMnVe3EXPMTmxTmlaZDCTsgIpPCs3w99lLo9/zDKkOrJuT5byw==} + /sass@1.77.6: + resolution: {integrity: sha512-ByXE1oLD79GVq9Ht1PeHWCPMPB8XHpBuz1r85oByKHjZY6qV6rWnQovQzXJXuQ/XyE1Oj3iPk3lo28uzaRA2/Q==} engines: {node: '>=14.0.0'} hasBin: true dependencies: chokidar: 3.5.3 immutable: 4.3.0 source-map-js: 1.0.2 - dev: false + dev: true - /saxes@5.0.1: - resolution: {integrity: sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==} - engines: {node: '>=10'} + /saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} dependencies: xmlchars: 2.2.0 dev: true - /semver@5.7.1: - resolution: {integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==} - hasBin: true - dev: true - - /semver@6.3.0: - resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==} + /semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true dev: true @@ -5267,6 +5150,7 @@ packages: hasBin: true dependencies: lru-cache: 6.0.0 + dev: false /semver@7.6.0: resolution: {integrity: sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==} @@ -5276,25 +5160,14 @@ packages: lru-cache: 6.0.0 dev: true - /set-blocking@2.0.0: - resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} - dev: true - - /set-value@2.0.1: - resolution: {integrity: sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==} - engines: {node: '>=0.10.0'} - dependencies: - extend-shallow: 2.0.1 - is-extendable: 0.1.1 - is-plain-object: 2.0.4 - split-string: 3.1.0 + /semver@7.6.2: + resolution: {integrity: sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==} + engines: {node: '>=10'} + hasBin: true dev: true - /shebang-command@1.2.0: - resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} - engines: {node: '>=0.10.0'} - dependencies: - shebang-regex: 1.0.0 + /serialize-javascript@2.1.2: + resolution: {integrity: sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==} dev: true /shebang-command@2.0.0: @@ -5304,26 +5177,20 @@ packages: shebang-regex: 3.0.0 dev: true - /shebang-regex@1.0.0: - resolution: {integrity: sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==} - engines: {node: '>=0.10.0'} - dev: true - /shebang-regex@3.0.0: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} dev: true - /shellwords@0.1.1: - resolution: {integrity: sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==} - requiresBuild: true - dev: true - optional: true - /signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} dev: true + /signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + dev: true + /sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} dev: true @@ -5333,49 +5200,21 @@ packages: engines: {node: '>=8'} dev: true + /slashes@3.0.12: + resolution: {integrity: sha512-Q9VME8WyGkc7pJf6QEkj3wE+2CnvZMI+XJhwdTPR8Z/kWQRXi7boAWLDibRPyHRTUTPx5FaU7MsyrjI3yLB4HA==} + dev: true + /smart-buffer@4.2.0: resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} dev: true - /snapdragon-node@2.1.1: - resolution: {integrity: sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==} - engines: {node: '>=0.10.0'} - dependencies: - define-property: 1.0.0 - isobject: 3.0.1 - snapdragon-util: 3.0.1 - dev: true - - /snapdragon-util@3.0.1: - resolution: {integrity: sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==} - engines: {node: '>=0.10.0'} - dependencies: - kind-of: 3.2.2 - dev: true - - /snapdragon@0.8.2: - resolution: {integrity: sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==} - engines: {node: '>=0.10.0'} - dependencies: - base: 0.11.2 - debug: 2.6.9 - define-property: 0.2.5 - extend-shallow: 2.0.1 - map-cache: 0.2.2 - source-map: 0.5.7 - source-map-resolve: 0.5.3 - use: 3.1.1 - transitivePeerDependencies: - - supports-color - dev: true - /socks-proxy-agent@8.0.2: resolution: {integrity: sha512-8zuqoLv1aP/66PHF5TqwJ7Czm3Yv32urJQHrVyhD7mmA6d61Zv8cIXQYPTWwmg6qlupnPvs/QKDmfa4P/qct2g==} engines: {node: '>= 14'} dependencies: agent-base: 7.1.0 - debug: 4.3.4 + debug: 4.3.5 socks: 2.7.1 transitivePeerDependencies: - supports-color @@ -5392,17 +5231,6 @@ packages: /source-map-js@1.0.2: resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} engines: {node: '>=0.10.0'} - dev: false - - /source-map-resolve@0.5.3: - resolution: {integrity: sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==} - deprecated: See https://github.com/lydell/source-map-resolve#deprecated - dependencies: - atob: 2.1.2 - decode-uri-component: 0.2.2 - resolve-url: 0.2.1 - source-map-url: 0.4.1 - urix: 0.1.0 dev: true /source-map-resolve@0.6.0: @@ -5413,45 +5241,23 @@ packages: decode-uri-component: 0.2.2 dev: true - /source-map-support@0.5.21: - resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + /source-map-support@0.5.13: + resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} dependencies: buffer-from: 1.1.2 source-map: 0.6.1 dev: true - /source-map-url@0.4.1: - resolution: {integrity: sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==} - deprecated: See https://github.com/lydell/source-map-url#deprecated - dev: true - - /source-map@0.5.7: - resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} - engines: {node: '>=0.10.0'} - dev: true - /source-map@0.6.1: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} - /source-map@0.7.4: - resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} - engines: {node: '>= 8'} - dev: true - - /spdx-correct@3.2.0: - resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} - dependencies: - spdx-expression-parse: 3.0.1 - spdx-license-ids: 3.0.13 - dev: true - /spdx-exceptions@2.3.0: resolution: {integrity: sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==} dev: true - /spdx-expression-parse@3.0.1: - resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} + /spdx-expression-parse@4.0.0: + resolution: {integrity: sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==} dependencies: spdx-exceptions: 2.3.0 spdx-license-ids: 3.0.13 @@ -5461,13 +5267,6 @@ packages: resolution: {integrity: sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==} dev: true - /split-string@3.1.0: - resolution: {integrity: sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==} - engines: {node: '>=0.10.0'} - dependencies: - extend-shallow: 3.0.2 - dev: true - /sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} dev: true @@ -5479,14 +5278,6 @@ packages: escape-string-regexp: 2.0.0 dev: true - /static-extend@0.1.2: - resolution: {integrity: sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==} - engines: {node: '>=0.10.0'} - dependencies: - define-property: 0.2.5 - object-copy: 0.1.0 - dev: true - /streamx@2.15.0: resolution: {integrity: sha512-HcxY6ncGjjklGs1xsP1aR71INYcsXFJet5CU1CHqihQ2J5nOsbd4OjgjHO42w/4QNv9gZb3BueV+Vxok5pLEXg==} dependencies: @@ -5510,22 +5301,33 @@ packages: is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 + /string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + dev: true + /strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} dependencies: ansi-regex: 5.0.1 + /strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} + dependencies: + ansi-regex: 6.0.1 + dev: true + /strip-bom@4.0.0: resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} engines: {node: '>=8'} dev: true - /strip-eof@1.0.0: - resolution: {integrity: sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==} - engines: {node: '>=0.10.0'} - dev: true - /strip-final-newline@2.0.0: resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} engines: {node: '>=6'} @@ -5535,10 +5337,6 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} - /style-mod@4.0.3: - resolution: {integrity: sha512-78Jv8kYJdjbvRwwijtCevYADfsI0lGzYJe4mMFdceO8l75DFFDoqBhR1jVDicDRRaX4//g1u9wKeo+ztc2h1Rw==} - dev: false - /style-mod@4.1.0: resolution: {integrity: sha512-Ca5ib8HrFn+f+0n4N4ScTIA9iTOQ7MaGS1ylHcoVqW9J7w2w8PzN6g9gKmTYgGEBH8e120+RCmhpje6jC5uGWA==} dev: false @@ -5550,18 +5348,24 @@ packages: has-flag: 3.0.0 dev: true + /supports-color@6.1.0: + resolution: {integrity: sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==} + engines: {node: '>=6'} + dependencies: + has-flag: 3.0.0 + dev: true + /supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} dependencies: has-flag: 4.0.0 - /supports-hyperlinks@2.3.0: - resolution: {integrity: sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==} - engines: {node: '>=8'} + /supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} dependencies: has-flag: 4.0.0 - supports-color: 7.2.0 dev: true /supports-preserve-symlinks-flag@1.0.0: @@ -5573,6 +5377,14 @@ packages: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} dev: true + /synckit@0.9.0: + resolution: {integrity: sha512-7RnqIMq572L8PeEzKeBINYEJDDxpcH8JEgLwUqBd3TkofhFRbkq4QLR0u+36avGAhCRbk2nnmjcW9SE531hPDg==} + engines: {node: ^14.18.0 || >=16.0.0} + dependencies: + '@pkgr/core': 0.1.1 + tslib: 2.6.3 + dev: true + /tar-fs@3.0.5: resolution: {integrity: sha512-JOgGAmZyMgbqpLwct7ZV8VzkEB6pxXFBVErLtb+XCOqzc6w1xiWKI9GVd6bwk68EX7eJ4DWmfXVmq8K2ziZTGg==} dependencies: @@ -5591,14 +5403,6 @@ packages: streamx: 2.15.0 dev: true - /terminal-link@2.1.1: - resolution: {integrity: sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==} - engines: {node: '>=8'} - dependencies: - ansi-escapes: 4.3.2 - supports-hyperlinks: 2.3.0 - dev: true - /test-exclude@6.0.0: resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} engines: {node: '>=8'} @@ -5612,10 +5416,6 @@ packages: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} dev: true - /throat@5.0.0: - resolution: {integrity: sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==} - dev: true - /through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} dev: true @@ -5635,35 +5435,11 @@ packages: resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} engines: {node: '>=4'} - /to-object-path@0.3.0: - resolution: {integrity: sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==} - engines: {node: '>=0.10.0'} - dependencies: - kind-of: 3.2.2 - dev: true - - /to-regex-range@2.1.1: - resolution: {integrity: sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==} - engines: {node: '>=0.10.0'} - dependencies: - is-number: 3.0.0 - repeat-string: 1.6.1 - dev: true - /to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} dependencies: is-number: 7.0.0 - - /to-regex@3.0.2: - resolution: {integrity: sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==} - engines: {node: '>=0.10.0'} - dependencies: - define-property: 2.0.2 - extend-shallow: 3.0.2 - regex-not: 1.0.2 - safe-regex: 1.1.0 dev: true /topojson-client@3.1.0: @@ -5687,35 +5463,31 @@ packages: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} dev: false - /tr46@2.1.0: - resolution: {integrity: sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==} - engines: {node: '>=8'} + /tr46@3.0.0: + resolution: {integrity: sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==} + engines: {node: '>=12'} dependencies: punycode: 2.3.0 dev: true - /tslib@1.14.1: - resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} - dev: true - - /tslib@2.5.3: - resolution: {integrity: sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==} - - /tsutils@3.21.0(typescript@5.0.4): - resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} - engines: {node: '>= 6'} + /ts-api-utils@1.3.0(typescript@5.5.2): + resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} + engines: {node: '>=16'} peerDependencies: - typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' + typescript: '>=4.2.0' dependencies: - tslib: 1.14.1 - typescript: 5.0.4 + typescript: 5.5.2 dev: true + /tslib@2.6.3: + resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==} + /type-check@0.3.2: resolution: {integrity: sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==} engines: {node: '>= 0.8.0'} dependencies: prelude-ls: 1.1.2 + dev: false /type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} @@ -5739,25 +5511,9 @@ packages: engines: {node: '>=10'} dev: true - /type-fest@0.6.0: - resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==} - engines: {node: '>=8'} - dev: true - - /type-fest@0.8.1: - resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} - engines: {node: '>=8'} - dev: true - - /typedarray-to-buffer@3.1.5: - resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==} - dependencies: - is-typedarray: 1.0.0 - dev: true - - /typescript@5.0.4: - resolution: {integrity: sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==} - engines: {node: '>=12.20'} + /typescript@5.5.2: + resolution: {integrity: sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew==} + engines: {node: '>=14.17'} hasBin: true dev: true @@ -5769,7 +5525,6 @@ packages: resolution: {integrity: sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==} engines: {node: '>=0.8.0'} hasBin: true - dev: false /unbzip2-stream@1.4.3: resolution: {integrity: sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==} @@ -5782,15 +5537,8 @@ packages: resolution: {integrity: sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==} dev: false - /union-value@1.0.1: - resolution: {integrity: sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==} - engines: {node: '>=0.10.0'} - dependencies: - arr-union: 3.1.0 - get-value: 2.0.6 - is-extendable: 0.1.1 - set-value: 2.0.1 - dev: true + /undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} /universalify@0.1.2: resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} @@ -5802,23 +5550,15 @@ packages: engines: {node: '>= 4.0.0'} dev: true - /unset-value@1.0.0: - resolution: {integrity: sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==} - engines: {node: '>=0.10.0'} - dependencies: - has-value: 0.3.1 - isobject: 3.0.1 - dev: true - - /update-browserslist-db@1.0.11(browserslist@4.21.9): - resolution: {integrity: sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==} + /update-browserslist-db@1.0.16(browserslist@4.23.1): + resolution: {integrity: sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' dependencies: - browserslist: 4.21.9 - escalade: 3.1.1 - picocolors: 1.0.0 + browserslist: 4.23.1 + escalade: 3.1.2 + picocolors: 1.0.1 dev: true /uri-js@4.4.1: @@ -5827,11 +5567,6 @@ packages: punycode: 2.3.0 dev: true - /urix@0.1.0: - resolution: {integrity: sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==} - deprecated: Please see https://github.com/lydell/urix#deprecated - dev: true - /url-parse@1.5.10: resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} dependencies: @@ -5843,11 +5578,6 @@ packages: 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'} - dev: true - /util@0.12.5: resolution: {integrity: sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==} dependencies: @@ -5858,65 +5588,51 @@ packages: which-typed-array: 1.1.9 dev: false - /uuid@8.3.2: - resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} - hasBin: true - requiresBuild: true - dev: true - optional: true - - /uuid@9.0.0: - resolution: {integrity: sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==} + /uuid@10.0.0: + resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==} hasBin: true dev: false - /v8-to-istanbul@7.1.2: - resolution: {integrity: sha512-TxNb7YEUwkLXCQYeudi6lgQ/SZrzNO4kMdlqVxaZPUIUjCv6iSSypUQX70kNBSERpQ8fk48+d61FXk+tgqcWow==} - engines: {node: '>=10.10.0'} + /v8-to-istanbul@9.3.0: + resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==} + engines: {node: '>=10.12.0'} dependencies: + '@jridgewell/trace-mapping': 0.3.18 '@types/istanbul-lib-coverage': 2.0.4 - convert-source-map: 1.9.0 - source-map: 0.7.4 - dev: true - - /validate-npm-package-license@3.0.4: - resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} - dependencies: - spdx-correct: 3.2.0 - spdx-expression-parse: 3.0.1 + convert-source-map: 2.0.0 dev: true /vega-canvas@1.2.7: resolution: {integrity: sha512-OkJ9CACVcN9R5Pi9uF6MZBF06pO6qFpDYHWSKBJsdHP5o724KrsgR6UvbnXFH82FdsiTOff/HqjuaG8C7FL+9Q==} dev: false - /vega-crossfilter@4.1.1: - resolution: {integrity: sha512-yesvlMcwRwxrtAd9IYjuxWJJuAMI0sl7JvAFfYtuDkkGDtqfLXUcCzHIATqW6igVIE7tWwGxnbfvQLhLNgK44Q==} + /vega-crossfilter@4.1.2: + resolution: {integrity: sha512-J7KVEXkpfRJBfRvwLxn5vNCzQCNkrnzmDvkvwhuiwT4gPm5sk7MK5TuUP8GCl/iKYw+kWeVXEtrVHwWtug+bcQ==} dependencies: d3-array: 3.2.4 - vega-dataflow: 5.7.5 + vega-dataflow: 5.7.6 vega-util: 1.17.2 transitivePeerDependencies: - encoding dev: false - /vega-dataflow@5.7.5: - resolution: {integrity: sha512-EdsIl6gouH67+8B0f22Owr2tKDiMPNNR8lEvJDcxmFw02nXd8juimclpLvjPQriqn6ta+3Dn5txqfD117H04YA==} + /vega-dataflow@5.7.6: + resolution: {integrity: sha512-9Md8+5iUC1MVKPKDyZ7pCEHk6I9am+DgaMzZqo/27O/KI4f23/WQXPyuI8jbNmc/mkm340P0TKREmzL5M7+2Dg==} dependencies: - vega-format: 1.1.1 - vega-loader: 4.5.1 + vega-format: 1.1.2 + vega-loader: 4.5.2 vega-util: 1.17.2 transitivePeerDependencies: - encoding dev: false - /vega-encode@4.9.2: - resolution: {integrity: sha512-c3J0LYkgYeXQxwnYkEzL15cCFBYPRaYUon8O2SZ6O4PhH4dfFTXBzSyT8+gh8AhBd572l2yGDfxpEYA6pOqdjg==} + /vega-encode@4.10.1: + resolution: {integrity: sha512-d25nVKZDrg109rC65M8uxE+7iUrTxktaqgK4fU3XZBgpWlh1K4UbU5nDag7kiHVVN4tKqwgd+synEotra9TiVQ==} dependencies: d3-array: 3.2.4 d3-interpolate: 3.0.1 - vega-dataflow: 5.7.5 - vega-scale: 7.3.0 + vega-dataflow: 5.7.6 + vega-scale: 7.4.1 vega-util: 1.17.2 transitivePeerDependencies: - encoding @@ -5929,184 +5645,188 @@ packages: /vega-expression@5.1.0: resolution: {integrity: sha512-u8Rzja/cn2PEUkhQN3zUj3REwNewTA92ExrcASNKUJPCciMkHJEjESwFYuI6DWMCq4hQElQ92iosOAtwzsSTqA==} dependencies: - '@types/estree': 1.0.1 + '@types/estree': 1.0.5 + vega-util: 1.17.2 + dev: false + + /vega-expression@5.1.1: + resolution: {integrity: sha512-zv9L1Hm0KHE9M7mldHyz8sXbGu3KmC0Cdk7qfHkcTNS75Jpsem6jkbu6ZAwx5cNUeW91AxUQOu77r4mygq2wUQ==} + dependencies: + '@types/estree': 1.0.5 vega-util: 1.17.2 dev: false - /vega-force@4.2.0: - resolution: {integrity: sha512-aE2TlP264HXM1r3fl58AvZdKUWBNOGkIvn4EWyqeJdgO2vz46zSU7x7TzPG4ZLuo44cDRU5Ng3I1eQk23Asz6A==} + /vega-force@4.2.1: + resolution: {integrity: sha512-2BcuuqFr77vcCyKfcpedNFeYMxi+XEFCrlgLWNx7YV0PI8pdP5y/yPkzyuE9Tb894+KkRAvfQHZRAshcnFNcMw==} dependencies: d3-force: 3.0.0 - vega-dataflow: 5.7.5 + vega-dataflow: 5.7.6 vega-util: 1.17.2 transitivePeerDependencies: - encoding dev: false - /vega-format@1.1.1: - resolution: {integrity: sha512-Rll7YgpYbsgaAa54AmtEWrxaJqgOh5fXlvM2wewO4trb9vwM53KBv4Q/uBWCLK3LLGeBXIF6gjDt2LFuJAUtkQ==} + /vega-format@1.1.2: + resolution: {integrity: sha512-0kUfAj0dg0U6GcEY0Kp6LiSTCZ8l8jl1qVdQyToMyKmtZg/q56qsiJQZy3WWRr1MtWkTIZL71xSJXgjwjeUaAw==} dependencies: d3-array: 3.2.4 d3-format: 3.1.0 d3-time-format: 4.1.0 - vega-time: 2.1.1 + vega-time: 2.1.2 vega-util: 1.17.2 dev: false - /vega-functions@5.13.2: - resolution: {integrity: sha512-YE1Xl3Qi28kw3vdXVYgKFMo20ttd3+SdKth1jUNtBDGGdrOpvPxxFhZkVqX+7FhJ5/1UkDoAYs/cZY0nRKiYgA==} + /vega-functions@5.15.0: + resolution: {integrity: sha512-pCqmm5efd+3M65jrJGxEy3UGuRksmK6DnWijoSNocnxdCBxez+yqUUVX9o2pN8VxMe3648vZnR9/Vk5CXqRvIQ==} dependencies: d3-array: 3.2.4 d3-color: 3.1.0 d3-geo: 3.1.0 - vega-dataflow: 5.7.5 - vega-expression: 5.1.0 - vega-scale: 7.3.0 - vega-scenegraph: 4.10.2 - vega-selections: 5.4.1 + vega-dataflow: 5.7.6 + vega-expression: 5.1.1 + vega-scale: 7.4.1 + vega-scenegraph: 4.13.0 + vega-selections: 5.4.2 vega-statistics: 1.9.0 - vega-time: 2.1.1 + vega-time: 2.1.2 vega-util: 1.17.2 transitivePeerDependencies: - encoding dev: false - /vega-geo@4.4.1: - resolution: {integrity: sha512-s4WeZAL5M3ZUV27/eqSD3v0FyJz3PlP31XNSLFy4AJXHxHUeXT3qLiDHoVQnW5Om+uBCPDtTT1ROx1smGIf2aA==} + /vega-geo@4.4.2: + resolution: {integrity: sha512-unuV/UxUHf6UJu6GYxMZonC3SZlMfFXYLOkgEsRSvmsMPt3+CVv8FmG88dXNRUJUrdROrJepgecqx0jOwMSnGA==} dependencies: d3-array: 3.2.4 d3-color: 3.1.0 d3-geo: 3.1.0 vega-canvas: 1.2.7 - vega-dataflow: 5.7.5 - vega-projection: 1.6.0 + vega-dataflow: 5.7.6 + vega-projection: 1.6.1 vega-statistics: 1.9.0 vega-util: 1.17.2 transitivePeerDependencies: - encoding dev: false - /vega-hierarchy@4.1.1: - resolution: {integrity: sha512-h5mbrDtPKHBBQ9TYbvEb/bCqmGTlUX97+4CENkyH21tJs7naza319B15KRK0NWOHuhbGhFmF8T0696tg+2c8XQ==} + /vega-hierarchy@4.1.2: + resolution: {integrity: sha512-m+xDtT5092YPSnV0rdTLW+AWmoCb+A54JQ66MUJwiDBpKxvfKnTiQeuiWDU2YudjUoXZN9EBOcI6QHF8H2Lu2A==} dependencies: d3-hierarchy: 3.1.2 - vega-dataflow: 5.7.5 + vega-dataflow: 5.7.6 vega-util: 1.17.2 transitivePeerDependencies: - encoding dev: false - /vega-label@1.2.1: - resolution: {integrity: sha512-n/ackJ5lc0Xs9PInCaGumYn2awomPjJ87EMVT47xNgk2bHmJoZV1Ve/1PUM6Eh/KauY211wPMrNp/9Im+7Ripg==} + /vega-label@1.3.0: + resolution: {integrity: sha512-EfSFSCWAwVPsklM5g0gUEuohALgryuGC/SKMmsOH7dYT/bywmLBZhLVbrE+IHJAUauoGrMhYw1mqnXL/0giJBg==} dependencies: vega-canvas: 1.2.7 - vega-dataflow: 5.7.5 - vega-scenegraph: 4.10.2 + vega-dataflow: 5.7.6 + vega-scenegraph: 4.13.0 vega-util: 1.17.2 transitivePeerDependencies: - encoding dev: false - /vega-lite@5.11.0(vega@5.25.0): - resolution: {integrity: sha512-2PmF7bbgNDDDcBWS4uZ3+I7aaQ3j9G+WaJTDtKMEBzkhADwei5CuYrwBcVk63hRZqvfeuIXxk4DBOlSW3hRx8Q==} - engines: {node: '>=16'} + /vega-lite@5.19.0(vega@5.30.0): + resolution: {integrity: sha512-DtSArHnomfYdKnkz7rDCLkXpuh4O6kHVU1YSDoMtQR0ewEwqelb5YY85mvJPCJRT9E7UG84RGdxjDbuwowtHRg==} + engines: {node: '>=18'} hasBin: true peerDependencies: vega: ^5.24.0 dependencies: - '@types/clone': 2.1.1 - clone: 2.1.2 - fast-deep-equal: 3.1.3 - fast-json-stable-stringify: 2.1.0 json-stringify-pretty-compact: 3.0.0 - tslib: 2.5.3 - vega: 5.25.0 + tslib: 2.6.3 + vega: 5.30.0 vega-event-selector: 3.0.1 vega-expression: 5.1.0 vega-util: 1.17.2 yargs: 17.7.2 dev: false - /vega-loader@4.5.1: - resolution: {integrity: sha512-qy5x32SaT0YkEujQM2yKqvLGV9XWQ2aEDSugBFTdYzu/1u4bxdUSRDREOlrJ9Km3RWIOgFiCkobPmFxo47SKuA==} + /vega-loader@4.5.2: + resolution: {integrity: sha512-ktIdGz3DRIS3XfTP9lJ6oMT5cKwC86nQkjUbXZbOtwXQFVNE2xVWBuH13GP6FKUZxg5hJCMtb5v/e/fwTvhKsQ==} dependencies: d3-dsv: 3.0.1 node-fetch: 2.6.11 topojson-client: 3.1.0 - vega-format: 1.1.1 + vega-format: 1.1.2 vega-util: 1.17.2 transitivePeerDependencies: - encoding dev: false - /vega-parser@6.2.0: - resolution: {integrity: sha512-as+QnX8Qxe9q51L1C2sVBd+YYYctP848+zEvkBT2jlI2g30aZ6Uv7sKsq7QTL6DUbhXQKR0XQtzlanckSFdaOQ==} + /vega-parser@6.4.0: + resolution: {integrity: sha512-/hFIJs0yITxfvLIfhhcpUrcbKvu4UZYoMGmly5PSsbgo60oAsVQW8ZbX2Ji3iNFqZJh1ifoX/P0j+9wep1OISw==} dependencies: - vega-dataflow: 5.7.5 + vega-dataflow: 5.7.6 vega-event-selector: 3.0.1 - vega-functions: 5.13.2 - vega-scale: 7.3.0 + vega-functions: 5.15.0 + vega-scale: 7.4.1 vega-util: 1.17.2 transitivePeerDependencies: - encoding dev: false - /vega-projection@1.6.0: - resolution: {integrity: sha512-LGUaO/kpOEYuTlul+x+lBzyuL9qmMwP1yShdUWYLW+zXoeyGbs5OZW+NbPPwLYqJr5lpXDr/vGztFuA/6g2xvQ==} + /vega-projection@1.6.1: + resolution: {integrity: sha512-sqfnAAHumU7MWU1tQN3b6HNgKGF3legek0uLHhjLKcDJQxEc7kwcD18txFz2ffQks6d5j+AUhBiq4GARWf0DEQ==} dependencies: d3-geo: 3.1.0 d3-geo-projection: 4.0.0 - vega-scale: 7.3.0 + vega-scale: 7.4.1 dev: false - /vega-regression@1.2.0: - resolution: {integrity: sha512-6TZoPlhV/280VbxACjRKqlE0Nv48z5g4CSNf1FmGGTWS1rQtElPTranSoVW4d7ET5eVQ6f9QLxNAiALptvEq+g==} + /vega-regression@1.3.0: + resolution: {integrity: sha512-gxOQfmV7Ft/MYKpXDEo09WZyBuKOBqxqDRWay9KtfGq/E0Y4vbTPsWLv2cB1ToPJdKE6XSN6Re9tCIw5M/yMUg==} dependencies: d3-array: 3.2.4 - vega-dataflow: 5.7.5 + vega-dataflow: 5.7.6 vega-statistics: 1.9.0 vega-util: 1.17.2 transitivePeerDependencies: - encoding dev: false - /vega-runtime@6.1.4: - resolution: {integrity: sha512-0dDYXyFLQcxPQ2OQU0WuBVYLRZnm+/CwVu6i6N4idS7R9VXIX5581EkCh3pZ20pQ/+oaA7oJ0pR9rJgJ6rukRQ==} + /vega-runtime@6.2.0: + resolution: {integrity: sha512-30UXbujWjKNd5aeP+oeHuwFmzuyVYlBj4aDy9+AjfWLECu8wJt4K01vwegcaGPdCWcPLVIv4Oa9Lob4mcXn5KQ==} dependencies: - vega-dataflow: 5.7.5 + vega-dataflow: 5.7.6 vega-util: 1.17.2 transitivePeerDependencies: - encoding dev: false - /vega-scale@7.3.0: - resolution: {integrity: sha512-pMOAI2h+e1z7lsqKG+gMfR6NKN2sTcyjZbdJwntooW0uFHwjLGjMSY7kSd3nSEquF0HQ8qF7zR6gs1eRwlGimw==} + /vega-scale@7.4.1: + resolution: {integrity: sha512-dArA28DbV/M92O2QvswnzCmQ4bq9WwLKUoyhqFYWCltmDwkmvX7yhqiFLFMWPItIm7mi4Qyoygby6r4DKd1X2A==} dependencies: d3-array: 3.2.4 d3-interpolate: 3.0.1 d3-scale: 4.0.2 - vega-time: 2.1.1 + d3-scale-chromatic: 3.1.0 + vega-time: 2.1.2 vega-util: 1.17.2 dev: false - /vega-scenegraph@4.10.2: - resolution: {integrity: sha512-R8m6voDZO5+etwNMcXf45afVM3XAtokMqxuDyddRl9l1YqSJfS+3u8hpolJ50c2q6ZN20BQiJwKT1o0bB7vKkA==} + /vega-scenegraph@4.13.0: + resolution: {integrity: sha512-nfl45XtuqB5CxyIZJ+bbJ+dofzosPCRlmF+eUQo+0J23NkNXsTzur+1krJDSdhcw0SOYs4sbYRoMz1cpuOM4+Q==} dependencies: d3-path: 3.1.0 d3-shape: 3.2.0 vega-canvas: 1.2.7 - vega-loader: 4.5.1 - vega-scale: 7.3.0 + vega-loader: 4.5.2 + vega-scale: 7.4.1 vega-util: 1.17.2 transitivePeerDependencies: - encoding dev: false - /vega-selections@5.4.1: - resolution: {integrity: sha512-EtYc4DvA+wXqBg9tq+kDomSoVUPCmQfS7hUxy2qskXEed79YTimt3Hcl1e1fW226I4AVDBEqTTKebmKMzbSgAA==} + /vega-selections@5.4.2: + resolution: {integrity: sha512-99FUhYmg0jOJr2/K4TcEURmJRkuibrCDc8KBUX7qcQEITzrZ5R6a4QE+sarCvbb3hi8aA9GV2oyST6MQeA9mgQ==} dependencies: - d3-array: 3.2.2 - vega-expression: 5.1.0 + d3-array: 3.2.4 + vega-expression: 5.1.1 vega-util: 1.17.2 dev: false @@ -6116,32 +5836,32 @@ packages: d3-array: 3.2.4 dev: false - /vega-time@2.1.1: - resolution: {integrity: sha512-z1qbgyX0Af2kQSGFbApwBbX2meenGvsoX8Nga8uyWN8VIbiySo/xqizz1KrP6NbB6R+x5egKmkjdnyNThPeEWA==} + /vega-time@2.1.2: + resolution: {integrity: sha512-6rXc6JdDt8MnCRy6UzUCsa6EeFycPDmvioMddLfKw38OYCV8pRQC5nw44gyddOwXgUTJLiCtn/sp53P0iA542A==} dependencies: d3-array: 3.2.4 d3-time: 3.1.0 vega-util: 1.17.2 dev: false - /vega-transforms@4.10.2: - resolution: {integrity: sha512-sJELfEuYQ238PRG+GOqQch8D69RYnJevYSGLsRGQD2LxNz3j+GlUX6Pid+gUEH5HJy22Q5L0vsTl2ZNhIr4teQ==} + /vega-transforms@4.12.0: + resolution: {integrity: sha512-bh/2Qbj85O70mjfLRgPKAsABArgSUP0k+GjmaY54zukIRxoGxKju+85nigeX/aR/INpEqNWif+5lL+NvmyWA5w==} dependencies: d3-array: 3.2.4 - vega-dataflow: 5.7.5 + vega-dataflow: 5.7.6 vega-statistics: 1.9.0 - vega-time: 2.1.1 + vega-time: 2.1.2 vega-util: 1.17.2 transitivePeerDependencies: - encoding dev: false - /vega-typings@0.24.1: - resolution: {integrity: sha512-WNw6tDxwMsynQ9osJb3RZi3g8GZruxVgXfe8N7nbqvNOgDQkUuVjqTZiwGg5kqjmLqx09lRRlskgp/ov7lEGeg==} + /vega-typings@1.3.1: + resolution: {integrity: sha512-j9Sdgmvowz09jkMgTFGVfiv7ycuRP/TQkdHRPXIYwt3RDgPQn7inyFcJ8C8ABFt4MiMWdjOwbneF6KWW8TRXIw==} dependencies: '@types/geojson': 7946.0.4 vega-event-selector: 3.0.1 - vega-expression: 5.1.0 + vega-expression: 5.1.1 vega-util: 1.17.2 dev: false @@ -6149,83 +5869,83 @@ packages: resolution: {integrity: sha512-omNmGiZBdjm/jnHjZlywyYqafscDdHaELHx1q96n5UOz/FlO9JO99P4B3jZg391EFG8dqhWjQilSf2JH6F1mIw==} dev: false - /vega-view-transforms@4.5.9: - resolution: {integrity: sha512-NxEq4ZD4QwWGRrl2yDLnBRXM9FgCI+vvYb3ZC2+nVDtkUxOlEIKZsMMw31op5GZpfClWLbjCT3mVvzO2xaTF+g==} + /vega-view-transforms@4.6.0: + resolution: {integrity: sha512-z3z66aJTA3ZRo4oBY4iBXnn+A4KqBGZT/UrlKDbm+7Ec+Ip+hK2tF8Kmhp/WNcMsDZoUWFqLJgR2VgOgvJk9RA==} dependencies: - vega-dataflow: 5.7.5 - vega-scenegraph: 4.10.2 + vega-dataflow: 5.7.6 + vega-scenegraph: 4.13.0 vega-util: 1.17.2 transitivePeerDependencies: - encoding dev: false - /vega-view@5.11.1: - resolution: {integrity: sha512-RoWxuoEMI7xVQJhPqNeLEHCezudsf3QkVMhH5tCovBqwBADQGqq9iWyax3ZzdyX1+P3eBgm7cnLvpqtN2hU8kA==} + /vega-view@5.13.0: + resolution: {integrity: sha512-ZPAAQ3iYz6YrQjJoDT+0bcxJkXt9PKF5v4OO7Omw8PFhkIv++jFXeKlQTW1bBtyQ92dkdGGHv5lYY67Djqjf3A==} dependencies: d3-array: 3.2.4 d3-timer: 3.0.1 - vega-dataflow: 5.7.5 - vega-format: 1.1.1 - vega-functions: 5.13.2 - vega-runtime: 6.1.4 - vega-scenegraph: 4.10.2 + vega-dataflow: 5.7.6 + vega-format: 1.1.2 + vega-functions: 5.15.0 + vega-runtime: 6.2.0 + vega-scenegraph: 4.13.0 vega-util: 1.17.2 transitivePeerDependencies: - encoding dev: false - /vega-voronoi@4.2.1: - resolution: {integrity: sha512-zzi+fxU/SBad4irdLLsG3yhZgXWZezraGYVQfZFWe8kl7W/EHUk+Eqk/eetn4bDeJ6ltQskX+UXH3OP5Vh0Q0Q==} + /vega-voronoi@4.2.3: + resolution: {integrity: sha512-aYYYM+3UGqwsOx+TkVtF1IZfguy0H7AN79dR8H0nONRIc+vhk/lbnlkgwY2nSzEu0EZ4b5wZxeGoDBEVmdDEcg==} dependencies: d3-delaunay: 6.0.4 - vega-dataflow: 5.7.5 + vega-dataflow: 5.7.6 vega-util: 1.17.2 transitivePeerDependencies: - encoding dev: false - /vega-wordcloud@4.1.4: - resolution: {integrity: sha512-oeZLlnjiusLAU5vhk0IIdT5QEiJE0x6cYoGNq1th+EbwgQp153t4r026fcib9oq15glHFOzf81a8hHXHSJm1Jw==} + /vega-wordcloud@4.1.5: + resolution: {integrity: sha512-p+qXU3cb9VeWzJ/HEdax0TX2mqDJcSbrCIfo2d/EalOXGkvfSLKobsmMQ8DxPbtVp0uhnpvfCGDyMJw+AzcI2A==} dependencies: vega-canvas: 1.2.7 - vega-dataflow: 5.7.5 - vega-scale: 7.3.0 + vega-dataflow: 5.7.6 + vega-scale: 7.4.1 vega-statistics: 1.9.0 vega-util: 1.17.2 transitivePeerDependencies: - encoding dev: false - /vega@5.25.0: - resolution: {integrity: sha512-lr+uj0mhYlSN3JOKbMNp1RzZBenWp9DxJ7kR3lha58AFNCzzds7pmFa7yXPbtbaGhB7Buh/t6n+Bzk3Y0VnF5g==} + /vega@5.30.0: + resolution: {integrity: sha512-ZGoC8LdfEUV0LlXIuz7hup9jxuQYhSaWek2M7r9dEHAPbPrzSQvKXZ0BbsJbrarM100TGRpTVN/l1AFxCwDkWw==} dependencies: - vega-crossfilter: 4.1.1 - vega-dataflow: 5.7.5 - vega-encode: 4.9.2 + vega-crossfilter: 4.1.2 + vega-dataflow: 5.7.6 + vega-encode: 4.10.1 vega-event-selector: 3.0.1 - vega-expression: 5.1.0 - vega-force: 4.2.0 - vega-format: 1.1.1 - vega-functions: 5.13.2 - vega-geo: 4.4.1 - vega-hierarchy: 4.1.1 - vega-label: 1.2.1 - vega-loader: 4.5.1 - vega-parser: 6.2.0 - vega-projection: 1.6.0 - vega-regression: 1.2.0 - vega-runtime: 6.1.4 - vega-scale: 7.3.0 - vega-scenegraph: 4.10.2 + vega-expression: 5.1.1 + vega-force: 4.2.1 + vega-format: 1.1.2 + vega-functions: 5.15.0 + vega-geo: 4.4.2 + vega-hierarchy: 4.1.2 + vega-label: 1.3.0 + vega-loader: 4.5.2 + vega-parser: 6.4.0 + vega-projection: 1.6.1 + vega-regression: 1.3.0 + vega-runtime: 6.2.0 + vega-scale: 7.4.1 + vega-scenegraph: 4.13.0 vega-statistics: 1.9.0 - vega-time: 2.1.1 - vega-transforms: 4.10.2 - vega-typings: 0.24.1 + vega-time: 2.1.2 + vega-transforms: 4.12.0 + vega-typings: 1.3.1 vega-util: 1.17.2 - vega-view: 5.11.1 - vega-view-transforms: 4.5.9 - vega-voronoi: 4.2.1 - vega-wordcloud: 4.1.4 + vega-view: 5.13.0 + vega-view-transforms: 4.6.0 + vega-voronoi: 4.2.3 + vega-wordcloud: 4.1.5 transitivePeerDependencies: - encoding dev: false @@ -6234,22 +5954,15 @@ packages: resolution: {integrity: sha512-DRibZL6DsNhIgYQ+wNdWDL2SL3bKPlVrRiBqV5yuMm++op8W4kGFtaQfCs4KEJn0wBZcHVHJ3eoywX8983k1ow==} 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. - dependencies: - browser-process-hrtime: 1.0.0 - dev: true - /w3c-keyname@2.2.8: resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} dev: false - /w3c-xmlserializer@2.0.0: - resolution: {integrity: sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==} - engines: {node: '>=10'} + /w3c-xmlserializer@4.0.0: + resolution: {integrity: sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==} + engines: {node: '>=14'} dependencies: - xml-name-validator: 3.0.0 + xml-name-validator: 4.0.0 dev: true /walker@1.0.8: @@ -6262,24 +5975,29 @@ packages: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} dev: false - /webidl-conversions@5.0.0: - resolution: {integrity: sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==} - engines: {node: '>=8'} + /webidl-conversions@7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} dev: true - /webidl-conversions@6.1.0: - resolution: {integrity: sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==} - engines: {node: '>=10.4'} + /whatwg-encoding@2.0.0: + resolution: {integrity: sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==} + engines: {node: '>=12'} + dependencies: + iconv-lite: 0.6.3 dev: true - /whatwg-encoding@1.0.5: - resolution: {integrity: sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==} - dependencies: - iconv-lite: 0.4.24 + /whatwg-mimetype@3.0.0: + resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==} + engines: {node: '>=12'} dev: true - /whatwg-mimetype@2.3.0: - resolution: {integrity: sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==} + /whatwg-url@11.0.0: + resolution: {integrity: sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==} + engines: {node: '>=12'} + dependencies: + tr46: 3.0.0 + webidl-conversions: 7.0.0 dev: true /whatwg-url@5.0.0: @@ -6289,19 +6007,6 @@ packages: webidl-conversions: 3.0.1 dev: false - /whatwg-url@8.7.0: - resolution: {integrity: sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==} - engines: {node: '>=10'} - dependencies: - lodash: 4.17.21 - tr46: 2.1.0 - webidl-conversions: 6.1.0 - dev: true - - /which-module@2.0.1: - resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} - dev: true - /which-typed-array@1.1.9: resolution: {integrity: sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==} engines: {node: '>= 0.4'} @@ -6314,13 +6019,6 @@ packages: is-typed-array: 1.1.10 dev: false - /which@1.3.1: - resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} - hasBin: true - dependencies: - isexe: 2.0.0 - dev: true - /which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -6329,19 +6027,10 @@ packages: isexe: 2.0.0 dev: true - /word-wrap@1.2.3: - resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==} + /word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} - /wrap-ansi@6.2.0: - resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} - engines: {node: '>=8'} - dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - dev: true - /wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -6350,33 +6039,28 @@ packages: string-width: 4.2.3 strip-ansi: 6.0.1 + /wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + dev: true + /wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - /write-file-atomic@3.0.3: - resolution: {integrity: sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==} + /write-file-atomic@4.0.2: + resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} dependencies: imurmurhash: 0.1.4 - is-typedarray: 1.0.0 signal-exit: 3.0.7 - typedarray-to-buffer: 3.1.5 - dev: true - - /ws@7.5.9: - resolution: {integrity: sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==} - engines: {node: '>=8.3.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: ^5.0.2 - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true dev: true - /ws@8.16.0: - resolution: {integrity: sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==} + /ws@8.17.1: + resolution: {integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -6388,8 +6072,9 @@ packages: optional: true dev: true - /xml-name-validator@3.0.0: - resolution: {integrity: sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==} + /xml-name-validator@4.0.0: + resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} + engines: {node: '>=12'} dev: true /xmlchars@2.2.0: @@ -6400,10 +6085,6 @@ packages: resolution: {integrity: sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==} dev: false - /y18n@4.0.3: - resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} - dev: true - /y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} @@ -6415,41 +6096,16 @@ packages: /yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} - /yargs-parser@18.1.3: - resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} - engines: {node: '>=6'} - dependencies: - camelcase: 5.3.1 - decamelize: 1.2.0 - dev: true - /yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} - /yargs@15.4.1: - resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} - engines: {node: '>=8'} - dependencies: - cliui: 6.0.0 - decamelize: 1.2.0 - find-up: 4.1.0 - get-caller-file: 2.0.5 - require-directory: 2.1.1 - require-main-filename: 2.0.0 - set-blocking: 2.0.0 - string-width: 4.2.3 - which-module: 2.0.1 - y18n: 4.0.3 - yargs-parser: 18.1.3 - dev: true - /yargs@17.7.2: resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} engines: {node: '>=12'} dependencies: cliui: 8.0.1 - escalade: 3.1.1 + escalade: 3.1.2 get-caller-file: 2.0.5 require-directory: 2.1.1 string-width: 4.2.3 @@ -6468,11 +6124,5 @@ 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 - dev: false + /zod@3.23.8: + resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} diff --git a/ui/release/OWNERS b/ui/release/OWNERS index 410876411d..49b921fa9f 100644 --- a/ui/release/OWNERS +++ b/ui/release/OWNERS @@ -2,5 +2,4 @@ set noparent eseckler@google.com lalitm@google.com primiano@google.com -skyostil@google.com stevegolton@google.com diff --git a/ui/release/channels.json b/ui/release/channels.json index 7e403f22a3..94028bd174 100644 --- a/ui/release/channels.json +++ b/ui/release/channels.json @@ -2,11 +2,11 @@ "channels": [ { "name": "stable", - "rev": "444bb44f0712aeb12d6ab546035811d4541a8f93" + "rev": "206f403988fb603720111dcabb14f38f6ebc1a54" }, { "name": "canary", - "rev": "4fad83137e7ce34736f5238b7667b1dbdeeb5e2a" + "rev": "f5e863aa2933ab2ac5746c1f4a12c3ba073e46ae" }, { "name": "autopush", diff --git a/ui/src/assets/brand.png b/ui/src/assets/brand.png index dc6f8b6f57f1de6d85a4799693ae759b93541d1c..63fe6f3cd7a6dbc8783ababaedef208c20837f22 100644 GIT binary patch literal 3260 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 E2YqPk5&!@I literal 4000 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 delta 2237 zcmV;u2txO+555tQ8Gi-<0063Kaozv`00d`2O+f$vv5yP^wDJ&nAmW?@;`KTOqY}5qQW@JP$MjBKtrP2M2W(XRV3IP)>(EXKk$XTO@ zx3)yU2U|j5Ej%)z}7XORJKP(%T}*%Z~{)E>vfN-lHwx zzt{@C+p<987{b8YVTW=@UubY>1aW?wA8cI|3=>8Tf`3R|vX~IZuuZLF;Pe(NvDj67 zjR{UOga0BMc$eEN_L_e0F3>BJRZZjvgKh;KY?3ogr^T z4BwEbP(=j3FCX~41-y3KWsygWcEqVBfOA-^ogK#86FeUCTot1mlS{lgUUdwDI)}GB zq9>k(hC}=Yv|@Dt42Vl&i=fmdphcw(UZ~atU4K_)C?@W`(+-{A%OrK!AhIw8a+hgg z>9YP%vLqPx69OmR?*_Ma`m)FTsmV}ZUk?wiIRr&I^=@}~N}t3sjIzB$qn~HHx^4@t zvPt`VoCW>`R_HT4m5B|+f}059^T+1IKxttR+$ifrzIqhY*WF?SczCu@w5MF#;15yy zB!8BRlpH5uRK*qK^qQ0=k2o=o0pl$Y>OsgGsW-#+5bq! z(TNQm+lQo>d80ZSz8_{mu+^-}glELKsef?q-aUqVU0ofRhva}U#Q>)^O9-U)c)^9F z!4R)yh0xW46DXWv(GBUk(k6*~m-QLYFF_shF-e(l;o>FN*Wb0z;rb7QpM&dq3JUOV zi67ifj)DD&I(D>3IP%Wt=_xG+I+_FG zjt_-9@`zar3MD=JNrGNb07SZu3XTZprtaUE zy={v4@lU2`N(#?egs9+C+24>v>7a-gMpH%Vg-}|{#)dVOl z+r=VZRb2xEjW${Jj3beskO0>SqG-EAxNC3~rg>q&MB$hpa{!@wb;wVdwSYw)QM_rZ z{GLScx5zM{!(-%*0+h*_@qhWfX;X!KqAeS4R8_Oc->IzyL;6rfbvi_b!5Tw6EkK#3 zajmYY&~Ke^@Y8E*nkoW(b>v&lua84QX`Qdf#|sLu%@9Wma8ko8R5mO0{V1!&$N&7r zD>%vDzkeTW*<%#fJ3+(_u;T&qWliJCY*V3kxv&61PkSx^hPPY-RDXP4=~2By^}_D( z2V<=JGlaW_PJ4On5%vHfCeK6P%0ovv1sIVx#iM!?NN6l1z(q1erqgBe1lVi$D8u8# z^tlUJ*I1_Xz2Q0w#N=&<_<|Y+c#UOZJJ4L)@aTjH z4Sez8bKnLnqwhn)KG>%%gUnBg>UN{p3kAnMZ%!7}jo_V72J}Vy+DmQUj8<5VXM4kUAZfzs;tF4ot{7G9xKrDKQ};*VVQ7kr_FM zrUX-L5;jv}a)O*zJ2a*A*jx)GMcd}s+>?WoPmaz$IXvBAj?e!Op+^Z~y~&Dy00000 LNkvXXu0mjf3dlH~ diff --git a/ui/src/assets/index.html b/ui/src/assets/index.html index 71978b3adb..5867c1e5a9 100644 --- a/ui/src/assets/index.html +++ b/ui/src/assets/index.html @@ -28,7 +28,10 @@ Perfetto UI - An unrecoverable problem occurred If you are seeing this message, something went wrong while loading the UI. -Please file a bug (details below) and try these remediation steps: +In most cases this is due to very slow or flaky network and it goes away by +disabling and re-enabling WiFi or trying reloading. + +If the problem persists try these remediation steps: * Force-reload the page with Ctrl+Shift+R (Mac: Meta+Shift+R) or Shift + click on the refresh button. @@ -37,7 +40,7 @@ * Clear the site data and caches from devtools, following these instructions. -In any case, **FILE A BUG** attaching logs and screenshots from devtools. +If none of this works, file a bug attaching logs and screenshots from devtools. Googlers: go/perfetto-ui-bug Non-googlers: github.com/google/perfetto/issues/new @@ -49,7 +52,7 @@

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�Fcw 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᜻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 literal 126643 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