| // Copyright 2014 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/metrics/metrics_state_manager.h" |
| |
| #include <cstddef> |
| #include <cstdint> |
| #include <limits> |
| #include <memory> |
| #include <random> |
| #include <string> |
| #include <tuple> |
| #include <utility> |
| |
| #include "base/base_switches.h" |
| #include "base/check.h" |
| #include "base/command_line.h" |
| #include "base/debug/leak_annotations.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/memory/raw_ref.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/numerics/safe_conversions.h" |
| #include "base/rand_util.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/threading/thread_restrictions.h" |
| #include "base/time/time.h" |
| #include "base/uuid.h" |
| #include "build/branding_buildflags.h" |
| #include "build/build_config.h" |
| #include "components/metrics/cloned_install_detector.h" |
| #include "components/metrics/enabled_state_provider.h" |
| #include "components/metrics/entropy_state.h" |
| #include "components/metrics/metrics_data_validation.h" |
| #include "components/metrics/metrics_log.h" |
| #include "components/metrics/metrics_pref_names.h" |
| #include "components/metrics/metrics_provider.h" |
| #include "components/metrics/metrics_switches.h" |
| #include "components/prefs/pref_registry_simple.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/variations/entropy_provider.h" |
| #include "components/variations/field_trial_config/field_trial_util.h" |
| #include "components/variations/pref_names.h" |
| #include "components/variations/variations_switches.h" |
| #include "third_party/metrics_proto/chrome_user_metrics_extension.pb.h" |
| #include "third_party/metrics_proto/system_profile.pb.h" |
| |
| namespace metrics { |
| namespace { |
| |
| int64_t ReadEnabledDate(PrefService* local_state) { |
| return local_state->GetInt64(prefs::kMetricsReportingEnabledTimestamp); |
| } |
| |
| int64_t ReadInstallDate(PrefService* local_state) { |
| return local_state->GetInt64(prefs::kInstallDate); |
| } |
| |
| std::string ReadClientId(PrefService* local_state) { |
| return local_state->GetString(prefs::kMetricsClientID); |
| } |
| |
| // Round a timestamp measured in seconds since epoch to one with a granularity |
| // of an hour. This can be used before uploaded potentially sensitive |
| // timestamps. |
| int64_t RoundSecondsToHour(int64_t time_in_seconds) { |
| return 3600 * (time_in_seconds / 3600); |
| } |
| |
| // Records the cloned install histogram. |
| void LogClonedInstall() { |
| // Equivalent to UMA_HISTOGRAM_BOOLEAN with the stability flag set. |
| UMA_STABILITY_HISTOGRAM_ENUMERATION("UMA.IsClonedInstall", 1, 2); |
| } |
| |
| // No-op function used to create a MetricsStateManager. |
| std::unique_ptr<metrics::ClientInfo> NoOpLoadClientInfoBackup() { |
| return nullptr; |
| } |
| |
| // Exits the browser with a helpful error message if an invalid, |
| // field-trial-related command-line flag was specified. |
| void ExitWithMessage(const std::string& message) { |
| puts(message.c_str()); |
| exit(1); |
| } |
| |
| // Returns a log normal distribution based on the feature params of |
| // |kNonUniformityValidationFeature|. |
| std::lognormal_distribution<double> GetLogNormalDist() { |
| double mean = kLogNormalMean.Get(); |
| double delta = kLogNormalDelta.Get(); |
| double std_dev = kLogNormalStdDev.Get(); |
| return std::lognormal_distribution<double>(mean + std::log(1.0 + delta), |
| std_dev); |
| } |
| |
| // Used to draw a data point from a log normal distribution. |
| struct LogNormalMetricState { |
| LogNormalMetricState() |
| : dist(GetLogNormalDist()), gen(std::mt19937(base::RandUint64())) {} |
| |
| // Records the artificial non-uniformity histogram for data validation. |
| void LogArtificialNonUniformity() { |
| double rand = dist(gen); |
| // We pick 10k as the upper bound for this histogram so as to avoid losing |
| // precision. See comments for |kLogNormalMean|. |
| base::UmaHistogramCounts10000("UMA.DataValidation.LogNormal", |
| base::saturated_cast<int>(rand)); |
| } |
| |
| // A log normal distribution generator generated by the `GetLogNormalDist()` |
| // function. |
| std::lognormal_distribution<double> dist; |
| // The pseudo-random generator used to generate a data point from |dist|. |
| std::mt19937 gen; |
| }; |
| |
| class MetricsStateMetricsProvider : public MetricsProvider { |
| public: |
| MetricsStateMetricsProvider( |
| PrefService* local_state, |
| bool metrics_ids_were_reset, |
| std::string previous_client_id, |
| std::string initial_client_id, |
| ClonedInstallDetector const& cloned_install_detector) |
| : local_state_(local_state), |
| metrics_ids_were_reset_(metrics_ids_were_reset), |
| previous_client_id_(std::move(previous_client_id)), |
| initial_client_id_(std::move(initial_client_id)), |
| cloned_install_detector_(cloned_install_detector) {} |
| |
| MetricsStateMetricsProvider(const MetricsStateMetricsProvider&) = delete; |
| MetricsStateMetricsProvider& operator=(const MetricsStateMetricsProvider&) = |
| delete; |
| |
| // MetricsProvider: |
| void ProvideSystemProfileMetrics( |
| SystemProfileProto* system_profile) override { |
| system_profile->set_uma_enabled_date( |
| RoundSecondsToHour(ReadEnabledDate(local_state_))); |
| system_profile->set_install_date( |
| RoundSecondsToHour(ReadInstallDate(local_state_))); |
| |
| // Client id in the log shouldn't be different than the |local_state_| one |
| // except when the client disabled UMA before we populate this field to the |
| // log. If that's the case, the client id in the |local_state_| should be |
| // empty and we should set |client_id_was_used_for_trial_assignment| to |
| // false. |
| std::string client_id = ReadClientId(local_state_); |
| system_profile->set_client_id_was_used_for_trial_assignment( |
| !client_id.empty() && client_id == initial_client_id_); |
| |
| ClonedInstallInfo cloned = |
| ClonedInstallDetector::ReadClonedInstallInfo(local_state_); |
| if (cloned.reset_count == 0) |
| return; |
| auto* cloned_install_info = system_profile->mutable_cloned_install_info(); |
| if (metrics_ids_were_reset_) { |
| // Only report the cloned from client_id in the resetting session. |
| if (!previous_client_id_.empty()) { |
| cloned_install_info->set_cloned_from_client_id( |
| MetricsLog::Hash(previous_client_id_)); |
| } |
| } |
| cloned_install_info->set_last_timestamp( |
| RoundSecondsToHour(cloned.last_reset_timestamp)); |
| cloned_install_info->set_first_timestamp( |
| RoundSecondsToHour(cloned.first_reset_timestamp)); |
| cloned_install_info->set_count(cloned.reset_count); |
| } |
| |
| void ProvidePreviousSessionData( |
| ChromeUserMetricsExtension* uma_proto) override { |
| if (metrics_ids_were_reset_) { |
| LogClonedInstall(); |
| if (!previous_client_id_.empty()) { |
| // NOTE: If you are adding anything here, consider also changing |
| // FileMetricsProvider::ProvideIndependentMetricsOnTaskRunner(). |
| |
| // If we know the previous client id, overwrite the client id for the |
| // previous session log so the log contains the client id at the time |
| // of the previous session. This allows better attribution of crashes |
| // to earlier behavior. If the previous client id is unknown, leave |
| // the current client id. |
| uma_proto->set_client_id(MetricsLog::Hash(previous_client_id_)); |
| } |
| } |
| } |
| |
| void ProvideCurrentSessionData( |
| ChromeUserMetricsExtension* uma_proto) override { |
| if (cloned_install_detector_->ClonedInstallDetectedInCurrentSession()) { |
| LogClonedInstall(); |
| } |
| log_normal_metric_state_.LogArtificialNonUniformity(); |
| } |
| |
| // Set a random seed for the random number generator. |
| void SetRandomSeedForTesting(int64_t seed) { |
| log_normal_metric_state_.gen = std::mt19937(seed); |
| } |
| |
| private: |
| const raw_ptr<PrefService> local_state_; |
| const bool metrics_ids_were_reset_; |
| // |previous_client_id_| is set only (if known) when |
| // |metrics_ids_were_reset_| |
| const std::string previous_client_id_; |
| // The client id that was used to randomize field trials. An empty string if |
| // the low entropy source was used to do randomization. |
| const std::string initial_client_id_; |
| const raw_ref<const ClonedInstallDetector> cloned_install_detector_; |
| LogNormalMetricState log_normal_metric_state_; |
| }; |
| |
| bool ShouldEnableBenchmarking(bool force_benchmarking_mode) { |
| // TODO(crbug.com/40792683): See whether it's possible to consolidate the |
| // switches. |
| return force_benchmarking_mode || |
| base::CommandLine::ForCurrentProcess()->HasSwitch( |
| variations::switches::kEnableBenchmarking); |
| } |
| |
| } // namespace |
| |
| // static |
| bool MetricsStateManager::instance_exists_ = false; |
| |
| // static |
| bool MetricsStateManager::enable_provisional_client_id_for_testing_ = false; |
| |
| MetricsStateManager::MetricsStateManager( |
| PrefService* local_state, |
| EnabledStateProvider* enabled_state_provider, |
| const std::wstring& backup_registry_key, |
| const base::FilePath& user_data_dir, |
| EntropyParams entropy_params, |
| StartupVisibility startup_visibility, |
| StoreClientInfoCallback store_client_info, |
| LoadClientInfoCallback retrieve_client_info, |
| base::StringPiece external_client_id) |
| : local_state_(local_state), |
| enabled_state_provider_(enabled_state_provider), |
| entropy_params_(entropy_params), |
| store_client_info_(std::move(store_client_info)), |
| load_client_info_(std::move(retrieve_client_info)), |
| clean_exit_beacon_(backup_registry_key, user_data_dir, local_state), |
| external_client_id_(external_client_id), |
| entropy_state_(local_state), |
| entropy_source_returned_(ENTROPY_SOURCE_NONE), |
| metrics_ids_were_reset_(false), |
| startup_visibility_(startup_visibility) { |
| DCHECK(!store_client_info_.is_null()); |
| DCHECK(!load_client_info_.is_null()); |
| ResetMetricsIDsIfNecessary(); |
| |
| [[maybe_unused]] bool is_first_run = false; |
| int64_t install_date = local_state_->GetInt64(prefs::kInstallDate); |
| |
| // Set the install date if this is our first run. |
| if (install_date == 0) { |
| local_state_->SetInt64(prefs::kInstallDate, base::Time::Now().ToTimeT()); |
| is_first_run = true; |
| } |
| |
| if (enabled_state_provider_->IsConsentGiven()) { |
| ForceClientIdCreation(); |
| } else { |
| #if BUILDFLAG(IS_ANDROID) |
| // If on start up we determine that the client has not given their consent |
| // to report their metrics, the new sampling trial should be used to |
| // determine whether the client is sampled in or out (if the user ever |
| // enables metrics reporting). This covers users that are going through |
| // the first run, as well as users that have metrics reporting disabled. |
| // |
| // See crbug/1306481 and the comment above |kUsePostFREFixSamplingTrial| in |
| // components/metrics/metrics_pref_names.cc for more details. |
| local_state_->SetBoolean(metrics::prefs::kUsePostFREFixSamplingTrial, true); |
| #endif // BUILDFLAG(IS_ANDROID) |
| } |
| |
| // Generate and store a provisional client ID if necessary. This ID will be |
| // used for field trial randomization on first run (and possibly in future |
| // runs if the user closes Chrome during the FRE) and will be promoted to |
| // become the client ID if UMA is enabled during this session, via the logic |
| // in ForceClientIdCreation(). If UMA is disabled (refused), we discard it. |
| // |
| // Note: This means that if a provisional client ID is used for this session, |
| // and the user disables (refuses) UMA, then starting from the next run, the |
| // field trial randomization (group assignment) will be different. |
| if (ShouldGenerateProvisionalClientId(is_first_run)) { |
| local_state_->SetString(prefs::kMetricsProvisionalClientID, |
| base::Uuid::GenerateRandomV4().AsLowercaseString()); |
| } |
| |
| // `initial_client_id_` will only be set in the following cases: |
| // 1. UMA is enabled |
| // 2. there is a provisional client id (due to this being a first run) |
| // 3. there is an externally provided client ID (e.g. in Lacros, from Ash) |
| if (!client_id_.empty()) { |
| initial_client_id_ = client_id_; |
| } else if (!external_client_id_.empty()) { |
| // Typically, `client_id_` should have been set to the external client ID in |
| // the call to ForceClientIdCreation() above. However, that call is gated, |
| // and may not always happen, for example if this is a first run and the |
| // consent state is not yet known (although we know it is soon going to be |
| // set to true, since an external client ID was provided). |
| initial_client_id_ = external_client_id_; |
| } else { |
| // Note that there is possibly no provisional client ID. |
| initial_client_id_ = |
| local_state_->GetString(prefs::kMetricsProvisionalClientID); |
| } |
| CHECK(!instance_exists_); |
| instance_exists_ = true; |
| } |
| |
| MetricsStateManager::~MetricsStateManager() { |
| CHECK(instance_exists_); |
| instance_exists_ = false; |
| } |
| |
| std::unique_ptr<MetricsProvider> MetricsStateManager::GetProvider() { |
| return std::make_unique<MetricsStateMetricsProvider>( |
| local_state_, metrics_ids_were_reset_, previous_client_id_, |
| initial_client_id_, cloned_install_detector_); |
| } |
| |
| std::unique_ptr<MetricsProvider> |
| MetricsStateManager::GetProviderAndSetRandomSeedForTesting(int64_t seed) { |
| auto provider = std::make_unique<MetricsStateMetricsProvider>( |
| local_state_, metrics_ids_were_reset_, previous_client_id_, |
| initial_client_id_, cloned_install_detector_); |
| provider->SetRandomSeedForTesting(seed); // IN-TEST |
| return provider; |
| } |
| |
| bool MetricsStateManager::IsMetricsReportingEnabled() { |
| return enabled_state_provider_->IsReportingEnabled(); |
| } |
| |
| bool MetricsStateManager::IsExtendedSafeModeSupported() const { |
| return clean_exit_beacon_.IsExtendedSafeModeSupported(); |
| } |
| |
| int MetricsStateManager::GetLowEntropySource() { |
| return entropy_state_.GetLowEntropySource(); |
| } |
| |
| int MetricsStateManager::GetOldLowEntropySource() { |
| return entropy_state_.GetOldLowEntropySource(); |
| } |
| |
| int MetricsStateManager::GetPseudoLowEntropySource() { |
| return entropy_state_.GetPseudoLowEntropySource(); |
| } |
| |
| void MetricsStateManager::InstantiateFieldTrialList() { |
| // Instantiate the FieldTrialList to support field trials. If an instance |
| // already exists, this is likely a test scenario with a ScopedFeatureList, so |
| // use the existing instance so that any overrides are still applied. |
| if (!base::FieldTrialList::GetInstance()) { |
| // This is intentionally leaked since it needs to live for the duration of |
| // the browser process and there's no benefit in cleaning it up at exit. |
| base::FieldTrialList* leaked_field_trial_list = new base::FieldTrialList(); |
| ANNOTATE_LEAKING_OBJECT_PTR(leaked_field_trial_list); |
| std::ignore = leaked_field_trial_list; |
| } |
| |
| // When benchmarking is enabled, field trials' default groups are chosen, so |
| // see whether benchmarking needs to be enabled here, before any field trials |
| // are created. |
| // TODO(crbug/1257204): Some FieldTrial-setup-related code is here and some is |
| // in VariationsFieldTrialCreator::SetUpFieldTrials(). It's not ideal that |
| // it's in two places. |
| if (ShouldEnableBenchmarking(entropy_params_.force_benchmarking_mode)) |
| base::FieldTrial::EnableBenchmarking(); |
| |
| const base::CommandLine* command_line = |
| base::CommandLine::ForCurrentProcess(); |
| if (command_line->HasSwitch(variations::switches::kForceFieldTrialParams)) { |
| bool result = |
| variations::AssociateParamsFromString(command_line->GetSwitchValueASCII( |
| variations::switches::kForceFieldTrialParams)); |
| if (!result) { |
| // Some field trial params implement things like csv or json with a |
| // particular param. If some control characters are not %-encoded, it can |
| // lead to confusing error messages, so add a hint here. |
| ExitWithMessage(base::StringPrintf( |
| "Invalid --%s list specified. Make sure you %%-" |
| "encode the following characters in param values: %%:/.,", |
| variations::switches::kForceFieldTrialParams)); |
| } |
| } |
| |
| // Ensure any field trials specified on the command line are initialized. |
| if (command_line->HasSwitch(::switches::kForceFieldTrials)) { |
| // Create field trials without activating them, so that this behaves in a |
| // consistent manner with field trials created from the server. |
| bool result = base::FieldTrialList::CreateTrialsFromString( |
| command_line->GetSwitchValueASCII(::switches::kForceFieldTrials)); |
| if (!result) { |
| ExitWithMessage(base::StringPrintf("Invalid --%s list specified.", |
| ::switches::kForceFieldTrials)); |
| } |
| } |
| |
| // Initializing the CleanExitBeacon is done after FieldTrialList instantiation |
| // to allow experimentation on the CleanExitBeacon. |
| clean_exit_beacon_.Initialize(); |
| } |
| |
| void MetricsStateManager::LogHasSessionShutdownCleanly( |
| bool has_session_shutdown_cleanly, |
| bool is_extended_safe_mode) { |
| clean_exit_beacon_.WriteBeaconValue(has_session_shutdown_cleanly, |
| is_extended_safe_mode); |
| } |
| |
| void MetricsStateManager::ForceClientIdCreation() { |
| // TODO(asvitkine): Ideally, all tests would actually set up consent properly, |
| // so the command-line checks wouldn't be needed here. |
| // Currently, kForceEnableMetricsReporting is used by Java UkmTest and |
| // kMetricsRecordingOnly is used by Chromedriver tests. |
| DCHECK(enabled_state_provider_->IsConsentGiven() || |
| IsMetricsReportingForceEnabled() || IsMetricsRecordingOnlyEnabled()); |
| if (!external_client_id_.empty()) { |
| client_id_ = external_client_id_; |
| base::UmaHistogramEnumeration("UMA.ClientIdSource", |
| ClientIdSource::kClientIdFromExternal); |
| local_state_->SetString(prefs::kMetricsClientID, client_id_); |
| return; |
| } |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| std::string previous_client_id = client_id_; |
| #endif // BUILDFLAG(IS_CHROMEOS_ASH) |
| { |
| std::string client_id_from_prefs = ReadClientId(local_state_); |
| // If client id in prefs matches the cached copy, return early. |
| if (!client_id_from_prefs.empty() && client_id_from_prefs == client_id_) { |
| base::UmaHistogramEnumeration("UMA.ClientIdSource", |
| ClientIdSource::kClientIdMatches); |
| return; |
| } |
| client_id_.swap(client_id_from_prefs); |
| } |
| |
| if (!client_id_.empty()) { |
| base::UmaHistogramEnumeration("UMA.ClientIdSource", |
| ClientIdSource::kClientIdFromLocalState); |
| return; |
| } |
| |
| const std::unique_ptr<ClientInfo> client_info_backup = LoadClientInfo(); |
| if (client_info_backup) { |
| client_id_ = client_info_backup->client_id; |
| |
| const base::Time now = base::Time::Now(); |
| |
| // Save the recovered client id and also try to reinstantiate the backup |
| // values for the dates corresponding with that client id in order to avoid |
| // weird scenarios where we could report an old client id with a recent |
| // install date. |
| local_state_->SetString(prefs::kMetricsClientID, client_id_); |
| local_state_->SetInt64(prefs::kInstallDate, |
| client_info_backup->installation_date != 0 |
| ? client_info_backup->installation_date |
| : now.ToTimeT()); |
| local_state_->SetInt64(prefs::kMetricsReportingEnabledTimestamp, |
| client_info_backup->reporting_enabled_date != 0 |
| ? client_info_backup->reporting_enabled_date |
| : now.ToTimeT()); |
| |
| base::TimeDelta recovered_installation_age; |
| if (client_info_backup->installation_date != 0) { |
| recovered_installation_age = |
| now - base::Time::FromTimeT(client_info_backup->installation_date); |
| } |
| base::UmaHistogramEnumeration("UMA.ClientIdSource", |
| ClientIdSource::kClientIdBackupRecovered); |
| base::UmaHistogramCounts10000("UMA.ClientIdBackupRecoveredWithAge", |
| recovered_installation_age.InHours()); |
| |
| // Flush the backup back to persistent storage in case we re-generated |
| // missing data above. |
| BackUpCurrentClientInfo(); |
| return; |
| } |
| |
| // If we're here, there was no client ID yet (either in prefs or backup), |
| // so generate a new one. If there's a provisional client id (e.g. UMA |
| // was enabled as part of first run), promote that to the client id, |
| // otherwise (e.g. UMA enabled in a future session), generate a new one. |
| std::string provisional_client_id = |
| local_state_->GetString(prefs::kMetricsProvisionalClientID); |
| if (provisional_client_id.empty()) { |
| client_id_ = base::Uuid::GenerateRandomV4().AsLowercaseString(); |
| base::UmaHistogramEnumeration("UMA.ClientIdSource", |
| ClientIdSource::kClientIdNew); |
| } else { |
| client_id_ = provisional_client_id; |
| local_state_->ClearPref(prefs::kMetricsProvisionalClientID); |
| base::UmaHistogramEnumeration("UMA.ClientIdSource", |
| ClientIdSource::kClientIdFromProvisionalId); |
| } |
| local_state_->SetString(prefs::kMetricsClientID, client_id_); |
| |
| // Record the timestamp of when the user opted in to UMA. |
| local_state_->SetInt64(prefs::kMetricsReportingEnabledTimestamp, |
| base::Time::Now().ToTimeT()); |
| |
| BackUpCurrentClientInfo(); |
| } |
| |
| void MetricsStateManager::SetExternalClientId(const std::string& id) { |
| external_client_id_ = id; |
| } |
| |
| void MetricsStateManager::CheckForClonedInstall() { |
| cloned_install_detector_.CheckForClonedInstall(local_state_); |
| } |
| |
| bool MetricsStateManager::ShouldResetClientIdsOnClonedInstall() { |
| return cloned_install_detector_.ShouldResetClientIds(local_state_); |
| } |
| |
| base::CallbackListSubscription |
| MetricsStateManager::AddOnClonedInstallDetectedCallback( |
| base::OnceClosure callback) { |
| return cloned_install_detector_.AddOnClonedInstallDetectedCallback( |
| std::move(callback)); |
| } |
| |
| std::unique_ptr<const variations::EntropyProviders> |
| MetricsStateManager::CreateEntropyProviders(bool enable_limited_entropy_mode) { |
| // TODO(crbug.com/1508150): remove `enable_limited_entropy_mode` when it's |
| // true for all callers. |
| auto limited_entropy_randomization_source = |
| enable_limited_entropy_mode ? GetLimitedEntropyRandomizationSource() |
| : std::string_view(); |
| return std::make_unique<variations::EntropyProviders>( |
| GetHighEntropySource(), |
| variations::ValueInRange{ |
| .value = base::checked_cast<uint32_t>(GetLowEntropySource()), |
| .range = EntropyState::kMaxLowEntropySize}, |
| limited_entropy_randomization_source, |
| ShouldEnableBenchmarking(entropy_params_.force_benchmarking_mode)); |
| } |
| |
| // static |
| std::unique_ptr<MetricsStateManager> MetricsStateManager::Create( |
| PrefService* local_state, |
| EnabledStateProvider* enabled_state_provider, |
| const std::wstring& backup_registry_key, |
| const base::FilePath& user_data_dir, |
| StartupVisibility startup_visibility, |
| EntropyParams entropy_params, |
| StoreClientInfoCallback store_client_info, |
| LoadClientInfoCallback retrieve_client_info, |
| base::StringPiece external_client_id) { |
| std::unique_ptr<MetricsStateManager> result; |
| // Note: |instance_exists_| is updated in the constructor and destructor. |
| if (!instance_exists_) { |
| result.reset(new MetricsStateManager( |
| local_state, enabled_state_provider, backup_registry_key, user_data_dir, |
| entropy_params, startup_visibility, |
| store_client_info.is_null() ? base::DoNothing() |
| : std::move(store_client_info), |
| retrieve_client_info.is_null() |
| ? base::BindRepeating(&NoOpLoadClientInfoBackup) |
| : std::move(retrieve_client_info), |
| external_client_id)); |
| } |
| return result; |
| } |
| |
| // static |
| void MetricsStateManager::RegisterPrefs(PrefRegistrySimple* registry) { |
| registry->RegisterStringPref(prefs::kMetricsProvisionalClientID, |
| std::string()); |
| registry->RegisterStringPref(prefs::kMetricsClientID, std::string()); |
| registry->RegisterInt64Pref(prefs::kMetricsReportingEnabledTimestamp, 0); |
| registry->RegisterInt64Pref(prefs::kInstallDate, 0); |
| #if BUILDFLAG(IS_ANDROID) |
| registry->RegisterBooleanPref(prefs::kUsePostFREFixSamplingTrial, false); |
| #endif // BUILDFLAG(IS_ANDROID) |
| |
| EntropyState::RegisterPrefs(registry); |
| ClonedInstallDetector::RegisterPrefs(registry); |
| } |
| |
| void MetricsStateManager::BackUpCurrentClientInfo() { |
| ClientInfo client_info; |
| client_info.client_id = client_id_; |
| client_info.installation_date = ReadInstallDate(local_state_); |
| client_info.reporting_enabled_date = ReadEnabledDate(local_state_); |
| store_client_info_.Run(client_info); |
| } |
| |
| std::unique_ptr<ClientInfo> MetricsStateManager::LoadClientInfo() { |
| // If a cloned install was detected, loading ClientInfo from backup will be |
| // a race condition with clearing the backup. Skip all backup reads for this |
| // session. |
| if (metrics_ids_were_reset_) |
| return nullptr; |
| |
| std::unique_ptr<ClientInfo> client_info = load_client_info_.Run(); |
| |
| // The GUID retrieved should be valid unless retrieval failed. |
| // If not, return nullptr. This will result in a new GUID being generated by |
| // the calling function ForceClientIdCreation(). |
| if (client_info && |
| !base::Uuid::ParseCaseInsensitive(client_info->client_id).is_valid()) { |
| return nullptr; |
| } |
| |
| return client_info; |
| } |
| |
| std::string_view MetricsStateManager::GetLimitedEntropyRandomizationSource() { |
| // No limited entropy randomization source will be generated if limited |
| // entropy randomization is not supported in this context (e.g. in Android |
| // Webview). |
| if (entropy_params_.default_entropy_provider_type == |
| EntropyProviderType::kLow) { |
| return std::string_view(); |
| } |
| return entropy_state_.GetLimitedEntropyRandomizationSource(); |
| } |
| |
| std::string MetricsStateManager::GetHighEntropySource() { |
| // If high entropy randomization is not supported in this context (e.g. in |
| // Android Webview), or if UMA is not enabled (so there is no client id), then |
| // high entropy randomization is disabled. |
| if (entropy_params_.default_entropy_provider_type == |
| EntropyProviderType::kLow || |
| initial_client_id_.empty()) { |
| UpdateEntropySourceReturnedValue(ENTROPY_SOURCE_LOW); |
| return ""; |
| } |
| UpdateEntropySourceReturnedValue(ENTROPY_SOURCE_HIGH); |
| return entropy_state_.GetHighEntropySource(initial_client_id_); |
| } |
| |
| void MetricsStateManager::UpdateEntropySourceReturnedValue( |
| EntropySourceType type) { |
| if (entropy_source_returned_ != ENTROPY_SOURCE_NONE) |
| return; |
| |
| entropy_source_returned_ = type; |
| base::UmaHistogramEnumeration("UMA.EntropySourceType", type, |
| ENTROPY_SOURCE_ENUM_SIZE); |
| } |
| |
| void MetricsStateManager::ResetMetricsIDsIfNecessary() { |
| if (!ShouldResetClientIdsOnClonedInstall()) |
| return; |
| metrics_ids_were_reset_ = true; |
| previous_client_id_ = ReadClientId(local_state_); |
| |
| base::UmaHistogramBoolean("UMA.MetricsIDsReset", true); |
| |
| DCHECK(client_id_.empty()); |
| |
| local_state_->ClearPref(prefs::kMetricsClientID); |
| local_state_->ClearPref(prefs::kMetricsLogRecordId); |
| EntropyState::ClearPrefs(local_state_); |
| |
| ClonedInstallDetector::RecordClonedInstallInfo(local_state_); |
| |
| // Also clear the backed up client info. This is asynchronus; any reads |
| // shortly after may retrieve the old ClientInfo from the backup. |
| store_client_info_.Run(ClientInfo()); |
| } |
| |
| bool MetricsStateManager::ShouldGenerateProvisionalClientId(bool is_first_run) { |
| #if BUILDFLAG(IS_WIN) |
| // We do not want to generate a provisional client ID on Windows because |
| // there's no UMA checkbox on first run. Instead it comes from the install |
| // page. So if UMA is not enabled at this point, it's unlikely it will be |
| // enabled in the same session since that requires the user to manually do |
| // that via settings page after they unchecked it on the download page. |
| // |
| // Note: Windows first run is covered by browser tests |
| // FirstRunMasterPrefsVariationsSeedTest.PRE_SecondRun and |
| // FirstRunMasterPrefsVariationsSeedTest.SecondRun. If the platform ifdef |
| // for this logic changes, the tests should be updated as well. |
| return false; |
| #else |
| // We should only generate a provisional client ID on the first run. If for |
| // some reason there is already a client ID, we do not generate one either. |
| // This can happen if metrics reporting is managed by a policy. |
| if (!is_first_run || !client_id_.empty()) |
| return false; |
| |
| // Return false if |kMetricsReportingEnabled| is managed by a policy. For |
| // example, if metrics reporting is disabled by a policy, then |
| // |kMetricsReportingEnabled| will always be set to false, so there is no |
| // reason to generate a provisional client ID. If metrics reporting is enabled |
| // by a policy, then the default value of |kMetricsReportingEnabled| will be |
| // true, and so a client ID will have already been generated (we would have |
| // returned false already because of the previous check). |
| if (local_state_->IsManagedPreference(prefs::kMetricsReportingEnabled)) |
| return false; |
| |
| // If this is a non-Google-Chrome-branded build, we do not want to generate a |
| // provisional client ID because metrics reporting is not enabled on those |
| // builds. This would be problematic because we store the provisional client |
| // ID in the Local State, and clear it when either 1) we enable UMA (the |
| // provisional client ID becomes the client ID), or 2) we disable UMA. Since |
| // in non-Google-Chrome-branded builds we never actually go through the code |
| // paths to either enable or disable UMA, the pref storing the provisional |
| // client ID would never be cleared. However, for test consistency between |
| // the different builds, we do not return false here if |
| // |enable_provisional_client_id_for_testing_| is set to true. |
| if (!BUILDFLAG(GOOGLE_CHROME_BRANDING) && |
| !enable_provisional_client_id_for_testing_) { |
| return false; |
| } |
| |
| return true; |
| #endif // BUILDFLAG(IS_WIN) |
| } |
| |
| } // namespace metrics |