blob: 8df64641b4ad3bbbfd95957913cb071050e56189 [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/tracing/common/background_tracing_utils.h"
#include <memory>
#include <string>
#include <utility>
#include "base/base64.h"
#include "base/check.h"
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/json/json_reader.h"
#include "base/logging.h"
#include "base/metrics/field_trial_params.h"
#include "base/metrics/histogram_macros.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "components/tracing/common/background_tracing_state_manager.h"
#include "components/tracing/common/tracing_switches.h"
#include "content/public/browser/background_tracing_manager.h"
#include "content/public/browser/browser_thread.h"
#include "third_party/snappy/src/snappy.h"
namespace tracing {
BASE_FEATURE(kTracingTriggers,
"TracingTriggers",
base::FEATURE_DISABLED_BY_DEFAULT);
BASE_FEATURE(kFieldTracing, "FieldTracing", base::FEATURE_DISABLED_BY_DEFAULT);
BASE_FEATURE(kPresetTracing,
"PresetTracing",
base::FEATURE_DISABLED_BY_DEFAULT);
namespace {
const base::FeatureParam<std::string> kTracingTriggerRulesConfig{
&kTracingTriggers, "config", ""};
const base::FeatureParam<bool> kTracingTriggerRulesCompressed{
&kTracingTriggers, "compressed", false};
const base::FeatureParam<std::string> kFieldTracingConfig{&kFieldTracing,
"config", ""};
const base::FeatureParam<bool> kFieldTracingCompressed{&kFieldTracing,
"compressed", false};
const base::FeatureParam<bool> kFieldTracingAnonymized{&kFieldTracing,
"anonymized", true};
const base::FeatureParam<bool> kStartupFieldTracing{&kFieldTracing, "startup",
false};
const base::FeatureParam<std::string> kPresetTracingConfig{&kPresetTracing,
"config", ""};
const base::FeatureParam<bool> kPresetTracingCompressed{&kPresetTracing,
"compressed", false};
bool BlockingWriteTraceToFile(const base::FilePath& output_file,
std::string file_contents) {
if (base::WriteFile(output_file, file_contents)) {
LOG(ERROR) << "Background trace written to "
<< output_file.LossyDisplayName();
return true;
}
LOG(ERROR) << "Failed to write background trace to "
<< output_file.LossyDisplayName();
return false;
}
void WriteTraceToFile(
const base::FilePath& output_path,
const std::string& file_name,
std::string file_contents,
content::BackgroundTracingManager::FinishedProcessingCallback
done_callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
base::FilePath output_file = output_path.AppendASCII(file_name);
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::MayBlock()},
base::BindOnce(&BlockingWriteTraceToFile, output_file,
std::move(file_contents)),
std::move(done_callback));
}
std::unique_ptr<content::BackgroundTracingConfig>
GetBackgroundTracingConfigFromFile(const base::FilePath& config_file) {
std::string config_text;
if (!base::ReadFileToString(config_file, &config_text) ||
config_text.empty()) {
LOG(ERROR) << "Failed to read background tracing config file "
<< config_file.value();
return nullptr;
}
auto value_with_error = base::JSONReader::ReadAndReturnValueWithError(
config_text, base::JSON_ALLOW_TRAILING_COMMAS);
if (!value_with_error.has_value()) {
LOG(ERROR) << "Background tracing has incorrect config: "
<< value_with_error.error().message;
return nullptr;
}
if (!value_with_error->is_dict()) {
LOG(ERROR) << "Background tracing config is not a dict";
return nullptr;
}
auto config = content::BackgroundTracingConfig::FromDict(
std::move(*value_with_error).TakeDict());
if (!config) {
LOG(ERROR) << "Background tracing config dict has invalid contents";
return nullptr;
}
return config;
}
std::optional<perfetto::protos::gen::ChromeFieldTracingConfig>
GetTracingConfigFromFeature(const base::Feature& feature,
const base::FeatureParam<std::string> feature_param,
bool is_compressed) {
if (!base::FeatureList::IsEnabled(feature)) {
return std::nullopt;
}
std::string serialized_config;
if (!base::Base64Decode(feature_param.Get(), &serialized_config)) {
return std::nullopt;
}
if (is_compressed) {
std::string decompressed_config;
if (!snappy::Uncompress(serialized_config.data(), serialized_config.size(),
&decompressed_config)) {
return std::nullopt;
}
serialized_config = std::move(decompressed_config);
}
perfetto::protos::gen::ChromeFieldTracingConfig config;
if (config.ParseFromString(serialized_config)) {
return config;
}
return std::nullopt;
}
} // namespace
void RecordDisallowedMetric(TracingFinalizationDisallowedReason reason) {
UMA_HISTOGRAM_ENUMERATION("Tracing.Background.FinalizationDisallowedReason",
reason);
}
bool SetupBackgroundTracingFromJsonConfigFile(
const base::FilePath& config_file) {
std::unique_ptr<content::BackgroundTracingConfig> config =
GetBackgroundTracingConfigFromFile(config_file);
if (!config) {
return false;
}
// NO_DATA_FILTERING is set because the trace is saved to a local output file
// instead of being uploaded to a metrics server, so there are no PII
// concerns.
return content::BackgroundTracingManager::GetInstance().SetActiveScenario(
std::move(config), content::BackgroundTracingManager::NO_DATA_FILTERING);
}
bool SetupBackgroundTracingFromProtoConfigFile(
const base::FilePath& config_file) {
perfetto::protos::gen::ChromeFieldTracingConfig config;
std::string config_text;
if (!base::ReadFileToString(config_file, &config_text) ||
config_text.empty() || !config.ParseFromString(config_text)) {
LOG(ERROR) << "Failed to read field tracing config file "
<< config_file.value() << "."
<< "Make sure to provide a serialized proto, or use "
<< "--enable-legacy-background-tracing to provide a "
<< "JSON config.";
return false;
}
// NO_DATA_FILTERING is set because the trace is saved to a local output file
// instead of being uploaded to a metrics server, so there are no PII
// concerns.
auto scenarios =
content::BackgroundTracingManager::GetInstance().AddPresetScenarios(
std::move(config),
content::BackgroundTracingManager::NO_DATA_FILTERING);
return content::BackgroundTracingManager::GetInstance().SetEnabledScenarios(
scenarios);
}
bool SetupBackgroundTracingFromCommandLine() {
auto* command_line = base::CommandLine::ForCurrentProcess();
if (tracing::HasBackgroundTracingOutputPath() &&
!tracing::SetBackgroundTracingOutputPath()) {
return false;
}
switch (GetBackgroundTracingSetupMode()) {
case BackgroundTracingSetupMode::kDisabledInvalidCommandLine:
return false;
case BackgroundTracingSetupMode::kFromJsonConfigFile:
return SetupBackgroundTracingFromJsonConfigFile(
command_line->GetSwitchValuePath(
switches::kEnableLegacyBackgroundTracing));
case BackgroundTracingSetupMode::kFromProtoConfigFile:
return SetupBackgroundTracingFromProtoConfigFile(
command_line->GetSwitchValuePath(switches::kEnableBackgroundTracing));
case BackgroundTracingSetupMode::kFromFieldTrial:
return false;
}
}
bool SetupPresetTracingFromFieldTrial() {
if (GetBackgroundTracingSetupMode() !=
BackgroundTracingSetupMode::kFromFieldTrial) {
return false;
}
auto& manager = content::BackgroundTracingManager::GetInstance();
auto field_tracing_config = tracing::GetPresetTracingConfig();
if (field_tracing_config) {
manager.AddPresetScenarios(
std::move(*field_tracing_config),
content::BackgroundTracingManager::NO_DATA_FILTERING);
const auto& enabled_scenarios =
tracing::BackgroundTracingStateManager::GetInstance()
.enabled_scenarios();
if (!enabled_scenarios.empty()) {
return manager.SetEnabledScenarios(enabled_scenarios);
}
return true;
}
return false;
}
bool HasBackgroundTracingOutputPath() {
auto* command_line = base::CommandLine::ForCurrentProcess();
return command_line->HasSwitch(switches::kBackgroundTracingOutputPath);
}
bool SetBackgroundTracingOutputPath() {
auto* command_line = base::CommandLine::ForCurrentProcess();
if (command_line->GetSwitchValuePath(switches::kBackgroundTracingOutputPath)
.empty()) {
LOG(ERROR) << "--background-tracing-output-path needs an output path";
return false;
}
auto output_path =
command_line->GetSwitchValuePath(switches::kBackgroundTracingOutputPath);
auto receive_callback = base::BindRepeating(&WriteTraceToFile, output_path);
content::BackgroundTracingManager::GetInstance().SetReceiveCallback(
std::move(receive_callback));
return true;
}
BackgroundTracingSetupMode GetBackgroundTracingSetupMode() {
auto* command_line = base::CommandLine::ForCurrentProcess();
if (!command_line->HasSwitch(switches::kEnableBackgroundTracing) &&
!command_line->HasSwitch(switches::kEnableLegacyBackgroundTracing)) {
return BackgroundTracingSetupMode::kFromFieldTrial;
}
if (command_line->HasSwitch(switches::kEnableBackgroundTracing) &&
command_line->HasSwitch(switches::kEnableLegacyBackgroundTracing)) {
LOG(ERROR) << "Can't specify both --enable-background-tracing and "
"--enable-legacy-background-tracing";
return BackgroundTracingSetupMode::kDisabledInvalidCommandLine;
}
if (command_line->HasSwitch(switches::kEnableBackgroundTracing) &&
command_line->GetSwitchValueNative(switches::kEnableBackgroundTracing)
.empty()) {
LOG(ERROR) << "--enable-background-tracing needs a config file path";
return BackgroundTracingSetupMode::kDisabledInvalidCommandLine;
}
if (command_line->HasSwitch(switches::kEnableLegacyBackgroundTracing) &&
command_line
->GetSwitchValueNative(switches::kEnableLegacyBackgroundTracing)
.empty()) {
LOG(ERROR) << "--enable-legacy-background-tracing needs a config file path";
return BackgroundTracingSetupMode::kDisabledInvalidCommandLine;
}
if (command_line->HasSwitch(switches::kEnableBackgroundTracing)) {
return BackgroundTracingSetupMode::kFromProtoConfigFile;
}
return BackgroundTracingSetupMode::kFromJsonConfigFile;
}
std::optional<perfetto::protos::gen::ChromeFieldTracingConfig>
GetFieldTracingConfig() {
return GetTracingConfigFromFeature(kFieldTracing, kFieldTracingConfig,
kFieldTracingCompressed.Get());
}
bool ShouldAnonymizeFieldTracing() {
return kFieldTracingAnonymized.Get();
}
bool ShouldTraceStartup() {
return kStartupFieldTracing.Get();
}
std::optional<perfetto::protos::gen::ChromeFieldTracingConfig>
GetPresetTracingConfig() {
return GetTracingConfigFromFeature(kPresetTracing, kPresetTracingConfig,
kPresetTracingCompressed.Get());
}
std::optional<perfetto::protos::gen::TracingTriggerRulesConfig>
GetTracingTriggerRulesConfig() {
if (!base::FeatureList::IsEnabled(kTracingTriggers)) {
return std::nullopt;
}
std::string serialized_config;
if (!base::Base64Decode(kTracingTriggerRulesConfig.Get(),
&serialized_config)) {
return std::nullopt;
}
if (kTracingTriggerRulesCompressed.Get()) {
std::string decompressed_config;
if (!snappy::Uncompress(serialized_config.data(), serialized_config.size(),
&decompressed_config)) {
return std::nullopt;
}
serialized_config = std::move(decompressed_config);
}
perfetto::protos::gen::TracingTriggerRulesConfig config;
if (config.ParseFromString(serialized_config)) {
return config;
}
return std::nullopt;
}
bool IsFieldTracingEnabled() {
return base::FeatureList::IsEnabled(kFieldTracing);
}
} // namespace tracing