| // 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); |