blob: a221737ea1068899860bed3e560886da6cc1943b [file] [log] [blame]
// 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/perfetto/test_utils.h"
#include <utility>
#include "base/bind.h"
#include "base/run_loop.h"
#include "base/tracing/perfetto_platform.h"
#include "services/tracing/public/cpp/perfetto/shared_memory.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/perfetto/include/perfetto/ext/tracing/core/commit_data_request.h"
#include "third_party/perfetto/include/perfetto/ext/tracing/core/trace_packet.h"
#include "third_party/perfetto/include/perfetto/ext/tracing/core/trace_writer.h"
#include "third_party/perfetto/include/perfetto/tracing/core/trace_config.h"
#include "third_party/perfetto/protos/perfetto/trace/test_event.pbzero.h"
#include "third_party/perfetto/protos/perfetto/trace/trace_packet.pb.h"
#include "third_party/perfetto/protos/perfetto/trace/trace_packet.pbzero.h"
namespace tracing {
namespace {
perfetto::TraceConfig GetDefaultTraceConfig(
const std::vector<std::string>& data_sources) {
perfetto::TraceConfig trace_config;
trace_config.add_buffers()->set_size_kb(1024 * 32);
for (const auto& data_source : data_sources) {
auto* ds_config = trace_config.add_data_sources()->mutable_config();
ds_config->set_name(data_source);
ds_config->set_target_buffer(0);
}
return trace_config;
}
} // namespace
// static
std::unique_ptr<TestDataSource> TestDataSource::CreateAndRegisterDataSource(
const std::string& data_source_name,
size_t send_packet_count) {
auto data_source = std::unique_ptr<TestDataSource>(
new TestDataSource(data_source_name, send_packet_count));
PerfettoTracedProcess::Get()->AddDataSource(data_source.get());
return data_source;
}
TestDataSource::TestDataSource(const std::string& data_source_name,
size_t send_packet_count)
: DataSourceBase(data_source_name), send_packet_count_(send_packet_count) {
}
TestDataSource::~TestDataSource() = default;
void TestDataSource::WritePacketBigly() {
std::unique_ptr<char[]> payload(new char[kLargeMessageSize]);
memset(payload.get(), '.', kLargeMessageSize);
payload.get()[kLargeMessageSize - 1] = 0;
std::unique_ptr<perfetto::TraceWriter> writer =
producer_->CreateTraceWriter(config_.target_buffer());
CHECK(writer);
writer->NewTracePacket()->set_for_testing()->set_str(payload.get(),
kLargeMessageSize);
}
void TestDataSource::StartTracingImpl(
PerfettoProducer* producer,
const perfetto::DataSourceConfig& data_source_config) {
producer_ = producer;
config_ = data_source_config;
if (send_packet_count_ > 0) {
std::unique_ptr<perfetto::TraceWriter> writer =
producer_->CreateTraceWriter(config_.target_buffer());
CHECK(writer);
for (size_t i = 0; i < send_packet_count_; i++) {
writer->NewTracePacket()->set_for_testing()->set_str(kPerfettoTestString);
}
}
if (!start_tracing_callback_.is_null()) {
std::move(start_tracing_callback_).Run();
}
}
void TestDataSource::StopTracingImpl(base::OnceClosure stop_complete_callback) {
CHECK(producer_);
producer_ = nullptr;
std::move(stop_complete_callback).Run();
}
void TestDataSource::Flush(base::RepeatingClosure flush_complete_callback) {
if (flush_complete_callback) {
flush_complete_callback.Run();
}
}
void TestDataSource::set_start_tracing_callback(
base::OnceClosure start_tracing_callback) {
start_tracing_callback_ = std::move(start_tracing_callback);
}
MockProducerClient::MockProducerClient(
uint32_t num_data_sources,
base::OnceClosure client_enabled_callback,
base::OnceClosure client_disabled_callback)
: ProducerClient(PerfettoTracedProcess::Get()->GetTaskRunner()),
num_data_sources_expected_(num_data_sources),
client_enabled_callback_(std::move(client_enabled_callback)),
client_disabled_callback_(std::move(client_disabled_callback)) {
// Create SMB immediately since we never call ProducerClient's Connect().
CHECK(InitSharedMemoryIfNeeded());
}
MockProducerClient::~MockProducerClient() = default;
// static
std::unique_ptr<MockProducerClient::Handle> MockProducerClient::Create(
uint32_t num_data_sources,
base::OnceClosure client_enabled_callback,
base::OnceClosure client_disabled_callback) {
std::unique_ptr<MockProducerClient> client(new MockProducerClient(
num_data_sources, std::move(client_enabled_callback),
std::move(client_disabled_callback)));
auto handle = std::make_unique<Handle>(client.get());
// Transfer ownership of the client to PerfettoTracedProcess.
PerfettoTracedProcess::Get()
->GetTaskRunner()
->GetOrCreateTaskRunner()
->PostTask(
FROM_HERE,
base::BindOnce(
[](std::unique_ptr<MockProducerClient> client) {
auto* raw_client = client.get();
raw_client->old_producer_ =
PerfettoTracedProcess::Get()->SetProducerClientForTesting(
std::move(client));
},
std::move(client)));
return handle;
}
MockProducerClient::Handle::~Handle() {
// Replace the previous client in PerfettoTracedProcess, deleting the mock
// as a side-effect.
PerfettoTracedProcess::Get()
->GetTaskRunner()
->GetOrCreateTaskRunner()
->PostTask(
FROM_HERE,
base::BindOnce(
[](std::unique_ptr<ProducerClient> old_producer) {
PerfettoTracedProcess::Get()->SetProducerClientForTesting(
std::move(old_producer));
},
std::move(client_->old_producer_)));
}
void MockProducerClient::SetupDataSource(const std::string& data_source_name) {}
void MockProducerClient::StartDataSource(
uint64_t id,
const perfetto::DataSourceConfig& data_source_config,
StartDataSourceCallback callback) {
ProducerClient::StartDataSource(id, std::move(data_source_config),
std::move(callback));
CHECK_LT(num_data_sources_active_, num_data_sources_expected_);
if (++num_data_sources_active_ == num_data_sources_expected_ &&
client_enabled_callback_) {
std::move(client_enabled_callback_).Run();
}
}
void MockProducerClient::StopDataSource(uint64_t id,
StopDataSourceCallback callback) {
ProducerClient::StopDataSource(id, std::move(callback));
CHECK_GT(num_data_sources_active_, 0u);
if (--num_data_sources_active_ == 0 && client_disabled_callback_) {
std::move(client_disabled_callback_).Run();
}
}
void MockProducerClient::CommitData(const perfetto::CommitDataRequest& commit,
CommitDataCallback callback) {
// Only write out commits that have actual data in it; Perfetto
// might send two commits from different threads (one always empty),
// which causes TSan to complain.
if (commit.chunks_to_patch_size() || commit.chunks_to_move_size()) {
all_client_commit_data_requests_.push_back(commit.SerializeAsString());
}
ProducerClient::CommitData(commit, callback);
}
void MockProducerClient::SetAgentEnabledCallback(
base::OnceClosure client_enabled_callback) {
client_enabled_callback_ = std::move(client_enabled_callback);
}
void MockProducerClient::SetAgentDisabledCallback(
base::OnceClosure client_disabled_callback) {
client_disabled_callback_ = std::move(client_disabled_callback);
}
MockConsumer::MockConsumer(std::vector<std::string> data_source_names,
perfetto::TracingService* service,
PacketReceivedCallback packet_received_callback)
: MockConsumer(data_source_names,
service,
std::move(packet_received_callback),
GetDefaultTraceConfig(data_source_names)) {}
MockConsumer::MockConsumer(std::vector<std::string> data_source_names,
perfetto::TracingService* service,
PacketReceivedCallback packet_received_callback,
const perfetto::TraceConfig& config)
: packet_received_callback_(packet_received_callback),
trace_config_(config) {
for (const auto& source : data_source_names) {
data_sources_.emplace_back(DataSourceStatus{
source,
perfetto::ObservableEvents::DATA_SOURCE_INSTANCE_STATE_STOPPED});
}
CHECK(!data_sources_.empty());
consumer_endpoint_ = service->ConnectConsumer(this, /*uid=*/0);
CHECK(consumer_endpoint_);
}
MockConsumer::~MockConsumer() = default;
void MockConsumer::ReadBuffers() {
CHECK(consumer_endpoint_);
consumer_endpoint_->ReadBuffers();
}
void MockConsumer::StopTracing() {
ReadBuffers();
CHECK(consumer_endpoint_);
consumer_endpoint_->DisableTracing();
}
void MockConsumer::StartTracing() {
CHECK(consumer_endpoint_);
consumer_endpoint_->EnableTracing(trace_config_);
}
void MockConsumer::FreeBuffers() {
CHECK(consumer_endpoint_);
consumer_endpoint_->FreeBuffers();
}
void MockConsumer::OnConnect() {
consumer_endpoint_->ObserveEvents(
perfetto::ObservableEvents::TYPE_DATA_SOURCES_INSTANCES);
StartTracing();
}
void MockConsumer::OnDisconnect() {}
void MockConsumer::OnTracingDisabled(const std::string& error) {}
void MockConsumer::OnTraceData(std::vector<perfetto::TracePacket> packets,
bool has_more) {
for (auto& encoded_packet : packets) {
perfetto::protos::TracePacket packet;
EXPECT_TRUE(packet.ParseFromString(encoded_packet.GetRawBytesForTesting()));
++received_packets_;
if (packet.for_testing().str() == kPerfettoTestString) {
++received_test_packets_;
}
}
packet_received_callback_(has_more);
}
void MockConsumer::OnDetach(bool /*success*/) {}
void MockConsumer::OnAttach(bool /*success*/, const perfetto::TraceConfig&) {}
void MockConsumer::OnTraceStats(bool /*success*/, const perfetto::TraceStats&) {
}
void MockConsumer::OnObservableEvents(
const perfetto::ObservableEvents& events) {
for (const auto& change : events.instance_state_changes()) {
for (auto& data_source_status : data_sources_) {
if (change.data_source_name() != data_source_status.name) {
continue;
}
data_source_status.state = change.state();
}
CheckForAllDataSourcesStarted();
CheckForAllDataSourcesStopped();
}
}
void MockConsumer::WaitForAllDataSourcesStarted() {
base::RunLoop on_started;
on_started_runloop_ = &on_started;
CheckForAllDataSourcesStarted();
if (on_started_runloop_) {
on_started_runloop_->Run();
}
}
void MockConsumer::WaitForAllDataSourcesStopped() {
base::RunLoop on_stopped;
on_stopped_runloop_ = &on_stopped;
CheckForAllDataSourcesStopped();
if (on_stopped_runloop_) {
on_stopped_runloop_->Run();
}
}
void MockConsumer::CheckForAllDataSourcesStarted() {
for (auto& data_source_status : data_sources_) {
if (data_source_status.state !=
perfetto::ObservableEvents::DATA_SOURCE_INSTANCE_STATE_STARTED) {
return;
}
}
if (on_started_runloop_) {
on_started_runloop_->Quit();
on_started_runloop_ = nullptr;
}
}
void MockConsumer::CheckForAllDataSourcesStopped() {
for (auto& data_source_status : data_sources_) {
if (data_source_status.state !=
perfetto::ObservableEvents::DATA_SOURCE_INSTANCE_STATE_STOPPED) {
return;
}
}
if (on_stopped_runloop_) {
on_stopped_runloop_->Quit();
on_stopped_runloop_ = nullptr;
}
}
MockProducerHost::MockProducerHost(
const std::string& producer_name,
const std::string& data_source_name,
PerfettoService* service,
MockProducerClient* producer_client,
base::OnceClosure datasource_registered_callback)
: ProducerHost(service->perfetto_task_runner()),
producer_name_(producer_name),
datasource_registered_callback_(
std::move(datasource_registered_callback)) {
mojo::PendingRemote<mojom::ProducerClient> client;
mojo::PendingRemote<mojom::ProducerHost> host_remote;
auto client_receiver = client.InitWithNewPipeAndPassReceiver();
Initialize(std::move(client), service->GetService(), producer_name_,
static_cast<MojoSharedMemory*>(
producer_client->shared_memory_for_testing())
->Clone(),
PerfettoProducer::kSMBPageSizeBytes);
receiver_.Bind(host_remote.InitWithNewPipeAndPassReceiver());
producer_client->BindClientAndHostPipesForTesting(std::move(client_receiver),
std::move(host_remote));
producer_client->SetupDataSource(data_source_name);
}
MockProducerHost::~MockProducerHost() = default;
void MockProducerHost::RegisterDataSource(
const perfetto::DataSourceDescriptor& registration_info) {
ProducerHost::RegisterDataSource(registration_info);
on_commit_callback_for_testing_ =
base::BindRepeating(&MockProducerHost::OnCommit, base::Unretained(this));
if (datasource_registered_callback_) {
std::move(datasource_registered_callback_).Run();
}
}
void MockProducerHost::OnConnect() {}
void MockProducerHost::OnCommit(
const perfetto::CommitDataRequest& commit_data_request) {
if (!commit_data_request.chunks_to_patch_size() &&
!commit_data_request.chunks_to_move_size()) {
return;
}
all_host_commit_data_requests_.push_back(
commit_data_request.SerializeAsString());
}
MockProducer::MockProducer(const std::string& producer_name,
const std::string& data_source_name,
PerfettoService* service,
base::OnceClosure on_datasource_registered,
base::OnceClosure on_tracing_started,
size_t num_packets) {
// Construct MockProducerClient before TestDataSource to avoid a race.
//
// TestDataSource calls AddDataSource on the global PerfettoTracedProcess,
// which PostTasks to the threadpool in the task it will access the
// |producer_client_| pointer that the PerfettoTracedProcess owns. However in
// the constructor for MockProducerClient we will set the |producer_client_|
// from the real client to the mock, however this is done on a different
// sequence and thus we have a race. By setting the pointer before we
// construct the data source the TestDataSource can not race.
producer_client_ = MockProducerClient::Create(
/* num_data_sources = */ 1, std::move(on_tracing_started));
data_source_ = TestDataSource::CreateAndRegisterDataSource(data_source_name,
num_packets);
producer_host_ = std::make_unique<MockProducerHost>(
producer_name, data_source_name, service, **producer_client_,
std::move(on_datasource_registered));
}
MockProducer::~MockProducer() = default;
void MockProducer::WritePacketBigly(base::OnceClosure on_write_complete) {
PerfettoTracedProcess::Get()
->GetTaskRunner()
->GetOrCreateTaskRunner()
->PostTaskAndReply(FROM_HERE,
base::BindOnce(&TestDataSource::WritePacketBigly,
base::Unretained(data_source())),
std::move(on_write_complete));
}
TracingUnitTest::TracingUnitTest()
: task_environment_(std::make_unique<base::test::TaskEnvironment>(
base::test::TaskEnvironment::MainThreadType::IO)),
tracing_environment_(std::make_unique<base::test::TracingEnvironment>(
*task_environment_,
base::ThreadTaskRunnerHandle::Get(),
PerfettoTracedProcess::Get()->perfetto_platform_for_testing())) {}
TracingUnitTest::~TracingUnitTest() {
CHECK(setup_called_ && teardown_called_);
}
void TracingUnitTest::SetUp() {
setup_called_ = true;
// Also tell PerfettoTracedProcess to use the current task environment.
test_handle_ = PerfettoTracedProcess::SetupForTesting(
base::ThreadTaskRunnerHandle::Get());
PerfettoTracedProcess::Get()->OnThreadPoolAvailable(
/* enable_consumer */ true);
// Wait for any posted construction tasks to execute.
RunUntilIdle();
}
void TracingUnitTest::TearDown() {
teardown_called_ = true;
tracing_environment_.reset();
// Wait for any posted destruction tasks to execute.
RunUntilIdle();
// From here on, no more tasks should be posted.
task_environment_.reset();
// Clear task runner and data sources.
PerfettoTracedProcess::Get()->GetTaskRunner()->ResetTaskRunnerForTesting(
nullptr);
PerfettoTracedProcess::Get()->ClearDataSourcesForTesting();
test_handle_.reset();
}
} // namespace tracing