blob: 2772a018f0e3f01fbcd8840dace1a9a5bbd097c6 [file] [log] [blame]
// 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 "chrome/browser/metrics/chrome_metrics_services_manager_client.h"
#include <map>
#include <string>
#include "base/check_op.h"
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/field_trial_params.h"
#include "base/metrics/histogram_functions.h"
#include "base/path_service.h"
#include "base/strings/string_number_conversions.h"
#include "base/task/thread_pool.h"
#include "build/build_config.h"
#include "cc/base/switches.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/metrics/chrome_metrics_service_accessor.h"
#include "chrome/browser/metrics/chrome_metrics_service_client.h"
#include "chrome/browser/metrics/variations/chrome_variations_service_client.h"
#include "chrome/browser/metrics/variations/ui_string_overrider_factory.h"
#include "chrome/browser/net/system_network_context_manager.h"
#include "chrome/browser/ui/browser_otr_state.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/installer/util/google_update_settings.h"
#include "components/metrics/enabled_state_provider.h"
#include "components/metrics/metrics_pref_names.h"
#include "components/metrics/metrics_state_manager.h"
#include "components/prefs/pref_service.h"
#include "components/variations/service/variations_service.h"
#include "components/variations/synthetic_trial_registry.h"
#include "components/variations/variations_associated_data.h"
#include "components/version_info/version_info.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/network_service_instance.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#if BUILDFLAG(IS_ANDROID)
#include "base/metrics/histogram_functions.h"
#include "chrome/browser/android/metrics/uma_session_stats.h"
#include "chrome/browser/ui/android/tab_model/tab_model.h"
#include "chrome/browser/ui/android/tab_model/tab_model_list.h"
#else
#include "chrome/browser/ui/browser_list.h"
#endif // BUILDFLAG(IS_ANDROID)
#if BUILDFLAG(IS_WIN)
#include "base/win/registry.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/install_static/install_util.h"
#include "components/crash/core/app/crash_export_thunks.h"
#include "components/crash/core/app/crashpad.h"
#endif // BUILDFLAG(IS_WIN)
#if BUILDFLAG(IS_CHROMEOS)
#include "chrome/browser/ash/settings/stats_reporting_controller.h"
#include "components/metrics/structured/recorder.h"
#endif // BUILDFLAG(IS_CHROMEOS)
namespace metrics {
namespace internal {
// Metrics reporting feature. This feature, along with user consent, controls if
// recording and reporting are enabled. If the feature is enabled, but no
// consent is given, then there will be no recording or reporting.
BASE_FEATURE(kMetricsReportingFeature,
"MetricsReporting",
base::FEATURE_ENABLED_BY_DEFAULT);
#if BUILDFLAG(IS_ANDROID)
// Same as |kMetricsReportingFeature|, but this feature is associated with a
// different trial, which has different sampling rates. This is due to a bug
// in which the old sampling rate was not being applied correctly. In order for
// the fix to not affect the overall sampling rate, this new feature was
// created. See crbug/1306481.
BASE_FEATURE(kPostFREFixMetricsReportingFeature,
"PostFREFixMetricsReporting",
base::FEATURE_ENABLED_BY_DEFAULT);
#endif // BUILDFLAG(IS_ANDROID)
// Name of the variations param that defines the sampling rate.
const char kRateParamName[] = "sampling_rate_per_mille";
} // namespace internal
} // namespace metrics
namespace {
// Posts |GoogleUpdateSettings::StoreMetricsClientInfo| on blocking pool thread
// because it needs access to IO and cannot work from UI thread.
void PostStoreMetricsClientInfo(const metrics::ClientInfo& client_info) {
// This must happen on the same sequence as the tasks to enable/disable
// metrics reporting. Otherwise, this may run while disabling metrics
// reporting if the user quickly enables and disables metrics reporting.
GoogleUpdateSettings::CollectStatsConsentTaskRunner()->PostTask(
FROM_HERE, base::BindOnce(&GoogleUpdateSettings::StoreMetricsClientInfo,
client_info));
}
#if BUILDFLAG(IS_ANDROID)
// Returns true if we should use the new sampling trial and feature to determine
// sampling. See the comment on |kUsePostFREFixSamplingTrial| for more details.
bool ShouldUsePostFREFixSamplingTrial(PrefService* local_state) {
return local_state->GetBoolean(metrics::prefs::kUsePostFREFixSamplingTrial);
}
bool ShouldUsePostFREFixSamplingTrial() {
// We check for g_browser_process and local_state() because some unit tests
// may reach this point without creating a test browser process and/or local
// state.
// TODO(crbug.com/40837610): Fix the unit tests so that we do not need to
// check for g_browser_process and local_state().
return g_browser_process && g_browser_process->local_state() &&
ShouldUsePostFREFixSamplingTrial(g_browser_process->local_state());
}
#endif // BUILDFLAG(IS_ANDROID)
// Implementation of IsClientInSample() that takes a PrefService param.
bool IsClientInSampleImpl(PrefService* local_state) {
// Test the MetricsReporting or PostFREFixMetricsReporting feature (depending
// on the |kUsePostFREFixSamplingTrial| pref and platform) for all users to
// ensure that the trial is reported. See the comment on
// |kUsePostFREFixSamplingTrial| for more details on why there are two
// different features.
#if BUILDFLAG(IS_ANDROID)
if (ShouldUsePostFREFixSamplingTrial(local_state)) {
return base::FeatureList::IsEnabled(
metrics::internal::kPostFREFixMetricsReportingFeature);
}
#endif // BUILDFLAG(IS_ANDROID)
return base::FeatureList::IsEnabled(
metrics::internal::kMetricsReportingFeature);
}
#if BUILDFLAG(IS_CHROMEOS)
// Callback to update the metrics reporting state when the Chrome OS metrics
// reporting setting changes.
void OnCrosMetricsReportingSettingChange(
ChangeMetricsReportingStateCalledFrom called_from) {
bool enable_metrics = ash::StatsReportingController::Get()->IsEnabled();
ChangeMetricsReportingState(enable_metrics, called_from);
}
#endif
// Returns the name of a key under HKEY_CURRENT_USER that can be used to store
// backups of metrics data. Unused except on Windows.
std::wstring GetRegistryBackupKey() {
#if BUILDFLAG(IS_WIN)
return install_static::GetRegistryPath().append(L"\\StabilityMetrics");
#else
return std::wstring();
#endif
}
} // namespace
class ChromeMetricsServicesManagerClient::ChromeEnabledStateProvider
: public metrics::EnabledStateProvider {
public:
explicit ChromeEnabledStateProvider(PrefService* local_state)
: local_state_(local_state) {}
ChromeEnabledStateProvider(const ChromeEnabledStateProvider&) = delete;
ChromeEnabledStateProvider& operator=(const ChromeEnabledStateProvider&) =
delete;
~ChromeEnabledStateProvider() override = default;
bool IsConsentGiven() const override {
return ChromeMetricsServiceAccessor::IsMetricsAndCrashReportingEnabled(
local_state_);
}
bool IsReportingEnabled() const override {
return metrics::EnabledStateProvider::IsReportingEnabled() &&
IsClientInSampleImpl(local_state_);
}
private:
const raw_ptr<PrefService> local_state_;
};
ChromeMetricsServicesManagerClient::ChromeMetricsServicesManagerClient(
PrefService* local_state)
: enabled_state_provider_(
std::make_unique<ChromeEnabledStateProvider>(local_state)),
local_state_(local_state) {
DCHECK(local_state);
}
ChromeMetricsServicesManagerClient::~ChromeMetricsServicesManagerClient() =
default;
metrics::MetricsStateManager*
ChromeMetricsServicesManagerClient::GetMetricsStateManagerForTesting() {
return GetMetricsStateManager();
}
// static
bool ChromeMetricsServicesManagerClient::IsClientInSampleForMetrics() {
return IsClientInSampleImpl(g_browser_process->local_state());
}
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_ANDROID)
// static
bool ChromeMetricsServicesManagerClient::IsClientInSampleForCrashes() {
#if BUILDFLAG(IS_ANDROID)
// On Android, there are two field trials that, together, drive metrics and
// crash reporting. The determination of which trial to use is based on
// whether the client went through the FRE before or after the fix to
// crbug.com/1306481 was deployed.
//
// The PostFREFixSamplingTrial controls crash and metrics sampling for clients
// which went through the FRE after the FRE fix was deployed. These clients
// use the PostFREFixMetricsReortingFeature and its "disable_crashes" feature
// parameter to control whether the client is in-sample for crash reporting.
if (ShouldUsePostFREFixSamplingTrial(g_browser_process->local_state())) {
// If reporting isn't enabled at all, then we can return early.
if (!base::FeatureList::IsEnabled(
metrics::internal::kPostFREFixMetricsReportingFeature)) {
return false;
}
// Otherwise, send crashes if crash reporting is NOT disabled. By default
// crash reporting is not disabled.
const bool crashes_are_disabled = base::GetFieldTrialParamByFeatureAsBool(
metrics::internal::kPostFREFixMetricsReportingFeature,
"disable_crashes", false);
return !crashes_are_disabled;
}
#endif // BUILDFLAG(IS_ANDROID)
// If this is a Windows client, or if this is an Android client that went
// through the FRE before the FRE fix was deployed, then this client uses
// the MetricsReportingFeature and its "disable_crashes" parameter to control
// whether the client is in-sample for crash reporting.
// If reporting isn't enabled at all, then we can return early.
if (!base::FeatureList::IsEnabled(
metrics::internal::kMetricsReportingFeature)) {
return false;
}
const bool crashes_are_disabled = base::GetFieldTrialParamByFeatureAsBool(
metrics::internal::kMetricsReportingFeature, "disable_crashes", false);
return !crashes_are_disabled;
}
#endif // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_ANDROID)
// static
bool ChromeMetricsServicesManagerClient::GetSamplingRatePerMille(int* rate) {
#if BUILDFLAG(IS_ANDROID)
const base::Feature& feature =
ShouldUsePostFREFixSamplingTrial()
? metrics::internal::kPostFREFixMetricsReportingFeature
: metrics::internal::kMetricsReportingFeature;
#else
const base::Feature& feature = metrics::internal::kMetricsReportingFeature;
#endif // BUILDFLAG(IS_ANDROID)
std::string rate_str = base::GetFieldTrialParamValueByFeature(
feature, metrics::internal::kRateParamName);
if (rate_str.empty())
return false;
if (!base::StringToInt(rate_str, rate) || *rate > 1000)
return false;
return true;
}
#if BUILDFLAG(IS_CHROMEOS)
void ChromeMetricsServicesManagerClient::OnCrosSettingsCreated() {
// Listen for changes to metrics reporting state.
reporting_setting_subscription_ =
ash::StatsReportingController::Get()->AddObserver(base::BindRepeating(
&OnCrosMetricsReportingSettingChange,
ChangeMetricsReportingStateCalledFrom::kCrosMetricsSettingsChange));
// Invoke the callback once initially to set the metrics reporting state.
OnCrosMetricsReportingSettingChange(
ChangeMetricsReportingStateCalledFrom::kCrosMetricsSettingsCreated);
}
#endif
std::unique_ptr<variations::VariationsService>
ChromeMetricsServicesManagerClient::CreateVariationsService() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
return variations::VariationsService::Create(
std::make_unique<ChromeVariationsServiceClient>(), local_state_,
GetMetricsStateManager(), switches::kDisableBackgroundNetworking,
chrome_variations::CreateUIStringOverrider(),
base::BindOnce(&content::GetNetworkConnectionTracker));
}
std::unique_ptr<metrics::MetricsServiceClient>
ChromeMetricsServicesManagerClient::CreateMetricsServiceClient(
variations::SyntheticTrialRegistry* synthetic_trial_registry) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
return ChromeMetricsServiceClient::Create(GetMetricsStateManager(),
synthetic_trial_registry);
}
metrics::MetricsStateManager*
ChromeMetricsServicesManagerClient::GetMetricsStateManager() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (!metrics_state_manager_) {
base::FilePath user_data_dir;
base::PathService::Get(chrome::DIR_USER_DATA, &user_data_dir);
metrics::StartupVisibility startup_visibility;
#if BUILDFLAG(IS_ANDROID)
startup_visibility = UmaSessionStats::HasVisibleActivity()
? metrics::StartupVisibility::kForeground
: metrics::StartupVisibility::kBackground;
base::UmaHistogramEnumeration("UMA.StartupVisibility", startup_visibility);
#else
startup_visibility = metrics::StartupVisibility::kForeground;
#endif // BUILDFLAG(IS_ANDROID)
metrics_state_manager_ = metrics::MetricsStateManager::Create(
local_state_, enabled_state_provider_.get(), GetRegistryBackupKey(),
user_data_dir, startup_visibility,
{
.default_entropy_provider_type =
metrics::EntropyProviderType::kDefault,
.force_benchmarking_mode =
base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnableGpuBenchmarking),
},
base::BindRepeating(&PostStoreMetricsClientInfo),
base::BindRepeating(&GoogleUpdateSettings::LoadMetricsClientInfo));
}
return metrics_state_manager_.get();
}
scoped_refptr<network::SharedURLLoaderFactory>
ChromeMetricsServicesManagerClient::GetURLLoaderFactory() {
return g_browser_process->system_network_context_manager()
->GetSharedURLLoaderFactory();
}
const metrics::EnabledStateProvider&
ChromeMetricsServicesManagerClient::GetEnabledStateProvider() {
return *enabled_state_provider_;
}
bool ChromeMetricsServicesManagerClient::IsOffTheRecordSessionActive() {
#if BUILDFLAG(IS_ANDROID)
// This differs from TabModelList::IsOffTheRecordSessionActive in that it
// does not ignore TabModels that have no open tabs, because it may be checked
// before tabs get added to the TabModel. This means it may be more
// conservative in case unused TabModels are not cleaned up, but it seems to
// work correctly.
// TODO(crbug.com/40529753): Check if TabModelList's version can be updated
// safely.
// TODO(crbug.com/40107157): This function should return true for Incognito
// CCTs.
for (const TabModel* model : TabModelList::models()) {
if (model->IsOffTheRecord())
return true;
}
return false;
#else
// Depending directly on BrowserList, since that is the implementation
// that we get correct notifications for.
return BrowserList::IsOffTheRecordBrowserActive();
#endif
}
#if BUILDFLAG(IS_WIN)
void ChromeMetricsServicesManagerClient::UpdateRunningServices(
bool may_record,
bool may_upload) {
// First, set the registry value so that Crashpad will have the sampling state
// now and for subsequent runs. Note that Crashpad uses *both* the registry
// value and the value sent from SetUploadConsent below.
// We use IsClientInSampleForCrash() which checks the feature for if crashes
// are allowed.
install_static::SetCollectStatsInSample(IsClientInSampleForCrashes());
// The intent here is to set the value of the consent. However, since right
// now we have may_record which is based off both consent and the Feature
// state, this is redundant with the above value. This is pretty confusing
// right now, and we may want to rethink this. One extra complexity here is we
// currently check the disable_crashes parameter, which does not go
// into may_record. This is because this is specifically intending to test for
// consent, and as mentioned, on the crashpad side we check both. See
// SetUploadConsent() in components/crash/core/app/crashpad.cc for how this
// gets used.
SetUploadConsent_ExportThunk(may_record && may_upload);
}
#endif // BUILDFLAG(IS_WIN)