| // 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/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_macros.h" |
| #include "base/no_destructor.h" |
| #include "base/path_service.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "base/threading/sequence_bound.h" |
| #include "base/time/time.h" |
| #include "base/uuid.h" |
| #include "base/values.h" |
| #include "build/build_config.h" |
| #include "components/tracing/common/trace_startup_config.h" |
| #include "components/variations/hashing.h" |
| #include "content/browser/tracing/background_startup_tracing_observer.h" |
| #include "content/browser/tracing/background_tracing_active_scenario.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/trace_report_database.h" |
| #include "content/browser/tracing/trace_report/trace_upload_list.h" |
| #include "content/browser/tracing/tracing_controller_impl.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/browser/tracing_delegate.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/perfetto/trace_event_data_source.h" |
| #include "services/tracing/public/cpp/trace_event_agent.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 is currently 14 days. |
| const base::TimeDelta kTraceTimeToLive = base::Days(14); |
| // 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. |
| const base::TimeDelta kMinTimeUntilNextUpload = base::Days(7); |
| // We limit the overall number of traces per scenario saved to the database at |
| // 20. When traces are deleted after their TTL, it leaves more capacity for new |
| // traces. |
| const size_t kMaxTracesPerScenario = 20; |
| |
| const char kBackgroundTracingConfig[] = "config"; |
| |
| // |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; |
| if (base::FeatureList::IsEnabled(kBackgroundTracingDatabase)) { |
| report_to_upload = database->GetNextReportPendingUpload(); |
| } else { |
| // Traces pending upload from previous sessions have timed out. |
| database->AllPendingUploadSkipped(SkipUploadReason::kUploadTimedOut); |
| } |
| GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, base::BindOnce(std::move(on_database_created), |
| database->GetScenarioCounts(), |
| 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 is_crash_scenario, |
| base::OnceCallback<void(std::optional<NewTraceReport>, 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, since); |
| if (base_report.skip_reason == SkipUploadReason::kNoSkip && |
| !is_crash_scenario && 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); |
| std::optional<NewTraceReport> report_to_upload; |
| if (success) { |
| UMA_HISTOGRAM_COUNTS_100000("Tracing.Background.CompressedTraceSizeInKB", |
| compressed_trace.size() / 1024); |
| |
| if (base::FeatureList::IsEnabled(kBackgroundTracingDatabase)) { |
| 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); |
| } else { |
| // When the database is disabled, we still store the base report without |
| // the trace content proto to enable tracking of trace upload limits. |
| success = database->AddTrace(base_report); |
| if (success && base_report.skip_reason == SkipUploadReason::kNoSkip) { |
| report_to_upload = std::move(base_report); |
| report_to_upload->trace_content = std::move(compressed_trace); |
| report_to_upload->system_profile = std::move(serialized_system_profile); |
| } |
| } |
| } |
| if (base::FeatureList::IsEnabled(kBackgroundTracingDatabase)) { |
| report_to_upload = database->GetNextReportPendingUpload(); |
| } |
| GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, base::BindOnce(std::move(on_trace_saved), |
| std::move(report_to_upload), success)); |
| } |
| |
| void GetProtoValueOnDatabaseTaskRunner( |
| TraceReportDatabase* database, |
| base::Token uuid, |
| base::OnceCallback<void(std::optional<std::string>, |
| std::optional<std::string>)> receive_callback, |
| base::OnceCallback<void(std::optional<BaseTraceReport>, bool)> |
| on_finalize_complete) { |
| DCHECK(base::FeatureList::IsEnabled(kBackgroundTracingDatabase)); |
| auto trace_content = database->GetTraceContent(uuid); |
| auto serialized_system_profile = database->GetSystemProfile(uuid); |
| std::optional<ClientTraceReport> next_report; |
| if (trace_content) { |
| 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), |
| trace_content.has_value())); |
| std::move(receive_callback) |
| .Run(std::move(trace_content), std::move(serialized_system_profile)); |
| } |
| |
| } // namespace |
| |
| BASE_FEATURE(kBackgroundTracingDatabase, |
| "BackgroundTracingDatabase", |
| base::FEATURE_DISABLED_BY_DEFAULT); |
| |
| // static |
| const char BackgroundTracingManager::kContentTriggerConfig[] = |
| "content-trigger-config"; |
| |
| // static |
| std::unique_ptr<BackgroundTracingManager> |
| BackgroundTracingManager::CreateInstance() { |
| return std::make_unique<BackgroundTracingManagerImpl>(); |
| } |
| |
| // 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() |
| : delegate_(GetContentClient()->browser()->GetTracingDelegate()), |
| 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; |
| BackgroundStartupTracingObserver::GetInstance(); |
| } |
| |
| BackgroundTracingManagerImpl::~BackgroundTracingManagerImpl() { |
| DCHECK_EQ(this, g_background_tracing_manager_impl); |
| if (active_scenario_) { |
| active_scenario_->Abort(); |
| } else { |
| for (auto& scenario : scenarios_) { |
| scenario->Disable(); |
| } |
| } |
| if (legacy_active_scenario_) { |
| legacy_active_scenario_->AbortScenario(); |
| } |
| 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; |
| } |
| 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<NewTraceReport> 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() { |
| auto* metadata_source = tracing::TraceEventMetadataSource::GetInstance(); |
| metadata_source->AddGeneratorFunction( |
| base::BindRepeating(&BackgroundTracingManagerImpl::GenerateMetadataProto, |
| base::Unretained(this))); |
| } |
| |
| bool BackgroundTracingManagerImpl::RequestActivateScenario() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| RecordMetric(Metrics::SCENARIO_ACTIVATION_REQUESTED); |
| // Multi-scenarios sessions can't be initialized twice. |
| DCHECK(scenarios_.empty()); |
| |
| if (legacy_active_scenario_ && |
| (legacy_active_scenario_->state() != |
| BackgroundTracingActiveScenario::State::kIdle)) { |
| 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::SetReceiveCallback( |
| ReceiveCallback receive_callback) { |
| receive_callback_ = std::move(receive_callback); |
| } |
| |
| bool BackgroundTracingManagerImpl::InitializeScenarios( |
| const perfetto::protos::gen::ChromeFieldTracingConfig& config, |
| DataFiltering data_filtering) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (!RequestActivateScenario()) { |
| return false; |
| } |
| |
| requires_anonymized_data_ = (data_filtering != NO_DATA_FILTERING); |
| bool enable_package_name_filter = |
| (data_filtering == ANONYMIZE_DATA_AND_FILTER_PACKAGE_NAME); |
| InitializeTraceReportDatabase(); |
| |
| for (const auto& scenario_config : config.scenarios()) { |
| auto scenario = |
| TracingScenario::Create(scenario_config, requires_anonymized_data_, |
| enable_package_name_filter, this); |
| if (!scenario) { |
| return false; |
| } |
| scenarios_.push_back(std::move(scenario)); |
| scenarios_.back()->Enable(); |
| } |
| RecordMetric(Metrics::SCENARIO_ACTIVATED_SUCCESSFULLY); |
| return true; |
| } |
| |
| bool BackgroundTracingManagerImpl::SetActiveScenario( |
| std::unique_ptr<BackgroundTracingConfig> config, |
| DataFiltering data_filtering) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| std::unique_ptr<BackgroundTracingConfigImpl> config_impl( |
| static_cast<BackgroundTracingConfigImpl*>(config.release())); |
| config_impl = BackgroundStartupTracingObserver::GetInstance() |
| .IncludeStartupConfigIfNeeded(std::move(config_impl)); |
| bool startup_tracing_enabled = BackgroundStartupTracingObserver::GetInstance() |
| .enabled_in_current_session(); |
| if (startup_tracing_enabled) { |
| // Anonymize data for startup tracing by default. We currently do not |
| // support storing the config in preferences for next session. |
| data_filtering = DataFiltering::ANONYMIZE_DATA; |
| } |
| if (!config_impl) { |
| return false; |
| } |
| |
| if (!RequestActivateScenario()) { |
| return false; |
| } |
| |
| #if !BUILDFLAG(USE_PERFETTO_CLIENT_LIBRARY) |
| // If startup config was not set and we're not a SYSTEM scenario (system |
| // might already have started a trace in the background) but tracing was |
| // enabled, then do not set any scenario. |
| if (base::trace_event::TraceLog::GetInstance()->IsEnabled() && |
| !startup_tracing_enabled && |
| config_impl->tracing_mode() != BackgroundTracingConfigImpl::SYSTEM) { |
| return false; |
| } |
| #endif |
| |
| if (config_impl->upload_limit_kb()) { |
| upload_limit_kb_ = *config_impl->upload_limit_kb(); |
| } |
| if (config_impl->upload_limit_network_kb()) { |
| upload_limit_network_kb_ = *config_impl->upload_limit_network_kb(); |
| } |
| |
| requires_anonymized_data_ = (data_filtering != NO_DATA_FILTERING); |
| config_impl->set_requires_anonymized_data(requires_anonymized_data_); |
| |
| bool enable_package_name_filter = |
| (data_filtering == ANONYMIZE_DATA_AND_FILTER_PACKAGE_NAME); |
| config_impl->SetPackageNameFilteringEnabled(enable_package_name_filter); |
| |
| // TODO(oysteine): Retry when time_until_allowed has elapsed. |
| if (delegate_ && |
| !delegate_->OnBackgroundTracingActive(requires_anonymized_data_)) { |
| return false; |
| } |
| |
| legacy_active_scenario_ = std::make_unique<BackgroundTracingActiveScenario>( |
| std::move(config_impl), delegate_.get(), |
| base::BindOnce(&BackgroundTracingManagerImpl::OnScenarioAborted, |
| base::Unretained(this))); |
| for (EnabledStateTestObserver* observer : background_tracing_observers_) { |
| observer->OnScenarioActive( |
| legacy_active_scenario_->GetConfig()->scenario_name()); |
| } |
| |
| InitializeTraceReportDatabase(); |
| |
| if (startup_tracing_enabled) { |
| RecordMetric(Metrics::STARTUP_SCENARIO_TRIGGERED); |
| DoEmitNamedTrigger(kStartupTracingTriggerName); |
| } |
| |
| legacy_active_scenario_->StartTracingIfConfigNeedsIt(); |
| RecordMetric(Metrics::SCENARIO_ACTIVATED_SUCCESSFULLY); |
| |
| return true; |
| } |
| |
| 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; |
| } |
| if (delegate_ && |
| !delegate_->OnBackgroundTracingActive(requires_anonymized_data_)) { |
| return false; |
| } |
| active_scenario_ = active_scenario; |
| UMA_HISTOGRAM_SPARSE("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 : 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; |
| UMA_HISTOGRAM_SPARSE("Tracing.Background.Scenario.Idle", |
| variations::HashName(idle_scenario->scenario_name())); |
| for (EnabledStateTestObserver* observer : background_tracing_observers_) { |
| observer->OnScenarioIdle(idle_scenario->scenario_name()); |
| } |
| bool is_allowed_finalization = |
| !delegate_ || |
| delegate_->OnBackgroundTracingIdle(requires_anonymized_data_); |
| for (auto& scenario : scenarios_) { |
| scenario->Enable(); |
| } |
| return is_allowed_finalization; |
| } |
| |
| void BackgroundTracingManagerImpl::OnScenarioRecording( |
| TracingScenario* scenario) { |
| DCHECK_EQ(active_scenario_, scenario); |
| UMA_HISTOGRAM_SPARSE("Tracing.Background.Scenario.Recording", |
| variations::HashName(scenario->scenario_name())); |
| 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_id(), /*is_crash_scenario=*/false, |
| trace_uuid); |
| } |
| |
| bool BackgroundTracingManagerImpl::HasActiveScenario() { |
| return legacy_active_scenario_ != nullptr || active_scenario_ != nullptr; |
| } |
| |
| bool BackgroundTracingManagerImpl::HasTraceToUpload() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| // 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. If we did |
| // collect a trace that is bigger than expected, then we will end up never |
| // uploading, and drop the trace. This should never happen because the trace |
| // buffer limits are set appropriately. |
| if (!trace_report_to_upload_) { |
| return false; |
| } |
| if (trace_report_to_upload_->total_size <= GetTraceUploadLimitKb() * 1024) { |
| return true; |
| } |
| RecordMetric(Metrics::LARGE_UPLOAD_WAITING_TO_RETRY); |
| return false; |
| } |
| |
| void BackgroundTracingManagerImpl::GetTraceToUpload( |
| base::OnceCallback<void(std::optional<std::string>, |
| std::optional<std::string>)> receive_callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (!trace_report_to_upload_) { |
| std::move(receive_callback).Run(std::nullopt, std::nullopt); |
| return; |
| } |
| |
| // Trace content was kept in memory. |
| if (!trace_report_to_upload_->trace_content.empty()) { |
| std::move(receive_callback) |
| .Run(std::move(trace_report_to_upload_->trace_content), |
| std::move(trace_report_to_upload_->system_profile)); |
| database_task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| [](TraceReportDatabase* trace_database, const base::Token& uuid) { |
| trace_database->UploadComplete(uuid, base::Time::Now()); |
| }, |
| base::Unretained(trace_database_.get()), |
| trace_report_to_upload_->uuid)); |
| OnFinalizeComplete(std::nullopt, true); |
| return; |
| } |
| |
| DCHECK(trace_database_); |
| BaseTraceReport trace_report = *std::move(trace_report_to_upload_); |
| database_task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| GetProtoValueOnDatabaseTaskRunner, |
| base::Unretained(trace_database_.get()), trace_report.uuid, |
| std::move(receive_callback), |
| base::BindOnce(&BackgroundTracingManagerImpl::OnFinalizeComplete, |
| weak_factory_.GetWeakPtr()))); |
| } |
| |
| 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); |
| } |
| } |
| |
| BackgroundTracingActiveScenario* |
| BackgroundTracingManagerImpl::GetActiveScenarioForTesting() { |
| DCHECK(legacy_active_scenario_); |
| return legacy_active_scenario_.get(); |
| } |
| |
| bool BackgroundTracingManagerImpl::IsTracingForTesting() { |
| if (legacy_active_scenario_) { |
| return legacy_active_scenario_->state() == |
| BackgroundTracingActiveScenario::State::kTracing; |
| } else if (active_scenario_) { |
| return active_scenario_->current_state() == |
| TracingScenario::State::kRecording; |
| } |
| return false; |
| } |
| |
| 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, |
| /*is_crash_scenario=*/false, uuid); |
| } |
| |
| 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, |
| bool is_crash_scenario, |
| 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_); |
| |
| UMA_HISTOGRAM_SPARSE("Tracing.Background.Scenario.SaveTrace", |
| variations::HashName(scenario_name)); |
| |
| SkipUploadReason skip_reason = SkipUploadReason::kNoSkip; |
| if (!requires_anonymized_data_) { |
| skip_reason = SkipUploadReason::kNotAnonymized; |
| } else if (serialized_trace.size() > upload_limit_kb_ * 1024) { |
| skip_reason = SkipUploadReason::kSizeLimitExceeded; |
| } |
| bool should_save_trace = |
| !delegate_ || delegate_->ShouldSaveUnuploadedTrace(); |
| if (skip_reason != SkipUploadReason::kNoSkip && !should_save_trace) { |
| return; |
| } |
| BackgroundTracingManagerImpl::RecordMetric(Metrics::FINALIZATION_STARTED); |
| UMA_HISTOGRAM_COUNTS_100000("Tracing.Background.FinalizingTraceSizeInKB2", |
| serialized_trace.size() / 1024); |
| |
| 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.total_size = serialized_trace.size(); |
| base_report.skip_reason = skip_reason; |
| |
| std::string serialized_system_profile; |
| if (system_profile_recorder_) { |
| serialized_system_profile = system_profile_recorder_.Run(); |
| } |
| |
| 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, is_crash_scenario, |
| base::BindOnce(&BackgroundTracingManagerImpl::OnTraceSaved, |
| weak_factory_.GetWeakPtr(), scenario_name))); |
| } else { |
| BackgroundTracingManagerImpl::RecordMetric( |
| Metrics::FINALIZATION_STARTED_WITH_LOCAL_OUTPUT); |
| receive_callback_.Run( |
| std::move(serialized_trace), |
| base::BindOnce(&BackgroundTracingManagerImpl::OnFinalizeComplete, |
| weak_factory_.GetWeakPtr(), std::nullopt)); |
| } |
| } |
| |
| std::unique_ptr<content::BackgroundTracingConfig> |
| BackgroundTracingManagerImpl::GetBackgroundTracingConfig( |
| const std::string& trial_name) { |
| std::string config_text = |
| base::GetFieldTrialParamValue(trial_name, kBackgroundTracingConfig); |
| if (config_text.empty()) |
| return nullptr; |
| |
| auto value = base::JSONReader::Read(config_text); |
| if (!value) |
| return nullptr; |
| |
| if (!value->is_dict()) |
| return nullptr; |
| |
| return BackgroundTracingConfig::FromDict(std::move(*value).TakeDict()); |
| } |
| |
| void BackgroundTracingManagerImpl::SetSystemProfileRecorder( |
| base::RepeatingCallback<std::string()> recorder) { |
| system_profile_recorder_ = std::move(recorder); |
| } |
| |
| void BackgroundTracingManagerImpl::SetNamedTriggerCallback( |
| const std::string& trigger_name, |
| base::RepeatingCallback<bool()> callback) { |
| if (!callback) { |
| named_trigger_callbacks_.erase(trigger_name); |
| } else { |
| named_trigger_callbacks_.emplace(trigger_name, std::move(callback)); |
| } |
| } |
| |
| bool BackgroundTracingManagerImpl::DoEmitNamedTrigger( |
| const std::string& trigger_name) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| auto it = named_trigger_callbacks_.find(trigger_name); |
| if (it == named_trigger_callbacks_.end()) { |
| return false; |
| } |
| return it->second.Run(); |
| } |
| |
| void BackgroundTracingManagerImpl::InvalidateTriggersCallbackForTesting() { |
| named_trigger_callbacks_.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 (legacy_active_scenario_) { |
| legacy_active_scenario_->GenerateMetadataProto(metadata); |
| } else if (active_scenario_) { |
| active_scenario_->GenerateMetadataProto(metadata); |
| } |
| } |
| |
| void BackgroundTracingManagerImpl::AbortScenarioForTesting() { |
| if (legacy_active_scenario_) { |
| legacy_active_scenario_->AbortScenario(); |
| } else if (active_scenario_) { |
| active_scenario_->Abort(); |
| } |
| } |
| |
| void BackgroundTracingManagerImpl::OnScenarioAborted() { |
| DCHECK(legacy_active_scenario_); |
| |
| for (EnabledStateTestObserver* observer : background_tracing_observers_) { |
| observer->OnScenarioIdle( |
| legacy_active_scenario_->GetConfig()->scenario_name()); |
| } |
| |
| legacy_active_scenario_.reset(); |
| } |
| |
| void BackgroundTracingManagerImpl::CleanDatabase() { |
| DCHECK(trace_database_); |
| |
| database_task_runner_->PostTaskAndReplyWithResult( |
| FROM_HERE, |
| base::BindOnce( |
| [](TraceReportDatabase* trace_database) { |
| trace_database->DeleteTracesOlderThan(kTraceTimeToLive); |
| return trace_database->GetScenarioCounts(); |
| }, |
| 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->GetScenarioCounts())); |
| } 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()) |
| 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(); |
| } |
| |
| size_t BackgroundTracingManagerImpl::GetTraceUploadLimitKb() const { |
| #if BUILDFLAG(IS_ANDROID) |
| auto type = net::NetworkChangeNotifier::GetConnectionType(); |
| if (net::NetworkChangeNotifier::IsConnectionCellular(type)) { |
| return upload_limit_network_kb_; |
| } |
| #endif |
| return upload_limit_kb_; |
| } |
| |
| } // namespace content |