blob: 1efbf323c8f76229a9db9b093c2a7ff78ae51792 [file] [log] [blame]
// 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