blob: bad0f9d9e223b2f528b520fcb5acbfa4f2ed4171 [file] [log] [blame]
// Copyright 2017 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/ash/arc/tracing/arc_tracing_bridge.h"
#include <utility>
#include "ash/components/arc/arc_browser_context_keyed_service_factory_base.h"
#include "ash/components/arc/mojom/tracing.mojom.h"
#include "ash/components/arc/session/arc_bridge_service.h"
#include "ash/components/arc/session/arc_service_manager.h"
#include "base/files/file.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/memory/singleton.h"
#include "base/no_destructor.h"
#include "base/posix/unix_domain_socket.h"
#include "base/task/sequenced_task_runner.h"
#include "base/time/time.h"
#include "base/trace_event/trace_config.h"
#include "base/trace_event/trace_event.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "mojo/public/cpp/system/platform_handle.h"
#include "services/tracing/public/cpp/perfetto/perfetto_traced_process.h"
#include "services/tracing/public/cpp/perfetto/system_trace_writer.h"
#include "services/tracing/public/mojom/constants.mojom.h"
#include "services/tracing/public/mojom/perfetto_service.mojom.h"
namespace arc {
namespace {
// The prefix of the categories to be shown on the trace selection UI.
// The space at the end of the string is intentional as the separator between
// the prefix and the real categories.
constexpr char kCategoryPrefix[] = TRACE_DISABLED_BY_DEFAULT("android ");
// Singleton factory for ArcTracingBridge.
class ArcTracingBridgeFactory
: public internal::ArcBrowserContextKeyedServiceFactoryBase<
ArcTracingBridge,
ArcTracingBridgeFactory> {
public:
// Factory name used by ArcBrowserContextKeyedServiceFactoryBase.
static constexpr const char* kName = "ArcTracingBridgeFactory";
static ArcTracingBridgeFactory* GetInstance() {
return base::Singleton<ArcTracingBridgeFactory>::get();
}
private:
friend base::DefaultSingletonTraits<ArcTracingBridgeFactory>;
ArcTracingBridgeFactory() = default;
~ArcTracingBridgeFactory() override = default;
};
// Perfetto data source which coordinates ARC tracing sessions with perfetto's
// PerfettoProducer when perfetto is used as the tracing backend.
class ArcTracingDataSource
: public tracing::PerfettoTracedProcess::DataSourceBase {
public:
static ArcTracingDataSource* GetInstance() {
static base::NoDestructor<ArcTracingDataSource> instance;
return instance.get();
}
ArcTracingDataSource(const ArcTracingDataSource&) = delete;
ArcTracingDataSource& operator=(const ArcTracingDataSource&) = delete;
// Called after constructing |bridge|.
void RegisterBridgeOnUI(ArcTracingBridge* bridge) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK_EQ(ArcTracingBridge::State::kDisabled, bridge->state());
bool success = bridges_.insert(bridge).second;
DCHECK(success);
if (producer_on_ui_thread_ && !stop_complete_callback_) {
// We're currently tracing, so start the new bridge, too.
// |this| never gets destructed, so it's OK to bind an unretained pointer.
bridge->StartTracing(
data_source_config_.chrome_config().trace_config(),
base::BindOnce(&ArcTracingDataSource::OnTracingStartedOnUI,
base::Unretained(this)));
}
}
// Called when destructing |bridge|.
void UnregisterBridgeOnUI(ArcTracingBridge* bridge) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
const size_t erase_count = bridges_.erase(bridge);
DCHECK_EQ(1u, erase_count);
// Make sure we don't continue to wait for any of the bridge's callbacks.
OnTracingStartedOnUI(false /*success*/);
OnTracingStoppedOnUI();
}
private:
friend class base::NoDestructor<ArcTracingDataSource>;
#if BUILDFLAG(USE_PERFETTO_CLIENT_LIBRARY)
using DataSourceProxy =
tracing::PerfettoTracedProcess::DataSourceProxy<CastDataSource>;
using SystemTraceWriter =
tracing::SystemTraceWriter<std::string, DataSourceProxy>;
#else
using SystemTraceWriter = tracing::SystemTraceWriter<std::string>;
#endif
ArcTracingDataSource()
: DataSourceBase(tracing::mojom::kArcTraceDataSourceName),
perfetto_task_runner_(tracing::PerfettoTracedProcess::Get()
->GetTaskRunner()
->GetOrCreateTaskRunner()) {
tracing::PerfettoTracedProcess::Get()->AddDataSource(this);
#if BUILDFLAG(USE_PERFETTO_CLIENT_LIBRARY)
perfetto::DataSourceDescriptor dsd;
dsd.set_name(mojom::kArcTraceDataSourceName);
DataSourceProxy::Register(dsd, this);
#endif
}
// Note that ArcTracingDataSource is a singleton that's never destroyed.
~ArcTracingDataSource() override = default;
// tracing::PerfettoProducer::DataSourceBase.
void StartTracingImpl(
tracing::PerfettoProducer* producer,
const perfetto::DataSourceConfig& data_source_config) override {
// |this| never gets destructed, so it's OK to bind an unretained pointer.
// |producer| is a singleton that is never destroyed.
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&ArcTracingDataSource::StartTracingOnUI,
base::Unretained(this), producer, data_source_config));
}
void StopTracingImpl(base::OnceClosure stop_complete_callback) override {
// |this| never gets destructed, so it's OK to bind an unretained pointer.
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(&ArcTracingDataSource::StopTracingOnUI,
base::Unretained(this),
std::move(stop_complete_callback)));
}
void Flush(base::RepeatingClosure flush_complete_callback) override {
// ARC's tracing service doesn't currently support flushing while recording.
flush_complete_callback.Run();
}
// Starts all registered bridges.
void StartTracingOnUI(tracing::PerfettoProducer* producer,
const perfetto::DataSourceConfig& data_source_config) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(!producer_on_ui_thread_);
producer_on_ui_thread_ = producer;
data_source_config_ = data_source_config;
for (ArcTracingBridge* bridge : bridges_) {
// |this| never gets destructed, so it's OK to bind an unretained pointer.
bridge->StartTracing(
data_source_config_.chrome_config().trace_config(),
base::BindOnce(&ArcTracingDataSource::OnTracingStartedOnUI,
base::Unretained(this)));
}
}
// Stops all registered bridges. Calls |stop_complete_callback| when all
// bridges have stopped.
void StopTracingOnUI(base::OnceClosure stop_complete_callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// We may receive a StopTracing without StartTracing.
if (!producer_on_ui_thread_) {
perfetto_task_runner_->PostTask(FROM_HERE,
std::move(stop_complete_callback));
return;
}
// We may still be in startup. In this case, store a callback to rerun
// StopTracingOnUI() once startup is complete.
if (IsAnyBridgeStarting()) {
DCHECK(!pending_stop_tracing_);
pending_stop_tracing_ = base::BindOnce(
&ArcTracingDataSource::StopTracingOnUI, base::Unretained(this),
std::move(stop_complete_callback));
return;
}
stop_complete_callback_ = std::move(stop_complete_callback);
for (ArcTracingBridge* bridge : bridges_) {
// StopTracingOnUI should only be called once all bridges have completed
// or abandoned startup.
DCHECK_NE(ArcTracingBridge::State::kStarting, bridge->state());
if (bridge->state() != ArcTracingBridge::State::kEnabled)
continue;
// |this| never gets destructed, so it's OK to bind an unretained pointer.
bridge->StopTracing(base::BindOnce(
&ArcTracingDataSource::OnTracingStoppedOnUI, base::Unretained(this)));
}
// There may not have been any bridges left in State::kEnabled. This will
// call the callback if all bridges are already stopped.
OnTracingStoppedOnUI();
}
// Called by each bridge when it has started tracing. Also called when a
// bridge is unregisted.
void OnTracingStartedOnUI(bool success) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!IsAnyBridgeStarting() && pending_stop_tracing_)
std::move(pending_stop_tracing_).Run();
}
// Called by each bridge when it has stopped tracing. Also called when a
// bridge is unregisted. Records the supplied |data| into the
// |producer_on_ui_thread_|'s buffer.
void OnTracingStoppedOnUI() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// When a bridge unregisters, we may not actually be stopping.
if (!stop_complete_callback_)
return;
DCHECK(producer_on_ui_thread_);
if (AreAllBridgesStopped()) {
if (!trace_writer_) {
OnTraceDataCommittedOnUI();
return;
}
trace_writer_->Flush(
base::BindOnce(&ArcTracingDataSource::OnTraceDataCommittedOnUI,
base::Unretained(this)));
}
}
void OnTraceDataCommittedOnUI() {
producer_on_ui_thread_ = nullptr;
perfetto_task_runner_->PostTask(FROM_HERE,
std::move(stop_complete_callback_));
}
bool IsAnyBridgeStarting() const {
for (ArcTracingBridge* bridge : bridges_) {
if (bridge->state() == ArcTracingBridge::State::kStarting)
return true;
}
return false;
}
bool AreAllBridgesStopped() const {
for (ArcTracingBridge* bridge : bridges_) {
if (bridge->state() != ArcTracingBridge::State::kDisabled)
return false;
}
return true;
}
scoped_refptr<base::SequencedTaskRunner> perfetto_task_runner_;
std::set<ArcTracingBridge*> bridges_;
// In case StopTracing() is called before tracing was started for all bridges,
// this stores a callback to StopTracing() that's executed when all bridges
// have started.
base::OnceClosure pending_stop_tracing_;
// Called when all bridges have completed stopping, notifying
// PerfettoProducer.
base::OnceClosure stop_complete_callback_;
// Parent class's |producer_| member is only valid on the perfetto sequence,
// we need to track it ourselves for access from the UI thread.
tracing::PerfettoProducer* producer_on_ui_thread_ = nullptr;
perfetto::DataSourceConfig data_source_config_;
std::unique_ptr<SystemTraceWriter> trace_writer_;
};
} // namespace
struct ArcTracingBridge::Category {
// The name used by Android to trigger tracing.
std::string name;
// The full name shown in the tracing UI in chrome://tracing.
std::string full_name;
};
// static
ArcTracingBridge* ArcTracingBridge::GetForBrowserContext(
content::BrowserContext* context) {
return ArcTracingBridgeFactory::GetForBrowserContext(context);
}
// static
ArcTracingBridge* ArcTracingBridge::GetForBrowserContextForTesting(
content::BrowserContext* context) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
return ArcTracingBridgeFactory::GetForBrowserContextForTesting(context);
}
ArcTracingBridge::ArcTracingBridge(content::BrowserContext* context,
ArcBridgeService* bridge_service)
: arc_bridge_service_(bridge_service), agent_(this) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
arc_bridge_service_->tracing()->AddObserver(this);
ArcTracingDataSource::GetInstance()->RegisterBridgeOnUI(this);
}
ArcTracingBridge::~ArcTracingBridge() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
ArcTracingDataSource::GetInstance()->UnregisterBridgeOnUI(this);
arc_bridge_service_->tracing()->RemoveObserver(this);
}
void ArcTracingBridge::GetCategories(std::set<std::string>* category_set) {
base::AutoLock lock(categories_lock_);
for (const auto& category : categories_) {
category_set->insert(category.full_name);
}
}
void ArcTracingBridge::OnConnectionReady() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
mojom::TracingInstance* tracing_instance = ARC_GET_INSTANCE_FOR_METHOD(
arc_bridge_service_->tracing(), QueryAvailableCategories);
if (!tracing_instance)
return;
tracing_instance->QueryAvailableCategories(base::BindOnce(
&ArcTracingBridge::OnCategoriesReady, weak_ptr_factory_.GetWeakPtr()));
}
void ArcTracingBridge::OnCategoriesReady(
const std::vector<std::string>& categories) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
base::AutoLock lock(categories_lock_);
// There is no API in TraceLog to remove a category from the UI. As an
// alternative, the old category that is no longer in |categories_| will be
// ignored when calling |StartTracing|.
categories_.clear();
for (const auto& category : categories)
categories_.emplace_back(Category{category, kCategoryPrefix + category});
}
void ArcTracingBridge::StartTracing(const std::string& config,
StartCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (state_ != State::kDisabled) {
DLOG(WARNING) << "Cannot start tracing, it is already enabled.";
std::move(callback).Run(false /*success*/);
return;
}
state_ = State::kStarting;
base::trace_event::TraceConfig trace_config(config);
if (!trace_config.IsSystraceEnabled()) {
OnArcTracingStarted(std::move(callback), false /*success*/);
return;
}
mojom::TracingInstance* tracing_instance =
ARC_GET_INSTANCE_FOR_METHOD(arc_bridge_service_->tracing(), StartTracing);
if (!tracing_instance) {
OnArcTracingStarted(std::move(callback), false /*success*/);
return;
}
std::vector<std::string> selected_categories;
{
base::AutoLock lock(categories_lock_);
for (const auto& category : categories_) {
if (trace_config.IsCategoryGroupEnabled(category.full_name))
selected_categories.push_back(category.name);
}
}
tracing_instance->StartTracing(
selected_categories, mojo::ScopedHandle(),
base::BindOnce(&ArcTracingBridge::OnArcTracingStarted,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void ArcTracingBridge::OnArcTracingStarted(StartCallback callback,
bool success) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK_EQ(State::kStarting, state_);
state_ = success ? State::kEnabled : State::kDisabled;
std::move(callback).Run(success);
}
void ArcTracingBridge::StopTracing(StopCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (state_ != State::kEnabled) {
DLOG(WARNING) << "Cannot stop tracing, it is not enabled.";
std::move(callback).Run();
return;
}
state_ = State::kStopping;
mojom::TracingInstance* tracing_instance =
ARC_GET_INSTANCE_FOR_METHOD(arc_bridge_service_->tracing(), StopTracing);
if (!tracing_instance) {
OnArcTracingStopped(std::move(callback), false);
return;
}
tracing_instance->StopTracing(
base::BindOnce(&ArcTracingBridge::OnArcTracingStopped,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void ArcTracingBridge::OnArcTracingStopped(StopCallback callback,
bool success) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK_EQ(State::kStopping, state_);
state_ = State::kDisabled;
if (!success)
LOG(ERROR) << "Failed to stop tracing";
std::move(callback).Run();
}
ArcTracingBridge::ArcTracingAgent::ArcTracingAgent(ArcTracingBridge* bridge)
: bridge_(bridge) {}
ArcTracingBridge::ArcTracingAgent::~ArcTracingAgent() = default;
void ArcTracingBridge::ArcTracingAgent::GetCategories(
std::set<std::string>* category_set) {
bridge_->GetCategories(category_set);
}
// static
void ArcTracingBridge::EnsureFactoryBuilt() {
ArcTracingBridgeFactory::GetInstance();
}
} // namespace arc