blob: c0e5aab0157f199338f61f6f71e21d567456067f [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "services/tracing/public/cpp/perfetto/posix_system_producer.h"
#include <utility>
#include "base/bind.h"
#include "base/command_line.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/trace_event/trace_log.h"
#include "build/build_config.h"
#include "services/tracing/public/cpp/perfetto/shared_memory.h"
#include "services/tracing/public/cpp/trace_startup.h"
#include "services/tracing/public/cpp/traced_process_impl.h"
#include "services/tracing/public/cpp/tracing_features.h"
#include "third_party/perfetto/include/perfetto/ext/tracing/core/commit_data_request.h"
#include "third_party/perfetto/include/perfetto/ext/tracing/core/shared_memory_arbiter.h"
#include "third_party/perfetto/include/perfetto/ext/tracing/core/trace_writer.h"
#include "third_party/perfetto/include/perfetto/ext/tracing/ipc/producer_ipc_client.h"
#include "third_party/perfetto/include/perfetto/protozero/scattered_heap_buffer.h"
#include "third_party/perfetto/include/perfetto/protozero/scattered_stream_writer.h"
#include "third_party/perfetto/protos/perfetto/common/track_event_descriptor.pbzero.h"
#if BUILDFLAG(IS_ANDROID)
#include "base/android/build_info.h"
#endif // BUILDFLAG(IS_ANDROID)
#if !BUILDFLAG(IS_ANDROID)
#include "mojo/public/cpp/bindings/remote.h"
#include "services/tracing/public/cpp/system_tracing_service.h"
#endif
namespace tracing {
namespace {
constexpr uint32_t kInitialConnectionBackoffMs = 100;
constexpr uint32_t kMaxConnectionBackoffMs = 30 * 1000;
perfetto::DataSourceConfig EnsureGuardRailsAreFollowed(
const perfetto::DataSourceConfig& data_source_config) {
if (!data_source_config.enable_extra_guardrails() ||
data_source_config.chrome_config().privacy_filtering_enabled()) {
return data_source_config;
}
// If extra_guardrails is enabled then we have to ensure we have privacy
// filtering enabled.
perfetto::DataSourceConfig config = data_source_config;
config.mutable_chrome_config()->set_privacy_filtering_enabled(true);
return config;
}
uint32_t IncreaseBackoff(uint32_t current, uint32_t max) {
return std::min(current * 2, max);
}
} // namespace
PosixSystemProducer::PosixSystemProducer(
const char* socket,
base::tracing::PerfettoTaskRunner* task_runner)
: SystemProducer(task_runner),
socket_name_(socket),
connection_backoff_ms_(kInitialConnectionBackoffMs) {
DETACH_FROM_SEQUENCE(sequence_checker_);
}
PosixSystemProducer::~PosixSystemProducer() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
void PosixSystemProducer::SetDisallowPreAndroidPieForTesting(bool disallow) {
bool was_disallowed = SkipIfOnAndroidAndPreAndroidPie();
disallow_pre_android_pie_ = disallow;
if (!disallow && was_disallowed && state_ == State::kDisconnected) {
// If previously we would not have connected, we now attempt to connect
// since we are now skipping a check.
Connect();
}
}
void PosixSystemProducer::SetNewSocketForTesting(const char* socket) {
socket_name_ = socket;
if (state_ == State::kDisconnected) {
// Not connected yet, wait for ConnectToSystemService().
return;
}
if (state_ == State::kConnected) {
// If we are fully connected we need to reset the service before we
// reconnect.
DisconnectWithReply(base::BindOnce(&PosixSystemProducer::OnDisconnect,
base::Unretained(this)));
return;
}
// In any other case, we just need to do a normal disconnect and
// DisconnectWithReply will ensure we set up the retries on the new |socket|.
DisconnectWithReply(base::OnceClosure());
}
perfetto::SharedMemoryArbiter* PosixSystemProducer::MaybeSharedMemoryArbiter() {
base::AutoLock lock(lock_);
DCHECK(GetService());
return GetService()->MaybeSharedMemoryArbiter();
}
void PosixSystemProducer::NewDataSourceAdded(
const PerfettoTracedProcess::DataSourceBase* const data_source) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (state_ != State::kConnected) {
return;
}
perfetto::DataSourceDescriptor new_registration;
new_registration.set_name(data_source->name());
new_registration.set_will_notify_on_start(true);
new_registration.set_will_notify_on_stop(true);
new_registration.set_handles_incremental_state_clear(true);
// Add categories to the DataSourceDescriptor.
std::set<std::string> category_set;
tracing::TracedProcessImpl::GetInstance()->GetCategories(&category_set);
protozero::HeapBuffered<perfetto::protos::pbzero::TrackEventDescriptor> proto;
for (const std::string& s : category_set) {
auto* cat = proto->add_available_categories();
cat->set_name(s);
if (s.find(TRACE_DISABLED_BY_DEFAULT("")) == 0) {
cat->add_tags("slow");
}
}
new_registration.set_track_event_descriptor_raw(proto.SerializeAsString());
GetService()->RegisterDataSource(new_registration);
}
bool PosixSystemProducer::IsTracingActive() {
base::AutoLock lock(lock_);
return data_sources_tracing_ > 0;
}
void PosixSystemProducer::ConnectToSystemService() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(IsTracingInitialized());
DCHECK(state_ == State::kDisconnected);
// Some Telemetry tests use sideloaded Perfetto library on pre-Pie devices.
// We allow those tests to use system tracing by setting the
// EnablePerfettoSystemTracing feature.
disallow_pre_android_pie_ =
!base::FeatureList::IsEnabled(features::kEnablePerfettoSystemTracing);
Connect();
}
void PosixSystemProducer::ActivateTriggers(
const std::vector<std::string>& triggers) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (state_ == State::kConnected) {
GetService()->ActivateTriggers(triggers);
}
}
void PosixSystemProducer::DisconnectWithReply(
base::OnceClosure on_disconnect_complete) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (state_ == State::kConnected) {
// We are connected and need to unregister the DataSources to
// inform the service these data sources are going away. If we
// are currently tracing the service will ask for them to shut
// down asynchronously.
//
// Note that the system service may have concurrently posted a
// task to request one of these data sources to start. However we
// will ignore such requests by verifying that we're allowed to
// trace in StartDataSource().
for (const auto* const data_source :
PerfettoTracedProcess::Get()->data_sources()) {
DCHECK(GetService());
GetService()->UnregisterDataSource(data_source->name());
}
state_ = State::kUnregistered;
}
// If we are tracing we need to wait until we're fully disconnected
// to run the callback, otherwise we run it immediately (we will
// still unregister the data sources but that can happen async in
// the background).
if (!on_disconnect_complete.is_null()) {
if (IsTracingActive() || !on_disconnect_callbacks_.empty()) {
on_disconnect_callbacks_.push_back(std::move(on_disconnect_complete));
} else {
std::move(on_disconnect_complete).Run();
}
}
DelayedReconnect();
}
void PosixSystemProducer::OnConnect() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!PerfettoTracedProcess::Get()->CanStartTracing(this,
base::OnceClosure())) {
// We are succesfully connected, but we can't register the data sources
// right now, so move into "kUnregistered".
state_ = State::kUnregistered;
DisconnectWithReply();
return;
}
state_ = State::kConnected;
connection_backoff_ms_ = kInitialConnectionBackoffMs;
for (const auto* const data_source :
PerfettoTracedProcess::Get()->data_sources()) {
NewDataSourceAdded(data_source);
}
}
void PosixSystemProducer::OnDisconnect() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(GetService());
// Currently our data sources don't support the concept of the service
// disappearing and thus can't shut down cleanly (they would attempt to flush
// data across the broken socket). Add a CHECK to catch this if its a problem.
//
// TODO(nuskos): Fix this, make it so we cleanly shut down on IPC errors.
CHECK(!IsTracingActive());
// This PostTask is needed because we want to clean up the state AFTER the
// |ProducerEndpoint| has finished cleaning up.
task_runner()->GetOrCreateTaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(
[](base::WeakPtr<PosixSystemProducer> weak_ptr) {
if (!weak_ptr) {
return;
}
if (weak_ptr->state_ == State::kConnecting) {
base::AutoLock lock(weak_ptr->lock_);
// We never connected, which means this disconnect is
// an error from connecting, which means we don't need
// to keep this endpoint (and associated memory around
// forever) this prevents the memory leak from getting
// excessive.
weak_ptr->services_.erase(weak_ptr->services_.end() - 1);
}
weak_ptr->state_ = State::kDisconnected;
weak_ptr->DelayedReconnect();
},
weak_ptr_factory_.GetWeakPtr()));
}
void PosixSystemProducer::OnTracingSetup() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Called by the IPC layer when tracing is first started and after shared
// memory is set up.
DCHECK(MaybeSharedMemoryArbiter());
if (MaybeSharedMemoryArbiter()->EnableDirectSMBPatching()) {
MaybeSharedMemoryArbiter()->SetBatchCommitsDuration(
kShmArbiterBatchCommitDurationMs);
}
}
void PosixSystemProducer::SetupDataSource(perfetto::DataSourceInstanceID,
const perfetto::DataSourceConfig&) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Always called before StartDataSource but not used for any setup currently.
}
void PosixSystemProducer::StartDataSource(
perfetto::DataSourceInstanceID id,
const perfetto::DataSourceConfig& config) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (state_ != State::kConnected) {
// Because StartDataSource is async, its possible a previous StartDataSource
// is still in the PostTask queue, so we just ignore it here (We'll get
// a new one if/when we re-register the DataSource once we've moved into
// kConnected).
return;
}
for (auto* const data_source : PerfettoTracedProcess::Get()->data_sources()) {
if (data_source->name() == config.name()) {
auto can_trace = PerfettoTracedProcess::Get()->CanStartTracing(
this,
base::BindOnce(
[](base::WeakPtr<PosixSystemProducer> weak_ptr,
PerfettoTracedProcess::DataSourceBase* data_source,
perfetto::DataSourceInstanceID id,
const perfetto::DataSourceConfig& data_source_config) {
if (!weak_ptr) {
return;
}
DCHECK_CALLED_ON_VALID_SEQUENCE(weak_ptr->sequence_checker_);
{
base::AutoLock lock(weak_ptr->lock_);
++weak_ptr->data_sources_tracing_;
}
data_source->StartTracing(
id, weak_ptr.get(),
EnsureGuardRailsAreFollowed(data_source_config));
weak_ptr->GetService()->NotifyDataSourceStarted(id);
},
weak_ptr_factory_.GetWeakPtr(), data_source, id, config));
if (!can_trace) {
DisconnectWithReply(base::OnceClosure());
}
return;
}
}
}
void PosixSystemProducer::StopDataSource(perfetto::DataSourceInstanceID id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
for (auto* const data_source : PerfettoTracedProcess::Get()->data_sources()) {
if (data_source->data_source_id() == id &&
data_source->producer() == this) {
data_source->StopTracing(base::BindOnce(
[](base::WeakPtr<PosixSystemProducer> weak_ptr,
perfetto::DataSourceInstanceID id) {
if (!weak_ptr) {
return;
}
DCHECK_CALLED_ON_VALID_SEQUENCE(weak_ptr->sequence_checker_);
// Flush any commits that might have been batched by
// SharedMemoryArbiter.
weak_ptr->GetService()
->MaybeSharedMemoryArbiter()
->FlushPendingCommitDataRequests();
weak_ptr->GetService()->NotifyDataSourceStopped(id);
{
base::AutoLock lock(weak_ptr->lock_);
--weak_ptr->data_sources_tracing_;
}
if (!weak_ptr->IsTracingActive()) {
// If this is the last data source to be shut down then
// perhaps we need to invoke any callbacks that were stored
// (there might be none).
weak_ptr->InvokeStoredOnDisconnectCallbacks();
}
},
weak_ptr_factory_.GetWeakPtr(), id));
return;
}
}
}
void PosixSystemProducer::Flush(
perfetto::FlushRequestID id,
const perfetto::DataSourceInstanceID* data_source_ids,
size_t num_data_sources) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
pending_replies_for_latest_flush_ = {id, num_data_sources};
for (auto* const data_source : PerfettoTracedProcess::Get()->data_sources()) {
if (std::find(data_source_ids, data_source_ids + num_data_sources,
data_source->data_source_id()) !=
data_source_ids + num_data_sources) {
data_source->Flush(base::BindRepeating(
[](base::WeakPtr<PosixSystemProducer> weak_ptr,
perfetto::FlushRequestID flush_id) {
if (weak_ptr) {
weak_ptr->NotifyDataSourceFlushComplete(flush_id);
}
},
weak_ptr_factory_.GetWeakPtr(), id));
}
}
}
void PosixSystemProducer::ClearIncrementalState(
const perfetto::DataSourceInstanceID* data_source_ids,
size_t num_data_sources) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(data_source_ids);
DCHECK_GT(num_data_sources, 0u);
std::unordered_set<perfetto::DataSourceInstanceID> to_clear{
data_source_ids, data_source_ids + num_data_sources};
for (auto* data_source : PerfettoTracedProcess::Get()->data_sources()) {
if (to_clear.find(data_source->data_source_id()) != to_clear.end()) {
data_source->ClearIncrementalState();
}
}
}
bool PosixSystemProducer::SetupSharedMemoryForStartupTracing() {
// TODO(eseckler): Support startup tracing using an unbound SMA.
NOTIMPLEMENTED();
return false;
}
void PosixSystemProducer::ConnectSocket() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
state_ = State::kConnecting;
const char* host_package_name = nullptr;
#if BUILDFLAG(IS_ANDROID)
host_package_name =
base::android::BuildInfo::GetInstance()->host_package_name();
#endif // BUILDFLAG(IS_ANDROID)
// On android we want to include if this is webview inside of an app or
// Android Chrome. To aid this we add the host_package_name to differentiate
// the various apps and sources.
std::string producer_name;
if (host_package_name) {
producer_name = base::StrCat(
{mojom::kPerfettoProducerNamePrefix, host_package_name, "-",
base::NumberToString(
base::trace_event::TraceLog::GetInstance()->process_id())});
} else {
producer_name = base::StrCat(
{mojom::kPerfettoProducerNamePrefix,
base::NumberToString(
base::trace_event::TraceLog::GetInstance()->process_id())});
}
// If the security sandbox allows making socket connections, open the producer
// socket directly. Otherwise, use Mojo to open the socket in the browser
// process.
if (!SandboxForbidsSocketConnection()) {
auto service = perfetto::ProducerIPCClient::Connect(
socket_name_.c_str(), this, std::move(producer_name), task_runner(),
perfetto::TracingService::ProducerSMBScrapingMode::kEnabled,
GetPreferredSmbSizeBytes(), kSMBPageSizeBytes);
base::AutoLock lock(lock_);
services_.push_back(std::move(service));
return;
}
#if !BUILDFLAG(IS_ANDROID)
// If the child process hasn't received the Mojo remote, try again later.
auto& remote = TracedProcessImpl::GetInstance()->system_tracing_service();
if (!remote.is_bound()) {
// We don't really open the socket using ProducerIPCClient in child
// processes and need to reset |state_| to make DelayedReconnect() retry the
// connection using mojo.
DCHECK(state_ == State::kConnecting);
state_ = State::kDisconnected;
DelayedReconnect();
return;
}
auto callback = base::BindOnce(
[](std::string producer_name, base::WeakPtr<PosixSystemProducer> self,
base::File file) {
if (!self)
return;
if (!file.IsValid()) {
// Reset |state_| to make DelayedReconnect() retry the connection.
DCHECK(self->state_ == State::kConnecting);
self->state_ = State::kDisconnected;
self->DelayedReconnect();
return;
}
// Connect using an already connected socket.
auto service = perfetto::ProducerIPCClient::Connect(
perfetto::ipc::Client::ConnArgs(
perfetto::base::ScopedFile(file.TakePlatformFile())),
self.get(), std::move(producer_name), self->task_runner(),
perfetto::TracingService::ProducerSMBScrapingMode::kEnabled,
self->GetPreferredSmbSizeBytes(), kSMBPageSizeBytes);
base::AutoLock lock(self->lock_);
self->services_.push_back(std::move(service));
},
std::move(producer_name), weak_ptr_factory_.GetWeakPtr());
// Open the socket remotely using Mojo.
remote->OpenProducerSocket(std::move(callback));
#endif // !BUILDFLAG(IS_ANDROID)
}
bool PosixSystemProducer::SkipIfOnAndroidAndPreAndroidPie() const {
#if BUILDFLAG(IS_ANDROID)
return disallow_pre_android_pie_ &&
base::android::BuildInfo::GetInstance()->sdk_int() <
base::android::SDK_VERSION_P;
#else
return false;
#endif // BUILDFLAG(IS_ANDROID)
}
void PosixSystemProducer::InvokeStoredOnDisconnectCallbacks() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
for (auto& callback : on_disconnect_callbacks_) {
DCHECK(!callback.is_null());
std::move(callback).Run();
}
on_disconnect_callbacks_.clear();
}
void PosixSystemProducer::Connect() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (SkipIfOnAndroidAndPreAndroidPie()) {
return;
}
switch (state_) {
case State::kDisconnected:
ConnectSocket();
break;
case State::kConnecting:
case State::kConnected:
// We are already connected (in which case do nothing). Or we're
// currently connecting the socket and waiting for the OnConnect call
// from the service.
return;
case State::kUnregistered:
DCHECK(GetService());
// We unregistered all our data sources due to a concurrent tracing
// session but still have an open connection so just reregister
// everything.
OnConnect();
break;
}
}
bool PosixSystemProducer::SandboxForbidsSocketConnection() {
#if BUILDFLAG(IS_ANDROID)
// Android renderer can connect to the producer socket directly.
return false;
#else
// Connect to the system tracing service using Mojo from non-browser
// processes. Note that the network utility process can make socket
// connections, but we make it connect using Mojo for simplicity.
auto type =
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII("type");
return !type.empty();
#endif
}
void PosixSystemProducer::DelayedReconnect() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (SkipIfOnAndroidAndPreAndroidPie()) {
return;
}
if (retrying_) {
return;
}
retrying_ = true;
task_runner()->GetOrCreateTaskRunner()->PostDelayedTask(
FROM_HERE,
base::BindOnce(
[](base::WeakPtr<PosixSystemProducer> weak_ptr) {
if (!weak_ptr) {
return;
}
weak_ptr->retrying_ = false;
if (PerfettoTracedProcess::Get()->CanStartTracing(
weak_ptr.get(), base::OnceClosure())) {
weak_ptr->Connect();
} else {
weak_ptr->DelayedReconnect();
}
},
weak_ptr_factory_.GetWeakPtr()),
base::Milliseconds(connection_backoff_ms_));
connection_backoff_ms_ =
IncreaseBackoff(connection_backoff_ms_, kMaxConnectionBackoffMs);
}
void PosixSystemProducer::NotifyDataSourceFlushComplete(
perfetto::FlushRequestID id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (pending_replies_for_latest_flush_.first != id) {
// Ignore; completed flush was for an earlier request.
return;
}
DCHECK_NE(pending_replies_for_latest_flush_.second, 0u);
if (--pending_replies_for_latest_flush_.second == 0) {
DCHECK(MaybeSharedMemoryArbiter());
MaybeSharedMemoryArbiter()->NotifyFlushComplete(id);
}
}
perfetto::TracingService::ProducerEndpoint* PosixSystemProducer::GetService() {
#if DCHECK_IS_ON()
// Requires lock to be held when called on a non-producer sequence/thread.
if (!sequence_checker_.CalledOnValidSequence()) {
lock_.AssertAcquired();
}
#endif // DCHECK_IS_ON()
switch (state_) {
case State::kConnecting:
case State::kConnected:
case State::kUnregistered:
return services_.back().get();
default:
return nullptr;
}
}
} // namespace tracing