blob: 7e4e342d1dfcb77e62ec1b0eeccd91c29073252e [file] [log] [blame]
// Copyright 2022 The Fuchsia 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 <lib/async-loop/default.h>
#include <lib/async/cpp/task.h>
#include <lib/async/dispatcher.h>
#include <lib/fidl/cpp/wire/internal/transport.h>
#include <lib/fit/function.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/trace-engine/instrumentation.h>
#include <lib/trace-provider/provider.h>
#include <lib/zx/time.h>
#include <deque>
#include <optional>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <perfetto/ext/base/thread_task_runner.h>
#include <perfetto/ext/tracing/core/consumer.h>
#include <perfetto/ext/tracing/core/trace_packet.h>
#include <perfetto/ext/tracing/core/tracing_service.h>
#include <perfetto/tracing/platform.h>
#include <rapidjson/document.h>
#include "src/lib/testing/loop_fixture/test_loop_fixture.h"
#include "src/performance/perfetto-bridge/consumer_adapter.h"
#include <third_party/perfetto/protos/perfetto/common/trace_stats.gen.h>
#include <third_party/perfetto/protos/perfetto/config/chrome/chrome_config.gen.h>
#include <third_party/perfetto/protos/perfetto/config/data_source_config.gen.h>
#include <third_party/perfetto/protos/perfetto/config/trace_config.gen.h>
#include <third_party/perfetto/protos/perfetto/config/track_event/track_event_config.gen.h>
using testing::ElementsAre;
namespace {
constexpr size_t kTestBufSize = 100;
constexpr size_t kTestBufUtilizationThreshold =
static_cast<size_t>(kTestBufSize * ConsumerAdapter::kConsumerUtilizationReadThreshold);
// Adapts Perfetto's TaskRunner interface to use a Zircon dispatcher.
class TestPerfettoTaskRunner : public perfetto::base::TaskRunner {
public:
explicit TestPerfettoTaskRunner(async_dispatcher_t* dispatcher) : dispatcher_(dispatcher) {
FX_DCHECK(dispatcher_);
}
~TestPerfettoTaskRunner() override = default;
TestPerfettoTaskRunner(TestPerfettoTaskRunner&) = delete;
void operator=(TestPerfettoTaskRunner&) = delete;
// TaskRunner implementation.
void PostTask(std::function<void()> closure) override {
FX_CHECK(async::PostTask(dispatcher_, std::move(closure)) == ZX_OK);
}
void PostDelayedTask(std::function<void()> closure, uint32_t delay_ms) override {
FX_CHECK(async::PostDelayedTask(dispatcher_, closure, zx::msec(delay_ms)) == ZX_OK);
}
void AddFileDescriptorWatch(perfetto::base::PlatformHandle, std::function<void()>) override {}
void RemoveFileDescriptorWatch(perfetto::base::PlatformHandle) override {}
bool RunsTasksOnCurrentThread() const override { return true; }
private:
async_dispatcher_t* dispatcher_;
};
// Simulates interactions with Perfetto's ConsumerEndpoint.
// Behavior is GMock-like, except that expectations can be set as needed and not
// necessarily up front, and there is additional logic for capturing the TraceConfig.
class FakeConsumerEndpoint : public perfetto::ConsumerEndpoint {
public:
enum class CallType {
ENABLE_TRACING,
START_TRACING,
DISABLE_TRACING,
FLUSH,
READ_BUFFERS,
GET_TRACE_STATS,
};
// |on_destroy_cb| is invoked on destruction.
explicit FakeConsumerEndpoint(fit::closure on_destroy_cb)
: on_destroy_cb_(std::move(on_destroy_cb)) {
// The endpoint is immediately invoked with ENABLE_TRACING.
ExpectCall(CallType::ENABLE_TRACING);
}
~FakeConsumerEndpoint() override {
FX_CHECK(expected_calls_.empty());
on_destroy_cb_();
}
// Trace configuration, stored from EnableTracing.
const perfetto::TraceConfig& config() { return config_; }
// Flush completion callback.
const std::function<void(bool)>& flush_cb() { return flush_cb_; }
// Registers an expectation for a call to ConsumerEndpoint.
void ExpectCall(CallType type) { expected_calls_.push_back(type); }
private:
void RecordObservedCall(CallType type) {
FX_CHECK(!expected_calls_.empty() && expected_calls_.front() == type)
<< "Unexpected call: " << static_cast<int>(type);
expected_calls_.pop_front();
}
// perfetto::ConsumerEndpoint implementation.
void EnableTracing(const perfetto::TraceConfig& config, perfetto::base::ScopedFile) override {
config_ = config;
RecordObservedCall(CallType::ENABLE_TRACING);
}
void StartTracing() override { RecordObservedCall(CallType::START_TRACING); }
void DisableTracing() override { RecordObservedCall(CallType::DISABLE_TRACING); }
void Flush(uint32_t timeout_ms, FlushCallback callback, perfetto::FlushFlags flags) override {
flush_cb_ = callback;
RecordObservedCall(CallType::FLUSH);
}
void ReadBuffers() override { RecordObservedCall(CallType::READ_BUFFERS); }
void GetTraceStats() override { RecordObservedCall(CallType::GET_TRACE_STATS); }
// Unused methods from perfetto::ConsumerEndpoint.
void ChangeTraceConfig(const perfetto::TraceConfig&) override { FX_NOTREACHED(); }
void FreeBuffers() override { FX_NOTREACHED(); }
void Detach(const std::string& key) override { FX_NOTREACHED(); }
void Attach(const std::string& key) override { FX_NOTREACHED(); }
void ObserveEvents(uint32_t events_mask) override { FX_NOTREACHED(); }
void QueryServiceState(QueryServiceStateArgs, QueryServiceStateCallback) override {
FX_NOTREACHED();
}
void QueryCapabilities(QueryCapabilitiesCallback) override { FX_NOTREACHED(); }
void SaveTraceForBugreport(SaveTraceForBugreportCallback) override { FX_NOTREACHED(); }
void CloneSession(CloneSessionArgs) override { FX_NOTREACHED(); }
perfetto::TraceConfig config_;
std::deque<CallType> expected_calls_;
std::function<void(bool)> flush_cb_;
fit::closure on_destroy_cb_;
};
// Simulates interactions with the Fuchsia trace controller/observer
// and ensures that contexts are managed properly.
class FakeFuchsiaTracing : public FuchsiaTracing {
public:
FakeFuchsiaTracing() = default;
~FakeFuchsiaTracing() override = default;
FakeFuchsiaTracing(FakeFuchsiaTracing&) = delete;
void operator=(FakeFuchsiaTracing&) = delete;
void ChangeTraceState(trace_state_t state) {
FX_CHECK(observe_cb_);
observe_cb_(state);
}
trace::ProviderConfig* provider_config() { return &provider_config_; }
const trace::GetKnownCategoriesCallback& get_known_categories_callback() {
return get_known_categories_callback_;
}
const std::vector<std::string>& GetReceivedBlobs() { return blobs_; }
private:
// FuchsiaTracing implementation.
void StartObserving(fit::function<void(trace_state_t)> observe_cb) override {
FX_CHECK(observe_cb);
FX_CHECK(!observe_cb_);
observe_cb_ = std::move(observe_cb);
}
void AcquireProlongedContext() override {
FX_CHECK(!has_prolonged_context_);
has_prolonged_context_ = true;
}
void ReleaseProlongedContext() override {
FX_CHECK(has_prolonged_context_);
has_prolonged_context_ = false;
}
void AcquireWriteContext() override {
FX_CHECK(!has_write_context_);
has_write_context_ = true;
}
bool HasWriteContext() override { return has_write_context_; }
void WriteBlob(const char* data, size_t size) override {
FX_CHECK(HasWriteContext());
blobs_.emplace_back(data, size);
}
void ReleaseWriteContext() override { has_write_context_ = false; }
trace::ProviderConfig GetProviderConfig() override { return provider_config_; }
void SetGetKnownCategoriesCallback(trace::GetKnownCategoriesCallback callback) override {
get_known_categories_callback_ = std::move(callback);
}
fit::function<void(trace_state_t)> observe_cb_;
bool has_write_context_ = false;
bool has_prolonged_context_ = false;
std::vector<std::string> blobs_;
trace::ProviderConfig provider_config_;
trace::GetKnownCategoriesCallback get_known_categories_callback_;
};
class ConsumerAdapterTest : public gtest::TestLoopFixture {
public:
ConsumerAdapterTest() : perfetto_task_runner_(dispatcher()) {
auto fuchsia_tracing_owned = std::make_unique<FakeFuchsiaTracing>();
fuchsia_tracing_ = fuchsia_tracing_owned.get();
consumer_adapter_.emplace(
[this](perfetto::Consumer* consumer) { return ConnectConsumer(consumer); },
std::move(fuchsia_tracing_owned), &perfetto_task_runner_);
}
~ConsumerAdapterTest() override {
consumer_adapter_.reset();
RunLoopUntilIdle();
}
void WaitForGetStats() { RunLoopFor(zx::sec(1)); }
std::unique_ptr<perfetto::ConsumerEndpoint> ConnectConsumer(perfetto::Consumer* consumer) {
zx_status_t status = async::PostTask(dispatcher(), [this, consumer]() {
FX_DCHECK(consumer);
consumer_ = consumer;
});
FX_DCHECK(status == ZX_OK) << "Error calling PostTask: " << zx_status_get_string(status);
std::unique_ptr mock_endpoint =
std::make_unique<FakeConsumerEndpoint>([this]() { mock_endpoint_ = nullptr; });
mock_endpoint_ = reinterpret_cast<FakeConsumerEndpoint*>(mock_endpoint.get());
return mock_endpoint;
}
FakeFuchsiaTracing* fuchsia_tracing_;
TestPerfettoTaskRunner perfetto_task_runner_;
FakeConsumerEndpoint* mock_endpoint_ = nullptr;
perfetto::Consumer* consumer_ = nullptr;
std::optional<ConsumerAdapter> consumer_adapter_;
};
perfetto::TraceStats MakeTraceStats(size_t buffer_size, size_t bytes_written, size_t bytes_read,
size_t bytes_overwritten) {
perfetto::TraceStats stats;
auto* buffer_stats = stats.add_buffer_stats();
buffer_stats->set_buffer_size(buffer_size);
buffer_stats->set_bytes_written(bytes_written);
buffer_stats->set_bytes_read(bytes_read);
buffer_stats->set_bytes_overwritten(bytes_overwritten);
return stats;
}
struct ExpectedDataSource {
std::string name;
std::optional<std::string> provider_filter;
std::vector<std::string> enabled_categories;
};
TEST_F(ConsumerAdapterTest, TraceConfig) {
ASSERT_FALSE(mock_endpoint_);
fuchsia_tracing_->provider_config()->categories = {"foo*", "bar", "provider1/category1",
"provider1/category2", "provider2/categoryX"};
fuchsia_tracing_->ChangeTraceState(TRACE_STARTED);
RunLoopUntilIdle();
ASSERT_TRUE(mock_endpoint_);
const std::vector<ExpectedDataSource> kExpectedDataSources = {
{.name = "track_event", .enabled_categories = {"foo*", "bar"}},
{.name = "track_event", .provider_filter = "provider2", .enabled_categories = {"categoryX"}},
{.name = "track_event",
.provider_filter = "provider1",
.enabled_categories = {"category1", "category2"}},
{.name = "org.chromium.trace_event", .enabled_categories = {"foo*", "bar"}},
{.name = "org.chromium.trace_event",
.provider_filter = "provider2",
.enabled_categories = {"categoryX"}},
{.name = "org.chromium.trace_event",
.provider_filter = "provider1",
.enabled_categories = {"category1", "category2"}},
};
const auto& actual_data_sources = mock_endpoint_->config().data_sources();
ASSERT_EQ(kExpectedDataSources.size(), actual_data_sources.size());
for (size_t i = 0; i < kExpectedDataSources.size(); ++i) {
const auto& expected_data_source = kExpectedDataSources[i];
const auto& actual_data_source = actual_data_sources[i];
ASSERT_EQ(expected_data_source.name, actual_data_source.config().name());
if (expected_data_source.provider_filter) {
EXPECT_EQ(std::vector{expected_data_source.provider_filter.value()},
actual_data_source.producer_name_filter());
}
if (expected_data_source.name == "track_event") {
perfetto::protos::gen::TrackEventConfig track_event_config;
ASSERT_TRUE(
track_event_config.ParseFromString(actual_data_source.config().track_event_config_raw()));
EXPECT_EQ(expected_data_source.enabled_categories, track_event_config.enabled_categories());
EXPECT_THAT(track_event_config.disabled_categories(), ElementsAre("*"));
} else if (expected_data_source.name == "org.chromium.trace_event") {
const auto& chrome_config = actual_data_source.config().chrome_config();
rapidjson::Document chrome_config_parsed;
FX_LOGS(INFO) << chrome_config.trace_config().data();
chrome_config_parsed.Parse(chrome_config.trace_config().data(),
chrome_config.trace_config().size());
ASSERT_FALSE(chrome_config_parsed.HasParseError());
ASSERT_TRUE(chrome_config_parsed["included_categories"].IsArray());
auto included_categories = chrome_config_parsed["included_categories"].GetArray();
std::vector<std::string> included_categories_vector;
for (const auto& included_category : included_categories) {
included_categories_vector.emplace_back(included_category.GetString());
}
EXPECT_EQ(expected_data_source.enabled_categories, included_categories_vector);
ASSERT_TRUE(chrome_config_parsed["excluded_categories"].IsArray());
const auto excluded_categories = chrome_config_parsed["excluded_categories"].GetArray();
EXPECT_EQ(excluded_categories.Size(), 1u);
EXPECT_EQ(std::string(excluded_categories[0].GetString()), "*");
} else {
ASSERT_TRUE(false) << "got an unexpected data source name";
}
}
}
TEST_F(ConsumerAdapterTest, BufferUtilizationTriggersRead) {
ASSERT_FALSE(mock_endpoint_);
fuchsia_tracing_->ChangeTraceState(TRACE_STARTED);
RunLoopUntilIdle();
ASSERT_TRUE(mock_endpoint_);
// 0% utilization - no flush.
mock_endpoint_->ExpectCall(FakeConsumerEndpoint::CallType::GET_TRACE_STATS);
WaitForGetStats();
RunLoopUntilIdle();
consumer_->OnTraceStats(true, MakeTraceStats(kTestBufSize, 0, 0, 0));
RunLoopUntilIdle();
// Just below the read threshold - no flush.
mock_endpoint_->ExpectCall(FakeConsumerEndpoint::CallType::GET_TRACE_STATS);
WaitForGetStats();
RunLoopUntilIdle();
consumer_->OnTraceStats(true,
MakeTraceStats(kTestBufSize, kTestBufUtilizationThreshold - 1, 0, 0));
// We've hit the read threshold - flush it!
mock_endpoint_->ExpectCall(FakeConsumerEndpoint::CallType::GET_TRACE_STATS);
WaitForGetStats();
mock_endpoint_->ExpectCall(FakeConsumerEndpoint::CallType::READ_BUFFERS);
consumer_->OnTraceStats(true, MakeTraceStats(kTestBufSize, kTestBufUtilizationThreshold, 0, 0));
RunLoopUntilIdle();
}
// Strips the two byte prelude header from a small serialized proto string
// so that tests can focus only on the payload contents.
std::string StripProtoPrelude(const std::string& input) { return input.substr(2); }
TEST_F(ConsumerAdapterTest, PollStatsAndReadBuffers) {
ASSERT_FALSE(mock_endpoint_);
fuchsia_tracing_->ChangeTraceState(TRACE_STARTED);
RunLoopUntilIdle();
ASSERT_TRUE(mock_endpoint_);
// Simulate that we're hit the utilization threshold to trigger a read.
mock_endpoint_->ExpectCall(FakeConsumerEndpoint::CallType::GET_TRACE_STATS);
mock_endpoint_->ExpectCall(FakeConsumerEndpoint::CallType::READ_BUFFERS);
WaitForGetStats();
consumer_->OnTraceStats(true, MakeTraceStats(kTestBufSize, kTestBufUtilizationThreshold, 0, 0));
RunLoopUntilIdle();
// Simulate multiple packets * multiple numbers of slices * multiple calls
// to OnTraceData.
std::string call1_packet1_slice1 = "c1p1s1";
std::string call1_packet1_slice2 = "c1p1s2";
std::string call1_packet2_slice = "c1p2s1";
std::string call2_packet_slice = "c2p1s1";
perfetto::TracePacket call1_packet1, call1_packet2, call2_packet;
call1_packet1.AddSlice(call1_packet1_slice1.data(), call1_packet1_slice1.size());
call1_packet1.AddSlice(call1_packet1_slice2.data(), call1_packet1_slice2.size());
call1_packet2.AddSlice(call1_packet2_slice.data(), call1_packet2_slice.size());
call2_packet.AddSlice(call2_packet_slice.data(), call2_packet_slice.size());
std::vector<perfetto::TracePacket> packets;
packets.emplace_back(std::move(call1_packet1));
packets.emplace_back(std::move(call1_packet2));
consumer_->OnTraceData(std::move(packets), true);
packets.clear();
packets.emplace_back(std::move(call2_packet));
consumer_->OnTraceData(std::move(packets), false);
RunLoopUntilIdle();
EXPECT_EQ(fuchsia_tracing_->GetReceivedBlobs().size(), 3u);
EXPECT_EQ(StripProtoPrelude(fuchsia_tracing_->GetReceivedBlobs()[0]), "c1p1s1c1p1s2");
EXPECT_EQ(StripProtoPrelude(fuchsia_tracing_->GetReceivedBlobs()[1]), "c1p2s1");
EXPECT_EQ(StripProtoPrelude(fuchsia_tracing_->GetReceivedBlobs()[2]), "c2p1s1");
// Back to polling.
mock_endpoint_->ExpectCall(FakeConsumerEndpoint::CallType::GET_TRACE_STATS);
WaitForGetStats();
}
TEST_F(ConsumerAdapterTest, TeardownTasks) {
ASSERT_FALSE(mock_endpoint_);
fuchsia_tracing_->ChangeTraceState(TRACE_STARTED);
RunLoopUntilIdle();
ASSERT_TRUE(mock_endpoint_);
mock_endpoint_->ExpectCall(FakeConsumerEndpoint::CallType::FLUSH);
fuchsia_tracing_->ChangeTraceState(TRACE_STOPPING);
RunLoopUntilIdle();
mock_endpoint_->ExpectCall(FakeConsumerEndpoint::CallType::DISABLE_TRACING);
mock_endpoint_->flush_cb()(true);
RunLoopUntilIdle();
mock_endpoint_->ExpectCall(FakeConsumerEndpoint::CallType::READ_BUFFERS);
consumer_->OnTracingDisabled({});
RunLoopUntilIdle();
mock_endpoint_->ExpectCall(FakeConsumerEndpoint::CallType::GET_TRACE_STATS);
consumer_->OnTraceData({}, false);
RunLoopUntilIdle();
consumer_->OnTraceStats(true,
MakeTraceStats(kTestBufSize, kTestBufUtilizationThreshold - 1, 0, 0));
RunLoopUntilIdle();
EXPECT_EQ(mock_endpoint_, nullptr);
}
} // namespace