| // Copyright 2019 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/metrics/perf/metric_provider.h" |
| |
| #include "base/bind.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/task/post_task.h" |
| #include "base/task/task_traits.h" |
| #include "base/task/thread_pool.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/profiles/profile_manager.h" |
| #include "chrome/browser/sync/profile_sync_service_factory.h" |
| #include "chromeos/constants/chromeos_features.h" |
| #include "components/sync/base/user_selectable_type.h" |
| #include "components/sync/driver/sync_service.h" |
| #include "components/sync/driver/sync_user_settings.h" |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "third_party/metrics_proto/sampled_profile.pb.h" |
| |
| namespace metrics { |
| |
| namespace { |
| |
| // Name prefix of the histogram that counts the number of reports uploaded by a |
| // metric provider. |
| const char kUploadCountHistogramPrefix[] = "ChromeOS.CWP.Upload"; |
| |
| // Name prefix of the histogram that tracks the various outcomes of saving the |
| // collected profile to local cache. |
| const char kRecordOutcomeHistogramPrefix[] = "ChromeOS.CWP.Record"; |
| |
| // An upper bound on the count of reports expected to be uploaded by an UMA |
| // callback. |
| const int kMaxValueUploadReports = 10; |
| |
| // The MD5 prefix to replace the original comm_md5_prefix of COMM events in perf |
| // data proto, if necessary. We used string "<redacted>" to compute this MD5 |
| // prefix. |
| const uint64_t kRedactedCommMd5Prefix = 0xee1f021828a1fcbc; |
| |
| // This function modifies the comm_md5_prefix of all the COMM events in the |
| // given perf data proto by replacing it with the md5 prefix of an artificial |
| // string. |
| void RedactCommMd5Prefixes(PerfDataProto* proto) { |
| for (PerfDataProto::PerfEvent& event : *proto->mutable_events()) { |
| if (event.has_comm_event()) { |
| event.mutable_comm_event()->set_comm_md5_prefix(kRedactedCommMd5Prefix); |
| } |
| } |
| } |
| |
| // Check if App Sync is enabled for a given user profile. |
| bool IsAppSyncEnabledForUserProfile(Profile* profile) { |
| syncer::SyncService* sync_service = |
| ProfileSyncServiceFactory::GetForProfile(profile); |
| if (!sync_service) |
| return false; |
| syncer::SyncUserSettings* sync_settings = sync_service->GetUserSettings(); |
| |
| // Chrome versions >= M78 have a feature that splits the sync settings between |
| // Chrome and ChromeOS. The App Sync toggle is moved under the ChromeOS |
| // settings. If the split sync setting is enabled, we will directly read from |
| // the OS settings. Otherwise, we read from Chrome settings. We then check if |
| // the sync feature is enabled and the App Sync toggle is on. |
| if (chromeos::features::IsSplitSettingsSyncEnabled()) { |
| return sync_settings->IsOsSyncFeatureEnabled() && |
| sync_settings->GetSelectedOsTypes().Has( |
| syncer::UserSelectableOsType::kOsApps); |
| } |
| // Read chrome settings if split sync is disabled. |
| return sync_service->IsSyncFeatureEnabled() && |
| sync_settings->GetSelectedTypes().Has( |
| syncer::UserSelectableType::kApps); |
| } |
| |
| } // namespace |
| |
| using MetricCollector = internal::MetricCollector; |
| |
| MetricProvider::MetricProvider(std::unique_ptr<MetricCollector> collector, |
| ProfileManager* profile_manager) |
| : upload_uma_histogram_(std::string(kUploadCountHistogramPrefix) + |
| collector->ToolName()), |
| record_uma_histogram_(std::string(kRecordOutcomeHistogramPrefix) + |
| collector->ToolName()), |
| // Run the collector at a higher priority to enable fast triggering of |
| // profile collections. In particular, we want fast triggering when |
| // jankiness is detected, but even random based periodic collection |
| // benefits from a higher priority, to avoid biasing the collection to |
| // times when the system is not busy. The work performed on the dedicated |
| // sequence is short and infrequent. Expensive parsing operations are |
| // executed asynchronously on the thread pool. |
| collector_task_runner_(base::ThreadPool::CreateSequencedTaskRunner( |
| {base::TaskPriority::USER_VISIBLE})), |
| metric_collector_(std::move(collector)), |
| profile_manager_(profile_manager), |
| weak_factory_(this) { |
| metric_collector_->set_profile_done_callback(base::BindRepeating( |
| &MetricProvider::OnProfileDone, weak_factory_.GetWeakPtr())); |
| } |
| |
| MetricProvider::~MetricProvider() { |
| // Destroy the metric_collector_ on the collector sequence. |
| collector_task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce([](std::unique_ptr<MetricCollector> collector_) {}, |
| std::move(metric_collector_))); |
| } |
| |
| void MetricProvider::Init() { |
| // It is safe to use base::Unretained to post tasks to the metric_collector_ |
| // on the collector sequence, since we control its lifetime. Any tasks |
| // posted to it are bound to run before we destroy it on the collector |
| // sequence. |
| collector_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&MetricCollector::Init, |
| base::Unretained(metric_collector_.get()))); |
| } |
| |
| bool MetricProvider::GetSampledProfiles( |
| std::vector<SampledProfile>* sampled_profiles) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| if (cached_profile_data_.empty()) { |
| base::UmaHistogramExactLinear(upload_uma_histogram_, 0, |
| kMaxValueUploadReports); |
| return false; |
| } |
| |
| base::UmaHistogramExactLinear(upload_uma_histogram_, |
| cached_profile_data_.size(), |
| kMaxValueUploadReports); |
| sampled_profiles->insert( |
| sampled_profiles->end(), |
| std::make_move_iterator(cached_profile_data_.begin()), |
| std::make_move_iterator(cached_profile_data_.end())); |
| collector_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&MetricCollector::ResetCachedDataSize, |
| base::Unretained(metric_collector_.get()))); |
| cached_profile_data_.clear(); |
| return true; |
| } |
| |
| void MetricProvider::OnUserLoggedIn() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| const base::TimeTicks now = base::TimeTicks::Now(); |
| collector_task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce(&MetricCollector::RecordUserLogin, |
| base::Unretained(metric_collector_.get()), now)); |
| } |
| |
| void MetricProvider::Deactivate() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| // Notifies the collector to turn off the timer. Does not delete any data that |
| // was already collected and stored in |cached_profile_data|. |
| collector_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&MetricCollector::StopTimer, |
| base::Unretained(metric_collector_.get()))); |
| } |
| |
| void MetricProvider::SuspendDone(base::TimeDelta sleep_duration) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| collector_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&MetricCollector::ScheduleSuspendDoneCollection, |
| base::Unretained(metric_collector_.get()), |
| sleep_duration)); |
| } |
| |
| void MetricProvider::OnSessionRestoreDone(int num_tabs_restored) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| collector_task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce(&MetricCollector::ScheduleSessionRestoreCollection, |
| base::Unretained(metric_collector_.get()), |
| num_tabs_restored)); |
| } |
| |
| // static |
| void MetricProvider::OnProfileDone( |
| base::WeakPtr<MetricProvider> provider, |
| std::unique_ptr<SampledProfile> sampled_profile) { |
| base::PostTask(FROM_HERE, base::TaskTraits(content::BrowserThread::UI), |
| base::BindOnce(&MetricProvider::AddProfileToCache, provider, |
| std::move(sampled_profile))); |
| } |
| |
| void MetricProvider::OnJankStarted() { |
| collector_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&MetricCollector::OnJankStarted, |
| base::Unretained(metric_collector_.get()))); |
| } |
| |
| void MetricProvider::OnJankStopped() { |
| collector_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&MetricCollector::OnJankStopped, |
| base::Unretained(metric_collector_.get()))); |
| } |
| |
| void MetricProvider::EnableRecording() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| recording_enabled_ = true; |
| } |
| |
| void MetricProvider::DisableRecording() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| recording_enabled_ = false; |
| } |
| |
| // Check the current state of App Sync in the settings. This is done by getting |
| // all currently fully initialized profiles and reading the sync settings from |
| // them. |
| MetricProvider::RecordAttemptStatus MetricProvider::GetAppSyncState() { |
| if (!profile_manager_) |
| return RecordAttemptStatus::kProfileManagerUnset; |
| |
| std::vector<Profile*> profiles = profile_manager_->GetLoadedProfiles(); |
| if (profiles.size() == 0) |
| return RecordAttemptStatus::kNoLoadedProfile; |
| |
| for (Profile* profile : profiles) { |
| if (!IsAppSyncEnabledForUserProfile(profile)) |
| return RecordAttemptStatus::kAppSyncDisabled; |
| } |
| |
| return RecordAttemptStatus::kAppSyncEnabled; |
| } |
| |
| void MetricProvider::AddProfileToCache( |
| std::unique_ptr<SampledProfile> sampled_profile) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| if (!recording_enabled_) { |
| base::UmaHistogramEnumeration(record_uma_histogram_, |
| RecordAttemptStatus::kRecordingDisabled); |
| return; |
| } |
| |
| // For privacy reasons, Chrome can not collect Android app names that may be |
| // present in the perf data, unless the user consent to enabling App Sync. |
| // Therefore, if the user does not enable App Sync, we redact comm_md5_prefix |
| // in all COMM events of perf data proto, so these MD5 prefixes can not be |
| // used to recover Android app names. We perform the check on App Sync here |
| // because the procedure to get the user profile (from which sync settings can |
| // be obtained) must execute on the UI thread. |
| auto app_sync_state = GetAppSyncState(); |
| base::UmaHistogramEnumeration(record_uma_histogram_, app_sync_state); |
| if (app_sync_state != RecordAttemptStatus::kAppSyncEnabled) |
| RedactCommMd5Prefixes(sampled_profile->mutable_perf_data()); |
| |
| collector_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&MetricCollector::AddCachedDataDelta, |
| base::Unretained(metric_collector_.get()), |
| sampled_profile->ByteSize())); |
| cached_profile_data_.resize(cached_profile_data_.size() + 1); |
| cached_profile_data_.back().Swap(sampled_profile.get()); |
| |
| if (!cache_updated_callback_.is_null()) |
| cache_updated_callback_.Run(); |
| } |
| |
| } // namespace metrics |