diff --git a/include/perfetto/ext/tracing/core/tracing_service.h b/include/perfetto/ext/tracing/core/tracing_service.h index e3beaffffa..cc20696ff9 100644 --- a/include/perfetto/ext/tracing/core/tracing_service.h +++ b/include/perfetto/ext/tracing/core/tracing_service.h @@ -211,6 +211,19 @@ class PERFETTO_EXPORT_COMPONENT ConsumerEndpoint { // If set, affects the generation of the FlushFlags::CloneTarget to be set // to kBugreport when requesting the flush to the producers. bool for_bugreport = false; + + // If not empty, this is stored in the trace as name of the trigger that + // caused the clone. + std::string clone_trigger_name; + // If not empty, this is stored in the trace as name of the producer that + // triggered the clone. + std::string clone_trigger_producer_name; + // If not zero, this is stored in the trace as uid of the producer that + // triggered the clone. + uid_t clone_trigger_trusted_producer_uid = 0; + // If not zero, this is stored in the trace as timestamp of the trigger that + // caused the clone. + uint64_t clone_trigger_boot_time_ns = 0; }; virtual void CloneSession(CloneSessionArgs) = 0; diff --git a/protos/perfetto/common/observable_events.proto b/protos/perfetto/common/observable_events.proto index b841a0b55f..4961db4115 100644 --- a/protos/perfetto/common/observable_events.proto +++ b/protos/perfetto/common/observable_events.proto @@ -64,8 +64,14 @@ message ObservableEvents { // 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. + // The name of the CLONE_SNAPSHOT trigger which was hit. optional string trigger_name = 2; + // The name of the producer that sent the CLONE_SNAPSHOT trigger. + optional string producer_name = 3; + // The uid of the producer that sent the CLONE_SNAPSHOT trigger. + optional uint32 producer_uid = 4; + // The timestamp of the CLONE_SNAPSHOT trigger which was hit. + optional uint64 boot_time_ns = 5; } repeated DataSourceInstanceStateChange instance_state_changes = 1; diff --git a/protos/perfetto/ipc/consumer_port.proto b/protos/perfetto/ipc/consumer_port.proto index a38e0eb552..ed1fb74434 100644 --- a/protos/perfetto/ipc/consumer_port.proto +++ b/protos/perfetto/ipc/consumer_port.proto @@ -304,6 +304,19 @@ message CloneSessionRequest { // If set, affects the generation of the FlushFlags::CloneTarget to be set // to kBugreport when requesting the flush to the producers. optional bool for_bugreport = 3; + + // If set, this is stored in the trace as name of the trigger that caused the + // clone. + optional string clone_trigger_name = 5; + // If set, this is stored in the trace as name of the producer that triggered + // the clone. + optional string clone_trigger_producer_name = 6; + // If set, this is stored in the trace as uid of the producer that triggered + // the clone. + optional int32 clone_trigger_trusted_producer_uid = 7; + // If set, this is stored in the trace as timestamp of the trigger that caused + // the clone. + optional uint64 clone_trigger_boot_time_ns = 8; } message CloneSessionResponse { diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto index ddf7de8a78..c819010d13 100644 --- a/protos/perfetto/trace/perfetto_trace.proto +++ b/protos/perfetto/trace/perfetto_trace.proto @@ -15672,7 +15672,7 @@ message UiState { // See the [Buffers and Dataflow](/docs/concepts/buffers.md) doc for details. // // Next reserved id: 14 (up to 15). -// Next id: 113. +// Next id: 114. message TracePacket { // The timestamp of the TracePacket. // By default this timestamps refers to the trace clock (CLOCK_BOOTTIME on @@ -15812,6 +15812,8 @@ message TracePacket { PixelModemEvents pixel_modem_events = 110; PixelModemTokenDatabase pixel_modem_token_database = 111; + Trigger clone_snapshot_trigger = 113; + // This field is only used for testing. // In previous versions of this proto this field had the id 268435455 // This caused many problems: diff --git a/protos/perfetto/trace/trace_packet.proto b/protos/perfetto/trace/trace_packet.proto index b6489e0937..6e84dc9c46 100644 --- a/protos/perfetto/trace/trace_packet.proto +++ b/protos/perfetto/trace/trace_packet.proto @@ -104,7 +104,7 @@ package perfetto.protos; // See the [Buffers and Dataflow](/docs/concepts/buffers.md) doc for details. // // Next reserved id: 14 (up to 15). -// Next id: 113. +// Next id: 114. message TracePacket { // The timestamp of the TracePacket. // By default this timestamps refers to the trace clock (CLOCK_BOOTTIME on @@ -244,6 +244,8 @@ message TracePacket { PixelModemEvents pixel_modem_events = 110; PixelModemTokenDatabase pixel_modem_token_database = 111; + Trigger clone_snapshot_trigger = 113; + // This field is only used for testing. // In previous versions of this proto this field had the id 268435455 // This caused many problems: diff --git a/src/perfetto_cmd/perfetto_cmd.cc b/src/perfetto_cmd/perfetto_cmd.cc index d3a12d41cd..729d056986 100644 --- a/src/perfetto_cmd/perfetto_cmd.cc +++ b/src/perfetto_cmd/perfetto_cmd.cc @@ -989,11 +989,11 @@ int PerfettoCmd::ConnectToServiceAndRun() { } if (is_clone()) { - if (snapshot_trigger_name_.empty()) { + if (!snapshot_trigger_info_.has_value()) { LogUploadEvent(PerfettoStatsdAtom::kCmdCloneTraceBegin); } else { LogUploadEvent(PerfettoStatsdAtom::kCmdCloneTriggerTraceBegin, - snapshot_trigger_name_); + snapshot_trigger_info_->trigger_name); } } else if (trace_config_->trigger_config().trigger_timeout_ms() == 0) { LogUploadEvent(PerfettoStatsdAtom::kTraceBegin); @@ -1076,6 +1076,13 @@ void PerfettoCmd::OnConnect() { } else if (!clone_name_.empty()) { args.unique_session_name = clone_name_; } + if (snapshot_trigger_info_.has_value()) { + args.clone_trigger_name = snapshot_trigger_info_->trigger_name; + args.clone_trigger_producer_name = snapshot_trigger_info_->producer_name; + args.clone_trigger_trusted_producer_uid = + snapshot_trigger_info_->producer_uid; + args.clone_trigger_boot_time_ns = snapshot_trigger_info_->boot_time_ns; + } consumer_endpoint_->CloneSession(std::move(args)); return; } @@ -1384,11 +1391,11 @@ void PerfettoCmd::OnSessionCloned(const OnSessionClonedArgs& args) { uuid_ = args.uuid.ToString(); // Log the new UUID with the clone tag. - if (snapshot_trigger_name_.empty()) { + if (!snapshot_trigger_info_.has_value()) { LogUploadEvent(PerfettoStatsdAtom::kCmdOnSessionClone); } else { LogUploadEvent(PerfettoStatsdAtom::kCmdOnTriggerSessionClone, - snapshot_trigger_name_); + snapshot_trigger_info_->trigger_name); } ReadbackTraceDataAndQuit(full_error); } @@ -1520,15 +1527,19 @@ void PerfettoCmd::OnObservableEvents( } if (observable_events.has_clone_trigger_hit()) { int64_t tsid = observable_events.clone_trigger_hit().tracing_session_id(); - std::string trigger_name = - observable_events.clone_trigger_hit().trigger_name(); + SnapshotTriggerInfo trigger = { + observable_events.clone_trigger_hit().boot_time_ns(), + observable_events.clone_trigger_hit().trigger_name(), + observable_events.clone_trigger_hit().producer_name(), + observable_events.clone_trigger_hit().producer_uid()}; OnCloneSnapshotTriggerReceived(static_cast(tsid), - std::move(trigger_name)); + trigger); } } -void PerfettoCmd::OnCloneSnapshotTriggerReceived(TracingSessionID tsid, - std::string trigger_name) { +void PerfettoCmd::OnCloneSnapshotTriggerReceived( + TracingSessionID tsid, + const SnapshotTriggerInfo& trigger) { std::string cmdline; cmdline.reserve(128); ArgsAppend(&cmdline, "perfetto"); @@ -1546,15 +1557,14 @@ void PerfettoCmd::OnCloneSnapshotTriggerReceived(TracingSessionID tsid, } else { PERFETTO_FATAL("Cannot use CLONE_SNAPSHOT with the current cmdline args"); } - CloneSessionOnThread(tsid, cmdline, kSingleExtraThread, - std::move(trigger_name), nullptr); + CloneSessionOnThread(tsid, cmdline, kSingleExtraThread, trigger, nullptr); } void PerfettoCmd::CloneSessionOnThread( TracingSessionID tsid, const std::string& cmdline, CloneThreadMode thread_mode, - std::string trigger_name, + const std::optional& trigger, std::function on_clone_callback) { PERFETTO_DLOG("Creating snapshot for tracing session %" PRIu64, tsid); @@ -1576,7 +1586,7 @@ void PerfettoCmd::CloneSessionOnThread( std::string trace_config_copy = trace_config_->SerializeAsString(); snapshot_threads_.back().PostTask( - [tsid, cmdline, trace_config_copy, trigger_name, on_clone_callback] { + [tsid, cmdline, trace_config_copy, trigger, on_clone_callback] { int argc = 0; char* argv[32]; // `splitter` needs to live on the stack for the whole scope as it owns @@ -1588,7 +1598,7 @@ void PerfettoCmd::CloneSessionOnThread( } perfetto::PerfettoCmd cmd; cmd.snapshot_config_ = std::move(trace_config_copy); - cmd.snapshot_trigger_name_ = std::move(trigger_name); + cmd.snapshot_trigger_info_ = trigger; cmd.on_session_cloned_ = on_clone_callback; auto cmdline_res = cmd.ParseCmdlineAndMaybeDaemonize(argc, argv); PERFETTO_CHECK(!cmdline_res.has_value()); // No daemonization expected. @@ -1680,7 +1690,8 @@ 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, std::nullopt, + sync_fn); } // for(sessions) PERFETTO_DLOG("Issuing %zu CloneSession requests", num_sessions); diff --git a/src/perfetto_cmd/perfetto_cmd.h b/src/perfetto_cmd/perfetto_cmd.h index 17bb751e40..bae2ba7795 100644 --- a/src/perfetto_cmd/perfetto_cmd.h +++ b/src/perfetto_cmd/perfetto_cmd.h @@ -75,6 +75,8 @@ class PerfettoCmd : public Consumer { void SignalCtrlC() { ctrl_c_evt_.Notify(); } private: + struct SnapshotTriggerInfo; + enum CloneThreadMode { kSingleExtraThread, kNewThreadPerRequest }; bool OpenOutputFile(); @@ -83,10 +85,11 @@ class PerfettoCmd : public Consumer { void PrintUsage(const char* argv0); void PrintServiceState(bool success, const TracingServiceState&); void CloneAllBugreportTraces(bool success, const TracingServiceState&); + void CloneSessionOnThread(TracingSessionID, const std::string& cmdline, // \0 separated. CloneThreadMode, - std::string clone_trigger_name, + const std::optional& trigger, std::function on_clone_callback); void OnTimeout(); bool is_detach() const { return !detach_key_.empty(); } @@ -131,7 +134,7 @@ class PerfettoCmd : public Consumer { void NotifyBgProcessPipe(BgProcessStatus status); void OnCloneSnapshotTriggerReceived(TracingSessionID, - std::string trigger_name); + const SnapshotTriggerInfo& trigger); #if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) static base::ScopedFile CreateUnlinkedTmpFile(); @@ -191,7 +194,18 @@ class PerfettoCmd : public Consumer { std::list snapshot_threads_; int snapshot_count_ = 0; std::string snapshot_config_; - std::string snapshot_trigger_name_; + // If the trigger caused the clone operation, we want to save the information + // about that trigger to the trace We may get multiple triggers with the same + // name, so we pass the entire structure to uniquely identify the trigger + // later. This structure is identical to the + // `TracingServiceImpl::TriggerInfo`. + struct SnapshotTriggerInfo { + uint64_t boot_time_ns = 0; + std::string trigger_name; + std::string producer_name; + uid_t producer_uid = 0; + }; + std::optional snapshot_trigger_info_; base::WeakPtrFactory weak_factory_{this}; }; diff --git a/src/tracing/ipc/consumer/consumer_ipc_client_impl.cc b/src/tracing/ipc/consumer/consumer_ipc_client_impl.cc index 5ab5aeddb6..92affc07c8 100644 --- a/src/tracing/ipc/consumer/consumer_ipc_client_impl.cc +++ b/src/tracing/ipc/consumer/consumer_ipc_client_impl.cc @@ -479,6 +479,19 @@ void ConsumerIPCClientImpl::CloneSession(CloneSessionArgs args) { } req.set_skip_trace_filter(args.skip_trace_filter); req.set_for_bugreport(args.for_bugreport); + if (!args.clone_trigger_name.empty()) { + req.set_clone_trigger_name(args.clone_trigger_name); + } + if (!args.clone_trigger_producer_name.empty()) { + req.set_clone_trigger_producer_name(args.clone_trigger_producer_name); + } + if (args.clone_trigger_trusted_producer_uid != 0) { + req.set_clone_trigger_trusted_producer_uid( + static_cast(args.clone_trigger_trusted_producer_uid)); + } + if (args.clone_trigger_boot_time_ns != 0) { + req.set_clone_trigger_boot_time_ns(args.clone_trigger_boot_time_ns); + } ipc::Deferred async_response; auto weak_this = weak_ptr_factory_.GetWeakPtr(); diff --git a/src/tracing/ipc/service/consumer_ipc_service.cc b/src/tracing/ipc/service/consumer_ipc_service.cc index 4773d9012a..20d054614a 100644 --- a/src/tracing/ipc/service/consumer_ipc_service.cc +++ b/src/tracing/ipc/service/consumer_ipc_service.cc @@ -339,6 +339,19 @@ void ConsumerIPCService::CloneSession( if (req.has_unique_session_name()) { args.unique_session_name = req.unique_session_name(); } + if (req.has_clone_trigger_name()) { + args.clone_trigger_name = req.clone_trigger_name(); + } + if (req.has_clone_trigger_producer_name()) { + args.clone_trigger_producer_name = req.clone_trigger_producer_name(); + } + if (req.has_clone_trigger_trusted_producer_uid()) { + args.clone_trigger_trusted_producer_uid = + static_cast(req.clone_trigger_trusted_producer_uid()); + } + if (req.has_clone_trigger_boot_time_ns()) { + args.clone_trigger_boot_time_ns = req.clone_trigger_boot_time_ns(); + } remote_consumer->service_endpoint->CloneSession(std::move(args)); } diff --git a/src/tracing/service/tracing_service_impl.cc b/src/tracing/service/tracing_service_impl.cc index 68b5ba6092..87bb223aa5 100644 --- a/src/tracing/service/tracing_service_impl.cc +++ b/src/tracing/service/tracing_service_impl.cc @@ -1753,9 +1753,9 @@ void TracingServiceImpl::ActivateTriggers( const bool triggers_already_received = !tracing_session.received_triggers.empty(); - tracing_session.received_triggers.push_back( - {static_cast(now_ns), iter->name(), producer->name_, - producer->uid()}); + const TriggerInfo trigger = {static_cast(now_ns), iter->name(), + producer->name_, producer->uid()}; + tracing_session.received_triggers.push_back(trigger); auto weak_this = weak_ptr_factory_.GetWeakPtr(); switch (trigger_mode) { case TraceConfig::TriggerConfig::START_TRACING: @@ -1811,14 +1811,13 @@ void TracingServiceImpl::ActivateTriggers( tracing_session.config, tracing_session.trace_uuid, PerfettoStatsdAtom::kTracedTriggerCloneSnapshot, iter->name()); task_runner_->PostDelayedTask( - [weak_this, tsid, trigger_name = iter->name()] { + [weak_this, tsid, trigger] { if (!weak_this) return; auto* tsess = weak_this->GetTracingSession(tsid); if (!tsess || !tsess->consumer_maybe_null) return; - tsess->consumer_maybe_null->NotifyCloneSnapshotTrigger( - trigger_name); + tsess->consumer_maybe_null->NotifyCloneSnapshotTrigger(trigger); }, iter->stop_delay_ms()); break; @@ -2501,6 +2500,7 @@ std::vector TracingServiceImpl::ReadBuffers( if (!tracing_session->config.builtin_data_sources().disable_trace_config()) { MaybeEmitTraceConfig(tracing_session, &packets); + MaybeEmitCloneTrigger(tracing_session, &packets); MaybeEmitReceivedTriggers(tracing_session, &packets); } if (!tracing_session->did_emit_initial_packets) { @@ -3909,6 +3909,24 @@ void TracingServiceImpl::MaybeEmitRemoteClockSync( tracing_session->did_emit_remote_clock_sync_ = true; } +void TracingServiceImpl::MaybeEmitCloneTrigger( + TracingSession* tracing_session, + std::vector* packets) { + if (tracing_session->clone_trigger.has_value()) { + protozero::HeapBuffered packet; + auto* trigger = packet->set_clone_snapshot_trigger(); + const auto& info = tracing_session->clone_trigger.value(); + trigger->set_trigger_name(info.trigger_name); + trigger->set_producer_name(info.producer_name); + trigger->set_trusted_producer_uid(static_cast(info.producer_uid)); + + packet->set_timestamp(info.boot_time_ns); + packet->set_trusted_uid(static_cast(uid_)); + packet->set_trusted_packet_sequence_id(kServicePacketSequenceID); + SerializeAndAppendPacket(packets, packet.SerializeAsArray()); + } +} + void TracingServiceImpl::MaybeEmitReceivedTriggers( TracingSession* tracing_session, std::vector* packets) { @@ -4059,6 +4077,12 @@ base::Status TracingServiceImpl::FlushAndCloneSession( std::vector>(session->buffers_index.size()); clone_op.weak_consumer = weak_consumer; clone_op.skip_trace_filter = args.skip_trace_filter; + if (!args.clone_trigger_name.empty()) { + clone_op.clone_trigger = {args.clone_trigger_boot_time_ns, + args.clone_trigger_name, + args.clone_trigger_producer_name, + args.clone_trigger_trusted_producer_uid}; + } // Issue separate flush requests for separate buffer groups. The buffer marked // as transfer_on_clone will be flushed and cloned separately: even if they're @@ -4161,7 +4185,8 @@ void TracingServiceImpl::OnFlushDoneForClone(TracingSessionID tsid, if (clone_op.weak_consumer) { result = FinishCloneSession( &*clone_op.weak_consumer, tsid, std::move(clone_op.buffers), - clone_op.skip_trace_filter, !clone_op.flush_failed, &uuid); + clone_op.skip_trace_filter, !clone_op.flush_failed, + clone_op.clone_trigger, &uuid); } } // if (result.ok()) @@ -4216,6 +4241,7 @@ base::Status TracingServiceImpl::FinishCloneSession( std::vector> buf_snaps, bool skip_trace_filter, bool final_flush_outcome, + std::optional clone_trigger, base::Uuid* new_uuid) { PERFETTO_DLOG("CloneSession(%" PRIu64 ", skip_trace_filter=%d) started, consumer uid: %d", @@ -4282,6 +4308,7 @@ base::Status TracingServiceImpl::FinishCloneSession( // far back (see b/290799105). // 2. Bloating memory (see b/290798988). cloned_session->should_emit_stats = true; + cloned_session->clone_trigger = clone_trigger; cloned_session->received_triggers = std::move(src->received_triggers); src->received_triggers.clear(); src->num_triggers_emitted_into_trace = 0; @@ -4537,14 +4564,17 @@ void TracingServiceImpl::ConsumerEndpointImpl::OnAllDataSourcesStarted() { } void TracingServiceImpl::ConsumerEndpointImpl::NotifyCloneSnapshotTrigger( - const std::string& trigger_name) { + const TriggerInfo& trigger) { 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); + clone_trig->set_trigger_name(trigger.trigger_name); + clone_trig->set_producer_name(trigger.producer_name); + clone_trig->set_producer_uid(trigger.producer_uid); + clone_trig->set_boot_time_ns(trigger.boot_time_ns); } ObservableEvents* diff --git a/src/tracing/service/tracing_service_impl.h b/src/tracing/service/tracing_service_impl.h index 22f527bc9e..92e743ce1c 100644 --- a/src/tracing/service/tracing_service_impl.h +++ b/src/tracing/service/tracing_service_impl.h @@ -78,6 +78,7 @@ class TracePacket; class TracingServiceImpl : public TracingService { private: struct DataSourceInstance; + struct TriggerInfo; public: static constexpr size_t kMaxShmSize = 32 * 1024 * 1024ul; @@ -212,7 +213,6 @@ class TracingServiceImpl : public TracingService { ~ConsumerEndpointImpl() override; void NotifyOnTracingDisabled(const std::string& error); - void NotifyCloneSnapshotTrigger(const std::string& trigger_name); // TracingService::ConsumerEndpoint implementation. void EnableTracing(const TraceConfig&, base::ScopedFile) override; @@ -246,6 +246,8 @@ class TracingServiceImpl : public TracingService { ConsumerEndpointImpl(const ConsumerEndpointImpl&) = delete; ConsumerEndpointImpl& operator=(const ConsumerEndpointImpl&) = delete; + void NotifyCloneSnapshotTrigger(const TriggerInfo& trigger_name); + // Returns a pointer to an ObservableEvents object that the caller can fill // and schedules a task to send the ObservableEvents to the consumer. ObservableEvents* AddObservableEvents(); @@ -474,6 +476,13 @@ class TracingServiceImpl : public TracingService { using PendingCloneID = uint64_t; + struct TriggerInfo { + uint64_t boot_time_ns = 0; + std::string trigger_name; + std::string producer_name; + uid_t producer_uid = 0; + }; + struct PendingClone { size_t pending_flush_cnt = 0; // This vector might not be populated all at once. Some buffers might be @@ -482,6 +491,7 @@ class TracingServiceImpl : public TracingService { bool flush_failed = false; base::WeakPtr weak_consumer; bool skip_trace_filter = false; + std::optional clone_trigger; }; // Holds the state of a tracing session. A tracing session is uniquely bound @@ -580,12 +590,6 @@ class TracingServiceImpl : public TracingService { // were received at. This is used to insert 'fake' packets back to the // consumer so they can tell when some event happened. The order matches the // order they were received. - struct TriggerInfo { - uint64_t boot_time_ns; - std::string trigger_name; - std::string producer_name; - uid_t producer_uid; - }; std::vector received_triggers; // The trace config provided by the Consumer when calling @@ -734,6 +738,9 @@ class TracingServiceImpl : public TracingService { // uuid to avoid emitting two different traces with the same uuid. base::Uuid trace_uuid; + // This is set when the clone operation was caused by a clone trigger. + std::optional clone_trigger; + // NOTE: when adding new fields here consider whether that state should be // copied over in DoCloneSession() or not. Ask yourself: is this a // "runtime state" (e.g. active data sources) or a "trace (meta)data state"? @@ -799,6 +806,7 @@ class TracingServiceImpl : public TracingService { void EmitUuid(TracingSession*, std::vector*); void MaybeEmitTraceConfig(TracingSession*, std::vector*); void EmitSystemInfo(std::vector*); + void MaybeEmitCloneTrigger(TracingSession*, std::vector*); void MaybeEmitReceivedTriggers(TracingSession*, std::vector*); void MaybeEmitRemoteClockSync(TracingSession*, std::vector*); void MaybeNotifyAllDataSourcesStarted(TracingSession*); @@ -830,6 +838,7 @@ class TracingServiceImpl : public TracingService { std::vector>, bool skip_filter, bool final_flush_outcome, + std::optional clone_trigger, base::Uuid*); void OnFlushDoneForClone(TracingSessionID src_tsid, PendingCloneID clone_id, diff --git a/src/tracing/service/tracing_service_impl_unittest.cc b/src/tracing/service/tracing_service_impl_unittest.cc index 0a6438ae35..b3cce2f3ff 100644 --- a/src/tracing/service/tracing_service_impl_unittest.cc +++ b/src/tracing/service/tracing_service_impl_unittest.cc @@ -5355,6 +5355,141 @@ TEST_F(TracingServiceImplTest, CloneSessionByName) { } } +TEST_F(TracingServiceImplTest, CloneSnapshotTriggerProducesEvent) { + std::unique_ptr consumer = CreateMockConsumer(); + consumer->Connect(svc.get()); + + std::unique_ptr producer = CreateMockProducer(); + constexpr uid_t kMockProducerUid = 77777; + constexpr auto kMockProducerName = "mock_producer"; + producer->Connect(svc.get(), kMockProducerName, kMockProducerUid); + producer->RegisterDataSource("ds_1"); + + TraceConfig trace_config; + trace_config.add_buffers()->set_size_kb(128); + trace_config.add_data_sources()->mutable_config()->set_name("ds_1"); + auto* trigger_config = trace_config.mutable_trigger_config(); + trigger_config->set_trigger_mode(TraceConfig::TriggerConfig::CLONE_SNAPSHOT); + trigger_config->set_trigger_timeout_ms(8.64e+7); + auto* trigger = trigger_config->add_triggers(); + static constexpr auto kCloneTriggerName = "clone_trigger_name"; + trigger->set_name(kCloneTriggerName); + trigger->set_stop_delay_ms(1); + + consumer->ObserveEvents(ObservableEvents::TYPE_CLONE_TRIGGER_HIT); + consumer->EnableTracing(trace_config); + producer->WaitForTracingSetup(); + + producer->WaitForDataSourceSetup("ds_1"); + producer->WaitForDataSourceStart("ds_1"); + + producer->endpoint()->ActivateTriggers({"clone_trigger_name"}); + + const auto events = consumer->WaitForObservableEvents(); + ASSERT_TRUE(events.has_clone_trigger_hit()); + const auto& trigger_hit_event = events.clone_trigger_hit(); + EXPECT_EQ(trigger_hit_event.trigger_name(), kCloneTriggerName); + EXPECT_EQ(trigger_hit_event.producer_name(), kMockProducerName); + EXPECT_EQ(trigger_hit_event.producer_uid(), kMockProducerUid); + EXPECT_GT(trigger_hit_event.boot_time_ns(), 0ul); + + consumer->DisableTracing(); + producer->WaitForDataSourceStop("ds_1"); + consumer->WaitForTracingDisabled(); +} + +TEST_F(TracingServiceImplTest, CloneSessionEmitsTrigger) { + // The consumer the creates the initial tracing session. + std::unique_ptr consumer = CreateMockConsumer(); + consumer->Connect(svc.get()); + + // The consumer that clones and read the trace. + std::unique_ptr consumer2 = CreateMockConsumer(); + consumer2->Connect(svc.get()); + + std::unique_ptr producer = CreateMockProducer(); + producer->Connect(svc.get(), "mock_producer"); + + producer->RegisterDataSource("ds_1"); + + TraceConfig trace_config; + trace_config.add_buffers()->set_size_kb(32); + 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); + producer->WaitForTracingSetup(); + producer->WaitForDataSourceSetup("ds_1"); + producer->WaitForDataSourceStart("ds_1"); + + std::unique_ptr writer = producer->CreateTraceWriter("ds_1"); + + static constexpr auto kTestPayload = "test_payload_string"; + writer->NewTracePacket()->set_for_testing()->set_str(kTestPayload); + + static constexpr auto kCloneTriggerName = "trigger_name"; + static constexpr auto kCloneTriggerProducerName = "trigger_producer_name"; + static constexpr uid_t kCloneTriggerProducerUid = 42; + static constexpr uint64_t kCloneTriggerTimestamp = 456789123; + { + auto clone_done = task_runner.CreateCheckpoint("clone_done"); + EXPECT_CALL(*consumer2, OnSessionCloned(_)) + .WillOnce( + Invoke([clone_done](const Consumer::OnSessionClonedArgs& args) { + ASSERT_TRUE(args.success); + ASSERT_TRUE(args.error.empty()); + clone_done(); + })); + ConsumerEndpoint::CloneSessionArgs args; + args.tsid = GetLastTracingSessionId(consumer2.get()); + args.clone_trigger_name = kCloneTriggerName; + args.clone_trigger_producer_name = kCloneTriggerProducerName; + args.clone_trigger_trusted_producer_uid = kCloneTriggerProducerUid; + args.clone_trigger_boot_time_ns = kCloneTriggerTimestamp; + consumer2->endpoint()->CloneSession(args); + // CloneSession() will implicitly issue a flush. Linearize with that. + producer->ExpectFlush(writer.get()); + task_runner.RunUntilCheckpoint("clone_done"); + } + + // Disable the initial tracing session. + consumer->DisableTracing(); + producer->WaitForDataSourceStop("ds_1"); + consumer->WaitForTracingDisabled(); + + // Read back the cloned trace and the original trace. + auto packets = consumer->ReadBuffers(); + auto cloned_packets = consumer2->ReadBuffers(); + + const auto test_payload_matcher = + Property(&protos::gen::TracePacket::for_testing, + Property(&protos::gen::TestEvent::str, Eq(kTestPayload))); + + EXPECT_THAT(packets, Contains(test_payload_matcher)); + // Assert original trace doesn't contain "clone_snapshot_trigger" packet. + EXPECT_THAT( + packets, + Not(Contains(Property( + &protos::gen::TracePacket::has_clone_snapshot_trigger, Eq(true))))); + + EXPECT_THAT(cloned_packets, Contains(test_payload_matcher)); + std::vector clone_trigger_packets; + for (const auto& packet : cloned_packets) { + if (packet.has_clone_snapshot_trigger()) { + clone_trigger_packets.push_back(packet); + } + } + ASSERT_EQ(clone_trigger_packets.size(), 1ul); + EXPECT_EQ(clone_trigger_packets[0].timestamp(), kCloneTriggerTimestamp); + + const auto trigger = clone_trigger_packets[0].clone_snapshot_trigger(); + EXPECT_EQ(trigger.trigger_name(), kCloneTriggerName); + EXPECT_EQ(trigger.producer_name(), kCloneTriggerProducerName); + EXPECT_EQ(trigger.trusted_producer_uid(), + static_cast(kCloneTriggerProducerUid)); +} + TEST_F(TracingServiceImplTest, InvalidBufferSizes) { std::unique_ptr consumer = CreateMockConsumer(); consumer->Connect(svc.get()); diff --git a/test/cmdline_integrationtest.cc b/test/cmdline_integrationtest.cc index c4a6fc7953..2c27722e64 100644 --- a/test/cmdline_integrationtest.cc +++ b/test/cmdline_integrationtest.cc @@ -51,6 +51,7 @@ using ::testing::ElementsAre; using ::testing::ElementsAreArray; using ::testing::Eq; using ::testing::HasSubstr; +using ::testing::IsEmpty; using ::testing::Property; using ::testing::SizeIs; @@ -900,6 +901,137 @@ TEST_F(PerfettoCmdlineTest, TriggerCloneSnapshot) { Eq("trigger_name"))))); } +TEST_F(PerfettoCmdlineTest, MultipleTriggersCloneSnapshot) { + protos::gen::TraceConfig trace_config = + CreateTraceConfigForTest(kTestMessageCount, kTestMessageSize); + auto* trigger_cfg = trace_config.mutable_trigger_config(); + trigger_cfg->set_trigger_mode( + protos::gen::TraceConfig::TriggerConfig::CLONE_SNAPSHOT); + trigger_cfg->set_trigger_timeout_ms(600000); + // Add two triggers, the "trigger_name_2" hits before "trigger_name_1". + auto* trigger = trigger_cfg->add_triggers(); + trigger->set_name("trigger_name_1"); + trigger->set_stop_delay_ms(1500); + trigger = trigger_cfg->add_triggers(); + trigger->set_name("trigger_name_2"); + trigger->set_stop_delay_ms(500); + + // We have to construct all the processes we want to fork before we start the + // service with |StartServiceIfRequired()|. this is because it is unsafe + // (could deadlock) to fork after we've spawned some threads which might + // printf (and thus hold locks). + const std::string path = RandomTraceFileName(); + ScopedFileRemove remove_on_test_exit(path); + auto perfetto_proc = ExecPerfetto( + { + "-o", + path, + "-c", + "-", + }, + trace_config.SerializeAsString()); + + auto triggers_proc = ExecTrigger({"trigger_name_1", "trigger_name_2"}); + + // Start the service and connect a simple fake producer. + StartServiceIfRequiredNoNewExecsAfterThis(); + auto* fake_producer = test_helper().ConnectFakeProducer(); + EXPECT_TRUE(fake_producer); + + std::thread background_trace([&perfetto_proc]() { + std::string stderr_str; + EXPECT_EQ(0, perfetto_proc.Run(&stderr_str)) << stderr_str; + }); + + test_helper().WaitForProducerEnabled(); + // Wait for the producer to start, and then write out some test packets, + // before the trace actually starts (the trigger is seen). + auto on_data_written = task_runner_.CreateCheckpoint("data_written_1"); + fake_producer->ProduceEventBatch(test_helper().WrapTask(on_data_written)); + task_runner_.RunUntilCheckpoint("data_written_1"); + + EXPECT_EQ(0, triggers_proc.Run(&stderr_)) << "stderr: " << stderr_; + + // Wait for both clone triggers to hit and wait for two snapshot files. + std::string snapshot_path = path + ".0"; + for (int i = 0; i < 100 && !base::FileExists(snapshot_path); i++) { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + ASSERT_TRUE(base::FileExists(snapshot_path)); + + std::string snapshot_path_2 = path + ".1"; + for (int i = 0; i < 100 && !base::FileExists(snapshot_path_2); i++) { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + ASSERT_TRUE(base::FileExists(snapshot_path_2)); + + perfetto_proc.SendSigterm(); + background_trace.join(); + + // We now have two traces, the first one was cloned by "trigger_name_2", + // the second was cloned by "trigger_name_1". + + // Asserts for the first trace. + protos::gen::Trace trace; + ASSERT_TRUE(ParseNotEmptyTraceFromFile(snapshot_path, trace)); + EXPECT_LT(static_cast(kTestMessageCount), trace.packet_size()); + EXPECT_THAT(GetReceivedTriggerNames(trace), + ElementsAre("trigger_name_1", "trigger_name_2")); + + std::vector clone_trigger_packets; + protos::gen::TracePacket trigger_packet; + for (const auto& packet : trace.packet()) { + if (packet.has_clone_snapshot_trigger()) { + clone_trigger_packets.push_back(packet); + } else if (packet.has_trigger() && + packet.trigger().trigger_name() == "trigger_name_2") { + trigger_packet = packet; + } + } + ASSERT_EQ(clone_trigger_packets.size(), 1ul); + EXPECT_EQ(clone_trigger_packets[0].clone_snapshot_trigger().trigger_name(), + "trigger_name_2"); + // Assert that all fields of 'clone_snapshot_trigger' equal to the same fields + // of a 'trigger'. + EXPECT_EQ(clone_trigger_packets[0].timestamp(), trigger_packet.timestamp()); + EXPECT_EQ(clone_trigger_packets[0].clone_snapshot_trigger(), + trigger_packet.trigger()); + + // Asserts for the second trace. + protos::gen::Trace trace_2; + ASSERT_TRUE(ParseNotEmptyTraceFromFile(snapshot_path_2, trace_2)); + EXPECT_LT(static_cast(kTestMessageCount), trace_2.packet_size()); + // List of received triggers from the main session was cleaned after the first + // clone operation happened, the list is empty in the second trace. + EXPECT_THAT(GetReceivedTriggerNames(trace_2), IsEmpty()); + + std::vector clone_trigger_packets_2; + for (const auto& packet : trace_2.packet()) { + if (packet.has_clone_snapshot_trigger()) { + clone_trigger_packets_2.push_back(packet); + } + } + ASSERT_EQ(clone_trigger_packets_2.size(), 1ul); + EXPECT_EQ(clone_trigger_packets_2[0].clone_snapshot_trigger().trigger_name(), + "trigger_name_1"); + + // There is no triggers in the second snapshot, but we can compare the + // "clone_snapshot_trigger" with the trigger saved into the first snapshot. + protos::gen::TracePacket trigger_packet_from_first_snapshot; + for (const auto& packet : trace.packet()) { + if (packet.has_trigger() && + packet.trigger().trigger_name() == "trigger_name_1") { + trigger_packet_from_first_snapshot = packet; + } + } + // Assert that all fields of 'clone_snapshot_trigger' equal to the same fields + // of a 'trigger'. + EXPECT_EQ(clone_trigger_packets_2[0].timestamp(), + trigger_packet_from_first_snapshot.timestamp()); + EXPECT_EQ(clone_trigger_packets_2[0].clone_snapshot_trigger(), + trigger_packet_from_first_snapshot.trigger()); +} + TEST_F(PerfettoCmdlineTest, SaveForBugreport) { TraceConfig trace_config = CreateTraceConfigForBugreportTest(); RunBugreportTest(std::move(trace_config));