blob: ad4df511763ec04a487faabaa74619db5c5c7cd1 [file] [log] [blame]
// Copyright 2015 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/tracing/background_tracing_manager_impl.h"
#include <optional>
#include <utility>
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/functional/callback_helpers.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/location.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/field_trial_params.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/no_destructor.h"
#include "base/path_service.h"
#include "base/strings/stringprintf.h"
#include "base/task/bind_post_task.h"
#include "base/task/single_thread_task_runner.h"
#include "base/threading/sequence_bound.h"
#include "base/time/time.h"
#include "base/tracing/trace_time.h"
#include "base/uuid.h"
#include "base/values.h"
#include "build/build_config.h"
#include "components/tracing/common/background_tracing_state_manager.h"
#include "components/variations/hashing.h"
#include "content/browser/tracing/background_tracing_agent_client_impl.h"
#include "content/browser/tracing/background_tracing_rule.h"
#include "content/browser/tracing/trace_report_database.h"
#include "content/browser/tracing/trace_upload_list.h"
#include "content/browser/tracing/tracing_controller_impl.h"
#include "content/browser/tracing/triggers_data_source.h"
#include "content/common/child_process.mojom.h"
#include "content/public/browser/browser_child_process_host.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/child_process_data.h"
#include "content/public/browser/child_process_host.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_switches.h"
#include "net/base/network_change_notifier.h"
#include "services/tracing/public/cpp/trace_startup_config.h"
#include "services/tracing/public/cpp/tracing_features.h"
#include "third_party/zlib/google/compression_utils.h"
namespace content {
namespace {
// The time to live of a trace report is currently 14 days.
constexpr base::TimeDelta kTraceReportTimeToLive = base::Days(14);
// The time to live of uploaded trace content is 2 days.
constexpr base::TimeDelta kUploadedTraceContentTimeToLive = base::Days(2);
// We limit the overall number of traces.
constexpr size_t kMaxTraceContent = 200;
// We limit uploads of 1 trace per scenario over a period of 7 days. Since
// traces live in the database for longer than 7 days, their TTL doesn't affect
// this unless the database is manually cleared.
constexpr base::TimeDelta kMinTimeUntilNextUpload = base::Days(7);
// We limit the overall number of traces per scenario saved to the database at
// 100 per day.
constexpr size_t kMaxTracesPerScenario = 100;
constexpr base::TimeDelta kMaxTracesPerScenarioDuration = base::Days(1);
// |g_background_tracing_manager| is intentionally leaked on shutdown.
BackgroundTracingManager* g_background_tracing_manager = nullptr;
BackgroundTracingManagerImpl* g_background_tracing_manager_impl = nullptr;
void OpenDatabaseOnDatabaseTaskRunner(
TraceReportDatabase* database,
std::optional<base::FilePath> database_dir,
base::OnceCallback<void(BackgroundTracingManagerImpl::ScenarioCountMap,
std::optional<BaseTraceReport>,
bool)> on_database_created) {
if (database->is_initialized()) {
return;
}
bool success;
if (!database_dir) {
success = database->OpenDatabaseInMemoryForTesting(); // IN-TEST
} else {
success = database->OpenDatabase(*database_dir);
}
std::optional<NewTraceReport> report_to_upload =
database->GetNextReportPendingUpload();
GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(std::move(on_database_created),
database->GetScenarioCountsSince(
base::Time::Now() - kMaxTracesPerScenarioDuration),
std::move(report_to_upload), success));
}
void AddTraceOnDatabaseTaskRunner(
TraceReportDatabase* database,
std::string&& serialized_trace,
std::string&& serialized_system_profile,
BaseTraceReport base_report,
bool should_save_trace,
bool force_upload,
base::OnceCallback<void(std::optional<BaseTraceReport>, bool)>
on_trace_saved) {
if (!database->is_initialized()) {
return;
}
base::Time since = base::Time::Now() - kMinTimeUntilNextUpload;
auto upload_count = database->UploadCountSince(
base_report.scenario_name, base_report.upload_rule_name, since);
if (base_report.skip_reason == SkipUploadReason::kNoSkip && !force_upload &&
upload_count && *upload_count > 0) {
base_report.skip_reason = SkipUploadReason::kScenarioQuotaExceeded;
if (!should_save_trace) {
return;
}
}
std::string compressed_trace;
bool success = compression::GzipCompress(serialized_trace, &compressed_trace);
if (success) {
NewTraceReport trace_report = base_report;
trace_report.trace_content = std::move(compressed_trace);
trace_report.system_profile = std::move(serialized_system_profile);
success = database->AddTrace(trace_report);
}
auto report_to_upload = database->GetNextReportPendingUpload();
GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(std::move(on_trace_saved),
std::move(report_to_upload), success));
}
void OnUploadCompleteOnDatabaseTaskRunner(
TraceReportDatabase* database,
BaseTraceReport base_report,
base::OnceCallback<void(std::optional<BaseTraceReport>, bool)>
on_finalize_complete) {
base::Token uuid = base_report.uuid;
base::UmaHistogramSparse("Tracing.Background.Scenario.Upload",
variations::HashName(base_report.scenario_name));
std::optional<ClientTraceReport> next_report;
if (database->UploadComplete(uuid, base::Time::Now())) {
next_report = database->GetNextReportPendingUpload();
}
GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(std::move(on_finalize_complete),
std::move(next_report), true));
}
void GetProtoValueOnDatabaseTaskRunner(
TraceReportDatabase* database,
BaseTraceReport base_report,
base::OnceCallback<void(std::optional<std::string>,
std::optional<std::string>,
base::OnceClosure)> receive_callback,
base::OnceClosure upload_complete) {
base::Token uuid = base_report.uuid;
auto compressed_trace_content = database->GetTraceContent(uuid);
if (!compressed_trace_content) {
std::move(receive_callback)
.Run(std::nullopt, std::nullopt, base::NullCallback());
} else {
auto serialized_system_profile = database->GetSystemProfile(uuid);
std::move(receive_callback)
.Run(std::move(compressed_trace_content),
std::move(serialized_system_profile), std::move(upload_complete));
}
}
class PreferenceManagerImpl
: public BackgroundTracingManagerImpl::PreferenceManager {
public:
bool GetBackgroundStartupTracingEnabled() const override {
return tracing::TraceStartupConfig::GetInstance().IsEnabled() &&
tracing::TraceStartupConfig::GetInstance().GetSessionOwner() ==
tracing::TraceStartupConfig::SessionOwner::kBackgroundTracing;
}
};
// Emits background tracing metadata as a data source.
class BackgroundMetadataDataSource
: public perfetto::DataSource<BackgroundMetadataDataSource> {
public:
static constexpr bool kRequiresCallbacksUnderLock = false;
static void Register() {
perfetto::DataSourceDescriptor desc;
desc.set_name("org.chromium.background_scenario_metadata");
CHECK(perfetto::DataSource<BackgroundMetadataDataSource>::Register(desc));
}
static void EmitMetadata(TracingScenario* scenario) {
Trace([&](TraceContext ctx) {
auto packet = ctx.NewTracePacket();
packet->set_timestamp(
TRACE_TIME_TICKS_NOW().since_origin().InNanoseconds());
packet->set_timestamp_clock_id(base::tracing::kTraceClockId);
auto* chrome_metadata = packet->set_chrome_metadata();
scenario->GenerateMetadataProto(chrome_metadata);
packet->Finalize();
ctx.Flush();
});
}
};
} // namespace
// static
std::unique_ptr<BackgroundTracingManager>
BackgroundTracingManager::CreateInstance(TracingDelegate* delegate) {
return std::make_unique<BackgroundTracingManagerImpl>(delegate);
}
// static
BackgroundTracingManager& BackgroundTracingManager::GetInstance() {
CHECK_NE(nullptr, g_background_tracing_manager);
return *g_background_tracing_manager;
}
// static
void BackgroundTracingManager::SetInstance(
BackgroundTracingManager* tracing_manager) {
DCHECK(g_background_tracing_manager == nullptr || tracing_manager == nullptr);
g_background_tracing_manager = tracing_manager;
}
// static
void BackgroundTracingManagerImpl::RecordMetric(Metrics metric) {
UMA_HISTOGRAM_ENUMERATION("Tracing.Background.ScenarioState", metric,
Metrics::NUMBER_OF_BACKGROUND_TRACING_METRICS);
}
// static
BackgroundTracingManagerImpl& BackgroundTracingManagerImpl::GetInstance() {
CHECK_NE(nullptr, g_background_tracing_manager_impl);
return *g_background_tracing_manager_impl;
}
// static
void BackgroundTracingManagerImpl::ActivateForProcess(
int child_process_id,
mojom::ChildProcess* child_process) {
// NOTE: Called from any thread.
mojo::PendingRemote<tracing::mojom::BackgroundTracingAgentProvider>
pending_provider;
child_process->GetBackgroundTracingAgentProvider(
pending_provider.InitWithNewPipeAndPassReceiver());
GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(&BackgroundTracingManagerImpl::AddPendingAgent,
child_process_id, std::move(pending_provider)));
}
BackgroundTracingManagerImpl::BackgroundTracingManagerImpl(
TracingDelegate* delegate)
: delegate_(delegate),
state_manager_(delegate_->CreateStateManager()),
database_task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
{base::MayBlock(), base::TaskPriority::USER_VISIBLE,
base::TaskShutdownBehavior::BLOCK_SHUTDOWN})),
trace_database_(nullptr,
base::OnTaskRunnerDeleter(database_task_runner_)) {
BackgroundTracingManager::SetInstance(this);
NamedTriggerManager::SetInstance(this);
g_background_tracing_manager_impl = this;
preferences_ = std::make_unique<PreferenceManagerImpl>();
if (perfetto::Tracing::IsInitialized()) {
AddMetadataGeneratorFunction();
}
}
BackgroundTracingManagerImpl::~BackgroundTracingManagerImpl() {
DCHECK_EQ(this, g_background_tracing_manager_impl);
DisableScenarios();
BackgroundTracingManager::SetInstance(nullptr);
NamedTriggerManager::SetInstance(nullptr);
g_background_tracing_manager_impl = nullptr;
}
void BackgroundTracingManagerImpl::OpenDatabaseIfExists() {
if (trace_database_) {
return;
}
std::optional<base::FilePath> database_dir =
GetContentClient()->browser()->GetLocalTracesDirectory();
if (!database_dir.has_value()) {
return;
}
trace_database_ = {new TraceReportDatabase,
base::OnTaskRunnerDeleter(database_task_runner_)};
database_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
[](TraceReportDatabase* trace_database, base::FilePath path) {
trace_database->OpenDatabaseIfExists(path);
},
base::Unretained(trace_database_.get()), database_dir.value()));
}
void BackgroundTracingManagerImpl::GetAllTraceReports(
GetReportsCallback callback) {
if (!trace_database_) {
std::move(callback).Run({});
return;
}
database_task_runner_->PostTaskAndReplyWithResult(
FROM_HERE,
base::BindOnce(&TraceReportDatabase::GetAllReports,
base::Unretained(trace_database_.get())),
std::move(callback));
}
void BackgroundTracingManagerImpl::DeleteSingleTrace(
const base::Token& trace_uuid,
FinishedProcessingCallback callback) {
if (!trace_database_) {
std::move(callback).Run(false);
return;
}
database_task_runner_->PostTaskAndReplyWithResult(
FROM_HERE,
base::BindOnce(&TraceReportDatabase::DeleteTrace,
base::Unretained(trace_database_.get()), trace_uuid),
std::move(callback));
}
void BackgroundTracingManagerImpl::DeleteAllTraces(
TraceUploadList::FinishedProcessingCallback callback) {
if (!trace_database_) {
std::move(callback).Run(false);
return;
}
database_task_runner_->PostTaskAndReplyWithResult(
FROM_HERE,
base::BindOnce(&TraceReportDatabase::DeleteAllTraces,
base::Unretained(trace_database_.get())),
std::move(callback));
}
void BackgroundTracingManagerImpl::UserUploadSingleTrace(
const base::Token& trace_uuid,
TraceUploadList::FinishedProcessingCallback callback) {
if (!trace_database_) {
std::move(callback).Run(false);
return;
}
database_task_runner_->PostTaskAndReplyWithResult(
FROM_HERE,
base::BindOnce(&TraceReportDatabase::UserRequestedUpload,
base::Unretained(trace_database_.get()), trace_uuid),
std::move(callback));
}
void BackgroundTracingManagerImpl::DownloadTrace(const base::Token& trace_uuid,
GetProtoCallback callback) {
if (!trace_database_) {
std::move(callback).Run(std::nullopt);
return;
}
database_task_runner_->PostTaskAndReplyWithResult(
FROM_HERE,
base::BindOnce(&TraceReportDatabase::GetTraceContent,
base::Unretained(trace_database_.get()), trace_uuid),
base::BindOnce(
[](GetProtoCallback callback,
const std::optional<std::string>& result) {
if (result) {
std::move(callback).Run(base::span<const char>(*result));
} else {
std::move(callback).Run(std::nullopt);
}
},
std::move(callback)));
}
void BackgroundTracingManagerImpl::OnTraceDatabaseCreated(
ScenarioCountMap scenario_saved_counts,
std::optional<BaseTraceReport> trace_to_upload,
bool creation_result) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
scenario_saved_counts_ = std::move(scenario_saved_counts);
trace_report_to_upload_ = std::move(trace_to_upload);
if (!creation_result) {
RecordMetric(Metrics::DATABASE_INITIALIZATION_FAILED);
return;
}
CleanDatabase();
clean_database_timer_.Start(
FROM_HERE, base::Days(1),
base::BindRepeating(&BackgroundTracingManagerImpl::CleanDatabase,
weak_factory_.GetWeakPtr()));
}
void BackgroundTracingManagerImpl::OnTraceDatabaseUpdated(
ScenarioCountMap scenario_saved_counts) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
scenario_saved_counts_ = std::move(scenario_saved_counts);
}
void BackgroundTracingManagerImpl::OnTraceSaved(
const std::string& scenario_name,
std::optional<BaseTraceReport> trace_to_upload,
bool success) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
RecordMetric(success ? Metrics::SAVE_TRACE_SUCCEEDED
: Metrics::SAVE_TRACE_FAILED);
trace_report_to_upload_ = std::move(trace_to_upload);
if (success) {
++scenario_saved_counts_[scenario_name];
}
for (EnabledStateTestObserver* observer : background_tracing_observers_) {
observer->OnTraceSaved();
}
}
void BackgroundTracingManagerImpl::AddMetadataGeneratorFunction() {
BackgroundMetadataDataSource::Register();
TriggersDataSource::Register();
}
bool BackgroundTracingManagerImpl::RequestActivateScenario() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// Multi-scenarios sessions can't be initialized twice.
DCHECK(field_scenarios_.empty());
DCHECK(enabled_scenarios_.empty());
RecordMetric(Metrics::SCENARIO_ACTIVATION_REQUESTED);
// Bail on scenario activation if trigger rules are already setup to be
// forwarded to system tracing.
if (!trigger_rules_.empty()) {
return false;
}
// If we don't have a high resolution timer available, traces will be
// too inaccurate to be useful.
if (!base::TimeTicks::IsHighResolution()) {
RecordMetric(Metrics::SCENARIO_ACTION_FAILED_LOWRES_CLOCK);
return false;
}
return true;
}
void BackgroundTracingManagerImpl::DisableScenarios() {
if (active_scenario_) {
enabled_scenarios_.clear();
active_scenario_->Abort();
} else {
for (auto& scenario : enabled_scenarios_) {
scenario->Disable();
}
enabled_scenarios_.clear();
}
for (auto& rule : trigger_rules_) {
rule->Uninstall();
}
trigger_rules_.clear();
}
void BackgroundTracingManagerImpl::SetReceiveCallback(
ReceiveCallback receive_callback) {
receive_callback_ = std::move(receive_callback);
}
bool BackgroundTracingManagerImpl::InitializePerfettoTriggerRules(
const perfetto::protos::gen::TracingTriggerRulesConfig& config) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// Trigger rules can't be initialized twice.
DCHECK(trigger_rules_.empty());
// Bail on setting up trigger rules if scenarios are already enabled.
if (!enabled_scenarios_.empty()) {
return false;
}
if (!BackgroundTracingRule::Append(config.rules(), trigger_rules_)) {
return false;
}
for (auto& rule : trigger_rules_) {
rule->Install(base::BindRepeating([](const BackgroundTracingRule* rule) {
base::UmaHistogramSparse("Tracing.Background.Perfetto.Trigger",
variations::HashName(rule->rule_name()));
perfetto::Tracing::ActivateTriggers({rule->rule_name()},
/*ttl_ms=*/0);
return true;
}));
}
return true;
}
bool BackgroundTracingManagerImpl::InitializeFieldScenarios(
const perfetto::protos::gen::ChromeFieldTracingConfig& config,
DataFiltering data_filtering,
bool force_uploads,
size_t upload_limit_kb) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (!RequestActivateScenario()) {
return false;
}
force_uploads_ = force_uploads;
if (upload_limit_kb > 0) {
upload_limit_kb_ = upload_limit_kb;
}
bool requires_anonymized_data = (data_filtering != NO_DATA_FILTERING);
bool enable_package_name_filter =
(data_filtering == ANONYMIZE_DATA_AND_FILTER_PACKAGE_NAME);
InitializeTraceReportDatabase();
if (preferences_->GetBackgroundStartupTracingEnabled()) {
perfetto::protos::gen::ScenarioConfig scenario_config;
scenario_config.set_scenario_name("Startup");
*scenario_config.mutable_trace_config() =
tracing::TraceStartupConfig::GetDefaultBackgroundStartupConfig();
scenario_config.add_start_rules()->set_manual_trigger_name(
base::trace_event::kStartupTracingTriggerName);
scenario_config.add_upload_rules()->set_delay_ms(30000);
// Startup tracing was already requested earlier for this scenario.
auto startup_scenario = TracingScenario::Create(
scenario_config, requires_anonymized_data, enable_package_name_filter,
/*is_local_scenario=*/false,
/*request_startup_tracing=*/false, this);
field_scenarios_.push_back(std::move(startup_scenario));
enabled_scenarios_.push_back(field_scenarios_.back().get());
enabled_scenarios_.back()->Enable();
}
bool result = true;
for (const auto& scenario_config : config.scenarios()) {
auto scenario = TracingScenario::Create(
scenario_config, requires_anonymized_data,
/*is_local_scenario=*/false, enable_package_name_filter, true, this);
if (!scenario) {
base::UmaHistogramSparse(
"Tracing.Background.Scenario.Invalid",
variations::HashName(scenario_config.scenario_name()));
result = false;
continue;
}
field_scenarios_.push_back(std::move(scenario));
enabled_scenarios_.push_back(field_scenarios_.back().get());
enabled_scenarios_.back()->Enable();
}
MaybeConstructPendingAgents();
RecordMetric(Metrics::SCENARIO_ACTIVATED_SUCCESSFULLY);
return result;
}
std::vector<std::string> BackgroundTracingManagerImpl::AddPresetScenarios(
const perfetto::protos::gen::ChromeFieldTracingConfig& config,
DataFiltering data_filtering) {
return AddPresetScenariosImpl(config, data_filtering, false);
}
std::vector<std::string> BackgroundTracingManagerImpl::OverwritePresetScenarios(
const perfetto::protos::gen::ChromeFieldTracingConfig& config,
DataFiltering data_filtering) {
return AddPresetScenariosImpl(config, data_filtering, true);
}
std::vector<std::string> BackgroundTracingManagerImpl::AddPresetScenariosImpl(
const perfetto::protos::gen::ChromeFieldTracingConfig& config,
DataFiltering data_filtering,
bool overwrite_conflicts) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
bool enable_privacy_filter = (data_filtering != NO_DATA_FILTERING);
bool enable_package_name_filter =
(data_filtering == ANONYMIZE_DATA_AND_FILTER_PACKAGE_NAME);
std::vector<std::string> added_scenarios;
std::set<raw_ptr<TracingScenario>> conflicting_scenarios_set;
std::vector<std::unique_ptr<TracingScenario>> conflicting_scenarios;
for (const auto& scenario_config : config.scenarios()) {
auto scenario = TracingScenario::Create(
scenario_config, enable_privacy_filter, /*is_local_scenario=*/true,
enable_package_name_filter, true, this);
if (!scenario) {
base::UmaHistogramSparse(
"Tracing.Background.Scenario.Invalid",
variations::HashName(scenario_config.scenario_name()));
continue;
}
if (auto it = preset_scenarios_.find(scenario_config.scenario_name());
it != preset_scenarios_.end()) {
if (!overwrite_conflicts) {
continue;
}
if (active_scenario_ == it->second.get()) {
active_scenario_->Abort();
active_scenario_ = nullptr;
conflicting_scenarios_set.insert(it->second.get());
conflicting_scenarios.emplace_back(std::move(it->second));
} else if (it->second->current_state() !=
TracingScenario::State::kDisabled) {
it->second->Disable();
conflicting_scenarios_set.insert(it->second.get());
conflicting_scenarios.emplace_back(std::move(it->second));
}
}
added_scenarios.push_back(scenario->scenario_name());
preset_scenarios_[scenario->scenario_name()] = std::move(scenario);
}
if (!conflicting_scenarios.empty()) {
std::erase_if(enabled_scenarios_, [&](raw_ptr<TracingScenario> scenario) {
return conflicting_scenarios_set.contains(scenario);
});
}
conflicting_scenarios_set.clear();
return added_scenarios;
}
std::vector<traces_internals::mojom::ScenarioPtr>
BackgroundTracingManagerImpl::GetAllScenarios() const {
std::vector<traces_internals::mojom::ScenarioPtr> result;
auto toMojoScenario = [this](TracingScenario* scenario) {
auto new_scenario = traces_internals::mojom::Scenario::New();
new_scenario->scenario_name = scenario->scenario_name();
new_scenario->description = scenario->description();
new_scenario->is_local_scenario = scenario->is_local_scenario();
new_scenario->is_enabled = base::Contains(enabled_scenarios_, scenario);
new_scenario->current_state = scenario->current_state();
return new_scenario;
};
for (const auto& scenario : preset_scenarios_) {
result.push_back(toMojoScenario(scenario.second.get()));
}
for (const auto& scenario : field_scenarios_) {
result.push_back(toMojoScenario(scenario.get()));
}
return result;
}
bool BackgroundTracingManagerImpl::SetEnabledScenarios(
std::vector<std::string> enabled_scenarios) {
DisableScenarios();
InitializeTraceReportDatabase();
for (const std::string& hash : enabled_scenarios) {
auto it = preset_scenarios_.find(hash);
if (it == preset_scenarios_.end()) {
return false;
}
enabled_scenarios_.push_back(it->second.get());
if (!active_scenario_) {
it->second->Enable();
}
}
MaybeConstructPendingAgents();
return true;
}
std::vector<std::string> BackgroundTracingManagerImpl::GetEnabledScenarios()
const {
std::vector<std::string> scenario_hashes;
for (auto scenario : enabled_scenarios_) {
scenario_hashes.push_back(scenario->scenario_name());
}
return scenario_hashes;
}
void BackgroundTracingManagerImpl::InitializeTraceReportDatabase(
bool open_in_memory) {
std::optional<base::FilePath> database_dir;
if (!trace_database_) {
trace_database_ = {new TraceReportDatabase,
base::OnTaskRunnerDeleter(database_task_runner_)};
if (!open_in_memory) {
database_dir = GetContentClient()->browser()->GetLocalTracesDirectory();
if (!database_dir.has_value()) {
OnTraceDatabaseCreated({}, std::nullopt, false);
return;
}
}
}
database_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
OpenDatabaseOnDatabaseTaskRunner,
base::Unretained(trace_database_.get()), std::move(database_dir),
base::BindOnce(&BackgroundTracingManagerImpl::OnTraceDatabaseCreated,
weak_factory_.GetWeakPtr())));
}
bool BackgroundTracingManagerImpl::OnScenarioActive(
TracingScenario* active_scenario) {
DCHECK_EQ(active_scenario_, nullptr);
if (GetScenarioSavedCount(active_scenario->scenario_name()) >=
kMaxTracesPerScenario) {
return false;
}
auto now = base::TimeTicks::Now();
if (!delegate_->IsRecordingAllowed(active_scenario->privacy_filter_enabled(),
now)) {
return false;
}
scenario_start_time_ = now;
active_scenario_ = active_scenario;
base::UmaHistogramSparse(
"Tracing.Background.Scenario.Active",
variations::HashName(active_scenario->scenario_name()));
for (EnabledStateTestObserver* observer : background_tracing_observers_) {
observer->OnScenarioActive(active_scenario_->scenario_name());
}
for (auto& scenario : enabled_scenarios_) {
if (scenario.get() == active_scenario) {
continue;
}
scenario->Disable();
}
return true;
}
bool BackgroundTracingManagerImpl::OnScenarioIdle(
TracingScenario* idle_scenario) {
DCHECK_EQ(active_scenario_, idle_scenario);
active_scenario_ = nullptr;
base::UmaHistogramSparse(
"Tracing.Background.Scenario.Idle",
variations::HashName(idle_scenario->scenario_name()));
for (EnabledStateTestObserver* observer : background_tracing_observers_) {
observer->OnScenarioIdle(idle_scenario->scenario_name());
}
for (auto& scenario : enabled_scenarios_) {
scenario->Enable();
}
return delegate_->IsRecordingAllowed(idle_scenario->privacy_filter_enabled(),
scenario_start_time_);
}
void BackgroundTracingManagerImpl::OnScenarioError(
TracingScenario* scenario,
perfetto::TracingError error) {
base::UmaHistogramSparse("Tracing.Background.Scenario.Error",
variations::HashName(scenario->scenario_name()));
DLOG(ERROR) << "Background tracing error: " << error.message;
}
bool BackgroundTracingManagerImpl::OnScenarioCloned(
TracingScenario* cloned_scenario) {
DCHECK_EQ(active_scenario_, cloned_scenario);
base::UmaHistogramSparse(
"Tracing.Background.Scenario.Clone",
variations::HashName(cloned_scenario->scenario_name()));
return delegate_->IsRecordingAllowed(
cloned_scenario->privacy_filter_enabled(), scenario_start_time_);
}
void BackgroundTracingManagerImpl::OnScenarioRecording(
TracingScenario* scenario) {
DCHECK_EQ(active_scenario_, scenario);
base::UmaHistogramSparse("Tracing.Background.Scenario.Recording",
variations::HashName(scenario->scenario_name()));
BackgroundMetadataDataSource::EmitMetadata(scenario);
OnStartTracingDone();
}
void BackgroundTracingManagerImpl::SaveTrace(
TracingScenario* scenario,
base::Token trace_uuid,
const BackgroundTracingRule* triggered_rule,
std::string&& trace_data) {
OnProtoDataComplete(
std::move(trace_data), scenario->scenario_name(),
triggered_rule->rule_name(), triggered_rule->triggered_value(),
scenario->privacy_filter_enabled(), scenario->is_local_scenario(),
/*force_upload=*/force_uploads_, trace_uuid);
}
bool BackgroundTracingManagerImpl::HasActiveScenario() {
return active_scenario_ != nullptr;
}
bool BackgroundTracingManagerImpl::HasTraceToUpload() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (!trace_report_to_upload_) {
return false;
}
#if BUILDFLAG(IS_ANDROID)
// Send the logs only when the trace size is within limits. If the connection
// type changes and we have a bigger than expected trace, then the next time
// service asks us when wifi is available, the trace will be sent.
auto type = net::NetworkChangeNotifier::GetConnectionType();
if (net::NetworkChangeNotifier::IsConnectionCellular(type) &&
trace_report_to_upload_->total_size > upload_limit_network_kb_ * 1000) {
RecordMetric(Metrics::LARGE_UPLOAD_WAITING_TO_RETRY);
return false;
}
#endif
return true;
}
void BackgroundTracingManagerImpl::GetTraceToUpload(
base::OnceCallback<void(std::optional<std::string>,
std::optional<std::string>,
base::OnceClosure)> receive_callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (!trace_report_to_upload_) {
std::move(receive_callback)
.Run(std::nullopt, std::nullopt, base::NullCallback());
return;
}
DCHECK(trace_database_);
BaseTraceReport trace_report = *std::move(trace_report_to_upload_);
trace_report_to_upload_.reset();
auto upload_complete_callback = base::BindPostTask(
database_task_runner_,
base::BindOnce(
OnUploadCompleteOnDatabaseTaskRunner,
base::Unretained(trace_database_.get()), trace_report,
base::BindOnce(&BackgroundTracingManagerImpl::OnFinalizeComplete,
weak_factory_.GetWeakPtr())));
database_task_runner_->PostTask(
FROM_HERE, base::BindOnce(GetProtoValueOnDatabaseTaskRunner,
base::Unretained(trace_database_.get()),
trace_report, std::move(receive_callback),
std::move(upload_complete_callback)));
}
void BackgroundTracingManagerImpl::OnFinalizeComplete(
std::optional<BaseTraceReport> trace_to_upload,
bool success) {
trace_report_to_upload_ = std::move(trace_to_upload);
if (success) {
BackgroundTracingManagerImpl::RecordMetric(Metrics::UPLOAD_SUCCEEDED);
} else {
BackgroundTracingManagerImpl::RecordMetric(Metrics::UPLOAD_FAILED);
}
}
void BackgroundTracingManagerImpl::AddEnabledStateObserverForTesting(
BackgroundTracingManager::EnabledStateTestObserver* observer) {
// Ensure that this code is called on the UI thread, except for
// tests where a UI thread might not have been initialized at this point.
DCHECK(
content::BrowserThread::CurrentlyOn(content::BrowserThread::UI) ||
!content::BrowserThread::IsThreadInitialized(content::BrowserThread::UI));
background_tracing_observers_.insert(observer);
}
void BackgroundTracingManagerImpl::RemoveEnabledStateObserverForTesting(
BackgroundTracingManager::EnabledStateTestObserver* observer) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
background_tracing_observers_.erase(observer);
}
void BackgroundTracingManagerImpl::AddAgent(
tracing::mojom::BackgroundTracingAgent* agent) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
agents_.insert(agent);
for (AgentObserver* observer : agent_observers_) {
observer->OnAgentAdded(agent);
}
}
void BackgroundTracingManagerImpl::RemoveAgent(
tracing::mojom::BackgroundTracingAgent* agent) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
for (AgentObserver* observer : agent_observers_) {
observer->OnAgentRemoved(agent);
}
agents_.erase(agent);
}
void BackgroundTracingManagerImpl::AddAgentObserver(AgentObserver* observer) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
agent_observers_.insert(observer);
MaybeConstructPendingAgents();
for (tracing::mojom::BackgroundTracingAgent* agent : agents_) {
observer->OnAgentAdded(agent);
}
}
void BackgroundTracingManagerImpl::RemoveAgentObserver(
AgentObserver* observer) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
agent_observers_.erase(observer);
for (tracing::mojom::BackgroundTracingAgent* agent : agents_) {
observer->OnAgentRemoved(agent);
}
}
bool BackgroundTracingManagerImpl::IsTracingForTesting() {
return active_scenario_->current_state() ==
TracingScenario::State::kRecording;
}
void BackgroundTracingManagerImpl::SaveTraceForTesting(
std::string&& serialized_trace,
const std::string& scenario_name,
const std::string& rule_name,
const base::Token& uuid) {
InitializeTraceReportDatabase(true);
OnProtoDataComplete(std::move(serialized_trace), scenario_name, rule_name,
/*rule_value=*/std::nullopt,
/*privacy_filter_enabled*/ true,
/*is_local_scenario=*/false,
/*force_upload=*/force_uploads_, uuid);
}
void BackgroundTracingManagerImpl::SetUploadLimitsForTesting(
size_t upload_limit_kb,
size_t upload_limit_network_kb) {
upload_limit_kb_ = upload_limit_kb;
upload_limit_network_kb_ = upload_limit_network_kb;
}
void BackgroundTracingManagerImpl::SetPreferenceManagerForTesting(
std::unique_ptr<PreferenceManager> preferences) {
preferences_ = std::move(preferences);
}
size_t BackgroundTracingManagerImpl::GetScenarioSavedCount(
const std::string& scenario_name) {
auto it = scenario_saved_counts_.find(scenario_name);
if (it != scenario_saved_counts_.end()) {
return it->second;
}
return 0;
}
void BackgroundTracingManagerImpl::OnProtoDataComplete(
std::string&& serialized_trace,
const std::string& scenario_name,
const std::string& rule_name,
std::optional<int32_t> rule_value,
bool privacy_filter_enabled,
bool is_local_scenario,
bool force_upload,
const base::Token& uuid) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
for (EnabledStateTestObserver* observer : background_tracing_observers_) {
observer->OnTraceReceived(serialized_trace);
}
if (!receive_callback_) {
DCHECK(trace_database_);
base::UmaHistogramSparse("Tracing.Background.Scenario.SaveTrace",
variations::HashName(scenario_name));
SkipUploadReason skip_reason = SkipUploadReason::kNoSkip;
if (!privacy_filter_enabled) {
skip_reason = SkipUploadReason::kNotAnonymized;
} else if (is_local_scenario) {
skip_reason = SkipUploadReason::kLocalScenario;
} else if (serialized_trace.size() > upload_limit_kb_ * 1024) {
skip_reason = SkipUploadReason::kSizeLimitExceeded;
}
bool should_save_trace = delegate_->ShouldSaveUnuploadedTrace();
if (skip_reason != SkipUploadReason::kNoSkip && !should_save_trace) {
return;
}
BackgroundTracingManagerImpl::RecordMetric(Metrics::FINALIZATION_STARTED);
BaseTraceReport base_report;
base_report.uuid = uuid;
base_report.creation_time = base::Time::Now();
base_report.scenario_name = scenario_name;
base_report.upload_rule_name = rule_name;
base_report.upload_rule_value = rule_value;
base_report.total_size = serialized_trace.size();
base_report.skip_reason = skip_reason;
std::string serialized_system_profile =
delegate_->RecordSerializedSystemProfileMetrics();
database_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
AddTraceOnDatabaseTaskRunner,
base::Unretained(trace_database_.get()),
std::move(serialized_trace), std::move(serialized_system_profile),
std::move(base_report), should_save_trace, force_upload,
base::BindOnce(&BackgroundTracingManagerImpl::OnTraceSaved,
weak_factory_.GetWeakPtr(), scenario_name)));
} else {
BackgroundTracingManagerImpl::RecordMetric(
Metrics::FINALIZATION_STARTED_WITH_LOCAL_OUTPUT);
receive_callback_.Run(
uuid.ToString() + ".perfetto.gz", std::move(serialized_trace),
base::BindOnce(&BackgroundTracingManagerImpl::OnFinalizeComplete,
weak_factory_.GetWeakPtr(), std::nullopt));
}
}
void BackgroundTracingManagerImpl::AddNamedTriggerObserver(
const std::string& trigger_name,
BackgroundTracingRule* observer) {
named_trigger_observers_[trigger_name].AddObserver(observer);
}
void BackgroundTracingManagerImpl::RemoveNamedTriggerObserver(
const std::string& trigger_name,
BackgroundTracingRule* observer) {
named_trigger_observers_[trigger_name].RemoveObserver(observer);
}
bool BackgroundTracingManagerImpl::DoEmitNamedTrigger(
const std::string& trigger_name,
std::optional<int32_t> value,
uint64_t flow_id) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
auto it = named_trigger_observers_.find(trigger_name);
if (it == named_trigger_observers_.end()) {
return false;
}
for (BackgroundTracingRule& obs : it->second) {
if (obs.OnRuleTriggered(value, flow_id)) {
TRACE_EVENT_INSTANT("toplevel,latency", "NamedTrigger",
perfetto::Flow::Global(flow_id));
return true;
}
}
return false;
}
void BackgroundTracingManagerImpl::InvalidateTriggersCallbackForTesting() {
named_trigger_observers_.clear();
}
void BackgroundTracingManagerImpl::OnStartTracingDone() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
for (EnabledStateTestObserver* observer : background_tracing_observers_) {
observer->OnTraceStarted();
}
}
void BackgroundTracingManagerImpl::GenerateMetadataProto(
perfetto::protos::pbzero::ChromeMetadataPacket* metadata,
bool privacy_filtering_enabled) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (active_scenario_) {
active_scenario_->GenerateMetadataProto(metadata);
}
}
void BackgroundTracingManagerImpl::AbortScenarioForTesting() {
if (active_scenario_) {
active_scenario_->Abort();
}
}
void BackgroundTracingManagerImpl::CleanDatabase() {
DCHECK(trace_database_);
database_task_runner_->PostTaskAndReplyWithResult(
FROM_HERE,
base::BindOnce(
[](TraceReportDatabase* trace_database) {
// Trace payload is cleared on a more frequent basis.
trace_database->DeleteOldTraceContent(kMaxTraceContent);
// The reports entries are kept (without the payload) for longer to
// track upload quotas.
trace_database->DeleteTraceReportsOlderThan(kTraceReportTimeToLive);
trace_database->DeleteUploadedTraceContentOlderThan(
kUploadedTraceContentTimeToLive);
return trace_database->GetScenarioCountsSince(
base::Time::Now() - kMaxTracesPerScenarioDuration);
},
base::Unretained(trace_database_.get())),
base::BindOnce(&BackgroundTracingManagerImpl::OnTraceDatabaseUpdated,
weak_factory_.GetWeakPtr()));
}
void BackgroundTracingManagerImpl::DeleteTracesInDateRange(base::Time start,
base::Time end) {
// The trace report database needs to exist for clean up. Avoid creating or
// initializing the trace report database to perform a database clean up.
std::optional<base::FilePath> database_dir;
if (!trace_database_) {
database_dir = GetContentClient()->browser()->GetLocalTracesDirectory();
if (database_dir.has_value()) {
return;
}
trace_database_ = {new TraceReportDatabase,
base::OnTaskRunnerDeleter(database_task_runner_)};
}
auto on_database_updated =
base::BindOnce(&BackgroundTracingManagerImpl::OnTraceDatabaseUpdated,
weak_factory_.GetWeakPtr());
database_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
[](TraceReportDatabase* trace_database,
std::optional<base::FilePath> database_dir, base::Time start,
base::Time end,
base::OnceCallback<void(ScenarioCountMap)> on_database_updated) {
if (database_dir.has_value() &&
!trace_database->OpenDatabaseIfExists(database_dir.value())) {
return;
}
if (!trace_database->is_initialized()) {
return;
}
if (trace_database->DeleteTracesInDateRange(start, end)) {
GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(
std::move(on_database_updated),
trace_database->GetScenarioCountsSince(
base::Time::Now() - kMaxTracesPerScenarioDuration)));
} else {
RecordMetric(Metrics::DATABASE_CLEANUP_FAILED);
}
},
base::Unretained(trace_database_.get()), database_dir, start, end,
std::move(on_database_updated)));
}
// static
void BackgroundTracingManagerImpl::AddPendingAgent(
int child_process_id,
mojo::PendingRemote<tracing::mojom::BackgroundTracingAgentProvider>
pending_provider) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// Delay agent initialization until we have an interested AgentObserver.
// We set disconnect handler for cleanup when the tracing target is closed.
mojo::Remote<tracing::mojom::BackgroundTracingAgentProvider> provider(
std::move(pending_provider));
provider.set_disconnect_handler(base::BindOnce(
&BackgroundTracingManagerImpl::ClearPendingAgent, child_process_id));
GetInstance().pending_agents_[child_process_id] = std::move(provider);
GetInstance().MaybeConstructPendingAgents();
}
// static
void BackgroundTracingManagerImpl::ClearPendingAgent(int child_process_id) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
GetInstance().pending_agents_.erase(child_process_id);
}
void BackgroundTracingManagerImpl::MaybeConstructPendingAgents() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (agent_observers_.empty() && enabled_scenarios_.empty()) {
return;
}
for (auto& pending_agent : pending_agents_) {
pending_agent.second.set_disconnect_handler(base::OnceClosure());
BackgroundTracingAgentClientImpl::Create(pending_agent.first,
std::move(pending_agent.second));
}
pending_agents_.clear();
}
} // namespace content
PERFETTO_DEFINE_DATA_SOURCE_STATIC_MEMBERS_WITH_ATTRS(
COMPONENT_EXPORT(TRACING_CPP),
content::BackgroundMetadataDataSource);