| // Copyright 2017 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 "chrome/browser/chromeos/arc/tracing/arc_tracing_bridge.h" |
| |
| #include <set> |
| |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/files/file.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/post_task.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "base/time/time.h" |
| #include "base/trace_event/trace_config.h" |
| #include "components/arc/arc_bridge_service.h" |
| #include "components/arc/arc_browser_context_keyed_service_factory_base.h" |
| #include "components/arc/arc_service_manager.h" |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/common/service_manager_connection.h" |
| #include "mojo/public/cpp/system/platform_handle.h" |
| #include "services/tracing/public/mojom/constants.mojom.h" |
| #include "services/tracing/public/mojom/perfetto_service.mojom.h" |
| #include "third_party/perfetto/include/perfetto/tracing/core/trace_writer.h" |
| #include "third_party/perfetto/protos/perfetto/trace/chrome/chrome_trace_event.pbzero.h" |
| #include "third_party/perfetto/protos/perfetto/trace/trace_packet.pbzero.h" |
| |
| using ChromeEventBundleHandle = |
| protozero::MessageHandle<perfetto::protos::pbzero::ChromeEventBundle>; |
| |
| namespace arc { |
| |
| namespace { |
| |
| // The maximum size used to store one trace event. The ad hoc trace format for |
| // atrace is 1024 bytes. Here we add additional size as we're using JSON and |
| // have additional data fields. |
| constexpr size_t kArcTraceMessageLength = 1024 + 512; |
| |
| constexpr char kChromeTraceEventLabel[] = "traceEvents"; |
| |
| // 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 |
| // ProducerClient when perfetto is used as the tracing backend. |
| class ArcTracingDataSource : public tracing::ProducerClient::DataSourceBase { |
| public: |
| static ArcTracingDataSource* GetInstance() { |
| static base::NoDestructor<ArcTracingDataSource> instance; |
| return instance.get(); |
| } |
| |
| // 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_client_ && !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_.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*/); |
| OnTraceDataOnUI(std::string() /*data*/); |
| } |
| |
| private: |
| friend class base::NoDestructor<ArcTracingDataSource>; |
| |
| ArcTracingDataSource() |
| : DataSourceBase(tracing::mojom::kArcTraceDataSourceName), |
| perfetto_task_runner_(tracing::ProducerClient::Get()->GetTaskRunner()) { |
| tracing::ProducerClient::Get()->AddDataSource(this); |
| } |
| |
| // Note that ArcTracingDataSource is a singleton that's never destroyed. |
| ~ArcTracingDataSource() override = default; |
| |
| // tracing::ProducerClient::DataSourceBase. |
| void StartTracing( |
| tracing::ProducerClient* producer_client, |
| const tracing::mojom::DataSourceConfig& data_source_config) override { |
| // |this| never gets destructed, so it's OK to bind an unretained pointer. |
| // |producer_client| is a singleton that is never destroyed. |
| base::PostTaskWithTraits( |
| FROM_HERE, {content::BrowserThread::UI}, |
| base::BindOnce(&ArcTracingDataSource::StartTracingOnUI, |
| base::Unretained(this), producer_client, |
| data_source_config)); |
| } |
| |
| void StopTracing(base::OnceClosure stop_complete_callback) override { |
| // |this| never gets destructed, so it's OK to bind an unretained pointer. |
| base::PostTaskWithTraits( |
| FROM_HERE, {content::BrowserThread::UI}, |
| 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::ProducerClient* producer_client, |
| const tracing::mojom::DataSourceConfig& data_source_config) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| DCHECK(!producer_client_); |
| producer_client_ = producer_client; |
| 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_.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_client_) |
| 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->StopAndFlush(base::BindOnce( |
| &ArcTracingDataSource::OnTraceDataOnUI, 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. |
| OnTraceDataOnUI(std::string()); |
| } |
| |
| // 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_client_|'s buffer. |
| void OnTraceDataOnUI(const std::string& data) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| // When a bridge unregisters, we may not actually be stopping. |
| if (!stop_complete_callback_) |
| return; |
| |
| DCHECK(producer_client_); |
| |
| if (!data.empty()) { |
| std::unique_ptr<perfetto::TraceWriter> trace_writer = |
| producer_client_->CreateTraceWriter( |
| data_source_config_.target_buffer); |
| DCHECK(trace_writer); |
| perfetto::TraceWriter::TracePacketHandle trace_packet_handle = |
| trace_writer->NewTracePacket(); |
| ChromeEventBundleHandle event_bundle = |
| ChromeEventBundleHandle(trace_packet_handle->set_chrome_events()); |
| auto* legacy_json_trace = event_bundle->add_legacy_json_trace(); |
| legacy_json_trace->set_type( |
| perfetto::protos::pbzero::ChromeLegacyJsonTrace::USER_TRACE); |
| legacy_json_trace->set_data(data.data(), data.length()); |
| } |
| |
| if (AreAllBridgesStopped()) { |
| producer_client_ = 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; |
| } |
| |
| 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 ProducerClient. |
| base::OnceClosure stop_complete_callback_; |
| tracing::ProducerClient* producer_client_ = nullptr; |
| tracing::mojom::DataSourceConfig data_source_config_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ArcTracingDataSource); |
| }; |
| |
| } // 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); |
| } |
| |
| ArcTracingBridge::ArcTracingBridge(content::BrowserContext* context, |
| ArcBridgeService* bridge_service) |
| : arc_bridge_service_(bridge_service), |
| agent_(this), |
| reader_(std::make_unique<ArcTracingReader>()), |
| weak_ptr_factory_(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); |
| |
| // Delete the reader on the IO thread. |
| base::CreateSingleThreadTaskRunnerWithTraits({content::BrowserThread::IO}) |
| ->DeleteSoon(FROM_HERE, reader_.release()); |
| } |
| |
| void ArcTracingBridge::GetCategories(std::set<std::string>* category_set) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| 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); |
| |
| // 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, |
| SuccessCallback callback) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| if (state_ != State::kDisabled) { |
| DLOG(WARNING) << "Cannot start tracing, it is already enabled."; |
| if (callback) |
| std::move(callback).Run(false /*success*/); |
| return; |
| } |
| state_ = State::kStarting; |
| |
| base::trace_event::TraceConfig trace_config(config); |
| |
| base::ScopedFD write_fd, read_fd; |
| bool success = |
| trace_config.IsSystraceEnabled() && CreateSocketPair(&read_fd, &write_fd); |
| |
| if (!success) { |
| 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; |
| for (const auto& category : categories_) { |
| if (trace_config.IsCategoryGroupEnabled(category.full_name)) |
| selected_categories.push_back(category.name); |
| } |
| |
| tracing_instance->StartTracing( |
| selected_categories, mojo::WrapPlatformFile(write_fd.release()), |
| base::BindOnce(&ArcTracingBridge::OnArcTracingStarted, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| |
| // |reader_| will be destroyed after us on the IO thread, so it's OK to use an |
| // unretained pointer. |
| base::PostTaskWithTraits( |
| FROM_HERE, {content::BrowserThread::IO}, |
| base::BindOnce(&ArcTracingReader::StartTracing, |
| base::Unretained(reader_.get()), std::move(read_fd))); |
| } |
| |
| void ArcTracingBridge::OnArcTracingStarted(SuccessCallback callback, |
| bool success) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| DCHECK_EQ(State::kStarting, state_); |
| state_ = success ? State::kEnabled : State::kDisabled; |
| if (callback) |
| std::move(callback).Run(success); |
| } |
| |
| void ArcTracingBridge::StopAndFlush(TraceDataCallback callback) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| if (state_ != State::kEnabled) { |
| DLOG(WARNING) << "Cannot stop tracing, it is not enabled."; |
| std::move(callback).Run(std::string()); |
| 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( |
| TraceDataCallback tracing_stopped_callback, |
| bool success) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| DCHECK_EQ(State::kStopping, state_); |
| if (!success) { |
| DLOG(WARNING) << "Failed to stop ARC tracing, closing file anyway."; |
| } |
| // |reader_| will be destroyed after us on the IO thread, so it's OK to use an |
| // unretained pointer. |
| base::PostTaskWithTraitsAndReplyWithResult( |
| FROM_HERE, {content::BrowserThread::IO}, |
| base::BindOnce(&ArcTracingReader::StopTracing, |
| base::Unretained(reader_.get())), |
| base::BindOnce(&ArcTracingBridge::OnTracingReaderStopped, |
| weak_ptr_factory_.GetWeakPtr(), |
| std::move(tracing_stopped_callback))); |
| } |
| |
| void ArcTracingBridge::OnTracingReaderStopped( |
| TraceDataCallback tracing_stopped_callback, |
| const std::string& data) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| DCHECK_EQ(State::kStopping, state_); |
| state_ = State::kDisabled; |
| std::move(tracing_stopped_callback).Run(data); |
| } |
| |
| ArcTracingBridge::ArcTracingAgent::ArcTracingAgent(ArcTracingBridge* bridge) |
| : BaseAgent( |
| |
| kChromeTraceEventLabel, |
| tracing::mojom::TraceDataType::ARRAY, |
| base::kNullProcessId), |
| bridge_(bridge) { |
| } |
| |
| ArcTracingBridge::ArcTracingAgent::~ArcTracingAgent() = default; |
| |
| void ArcTracingBridge::ArcTracingAgent::GetCategories( |
| std::set<std::string>* category_set) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| bridge_->GetCategories(category_set); |
| } |
| |
| void ArcTracingBridge::ArcTracingAgent::StartTracing( |
| const std::string& config, |
| base::TimeTicks coordinator_time) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| bridge_->StartTracing(config, SuccessCallback()); |
| } |
| |
| void ArcTracingBridge::ArcTracingAgent::StopAndFlush( |
| tracing::mojom::RecorderPtr recorder) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| recorder_ = std::move(recorder); |
| // |bridge_| owns us, so it's OK to pass an unretained pointer. |
| bridge_->StopAndFlush( |
| base::BindOnce(&ArcTracingAgent::OnTraceData, base::Unretained(this))); |
| } |
| |
| void ArcTracingBridge::ArcTracingAgent::OnTraceData(const std::string& data) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| DCHECK(recorder_); |
| recorder_->AddChunk(data); |
| recorder_.reset(); |
| } |
| |
| ArcTracingBridge::ArcTracingReader::ArcTracingReader() = default; |
| |
| ArcTracingBridge::ArcTracingReader::~ArcTracingReader() { |
| DCHECK(!fd_watcher_); |
| } |
| |
| void ArcTracingBridge::ArcTracingReader::StartTracing(base::ScopedFD read_fd) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
| read_fd_ = std::move(read_fd); |
| // We own |fd_watcher_|, so it's OK to use an unretained pointer. |
| fd_watcher_ = base::FileDescriptorWatcher::WatchReadable( |
| read_fd_.get(), |
| base::BindRepeating(&ArcTracingReader::OnTraceDataAvailable, |
| base::Unretained(this))); |
| } |
| |
| void ArcTracingBridge::ArcTracingReader::OnTraceDataAvailable() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
| |
| char buf[kArcTraceMessageLength]; |
| std::vector<base::ScopedFD> unused_fds; |
| ssize_t n = base::UnixDomainSocket::RecvMsg( |
| read_fd_.get(), buf, kArcTraceMessageLength, &unused_fds); |
| if (n < 0) { |
| DPLOG(WARNING) << "Unexpected error while reading trace from client."; |
| // Do nothing here as StopTracing will do the clean up and the existing |
| // trace logs will be returned. |
| return; |
| } |
| |
| if (n == 0) { |
| // When EOF is reached, stop listening for changes since there's never |
| // going to be any more data to be read. The rest of the cleanup will be |
| // done in StopTracing. |
| fd_watcher_.reset(); |
| return; |
| } |
| |
| if (n > static_cast<ssize_t>(kArcTraceMessageLength)) { |
| DLOG(WARNING) << "Unexpected data size when reading trace from client."; |
| return; |
| } |
| ring_buffer_.SaveToBuffer(std::string(buf, n)); |
| } |
| |
| std::string ArcTracingBridge::ArcTracingReader::StopTracing() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
| fd_watcher_.reset(); |
| read_fd_.reset(); |
| |
| bool append_comma = false; |
| std::string data; |
| for (auto it = ring_buffer_.Begin(); it; ++it) { |
| if (append_comma) |
| data.append(","); |
| else |
| append_comma = true; |
| data.append(**it); |
| } |
| ring_buffer_.Clear(); |
| |
| return data; |
| } |
| |
| } // namespace arc |