chromium/chrome/browser/metrics/perf/metric_provider.cc

// Copyright 2019 The Chromium Authors
// 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 "ash/constants/ash_features.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/metrics/histogram_functions.h"
#include "base/task/thread_pool.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/sync/sync_service_factory.h"
#include "components/sync/base/user_selectable_type.h"
#include "components/sync/service/sync_service.h"
#include "components/sync/service/sync_user_settings.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "third_party/metrics_proto/device_state.pb.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);
    }
  }
}

ThermalState ToProtoThermalStateEnum(
    base::PowerThermalObserver::DeviceThermalState state) {
  switch (state) {
    case base::PowerThermalObserver::DeviceThermalState::kUnknown:
      return THERMAL_STATE_UNKNOWN;
    case base::PowerThermalObserver::DeviceThermalState::kNominal:
      return THERMAL_STATE_NOMINAL;
    case base::PowerThermalObserver::DeviceThermalState::kFair:
      return THERMAL_STATE_FAIR;
    case base::PowerThermalObserver::DeviceThermalState::kSerious:
      return THERMAL_STATE_SERIOUS;
    case base::PowerThermalObserver::DeviceThermalState::kCritical:
      return THERMAL_STATE_CRITICAL;
  }
}

}  // 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::DoNothingWithBoundArgs(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) {
  content::GetUIThreadTaskRunner({})->PostTask(
      FROM_HERE, 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;
}

MetricProvider::RecordAttemptStatus MetricProvider::AppSyncStateForUserProfile(
    Profile* profile) {
  syncer::SyncService* sync_service =
      SyncServiceFactory::GetForProfile(profile);
  if (!sync_service)
    return RecordAttemptStatus::kSyncServiceUnavailable;
  syncer::SyncUserSettings* sync_settings = sync_service->GetUserSettings();

  if (!sync_service->IsSyncFeatureEnabled())
    return RecordAttemptStatus::kChromeSyncFeatureDisabled;

  if (!sync_settings->GetSelectedOsTypes().Has(
          syncer::UserSelectableOsType::kOsApps))
    return RecordAttemptStatus::kOSAppSyncDisabled;
  return RecordAttemptStatus::kAppSyncEnabled;
}

// 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();
  // Tracks the number of user profiles initialized on Chrome OS other than the
  // Default profile.
  int user_profile_count = 0;

  for (Profile* profile : profiles) {
    // The Default profile, lock screen app profile and lock screen profile are
    // all not regular user profiles on Chrome OS. They always disable sync and
    // we would skip them.
    if (!ash::ProfileHelper::IsUserProfile(profile))
      continue;
    auto app_sync_state = AppSyncStateForUserProfile(profile);
    if (app_sync_state != RecordAttemptStatus::kAppSyncEnabled)
      return app_sync_state;
    user_profile_count++;
  }

  if (user_profile_count == 0)
    return RecordAttemptStatus::kNoLoadedProfile;

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

  // Add the device thermal state and cpu speed limit to the profile.
  sampled_profile->set_thermal_state(ToProtoThermalStateEnum(thermal_state_));
  sampled_profile->set_cpu_speed_limit_percent(cpu_speed_limit_percent_);

  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