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::__anona85dd7410111::LogNormalMetricState103 LogNormalMetricState()
104 : dist(GetLogNormalDist()), gen(std::mt19937(base::RandUint64())) {}
105
106 // Records the artificial non-uniformity histogram for data validation.
LogArtificialNonUniformitymetrics::__anona85dd7410111::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 // NOTE: If you are adding anything here, consider also changing
182 // FileMetricsProvider::ProvideIndependentMetricsOnTaskRunner().
183
184 // If we know the previous client id, overwrite the client id for the
185 // previous session log so the log contains the client id at the time
186 // of the previous session. This allows better attribution of crashes
187 // to earlier behavior. If the previous client id is unknown, leave
188 // the current client id.
189 uma_proto->set_client_id(MetricsLog::Hash(previous_client_id_));
190 }
191 }
192 }
193
ProvideCurrentSessionData(ChromeUserMetricsExtension * uma_proto)194 void ProvideCurrentSessionData(
195 ChromeUserMetricsExtension* uma_proto) override {
196 if (cloned_install_detector_->ClonedInstallDetectedInCurrentSession()) {
197 LogClonedInstall();
198 }
199 log_normal_metric_state_.LogArtificialNonUniformity();
200 }
201
202 // Set a random seed for the random number generator.
SetRandomSeedForTesting(int64_t seed)203 void SetRandomSeedForTesting(int64_t seed) {
204 log_normal_metric_state_.gen = std::mt19937(seed);
205 }
206
207 private:
208 const raw_ptr<PrefService> local_state_;
209 const bool metrics_ids_were_reset_;
210 // |previous_client_id_| is set only (if known) when
211 // |metrics_ids_were_reset_|
212 const std::string previous_client_id_;
213 // The client id that was used to randomize field trials. An empty string if
214 // the low entropy source was used to do randomization.
215 const std::string initial_client_id_;
216 const raw_ref<const ClonedInstallDetector> cloned_install_detector_;
217 LogNormalMetricState log_normal_metric_state_;
218 };
219
ShouldEnableBenchmarking(bool force_benchmarking_mode)220 bool ShouldEnableBenchmarking(bool force_benchmarking_mode) {
221 // TODO(crbug.com/40792683): See whether it's possible to consolidate the
222 // 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 ForceClientIdCreation();
272 } else {
273 #if BUILDFLAG(IS_ANDROID)
274 // If on start up we determine that the client has not given their consent
275 // to report their metrics, the new sampling trial should be used to
276 // determine whether the client is sampled in or out (if the user ever
277 // enables metrics reporting). This covers users that are going through
278 // the first run, as well as users that have metrics reporting disabled.
279 //
280 // See crbug/1306481 and the comment above |kUsePostFREFixSamplingTrial| in
281 // components/metrics/metrics_pref_names.cc for more details.
282 local_state_->SetBoolean(metrics::prefs::kUsePostFREFixSamplingTrial, true);
283 #endif // BUILDFLAG(IS_ANDROID)
284 }
285
286 // Generate and store a provisional client ID if necessary. This ID will be
287 // used for field trial randomization on first run (and possibly in future
288 // runs if the user closes Chrome during the FRE) and will be promoted to
289 // become the client ID if UMA is enabled during this session, via the logic
290 // in ForceClientIdCreation(). If UMA is disabled (refused), we discard it.
291 //
292 // Note: This means that if a provisional client ID is used for this session,
293 // and the user disables (refuses) UMA, then starting from the next run, the
294 // field trial randomization (group assignment) will be different.
295 if (ShouldGenerateProvisionalClientId(is_first_run)) {
296 local_state_->SetString(prefs::kMetricsProvisionalClientID,
297 base::Uuid::GenerateRandomV4().AsLowercaseString());
298 }
299
300 // `initial_client_id_` will only be set in the following cases:
301 // 1. UMA is enabled
302 // 2. there is a provisional client id (due to this being a first run)
303 // 3. there is an externally provided client ID (e.g. in Lacros, from Ash)
304 if (!client_id_.empty()) {
305 initial_client_id_ = client_id_;
306 } else if (!external_client_id_.empty()) {
307 // Typically, `client_id_` should have been set to the external client ID in
308 // the call to ForceClientIdCreation() above. However, that call is gated,
309 // and may not always happen, for example if this is a first run and the
310 // consent state is not yet known (although we know it is soon going to be
311 // set to true, since an external client ID was provided).
312 initial_client_id_ = external_client_id_;
313 } else {
314 // Note that there is possibly no provisional client ID.
315 initial_client_id_ =
316 local_state_->GetString(prefs::kMetricsProvisionalClientID);
317 }
318 CHECK(!instance_exists_);
319 instance_exists_ = true;
320 }
321
~MetricsStateManager()322 MetricsStateManager::~MetricsStateManager() {
323 CHECK(instance_exists_);
324 instance_exists_ = false;
325 }
326
GetProvider()327 std::unique_ptr<MetricsProvider> MetricsStateManager::GetProvider() {
328 return std::make_unique<MetricsStateMetricsProvider>(
329 local_state_, metrics_ids_were_reset_, previous_client_id_,
330 initial_client_id_, cloned_install_detector_);
331 }
332
333 std::unique_ptr<MetricsProvider>
GetProviderAndSetRandomSeedForTesting(int64_t seed)334 MetricsStateManager::GetProviderAndSetRandomSeedForTesting(int64_t seed) {
335 auto provider = std::make_unique<MetricsStateMetricsProvider>(
336 local_state_, metrics_ids_were_reset_, previous_client_id_,
337 initial_client_id_, cloned_install_detector_);
338 provider->SetRandomSeedForTesting(seed); // IN-TEST
339 return provider;
340 }
341
IsMetricsReportingEnabled()342 bool MetricsStateManager::IsMetricsReportingEnabled() {
343 return enabled_state_provider_->IsReportingEnabled();
344 }
345
IsExtendedSafeModeSupported() const346 bool MetricsStateManager::IsExtendedSafeModeSupported() const {
347 return clean_exit_beacon_.IsExtendedSafeModeSupported();
348 }
349
GetLowEntropySource()350 int MetricsStateManager::GetLowEntropySource() {
351 return entropy_state_.GetLowEntropySource();
352 }
353
GetOldLowEntropySource()354 int MetricsStateManager::GetOldLowEntropySource() {
355 return entropy_state_.GetOldLowEntropySource();
356 }
357
GetPseudoLowEntropySource()358 int MetricsStateManager::GetPseudoLowEntropySource() {
359 return entropy_state_.GetPseudoLowEntropySource();
360 }
361
InstantiateFieldTrialList()362 void MetricsStateManager::InstantiateFieldTrialList() {
363 // Instantiate the FieldTrialList to support field trials. If an instance
364 // already exists, this is likely a test scenario with a ScopedFeatureList, so
365 // use the existing instance so that any overrides are still applied.
366 if (!base::FieldTrialList::GetInstance()) {
367 // This is intentionally leaked since it needs to live for the duration of
368 // the browser process and there's no benefit in cleaning it up at exit.
369 base::FieldTrialList* leaked_field_trial_list = new base::FieldTrialList();
370 ANNOTATE_LEAKING_OBJECT_PTR(leaked_field_trial_list);
371 std::ignore = leaked_field_trial_list;
372 }
373
374 // When benchmarking is enabled, field trials' default groups are chosen, so
375 // see whether benchmarking needs to be enabled here, before any field trials
376 // are created.
377 // TODO(crbug/1257204): Some FieldTrial-setup-related code is here and some is
378 // in VariationsFieldTrialCreator::SetUpFieldTrials(). It's not ideal that
379 // it's in two places.
380 if (ShouldEnableBenchmarking(entropy_params_.force_benchmarking_mode))
381 base::FieldTrial::EnableBenchmarking();
382
383 const base::CommandLine* command_line =
384 base::CommandLine::ForCurrentProcess();
385 if (command_line->HasSwitch(variations::switches::kForceFieldTrialParams)) {
386 bool result =
387 variations::AssociateParamsFromString(command_line->GetSwitchValueASCII(
388 variations::switches::kForceFieldTrialParams));
389 if (!result) {
390 // Some field trial params implement things like csv or json with a
391 // particular param. If some control characters are not %-encoded, it can
392 // lead to confusing error messages, so add a hint here.
393 ExitWithMessage(base::StringPrintf(
394 "Invalid --%s list specified. Make sure you %%-"
395 "encode the following characters in param values: %%:/.,",
396 variations::switches::kForceFieldTrialParams));
397 }
398 }
399
400 // Ensure any field trials specified on the command line are initialized.
401 if (command_line->HasSwitch(::switches::kForceFieldTrials)) {
402 // Create field trials without activating them, so that this behaves in a
403 // consistent manner with field trials created from the server.
404 bool result = base::FieldTrialList::CreateTrialsFromString(
405 command_line->GetSwitchValueASCII(::switches::kForceFieldTrials));
406 if (!result) {
407 ExitWithMessage(base::StringPrintf("Invalid --%s list specified.",
408 ::switches::kForceFieldTrials));
409 }
410 }
411
412 // Initializing the CleanExitBeacon is done after FieldTrialList instantiation
413 // to allow experimentation on the CleanExitBeacon.
414 clean_exit_beacon_.Initialize();
415 }
416
LogHasSessionShutdownCleanly(bool has_session_shutdown_cleanly,bool is_extended_safe_mode)417 void MetricsStateManager::LogHasSessionShutdownCleanly(
418 bool has_session_shutdown_cleanly,
419 bool is_extended_safe_mode) {
420 clean_exit_beacon_.WriteBeaconValue(has_session_shutdown_cleanly,
421 is_extended_safe_mode);
422 }
423
ForceClientIdCreation()424 void MetricsStateManager::ForceClientIdCreation() {
425 // TODO(asvitkine): Ideally, all tests would actually set up consent properly,
426 // so the command-line checks wouldn't be needed here.
427 // Currently, kForceEnableMetricsReporting is used by Java UkmTest and
428 // kMetricsRecordingOnly is used by Chromedriver tests.
429 DCHECK(enabled_state_provider_->IsConsentGiven() ||
430 IsMetricsReportingForceEnabled() || IsMetricsRecordingOnlyEnabled());
431 if (!external_client_id_.empty()) {
432 client_id_ = external_client_id_;
433 base::UmaHistogramEnumeration("UMA.ClientIdSource",
434 ClientIdSource::kClientIdFromExternal);
435 local_state_->SetString(prefs::kMetricsClientID, client_id_);
436 return;
437 }
438 #if BUILDFLAG(IS_CHROMEOS_ASH)
439 std::string previous_client_id = client_id_;
440 #endif // BUILDFLAG(IS_CHROMEOS_ASH)
441 {
442 std::string client_id_from_prefs = ReadClientId(local_state_);
443 // If client id in prefs matches the cached copy, return early.
444 if (!client_id_from_prefs.empty() && client_id_from_prefs == client_id_) {
445 base::UmaHistogramEnumeration("UMA.ClientIdSource",
446 ClientIdSource::kClientIdMatches);
447 return;
448 }
449 client_id_.swap(client_id_from_prefs);
450 }
451
452 if (!client_id_.empty()) {
453 base::UmaHistogramEnumeration("UMA.ClientIdSource",
454 ClientIdSource::kClientIdFromLocalState);
455 return;
456 }
457
458 const std::unique_ptr<ClientInfo> client_info_backup = LoadClientInfo();
459 if (client_info_backup) {
460 client_id_ = client_info_backup->client_id;
461
462 const base::Time now = base::Time::Now();
463
464 // Save the recovered client id and also try to reinstantiate the backup
465 // values for the dates corresponding with that client id in order to avoid
466 // weird scenarios where we could report an old client id with a recent
467 // install date.
468 local_state_->SetString(prefs::kMetricsClientID, client_id_);
469 local_state_->SetInt64(prefs::kInstallDate,
470 client_info_backup->installation_date != 0
471 ? client_info_backup->installation_date
472 : now.ToTimeT());
473 local_state_->SetInt64(prefs::kMetricsReportingEnabledTimestamp,
474 client_info_backup->reporting_enabled_date != 0
475 ? client_info_backup->reporting_enabled_date
476 : now.ToTimeT());
477
478 base::TimeDelta recovered_installation_age;
479 if (client_info_backup->installation_date != 0) {
480 recovered_installation_age =
481 now - base::Time::FromTimeT(client_info_backup->installation_date);
482 }
483 base::UmaHistogramEnumeration("UMA.ClientIdSource",
484 ClientIdSource::kClientIdBackupRecovered);
485 base::UmaHistogramCounts10000("UMA.ClientIdBackupRecoveredWithAge",
486 recovered_installation_age.InHours());
487
488 // Flush the backup back to persistent storage in case we re-generated
489 // missing data above.
490 BackUpCurrentClientInfo();
491 return;
492 }
493
494 // If we're here, there was no client ID yet (either in prefs or backup),
495 // so generate a new one. If there's a provisional client id (e.g. UMA
496 // was enabled as part of first run), promote that to the client id,
497 // otherwise (e.g. UMA enabled in a future session), generate a new one.
498 std::string provisional_client_id =
499 local_state_->GetString(prefs::kMetricsProvisionalClientID);
500 if (provisional_client_id.empty()) {
501 client_id_ = base::Uuid::GenerateRandomV4().AsLowercaseString();
502 base::UmaHistogramEnumeration("UMA.ClientIdSource",
503 ClientIdSource::kClientIdNew);
504 } else {
505 client_id_ = provisional_client_id;
506 local_state_->ClearPref(prefs::kMetricsProvisionalClientID);
507 base::UmaHistogramEnumeration("UMA.ClientIdSource",
508 ClientIdSource::kClientIdFromProvisionalId);
509 }
510 local_state_->SetString(prefs::kMetricsClientID, client_id_);
511
512 // Record the timestamp of when the user opted in to UMA.
513 local_state_->SetInt64(prefs::kMetricsReportingEnabledTimestamp,
514 base::Time::Now().ToTimeT());
515
516 BackUpCurrentClientInfo();
517 }
518
SetExternalClientId(const std::string & id)519 void MetricsStateManager::SetExternalClientId(const std::string& id) {
520 external_client_id_ = id;
521 }
522
CheckForClonedInstall()523 void MetricsStateManager::CheckForClonedInstall() {
524 cloned_install_detector_.CheckForClonedInstall(local_state_);
525 }
526
ShouldResetClientIdsOnClonedInstall()527 bool MetricsStateManager::ShouldResetClientIdsOnClonedInstall() {
528 return cloned_install_detector_.ShouldResetClientIds(local_state_);
529 }
530
531 base::CallbackListSubscription
AddOnClonedInstallDetectedCallback(base::OnceClosure callback)532 MetricsStateManager::AddOnClonedInstallDetectedCallback(
533 base::OnceClosure callback) {
534 return cloned_install_detector_.AddOnClonedInstallDetectedCallback(
535 std::move(callback));
536 }
537
538 std::unique_ptr<const variations::EntropyProviders>
CreateEntropyProviders(bool enable_limited_entropy_mode)539 MetricsStateManager::CreateEntropyProviders(bool enable_limited_entropy_mode) {
540 // TODO(crbug.com/1508150): remove `enable_limited_entropy_mode` when it's
541 // true for all callers.
542 auto limited_entropy_randomization_source =
543 enable_limited_entropy_mode ? GetLimitedEntropyRandomizationSource()
544 : std::string_view();
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 limited_entropy_randomization_source,
551 ShouldEnableBenchmarking(entropy_params_.force_benchmarking_mode));
552 }
553
554 // 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)555 std::unique_ptr<MetricsStateManager> MetricsStateManager::Create(
556 PrefService* local_state,
557 EnabledStateProvider* enabled_state_provider,
558 const std::wstring& backup_registry_key,
559 const base::FilePath& user_data_dir,
560 StartupVisibility startup_visibility,
561 EntropyParams entropy_params,
562 StoreClientInfoCallback store_client_info,
563 LoadClientInfoCallback retrieve_client_info,
564 base::StringPiece external_client_id) {
565 std::unique_ptr<MetricsStateManager> result;
566 // Note: |instance_exists_| is updated in the constructor and destructor.
567 if (!instance_exists_) {
568 result.reset(new MetricsStateManager(
569 local_state, enabled_state_provider, backup_registry_key, user_data_dir,
570 entropy_params, startup_visibility,
571 store_client_info.is_null() ? base::DoNothing()
572 : std::move(store_client_info),
573 retrieve_client_info.is_null()
574 ? base::BindRepeating(&NoOpLoadClientInfoBackup)
575 : std::move(retrieve_client_info),
576 external_client_id));
577 }
578 return result;
579 }
580
581 // static
RegisterPrefs(PrefRegistrySimple * registry)582 void MetricsStateManager::RegisterPrefs(PrefRegistrySimple* registry) {
583 registry->RegisterStringPref(prefs::kMetricsProvisionalClientID,
584 std::string());
585 registry->RegisterStringPref(prefs::kMetricsClientID, std::string());
586 registry->RegisterInt64Pref(prefs::kMetricsReportingEnabledTimestamp, 0);
587 registry->RegisterInt64Pref(prefs::kInstallDate, 0);
588 #if BUILDFLAG(IS_ANDROID)
589 registry->RegisterBooleanPref(prefs::kUsePostFREFixSamplingTrial, false);
590 #endif // BUILDFLAG(IS_ANDROID)
591
592 EntropyState::RegisterPrefs(registry);
593 ClonedInstallDetector::RegisterPrefs(registry);
594 }
595
BackUpCurrentClientInfo()596 void MetricsStateManager::BackUpCurrentClientInfo() {
597 ClientInfo client_info;
598 client_info.client_id = client_id_;
599 client_info.installation_date = ReadInstallDate(local_state_);
600 client_info.reporting_enabled_date = ReadEnabledDate(local_state_);
601 store_client_info_.Run(client_info);
602 }
603
LoadClientInfo()604 std::unique_ptr<ClientInfo> MetricsStateManager::LoadClientInfo() {
605 // If a cloned install was detected, loading ClientInfo from backup will be
606 // a race condition with clearing the backup. Skip all backup reads for this
607 // session.
608 if (metrics_ids_were_reset_)
609 return nullptr;
610
611 std::unique_ptr<ClientInfo> client_info = load_client_info_.Run();
612
613 // The GUID retrieved should be valid unless retrieval failed.
614 // If not, return nullptr. This will result in a new GUID being generated by
615 // the calling function ForceClientIdCreation().
616 if (client_info &&
617 !base::Uuid::ParseCaseInsensitive(client_info->client_id).is_valid()) {
618 return nullptr;
619 }
620
621 return client_info;
622 }
623
GetLimitedEntropyRandomizationSource()624 std::string_view MetricsStateManager::GetLimitedEntropyRandomizationSource() {
625 // No limited entropy randomization source will be generated if limited
626 // entropy randomization is not supported in this context (e.g. in Android
627 // Webview).
628 if (entropy_params_.default_entropy_provider_type ==
629 EntropyProviderType::kLow) {
630 return std::string_view();
631 }
632 return entropy_state_.GetLimitedEntropyRandomizationSource();
633 }
634
GetHighEntropySource()635 std::string MetricsStateManager::GetHighEntropySource() {
636 // If high entropy randomization is not supported in this context (e.g. in
637 // Android Webview), or if UMA is not enabled (so there is no client id), then
638 // high entropy randomization is disabled.
639 if (entropy_params_.default_entropy_provider_type ==
640 EntropyProviderType::kLow ||
641 initial_client_id_.empty()) {
642 UpdateEntropySourceReturnedValue(ENTROPY_SOURCE_LOW);
643 return "";
644 }
645 UpdateEntropySourceReturnedValue(ENTROPY_SOURCE_HIGH);
646 return entropy_state_.GetHighEntropySource(initial_client_id_);
647 }
648
UpdateEntropySourceReturnedValue(EntropySourceType type)649 void MetricsStateManager::UpdateEntropySourceReturnedValue(
650 EntropySourceType type) {
651 if (entropy_source_returned_ != ENTROPY_SOURCE_NONE)
652 return;
653
654 entropy_source_returned_ = type;
655 base::UmaHistogramEnumeration("UMA.EntropySourceType", type,
656 ENTROPY_SOURCE_ENUM_SIZE);
657 }
658
ResetMetricsIDsIfNecessary()659 void MetricsStateManager::ResetMetricsIDsIfNecessary() {
660 if (!ShouldResetClientIdsOnClonedInstall())
661 return;
662 metrics_ids_were_reset_ = true;
663 previous_client_id_ = ReadClientId(local_state_);
664
665 base::UmaHistogramBoolean("UMA.MetricsIDsReset", true);
666
667 DCHECK(client_id_.empty());
668
669 local_state_->ClearPref(prefs::kMetricsClientID);
670 local_state_->ClearPref(prefs::kMetricsLogRecordId);
671 EntropyState::ClearPrefs(local_state_);
672
673 ClonedInstallDetector::RecordClonedInstallInfo(local_state_);
674
675 // Also clear the backed up client info. This is asynchronus; any reads
676 // shortly after may retrieve the old ClientInfo from the backup.
677 store_client_info_.Run(ClientInfo());
678 }
679
ShouldGenerateProvisionalClientId(bool is_first_run)680 bool MetricsStateManager::ShouldGenerateProvisionalClientId(bool is_first_run) {
681 #if BUILDFLAG(IS_WIN)
682 // We do not want to generate a provisional client ID on Windows because
683 // there's no UMA checkbox on first run. Instead it comes from the install
684 // page. So if UMA is not enabled at this point, it's unlikely it will be
685 // enabled in the same session since that requires the user to manually do
686 // that via settings page after they unchecked it on the download page.
687 //
688 // Note: Windows first run is covered by browser tests
689 // FirstRunMasterPrefsVariationsSeedTest.PRE_SecondRun and
690 // FirstRunMasterPrefsVariationsSeedTest.SecondRun. If the platform ifdef
691 // for this logic changes, the tests should be updated as well.
692 return false;
693 #else
694 // We should only generate a provisional client ID on the first run. If for
695 // some reason there is already a client ID, we do not generate one either.
696 // This can happen if metrics reporting is managed by a policy.
697 if (!is_first_run || !client_id_.empty())
698 return false;
699
700 // Return false if |kMetricsReportingEnabled| is managed by a policy. For
701 // example, if metrics reporting is disabled by a policy, then
702 // |kMetricsReportingEnabled| will always be set to false, so there is no
703 // reason to generate a provisional client ID. If metrics reporting is enabled
704 // by a policy, then the default value of |kMetricsReportingEnabled| will be
705 // true, and so a client ID will have already been generated (we would have
706 // returned false already because of the previous check).
707 if (local_state_->IsManagedPreference(prefs::kMetricsReportingEnabled))
708 return false;
709
710 // If this is a non-Google-Chrome-branded build, we do not want to generate a
711 // provisional client ID because metrics reporting is not enabled on those
712 // builds. This would be problematic because we store the provisional client
713 // ID in the Local State, and clear it when either 1) we enable UMA (the
714 // provisional client ID becomes the client ID), or 2) we disable UMA. Since
715 // in non-Google-Chrome-branded builds we never actually go through the code
716 // paths to either enable or disable UMA, the pref storing the provisional
717 // client ID would never be cleared. However, for test consistency between
718 // the different builds, we do not return false here if
719 // |enable_provisional_client_id_for_testing_| is set to true.
720 if (!BUILDFLAG(GOOGLE_CHROME_BRANDING) &&
721 !enable_provisional_client_id_for_testing_) {
722 return false;
723 }
724
725 return true;
726 #endif // BUILDFLAG(IS_WIN)
727 }
728
729 } // namespace metrics
730