0

[tracing] Support perfetto config in command line

This CL support encoded perfertto config (base64 or proto) in
command line.

Change-Id: I37028be6bb4b689af4a23539bae5fc33c06f8110
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6505633
Reviewed-by: Mikhail Khokhlov <khokhlov@google.com>
Commit-Queue: Etienne Pierre-Doray <etiennep@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1457177}
This commit is contained in:
Etienne Pierre-doray
2025-05-07 12:05:28 -07:00
committed by Chromium LUCI CQ
parent b7208a34ef
commit 31c16a61af
7 changed files with 209 additions and 17 deletions

@ -18,10 +18,18 @@ namespace switches {
// < {input txt config}.pbtxt > {output proto config}.pb
const char kEnableBackgroundTracing[] = "enable-background-tracing";
// Causes TRACE_EVENT flags to be recorded from startup.
// This flag will be ignored if --trace-startup or --trace-shutdown is provided.
// Enables startup tracing by passing a file path containing the chrome Json
// tracing config as an argument. This flag will be ignored if --trace-startup
// or --trace-shutdown is provided.
const char kTraceConfigFile[] = "trace-config-file";
// Enables startup tracing by passing a file path containing the perfetto config
// as an argument. The config is a serialized or base64 encoded proto
// `perfetto.protos.TraceConfig` defined in
// third_party/perfetto/protos/perfetto/config/trace_config.proto. This flag
// will be ignored if --trace-startup or --trace-shutdown is provided.
const char kTracePerfettoConfigFile[] = "trace-perfetto-config-file";
// Causes TRACE_EVENT flags to be recorded from startup. Optionally, can
// specify the specific trace categories to include (e.g.
// --trace-startup=base,net) otherwise, all events are recorded. Setting this

@ -11,6 +11,7 @@ namespace switches {
TRACING_EXPORT extern const char kEnableBackgroundTracing[];
TRACING_EXPORT extern const char kTraceConfigFile[];
TRACING_EXPORT extern const char kTracePerfettoConfigFile[];
TRACING_EXPORT extern const char kTraceStartup[];
TRACING_EXPORT extern const char kTraceConfigHandle[];
TRACING_EXPORT extern const char kEnableTracing[];

@ -4,6 +4,7 @@ include_rules = [
"+third_party/perfetto/include",
"+third_party/perfetto/protos/perfetto",
"+third_party/protobuf/src/google/protobuf/io/zero_copy_stream.h",
"+third_party/snappy"
]
specific_include_rules = {

@ -136,6 +136,7 @@ target(tracing_lib_type, "cpp") {
"//build:chromecast_buildflags",
"//components/system_cpu:system_cpu",
"//third_party/perfetto/protos/perfetto/trace/chrome:minimal_complete_lite",
"//third_party/snappy:snappy",
]
if (!(is_chromeos && target_cpu == "arm64" && current_cpu == "arm")) {

@ -9,6 +9,7 @@
#include <memory>
#include <string>
#include "base/base64.h"
#include "base/command_line.h"
#include "base/files/file_util.h"
#include "base/json/json_reader.h"
@ -25,6 +26,7 @@
#include "services/tracing/public/cpp/perfetto/perfetto_config.h"
#include "services/tracing/public/mojom/perfetto_service.mojom.h"
#include "third_party/perfetto/protos/perfetto/config/track_event/track_event_config.gen.h"
#include "third_party/snappy/src/snappy.h"
#if BUILDFLAG(IS_ANDROID)
#include "base/android/early_trace_event_binding.h"
@ -120,7 +122,9 @@ TraceStartupConfig::TraceStartupConfig() {
DCHECK(IsEnabled());
} else if (EnableFromConfigHandle()) {
DCHECK(IsEnabled());
} else if (EnableFromConfigFile()) {
} else if (EnableFromJsonConfigFile()) {
DCHECK(IsEnabled());
} else if (EnableFromPerfettoConfigFile()) {
DCHECK(IsEnabled());
} else if (EnableFromBackgroundTracing()) {
DCHECK(IsEnabled());
@ -283,7 +287,7 @@ bool TraceStartupConfig::EnableFromConfigHandle() {
return true;
}
bool TraceStartupConfig::EnableFromConfigFile() {
bool TraceStartupConfig::EnableFromJsonConfigFile() {
#if BUILDFLAG(IS_ANDROID)
base::FilePath trace_config_file(kAndroidTraceConfigFile);
#else
@ -318,11 +322,57 @@ bool TraceStartupConfig::EnableFromConfigFile() {
DLOG(WARNING) << "Cannot read the trace config file correctly.";
return false;
}
is_enabled_ = ParseTraceConfigFileContent(trace_config_file_content);
if (!is_enabled_) {
auto config = ParseTraceJsonConfigFileContent(trace_config_file_content);
if (!config) {
DLOG(WARNING) << "Cannot parse the trace config file correctly.";
return false;
}
return is_enabled_;
perfetto_config_ = *config;
is_enabled_ = true;
return true;
}
bool TraceStartupConfig::EnableFromPerfettoConfigFile() {
auto* command_line = base::CommandLine::ForCurrentProcess();
if (!command_line->HasSwitch(switches::kTracePerfettoConfigFile)) {
return false;
}
base::FilePath config_file =
command_line->GetSwitchValuePath(switches::kTracePerfettoConfigFile);
if (config_file.empty()) {
DLOG(WARNING) << "--perfetto-config-file needs a config file path.";
return false;
}
if (!base::PathExists(config_file)) {
DLOG(WARNING) << "The perfetto config file does not exist.";
return false;
}
std::string config_text;
if (!base::ReadFileToString(config_file, &config_text)) {
DLOG(WARNING) << "Cannot read the trace config file correctly.";
return false;
}
std::optional<perfetto::TraceConfig> config;
if (base::FilePath::CompareEqualIgnoreCase(config_file.Extension(),
FILE_PATH_LITERAL(".pb"))) {
config = ParseSerializedPerfettoConfig(base::as_byte_span(config_text));
} else {
config = ParseEncodedPerfettoConfig(config_text);
}
if (!config) {
DLOG(WARNING) << "Failed to parse perfetto config file.";
return false;
}
if (AdaptPerfettoConfigForChrome(&*config)) {
DLOG(WARNING) << "Failed to adapt perfetto config file.";
}
perfetto_config_ = *config;
is_enabled_ = true;
return true;
}
bool TraceStartupConfig::EnableFromBackgroundTracing() {
@ -352,28 +402,29 @@ bool TraceStartupConfig::EnableFromBackgroundTracing() {
return true;
}
bool TraceStartupConfig::ParseTraceConfigFileContent(
std::optional<perfetto::TraceConfig>
TraceStartupConfig::ParseTraceJsonConfigFileContent(
const std::string& content) {
std::optional<base::Value::Dict> value = base::JSONReader::ReadDict(content);
if (!value) {
return false;
return std::nullopt;
}
auto* trace_config_dict = value->FindDict(kTraceConfigParam);
if (!trace_config_dict) {
return false;
return std::nullopt;
}
auto chrome_config =
base::trace_event::TraceConfig(std::move(*trace_config_dict));
perfetto_config_ = tracing::GetDefaultPerfettoConfig(
perfetto::TraceConfig perfetto_config = tracing::GetDefaultPerfettoConfig(
chrome_config, false, output_format_ != OutputFormat::kProto,
perfetto::protos::gen::ChromeConfig::USER_INITIATED, "");
int startup_duration_in_seconds =
value->FindInt(kStartupDurationParam).value_or(0);
if (startup_duration_in_seconds > 0) {
perfetto_config_.set_duration_ms(startup_duration_in_seconds * 1000);
perfetto_config.set_duration_ms(startup_duration_in_seconds * 1000);
}
if (auto* result_file = value->FindString(kResultFileParam)) {
@ -386,7 +437,39 @@ bool TraceStartupConfig::ParseTraceConfigFileContent(
"_chrometrace.log");
}
return true;
return perfetto_config;
}
std::optional<perfetto::TraceConfig>
TraceStartupConfig::ParseSerializedPerfettoConfig(
const base::span<const uint8_t>& config_bytes) {
perfetto::TraceConfig config;
if (config_bytes.empty()) {
return std::nullopt;
}
if (config.ParseFromArray(config_bytes.data(), config_bytes.size())) {
return config;
}
return std::nullopt;
}
std::optional<perfetto::TraceConfig>
TraceStartupConfig::ParseEncodedPerfettoConfig(
const std::string& config_string) {
std::string serialized_config;
if (!base::Base64Decode(config_string, &serialized_config,
base::Base64DecodePolicy::kForgiving)) {
return std::nullopt;
}
// `serialized_config` may optionally be compressed.
std::string decompressed_config;
if (!snappy::Uncompress(serialized_config.data(), serialized_config.size(),
&decompressed_config)) {
return ParseSerializedPerfettoConfig(base::as_byte_span(serialized_config));
}
return ParseSerializedPerfettoConfig(base::as_byte_span(decompressed_config));
}
} // namespace tracing

@ -6,6 +6,7 @@
#define SERVICES_TRACING_PUBLIC_CPP_TRACE_STARTUP_CONFIG_H_
#include "base/component_export.h"
#include "base/containers/span.h"
#include "base/files/file_path.h"
#include "base/trace_event/trace_config.h"
#include "build/build_config.h"
@ -154,11 +155,17 @@ class COMPONENT_EXPORT(TRACING_CPP) TraceStartupConfig {
TraceStartupConfig();
bool EnableFromCommandLine();
bool EnableFromConfigFile();
bool EnableFromJsonConfigFile();
bool EnableFromPerfettoConfigFile();
bool EnableFromConfigHandle();
bool EnableFromBackgroundTracing();
bool ParseTraceConfigFileContent(const std::string& content);
std::optional<perfetto::TraceConfig> ParseTraceJsonConfigFileContent(
const std::string& content);
std::optional<perfetto::TraceConfig> ParseSerializedPerfettoConfig(
const base::span<const uint8_t>& config_bytes);
std::optional<perfetto::TraceConfig> ParseEncodedPerfettoConfig(
const std::string& config_string);
bool is_enabled_ = false;
perfetto::TraceConfig perfetto_config_;

@ -7,19 +7,39 @@
#include <algorithm>
#include "base/at_exit.h"
#include "base/base64.h"
#include "base/command_line.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/memory/ptr_util.h"
#include "base/path_service.h"
#include "base/test/test_proto_loader.h"
#include "base/threading/thread_restrictions.h"
#include "components/tracing/common/tracing_switches.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/perfetto/include/perfetto/tracing/core/trace_config.h"
#include "third_party/perfetto/protos/perfetto/config/data_source_config.gen.h"
#include "third_party/perfetto/protos/perfetto/config/trace_config.gen.h"
namespace tracing {
namespace {
perfetto::protos::gen::TraceConfig ParseTracingConfigFromText(
const std::string& proto_text) {
base::ScopedAllowBlockingForTesting allow_blocking;
base::TestProtoLoader config_loader(
base::PathService::CheckedGet(base::DIR_GEN_TEST_DATA_ROOT)
.Append(FILE_PATH_LITERAL(
"third_party/perfetto/protos/perfetto/config/config.descriptor")),
"perfetto.protos.TraceConfig");
std::string serialized_message;
config_loader.ParseFromText(proto_text, serialized_message);
perfetto::protos::gen::TraceConfig destination;
destination.ParseFromString(serialized_message);
return destination;
}
const char kTraceConfig[] =
"{"
"\"enable_argument_filter\":true,"
@ -32,6 +52,20 @@ const char kTraceConfig[] =
"\"record_mode\":\"record-continuously\""
"}";
constexpr const char kPerfettoConfig[] = R"pb(
duration_ms: 10000
buffers: { size_kb: 4 fill_policy: RING_BUFFER }
data_sources: {
config: {
name: "track_event"
track_event_config: {
disabled_categories: [ "excluded" ]
enabled_categories: [ "included" ]
}
}
}
)pb";
std::string GetTraceConfigFileContent(std::string trace_config,
std::string startup_duration,
std::string result_file) {
@ -105,7 +139,7 @@ TEST_F(TraceStartupConfigTest, TraceStartupConfigEnabledWithInvalidPath) {
EXPECT_FALSE(startup_config_->IsEnabled());
}
TEST_F(TraceStartupConfigTest, ValidContent) {
TEST_F(TraceStartupConfigTest, ValidJsonContent) {
std::string content =
GetTraceConfigFileContent(kTraceConfig, "10", "trace_result_file.log");
@ -132,6 +166,47 @@ TEST_F(TraceStartupConfigTest, ValidContent) {
startup_config_->GetResultFile());
}
TEST_F(TraceStartupConfigTest, ValidProtoContent) {
std::string config_string =
ParseTracingConfigFromText(kPerfettoConfig).SerializeAsString();
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
base::FilePath config_file =
temp_dir.GetPath().Append(FILE_PATH_LITERAL("config.pb"));
ASSERT_TRUE(base::WriteFile(config_file, config_string));
base::CommandLine::ForCurrentProcess()->AppendSwitchPath(
switches::kTracePerfettoConfigFile, config_file);
Initialize();
ASSERT_TRUE(startup_config_->IsEnabled());
auto config = startup_config_->GetPerfettoConfig();
ASSERT_EQ(1, config.data_sources_size());
EXPECT_EQ("track_event", config.data_sources()[0].config().name());
EXPECT_EQ(10000U, config.duration_ms());
}
TEST_F(TraceStartupConfigTest, ValidBase64Content) {
std::string config_string =
ParseTracingConfigFromText(kPerfettoConfig).SerializeAsString();
std::string serialized_config = base::Base64Encode(config_string);
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
base::FilePath config_file =
temp_dir.GetPath().Append(FILE_PATH_LITERAL("config.txt"));
ASSERT_TRUE(base::WriteFile(config_file, serialized_config));
base::CommandLine::ForCurrentProcess()->AppendSwitchPath(
switches::kTracePerfettoConfigFile, config_file);
Initialize();
ASSERT_TRUE(startup_config_->IsEnabled());
auto config = startup_config_->GetPerfettoConfig();
ASSERT_EQ(1, config.data_sources_size());
EXPECT_EQ("track_event", config.data_sources()[0].config().name());
EXPECT_EQ(10000U, config.duration_ms());
}
TEST_F(TraceStartupConfigTest, ValidContentWithOnlyTraceConfig) {
std::string content = GetTraceConfigFileContent(kTraceConfig, "", "");
@ -228,7 +303,7 @@ TEST_F(TraceStartupConfigTest, ContentWithoutTraceConfig) {
EXPECT_FALSE(startup_config_->IsEnabled());
}
TEST_F(TraceStartupConfigTest, InvalidContent) {
TEST_F(TraceStartupConfigTest, InvalidJsonContent) {
std::string content = "invalid trace config file content";
base::FilePath trace_config_file;
@ -244,6 +319,22 @@ TEST_F(TraceStartupConfigTest, InvalidContent) {
EXPECT_FALSE(startup_config_->IsEnabled());
}
TEST_F(TraceStartupConfigTest, InvalidProtoContent) {
std::string content = "invalid trace config file content";
base::FilePath trace_config_file;
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
ASSERT_TRUE(
base::CreateTemporaryFileInDir(temp_dir.GetPath(), &trace_config_file));
ASSERT_TRUE(base::WriteFile(trace_config_file, content));
base::CommandLine::ForCurrentProcess()->AppendSwitchPath(
switches::kTracePerfettoConfigFile, trace_config_file);
Initialize();
EXPECT_FALSE(startup_config_->IsEnabled());
}
TEST_F(TraceStartupConfigTest, EmptyContent) {
base::FilePath trace_config_file;
base::ScopedTempDir temp_dir;