| // Copyright 2025 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/actor/aggregated_journal_serializer.h" |
| |
| #include "base/containers/span.h" |
| #include "chrome/common/actor/journal_details_builder.h" |
| #include "components/tracing/common/system_profile_metadata_recorder.h" |
| #include "third_party/abseil-cpp/absl/strings/str_format.h" |
| #include "third_party/perfetto/include/perfetto/ext/tracing/core/trace_packet.h" |
| #include "third_party/perfetto/include/perfetto/protozero/scattered_heap_buffer.h" |
| #include "third_party/perfetto/protos/perfetto/common/builtin_clock.pbzero.h" |
| #include "third_party/perfetto/protos/perfetto/config/data_source_config.pbzero.h" |
| #include "third_party/perfetto/protos/perfetto/config/trace_config.pbzero.h" |
| #include "third_party/perfetto/protos/perfetto/config/track_event/track_event_config.gen.h" |
| #include "third_party/perfetto/protos/perfetto/trace/chrome/chrome_trace_event.pbzero.h" |
| #include "third_party/perfetto/protos/perfetto/trace/clock_snapshot.pbzero.h" |
| #include "third_party/perfetto/protos/perfetto/trace/perfetto/tracing_service_event.pbzero.h" |
| #include "third_party/perfetto/protos/perfetto/trace/trace_packet.pbzero.h" |
| #include "third_party/perfetto/protos/perfetto/trace/track_event/debug_annotation.pbzero.h" |
| #include "third_party/perfetto/protos/perfetto/trace/track_event/process_descriptor.pbzero.h" |
| #include "third_party/perfetto/protos/perfetto/trace/track_event/screenshot.pbzero.h" |
| #include "third_party/perfetto/protos/perfetto/trace/track_event/track_descriptor.pbzero.h" |
| #include "third_party/perfetto/protos/perfetto/trace/track_event/track_event.pbzero.h" |
| |
| namespace actor { |
| |
| namespace { |
| |
| uint64_t NowInNanoseconds() { |
| return (base::Time::Now() - base::Time::UnixEpoch()).InNanoseconds(); |
| } |
| |
| } // namespace |
| |
| AggregatedJournalSerializer::AggregatedJournalSerializer( |
| AggregatedJournal& journal) |
| : journal_(journal.GetSafeRef()) {} |
| |
| void AggregatedJournalSerializer::InitImpl() { |
| WriteTracePreamble(); |
| journal_->AddObserver(this); |
| } |
| |
| void AggregatedJournalSerializer::WriteTracePreamble() { |
| observed_task_ids_.clear(); |
| observed_track_ids_.clear(); |
| // Write initial message in protobuf. |
| { |
| protozero::HeapBuffered<perfetto::protos::pbzero::TracePacket> init_msg; |
| init_msg->set_trusted_packet_sequence_id(sequence_id_++); |
| auto* clock_snapshot = init_msg->set_clock_snapshot(); |
| clock_snapshot->set_primary_trace_clock( |
| perfetto::protos::pbzero::BUILTIN_CLOCK_REALTIME); |
| auto* clock = clock_snapshot->add_clocks(); |
| clock->set_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_REALTIME); |
| clock->set_timestamp(NowInNanoseconds()); |
| |
| auto* trace_config = init_msg->set_trace_config(); |
| auto* data_source = trace_config->add_data_sources(); |
| auto* source_config = data_source->set_config(); |
| source_config->set_name("track_event"); |
| source_config->set_target_buffer(0); |
| perfetto::protos::gen::TrackEventConfig track_event_config; |
| track_event_config.add_enabled_categories("*"); |
| source_config->set_track_event_config_raw( |
| track_event_config.SerializeAsString()); |
| WriteTracePacket(init_msg.SerializeAsArray()); |
| } |
| // Write tracing started. |
| { |
| protozero::HeapBuffered<perfetto::protos::pbzero::TracePacket> msg; |
| msg->set_trusted_packet_sequence_id(sequence_id_++); |
| msg->set_timestamp(NowInNanoseconds()); |
| msg->set_timestamp_clock_id( |
| perfetto::protos::pbzero::BUILTIN_CLOCK_REALTIME); |
| auto* service_event = msg->set_service_event(); |
| service_event->set_tracing_started(true); |
| WriteTracePacket(msg.SerializeAsArray()); |
| } |
| // Write tracting active. |
| { |
| protozero::HeapBuffered<perfetto::protos::pbzero::TracePacket> msg; |
| msg->set_trusted_packet_sequence_id(sequence_id_++); |
| msg->set_timestamp(NowInNanoseconds()); |
| msg->set_timestamp_clock_id( |
| perfetto::protos::pbzero::BUILTIN_CLOCK_REALTIME); |
| auto* service_event = msg->set_service_event(); |
| service_event->set_all_data_sources_started(true); |
| WriteTracePacket(msg.SerializeAsArray()); |
| } |
| |
| // Record the system info in the actor journal. |
| { |
| protozero::HeapBuffered<perfetto::protos::pbzero::TracePacket> msg; |
| tracing::RecordSystemProfileMetadata(msg->set_chrome_events()); |
| WriteTracePacket(msg.SerializeAsArray()); |
| } |
| } |
| |
| AggregatedJournalSerializer::~AggregatedJournalSerializer() { |
| journal_->RemoveObserver(this); |
| } |
| |
| void AggregatedJournalSerializer::WillAddJournalEntry( |
| const AggregatedJournal::Entry& entry) { |
| ObservedTaskId(entry.data->task_id); |
| ObservedTrackId(entry.data->track_uuid, entry.data->task_id, |
| entry.data->event); |
| |
| protozero::HeapBuffered<perfetto::protos::pbzero::TracePacket> msg; |
| msg->set_trusted_packet_sequence_id(sequence_id_++); |
| msg->set_timestamp( |
| (entry.data->timestamp - base::Time::UnixEpoch()).InNanoseconds()); |
| msg->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_REALTIME); |
| auto* track_event = msg->set_track_event(); |
| perfetto::protos::pbzero::TrackEvent_Type pb_type = |
| perfetto::protos::pbzero::TrackEvent::TYPE_UNSPECIFIED; |
| switch (entry.data->type) { |
| case mojom::JournalEntryType::kBegin: |
| pb_type = perfetto::protos::pbzero::TrackEvent::TYPE_SLICE_BEGIN; |
| break; |
| case mojom::JournalEntryType::kEnd: |
| pb_type = perfetto::protos::pbzero::TrackEvent::TYPE_SLICE_END; |
| break; |
| case mojom::JournalEntryType::kInstant: |
| pb_type = perfetto::protos::pbzero::TrackEvent::TYPE_INSTANT; |
| break; |
| } |
| track_event->set_type(pb_type); |
| track_event->set_name(entry.data->event); |
| track_event->set_track_uuid(entry.data->track_uuid); |
| |
| // For Perfetto to read screenshots we need to have the category as |
| // "android_screenshot". See |
| // https://github.com/google/perfetto/blob/891351c7233523c01dc0e58ac8650df47fad9ab5/src/trace_processor/perfetto_sql/stdlib/android/screenshots.sql#L37 |
| track_event->add_categories( |
| entry.screenshot.has_value() ? "android_screenshot" : "actor"); |
| |
| for (auto& details_entry : entry.data->details) { |
| auto* annotation = track_event->add_debug_annotations(); |
| annotation->set_name(details_entry->key); |
| annotation->set_string_value(details_entry->value); |
| } |
| |
| // If we have an annontated page content we encode it into screenshot |
| // descriptor for now. TODO(dtapuska): annotation->set_proto_value |
| // wasn't working because it didn't know about the encoded protobuf |
| // type in the chrome_intelligence_proto_features.AnnotatedPageContent type. |
| if (entry.annotated_page_content.has_value()) { |
| auto* screenshot = track_event->set_screenshot(); |
| screenshot->set_jpg_image(entry.annotated_page_content->data(), |
| entry.annotated_page_content->size()); |
| } |
| |
| if (entry.screenshot.has_value()) { |
| auto* screenshot = track_event->set_screenshot(); |
| // Despite being named jpg_image this field will support any image/* payload |
| screenshot->set_jpg_image(entry.screenshot->data(), |
| entry.screenshot->size()); |
| } |
| |
| if (!entry.url.empty()) { |
| auto* annotation = track_event->add_debug_annotations(); |
| annotation->set_name("url"); |
| annotation->set_string_value(entry.url); |
| } |
| |
| WriteTracePacket(msg.SerializeAsArray()); |
| } |
| |
| void AggregatedJournalSerializer::ObservedTaskId(TaskId task_id) { |
| if (task_id.value() == 0 || observed_task_ids_.contains(task_id)) { |
| return; |
| } |
| |
| { |
| protozero::HeapBuffered<perfetto::protos::pbzero::TracePacket> msg; |
| msg->set_trusted_packet_sequence_id(sequence_id_++); |
| msg->set_timestamp(NowInNanoseconds()); |
| msg->set_timestamp_clock_id( |
| perfetto::protos::pbzero::BUILTIN_CLOCK_REALTIME); |
| auto* track_descriptor = msg->set_track_descriptor(); |
| track_descriptor->set_uuid(task_id.value()); |
| track_descriptor->set_name("Actor Task Default"); |
| auto* process_descriptor = track_descriptor->set_process(); |
| process_descriptor->set_pid(task_id.value()); |
| WriteTracePacket(msg.SerializeAsArray()); |
| } |
| { |
| protozero::HeapBuffered<perfetto::protos::pbzero::TracePacket> msg; |
| msg->set_trusted_packet_sequence_id(sequence_id_++); |
| msg->set_timestamp(NowInNanoseconds()); |
| msg->set_timestamp_clock_id( |
| perfetto::protos::pbzero::BUILTIN_CLOCK_REALTIME); |
| auto* track_descriptor = msg->set_track_descriptor(); |
| track_descriptor->set_uuid(MakeFrontEndTrackUUID(task_id)); |
| track_descriptor->set_parent_uuid(task_id.value()); |
| track_descriptor->set_name("Front End"); |
| WriteTracePacket(msg.SerializeAsArray()); |
| } |
| { |
| protozero::HeapBuffered<perfetto::protos::pbzero::TracePacket> msg; |
| msg->set_trusted_packet_sequence_id(sequence_id_++); |
| msg->set_timestamp(NowInNanoseconds()); |
| msg->set_timestamp_clock_id( |
| perfetto::protos::pbzero::BUILTIN_CLOCK_REALTIME); |
| auto* track_descriptor = msg->set_track_descriptor(); |
| track_descriptor->set_uuid(MakeBrowserTrackUUID(task_id)); |
| track_descriptor->set_parent_uuid(task_id.value()); |
| track_descriptor->set_name("Browser"); |
| WriteTracePacket(msg.SerializeAsArray()); |
| } |
| { |
| protozero::HeapBuffered<perfetto::protos::pbzero::TracePacket> msg; |
| msg->set_trusted_packet_sequence_id(sequence_id_++); |
| msg->set_timestamp(NowInNanoseconds()); |
| msg->set_timestamp_clock_id( |
| perfetto::protos::pbzero::BUILTIN_CLOCK_REALTIME); |
| auto* track_descriptor = msg->set_track_descriptor(); |
| track_descriptor->set_uuid(MakeRendererTrackUUID(task_id)); |
| track_descriptor->set_parent_uuid(task_id.value()); |
| track_descriptor->set_name("Renderer"); |
| WriteTracePacket(msg.SerializeAsArray()); |
| } |
| observed_task_ids_.insert(task_id); |
| observed_track_ids_.insert(task_id.value()); |
| observed_track_ids_.insert(MakeFrontEndTrackUUID(task_id)); |
| observed_track_ids_.insert(MakeBrowserTrackUUID(task_id)); |
| observed_track_ids_.insert(MakeRendererTrackUUID(task_id)); |
| } |
| |
| void AggregatedJournalSerializer::ObservedTrackId( |
| uint64_t track_uuid, |
| TaskId task_id, |
| const std::string& event_name) { |
| if (track_uuid == 0 || task_id.value() == 0 || |
| observed_track_ids_.contains(track_uuid)) { |
| return; |
| } |
| |
| { |
| protozero::HeapBuffered<perfetto::protos::pbzero::TracePacket> msg; |
| msg->set_trusted_packet_sequence_id(sequence_id_++); |
| msg->set_timestamp(NowInNanoseconds()); |
| msg->set_timestamp_clock_id( |
| perfetto::protos::pbzero::BUILTIN_CLOCK_REALTIME); |
| auto* track_descriptor = msg->set_track_descriptor(); |
| |
| // For any dynamic track, associate them with the browser track and |
| // the name of the first event causing that track. |
| // We may need to adjust this if the Renderer or front end start |
| // producing dynamic tracks. |
| track_descriptor->set_uuid(track_uuid); |
| track_descriptor->set_parent_uuid(MakeBrowserTrackUUID(task_id)); |
| track_descriptor->set_name(event_name); |
| WriteTracePacket(msg.SerializeAsArray()); |
| } |
| observed_track_ids_.insert(track_uuid); |
| } |
| |
| } // namespace actor |