diff --git a/Android.bp b/Android.bp index db99c35604..c934e165a0 100644 --- a/Android.bp +++ b/Android.bp @@ -15721,6 +15721,7 @@ filegroup { "src/trace_processor/importers/proto/proto_trace_parser_impl.cc", "src/trace_processor/importers/proto/proto_trace_reader.cc", "src/trace_processor/importers/proto/proto_trace_tokenizer.cc", + "src/trace_processor/importers/proto/protovm_incremental_tracing.cc", "src/trace_processor/importers/proto/stack_profile_sequence_state.cc", "src/trace_processor/importers/proto/track_event_module.cc", "src/trace_processor/importers/proto/track_event_parser.cc", @@ -16949,6 +16950,7 @@ cc_library_static { ":perfetto_src_base_version", ":perfetto_src_kernel_utils_kernel_wakelock_errors", ":perfetto_src_kernel_utils_syscall_table", + ":perfetto_src_protovm_protovm", ":perfetto_src_protozero_proto_ring_buffer", ":perfetto_src_protozero_protozero", ":perfetto_src_protozero_text_to_proto_text_to_proto", @@ -20298,6 +20300,7 @@ cc_library_static { ":perfetto_src_base_clock_snapshots", ":perfetto_src_kernel_utils_kernel_wakelock_errors", ":perfetto_src_kernel_utils_syscall_table", + ":perfetto_src_protovm_protovm", ":perfetto_src_protozero_protozero", ":perfetto_src_protozero_text_to_proto_text_to_proto", ":perfetto_src_trace_processor_containers_containers", @@ -20829,6 +20832,7 @@ cc_binary_host { ":perfetto_src_base_version", ":perfetto_src_kernel_utils_kernel_wakelock_errors", ":perfetto_src_kernel_utils_syscall_table", + ":perfetto_src_protovm_protovm", ":perfetto_src_protozero_proto_ring_buffer", ":perfetto_src_protozero_protozero", ":perfetto_src_protozero_text_to_proto_text_to_proto", diff --git a/BUILD b/BUILD index 551b611fa0..d3a53b9b18 100644 --- a/BUILD +++ b/BUILD @@ -351,6 +351,7 @@ perfetto_cc_library( srcs = [ ":src_kernel_utils_kernel_wakelock_errors", ":src_kernel_utils_syscall_table", + ":src_protovm_protovm", ":src_protozero_proto_ring_buffer", ":src_protozero_text_to_proto_text_to_proto", ":src_trace_processor_core_common_common", @@ -564,6 +565,7 @@ perfetto_cc_library( srcs = [ ":src_kernel_utils_kernel_wakelock_errors", ":src_kernel_utils_syscall_table", + ":src_protovm_protovm", ":src_protozero_proto_ring_buffer", ":src_protozero_text_to_proto_text_to_proto", ":src_trace_processor_core_common_common", @@ -2837,6 +2839,8 @@ perfetto_filegroup( "src/trace_processor/importers/proto/proto_trace_reader.h", "src/trace_processor/importers/proto/proto_trace_tokenizer.cc", "src/trace_processor/importers/proto/proto_trace_tokenizer.h", + "src/trace_processor/importers/proto/protovm_incremental_tracing.cc", + "src/trace_processor/importers/proto/protovm_incremental_tracing.h", "src/trace_processor/importers/proto/stack_profile_sequence_state.cc", "src/trace_processor/importers/proto/stack_profile_sequence_state.h", "src/trace_processor/importers/proto/track_event_event_importer.h", @@ -8459,6 +8463,7 @@ perfetto_cc_library( srcs = [ ":src_kernel_utils_kernel_wakelock_errors", ":src_kernel_utils_syscall_table", + ":src_protovm_protovm", ":src_protozero_text_to_proto_text_to_proto", ":src_trace_processor_core_common_common", ":src_trace_processor_core_dataframe_dataframe", @@ -8700,6 +8705,7 @@ perfetto_cc_binary( ":include_perfetto_trace_processor_util", ":src_kernel_utils_kernel_wakelock_errors", ":src_kernel_utils_syscall_table", + ":src_protovm_protovm", ":src_protozero_proto_ring_buffer", ":src_protozero_text_to_proto_text_to_proto", ":src_trace_processor_core_common_common", diff --git a/src/trace_processor/importers/proto/BUILD.gn b/src/trace_processor/importers/proto/BUILD.gn index a8e6af0a31..deabe8a7fd 100644 --- a/src/trace_processor/importers/proto/BUILD.gn +++ b/src/trace_processor/importers/proto/BUILD.gn @@ -56,6 +56,8 @@ source_set("minimal") { "proto_trace_reader.h", "proto_trace_tokenizer.cc", "proto_trace_tokenizer.h", + "protovm_incremental_tracing.cc", + "protovm_incremental_tracing.h", "stack_profile_sequence_state.cc", "stack_profile_sequence_state.h", "track_event_event_importer.h", @@ -94,6 +96,7 @@ source_set("minimal") { "../../../../protos/perfetto/trace/translation:zero", "../../../../protos/third_party/chromium:zero", "../../../base", + "../../../protovm", "../../../protozero", "../../containers", "../../sorter", diff --git a/src/trace_processor/importers/proto/proto_trace_reader.cc b/src/trace_processor/importers/proto/proto_trace_reader.cc index ee52c47ff7..1517eac5cf 100644 --- a/src/trace_processor/importers/proto/proto_trace_reader.cc +++ b/src/trace_processor/importers/proto/proto_trace_reader.cc @@ -152,6 +152,7 @@ class InlineSchedWakingSink ProtoTraceReader::ProtoTraceReader(TraceProcessorContext* ctx) : context_(ctx), parser_(std::make_unique(ctx, &module_context_)), + protovm_(ctx), skipped_packet_key_id_(ctx->storage->InternString("skipped_packet")), invalid_incremental_state_key_id_( ctx->storage->InternString("invalid_incremental_state")), @@ -236,6 +237,18 @@ base::Status ProtoTraceReader::ParsePacket(TraceBlobView packet) { PERFETTO_DCHECK(decoder.has_machine_id() == (context_->machine_id() != MachineId(kDefaultMachineId))); + if (decoder.has_trace_provenance()) { + protovm_.ProcessTraceProvenancePacket(decoder.trace_provenance()); + } else if (decoder.has_protovms()) { + protovm_.ProcessProtoVmsPacket(decoder.protovms()); + } else if (auto new_packet = protovm_.TryProcessPatch(packet); new_packet) { + packet = TraceBlobView(std::move(*new_packet)); + // TODO(keanmariotti): fix this hack + decoder.protos::pbzero::TracePacket::Decoder::~Decoder(); + new (&decoder) + protos::pbzero::TracePacket::Decoder(packet.data(), packet.length()); + } + uint32_t seq_id = decoder.trusted_packet_sequence_id(); auto [scoped_state, inserted] = sequence_state_.Insert(seq_id, {}); if (decoder.has_trusted_packet_sequence_id()) { diff --git a/src/trace_processor/importers/proto/proto_trace_reader.h b/src/trace_processor/importers/proto/proto_trace_reader.h index 3ae5a37e2a..c98bf76b23 100644 --- a/src/trace_processor/importers/proto/proto_trace_reader.h +++ b/src/trace_processor/importers/proto/proto_trace_reader.h @@ -31,6 +31,7 @@ #include "src/trace_processor/importers/proto/packet_sequence_state_builder.h" #include "src/trace_processor/importers/proto/proto_importer_module.h" #include "src/trace_processor/importers/proto/proto_trace_tokenizer.h" +#include "src/trace_processor/importers/proto/protovm_incremental_tracing.h" #include "src/trace_processor/storage/trace_storage.h" #include "src/trace_processor/types/trace_processor_context.h" @@ -125,6 +126,7 @@ class ProtoTraceReader : public ChunkedTraceReader { std::unique_ptr parser_; base::FlatHashMap> machine_to_proto_readers_; + ProtoVmIncrementalTracing protovm_; // Temporary. Currently trace packets do not have a timestamp, so the // timestamp given is latest_timestamp_. diff --git a/src/trace_processor/importers/proto/protovm_incremental_tracing.cc b/src/trace_processor/importers/proto/protovm_incremental_tracing.cc new file mode 100644 index 0000000000..48c0792a77 --- /dev/null +++ b/src/trace_processor/importers/proto/protovm_incremental_tracing.cc @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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/protovm_incremental_tracing.h" + +#include "perfetto/protozero/field.h" +#include "perfetto/protozero/scattered_heap_buffer.h" +#include "protos/perfetto/trace/perfetto/trace_provenance.pbzero.h" +#include "src/protovm/vm.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" + +namespace perfetto::trace_processor { + +ProtoVmIncrementalTracing::ProtoVmIncrementalTracing( + TraceProcessorContext* context) + : context_(context) {} + +ProtoVmIncrementalTracing::~ProtoVmIncrementalTracing() = default; + +void ProtoVmIncrementalTracing::ProcessTraceProvenancePacket( + protozero::ConstBytes blob) { + protos::pbzero::TraceProvenance::Decoder trace_provenance(blob); + for (auto it_buf = trace_provenance.buffers(); it_buf; ++it_buf) { + protos::pbzero::TraceProvenance::Buffer::Decoder buffer(*it_buf); + for (auto it_seq = buffer.sequences(); it_seq; ++it_seq) { + protos::pbzero::TraceProvenance::Sequence::Decoder sequence(*it_seq); + producer_id_to_sequence_ids_[sequence.producer_id()].push_back( + sequence.id()); + } + } +} + +void ProtoVmIncrementalTracing::ProcessProtoVmsPacket( + protozero::ConstBytes blob) { + protos::pbzero::TracePacket::ProtoVms::Decoder decoder(blob); + for (auto it = decoder.instance(); it; ++it) { + protos::pbzero::TracePacket::ProtoVms::Instance::Decoder instance(*it); + protozero::ConstBytes state = instance.has_state() + ? instance.state() + : protozero::ConstBytes{nullptr, 0}; + vms_.push_back(std::make_unique( + instance.program(), 1024 * instance.memory_limit_kb(), state)); + protovm::Vm* vm = vms_.back().get(); + for (auto producer_id = instance.producer_id(); producer_id; + ++producer_id) { + auto* sequence_ids = producer_id_to_sequence_ids_.Find(*producer_id); + if (!sequence_ids) { + context_->storage->IncrementStats(stats::protovm_registration_error); + continue; + } + for (auto sequence_id : *sequence_ids) { + sequence_id_to_vms_[sequence_id].push_back(vm); + } + } + } +} + +std::optional ProtoVmIncrementalTracing::TryProcessPatch( + const TraceBlobView& blob) { + protos::pbzero::TracePacket::Decoder patch(blob.data(), blob.size()); + if (PERFETTO_UNLIKELY(!patch.has_trusted_packet_sequence_id())) { + return std::nullopt; + } + std::vector* vms = + sequence_id_to_vms_.Find(patch.trusted_packet_sequence_id()); + if (!vms) { + return std::nullopt; + } + for (auto* vm : *vms) { + auto status = + vm->ApplyPatch(protozero::ConstBytes{blob.data(), blob.size()}); + if (status.IsOk()) { + return SerializeIncrementalState(*vm, patch); + } + if (status.IsAbort()) { + context_->storage->IncrementStats(stats::protovm_abort); + } + } + return std::nullopt; +} + +TraceBlob ProtoVmIncrementalTracing::SerializeIncrementalState( + const protovm::Vm& vm, + const protos::pbzero::TracePacket::Decoder& patch) const { + protozero::HeapBuffered proto; + vm.SerializeIncrementalState(proto.get()); + proto->set_trusted_uid(patch.trusted_uid()); + proto->set_trusted_pid(patch.trusted_pid()); + proto->set_trusted_packet_sequence_id(patch.trusted_packet_sequence_id()); + auto [data, size] = proto.SerializeAsUniquePtr(); + return TraceBlob::TakeOwnership(std::move(data), size); +} + +} // namespace perfetto::trace_processor diff --git a/src/trace_processor/importers/proto/protovm_incremental_tracing.h b/src/trace_processor/importers/proto/protovm_incremental_tracing.h new file mode 100644 index 0000000000..1a923a8e4a --- /dev/null +++ b/src/trace_processor/importers/proto/protovm_incremental_tracing.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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_PROTOVM_INCREMENTAL_TRACING_H_ +#define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_PROTOVM_INCREMENTAL_TRACING_H_ + +#include +#include +#include + +#include "perfetto/ext/base/flat_hash_map.h" +#include "perfetto/trace_processor/trace_blob.h" +#include "perfetto/trace_processor/trace_blob_view.h" +#include "protos/perfetto/trace/trace_packet.pbzero.h" + +namespace perfetto { + +namespace protovm { +class Vm; +} + +namespace trace_processor { + +class TraceProcessorContext; + +class ProtoVmIncrementalTracing { + public: + explicit ProtoVmIncrementalTracing(TraceProcessorContext*); + ~ProtoVmIncrementalTracing(); + void ProcessTraceProvenancePacket(protozero::ConstBytes blob); + void ProcessProtoVmsPacket(protozero::ConstBytes blob); + std::optional TryProcessPatch(const TraceBlobView& packet); + + private: + TraceBlob SerializeIncrementalState( + const protovm::Vm& vm, + const protos::pbzero::TracePacket::Decoder& patch) const; + + TraceProcessorContext* context_; + base::FlatHashMap> + producer_id_to_sequence_ids_; + base::FlatHashMap> sequence_id_to_vms_; + std::vector> vms_; +}; + +} // namespace trace_processor +} // namespace perfetto + +#endif // SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_PROTOVM_INCREMENTAL_TRACING_H_ diff --git a/src/trace_processor/storage/stats.h b/src/trace_processor/storage/stats.h index 147bd5906d..762d7b6947 100644 --- a/src/trace_processor/storage/stats.h +++ b/src/trace_processor/storage/stats.h @@ -826,7 +826,15 @@ namespace perfetto::trace_processor::stats { F(primes_missing_parent_id, kSingle, kInfo, kAnalysis, \ "The parent_id field was missing from an edge that requires it."), \ F(primes_malformed_timestamp, kSingle, kDataLoss, kAnalysis, \ - "The timestamp for an edge or trace was not able to be parsed") // clang-format on + "The timestamp for an edge or trace was not able to be parsed"), \ + F(protovm_abort, kSingle, kError, kAnalysis, \ + "A ProtoVM instance aborted the execution while applying the patch. " \ + "This might be due to inconsistencies between VM program logic and " \ + "actual patch format."), \ + F(protovm_registration_error, kSingle, kError, kAnalysis, \ + "Failed to find the sequence IDs corresponding to a ProtoVM's producer " \ + "ID. Such mapping should be provided by the TraceProvenance packet.") +// clang-format on enum Type { kSingle, // Single-value property, one value per key. diff --git a/test/trace_processor/diff_tests/include_index.py b/test/trace_processor/diff_tests/include_index.py index a69877bd34..5bb270ea43 100644 --- a/test/trace_processor/diff_tests/include_index.py +++ b/test/trace_processor/diff_tests/include_index.py @@ -57,6 +57,7 @@ from diff_tests.parser.android.tests_inputmethod_manager_service import InputMethodManagerService from diff_tests.parser.android.tests_inputmethod_service import InputMethodService from diff_tests.parser.android.tests_protolog import ProtoLog +from diff_tests.parser.android.tests_protovm_incremental_tracing import ProtoVmIncrementalTracing 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 @@ -248,6 +249,7 @@ def fetch_all_diff_tests( SurfaceFlingerTransactions, ShellTransitions, ProtoLog, + ProtoVmIncrementalTracing, ViewCapture, WindowManager, TrackEvent, diff --git a/test/trace_processor/diff_tests/parser/android/protovm_incremental_tracing.textproto b/test/trace_processor/diff_tests/parser/android/protovm_incremental_tracing.textproto new file mode 100644 index 0000000000..31c1129845 --- /dev/null +++ b/test/trace_processor/diff_tests/parser/android/protovm_incremental_tracing.textproto @@ -0,0 +1,162 @@ +packet { + clock_snapshot { + primary_trace_clock: BUILTIN_CLOCK_BOOTTIME + clocks { clock_id: 6 timestamp: 0 } + clocks { clock_id: 2 timestamp: 1682359234732002451 } + clocks { clock_id: 4 timestamp: 0 } + clocks { clock_id: 1 timestamp: 1682359234732002451 } + clocks { clock_id: 3 timestamp: 0 } + clocks { clock_id: 5 timestamp: 0 } + } + trusted_uid: 9999 + trusted_packet_sequence_id: 1 +} + +packet { + trace_provenance { + buffers: + [ { + sequences: + [ + # sequences from producer IDs 10 and 11 are processed by the ProtoVM + # (see ProtoVM config in packet below) + { id: 1 producer_id: 10 } + , { id: 2 producer_id: 11 } + , + # sequences from producer ID 100 are ignored by the ProtoVM + # (see ProtoVM config in packet below) + { id: 3 producer_id: 100 }] + }] + } +} + +# Minimal ProtoVM program. +# +# Input (patches): surfaceflinger_transaction packets +# Output (incremental states): surfaceflinger_layers_snapshot packets +# +# Logic: +# - Copy packet.surfaceflinger_transaction.vsync_id +# to packet.surfaceflinger_layers_snapshot.vsync_id +# - Copy packet.surfaceflinger_transaction.elapsed_realtime_nanos +# to packet.timestamp +packet { + protovms { + instance: + [ { + program { + instructions: + [ + # Copy packet.surfaceflinger_transaction.vsync_id + # into packet.surfaceflinger_layers_snapshot.vsync_id + { + select { + relative_path: + [ { + field_id: 94 # surfaceflinger_transaction + } + , + { + field_id: 2 # vsync_id + }] + } + nested_instructions: + [ { + select { + cursor: VM_CURSOR_DST + create_if_not_exist: true + relative_path: + [ { + field_id: 93 # surfaceflinger_layers_snapshot + } + , + { + field_id: 8 # vsync_id + }] + } + nested_instructions: + [ { set {} }] + }] + abort_level: SKIP_CURRENT_INSTRUCTION_AND_BREAK_OUTER + } + , + # Copy packet.surfaceflinger_transaction.elapsed_realtime_nanos + # into packet.timestamp + { + select { + relative_path: + [ { + field_id: 94 # surfaceflinger_transaction + } + , + { + field_id: 1 # elapsed_realtime_nanos + }] + } + nested_instructions: + [ { + select { + cursor: VM_CURSOR_DST + create_if_not_exist: true + relative_path: + [ { + field_id: 8 # timestamp + }] + } + nested_instructions: + [ { set {} }] + }] + }] + } + memory_limit_kb: 1 + producer_id: [ 10, 11 ] + state { + surfaceflinger_layers_snapshot { + where: "value set in initial state and never touched by the ProtoVM" + } + } + }] + } +} + +# Processed (patch applied) by the ProtoVM +packet { + first_packet_on_sequence: true + timestamp: 2749532000000 + timestamp_clock_id: 3 + surfaceflinger_transactions { + elapsed_realtime_nanos: 2749532000001 + vsync_id: 24776 + } + trusted_packet_sequence_id: 1 + previous_packet_dropped: true +} + +# Processed (patch applied) by the ProtoVM +packet { + timestamp: 2749555000000 + timestamp_clock_id: 3 + surfaceflinger_transactions { + elapsed_realtime_nanos: 2749555000001 + vsync_id: 24805 + } + trusted_packet_sequence_id: 2 +} + +# Ignored by the ProtoVM. Comes from Producer ID 10 (processed by the ProtoVM), +# but it isn't a surfaceflinger_transactions packet (i.e. not a patch). +packet { + timestamp: 2749560000000 + timestamp_clock_id: 3 + for_testing { seq_value: 0 } + trusted_packet_sequence_id: 2 +} + +# Ignored by the ProtoVM. It's a valid patch, but comes from producer ID 100 and +# the ProtoVM was configured to process only Producer ID 10 and 11. +packet { + timestamp: 2749570000000 + timestamp_clock_id: 3 + surfaceflinger_transactions { elapsed_realtime_nanos: 0 vsync_id: 0 } + trusted_packet_sequence_id: 3 +} diff --git a/test/trace_processor/diff_tests/parser/android/tests_protovm_incremental_tracing.py b/test/trace_processor/diff_tests/parser/android/tests_protovm_incremental_tracing.py new file mode 100644 index 0000000000..ff115a81b8 --- /dev/null +++ b/test/trace_processor/diff_tests/parser/android/tests_protovm_incremental_tracing.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 +# Copyright (C) 2026 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 + + +# Tests a simple ProtoVM program that consumes surfaceflinger_transaction packets +# as patches and applies them to surfaceflinger_layers_snapshot packets. +# See protovm_incremental_tracing.textproto for more details. +class ProtoVmIncrementalTracing(TestSuite): + + def test_timestamps(self): + return DiffTestBlueprint( + trace=Path('protovm_incremental_tracing.textproto'), + query=""" + SELECT + id, ts + FROM + surfaceflinger_layers_snapshot LIMIT 2; + """, + out=Csv(""" + "id","ts" + 0,2749532000001 + 1,2749555000001 + """)) + + def test_vsync_id(self): + return DiffTestBlueprint( + trace=Path('protovm_incremental_tracing.textproto'), + query=""" + SELECT + args.display_value + FROM + surfaceflinger_layers_snapshot AS sfs JOIN args ON sfs.arg_set_id = args.arg_set_id + WHERE args.key = "vsync_id" + ORDER BY sfs.id; + """, + out=Csv(""" + "display_value" + "24776" + "24805" + """)) + + def test_where(self): + return DiffTestBlueprint( + trace=Path('protovm_incremental_tracing.textproto'), + query=""" + SELECT + args.display_value + FROM + surfaceflinger_layers_snapshot AS sfs JOIN args ON sfs.arg_set_id = args.arg_set_id + WHERE args.key = "where" + ORDER BY sfs.id; + """, + out=Csv(""" + "display_value" + "value set in initial state and never touched by the ProtoVM" + "value set in initial state and never touched by the ProtoVM" + """))