• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2014 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "components/metrics/metrics_state_manager.h"
6 
7 #include <cstddef>
8 #include <cstdint>
9 #include <limits>
10 #include <memory>
11 #include <random>
12 #include <string>
13 #include <tuple>
14 #include <utility>
15 
16 #include "base/base_switches.h"
17 #include "base/check.h"
18 #include "base/command_line.h"
19 #include "base/debug/leak_annotations.h"
20 #include "base/functional/callback_helpers.h"
21 #include "base/memory/raw_ptr.h"
22 #include "base/memory/raw_ref.h"
23 #include "base/metrics/histogram_functions.h"
24 #include "base/metrics/histogram_macros.h"
25 #include "base/numerics/safe_conversions.h"
26 #include "base/rand_util.h"
27 #include "base/strings/string_number_conversions.h"
28 #include "base/strings/stringprintf.h"
29 #include "base/threading/thread_restrictions.h"
30 #include "base/time/time.h"
31 #include "base/uuid.h"
32 #include "build/branding_buildflags.h"
33 #include "build/build_config.h"
34 #include "components/metrics/cloned_install_detector.h"
35 #include "components/metrics/enabled_state_provider.h"
36 #include "components/metrics/entropy_state.h"
37 #include "components/metrics/metrics_data_validation.h"
38 #include "components/metrics/metrics_log.h"
39 #include "components/metrics/metrics_pref_names.h"
40 #include "components/metrics/metrics_provider.h"
41 #include "components/metrics/metrics_switches.h"
42 #include "components/prefs/pref_registry_simple.h"
43 #include "components/prefs/pref_service.h"
44 #include "components/variations/entropy_provider.h"
45 #include "components/variations/field_trial_config/field_trial_util.h"
46 #include "components/variations/pref_names.h"
47 #include "components/variations/variations_switches.h"
48 #include "third_party/metrics_proto/chrome_user_metrics_extension.pb.h"
49 #include "third_party/metrics_proto/system_profile.pb.h"
50 
51 namespace metrics {
52 namespace {
53 
ReadEnabledDate(PrefService * local_state)54 int64_t ReadEnabledDate(PrefService* local_state) {
55   return local_state->GetInt64(prefs::kMetricsReportingEnabledTimestamp);
56 }
57 
ReadInstallDate(PrefService * local_state)58 int64_t ReadInstallDate(PrefService* local_state) {
59   return local_state->GetInt64(prefs::kInstallDate);
60 }
61 
ReadClientId(PrefService * local_state)62 std::string ReadClientId(PrefService* local_state) {
63   return local_state->GetString(prefs::kMetricsClientID);
64 }
65 
66 // Round a timestamp measured in seconds since epoch to one with a granularity
67 // of an hour. This can be used before uploaded potentially sensitive
68 // timestamps.
RoundSecondsToHour(int64_t time_in_seconds)69 int64_t RoundSecondsToHour(int64_t time_in_seconds) {
70   return 3600 * (time_in_seconds / 3600);
71 }
72 
73 // Records the cloned install histogram.
LogClonedInstall()74 void LogClonedInstall() {
75   // Equivalent to UMA_HISTOGRAM_BOOLEAN with the stability flag set.
76   UMA_STABILITY_HISTOGRAM_ENUMERATION("UMA.IsClonedInstall", 1, 2);
77 }
78 
79 // No-op function used to create a MetricsStateManager.
NoOpLoadClientInfoBackup()80 std::unique_ptr<metrics::ClientInfo> NoOpLoadClientInfoBackup() {
81   return nullptr;
82 }
83 
84 // Exits the browser with a helpful error message if an invalid,
85 // field-trial-related command-line flag was specified.
ExitWithMessage(const std::string & message)86 void ExitWithMessage(const std::string& message) {
87   puts(message.c_str());
88   exit(1);
89 }
90 
91 // Returns a log normal distribution based on the feature params of
92 // |kNonUniformityValidationFeature|.
GetLogNormalDist()93 std::lognormal_distribution<double> GetLogNormalDist() {
94   double mean = kLogNormalMean.Get();
95   double delta = kLogNormalDelta.Get();
96   double std_dev = kLogNormalStdDev.Get();
97   return std::lognormal_distribution<double>(mean + std::log(1.0 + delta),
98                                              std_dev);
99 }
100 
101 // Used to draw a data point from a log normal distribution.
102 struct LogNormalMetricState {
LogNormalMetricStatemetrics::__anon0bdfb9300111::LogNormalMetricState103   LogNormalMetricState()
104       : dist(GetLogNormalDist()), gen(std::mt19937(base::RandUint64())) {}
105 
106   // Records the artificial non-uniformity histogram for data validation.
LogArtificialNonUniformitymetrics::__anon0bdfb9300111::LogNormalMetricState107   void LogArtificialNonUniformity() {
108     double rand = dist(gen);
109     // We pick 10k as the upper bound for this histogram so as to avoid losing
110     // precision. See comments for |kLogNormalMean|.
111     base::UmaHistogramCounts10000("UMA.DataValidation.LogNormal",
112                                   base::saturated_cast<int>(rand));
113   }
114 
115   // A log normal distribution generator generated by the `GetLogNormalDist()`
116   // function.
117   std::lognormal_distribution<double> dist;
118   // The pseudo-random generator used to generate a data point from |dist|.
119   std::mt19937 gen;
120 };
121 
122 class MetricsStateMetricsProvider : public MetricsProvider {
123  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)124   MetricsStateMetricsProvider(
125       PrefService* local_state,
126       bool metrics_ids_were_reset,
127       std::string previous_client_id,
128       std::string initial_client_id,
129       ClonedInstallDetector const& cloned_install_detector)
130       : local_state_(local_state),
131         metrics_ids_were_reset_(metrics_ids_were_reset),
132         previous_client_id_(std::move(previous_client_id)),
133         initial_client_id_(std::move(initial_client_id)),
134         cloned_install_detector_(cloned_install_detector) {}
135 
136   MetricsStateMetricsProvider(const MetricsStateMetricsProvider&) = delete;
137   MetricsStateMetricsProvider& operator=(const MetricsStateMetricsProvider&) =
138       delete;
139 
140   // MetricsProvider:
ProvideSystemProfileMetrics(SystemProfileProto * system_profile)141   void ProvideSystemProfileMetrics(
142       SystemProfileProto* system_profile) override {
143     system_profile->set_uma_enabled_date(
144         RoundSecondsToHour(ReadEnabledDate(local_state_)));
145     system_profile->set_install_date(
146         RoundSecondsToHour(ReadInstallDate(local_state_)));
147 
148     // Client id in the log shouldn't be different than the |local_state_| one
149     // except when the client disabled UMA before we populate this field to the
150     // log. If that's the case, the client id in the |local_state_| should be
151     // empty and we should set |client_id_was_used_for_trial_assignment| to
152     // false.
153     std::string client_id = ReadClientId(local_state_);
154     system_profile->set_client_id_was_used_for_trial_assignment(
155         !client_id.empty() && client_id == initial_client_id_);
156 
157     ClonedInstallInfo cloned =
158         ClonedInstallDetector::ReadClonedInstallInfo(local_state_);
159     if (cloned.reset_count == 0)
160       return;
161     auto* cloned_install_info = system_profile->mutable_cloned_install_info();
162     if (metrics_ids_were_reset_) {
163       // Only report the cloned from client_id in the resetting session.
164       if (!previous_client_id_.empty()) {
165         cloned_install_info->set_cloned_from_client_id(
166             MetricsLog::Hash(previous_client_id_));
167       }
168     }
169     cloned_install_info->set_last_timestamp(
170         RoundSecondsToHour(cloned.last_reset_timestamp));
171     cloned_install_info->set_first_timestamp(
172         RoundSecondsToHour(cloned.first_reset_timestamp));
173     cloned_install_info->set_count(cloned.reset_count);
174   }
175 
ProvidePreviousSessionData(ChromeUserMetricsExtension * uma_proto)176   void ProvidePreviousSessionData(
177       ChromeUserMetricsExtension* uma_proto) override {
178     if (metrics_ids_were_reset_) {
179       LogClonedInstall();
180       if (!previous_client_id_.empty()) {
181         // If we know the previous client id, overwrite the client id for the
182         // previous session log so the log contains the client id at the time
183         // of the previous session. This allows better attribution of crashes
184         // to earlier behavior. If the previous client id is unknown, leave
185         // the current client id.
186 #if BUILDFLAG(IS_CHROMEOS_ASH)
187         metrics::structured::NeutrinoDevicesLogWithClientId(
188             previous_client_id_, metrics::structured::NeutrinoDevicesLocation::
189                                      kProvidePreviousSessionData);
190 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
191         uma_proto->set_client_id(MetricsLog::Hash(previous_client_id_));
192       }
193     }
194   }
195 
ProvideCurrentSessionData(ChromeUserMetricsExtension * uma_proto)196   void ProvideCurrentSessionData(
197       ChromeUserMetricsExtension* uma_proto) override {
198     if (cloned_install_detector_->ClonedInstallDetectedInCurrentSession())
199       LogClonedInstall();
200     log_normal_metric_state_.LogArtificialNonUniformity();
201   }
202 
203   // Set a random seed for the random number generator.
SetRandomSeedForTesting(int64_t seed)204   void SetRandomSeedForTesting(int64_t seed) {
205     log_normal_metric_state_.gen = std::mt19937(seed);
206   }
207 
208  private:
209   const raw_ptr<PrefService> local_state_;
210   const bool metrics_ids_were_reset_;
211   // |previous_client_id_| is set only (if known) when
212   // |metrics_ids_were_reset_|
213   const std::string previous_client_id_;
214   // The client id that was used to randomize field trials. An empty string if
215   // the low entropy source was used to do randomization.
216   const std::string initial_client_id_;
217   const raw_ref<const ClonedInstallDetector> cloned_install_detector_;
218   LogNormalMetricState log_normal_metric_state_;
219 };
220 
ShouldEnableBenchmarking(bool force_benchmarking_mode)221 bool ShouldEnableBenchmarking(bool force_benchmarking_mode) {
222   // TODO(crbug/1251680): See whether it's possible to consolidate the switches.
223   return force_benchmarking_mode ||
224          base::CommandLine::ForCurrentProcess()->HasSwitch(
225              variations::switches::kEnableBenchmarking);
226 }
227 
228 }  // namespace
229 
230 // static
231 bool MetricsStateManager::instance_exists_ = false;
232 
233 // static
234 bool MetricsStateManager::enable_provisional_client_id_for_testing_ = false;
235 
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)236 MetricsStateManager::MetricsStateManager(
237     PrefService* local_state,
238     EnabledStateProvider* enabled_state_provider,
239     const std::wstring& backup_registry_key,
240     const base::FilePath& user_data_dir,
241     EntropyParams entropy_params,
242     StartupVisibility startup_visibility,
243     StoreClientInfoCallback store_client_info,
244     LoadClientInfoCallback retrieve_client_info,
245     base::StringPiece external_client_id)
246     : local_state_(local_state),
247       enabled_state_provider_(enabled_state_provider),
248       entropy_params_(entropy_params),
249       store_client_info_(std::move(store_client_info)),
250       load_client_info_(std::move(retrieve_client_info)),
251       clean_exit_beacon_(backup_registry_key, user_data_dir, local_state),
252       external_client_id_(external_client_id),
253       entropy_state_(local_state),
254       entropy_source_returned_(ENTROPY_SOURCE_NONE),
255       metrics_ids_were_reset_(false),
256       startup_visibility_(startup_visibility) {
257   DCHECK(!store_client_info_.is_null());
258   DCHECK(!load_client_info_.is_null());
259   ResetMetricsIDsIfNecessary();
260 
261   [[maybe_unused]] bool is_first_run = false;
262   int64_t install_date = local_state_->GetInt64(prefs::kInstallDate);
263 
264   // Set the install date if this is our first run.
265   if (install_date == 0) {
266     local_state_->SetInt64(prefs::kInstallDate, base::Time::Now().ToTimeT());
267     is_first_run = true;
268   }
269 
270   if (enabled_state_provider_->IsConsentGiven()) {
271 #if BUILDFLAG(IS_CHROMEOS_ASH)
272     metrics::structured::NeutrinoDevicesLogWithClientId(
273         client_id_,
274         metrics::structured::NeutrinoDevicesLocation::kMetricsStateManager);
275 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
276     ForceClientIdCreation();
277   } else {
278 #if BUILDFLAG(IS_ANDROID)
279     // If on start up we determine that the client has not given their consent
280     // to report their metrics, the new sampling trial should be used to
281     // determine whether the client is sampled in or out (if the user ever
282     // enables metrics reporting). This covers users that are going through
283     // the first run, as well as users that have metrics reporting disabled.
284     //
285     // See crbug/1306481 and the comment above |kUsePostFREFixSamplingTrial| in
286     // components/metrics/metrics_pref_names.cc for more details.
287     local_state_->SetBoolean(metrics::prefs::kUsePostFREFixSamplingTrial, true);
288 #endif  // BUILDFLAG(IS_ANDROID)
289   }
290 
291   // Generate and store a provisional client ID if necessary. This ID will be
292   // used for field trial randomization on first run (and possibly in future
293   // runs if the user closes Chrome during the FRE) and will be promoted to
294   // become the client ID if UMA is enabled during this session, via the logic
295   // in ForceClientIdCreation(). If UMA is disabled (refused), we discard it.
296   //
297   // Note: This means that if a provisional client ID is used for this session,
298   // and the user disables (refuses) UMA, then starting from the next run, the
299   // field trial randomization (group assignment) will be different.
300   if (ShouldGenerateProvisionalClientId(is_first_run)) {
301     local_state_->SetString(prefs::kMetricsProvisionalClientID,
302                             base::Uuid::GenerateRandomV4().AsLowercaseString());
303   }
304 
305   // The |initial_client_id_| should only be set if UMA is enabled or there's a
306   // provisional client id.
307   initial_client_id_ =
308       (client_id_.empty()
309            ? local_state_->GetString(prefs::kMetricsProvisionalClientID)
310            : client_id_);
311   DCHECK(!instance_exists_);
312   instance_exists_ = true;
313 }
314 
~MetricsStateManager()315 MetricsStateManager::~MetricsStateManager() {
316   DCHECK(instance_exists_);
317   instance_exists_ = false;
318 }
319 
GetProvider()320 std::unique_ptr<MetricsProvider> MetricsStateManager::GetProvider() {
321   return std::make_unique<MetricsStateMetricsProvider>(
322       local_state_, metrics_ids_were_reset_, previous_client_id_,
323       initial_client_id_, cloned_install_detector_);
324 }
325 
326 std::unique_ptr<MetricsProvider>
GetProviderAndSetRandomSeedForTesting(int64_t seed)327 MetricsStateManager::GetProviderAndSetRandomSeedForTesting(int64_t seed) {
328   auto provider = std::make_unique<MetricsStateMetricsProvider>(
329       local_state_, metrics_ids_were_reset_, previous_client_id_,
330       initial_client_id_, cloned_install_detector_);
331   provider->SetRandomSeedForTesting(seed);  // IN-TEST
332   return provider;
333 }
334 
IsMetricsReportingEnabled()335 bool MetricsStateManager::IsMetricsReportingEnabled() {
336   return enabled_state_provider_->IsReportingEnabled();
337 }
338 
IsExtendedSafeModeSupported() const339 bool MetricsStateManager::IsExtendedSafeModeSupported() const {
340   return clean_exit_beacon_.IsExtendedSafeModeSupported();
341 }
342 
GetLowEntropySource()343 int MetricsStateManager::GetLowEntropySource() {
344   return entropy_state_.GetLowEntropySource();
345 }
346 
InstantiateFieldTrialList()347 void MetricsStateManager::InstantiateFieldTrialList() {
348   // Instantiate the FieldTrialList to support field trials. If an instance
349   // already exists, this is likely a test scenario with a ScopedFeatureList, so
350   // use the existing instance so that any overrides are still applied.
351   if (!base::FieldTrialList::GetInstance()) {
352     // This is intentionally leaked since it needs to live for the duration of
353     // the browser process and there's no benefit in cleaning it up at exit.
354     base::FieldTrialList* leaked_field_trial_list = new base::FieldTrialList();
355     ANNOTATE_LEAKING_OBJECT_PTR(leaked_field_trial_list);
356     std::ignore = leaked_field_trial_list;
357   }
358 
359   // When benchmarking is enabled, field trials' default groups are chosen, so
360   // see whether benchmarking needs to be enabled here, before any field trials
361   // are created.
362   // TODO(crbug/1257204): Some FieldTrial-setup-related code is here and some is
363   // in VariationsFieldTrialCreator::SetUpFieldTrials(). It's not ideal that
364   // it's in two places.
365   if (ShouldEnableBenchmarking(entropy_params_.force_benchmarking_mode))
366     base::FieldTrial::EnableBenchmarking();
367 
368   const base::CommandLine* command_line =
369       base::CommandLine::ForCurrentProcess();
370   if (command_line->HasSwitch(variations::switches::kForceFieldTrialParams)) {
371     bool result =
372         variations::AssociateParamsFromString(command_line->GetSwitchValueASCII(
373             variations::switches::kForceFieldTrialParams));
374     if (!result) {
375       // Some field trial params implement things like csv or json with a
376       // particular param. If some control characters are not %-encoded, it can
377       // lead to confusing error messages, so add a hint here.
378       ExitWithMessage(base::StringPrintf(
379           "Invalid --%s list specified. Make sure you %%-"
380           "encode the following characters in param values: %%:/.,",
381           variations::switches::kForceFieldTrialParams));
382     }
383   }
384 
385   // Ensure any field trials specified on the command line are initialized.
386   if (command_line->HasSwitch(::switches::kForceFieldTrials)) {
387     // Create field trials without activating them, so that this behaves in a
388     // consistent manner with field trials created from the server.
389     bool result = base::FieldTrialList::CreateTrialsFromString(
390         command_line->GetSwitchValueASCII(::switches::kForceFieldTrials));
391     if (!result) {
392       ExitWithMessage(base::StringPrintf("Invalid --%s list specified.",
393                                          ::switches::kForceFieldTrials));
394     }
395   }
396 
397   // Initializing the CleanExitBeacon is done after FieldTrialList instantiation
398   // to allow experimentation on the CleanExitBeacon.
399   clean_exit_beacon_.Initialize();
400 }
401 
LogHasSessionShutdownCleanly(bool has_session_shutdown_cleanly,bool is_extended_safe_mode)402 void MetricsStateManager::LogHasSessionShutdownCleanly(
403     bool has_session_shutdown_cleanly,
404     bool is_extended_safe_mode) {
405   clean_exit_beacon_.WriteBeaconValue(has_session_shutdown_cleanly,
406                                       is_extended_safe_mode);
407 }
408 
ForceClientIdCreation()409 void MetricsStateManager::ForceClientIdCreation() {
410   // TODO(asvitkine): Ideally, all tests would actually set up consent properly,
411   // so the command-line checks wouldn't be needed here.
412   // Currently, kForceEnableMetricsReporting is used by Java UkmTest and
413   // kMetricsRecordingOnly is used by Chromedriver tests.
414   DCHECK(enabled_state_provider_->IsConsentGiven() ||
415          IsMetricsReportingForceEnabled() || IsMetricsRecordingOnlyEnabled());
416   if (!external_client_id_.empty()) {
417     client_id_ = external_client_id_;
418     base::UmaHistogramEnumeration("UMA.ClientIdSource",
419                                   ClientIdSource::kClientIdFromExternal);
420     local_state_->SetString(prefs::kMetricsClientID, client_id_);
421     return;
422   }
423 #if BUILDFLAG(IS_CHROMEOS_ASH)
424   std::string previous_client_id = client_id_;
425 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
426   {
427     std::string client_id_from_prefs = ReadClientId(local_state_);
428     // If client id in prefs matches the cached copy, return early.
429     if (!client_id_from_prefs.empty() && client_id_from_prefs == client_id_) {
430       base::UmaHistogramEnumeration("UMA.ClientIdSource",
431                                     ClientIdSource::kClientIdMatches);
432       return;
433     }
434     client_id_.swap(client_id_from_prefs);
435   }
436 
437   if (!client_id_.empty()) {
438     base::UmaHistogramEnumeration("UMA.ClientIdSource",
439                                   ClientIdSource::kClientIdFromLocalState);
440 #if BUILDFLAG(IS_CHROMEOS_ASH)
441     LogClientIdChanged(
442         metrics::structured::NeutrinoDevicesLocation::kClientIdFromLocalState,
443         previous_client_id);
444 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
445     return;
446   }
447 
448   const std::unique_ptr<ClientInfo> client_info_backup = LoadClientInfo();
449   if (client_info_backup) {
450     client_id_ = client_info_backup->client_id;
451 
452     const base::Time now = base::Time::Now();
453 
454     // Save the recovered client id and also try to reinstantiate the backup
455     // values for the dates corresponding with that client id in order to avoid
456     // weird scenarios where we could report an old client id with a recent
457     // install date.
458     local_state_->SetString(prefs::kMetricsClientID, client_id_);
459     local_state_->SetInt64(prefs::kInstallDate,
460                            client_info_backup->installation_date != 0
461                                ? client_info_backup->installation_date
462                                : now.ToTimeT());
463     local_state_->SetInt64(prefs::kMetricsReportingEnabledTimestamp,
464                            client_info_backup->reporting_enabled_date != 0
465                                ? client_info_backup->reporting_enabled_date
466                                : now.ToTimeT());
467 
468     base::TimeDelta recovered_installation_age;
469     if (client_info_backup->installation_date != 0) {
470       recovered_installation_age =
471           now - base::Time::FromTimeT(client_info_backup->installation_date);
472     }
473     base::UmaHistogramEnumeration("UMA.ClientIdSource",
474                                   ClientIdSource::kClientIdBackupRecovered);
475     base::UmaHistogramCounts10000("UMA.ClientIdBackupRecoveredWithAge",
476                                   recovered_installation_age.InHours());
477 #if BUILDFLAG(IS_CHROMEOS_ASH)
478     LogClientIdChanged(
479         metrics::structured::NeutrinoDevicesLocation::kClientIdBackupRecovered,
480         previous_client_id);
481 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
482 
483     // Flush the backup back to persistent storage in case we re-generated
484     // missing data above.
485     BackUpCurrentClientInfo();
486     return;
487   }
488 
489   // If we're here, there was no client ID yet (either in prefs or backup),
490   // so generate a new one. If there's a provisional client id (e.g. UMA
491   // was enabled as part of first run), promote that to the client id,
492   // otherwise (e.g. UMA enabled in a future session), generate a new one.
493   std::string provisional_client_id =
494       local_state_->GetString(prefs::kMetricsProvisionalClientID);
495   if (provisional_client_id.empty()) {
496     client_id_ = base::Uuid::GenerateRandomV4().AsLowercaseString();
497     base::UmaHistogramEnumeration("UMA.ClientIdSource",
498                                   ClientIdSource::kClientIdNew);
499 #if BUILDFLAG(IS_CHROMEOS_ASH)
500     LogClientIdChanged(
501         metrics::structured::NeutrinoDevicesLocation::kClientIdNew,
502         previous_client_id);
503 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
504   } else {
505     client_id_ = provisional_client_id;
506     local_state_->ClearPref(prefs::kMetricsProvisionalClientID);
507     base::UmaHistogramEnumeration("UMA.ClientIdSource",
508                                   ClientIdSource::kClientIdFromProvisionalId);
509 #if BUILDFLAG(IS_CHROMEOS_ASH)
510     LogClientIdChanged(metrics::structured::NeutrinoDevicesLocation::
511                            kClientIdFromProvisionalId,
512                        previous_client_id);
513 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
514   }
515   local_state_->SetString(prefs::kMetricsClientID, client_id_);
516 
517   // Record the timestamp of when the user opted in to UMA.
518   local_state_->SetInt64(prefs::kMetricsReportingEnabledTimestamp,
519                          base::Time::Now().ToTimeT());
520 
521   BackUpCurrentClientInfo();
522 }
523 
SetExternalClientId(const std::string & id)524 void MetricsStateManager::SetExternalClientId(const std::string& id) {
525   external_client_id_ = id;
526 }
527 
CheckForClonedInstall()528 void MetricsStateManager::CheckForClonedInstall() {
529   cloned_install_detector_.CheckForClonedInstall(local_state_);
530 }
531 
ShouldResetClientIdsOnClonedInstall()532 bool MetricsStateManager::ShouldResetClientIdsOnClonedInstall() {
533   return cloned_install_detector_.ShouldResetClientIds(local_state_);
534 }
535 
536 base::CallbackListSubscription
AddOnClonedInstallDetectedCallback(base::OnceClosure callback)537 MetricsStateManager::AddOnClonedInstallDetectedCallback(
538     base::OnceClosure callback) {
539   return cloned_install_detector_.AddOnClonedInstallDetectedCallback(
540       std::move(callback));
541 }
542 
543 std::unique_ptr<const variations::EntropyProviders>
CreateEntropyProviders()544 MetricsStateManager::CreateEntropyProviders() {
545   return std::make_unique<variations::EntropyProviders>(
546       GetHighEntropySource(),
547       variations::ValueInRange{
548           .value = base::checked_cast<uint32_t>(GetLowEntropySource()),
549           .range = EntropyState::kMaxLowEntropySize},
550       ShouldEnableBenchmarking(entropy_params_.force_benchmarking_mode));
551 }
552 
553 // static
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)554 std::unique_ptr<MetricsStateManager> MetricsStateManager::Create(
555     PrefService* local_state,
556     EnabledStateProvider* enabled_state_provider,
557     const std::wstring& backup_registry_key,
558     const base::FilePath& user_data_dir,
559     StartupVisibility startup_visibility,
560     EntropyParams entropy_params,
561     StoreClientInfoCallback store_client_info,
562     LoadClientInfoCallback retrieve_client_info,
563     base::StringPiece external_client_id) {
564   std::unique_ptr<MetricsStateManager> result;
565   // Note: |instance_exists_| is updated in the constructor and destructor.
566   if (!instance_exists_) {
567     result.reset(new MetricsStateManager(
568         local_state, enabled_state_provider, backup_registry_key, user_data_dir,
569         entropy_params, startup_visibility,
570         store_client_info.is_null() ? base::DoNothing()
571                                     : std::move(store_client_info),
572         retrieve_client_info.is_null()
573             ? base::BindRepeating(&NoOpLoadClientInfoBackup)
574             : std::move(retrieve_client_info),
575         external_client_id));
576   }
577   return result;
578 }
579 
580 // static
RegisterPrefs(PrefRegistrySimple * registry)581 void MetricsStateManager::RegisterPrefs(PrefRegistrySimple* registry) {
582   registry->RegisterStringPref(prefs::kMetricsProvisionalClientID,
583                                std::string());
584   registry->RegisterStringPref(prefs::kMetricsClientID, std::string());
585   registry->RegisterInt64Pref(prefs::kMetricsReportingEnabledTimestamp, 0);
586   registry->RegisterInt64Pref(prefs::kInstallDate, 0);
587 #if BUILDFLAG(IS_ANDROID)
588   registry->RegisterBooleanPref(prefs::kUsePostFREFixSamplingTrial, false);
589 #endif  // BUILDFLAG(IS_ANDROID)
590 
591   EntropyState::RegisterPrefs(registry);
592   ClonedInstallDetector::RegisterPrefs(registry);
593 }
594 
BackUpCurrentClientInfo()595 void MetricsStateManager::BackUpCurrentClientInfo() {
596   ClientInfo client_info;
597   client_info.client_id = client_id_;
598   client_info.installation_date = ReadInstallDate(local_state_);
599   client_info.reporting_enabled_date = ReadEnabledDate(local_state_);
600   store_client_info_.Run(client_info);
601 }
602 
LoadClientInfo()603 std::unique_ptr<ClientInfo> MetricsStateManager::LoadClientInfo() {
604   // If a cloned install was detected, loading ClientInfo from backup will be
605   // a race condition with clearing the backup. Skip all backup reads for this
606   // session.
607   if (metrics_ids_were_reset_)
608     return nullptr;
609 
610   std::unique_ptr<ClientInfo> client_info = load_client_info_.Run();
611 
612   // The GUID retrieved should be valid unless retrieval failed.
613   // If not, return nullptr. This will result in a new GUID being generated by
614   // the calling function ForceClientIdCreation().
615   if (client_info &&
616       !base::Uuid::ParseCaseInsensitive(client_info->client_id).is_valid()) {
617     return nullptr;
618   }
619 
620   return client_info;
621 }
622 
GetHighEntropySource()623 std::string MetricsStateManager::GetHighEntropySource() {
624   // If high entropy randomization is not supported in this context (e.g. in
625   // webview), or if UMA is not enabled (so there is no client id), then high
626   // entropy randomization is disabled.
627   if (entropy_params_.default_entropy_provider_type ==
628           EntropyProviderType::kLow ||
629       initial_client_id_.empty()) {
630     UpdateEntropySourceReturnedValue(ENTROPY_SOURCE_LOW);
631     return "";
632   }
633   UpdateEntropySourceReturnedValue(ENTROPY_SOURCE_HIGH);
634   return entropy_state_.GetHighEntropySource(initial_client_id_);
635 }
636 
GetOldLowEntropySource()637 int MetricsStateManager::GetOldLowEntropySource() {
638   return entropy_state_.GetOldLowEntropySource();
639 }
640 
UpdateEntropySourceReturnedValue(EntropySourceType type)641 void MetricsStateManager::UpdateEntropySourceReturnedValue(
642     EntropySourceType type) {
643   if (entropy_source_returned_ != ENTROPY_SOURCE_NONE)
644     return;
645 
646   entropy_source_returned_ = type;
647   base::UmaHistogramEnumeration("UMA.EntropySourceType", type,
648                                 ENTROPY_SOURCE_ENUM_SIZE);
649 }
650 
ResetMetricsIDsIfNecessary()651 void MetricsStateManager::ResetMetricsIDsIfNecessary() {
652   if (!ShouldResetClientIdsOnClonedInstall())
653     return;
654   metrics_ids_were_reset_ = true;
655   previous_client_id_ = ReadClientId(local_state_);
656 
657   base::UmaHistogramBoolean("UMA.MetricsIDsReset", true);
658 
659   DCHECK(client_id_.empty());
660 
661   local_state_->ClearPref(prefs::kMetricsClientID);
662   local_state_->ClearPref(prefs::kMetricsLogRecordId);
663   EntropyState::ClearPrefs(local_state_);
664 
665   ClonedInstallDetector::RecordClonedInstallInfo(local_state_);
666 
667   // Also clear the backed up client info. This is asynchronus; any reads
668   // shortly after may retrieve the old ClientInfo from the backup.
669   store_client_info_.Run(ClientInfo());
670 }
671 
ShouldGenerateProvisionalClientId(bool is_first_run)672 bool MetricsStateManager::ShouldGenerateProvisionalClientId(bool is_first_run) {
673 #if BUILDFLAG(IS_WIN)
674   // We do not want to generate a provisional client ID on Windows because
675   // there's no UMA checkbox on first run. Instead it comes from the install
676   // page. So if UMA is not enabled at this point, it's unlikely it will be
677   // enabled in the same session since that requires the user to manually do
678   // that via settings page after they unchecked it on the download page.
679   //
680   // Note: Windows first run is covered by browser tests
681   // FirstRunMasterPrefsVariationsSeedTest.PRE_SecondRun and
682   // FirstRunMasterPrefsVariationsSeedTest.SecondRun. If the platform ifdef
683   // for this logic changes, the tests should be updated as well.
684   return false;
685 #else
686   // We should only generate a provisional client ID on the first run. If for
687   // some reason there is already a client ID, we do not generate one either.
688   // This can happen if metrics reporting is managed by a policy.
689   if (!is_first_run || !client_id_.empty())
690     return false;
691 
692   // Return false if |kMetricsReportingEnabled| is managed by a policy. For
693   // example, if metrics reporting is disabled by a policy, then
694   // |kMetricsReportingEnabled| will always be set to false, so there is no
695   // reason to generate a provisional client ID. If metrics reporting is enabled
696   // by a policy, then the default value of |kMetricsReportingEnabled| will be
697   // true, and so a client ID will have already been generated (we would have
698   // returned false already because of the previous check).
699   if (local_state_->IsManagedPreference(prefs::kMetricsReportingEnabled))
700     return false;
701 
702   // If this is a non-Google-Chrome-branded build, we do not want to generate a
703   // provisional client ID because metrics reporting is not enabled on those
704   // builds. This would be problematic because we store the provisional client
705   // ID in the Local State, and clear it when either 1) we enable UMA (the
706   // provisional client ID becomes the client ID), or 2) we disable UMA. Since
707   // in non-Google-Chrome-branded builds we never actually go through the code
708   // paths to either enable or disable UMA, the pref storing the provisional
709   // client ID would never be cleared. However, for test consistency between
710   // the different builds, we do not return false here if
711   // |enable_provisional_client_id_for_testing_| is set to true.
712   if (!BUILDFLAG(GOOGLE_CHROME_BRANDING) &&
713       !enable_provisional_client_id_for_testing_) {
714     return false;
715   }
716 
717   return true;
718 #endif  // BUILDFLAG(IS_WIN)
719 }
720 
721 #if BUILDFLAG(IS_CHROMEOS_ASH)
LogClientIdChanged(metrics::structured::NeutrinoDevicesLocation location,std::string previous_client_id)722 void MetricsStateManager::LogClientIdChanged(
723     metrics::structured::NeutrinoDevicesLocation location,
724     std::string previous_client_id) {
725   metrics::structured::NeutrinoDevicesLogClientIdChanged(
726       client_id_, previous_client_id, ReadInstallDate(local_state_),
727       ReadEnabledDate(local_state_), location);
728 }
729 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
730 
731 }  // namespace metrics
732