blob: 84dad6590967e595c11d5248f4b44132701e4295 [file]
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/devtools/protocol/tracing_handler.h"
#include <memory>
#include "base/json/json_reader.h"
#include "base/process/process.h"
#include "base/strings/string_number_conversions.h"
#include "base/trace_event/trace_config.h"
#include "base/values.h"
#include "content/browser/devtools/devtools_session.h"
#include "content/browser/devtools/devtools_traceable_screenshot.h"
#include "services/tracing/public/cpp/perfetto/perfetto_data_source_names.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/geometry/size.h"
namespace content {
namespace protocol {
namespace {
const char kCustomTraceConfigString[] =
"{"
"\"enable_argument_filter\":true,"
"\"enable_package_name_filter\":false,"
"\"enable_systrace\":true,"
"\"excluded_categories\":[\"excluded\",\"exc_pattern*\"],"
"\"included_categories\":[\"included\","
"\"inc_pattern*\","
"\"disabled-by-default-cc\","
"\"disabled-by-default-memory-infra\"],"
"\"memory_dump_config\":{"
"\"allowed_dump_modes\":[\"background\",\"light\",\"detailed\"],"
"\"triggers\":["
"{"
"\"min_time_between_dumps_ms\":50,"
"\"mode\":\"light\","
"\"type\":\"periodic_interval\""
"},"
"{"
"\"min_time_between_dumps_ms\":1000,"
"\"mode\":\"detailed\","
"\"type\":\"periodic_interval\""
"}"
"]"
"},"
"\"record_mode\":\"record-continuously\","
"\"trace_buffer_size_in_kb\":262144"
"}";
const char kCustomTraceConfigStringDevToolsStyle[] =
"{"
"\"enableArgumentFilter\":true,"
"\"enableSystrace\":true,"
"\"excludedCategories\":[\"excluded\",\"exc_pattern*\"],"
"\"includedCategories\":[\"included\","
"\"inc_pattern*\","
"\"disabled-by-default-cc\","
"\"disabled-by-default-memory-infra\"],"
"\"memoryDumpConfig\":{"
"\"allowedDumpModes\":[\"background\",\"light\",\"detailed\"],"
"\"triggers\":["
"{"
"\"minTimeBetweenDumpsMs\":50,"
"\"mode\":\"light\","
"\"type\":\"periodic_interval\""
"},"
"{"
"\"minTimeBetweenDumpsMs\":1000,"
"\"mode\":\"detailed\","
"\"type\":\"periodic_interval\""
"}"
"]"
"},"
"\"recordMode\":\"recordContinuously\","
"\"traceBufferSizeInKb\":262144"
"}";
} // namespace
class StubClient : public DevToolsAgentHostClient {
public:
void DispatchProtocolMessage(DevToolsAgentHost* agent_host,
base::span<const uint8_t> message) override {}
void AgentHostClosed(DevToolsAgentHost* agent_host) override {}
};
class TracingHandlerTest : public testing::Test,
public testing::WithParamInterface<bool> {
public:
void SetUp() override {
client_ = std::make_unique<StubClient>();
session_ = std::make_unique<DevToolsSession>(
client_.get(), DevToolsSession::Mode::kSupportsTabTarget);
tracing_handler_ =
std::make_unique<TracingHandler>(nullptr, nullptr, session_.get(),
/*is_trusted=*/GetParam());
}
void TearDown() override { tracing_handler_.reset(); }
std::string GetValidTraceFragment(const std::string& trace_fragment) {
const std::string valid_trace_fragment =
tracing_handler_->UpdateTraceDataBuffer(trace_fragment);
return valid_trace_fragment.substr(
tracing_handler_->trace_data_buffer_state_.offset);
}
perfetto::TraceConfig GetTraceConfig() {
return tracing_handler_->trace_config_;
}
void SetDidInitiateRecording(bool value) {
tracing_handler_->did_initiate_recording_ = value;
}
void CallAddProcessToFilter(base::ProcessId pid) {
tracing_handler_->AddProcessToFilter(pid);
}
static void CallAddPidsToProcessFilter(
const std::unordered_set<base::ProcessId>& pids,
perfetto::TraceConfig& config) {
TracingHandler::AddPidsToProcessFilter(pids, config);
}
TracingHandler* tracing_handler() { return tracing_handler_.get(); }
protected:
std::unique_ptr<StubClient> client_;
std::unique_ptr<DevToolsSession> session_;
std::unique_ptr<TracingHandler> tracing_handler_;
};
TEST_P(TracingHandlerTest, GetTraceConfigFromDevToolsConfig) {
base::Value devtools_config =
base::JSONReader::Read(kCustomTraceConfigStringDevToolsStyle,
base::JSON_PARSE_CHROMIUM_EXTENSIONS)
.value();
base::trace_event::TraceConfig trace_config =
TracingHandler::GetTraceConfigFromDevToolsConfig(devtools_config);
EXPECT_STREQ(kCustomTraceConfigString, trace_config.ToString().c_str());
}
TEST_P(TracingHandlerTest, SimpleGetValidTraceFragment) {
// No prefix is valid.
EXPECT_EQ("", GetValidTraceFragment("{pid: 1, "));
// The longest valid prefix of "{pid: 1, args: {}}, {pid: 2" is
// "{pid: 1, args: {}}".
EXPECT_EQ("{pid: 1, args: {}}", GetValidTraceFragment("args: {}}, {pid: 2"));
EXPECT_EQ("{pid: 2}, {pid: 3}", GetValidTraceFragment("}, {pid: 3}"));
}
TEST_P(TracingHandlerTest, GetValidTraceFragmentBreakBeforeComma) {
EXPECT_EQ("{pid: 1}", GetValidTraceFragment("{pid: 1}"));
// The comma should be ignored.
EXPECT_EQ("{pid: 2}", GetValidTraceFragment(",{pid: 2}"));
}
TEST_P(TracingHandlerTest, ComplexGetValidTraceFragment) {
const std::string chunk1 = "{\"pid\":1,\"args\":{\"key\":\"}\"},\"tid\":1}";
const std::string chunk2 =
"{\"pid\":2,\"args\":{\"key\":{\"key\":\"\\\"t}\"},\"key2\":2},\"tid\":"
"2}";
const std::string trace_data = chunk1 + "," + chunk2;
EXPECT_EQ("", GetValidTraceFragment(trace_data.substr(0, chunk1.size() - 1)));
EXPECT_EQ(chunk1, GetValidTraceFragment(trace_data.substr(
chunk1.size() - 1, trace_data.size() - chunk1.size())));
EXPECT_EQ(chunk2,
GetValidTraceFragment(trace_data.substr(trace_data.size() - 1, 1)));
}
TEST_P(TracingHandlerTest, ProcessFilterAppendsPids) {
perfetto::TraceConfig trace_config;
auto* data_source = trace_config.add_data_sources();
auto* config = data_source->mutable_config();
config->set_name("track_event");
// Initial PIDs
std::unordered_set<base::ProcessId> pids1 = {1234, 5678};
TracingHandler::AddPidsToProcessFilter(pids1, trace_config);
ASSERT_EQ(1, trace_config.data_sources_size());
EXPECT_EQ(2, trace_config.data_sources()[0].producer_name_filter_size());
// Append new PID
std::unordered_set<base::ProcessId> pids2 = {9012};
TracingHandler::AddPidsToProcessFilter(pids2, trace_config);
ASSERT_EQ(1, trace_config.data_sources_size());
// Should have 3 PIDs now
EXPECT_EQ(3, trace_config.data_sources()[0].producer_name_filter_size());
EXPECT_THAT(trace_config.data_sources()[0].producer_name_filter(),
testing::Contains(
std::string(tracing::kPerfettoProducerNamePrefix) + "1234"));
EXPECT_THAT(trace_config.data_sources()[0].producer_name_filter(),
testing::Contains(
std::string(tracing::kPerfettoProducerNamePrefix) + "5678"));
EXPECT_THAT(trace_config.data_sources()[0].producer_name_filter(),
testing::Contains(
std::string(tracing::kPerfettoProducerNamePrefix) + "9012"));
}
TEST_P(TracingHandlerTest, ResolveScreenshotParams) {
constexpr int kDefaultSize = 500;
constexpr int kDefaultCount =
DevToolsTraceableScreenshot::kDefaultMaximumNumberOfScreenshots;
// Defaults are applied when no values are supplied.
{
gfx::Size size;
int count = 0;
EXPECT_TRUE(TracingHandler::ResolveScreenshotParams(
std::nullopt, std::nullopt, &size, &count)
.IsSuccess());
EXPECT_EQ(gfx::Size(kDefaultSize, kDefaultSize), size);
EXPECT_EQ(kDefaultCount, count);
}
// Explicit equivalent of the defaults is accepted.
{
gfx::Size size;
int count = 0;
EXPECT_TRUE(TracingHandler::ResolveScreenshotParams(
kDefaultSize, kDefaultCount, &size, &count)
.IsSuccess());
EXPECT_EQ(gfx::Size(kDefaultSize, kDefaultSize), size);
EXPECT_EQ(kDefaultCount, count);
}
// Trading resolution for count within the memory budget is accepted
// (e.g. 250x250 / 1800 = same total budget as 500x500 / 450).
{
gfx::Size size;
int count = 0;
EXPECT_TRUE(
TracingHandler::ResolveScreenshotParams(250, 1800, &size, &count)
.IsSuccess());
EXPECT_EQ(gfx::Size(250, 250), size);
EXPECT_EQ(1800, count);
}
// Non-positive values are rejected.
{
gfx::Size size;
int count = 0;
EXPECT_FALSE(
TracingHandler::ResolveScreenshotParams(0, kDefaultCount, &size, &count)
.IsSuccess());
EXPECT_FALSE(
TracingHandler::ResolveScreenshotParams(kDefaultSize, 0, &size, &count)
.IsSuccess());
EXPECT_FALSE(TracingHandler::ResolveScreenshotParams(-1, kDefaultCount,
&size, &count)
.IsSuccess());
}
// Combinations that exceed the per-session memory budget are rejected.
{
gfx::Size size;
int count = 0;
// 500x500 * 4 * 451 exceeds the 500x500 * 4 * 450 budget by one frame.
EXPECT_FALSE(TracingHandler::ResolveScreenshotParams(
kDefaultSize, kDefaultCount + 1, &size, &count)
.IsSuccess());
}
}
TEST_P(TracingHandlerTest, FilterUntrustedDataSources) {
perfetto::TraceConfig config;
config.add_data_sources()->mutable_config()->set_name("track_event");
config.add_data_sources()->mutable_config()->set_name(
tracing::kMetaData2SourceName);
config.add_data_sources()->mutable_config()->set_name(
"org.chromium.memory_instrumentation");
config.add_data_sources()->mutable_config()->set_name(
"org.chromium.sampler_profiler");
config.add_data_sources()->mutable_config()->set_name("unknown_data_source");
ASSERT_EQ(5, config.data_sources_size());
TracingHandler::FilterUntrustedDataSources(config);
ASSERT_EQ(2, config.data_sources_size());
EXPECT_EQ("track_event", config.data_sources()[0].config().name());
EXPECT_EQ(tracing::kMetaData2SourceName,
config.data_sources()[1].config().name());
}
TEST_P(TracingHandlerTest, StartDropsClientFilters) {
bool is_trusted = GetParam();
perfetto::TraceConfig config;
auto* data_source = config.add_data_sources();
data_source->mutable_config()->set_name("track_event");
data_source->add_producer_name_regex_filter(".*");
data_source->add_producer_name_filter("old_filter");
std::unordered_set<base::ProcessId> pids;
if (is_trusted) {
pids.insert(base::Process::Current().Pid());
} else {
pids.erase(base::Process::Current().Pid());
}
for (auto& ds : *config.mutable_data_sources()) {
if (ds.config().name() == "track_event") {
ds.clear_producer_name_regex_filter();
ds.clear_producer_name_filter();
}
}
CallAddPidsToProcessFilter(pids, config);
ASSERT_EQ(1, config.data_sources_size());
EXPECT_EQ(0, config.data_sources()[0].producer_name_regex_filter_size());
if (is_trusted) {
EXPECT_THAT(config.data_sources()[0].producer_name_filter(),
testing::Not(testing::Contains("old_filter")));
EXPECT_THAT(
config.data_sources()[0].producer_name_filter(),
testing::Contains(std::string(tracing::kPerfettoProducerNamePrefix) +
base::NumberToString(static_cast<uint32_t>(
base::Process::Current().Pid()))));
} else {
EXPECT_THAT(config.data_sources()[0].producer_name_filter(),
testing::Not(testing::Contains("old_filter")));
EXPECT_THAT(config.data_sources()[0].producer_name_filter(),
testing::Not(testing::Contains(
std::string(tracing::kPerfettoProducerNamePrefix) +
base::NumberToString(static_cast<uint32_t>(
base::Process::Current().Pid())))));
EXPECT_THAT(config.data_sources()[0].producer_name_filter(),
testing::Contains("org.chromium-0"));
}
}
INSTANTIATE_TEST_SUITE_P(All, TracingHandlerTest, testing::Bool());
} // namespace protocol
} // namespace content