Index: chrome/browser/metrics/metrics_state_manager.cc |
=================================================================== |
--- chrome/browser/metrics/metrics_state_manager.cc (revision 0) |
+++ chrome/browser/metrics/metrics_state_manager.cc (working copy) |
@@ -0,0 +1,233 @@ |
+// Copyright 2014 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "chrome/browser/metrics/metrics_state_manager.h" |
+ |
+#include "base/command_line.h" |
+#include "base/guid.h" |
+#include "base/metrics/histogram.h" |
+#include "base/metrics/sparse_histogram.h" |
+#include "base/prefs/pref_registry_simple.h" |
+#include "base/prefs/pref_service.h" |
+#include "base/rand_util.h" |
+#include "base/strings/string_number_conversions.h" |
+#include "base/time/time.h" |
+#include "chrome/browser/metrics/cloned_install_detector.h" |
+#include "chrome/browser/metrics/machine_id_provider.h" |
+#include "chrome/common/chrome_switches.h" |
+#include "chrome/common/metrics/caching_permuted_entropy_provider.h" |
+#include "chrome/common/pref_names.h" |
+ |
+#if defined(OS_CHROMEOS) |
+#include "chrome/browser/chromeos/settings/cros_settings.h" |
+#endif |
+ |
+namespace metrics { |
+ |
+namespace { |
+ |
+// The argument used to generate a non-identifying entropy source. We want no |
+// more than 13 bits of entropy, so use this max to return a number in the range |
+// [0, 7999] as the entropy source (12.97 bits of entropy). |
+const int kMaxLowEntropySize = 8000; |
+ |
+// Default prefs value for prefs::kMetricsLowEntropySource to indicate that the |
+// value has not yet been set. |
+const int kLowEntropySourceNotSet = -1; |
+ |
+// Generates a new non-identifying entropy source used to seed persistent |
+// activities. |
+int GenerateLowEntropySource() { |
+ return base::RandInt(0, kMaxLowEntropySize - 1); |
+} |
+ |
+} // namespace |
+ |
+// static |
+bool MetricsStateManager::instance_exists_ = false; |
+ |
+MetricsStateManager::MetricsStateManager(PrefService* local_state) |
+ : local_state_(local_state), |
+ low_entropy_source_(kLowEntropySourceNotSet), |
+ entropy_source_returned_(ENTROPY_SOURCE_NONE) { |
+ ResetMetricsIDsIfNecessary(); |
+ if (IsMetricsReportingEnabled()) |
+ ForceClientIdCreation(); |
+ |
+ DCHECK(!instance_exists_); |
+ instance_exists_ = true; |
+} |
+ |
+MetricsStateManager::~MetricsStateManager() { |
+ DCHECK(instance_exists_); |
+ instance_exists_ = false; |
+} |
+ |
+bool MetricsStateManager::IsMetricsReportingEnabled() { |
+ // If the user permits metrics reporting with the checkbox in the |
+ // prefs, we turn on recording. We disable metrics completely for |
+ // non-official builds. This can be forced with a flag. |
+ const CommandLine* command_line = CommandLine::ForCurrentProcess(); |
+ if (command_line->HasSwitch(switches::kEnableMetricsReportingForTesting)) |
+ return true; |
+ |
+ // Disable metrics reporting when field trials are forced. |
+ if (command_line->HasSwitch(switches::kForceFieldTrials)) |
+ return false; |
+ |
+ bool enabled = false; |
+#if defined(GOOGLE_CHROME_BUILD) |
+#if defined(OS_CHROMEOS) |
+ chromeos::CrosSettings::Get()->GetBoolean(chromeos::kStatsReportingPref, |
+ &enabled); |
+#else |
+ enabled = local_state->GetBoolean(prefs::kMetricsReportingEnabled); |
+#endif // #if defined(OS_CHROMEOS) |
+#endif // defined(GOOGLE_CHROME_BUILD) |
+ return enabled; |
+} |
+ |
+void MetricsStateManager::ForceClientIdCreation() { |
+ if (!client_id_.empty()) |
+ return; |
+ |
+ client_id_ = local_state_->GetString(prefs::kMetricsClientID); |
+ if (!client_id_.empty()) |
+ return; |
+ |
+ client_id_ = base::GenerateGUID(); |
+ local_state_->SetString(prefs::kMetricsClientID, client_id_); |
+ |
+ if (local_state_->GetString(prefs::kMetricsOldClientID).empty()) { |
+ // Record the timestamp of when the user opted in to UMA. |
+ local_state_->SetInt64(prefs::kMetricsReportingEnabledTimestamp, |
+ base::Time::Now().ToTimeT()); |
+ } else { |
+ UMA_HISTOGRAM_BOOLEAN("UMA.ClientIdMigrated", true); |
+ } |
+ local_state_->ClearPref(prefs::kMetricsOldClientID); |
+} |
+ |
+void MetricsStateManager::CheckForClonedInstall() { |
+ DCHECK(!cloned_install_detector_); |
+ |
+ MachineIdProvider* provider = MachineIdProvider::CreateInstance(); |
+ if (!provider) |
+ return; |
+ |
+ cloned_install_detector_.reset(new ClonedInstallDetector(provider)); |
+ cloned_install_detector_->CheckForClonedInstall(local_state_); |
+} |
+ |
+scoped_ptr<const base::FieldTrial::EntropyProvider> |
+MetricsStateManager::CreateEntropyProvider() { |
+ // For metrics reporting-enabled users, we combine the client ID and low |
+ // entropy source to get the final entropy source. Otherwise, only use the low |
+ // entropy source. |
+ // This has two useful properties: |
+ // 1) It makes the entropy source less identifiable for parties that do not |
+ // know the low entropy source. |
+ // 2) It makes the final entropy source resettable. |
+ const int low_entropy_source_value = GetLowEntropySource(); |
+ UMA_HISTOGRAM_SPARSE_SLOWLY("UMA.LowEntropySourceValue", |
+ low_entropy_source_value); |
+ if (IsMetricsReportingEnabled()) { |
+ if (entropy_source_returned_ == ENTROPY_SOURCE_NONE) |
+ entropy_source_returned_ = ENTROPY_SOURCE_HIGH; |
+ DCHECK_EQ(ENTROPY_SOURCE_HIGH, entropy_source_returned_); |
+ const std::string high_entropy_source = |
+ client_id_ + base::IntToString(low_entropy_source_value); |
+ return scoped_ptr<const base::FieldTrial::EntropyProvider>( |
+ new SHA1EntropyProvider(high_entropy_source)); |
+ } |
+ |
+ if (entropy_source_returned_ == ENTROPY_SOURCE_NONE) |
+ entropy_source_returned_ = ENTROPY_SOURCE_LOW; |
+ DCHECK_EQ(ENTROPY_SOURCE_LOW, entropy_source_returned_); |
+ |
+#if defined(OS_ANDROID) || defined(OS_IOS) |
+ return scoped_ptr<const base::FieldTrial::EntropyProvider>( |
+ new CachingPermutedEntropyProvider(local_state_, |
+ low_entropy_source_value, |
+ kMaxLowEntropySize)); |
+#else |
+ return scoped_ptr<const base::FieldTrial::EntropyProvider>( |
+ new PermutedEntropyProvider(low_entropy_source_value, |
+ kMaxLowEntropySize)); |
+#endif |
+} |
+ |
+// static |
+scoped_ptr<MetricsStateManager> MetricsStateManager::Create( |
+ PrefService* local_state) { |
+ scoped_ptr<MetricsStateManager> result; |
+ // Note: |instance_exists_| is updated in the constructor and destructor. |
+ if (!instance_exists_) |
+ result.reset(new MetricsStateManager(local_state)); |
+ return result.Pass(); |
+} |
+ |
+// static |
+void MetricsStateManager::RegisterPrefs(PrefRegistrySimple* registry) { |
+ registry->RegisterBooleanPref(prefs::kMetricsResetIds, false); |
+ registry->RegisterStringPref(prefs::kMetricsClientID, std::string()); |
+ registry->RegisterInt64Pref(prefs::kMetricsReportingEnabledTimestamp, 0); |
+ registry->RegisterIntegerPref(prefs::kMetricsLowEntropySource, |
+ kLowEntropySourceNotSet); |
+ |
+ ClonedInstallDetector::RegisterPrefs(registry); |
+ CachingPermutedEntropyProvider::RegisterPrefs(registry); |
+ |
+ // TODO(asvitkine): Remove these once a couple of releases have passed. |
+ // http://crbug.com/357704 |
+ registry->RegisterStringPref(prefs::kMetricsOldClientID, std::string()); |
+ registry->RegisterIntegerPref(prefs::kMetricsOldLowEntropySource, 0); |
+} |
+ |
+int MetricsStateManager::GetLowEntropySource() { |
+ // Note that the default value for the low entropy source and the default pref |
+ // value are both kLowEntropySourceNotSet, which is used to identify if the |
+ // value has been set or not. |
+ if (low_entropy_source_ != kLowEntropySourceNotSet) |
+ return low_entropy_source_; |
+ |
+ const CommandLine* command_line(CommandLine::ForCurrentProcess()); |
+ // Only try to load the value from prefs if the user did not request a reset. |
+ // Otherwise, skip to generating a new value. |
+ if (!command_line->HasSwitch(switches::kResetVariationState)) { |
+ int value = local_state_->GetInteger(prefs::kMetricsLowEntropySource); |
+ // If the value is outside the [0, kMaxLowEntropySize) range, re-generate |
+ // it below. |
+ if (value >= 0 && value < kMaxLowEntropySize) { |
+ low_entropy_source_ = value; |
+ UMA_HISTOGRAM_BOOLEAN("UMA.GeneratedLowEntropySource", false); |
+ return low_entropy_source_; |
+ } |
+ } |
+ |
+ UMA_HISTOGRAM_BOOLEAN("UMA.GeneratedLowEntropySource", true); |
+ low_entropy_source_ = GenerateLowEntropySource(); |
+ local_state_->SetInteger(prefs::kMetricsLowEntropySource, |
+ low_entropy_source_); |
+ local_state_->ClearPref(prefs::kMetricsOldLowEntropySource); |
+ metrics::CachingPermutedEntropyProvider::ClearCache(local_state_); |
+ |
+ return low_entropy_source_; |
+} |
+ |
+void MetricsStateManager::ResetMetricsIDsIfNecessary() { |
+ if (!local_state_->GetBoolean(prefs::kMetricsResetIds)) |
+ return; |
+ |
+ UMA_HISTOGRAM_BOOLEAN("UMA.MetricsIDsReset", true); |
+ |
+ DCHECK(client_id_.empty()); |
+ DCHECK_EQ(kLowEntropySourceNotSet, low_entropy_source_); |
+ |
+ local_state_->ClearPref(prefs::kMetricsClientID); |
+ local_state_->ClearPref(prefs::kMetricsLowEntropySource); |
+ local_state_->ClearPref(prefs::kMetricsResetIds); |
+} |
+ |
+} // namespace metrics |