diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2018-05-03 13:42:47 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2018-05-15 10:27:51 +0000 |
commit | 8c5c43c7b138c9b4b0bf56d946e61d3bbc111bec (patch) | |
tree | d29d987c4d7b173cf853279b79a51598f104b403 /chromium/services/tracing | |
parent | 830c9e163d31a9180fadca926b3e1d7dfffb5021 (diff) | |
download | qtwebengine-chromium-8c5c43c7b138c9b4b0bf56d946e61d3bbc111bec.tar.gz |
BASELINE: Update Chromium to 66.0.3359.156
Change-Id: I0c9831ad39911a086b6377b16f995ad75a51e441
Reviewed-by: Michal Klocek <michal.klocek@qt.io>
Diffstat (limited to 'chromium/services/tracing')
30 files changed, 2802 insertions, 0 deletions
diff --git a/chromium/services/tracing/BUILD.gn b/chromium/services/tracing/BUILD.gn new file mode 100644 index 00000000000..157a1a7bb8e --- /dev/null +++ b/chromium/services/tracing/BUILD.gn @@ -0,0 +1,92 @@ +# Copyright 2018 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. + +# There should be only one tracing service. It is currently +# in the browser process. So, only //content/browser should link to this target. +# Others modules should only need the public targets. +import("//services/catalog/public/tools/catalog.gni") +import("//services/service_manager/public/cpp/service.gni") +import("//services/service_manager/public/service_manifest.gni") +import("//services/service_manager/public/tools/test/service_test.gni") + +source_set("lib") { + sources = [ + "agent_registry.cc", + "agent_registry.h", + "coordinator.cc", + "coordinator.h", + "recorder.cc", + "recorder.h", + "tracing_service.cc", + "tracing_service.h", + ] + + public_deps = [ + "//base", + "//mojo/common", + "//mojo/public/cpp/bindings", + "//services/tracing/public/cpp", + ] +} + +service_manifest("manifest") { + name = "tracing" + source = "manifest.json" +} + +service("tracing") { + sources = [ + "service_main.cc", + ] + + deps = [ + ":lib", + "//mojo/public/cpp/system", + ] +} + +source_set("tests") { + testonly = true + + sources = [ + "agent_registry_unittest.cc", + "coordinator_unittest.cc", + "public/cpp/chrome_trace_event_agent_unittest.cc", + "recorder_unittest.cc", + "test_util.cc", + "test_util.h", + ] + + if (!is_android) { + sources += [ "tracing_service_unittest.cc" ] + } + + deps = [ + ":lib", + "//base", + "//base/test:test_support", + "//mojo/public/cpp/bindings", + "//services/service_manager/public/cpp", + "//services/service_manager/public/cpp:service_test_support", + "//services/service_manager/public/mojom", + "//services/tracing:lib", + "//testing/gmock", + "//testing/gtest", + ] + + data_deps = [ + ":tracing", + ] +} + +service_manifest("unittest_manifest") { + name = "tracing_unittests" + source = "unittest_manifest.json" +} + +catalog("tests_catalog") { + testonly = true + embedded_services = [ ":unittest_manifest" ] + standalone_services = [ ":manifest" ] +} diff --git a/chromium/services/tracing/DEPS b/chromium/services/tracing/DEPS new file mode 100644 index 00000000000..5f9afdbbf6f --- /dev/null +++ b/chromium/services/tracing/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+services/service_manager/public" +] diff --git a/chromium/services/tracing/OWNERS b/chromium/services/tracing/OWNERS new file mode 100644 index 00000000000..041b283cbdc --- /dev/null +++ b/chromium/services/tracing/OWNERS @@ -0,0 +1,8 @@ +file://base/trace_event/OWNERS +chiniforooshan@chromium.org + +per-file manifest.json=set noparent +per-file manifest.json=file://ipc/SECURITY_OWNERS + +per-file unittest_manifest.json=set noparent +per-file unittest_manifest.json=file://ipc/SECURITY_OWNERS diff --git a/chromium/services/tracing/agent_registry.cc b/chromium/services/tracing/agent_registry.cc new file mode 100644 index 00000000000..5dceb1513a8 --- /dev/null +++ b/chromium/services/tracing/agent_registry.cc @@ -0,0 +1,143 @@ +// 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 "services/tracing/agent_registry.h" + +#include <string> +#include <utility> + +#include "base/callback_forward.h" +#include "base/logging.h" +#include "base/threading/thread_checker.h" +#include "services/service_manager/public/cpp/bind_source_info.h" +#include "services/service_manager/public/cpp/service_context_ref.h" + +namespace { +tracing::AgentRegistry* g_agent_registry; +} + +namespace tracing { + +AgentRegistry::AgentEntry::AgentEntry( + std::unique_ptr<service_manager::ServiceContextRef> service_ref, + size_t id, + AgentRegistry* agent_registry, + mojom::AgentPtr agent, + const std::string& label, + mojom::TraceDataType type, + bool supports_explicit_clock_sync) + : id_(id), + agent_registry_(agent_registry), + agent_(std::move(agent)), + label_(label), + type_(type), + supports_explicit_clock_sync_(supports_explicit_clock_sync), + is_tracing_(false), + service_ref_(std::move(service_ref)) { + DCHECK(!label.empty()); + agent_.set_connection_error_handler(base::BindRepeating( + &AgentRegistry::AgentEntry::OnConnectionError, AsWeakPtr())); +} + +AgentRegistry::AgentEntry::~AgentEntry() = default; + +void AgentRegistry::AgentEntry::AddDisconnectClosure( + const void* closure_name, + base::OnceClosure closure) { + DCHECK_EQ(0u, closures_.count(closure_name)); + closures_[closure_name] = std::move(closure); +} + +bool AgentRegistry::AgentEntry::RemoveDisconnectClosure( + const void* closure_name) { + return closures_.erase(closure_name) > 0; +} + +bool AgentRegistry::AgentEntry::HasDisconnectClosure(const void* closure_name) { + return closures_.count(closure_name) > 0; +} + +void AgentRegistry::AgentEntry::OnConnectionError() { + // Run disconnect closures if there is any. We should mark |key_value.second| + // as movable so that the version of |Run| that takes an rvalue reference is + // selected not the version that takes a const reference. The former is for + // once callbacks and the latter is for repeating callbacks. + while (!closures_.empty()) { + auto iterator = closures_.begin(); + auto callback = std::move(iterator->second); + const size_t closures_size = closures_.size(); + std::move(callback).Run(); + // Verify that the callback has removed itself. + DCHECK_EQ(1u, closures_size - closures_.size()); + } + agent_registry_->UnregisterAgent(id_); +} + +// static +AgentRegistry* AgentRegistry::GetInstance() { + return g_agent_registry; +} + +AgentRegistry::AgentRegistry( + service_manager::ServiceContextRefFactory* service_ref_factory) + : service_ref_factory_(service_ref_factory) { + DCHECK(!g_agent_registry); + g_agent_registry = this; +} + +AgentRegistry::~AgentRegistry() { + // For testing only. + g_agent_registry = nullptr; +} + +void AgentRegistry::BindAgentRegistryRequest( + mojom::AgentRegistryRequest request, + const service_manager::BindSourceInfo& source_info) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + bindings_.AddBinding(this, std::move(request), source_info.identity); +} + +void AgentRegistry::SetAgentInitializationCallback( + const AgentInitializationCallback& callback) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + agent_initialization_callback_ = callback; + ForAllAgents([this](AgentEntry* agent_entry) { + agent_initialization_callback_.Run(agent_entry); + }); +} + +void AgentRegistry::RemoveAgentInitializationCallback() { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + agent_initialization_callback_.Reset(); +} + +bool AgentRegistry::HasDisconnectClosure(const void* closure_name) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + for (const auto& key_value : agents_) { + if (key_value.second->HasDisconnectClosure(closure_name)) + return true; + } + return false; +} + +void AgentRegistry::RegisterAgent(mojom::AgentPtr agent, + const std::string& label, + mojom::TraceDataType type, + bool supports_explicit_clock_sync) { + auto id = next_agent_id_++; + auto entry = std::make_unique<AgentEntry>(service_ref_factory_->CreateRef(), + id, this, std::move(agent), label, + type, supports_explicit_clock_sync); + if (!agent_initialization_callback_.is_null()) + agent_initialization_callback_.Run(entry.get()); + auto result = agents_.insert(std::make_pair(id, std::move(entry))); + DCHECK(result.second); +} + +void AgentRegistry::UnregisterAgent(size_t agent_id) { + size_t num_deleted = agents_.erase(agent_id); + DCHECK_EQ(1u, num_deleted); +} + +} // namespace tracing diff --git a/chromium/services/tracing/agent_registry.h b/chromium/services/tracing/agent_registry.h new file mode 100644 index 00000000000..91cb7b60363 --- /dev/null +++ b/chromium/services/tracing/agent_registry.h @@ -0,0 +1,127 @@ +// 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. + +#ifndef SERVICES_TRACING_AGENT_REGISTRY_H_ +#define SERVICES_TRACING_AGENT_REGISTRY_H_ + +#include <map> +#include <memory> +#include <string> + +#include "base/callback_forward.h" +#include "base/macros.h" +#include "base/memory/weak_ptr.h" +#include "base/threading/thread_checker.h" +#include "mojo/public/cpp/bindings/binding_set.h" +#include "services/service_manager/public/cpp/identity.h" +#include "services/tracing/public/mojom/tracing.mojom.h" + +namespace service_manager { +struct BindSourceInfo; +class ServiceContextRef; +class ServiceContextRefFactory; +} // namespace service_manager + +namespace tracing { + +class AgentRegistry : public mojom::AgentRegistry { + public: + class AgentEntry : public base::SupportsWeakPtr<AgentEntry> { + public: + AgentEntry(std::unique_ptr<service_manager::ServiceContextRef> service_ref, + size_t id, + AgentRegistry* agent_registry, + mojom::AgentPtr agent, + const std::string& label, + mojom::TraceDataType type, + bool supports_explicit_clock_sync); + ~AgentEntry(); + + void AddDisconnectClosure(const void* closure_name, + base::OnceClosure closure); + bool RemoveDisconnectClosure(const void* closure_name); + bool HasDisconnectClosure(const void* closure_name); + size_t num_disconnect_closures_for_testing() const { + return closures_.size(); + } + + mojom::Agent* agent() const { return agent_.get(); } + const std::string& label() const { return label_; } + mojom::TraceDataType type() const { return type_; } + bool supports_explicit_clock_sync() const { + return supports_explicit_clock_sync_; + } + bool is_tracing() const { return is_tracing_; } + void set_is_tracing(bool is_tracing) { is_tracing_ = is_tracing; } + + private: + void OnConnectionError(); + + const size_t id_; + AgentRegistry* agent_registry_; + mojom::AgentPtr agent_; + const std::string label_; + const mojom::TraceDataType type_; + const bool supports_explicit_clock_sync_; + std::map<const void*, base::OnceClosure> closures_; + bool is_tracing_; + std::unique_ptr<service_manager::ServiceContextRef> service_ref_; + + DISALLOW_COPY_AND_ASSIGN(AgentEntry); + }; + + // A function to be run for every agent that registers itself. + using AgentInitializationCallback = + base::RepeatingCallback<void(AgentEntry*)>; + + static AgentRegistry* GetInstance(); + + explicit AgentRegistry( + service_manager::ServiceContextRefFactory* service_ref_factory); + + void BindAgentRegistryRequest( + mojom::AgentRegistryRequest request, + const service_manager::BindSourceInfo& source_info); + void SetAgentInitializationCallback( + const AgentInitializationCallback& callback); + void RemoveAgentInitializationCallback(); + bool HasDisconnectClosure(const void* closure_name); + + template <typename FunctionType> + void ForAllAgents(FunctionType function) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + for (const auto& key_value : agents_) { + function(key_value.second.get()); + } + } + + private: + friend std::default_delete<AgentRegistry>; + friend class AgentRegistryTest; // For testing. + friend class CoordinatorTest; // For testing. + + ~AgentRegistry() override; + + // mojom::AgentRegistry + void RegisterAgent(mojom::AgentPtr agent, + const std::string& label, + mojom::TraceDataType type, + bool supports_explicit_clock_sync) override; + + void UnregisterAgent(size_t agent_id); + + mojo::BindingSet<mojom::AgentRegistry, service_manager::Identity> bindings_; + size_t next_agent_id_ = 0; + std::map<size_t, std::unique_ptr<AgentEntry>> agents_; + AgentInitializationCallback agent_initialization_callback_; + service_manager::ServiceContextRefFactory* service_ref_factory_; + + THREAD_CHECKER(thread_checker_); + + DISALLOW_COPY_AND_ASSIGN(AgentRegistry); +}; + +} // namespace tracing + +#endif // SERVICES_TRACING_AGENT_REGISTRY_H_ diff --git a/chromium/services/tracing/agent_registry_unittest.cc b/chromium/services/tracing/agent_registry_unittest.cc new file mode 100644 index 00000000000..7a75d0049a9 --- /dev/null +++ b/chromium/services/tracing/agent_registry_unittest.cc @@ -0,0 +1,128 @@ +// 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 "services/tracing/agent_registry.h" + +#include <memory> +#include <string> +#include <utility> + +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "services/service_manager/public/cpp/service_context_ref.h" +#include "services/tracing/public/mojom/tracing.mojom.h" +#include "services/tracing/test_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace tracing { + +class AgentRegistryTest : public testing::Test { + public: + AgentRegistryTest() : service_ref_factory_(base::DoNothing()) {} + + void SetUp() override { + message_loop_.reset(new base::MessageLoop()); + registry_.reset(new AgentRegistry(&service_ref_factory_)); + } + + void TearDown() override { + registry_.reset(); + message_loop_.reset(); + } + + void RegisterAgent(mojom::AgentPtr agent, + const std::string& label, + mojom::TraceDataType type, + bool supports_explicit_clock_sync) { + registry_->RegisterAgent(std::move(agent), label, type, + supports_explicit_clock_sync); + } + + void RegisterAgent(mojom::AgentPtr agent) { + registry_->RegisterAgent(std::move(agent), "label", + mojom::TraceDataType::ARRAY, false); + } + + std::unique_ptr<AgentRegistry> registry_; + service_manager::ServiceContextRefFactory service_ref_factory_; + + private: + std::unique_ptr<base::MessageLoop> message_loop_; +}; + +TEST_F(AgentRegistryTest, RegisterAgent) { + MockAgent agent1; + RegisterAgent(agent1.CreateAgentPtr(), "TraceEvent", + mojom::TraceDataType::ARRAY, false); + size_t num_agents = 0; + registry_->ForAllAgents([&num_agents](AgentRegistry::AgentEntry* entry) { + num_agents++; + EXPECT_EQ("TraceEvent", entry->label()); + EXPECT_EQ(mojom::TraceDataType::ARRAY, entry->type()); + EXPECT_FALSE(entry->supports_explicit_clock_sync()); + }); + EXPECT_EQ(1u, num_agents); + + MockAgent agent2; + RegisterAgent(agent2.CreateAgentPtr(), "Power", mojom::TraceDataType::STRING, + true); + num_agents = 0; + registry_->ForAllAgents([&num_agents](AgentRegistry::AgentEntry* entry) { + num_agents++; + // Properties of |agent1| is already verified. + if (entry->label() == "TraceEvent") + return; + EXPECT_EQ("Power", entry->label()); + EXPECT_EQ(mojom::TraceDataType::STRING, entry->type()); + EXPECT_TRUE(entry->supports_explicit_clock_sync()); + }); + EXPECT_EQ(2u, num_agents); +} + +TEST_F(AgentRegistryTest, UnregisterAgent) { + base::RunLoop run_loop; + MockAgent agent1; + RegisterAgent(agent1.CreateAgentPtr()); + { + MockAgent agent2; + RegisterAgent(agent2.CreateAgentPtr()); + size_t num_agents = 0; + registry_->ForAllAgents( + [&num_agents](AgentRegistry::AgentEntry* entry) { num_agents++; }); + EXPECT_EQ(2u, num_agents); + } + run_loop.RunUntilIdle(); + + // |agent2| is not alive anymore. + size_t num_agents = 0; + registry_->ForAllAgents( + [&num_agents](AgentRegistry::AgentEntry* entry) { num_agents++; }); + EXPECT_EQ(1u, num_agents); +} + +TEST_F(AgentRegistryTest, AgentInitialization) { + size_t num_calls = 0; + MockAgent agent1; + RegisterAgent(agent1.CreateAgentPtr()); + registry_->SetAgentInitializationCallback(base::BindRepeating( + [](size_t* num_calls, tracing::AgentRegistry::AgentEntry* entry) { + (*num_calls)++; + }, + base::Unretained(&num_calls))); + // Since an agent was already registered, the callback should be run once. + EXPECT_EQ(1u, num_calls); + + // The callback should be run on future agents, too. + MockAgent agent2; + RegisterAgent(agent2.CreateAgentPtr()); + EXPECT_EQ(2u, num_calls); + + // The callback should not be run on future agents if it is removed. + registry_->RemoveAgentInitializationCallback(); + MockAgent agent3; + RegisterAgent(agent3.CreateAgentPtr()); + EXPECT_EQ(2u, num_calls); +} + +} // namespace tracing diff --git a/chromium/services/tracing/coordinator.cc b/chromium/services/tracing/coordinator.cc new file mode 100644 index 00000000000..795abd0f371 --- /dev/null +++ b/chromium/services/tracing/coordinator.cc @@ -0,0 +1,586 @@ +// 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 "services/tracing/coordinator.h" + +#include <algorithm> +#include <string> +#include <utility> +#include <vector> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/callback_forward.h" +#include "base/callback_helpers.h" +#include "base/guid.h" +#include "base/json/json_writer.h" +#include "base/json/string_escape.h" +#include "base/logging.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "base/task_scheduler/post_task.h" +#include "base/task_scheduler/task_traits.h" +#include "base/threading/thread_task_runner_handle.h" +#include "base/time/time.h" +#include "base/trace_event/trace_config.h" +#include "base/trace_event/trace_event.h" +#include "mojo/common/data_pipe_utils.h" +#include "services/service_manager/public/cpp/bind_source_info.h" +#include "services/service_manager/public/cpp/service_context_ref.h" +#include "services/tracing/agent_registry.h" +#include "services/tracing/public/mojom/constants.mojom.h" +#include "services/tracing/public/mojom/tracing.mojom.h" +#include "services/tracing/recorder.h" + +namespace { + +const char kMetadataTraceLabel[] = "metadata"; + +const char kGetCategoriesClosureName[] = "GetCategoriesClosure"; +const char kRequestBufferUsageClosureName[] = "RequestBufferUsageClosure"; +const char kRequestClockSyncMarkerClosureName[] = + "RequestClockSyncMarkerClosure"; +const char kStartTracingClosureName[] = "StartTracingClosure"; + +tracing::Coordinator* g_coordinator = nullptr; + +} // namespace + +namespace tracing { + +class Coordinator::TraceStreamer : public base::SupportsWeakPtr<TraceStreamer> { + public: + // Constructed on |main_task_runner_|. + TraceStreamer( + mojo::ScopedDataPipeProducerHandle stream, + const std::string& agent_label, + const scoped_refptr<base::SequencedTaskRunner>& main_task_runner, + base::WeakPtr<Coordinator> coordinator) + : stream_(std::move(stream)), + agent_label_(agent_label), + main_task_runner_(main_task_runner), + coordinator_(coordinator), + metadata_(new base::DictionaryValue()), + stream_is_empty_(true), + json_field_name_written_(false) {} + + // Destroyed on |background_task_runner_|. + ~TraceStreamer() = default; + + // Called from |background_task_runner_|. + void CreateAndSendRecorder( + const std::string& label, + mojom::TraceDataType type, + base::WeakPtr<AgentRegistry::AgentEntry> agent_entry) { + mojom::RecorderPtr ptr; + auto recorder = std::make_unique<Recorder>( + MakeRequest(&ptr), type, + base::BindRepeating(&Coordinator::TraceStreamer::OnRecorderDataChange, + AsWeakPtr(), label)); + recorders_[label].insert(std::move(recorder)); + DCHECK(type != mojom::TraceDataType::STRING || + recorders_[label].size() == 1); + + // Tracing agent proxies are bound on the main thread and should be called + // from the main thread. + main_task_runner_->PostTask( + FROM_HERE, base::BindOnce(&Coordinator::SendRecorder, coordinator_, + agent_entry, std::move(ptr))); + } + + // Called from |background_task_runner_| to close the recorder proxy on the + // correct task runner. + void CloseRecorder(mojom::RecorderPtr recorder) {} + + // Called from |main_task_runner_| either after flushing is complete or at + // shutdown. We either will not write to the stream afterwards or do not care + // what happens to what we try to write. + void CloseStream() { + DCHECK(stream_.is_valid()); + stream_.reset(); + } + + // Called from |main_task_runner_| after flushing is completed. So we are sure + // there is no race in accessing metadata_. + std::unique_ptr<base::DictionaryValue> GetMetadata() { + return std::move(metadata_); + } + + private: + // Called from |background_task_runner_|. + void OnRecorderDataChange(const std::string& label) { + // Bail out if we are in the middle of writing events for another label to + // the stream, since we do not want to interleave chunks for different + // fields. For example, we do not want to mix |traceEvent| chunks with + // |battor| chunks. + // + // If we receive a |battor| chunk from an agent while writing |traceEvent| + // chunks to the stream, we wait until all agents that send |traceEvent| + // chunks are done, and then, we start writing |battor| chunks. + if (!streaming_label_.empty() && streaming_label_ != label) + return; + + while (streaming_label_.empty() || !StreamEventsForCurrentLabel()) { + // We are not waiting for data from any particular label now. So, we look + // at the recorders that have some data available and select the next + // label to stream. + streaming_label_.clear(); + bool all_finished = true; + for (const auto& key_value : recorders_) { + for (const auto& recorder : key_value.second) { + all_finished &= !recorder->is_recording(); + if (!recorder->data().empty()) { + streaming_label_ = key_value.first; + json_field_name_written_ = false; + break; + } + } + if (!streaming_label_.empty()) + break; + } + + if (streaming_label_.empty()) { + // No recorder has any data for us, right now. + if (all_finished) { + StreamMetadata(); + if (!stream_is_empty_ && agent_label_.empty()) { + mojo::common::BlockingCopyFromString("}", stream_); + stream_is_empty_ = false; + } + // Recorder connections should be closed on their binding thread. + main_task_runner_->PostTask( + FROM_HERE, + base::BindOnce(&Coordinator::OnFlushDone, coordinator_)); + } + return; + } + } + } + + // Called from |background_task_runner_|. + bool StreamEventsForCurrentLabel() { + bool waiting_for_agents = false; + mojom::TraceDataType data_type = + (*recorders_[streaming_label_].begin())->data_type(); + for (const auto& recorder : recorders_[streaming_label_]) { + waiting_for_agents |= recorder->is_recording(); + if (!agent_label_.empty() && streaming_label_ != agent_label_) + recorder->clear_data(); + if (recorder->data().empty()) + continue; + + std::string prefix; + if (!json_field_name_written_ && agent_label_.empty()) { + prefix = (stream_is_empty_ ? "{\"" : ",\"") + streaming_label_ + "\":"; + switch (data_type) { + case mojom::TraceDataType::ARRAY: + prefix += "["; + break; + case mojom::TraceDataType::OBJECT: + prefix += "{"; + break; + case mojom::TraceDataType::STRING: + prefix += "\""; + break; + default: + NOTREACHED(); + } + json_field_name_written_ = true; + } + if (data_type == mojom::TraceDataType::STRING) { + // Escape characters if needed for string data. + std::string escaped; + base::EscapeJSONString(recorder->data(), false /* put_in_quotes */, + &escaped); + mojo::common::BlockingCopyFromString(prefix + escaped, stream_); + } else { + if (prefix.empty() && !stream_is_empty_) + prefix = ","; + mojo::common::BlockingCopyFromString(prefix + recorder->data(), + stream_); + } + stream_is_empty_ = false; + recorder->clear_data(); + } + if (!waiting_for_agents) { + if (json_field_name_written_) { + switch (data_type) { + case mojom::TraceDataType::ARRAY: + mojo::common::BlockingCopyFromString("]", stream_); + break; + case mojom::TraceDataType::OBJECT: + mojo::common::BlockingCopyFromString("}", stream_); + break; + case mojom::TraceDataType::STRING: + mojo::common::BlockingCopyFromString("\"", stream_); + break; + default: + NOTREACHED(); + } + stream_is_empty_ = false; + } + } + return waiting_for_agents; + } + + // Called from |background_task_runner_|. + void StreamMetadata() { + if (!agent_label_.empty()) + return; + + for (const auto& key_value : recorders_) { + for (const auto& recorder : key_value.second) { + metadata_->MergeDictionary(&(recorder->metadata())); + } + } + + std::string metadataJSON; + if (!metadata_->empty() && + base::JSONWriter::Write(*metadata_, &metadataJSON)) { + std::string prefix = stream_is_empty_ ? "{\"" : ",\""; + mojo::common::BlockingCopyFromString( + prefix + std::string(kMetadataTraceLabel) + "\":" + metadataJSON, + stream_); + stream_is_empty_ = false; + } + } + + // The stream to which trace events from different agents should be + // serialized, eventually. This is set when tracing is stopped. + mojo::ScopedDataPipeProducerHandle stream_; + std::string agent_label_; + scoped_refptr<base::SequencedTaskRunner> main_task_runner_; + base::WeakPtr<Coordinator> coordinator_; + + std::map<std::string, std::set<std::unique_ptr<Recorder>>> recorders_; + + // If |streaming_label_| is not empty, it shows the label for which we are + // writing chunks to the output stream. + std::string streaming_label_; + std::unique_ptr<base::DictionaryValue> metadata_; + bool stream_is_empty_; + bool json_field_name_written_; + + DISALLOW_COPY_AND_ASSIGN(TraceStreamer); +}; + +// static +Coordinator* Coordinator::GetInstance() { + DCHECK(g_coordinator); + return g_coordinator; +} + +Coordinator::Coordinator( + service_manager::ServiceContextRefFactory* service_ref_factory) + : binding_(this), + task_runner_(base::ThreadTaskRunnerHandle::Get()), + agent_registry_(AgentRegistry::GetInstance()), + service_ref_(service_ref_factory->CreateRef()), + weak_ptr_factory_(this) { + DCHECK(!g_coordinator); + DCHECK(agent_registry_); + g_coordinator = this; + constexpr base::TaskTraits traits = {base::MayBlock(), + base::WithBaseSyncPrimitives(), + base::TaskPriority::BACKGROUND}; + background_task_runner_ = base::CreateSequencedTaskRunnerWithTraits(traits); +} + +Coordinator::~Coordinator() { + if (!stop_and_flush_callback_.is_null()) { + base::ResetAndReturn(&stop_and_flush_callback_) + .Run(std::make_unique<base::DictionaryValue>()); + } + if (!start_tracing_callback_.is_null()) + base::ResetAndReturn(&start_tracing_callback_).Run(false); + if (!request_buffer_usage_callback_.is_null()) + base::ResetAndReturn(&request_buffer_usage_callback_).Run(false, 0, 0); + if (!get_categories_callback_.is_null()) + base::ResetAndReturn(&get_categories_callback_).Run(false, ""); + + if (trace_streamer_) { + // We are in the middle of flushing trace data. We need to + // 1- Close the stream so that the TraceStreamer does not block on writing + // to it. + // 2- Delete the TraceStreamer on the background task runner; it owns + // recorders that should be destructed on the background task runner + // because they are bound on the background task runner. + trace_streamer_->CloseStream(); + background_task_runner_->DeleteSoon(FROM_HERE, trace_streamer_.release()); + } + + g_coordinator = nullptr; +} + +void Coordinator::BindCoordinatorRequest( + mojom::CoordinatorRequest request, + const service_manager::BindSourceInfo& source_info) { + binding_.Bind(std::move(request)); +} + +void Coordinator::StartTracing(const std::string& config, + const StartTracingCallback& callback) { + if (is_tracing_) { + // Cannot change the config while tracing is enabled. + callback.Run(config == config_); + return; + } + + is_tracing_ = true; + config_ = config; + agent_registry_->SetAgentInitializationCallback(base::BindRepeating( + &Coordinator::SendStartTracingToAgent, weak_ptr_factory_.GetWeakPtr())); + if (!agent_registry_->HasDisconnectClosure(&kStartTracingClosureName)) { + callback.Run(true); + return; + } + start_tracing_callback_ = callback; +} + +void Coordinator::SendStartTracingToAgent( + AgentRegistry::AgentEntry* agent_entry) { + DCHECK(!agent_entry->is_tracing()); + agent_entry->AddDisconnectClosure( + &kStartTracingClosureName, + base::BindOnce(&Coordinator::OnTracingStarted, + weak_ptr_factory_.GetWeakPtr(), + base::Unretained(agent_entry), false)); + agent_entry->agent()->StartTracing( + config_, base::TimeTicks::Now(), + base::BindRepeating(&Coordinator::OnTracingStarted, + weak_ptr_factory_.GetWeakPtr(), + base::Unretained(agent_entry))); +} + +void Coordinator::OnTracingStarted(AgentRegistry::AgentEntry* agent_entry, + bool success) { + agent_entry->set_is_tracing(success); + bool removed = + agent_entry->RemoveDisconnectClosure(&kStartTracingClosureName); + DCHECK(removed); + + if (!agent_registry_->HasDisconnectClosure(&kStartTracingClosureName) && + !start_tracing_callback_.is_null()) { + base::ResetAndReturn(&start_tracing_callback_).Run(true); + } +} + +void Coordinator::StopAndFlush(mojo::ScopedDataPipeProducerHandle stream, + const StopAndFlushCallback& callback) { + StopAndFlushAgent(std::move(stream), "", callback); +} + +void Coordinator::StopAndFlushAgent(mojo::ScopedDataPipeProducerHandle stream, + const std::string& agent_label, + const StopAndFlushCallback& callback) { + if (!is_tracing_) { + stream.reset(); + callback.Run(std::make_unique<base::DictionaryValue>()); + return; + } + DCHECK(!trace_streamer_); + DCHECK(stream.is_valid()); + is_tracing_ = false; + + // Do not send |StartTracing| to agents that connect from now on. + agent_registry_->RemoveAgentInitializationCallback(); + trace_streamer_.reset(new Coordinator::TraceStreamer( + std::move(stream), agent_label, task_runner_, + weak_ptr_factory_.GetWeakPtr())); + stop_and_flush_callback_ = callback; + StopAndFlushInternal(); +} + +void Coordinator::StopAndFlushInternal() { + if (agent_registry_->HasDisconnectClosure(&kStartTracingClosureName)) { + // We received a |StopAndFlush| command before receiving |StartTracing| acks + // from all agents. Let's retry after a delay. + task_runner_->PostDelayedTask( + FROM_HERE, + base::BindRepeating(&Coordinator::StopAndFlushInternal, + weak_ptr_factory_.GetWeakPtr()), + base::TimeDelta::FromMilliseconds( + mojom::kStopTracingRetryTimeMilliseconds)); + return; + } + + agent_registry_->ForAllAgents([this](AgentRegistry::AgentEntry* agent_entry) { + if (!agent_entry->is_tracing() || + !agent_entry->supports_explicit_clock_sync()) { + return; + } + const std::string sync_id = base::GenerateGUID(); + agent_entry->AddDisconnectClosure( + &kRequestClockSyncMarkerClosureName, + base::BindOnce(&Coordinator::OnRequestClockSyncMarkerResponse, + weak_ptr_factory_.GetWeakPtr(), + base::Unretained(agent_entry), sync_id, + base::TimeTicks(), base::TimeTicks())); + agent_entry->agent()->RequestClockSyncMarker( + sync_id, + base::BindRepeating(&Coordinator::OnRequestClockSyncMarkerResponse, + weak_ptr_factory_.GetWeakPtr(), + base::Unretained(agent_entry), sync_id)); + }); + if (!agent_registry_->HasDisconnectClosure( + &kRequestClockSyncMarkerClosureName)) { + StopAndFlushAfterClockSync(); + } +} + +void Coordinator::OnRequestClockSyncMarkerResponse( + AgentRegistry::AgentEntry* agent_entry, + const std::string& sync_id, + base::TimeTicks issue_ts, + base::TimeTicks issue_end_ts) { + bool removed = + agent_entry->RemoveDisconnectClosure(&kRequestClockSyncMarkerClosureName); + DCHECK(removed); + + // TODO(charliea): Change this function so that it can accept a boolean + // success indicator instead of having to rely on sentinel issue_ts and + // issue_end_ts values to signal failure. + if (!(issue_ts == base::TimeTicks() || issue_end_ts == base::TimeTicks())) + TRACE_EVENT_CLOCK_SYNC_ISSUER(sync_id, issue_ts, issue_end_ts); + + if (!agent_registry_->HasDisconnectClosure( + &kRequestClockSyncMarkerClosureName)) { + StopAndFlushAfterClockSync(); + } +} + +void Coordinator::StopAndFlushAfterClockSync() { + bool has_tracing_agents = false; + agent_registry_->ForAllAgents( + [this, &has_tracing_agents](AgentRegistry::AgentEntry* agent_entry) { + if (!agent_entry->is_tracing()) + return; + has_tracing_agents = true; + background_task_runner_->PostTask( + FROM_HERE, + base::BindOnce(&Coordinator::TraceStreamer::CreateAndSendRecorder, + trace_streamer_->AsWeakPtr(), agent_entry->label(), + agent_entry->type(), agent_entry->AsWeakPtr())); + }); + if (!has_tracing_agents) + OnFlushDone(); +} + +void Coordinator::SendRecorder( + base::WeakPtr<AgentRegistry::AgentEntry> agent_entry, + mojom::RecorderPtr recorder) { + if (agent_entry) { + agent_entry->agent()->StopAndFlush(std::move(recorder)); + } else { + // Recorders are created and closed on |background_task_runner_|. + background_task_runner_->PostTask( + FROM_HERE, + base::BindOnce(&Coordinator::TraceStreamer::CloseRecorder, + trace_streamer_->AsWeakPtr(), std::move(recorder))); + } +} + +void Coordinator::OnFlushDone() { + base::ResetAndReturn(&stop_and_flush_callback_) + .Run(trace_streamer_->GetMetadata()); + background_task_runner_->DeleteSoon(FROM_HERE, trace_streamer_.release()); + agent_registry_->ForAllAgents([this](AgentRegistry::AgentEntry* agent_entry) { + agent_entry->set_is_tracing(false); + }); + is_tracing_ = false; +} + +void Coordinator::IsTracing(const IsTracingCallback& callback) { + callback.Run(is_tracing_); +} + +void Coordinator::RequestBufferUsage( + const RequestBufferUsageCallback& callback) { + if (!request_buffer_usage_callback_.is_null()) { + callback.Run(false, 0, 0); + return; + } + + maximum_trace_buffer_usage_ = 0; + approximate_event_count_ = 0; + request_buffer_usage_callback_ = callback; + agent_registry_->ForAllAgents([this](AgentRegistry::AgentEntry* agent_entry) { + agent_entry->AddDisconnectClosure( + &kRequestBufferUsageClosureName, + base::BindOnce(&Coordinator::OnRequestBufferStatusResponse, + weak_ptr_factory_.GetWeakPtr(), + base::Unretained(agent_entry), 0 /* capacity */, + 0 /* count */)); + agent_entry->agent()->RequestBufferStatus(base::BindRepeating( + &Coordinator::OnRequestBufferStatusResponse, + weak_ptr_factory_.GetWeakPtr(), base::Unretained(agent_entry))); + }); +} + +void Coordinator::OnRequestBufferStatusResponse( + AgentRegistry::AgentEntry* agent_entry, + uint32_t capacity, + uint32_t count) { + bool removed = + agent_entry->RemoveDisconnectClosure(&kRequestBufferUsageClosureName); + DCHECK(removed); + + if (capacity > 0) { + float percent_full = + static_cast<float>(static_cast<double>(count) / capacity); + maximum_trace_buffer_usage_ = + std::max(maximum_trace_buffer_usage_, percent_full); + approximate_event_count_ += count; + } + + if (!agent_registry_->HasDisconnectClosure(&kRequestBufferUsageClosureName)) { + base::ResetAndReturn(&request_buffer_usage_callback_) + .Run(true, maximum_trace_buffer_usage_, approximate_event_count_); + } +} + +void Coordinator::GetCategories(const GetCategoriesCallback& callback) { + if (is_tracing_) { + callback.Run(false, ""); + } + + DCHECK(get_categories_callback_.is_null()); + is_tracing_ = true; + category_set_.clear(); + get_categories_callback_ = callback; + agent_registry_->ForAllAgents([this](AgentRegistry::AgentEntry* agent_entry) { + agent_entry->AddDisconnectClosure( + &kGetCategoriesClosureName, + base::BindOnce(&Coordinator::OnGetCategoriesResponse, + weak_ptr_factory_.GetWeakPtr(), + base::Unretained(agent_entry), "")); + agent_entry->agent()->GetCategories(base::BindRepeating( + &Coordinator::OnGetCategoriesResponse, weak_ptr_factory_.GetWeakPtr(), + base::Unretained(agent_entry))); + }); +} + +void Coordinator::OnGetCategoriesResponse( + AgentRegistry::AgentEntry* agent_entry, + const std::string& categories) { + bool removed = + agent_entry->RemoveDisconnectClosure(&kGetCategoriesClosureName); + DCHECK(removed); + + std::vector<std::string> split = base::SplitString( + categories, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); + for (const auto& category : split) { + category_set_.insert(category); + } + + if (!agent_registry_->HasDisconnectClosure(&kGetCategoriesClosureName)) { + std::vector<std::string> category_vector(category_set_.begin(), + category_set_.end()); + base::ResetAndReturn(&get_categories_callback_) + .Run(true, base::JoinString(category_vector, ",")); + is_tracing_ = false; + } +} + +} // namespace tracing diff --git a/chromium/services/tracing/coordinator.h b/chromium/services/tracing/coordinator.h new file mode 100644 index 00000000000..17e6867b04b --- /dev/null +++ b/chromium/services/tracing/coordinator.h @@ -0,0 +1,119 @@ +// 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. + +#ifndef SERVICES_TRACING_COORDINATOR_H_ +#define SERVICES_TRACING_COORDINATOR_H_ + +#include <map> +#include <memory> +#include <set> +#include <string> + +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "base/sequenced_task_runner.h" +#include "base/single_thread_task_runner.h" +#include "base/values.h" +#include "mojo/public/cpp/system/data_pipe.h" +#include "services/tracing/agent_registry.h" +#include "services/tracing/public/mojom/tracing.mojom.h" +#include "services/tracing/recorder.h" + +namespace base { +class TimeTicks; +} // namespace base + +namespace service_manager { +struct BindSourceInfo; +} // namespace service_manager + +namespace tracing { + +// Note that this implementation of mojom::Coordinator assumes that agents +// either respond to messages that expect a response or disconnect. Mojo +// verifies this to some extend by DCHECKing if the callback is deleted by the +// agent before being run. However, the agent should not store the callback and +// never run it. +// +// If we see that the above-mentioned assumption does not hold in some cases, we +// should guard against it using timeouts. +class Coordinator : public mojom::Coordinator { + public: + static Coordinator* GetInstance(); + + explicit Coordinator( + service_manager::ServiceContextRefFactory* service_ref_factory); + + void BindCoordinatorRequest( + mojom::CoordinatorRequest request, + const service_manager::BindSourceInfo& source_info); + + private: + friend std::default_delete<Coordinator>; + friend class CoordinatorTest; // For testing. + + class TraceStreamer; + + ~Coordinator() override; + + // mojom::Coordinator + void StartTracing(const std::string& config, + const StartTracingCallback& callback) override; + void StopAndFlush(mojo::ScopedDataPipeProducerHandle stream, + const StopAndFlushCallback& callback) override; + void StopAndFlushAgent(mojo::ScopedDataPipeProducerHandle stream, + const std::string& agent_label, + const StopAndFlushCallback& callback) override; + void IsTracing(const IsTracingCallback& callback) override; + void RequestBufferUsage(const RequestBufferUsageCallback& callback) override; + void GetCategories(const GetCategoriesCallback& callback) override; + + // Internal methods for collecting events from agents. + void SendStartTracingToAgent(AgentRegistry::AgentEntry* agent_entry); + void OnTracingStarted(AgentRegistry::AgentEntry* agent_entry, bool success); + void StopAndFlushInternal(); + void OnRequestClockSyncMarkerResponse(AgentRegistry::AgentEntry* agent_entry, + const std::string& sync_id, + base::TimeTicks issue_ts, + base::TimeTicks issue_end_ts); + void StopAndFlushAfterClockSync(); + void SendRecorder(base::WeakPtr<AgentRegistry::AgentEntry> agent_entry, + mojom::RecorderPtr recorder); + void OnFlushDone(); + + void OnRequestBufferStatusResponse(AgentRegistry::AgentEntry* agent_entry, + uint32_t capacity, + uint32_t count); + + void OnGetCategoriesResponse(AgentRegistry::AgentEntry* agent_entry, + const std::string& categories); + + mojo::Binding<mojom::Coordinator> binding_; + scoped_refptr<base::SingleThreadTaskRunner> task_runner_; + scoped_refptr<base::SequencedTaskRunner> background_task_runner_; + AgentRegistry* agent_registry_; + std::string config_; + bool is_tracing_ = false; + + std::unique_ptr<TraceStreamer> trace_streamer_; + StartTracingCallback start_tracing_callback_; + StopAndFlushCallback stop_and_flush_callback_; + + // For computing trace buffer usage. + float maximum_trace_buffer_usage_ = 0; + uint32_t approximate_event_count_ = 0; + RequestBufferUsageCallback request_buffer_usage_callback_; + + // For getting categories. + std::set<std::string> category_set_; + GetCategoriesCallback get_categories_callback_; + std::unique_ptr<service_manager::ServiceContextRef> service_ref_; + + base::WeakPtrFactory<Coordinator> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(Coordinator); +}; + +} // namespace tracing +#endif // SERVICES_TRACING_COORDINATOR_H_ diff --git a/chromium/services/tracing/coordinator_unittest.cc b/chromium/services/tracing/coordinator_unittest.cc new file mode 100644 index 00000000000..28f6d31c9f1 --- /dev/null +++ b/chromium/services/tracing/coordinator_unittest.cc @@ -0,0 +1,411 @@ +// 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 "services/tracing/coordinator.h" + +#include <algorithm> +#include <memory> +#include <set> +#include <string> +#include <utility> +#include <vector> + +#include "base/run_loop.h" +#include "base/strings/string_split.h" +#include "base/test/scoped_task_environment.h" +#include "mojo/common/data_pipe_drainer.h" +#include "mojo/public/cpp/system/data_pipe.h" +#include "services/service_manager/public/cpp/service_context_ref.h" +#include "services/tracing/public/mojom/tracing.mojom.h" +#include "services/tracing/test_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace tracing { + +class CoordinatorTest : public testing::Test, + public mojo::common::DataPipeDrainer::Client { + public: + CoordinatorTest() : service_ref_factory_(base::DoNothing()) {} + + // testing::Test + void SetUp() override { + agent_registry_.reset(new AgentRegistry(&service_ref_factory_)); + coordinator_.reset(new Coordinator(&service_ref_factory_)); + output_ = ""; + } + + // testing::Test + void TearDown() override { + agents_.clear(); + coordinator_.reset(); + agent_registry_.reset(); + } + + // mojo::common::DataPipeDrainer::Client + void OnDataAvailable(const void* data, size_t num_bytes) override { + output_.append(static_cast<const char*>(data), num_bytes); + } + + // mojo::common::DataPipeDrainer::Client + void OnDataComplete() override { base::ResetAndReturn(&quit_closure_).Run(); } + + MockAgent* AddArrayAgent() { + auto agent = std::make_unique<MockAgent>(); + agent_registry_->RegisterAgent(agent->CreateAgentPtr(), "traceEvents", + mojom::TraceDataType::ARRAY, false); + agents_.push_back(std::move(agent)); + return agents_.back().get(); + } + + MockAgent* AddObjectAgent() { + auto agent = std::make_unique<MockAgent>(); + agent_registry_->RegisterAgent(agent->CreateAgentPtr(), "systemTraceEvents", + mojom::TraceDataType::OBJECT, false); + agents_.push_back(std::move(agent)); + return agents_.back().get(); + } + + MockAgent* AddStringAgent() { + auto agent = std::make_unique<MockAgent>(); + agent_registry_->RegisterAgent(agent->CreateAgentPtr(), "battor", + mojom::TraceDataType::STRING, false); + agents_.push_back(std::move(agent)); + return agents_.back().get(); + } + + void StartTracing(std::string config, + bool expected_response, + bool stop_and_flush) { + base::RepeatingClosure closure; + if (stop_and_flush) { + closure = base::BindRepeating(&CoordinatorTest::StopAndFlush, + base::Unretained(this)); + } + + coordinator_->StartTracing( + config, + base::BindRepeating( + [](bool expected, base::RepeatingClosure closure, bool actual) { + EXPECT_EQ(expected, actual); + if (!closure.is_null()) + closure.Run(); + }, + expected_response, closure)); + } + + void StartTracing(std::string config, bool expected_response) { + StartTracing(config, expected_response, false); + } + + void StopAndFlush() { + mojo::DataPipe data_pipe; + auto dummy_callback = [](std::unique_ptr<base::DictionaryValue> metadata) { + }; + coordinator_->StopAndFlush(std::move(data_pipe.producer_handle), + base::BindRepeating(dummy_callback)); + drainer_.reset(new mojo::common::DataPipeDrainer( + this, std::move(data_pipe.consumer_handle))); + } + + void IsTracing(bool expected_response) { + coordinator_->IsTracing(base::BindRepeating( + [](bool expected, bool actual) { EXPECT_EQ(expected, actual); }, + expected_response)); + } + + void RequestBufferUsage(float expected_usage, uint32_t expected_count) { + coordinator_->RequestBufferUsage(base::BindRepeating( + [](float expected_usage, uint32_t expected_count, bool success, + float usage, uint32_t count) { + EXPECT_TRUE(success); + EXPECT_EQ(expected_usage, usage); + EXPECT_EQ(expected_count, count); + }, + expected_usage, expected_count)); + } + + void CheckDisconnectClosures(size_t num_agents) { + // Verify that all disconnect closures are cleared up. This means that, for + // each agent, either the tracing service is notified that the agent is + // disconnected or the agent has answered to all requests. + size_t count = 0; + agent_registry_->ForAllAgents([&count](AgentRegistry::AgentEntry* entry) { + count++; + EXPECT_EQ(0u, entry->num_disconnect_closures_for_testing()); + }); + EXPECT_EQ(num_agents, count); + } + + void GetCategories(bool expected_success, + std::set<std::string> expected_categories) { + coordinator_->GetCategories(base::BindRepeating( + [](bool expected_success, std::set<std::string> expected_categories, + bool success, const std::string& categories) { + EXPECT_EQ(expected_success, success); + if (!success) + return; + std::vector<std::string> category_vector = base::SplitString( + categories, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); + EXPECT_EQ(expected_categories.size(), category_vector.size()); + for (const auto& expected_category : expected_categories) { + EXPECT_EQ(1, std::count(category_vector.begin(), + category_vector.end(), expected_category)); + } + }, + expected_success, expected_categories)); + } + + base::test::ScopedTaskEnvironment scoped_task_environment_; + std::unique_ptr<AgentRegistry> agent_registry_; + std::unique_ptr<Coordinator> coordinator_; + std::vector<std::unique_ptr<MockAgent>> agents_; + std::unique_ptr<mojo::common::DataPipeDrainer> drainer_; + base::RepeatingClosure quit_closure_; + std::string output_; + service_manager::ServiceContextRefFactory service_ref_factory_; +}; + +TEST_F(CoordinatorTest, StartTracingSimple) { + base::RunLoop run_loop; + auto* agent = AddArrayAgent(); + StartTracing("*", true); + run_loop.RunUntilIdle(); + + // The agent should have received exactly one call from the coordinator. + EXPECT_EQ(1u, agent->call_stat().size()); + EXPECT_EQ("StartTracing", agent->call_stat()[0]); +} + +TEST_F(CoordinatorTest, StartTracingTwoAgents) { + base::RunLoop run_loop; + auto* agent1 = AddArrayAgent(); + StartTracing("*", true); + auto* agent2 = AddStringAgent(); + run_loop.RunUntilIdle(); + + // Each agent should have received exactly one call from the coordinatr. + EXPECT_EQ(1u, agent1->call_stat().size()); + EXPECT_EQ("StartTracing", agent1->call_stat()[0]); + EXPECT_EQ(1u, agent2->call_stat().size()); + EXPECT_EQ("StartTracing", agent2->call_stat()[0]); +} + +TEST_F(CoordinatorTest, StartTracingWithDifferentConfigs) { + base::RunLoop run_loop; + auto* agent = AddArrayAgent(); + StartTracing("config 1", true); + // The 2nd |StartTracing| should return false. + StartTracing("config 2", false); + run_loop.RunUntilIdle(); + + // The agent should have received exactly one call from the coordinator + // because the 2nd |StartTracing| was aborted. + EXPECT_EQ(1u, agent->call_stat().size()); + EXPECT_EQ("StartTracing", agent->call_stat()[0]); +} + +TEST_F(CoordinatorTest, StartTracingWithSameConfigs) { + base::RunLoop run_loop; + auto* agent = AddArrayAgent(); + StartTracing("config", true); + // The 2nd |StartTracing| should return true when we are not trying to change + // the config. + StartTracing("config", true); + run_loop.RunUntilIdle(); + + // The agent should have received exactly one call from the coordinator + // because the 2nd |StartTracing| was a no-op. + EXPECT_EQ(1u, agent->call_stat().size()); + EXPECT_EQ("StartTracing", agent->call_stat()[0]); +} + +TEST_F(CoordinatorTest, StopAndFlushObjectAgent) { + base::RunLoop run_loop; + quit_closure_ = run_loop.QuitClosure(); + + auto* agent = AddObjectAgent(); + agent->data_.push_back("\"content\":{\"a\":1}"); + agent->data_.push_back("\"name\":\"etw\""); + + StartTracing("config", true, true); + if (!quit_closure_.is_null()) + run_loop.Run(); + + EXPECT_EQ("{\"systemTraceEvents\":{\"content\":{\"a\":1},\"name\":\"etw\"}}", + output_); + + // Each agent should have received exactly two calls. + EXPECT_EQ(2u, agent->call_stat().size()); + EXPECT_EQ("StartTracing", agent->call_stat()[0]); + EXPECT_EQ("StopAndFlush", agent->call_stat()[1]); +} + +TEST_F(CoordinatorTest, StopAndFlushTwoArrayAgents) { + base::RunLoop run_loop; + quit_closure_ = run_loop.QuitClosure(); + + auto* agent1 = AddArrayAgent(); + agent1->data_.push_back("e1"); + agent1->data_.push_back("e2"); + + auto* agent2 = AddArrayAgent(); + agent2->data_.push_back("e3"); + agent2->data_.push_back("e4"); + + StartTracing("config", true, true); + if (!quit_closure_.is_null()) + run_loop.Run(); + + // |output_| should be of the form {"traceEvents":[ei,ej,ek,el]}, where + // ei,ej,ek,el is a permutation of e1,e2,e3,e4 such that e1 is before e2 and + // e3 is before e4 since e1 and 2 come from the same agent and their order + // should be preserved and, similarly, the order of e3 and e4 should be + // preserved, too. + EXPECT_TRUE(output_ == "{\"traceEvents\":[e1,e2,e3,e4]}" || + output_ == "{\"traceEvents\":[e1,e3,e2,e4]}" || + output_ == "{\"traceEvents\":[e1,e3,e4,e2]}" || + output_ == "{\"traceEvents\":[e3,e1,e2,e4]}" || + output_ == "{\"traceEvents\":[e3,e1,e4,e2]}" || + output_ == "{\"traceEvents\":[e3,e4,e1,e2]}"); + + // Each agent should have received exactly two calls. + EXPECT_EQ(2u, agent1->call_stat().size()); + EXPECT_EQ("StartTracing", agent1->call_stat()[0]); + EXPECT_EQ("StopAndFlush", agent1->call_stat()[1]); + + EXPECT_EQ(2u, agent2->call_stat().size()); + EXPECT_EQ("StartTracing", agent2->call_stat()[0]); + EXPECT_EQ("StopAndFlush", agent2->call_stat()[1]); +} + +TEST_F(CoordinatorTest, StopAndFlushDifferentTypeAgents) { + base::RunLoop run_loop; + quit_closure_ = run_loop.QuitClosure(); + + auto* agent1 = AddArrayAgent(); + agent1->data_.push_back("e1"); + agent1->data_.push_back("e2"); + + auto* agent2 = AddStringAgent(); + agent2->data_.push_back("e3"); + agent2->data_.push_back("e4"); + + StartTracing("config", true, true); + if (!quit_closure_.is_null()) + run_loop.Run(); + + EXPECT_TRUE(output_ == "{\"traceEvents\":[e1,e2],\"battor\":\"e3e4\"}" || + output_ == "{\"battor\":\"e3e4\",\"traceEvents\":[e1,e2]}"); + + // Each agent should have received exactly two calls. + EXPECT_EQ(2u, agent1->call_stat().size()); + EXPECT_EQ("StartTracing", agent1->call_stat()[0]); + EXPECT_EQ("StopAndFlush", agent1->call_stat()[1]); + + EXPECT_EQ(2u, agent2->call_stat().size()); + EXPECT_EQ("StartTracing", agent2->call_stat()[0]); + EXPECT_EQ("StopAndFlush", agent2->call_stat()[1]); +} + +TEST_F(CoordinatorTest, StopAndFlushWithMetadata) { + base::RunLoop run_loop; + quit_closure_ = run_loop.QuitClosure(); + + auto* agent = AddArrayAgent(); + agent->data_.push_back("event"); + agent->metadata_.SetString("key", "value"); + + StartTracing("config", true, true); + if (!quit_closure_.is_null()) + run_loop.Run(); + + // Metadata is written at after trace data. + EXPECT_EQ("{\"traceEvents\":[event],\"metadata\":{\"key\":\"value\"}}", + output_); + EXPECT_EQ(2u, agent->call_stat().size()); + EXPECT_EQ("StartTracing", agent->call_stat()[0]); + EXPECT_EQ("StopAndFlush", agent->call_stat()[1]); +} + +TEST_F(CoordinatorTest, IsTracing) { + base::RunLoop run_loop; + StartTracing("config", true); + IsTracing(true); + run_loop.RunUntilIdle(); +} + +TEST_F(CoordinatorTest, IsNotTracing) { + base::RunLoop run_loop; + IsTracing(false); + run_loop.RunUntilIdle(); +} + +TEST_F(CoordinatorTest, RequestBufferUsage) { + auto* agent1 = AddArrayAgent(); + agent1->trace_log_status_.event_capacity = 4; + agent1->trace_log_status_.event_count = 1; + RequestBufferUsage(0.25, 1); + base::RunLoop().RunUntilIdle(); + CheckDisconnectClosures(1); + + auto* agent2 = AddArrayAgent(); + agent2->trace_log_status_.event_capacity = 8; + agent2->trace_log_status_.event_count = 1; + // The buffer usage of |agent2| is less than the buffer usage of |agent1| and + // so the total buffer usage, i.e 0.25, does not change. But, the approximage + // count will be increased from 1 to 2. + RequestBufferUsage(0.25, 2); + base::RunLoop().RunUntilIdle(); + CheckDisconnectClosures(2); + + base::RunLoop run_loop3; + auto* agent3 = AddArrayAgent(); + agent3->trace_log_status_.event_capacity = 8; + agent3->trace_log_status_.event_count = 4; + // |agent3| has the worst buffer usage of 0.5. + RequestBufferUsage(0.5, 6); + base::RunLoop().RunUntilIdle(); + CheckDisconnectClosures(3); + + // At the end |agent1| receveis 3 calls, |agent2| receives 2 calls, and + // |agent3| receives 1 call. + EXPECT_EQ(3u, agent1->call_stat().size()); + EXPECT_EQ(2u, agent2->call_stat().size()); + EXPECT_EQ(1u, agent3->call_stat().size()); +} + +TEST_F(CoordinatorTest, GetCategoriesFail) { + base::RunLoop run_loop; + StartTracing("config", true); + std::set<std::string> expected_categories; + GetCategories(false, expected_categories); + run_loop.RunUntilIdle(); +} + +TEST_F(CoordinatorTest, GetCategoriesSimple) { + base::RunLoop run_loop; + auto* agent = AddArrayAgent(); + agent->categories_ = "cat2,cat1"; + std::set<std::string> expected_categories; + expected_categories.insert("cat1"); + expected_categories.insert("cat2"); + GetCategories(true, expected_categories); + run_loop.RunUntilIdle(); +} + +TEST_F(CoordinatorTest, GetCategoriesFromTwoAgents) { + base::RunLoop run_loop; + auto* agent1 = AddArrayAgent(); + agent1->categories_ = "cat2,cat1"; + auto* agent2 = AddArrayAgent(); + agent2->categories_ = "cat3,cat2"; + std::set<std::string> expected_categories; + expected_categories.insert("cat1"); + expected_categories.insert("cat2"); + expected_categories.insert("cat3"); + GetCategories(true, expected_categories); + run_loop.RunUntilIdle(); +} + +} // namespace tracing diff --git a/chromium/services/tracing/manifest.json b/chromium/services/tracing/manifest.json new file mode 100644 index 00000000000..7f5e1128fc2 --- /dev/null +++ b/chromium/services/tracing/manifest.json @@ -0,0 +1,21 @@ +{ + "name": "tracing", + "display_name": "Tracing", + "interface_provider_specs": { + "service_manager:connector": { + "provides": { + "app": [ + "tracing::mojom::AgentRegistry" + ], + "tracing": [ "tracing::mojom::Coordinator" ], + "tests": [ "*" ] + }, + "requires": { + "service_manager": [ + "service_manager:singleton", + "service_manager:service_manager" + ] + } + } + } +} diff --git a/chromium/services/tracing/public/cpp/BUILD.gn b/chromium/services/tracing/public/cpp/BUILD.gn new file mode 100644 index 00000000000..d46b333bf70 --- /dev/null +++ b/chromium/services/tracing/public/cpp/BUILD.gn @@ -0,0 +1,22 @@ +# Copyright 2018 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. + +component("cpp") { + sources = [ + "base_agent.cc", + "base_agent.h", + "chrome_trace_event_agent.cc", + "chrome_trace_event_agent.h", + ] + + defines = [ "IS_TRACING_CPP_IMPL" ] + output_name = "tracing_cpp" + + public_deps = [ + "//base", + "//mojo/public/cpp/bindings", + "//services/service_manager/public/cpp", + "//services/tracing/public/mojom", + ] +} diff --git a/chromium/services/tracing/public/cpp/base_agent.cc b/chromium/services/tracing/public/cpp/base_agent.cc new file mode 100644 index 00000000000..3df867d4e99 --- /dev/null +++ b/chromium/services/tracing/public/cpp/base_agent.cc @@ -0,0 +1,57 @@ +// Copyright 2018 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/base_agent.h" + +#include <utility> + +#include "services/service_manager/public/cpp/connector.h" +#include "services/tracing/public/mojom/constants.mojom.h" + +namespace tracing { + +BaseAgent::BaseAgent(service_manager::Connector* connector, + const std::string& label, + mojom::TraceDataType type, + bool supports_explicit_clock_sync) + : binding_(this) { + // |connector| can be null in tests. + if (!connector) + return; + tracing::mojom::AgentRegistryPtr agent_registry; + connector->BindInterface(tracing::mojom::kServiceName, &agent_registry); + + tracing::mojom::AgentPtr agent; + binding_.Bind(mojo::MakeRequest(&agent)); + agent_registry->RegisterAgent(std::move(agent), label, type, + supports_explicit_clock_sync); +} + +BaseAgent::~BaseAgent() = default; + +void BaseAgent::StartTracing(const std::string& config, + base::TimeTicks coordinator_time, + const Agent::StartTracingCallback& callback) { + callback.Run(true /* success */); +} + +void BaseAgent::StopAndFlush(tracing::mojom::RecorderPtr recorder) {} + +void BaseAgent::RequestClockSyncMarker( + const std::string& sync_id, + const Agent::RequestClockSyncMarkerCallback& callback) { + NOTREACHED() << "The agent claims to support explicit clock sync but does " + << "not override BaseAgent::RequestClockSyncMarker()"; +} + +void BaseAgent::GetCategories(const Agent::GetCategoriesCallback& callback) { + callback.Run("" /* categories */); +} + +void BaseAgent::RequestBufferStatus( + const Agent::RequestBufferStatusCallback& callback) { + callback.Run(0 /* capacity */, 0 /* count */); +} + +} // namespace tracing diff --git a/chromium/services/tracing/public/cpp/base_agent.h b/chromium/services/tracing/public/cpp/base_agent.h new file mode 100644 index 00000000000..ae6caf129d9 --- /dev/null +++ b/chromium/services/tracing/public/cpp/base_agent.h @@ -0,0 +1,51 @@ +// Copyright 2018 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. + +#ifndef SERVICES_TRACING_PUBLIC_CPP_BASE_AGENT_H_ +#define SERVICES_TRACING_PUBLIC_CPP_BASE_AGENT_H_ + +#include <string> + +#include "base/component_export.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "services/tracing/public/mojom/tracing.mojom.h" + +namespace service_manager { +class Connector; +} // namespace service_manager + +// This class is a minimal implementation of mojom::Agent to reduce boilerplate +// code in tracing agents. A tracing agent can inherit from this class and only +// override methods that actually do something, in most cases only StartTracing +// and StopAndFlush. +namespace tracing { +class COMPONENT_EXPORT(TRACING_CPP) BaseAgent : public mojom::Agent { + protected: + BaseAgent(service_manager::Connector* connector, + const std::string& label, + mojom::TraceDataType type, + bool supports_explicit_clock_sync); + ~BaseAgent() override; + + private: + // tracing::mojom::Agent: + void StartTracing(const std::string& config, + base::TimeTicks coordinator_time, + const Agent::StartTracingCallback& callback) override; + void StopAndFlush(tracing::mojom::RecorderPtr recorder) override; + void RequestClockSyncMarker( + const std::string& sync_id, + const Agent::RequestClockSyncMarkerCallback& callback) override; + void GetCategories(const Agent::GetCategoriesCallback& callback) override; + void RequestBufferStatus( + const Agent::RequestBufferStatusCallback& callback) override; + + mojo::Binding<tracing::mojom::Agent> binding_; + + DISALLOW_COPY_AND_ASSIGN(BaseAgent); +}; + +} // namespace tracing + +#endif // SERVICES_TRACING_PUBLIC_CPP_BASE_AGENT_H_ diff --git a/chromium/services/tracing/public/cpp/chrome_trace_event_agent.cc b/chromium/services/tracing/public/cpp/chrome_trace_event_agent.cc new file mode 100644 index 00000000000..0def62c7a34 --- /dev/null +++ b/chromium/services/tracing/public/cpp/chrome_trace_event_agent.cc @@ -0,0 +1,122 @@ +// 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 "services/tracing/public/cpp/chrome_trace_event_agent.h" + +#include <string> +#include <utility> +#include <vector> + +#include "base/bind.h" +#include "base/memory/ref_counted.h" +#include "base/memory/ref_counted_memory.h" +#include "base/strings/string_util.h" +#include "base/threading/thread_checker.h" +#include "base/time/time.h" +#include "base/trace_event/trace_log.h" +#include "base/values.h" +#include "services/service_manager/public/cpp/connector.h" +#include "services/tracing/public/mojom/constants.mojom.h" + +namespace { + +const char kChromeTraceEventLabel[] = "traceEvents"; + +tracing::ChromeTraceEventAgent* g_chrome_trace_event_agent; + +} // namespace + +namespace tracing { + +// static +ChromeTraceEventAgent* ChromeTraceEventAgent::GetInstance() { + return g_chrome_trace_event_agent; +} + +ChromeTraceEventAgent::ChromeTraceEventAgent( + service_manager::Connector* connector) + : BaseAgent(connector, + kChromeTraceEventLabel, + mojom::TraceDataType::ARRAY, + false /* supports_explicit_clock_sync */), + enabled_tracing_modes_(0) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + DCHECK(!g_chrome_trace_event_agent); + g_chrome_trace_event_agent = this; +} + +ChromeTraceEventAgent::~ChromeTraceEventAgent() { + DCHECK(!trace_log_needs_me_); + g_chrome_trace_event_agent = nullptr; +} + +void ChromeTraceEventAgent::AddMetadataGeneratorFunction( + MetadataGeneratorFunction generator) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + metadata_generator_functions_.push_back(generator); +} + +void ChromeTraceEventAgent::StartTracing(const std::string& config, + base::TimeTicks coordinator_time, + const StartTracingCallback& callback) { + DCHECK(!recorder_); +#if defined(__native_client__) + // NaCl and system times are offset by a bit, so subtract some time from + // the captured timestamps. The value might be off by a bit due to messaging + // latency. + base::TimeDelta time_offset = base::TimeTicks::Now() - coordinator_time; + TraceLog::GetInstance()->SetTimeOffset(time_offset); +#endif + enabled_tracing_modes_ = base::trace_event::TraceLog::RECORDING_MODE; + const base::trace_event::TraceConfig trace_config(config); + if (!trace_config.event_filters().empty()) + enabled_tracing_modes_ |= base::trace_event::TraceLog::FILTERING_MODE; + base::trace_event::TraceLog::GetInstance()->SetEnabled( + trace_config, enabled_tracing_modes_); + callback.Run(true); +} + +void ChromeTraceEventAgent::StopAndFlush(mojom::RecorderPtr recorder) { + DCHECK(!recorder_); + recorder_ = std::move(recorder); + base::trace_event::TraceLog::GetInstance()->SetDisabled( + enabled_tracing_modes_); + enabled_tracing_modes_ = 0; + for (const auto& generator : metadata_generator_functions_) { + auto metadata = generator.Run(); + if (metadata) + recorder_->AddMetadata(std::move(metadata)); + } + trace_log_needs_me_ = true; + base::trace_event::TraceLog::GetInstance()->Flush(base::Bind( + &ChromeTraceEventAgent::OnTraceLogFlush, base::Unretained(this))); +} + +void ChromeTraceEventAgent::RequestBufferStatus( + const RequestBufferStatusCallback& callback) { + base::trace_event::TraceLogStatus status = + base::trace_event::TraceLog::GetInstance()->GetStatus(); + callback.Run(status.event_capacity, status.event_count); +} + +void ChromeTraceEventAgent::GetCategories( + const GetCategoriesCallback& callback) { + std::vector<std::string> category_vector; + base::trace_event::TraceLog::GetInstance()->GetKnownCategoryGroups( + &category_vector); + callback.Run(base::JoinString(category_vector, ",")); +} + +void ChromeTraceEventAgent::OnTraceLogFlush( + const scoped_refptr<base::RefCountedString>& events_str, + bool has_more_events) { + if (!events_str->data().empty()) + recorder_->AddChunk(events_str->data()); + if (!has_more_events) { + trace_log_needs_me_ = false; + recorder_.reset(); + } +} + +} // namespace tracing diff --git a/chromium/services/tracing/public/cpp/chrome_trace_event_agent.h b/chromium/services/tracing/public/cpp/chrome_trace_event_agent.h new file mode 100644 index 00000000000..f2f0c57b812 --- /dev/null +++ b/chromium/services/tracing/public/cpp/chrome_trace_event_agent.h @@ -0,0 +1,70 @@ +// 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. + +#ifndef SERVICES_TRACING_PUBLIC_CPP_CHROME_TRACE_EVENT_AGENT_H_ +#define SERVICES_TRACING_PUBLIC_CPP_CHROME_TRACE_EVENT_AGENT_H_ + +#include <memory> +#include <string> +#include <vector> + +#include "base/component_export.h" +#include "base/memory/ref_counted.h" +#include "base/memory/ref_counted_memory.h" +#include "base/threading/thread_checker.h" +#include "base/values.h" +#include "services/tracing/public/cpp/base_agent.h" +#include "services/tracing/public/mojom/tracing.mojom.h" + +namespace base { +class TimeTicks; +} // namespace base + +namespace service_manager { +class Connector; +} // namespace service_manager + +namespace tracing { + +class COMPONENT_EXPORT(TRACING_CPP) ChromeTraceEventAgent : public BaseAgent { + public: + using MetadataGeneratorFunction = + base::RepeatingCallback<std::unique_ptr<base::DictionaryValue>()>; + + static ChromeTraceEventAgent* GetInstance(); + + explicit ChromeTraceEventAgent(service_manager::Connector* connector); + + void AddMetadataGeneratorFunction(MetadataGeneratorFunction generator); + + private: + friend std::default_delete<ChromeTraceEventAgent>; // For Testing + friend class ChromeTraceEventAgentTest; // For Testing + + ~ChromeTraceEventAgent() override; + + // mojom::Agent + void StartTracing(const std::string& config, + base::TimeTicks coordinator_time, + const StartTracingCallback& callback) override; + void StopAndFlush(mojom::RecorderPtr recorder) override; + void RequestBufferStatus( + const RequestBufferStatusCallback& callback) override; + void GetCategories(const GetCategoriesCallback& callback) override; + + void OnTraceLogFlush(const scoped_refptr<base::RefCountedString>& events_str, + bool has_more_events); + + uint8_t enabled_tracing_modes_; + mojom::RecorderPtr recorder_; + std::vector<MetadataGeneratorFunction> metadata_generator_functions_; + bool trace_log_needs_me_ = false; + + THREAD_CHECKER(thread_checker_); + + DISALLOW_COPY_AND_ASSIGN(ChromeTraceEventAgent); +}; + +} // namespace tracing +#endif // SERVICES_TRACING_PUBLIC_CPP_CHROME_TRACE_EVENT_AGENT_H_ diff --git a/chromium/services/tracing/public/cpp/chrome_trace_event_agent_unittest.cc b/chromium/services/tracing/public/cpp/chrome_trace_event_agent_unittest.cc new file mode 100644 index 00000000000..ab829416ce7 --- /dev/null +++ b/chromium/services/tracing/public/cpp/chrome_trace_event_agent_unittest.cc @@ -0,0 +1,186 @@ +// 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 "services/tracing/public/cpp/chrome_trace_event_agent.h" + +#include <utility> + +#include "base/bind.h" +#include "base/callback_forward.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/test/trace_event_analyzer.h" +#include "base/time/time.h" +#include "base/trace_event/trace_config.h" +#include "base/trace_event/trace_event.h" +#include "base/trace_event/trace_log.h" +#include "base/values.h" +#include "services/tracing/public/mojom/tracing.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace tracing { + +namespace { +const char kTestCategory[] = "ChromeTraceEventAgentTestCategory"; +const char kTestMetadataKey[] = "ChromeTraceEventAgentTestMetadata"; +} // namespace + +class MockRecorder : public mojom::Recorder { + public: + explicit MockRecorder(mojom::RecorderRequest request) + : binding_(this, std::move(request)) { + binding_.set_connection_error_handler(base::BindRepeating( + &MockRecorder::OnConnectionError, base::Unretained(this))); + } + + std::string events() const { return events_; } + std::string metadata() const { return metadata_; } + void set_quit_closure(base::Closure quit_closure) { + quit_closure_ = quit_closure; + } + + private: + void Append(std::string* dest, const std::string& chunk) { + if (chunk.empty()) + return; + if (!dest->empty()) + dest->push_back(','); + dest->append(chunk); + } + + void AddChunk(const std::string& chunk) override { + std::unique_ptr<trace_analyzer::TraceAnalyzer> analyzer( + trace_analyzer::TraceAnalyzer::Create("[" + chunk + "]")); + trace_analyzer::TraceEventVector events; + analyzer->FindEvents(trace_analyzer::Query::EventCategoryIs(kTestCategory), + &events); + for (const auto* event : events) { + Append(&events_, event->name); + } + } + + void AddMetadata(std::unique_ptr<base::DictionaryValue> metadata) override { + base::DictionaryValue* dict = nullptr; + EXPECT_TRUE(metadata->GetAsDictionary(&dict)); + std::string value; + if (dict->GetString(kTestMetadataKey, &value)) + Append(&metadata_, value); + } + + void OnConnectionError() { + if (quit_closure_) + quit_closure_.Run(); + } + + mojo::Binding<mojom::Recorder> binding_; + std::string events_; + std::string metadata_; + base::Closure quit_closure_; +}; + +class ChromeTraceEventAgentTest : public testing::Test { + public: + void SetUp() override { + message_loop_.reset(new base::MessageLoop()); + agent_.reset(new ChromeTraceEventAgent(nullptr)); + } + + void TearDown() override { + base::trace_event::TraceLog::GetInstance()->SetDisabled(); + recorder_.reset(); + agent_.reset(); + message_loop_.reset(); + } + + void StartTracing(const std::string& categories) { + agent_->StartTracing( + base::trace_event::TraceConfig(categories, "").ToString(), + base::TimeTicks::Now(), + base::BindRepeating([](bool success) { EXPECT_TRUE(success); })); + } + + void StopAndFlush(base::Closure quit_closure) { + mojom::RecorderPtr recorder_ptr; + recorder_.reset(new MockRecorder(MakeRequest(&recorder_ptr))); + recorder_->set_quit_closure(quit_closure); + agent_->StopAndFlush(std::move(recorder_ptr)); + } + + void AddMetadataGeneratorFunction( + ChromeTraceEventAgent::MetadataGeneratorFunction generator) { + agent_->AddMetadataGeneratorFunction(generator); + } + + void GetCategories(const std::string& expected_category, + base::Closure quit_closure) { + agent_->GetCategories(base::BindRepeating( + &ChromeTraceEventAgentTest::OnGetCategoriesReply, + base::Unretained(this), expected_category, quit_closure)); + } + + void OnGetCategoriesReply(const std::string& expected_category, + base::Closure quit_closure, + const std::string& categories) { + EXPECT_FALSE(categories.rfind(expected_category) == std::string::npos); + quit_closure.Run(); + } + + MockRecorder* recorder() const { return recorder_.get(); } + + private: + std::unique_ptr<base::MessageLoop> message_loop_; + std::unique_ptr<ChromeTraceEventAgent> agent_; + std::unique_ptr<MockRecorder> recorder_; +}; + +TEST_F(ChromeTraceEventAgentTest, StartTracing) { + EXPECT_FALSE(base::trace_event::TraceLog::GetInstance()->IsEnabled()); + base::RunLoop run_loop; + StartTracing("*"); + EXPECT_TRUE(base::trace_event::TraceLog::GetInstance()->IsEnabled()); + StopAndFlush(run_loop.QuitClosure()); + run_loop.Run(); +} + +TEST_F(ChromeTraceEventAgentTest, StopAndFlushEvents) { + EXPECT_FALSE(base::trace_event::TraceLog::GetInstance()->IsEnabled()); + base::RunLoop run_loop; + StartTracing(kTestCategory); + TRACE_EVENT_INSTANT0(kTestCategory, "event1", TRACE_EVENT_SCOPE_THREAD); + TRACE_EVENT_INSTANT0(kTestCategory, "event2", TRACE_EVENT_SCOPE_THREAD); + StopAndFlush(run_loop.QuitClosure()); + run_loop.Run(); + + auto* mock_recorder = recorder(); + EXPECT_EQ("event1,event2", mock_recorder->events()); + EXPECT_EQ("", mock_recorder->metadata()); + EXPECT_FALSE(base::trace_event::TraceLog::GetInstance()->IsEnabled()); +} + +TEST_F(ChromeTraceEventAgentTest, GetCategories) { + base::RunLoop run_loop; + TRACE_EVENT_INSTANT0(kTestCategory, "event1", TRACE_EVENT_SCOPE_THREAD); + GetCategories(kTestCategory, run_loop.QuitClosure()); + run_loop.Run(); +} + +TEST_F(ChromeTraceEventAgentTest, StopAndFlushMetadata) { + EXPECT_FALSE(base::trace_event::TraceLog::GetInstance()->IsEnabled()); + base::RunLoop run_loop; + AddMetadataGeneratorFunction(base::BindRepeating([] { + std::unique_ptr<base::DictionaryValue> metadata_dict( + new base::DictionaryValue()); + metadata_dict->SetString(kTestMetadataKey, "test metadata"); + return metadata_dict; + })); + StartTracing(kTestCategory); + StopAndFlush(run_loop.QuitClosure()); + run_loop.Run(); + + auto* mock_recorder = recorder(); + EXPECT_EQ("", mock_recorder->events()); + EXPECT_EQ("test metadata", mock_recorder->metadata()); + EXPECT_FALSE(base::trace_event::TraceLog::GetInstance()->IsEnabled()); +} +} // namespace tracing diff --git a/chromium/services/tracing/public/mojom/BUILD.gn b/chromium/services/tracing/public/mojom/BUILD.gn new file mode 100644 index 00000000000..ebfa6b95a02 --- /dev/null +++ b/chromium/services/tracing/public/mojom/BUILD.gn @@ -0,0 +1,22 @@ +# Copyright 2018 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. + +import("//mojo/public/tools/bindings/mojom.gni") + +mojom_component("mojom") { + output_prefix = "tracing_mojom" + macro_prefix = "TRACING_MOJOM" + + sources = [ + "constants.mojom", + "tracing.mojom", + ] + + public_deps = [ + "//mojo/common:common_custom_types", + ] + + # TODO(crbug.com/714018): Convert the implementation to use OnceCallback. + use_once_callback = false +} diff --git a/chromium/services/tracing/public/mojom/OWNERS b/chromium/services/tracing/public/mojom/OWNERS new file mode 100644 index 00000000000..08850f42120 --- /dev/null +++ b/chromium/services/tracing/public/mojom/OWNERS @@ -0,0 +1,2 @@ +per-file *.mojom=set noparent +per-file *.mojom=file://ipc/SECURITY_OWNERS diff --git a/chromium/services/tracing/public/mojom/constants.mojom b/chromium/services/tracing/public/mojom/constants.mojom new file mode 100644 index 00000000000..abf03fa664b --- /dev/null +++ b/chromium/services/tracing/public/mojom/constants.mojom @@ -0,0 +1,13 @@ +// 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. + +module tracing.mojom; + +const uint32 kStopTracingRetryTimeMilliseconds = 100; + +const string kServiceName = "tracing"; + +// The label of agents that provide trace data of the format explained in +// https://goo.gl/Dw8qPY. +const string kChromeTraceEventLabel = "traceEvents"; diff --git a/chromium/services/tracing/public/mojom/tracing.mojom b/chromium/services/tracing/public/mojom/tracing.mojom new file mode 100644 index 00000000000..39a74c1a9f2 --- /dev/null +++ b/chromium/services/tracing/public/mojom/tracing.mojom @@ -0,0 +1,77 @@ +// 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. + +module tracing.mojom; + +import "mojo/common/time.mojom"; +import "mojo/common/values.mojom"; + +// The JSON type of data coming from a tracing agents. +// +// - All agents with the same label should have the same type. +// - There can be multiple agents with the same label, if their data type is +// ARRAY or OBJECT. Their data will be concatenated together and separated by +// commas. +// - There can be only one agent with data type STRING. +enum TraceDataType { + ARRAY, + OBJECT, + STRING +}; + +// Tracing agents, like |chrome|, |etw|, |battor|, and |cros|, use this +// interface to register themselves to the tracing service. +// +// This is a separate interface from |Coordinator| for security and privacy +// reasons: although we want to let almost every process be able to send tracing +// data to the service, we do not want to let an untrusted child process be able +// to collect traces from other processes using the |Coordinator| interface. +interface AgentRegistry { + RegisterAgent(Agent agent, string label, TraceDataType type, + bool supports_explicit_clock_sync_); +}; + +// When the tracing service calls |StopAndFlush| on an agent, the agent begins +// serializing data into the given recorder. When finished, the agent should +// close the recorder connection to signal the tracing service that no more data +// will be sent. +interface Agent { + StartTracing(string config, mojo.common.mojom.TimeTicks coordinator_time) => ( + bool success); + StopAndFlush(Recorder recorder); + RequestClockSyncMarker(string sync_id) => ( + mojo.common.mojom.TimeTicks issue_ts, + mojo.common.mojom.TimeTicks issue_end_ts); + RequestBufferStatus() => (uint32 capacity, uint32 count); + GetCategories() => (string categories); +}; + +// An agent can make several calls to |AddChunk|. Chunks will be concatenated +// with no separator (type STRING) or using comma as the separator (type ARRAY). +// There should be only one agent of type STRING per agent label; otherwise +// their trace data would be mixed up. +interface Recorder { + AddChunk(string chunk); + AddMetadata(mojo.common.mojom.DictionaryValue metadata); +}; + +// A tracing controller uses this interface to coordinate trace data collection +// from all registered agents. At any given time, there should be at most one +// connected controller. +interface Coordinator { + // The return value is false if tracing is already enabled with a different + // config. Otherwise, true is returned as soon as the service receives acks + // from all existing agents and agents that connect during |StartTracing|. + StartTracing(string config) => (bool success); + StopAndFlush(handle<data_pipe_producer> stream) => ( + mojo.common.mojom.DictionaryValue metadata); + // Same as |StopAndFlush| but only write data from a certain |agent_label| to + // the |stream|. + StopAndFlushAgent(handle<data_pipe_producer> stream, string agent_label) => ( + mojo.common.mojom.DictionaryValue metadata); + IsTracing() => (bool is_tracing); + RequestBufferUsage() => (bool success, float percent_full, + uint32 approximate_count); + GetCategories() => (bool success, string categories); +}; diff --git a/chromium/services/tracing/recorder.cc b/chromium/services/tracing/recorder.cc new file mode 100644 index 00000000000..4c25d9cf549 --- /dev/null +++ b/chromium/services/tracing/recorder.cc @@ -0,0 +1,47 @@ +// 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 "services/tracing/recorder.h" + +#include <utility> + +#include "base/callback_forward.h" +#include "base/memory/ref_counted.h" +#include "services/tracing/public/mojom/tracing.mojom.h" + +namespace tracing { + +Recorder::Recorder(mojom::RecorderRequest request, + mojom::TraceDataType data_type, + const base::RepeatingClosure& on_data_change_callback) + : is_recording_(true), + data_type_(data_type), + on_data_change_callback_(on_data_change_callback), + binding_(this, std::move(request)), + weak_ptr_factory_(this) { + binding_.set_connection_error_handler(base::BindOnce( + &Recorder::OnConnectionError, weak_ptr_factory_.GetWeakPtr())); +} + +Recorder::~Recorder() = default; + +void Recorder::AddChunk(const std::string& chunk) { + if (chunk.empty()) + return; + if (data_type_ != mojom::TraceDataType::STRING && !data_.empty()) + data_.append(","); + data_.append(chunk); + on_data_change_callback_.Run(); +} + +void Recorder::AddMetadata(std::unique_ptr<base::DictionaryValue> metadata) { + metadata_.MergeDictionary(metadata.get()); +} + +void Recorder::OnConnectionError() { + is_recording_ = false; + on_data_change_callback_.Run(); +} + +} // namespace tracing diff --git a/chromium/services/tracing/recorder.h b/chromium/services/tracing/recorder.h new file mode 100644 index 00000000000..1178004ff50 --- /dev/null +++ b/chromium/services/tracing/recorder.h @@ -0,0 +1,70 @@ +// 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. + +#ifndef SERVICES_TRACING_RECORDER_H_ +#define SERVICES_TRACING_RECORDER_H_ + +#include <memory> +#include <string> + +#include "base/callback_forward.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "base/values.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "services/tracing/public/mojom/tracing.mojom.h" + +namespace tracing { + +class Recorder : public mojom::Recorder { + public: + // The tracing service creates instances of the |Recorder| class and send them + // to agents. The agents then use the recorder for sending trace data to the + // tracing service. + // + // |data_is_array| tells the recorder whether the data is of type array or + // string. Chunks of type array are concatenated using a comma as the + // separator; chuunks of type string are concatenated without a separator. + // + // |on_data_change_callback| is run whenever the recorder receives data from + // the agent or when the connection is lost to notify the tracing service of + // the data change. + Recorder(mojom::RecorderRequest request, + mojom::TraceDataType data_type, + const base::RepeatingClosure& on_data_change_callback); + ~Recorder() override; + + const std::string& data() const { return data_; } + + void clear_data() { data_.clear(); } + + const base::DictionaryValue& metadata() const { return metadata_; } + bool is_recording() const { return is_recording_; } + mojom::TraceDataType data_type() const { return data_type_; } + + private: + friend class RecorderTest; // For testing. + // mojom::Recorder + // These are called by agents for sending trace data to the tracing service. + void AddChunk(const std::string& chunk) override; + void AddMetadata(std::unique_ptr<base::DictionaryValue> metadata) override; + + void OnConnectionError(); + + std::string data_; + base::DictionaryValue metadata_; + bool is_recording_; + mojom::TraceDataType data_type_; + base::RepeatingClosure on_data_change_callback_; + mojo::Binding<mojom::Recorder> binding_; + + base::WeakPtrFactory<Recorder> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(Recorder); +}; + +} // namespace tracing +#endif // SERVICES_TRACING_RECORDER_H_ diff --git a/chromium/services/tracing/recorder_unittest.cc b/chromium/services/tracing/recorder_unittest.cc new file mode 100644 index 00000000000..9462477e3e2 --- /dev/null +++ b/chromium/services/tracing/recorder_unittest.cc @@ -0,0 +1,135 @@ +// 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 "services/tracing/recorder.h" + +#include <utility> + +#include "base/bind.h" +#include "base/callback_forward.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/threading/thread_task_runner_handle.h" +#include "base/values.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace tracing { + +class RecorderTest : public testing::Test { + public: + void SetUp() override { message_loop_.reset(new base::MessageLoop()); } + + void TearDown() override { + recorder_.reset(); + message_loop_.reset(); + } + + void CreateRecorder(mojom::RecorderRequest request, + mojom::TraceDataType data_type, + const base::Closure& callback) { + recorder_.reset(new Recorder(std::move(request), data_type, callback)); + } + + void CreateRecorder(mojom::TraceDataType data_type, + const base::Closure& callback) { + CreateRecorder(nullptr, data_type, callback); + } + + void AddChunk(const std::string& chunk) { recorder_->AddChunk(chunk); } + + void AddMetadata(std::unique_ptr<base::DictionaryValue> metadata) { + recorder_->AddMetadata(std::move(metadata)); + } + + std::unique_ptr<Recorder> recorder_; + + private: + std::unique_ptr<base::MessageLoop> message_loop_; +}; + +TEST_F(RecorderTest, AddChunkArray) { + size_t num_calls = 0; + CreateRecorder(mojom::TraceDataType::ARRAY, + base::BindRepeating([](size_t* num_calls) { (*num_calls)++; }, + base::Unretained(&num_calls))); + AddChunk("chunk1"); + AddChunk("chunk2"); + AddChunk("chunk3"); + EXPECT_EQ("chunk1,chunk2,chunk3", recorder_->data()); + + // Verify that the recorder has called the callback every time it received a + // chunk. + EXPECT_EQ(3u, num_calls); +} + +TEST_F(RecorderTest, AddChunkObject) { + size_t num_calls = 0; + CreateRecorder(mojom::TraceDataType::OBJECT, + base::BindRepeating([](size_t* num_calls) { (*num_calls)++; }, + base::Unretained(&num_calls))); + AddChunk("chunk1"); + AddChunk("chunk2"); + AddChunk("chunk3"); + + // Objects are similar to arrays. Their chunks are separated by commas. + EXPECT_EQ("chunk1,chunk2,chunk3", recorder_->data()); + + // Verify that the recorder has called the callback every time it received a + // chunk. + EXPECT_EQ(3u, num_calls); +} + +TEST_F(RecorderTest, AddChunkString) { + size_t num_calls = 0; + CreateRecorder(mojom::TraceDataType::STRING, + base::BindRepeating([](size_t* num_calls) { (*num_calls)++; }, + base::Unretained(&num_calls))); + AddChunk("chunk1"); + AddChunk("chunk2"); + AddChunk("chunk3"); + EXPECT_EQ("chunk1chunk2chunk3", recorder_->data()); + EXPECT_EQ(3u, num_calls); +} + +TEST_F(RecorderTest, AddMetadata) { + CreateRecorder(mojom::TraceDataType::ARRAY, base::BindRepeating([] {})); + + auto dict1 = std::make_unique<base::DictionaryValue>(); + dict1->SetString("network-type", "Ethernet"); + AddMetadata(std::move(dict1)); + + auto dict2 = std::make_unique<base::DictionaryValue>(); + dict2->SetString("os-name", "CrOS"); + AddMetadata(std::move(dict2)); + + EXPECT_EQ(2u, recorder_->metadata().size()); + std::string net; + EXPECT_TRUE(recorder_->metadata().GetString("network-type", &net)); + EXPECT_EQ("Ethernet", net); + std::string os; + EXPECT_TRUE(recorder_->metadata().GetString("os-name", &os)); + EXPECT_EQ("CrOS", os); +} + +TEST_F(RecorderTest, OnConnectionError) { + base::RunLoop run_loop; + size_t num_calls = 0; + { + mojom::RecorderPtr ptr; + auto request = MakeRequest(&ptr); + CreateRecorder(std::move(request), mojom::TraceDataType::STRING, + base::BindRepeating( + [](size_t* num_calls, base::Closure quit_closure) { + (*num_calls)++; + quit_closure.Run(); + }, + base::Unretained(&num_calls), run_loop.QuitClosure())); + } + // |ptr| is deleted at this point and so the recorder should notify us that + // the client is not going to send any more data by running the callback. + run_loop.Run(); + EXPECT_EQ(1u, num_calls); +} + +} // namespace tracing diff --git a/chromium/services/tracing/service_main.cc b/chromium/services/tracing/service_main.cc new file mode 100644 index 00000000000..98adace3336 --- /dev/null +++ b/chromium/services/tracing/service_main.cc @@ -0,0 +1,12 @@ +// Copyright 2018 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/service_manager/public/c/main.h" +#include "services/service_manager/public/cpp/service_runner.h" +#include "services/tracing/tracing_service.h" + +MojoResult ServiceMain(MojoHandle service_request_handle) { + return service_manager::ServiceRunner(new tracing::TracingService()) + .Run(service_request_handle); +} diff --git a/chromium/services/tracing/test_util.cc b/chromium/services/tracing/test_util.cc new file mode 100644 index 00000000000..e60d7cab14a --- /dev/null +++ b/chromium/services/tracing/test_util.cc @@ -0,0 +1,55 @@ +// 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 "services/tracing/test_util.h" + +#include <string> + +#include "services/tracing/public/mojom/tracing.mojom.h" + +namespace tracing { + +MockAgent::MockAgent() : binding_(this) {} + +MockAgent::~MockAgent() = default; + +mojom::AgentPtr MockAgent::CreateAgentPtr() { + mojom::AgentPtr agent_proxy; + binding_.Bind(mojo::MakeRequest(&agent_proxy)); + return agent_proxy; +} + +void MockAgent::StartTracing(const std::string& config, + base::TimeTicks coordinator_time, + const StartTracingCallback& cb) { + call_stat_.push_back("StartTracing"); + cb.Run(true); +} + +void MockAgent::StopAndFlush(mojom::RecorderPtr recorder) { + call_stat_.push_back("StopAndFlush"); + if (!metadata_.empty()) + recorder->AddMetadata(metadata_.CreateDeepCopy()); + for (const auto& chunk : data_) { + recorder->AddChunk(chunk); + } +} + +void MockAgent::RequestClockSyncMarker( + const std::string& sync_id, + const RequestClockSyncMarkerCallback& cb) { + call_stat_.push_back("RequestClockSyncMarker"); +} + +void MockAgent::GetCategories(const GetCategoriesCallback& cb) { + call_stat_.push_back("GetCategories"); + cb.Run(categories_); +} + +void MockAgent::RequestBufferStatus(const RequestBufferStatusCallback& cb) { + call_stat_.push_back("RequestBufferStatus"); + cb.Run(trace_log_status_.event_capacity, trace_log_status_.event_count); +} + +} // namespace tracing diff --git a/chromium/services/tracing/test_util.h b/chromium/services/tracing/test_util.h new file mode 100644 index 00000000000..082b4de47a0 --- /dev/null +++ b/chromium/services/tracing/test_util.h @@ -0,0 +1,55 @@ +// 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. + +#ifndef SERVICES_TRACING_TEST_UTIL_H_ +#define SERVICES_TRACING_TEST_UTIL_H_ + +#include <string> +#include <vector> + +#include "base/trace_event/trace_log.h" +#include "base/values.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "services/tracing/public/mojom/tracing.mojom.h" + +namespace base { +class TimeTicks; +} // namespace base + +namespace tracing { + +class MockAgent : public mojom::Agent { + public: + MockAgent(); + ~MockAgent() override; + + mojom::AgentPtr CreateAgentPtr(); + + std::vector<std::string> call_stat() const { return call_stat_; } + + // Set these variables to configure the agent. + std::vector<std::string> data_; + base::DictionaryValue metadata_; + std::string categories_; + base::trace_event::TraceLogStatus trace_log_status_; + + private: + // mojom::Agent + void StartTracing(const std::string& config, + base::TimeTicks coordinator_time, + const StartTracingCallback& cb) override; + void StopAndFlush(mojom::RecorderPtr recorder) override; + void RequestClockSyncMarker( + const std::string& sync_id, + const RequestClockSyncMarkerCallback& cb) override; + void GetCategories(const GetCategoriesCallback& cb) override; + void RequestBufferStatus(const RequestBufferStatusCallback& cb) override; + + mojo::Binding<mojom::Agent> binding_; + std::vector<std::string> call_stat_; +}; + +} // namespace tracing + +#endif // SERVICES_TRACING_TEST_UTIL_H_ diff --git a/chromium/services/tracing/tracing_service.cc b/chromium/services/tracing/tracing_service.cc new file mode 100644 index 00000000000..701ea444cda --- /dev/null +++ b/chromium/services/tracing/tracing_service.cc @@ -0,0 +1,47 @@ +// Copyright 2018 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/tracing_service.h" + +#include <utility> + +#include "base/timer/timer.h" +#include "services/service_manager/public/cpp/service_context.h" +#include "services/tracing/agent_registry.h" +#include "services/tracing/coordinator.h" + +namespace tracing { + +std::unique_ptr<service_manager::Service> TracingService::Create() { + return std::make_unique<TracingService>(); +} + +TracingService::TracingService() : weak_factory_(this) {} + +TracingService::~TracingService() = default; + +void TracingService::OnStart() { + ref_factory_.reset(new service_manager::ServiceContextRefFactory( + context()->CreateQuitClosure())); + + tracing_agent_registry_ = std::make_unique<AgentRegistry>(ref_factory_.get()); + registry_.AddInterface( + base::BindRepeating(&AgentRegistry::BindAgentRegistryRequest, + base::Unretained(tracing_agent_registry_.get()))); + + tracing_coordinator_ = std::make_unique<Coordinator>(ref_factory_.get()); + registry_.AddInterface( + base::BindRepeating(&Coordinator::BindCoordinatorRequest, + base::Unretained(tracing_coordinator_.get()))); +} + +void TracingService::OnBindInterface( + const service_manager::BindSourceInfo& source_info, + const std::string& interface_name, + mojo::ScopedMessagePipeHandle interface_pipe) { + registry_.BindInterface(interface_name, std::move(interface_pipe), + source_info); +} + +} // namespace tracing diff --git a/chromium/services/tracing/tracing_service.h b/chromium/services/tracing/tracing_service.h new file mode 100644 index 00000000000..e70a29027fa --- /dev/null +++ b/chromium/services/tracing/tracing_service.h @@ -0,0 +1,58 @@ +// Copyright 2018 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. + +#ifndef SERVICES_TRACING_TRACING_SERVICE_H_ +#define SERVICES_TRACING_TRACING_SERVICE_H_ + +#include <memory> +#include <string> + +#include "base/callback.h" +#include "base/macros.h" +#include "base/memory/weak_ptr.h" +#include "services/service_manager/public/cpp/binder_registry.h" +#include "services/service_manager/public/cpp/service.h" +#include "services/service_manager/public/cpp/service_context_ref.h" +#include "services/tracing/agent_registry.h" +#include "services/tracing/coordinator.h" + +namespace tracing { + +class TracingService : public service_manager::Service { + public: + TracingService(); + ~TracingService() override; + + // service_manager::Service: + // Factory function for use as an embedded service. + static std::unique_ptr<service_manager::Service> Create(); + + // service_manager::Service: + void OnStart() override; + void OnBindInterface(const service_manager::BindSourceInfo& source_info, + const std::string& interface_name, + mojo::ScopedMessagePipeHandle interface_pipe) override; + + service_manager::ServiceContextRefFactory* ref_factory() { + return ref_factory_.get(); + } + + private: + service_manager::BinderRegistryWithArgs< + const service_manager::BindSourceInfo&> + registry_; + std::unique_ptr<tracing::AgentRegistry> tracing_agent_registry_; + std::unique_ptr<tracing::Coordinator> tracing_coordinator_; + std::unique_ptr<service_manager::ServiceContextRefFactory> ref_factory_; + + // WeakPtrFactory members should always come last so WeakPtrs are destructed + // before other members. + base::WeakPtrFactory<TracingService> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(TracingService); +}; + +} // namespace tracing + +#endif // SERVICES_TRACING_TRACING_SERVICE_H_ diff --git a/chromium/services/tracing/tracing_service_unittest.cc b/chromium/services/tracing/tracing_service_unittest.cc new file mode 100644 index 00000000000..b71fc43ad46 --- /dev/null +++ b/chromium/services/tracing/tracing_service_unittest.cc @@ -0,0 +1,52 @@ +// Copyright 2018 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 <memory> + +#include "base/macros.h" +#include "base/run_loop.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "services/service_manager/public/cpp/service.h" +#include "services/service_manager/public/cpp/service_test.h" +#include "services/tracing/public/mojom/constants.mojom.h" +#include "services/tracing/public/mojom/tracing.mojom.h" + +namespace tracing { + +class TracingServiceTest : public service_manager::test::ServiceTest { + public: + TracingServiceTest() + : service_manager::test::ServiceTest("tracing_unittests") {} + ~TracingServiceTest() override {} + + protected: + void SetUp() override { + service_manager::test::ServiceTest::SetUp(); + connector()->StartService(mojom::kServiceName); + } + + void SetRunLoopToQuit(base::RunLoop* loop) { loop_ = loop; } + + private: + base::RunLoop* loop_ = nullptr; + + DISALLOW_COPY_AND_ASSIGN(TracingServiceTest); +}; + +TEST_F(TracingServiceTest, TracingServiceInstantiate) { + mojom::AgentRegistryPtr agent_registry; + connector()->BindInterface(mojom::kServiceName, + mojo::MakeRequest(&agent_registry)); + + tracing::mojom::AgentPtr agent; + agent_registry->RegisterAgent(std::move(agent), "FOO", + mojom::TraceDataType::STRING, + false /*supports_explicit_clock_sync*/); + + base::RunLoop loop; + SetRunLoopToQuit(&loop); + loop.Run(); +} + +} // namespace tracing diff --git a/chromium/services/tracing/unittest_manifest.json b/chromium/services/tracing/unittest_manifest.json new file mode 100644 index 00000000000..311385fd3ec --- /dev/null +++ b/chromium/services/tracing/unittest_manifest.json @@ -0,0 +1,11 @@ +{ + "name": "tracing_unittests", + "display_name": "Tracing Unittests", + "interface_provider_specs": { + "service_manager:connector": { + "requires": { + "tracing": [ "tests" ] + } + } + } +} |