metrics_state_manager.cc revision 03b57e008b61dfcb1fbad3aea950ae0e001748b0
1// Copyright 2014 The Chromium Authors. All rights reserved. 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 "base/command_line.h" 8#include "base/guid.h" 9#include "base/metrics/histogram.h" 10#include "base/metrics/sparse_histogram.h" 11#include "base/prefs/pref_registry_simple.h" 12#include "base/prefs/pref_service.h" 13#include "base/rand_util.h" 14#include "base/strings/string_number_conversions.h" 15#include "base/time/time.h" 16#include "components/metrics/cloned_install_detector.h" 17#include "components/metrics/machine_id_provider.h" 18#include "components/metrics/metrics_pref_names.h" 19#include "components/metrics/metrics_switches.h" 20#include "components/variations/caching_permuted_entropy_provider.h" 21 22namespace metrics { 23 24namespace { 25 26// The argument used to generate a non-identifying entropy source. We want no 27// more than 13 bits of entropy, so use this max to return a number in the range 28// [0, 7999] as the entropy source (12.97 bits of entropy). 29const int kMaxLowEntropySize = 8000; 30 31// Default prefs value for prefs::kMetricsLowEntropySource to indicate that 32// the value has not yet been set. 33const int kLowEntropySourceNotSet = -1; 34 35// Generates a new non-identifying entropy source used to seed persistent 36// activities. 37int GenerateLowEntropySource() { 38 return base::RandInt(0, kMaxLowEntropySize - 1); 39} 40 41} // namespace 42 43// static 44bool MetricsStateManager::instance_exists_ = false; 45 46MetricsStateManager::MetricsStateManager( 47 PrefService* local_state, 48 const base::Callback<bool(void)>& is_reporting_enabled_callback, 49 const StoreClientInfoCallback& store_client_info, 50 const LoadClientInfoCallback& retrieve_client_info) 51 : local_state_(local_state), 52 is_reporting_enabled_callback_(is_reporting_enabled_callback), 53 store_client_info_(store_client_info), 54 load_client_info_(retrieve_client_info), 55 low_entropy_source_(kLowEntropySourceNotSet), 56 entropy_source_returned_(ENTROPY_SOURCE_NONE) { 57 ResetMetricsIDsIfNecessary(); 58 if (IsMetricsReportingEnabled()) 59 ForceClientIdCreation(); 60 61 DCHECK(!instance_exists_); 62 instance_exists_ = true; 63} 64 65MetricsStateManager::~MetricsStateManager() { 66 DCHECK(instance_exists_); 67 instance_exists_ = false; 68} 69 70bool MetricsStateManager::IsMetricsReportingEnabled() { 71 return is_reporting_enabled_callback_.Run(); 72} 73 74void MetricsStateManager::ForceClientIdCreation() { 75 if (!client_id_.empty()) 76 return; 77 78 client_id_ = local_state_->GetString(prefs::kMetricsClientID); 79 if (!client_id_.empty()) { 80 // It is technically sufficient to only save a backup of the client id when 81 // it is initially generated below, but since the backup was only introduced 82 // in M38, seed it explicitly from here for some time. 83 BackUpCurrentClientInfo(); 84 return; 85 } 86 87 const scoped_ptr<ClientInfo> client_info_backup = 88 LoadClientInfoAndMaybeMigrate(); 89 if (client_info_backup) { 90 client_id_ = client_info_backup->client_id; 91 92 const base::Time now = base::Time::Now(); 93 94 // Save the recovered client id and also try to reinstantiate the backup 95 // values for the dates corresponding with that client id in order to avoid 96 // weird scenarios where we could report an old client id with a recent 97 // install date. 98 local_state_->SetString(prefs::kMetricsClientID, client_id_); 99 local_state_->SetInt64(prefs::kInstallDate, 100 client_info_backup->installation_date != 0 101 ? client_info_backup->installation_date 102 : now.ToTimeT()); 103 local_state_->SetInt64(prefs::kMetricsReportingEnabledTimestamp, 104 client_info_backup->reporting_enabled_date != 0 105 ? client_info_backup->reporting_enabled_date 106 : now.ToTimeT()); 107 108 base::TimeDelta recovered_installation_age; 109 if (client_info_backup->installation_date != 0) { 110 recovered_installation_age = 111 now - base::Time::FromTimeT(client_info_backup->installation_date); 112 } 113 UMA_HISTOGRAM_COUNTS_10000("UMA.ClientIdBackupRecoveredWithAge", 114 recovered_installation_age.InHours()); 115 116 // Flush the backup back to persistent storage in case we re-generated 117 // missing data above. 118 BackUpCurrentClientInfo(); 119 return; 120 } 121 122 // Failing attempts at getting an existing client ID, generate a new one. 123 client_id_ = base::GenerateGUID(); 124 local_state_->SetString(prefs::kMetricsClientID, client_id_); 125 126 if (local_state_->GetString(prefs::kMetricsOldClientID).empty()) { 127 // Record the timestamp of when the user opted in to UMA. 128 local_state_->SetInt64(prefs::kMetricsReportingEnabledTimestamp, 129 base::Time::Now().ToTimeT()); 130 } else { 131 UMA_HISTOGRAM_BOOLEAN("UMA.ClientIdMigrated", true); 132 } 133 local_state_->ClearPref(prefs::kMetricsOldClientID); 134 135 BackUpCurrentClientInfo(); 136} 137 138void MetricsStateManager::CheckForClonedInstall( 139 scoped_refptr<base::SingleThreadTaskRunner> task_runner) { 140 DCHECK(!cloned_install_detector_); 141 142 MachineIdProvider* provider = MachineIdProvider::CreateInstance(); 143 if (!provider) 144 return; 145 146 cloned_install_detector_.reset(new ClonedInstallDetector(provider)); 147 cloned_install_detector_->CheckForClonedInstall(local_state_, task_runner); 148} 149 150scoped_ptr<const base::FieldTrial::EntropyProvider> 151MetricsStateManager::CreateEntropyProvider() { 152 // For metrics reporting-enabled users, we combine the client ID and low 153 // entropy source to get the final entropy source. Otherwise, only use the low 154 // entropy source. 155 // This has two useful properties: 156 // 1) It makes the entropy source less identifiable for parties that do not 157 // know the low entropy source. 158 // 2) It makes the final entropy source resettable. 159 const int low_entropy_source_value = GetLowEntropySource(); 160 UMA_HISTOGRAM_SPARSE_SLOWLY("UMA.LowEntropySourceValue", 161 low_entropy_source_value); 162 if (IsMetricsReportingEnabled()) { 163 if (entropy_source_returned_ == ENTROPY_SOURCE_NONE) 164 entropy_source_returned_ = ENTROPY_SOURCE_HIGH; 165 const std::string high_entropy_source = 166 client_id_ + base::IntToString(low_entropy_source_value); 167 return scoped_ptr<const base::FieldTrial::EntropyProvider>( 168 new SHA1EntropyProvider(high_entropy_source)); 169 } 170 171 if (entropy_source_returned_ == ENTROPY_SOURCE_NONE) 172 entropy_source_returned_ = ENTROPY_SOURCE_LOW; 173 174#if defined(OS_ANDROID) || defined(OS_IOS) 175 return scoped_ptr<const base::FieldTrial::EntropyProvider>( 176 new CachingPermutedEntropyProvider(local_state_, 177 low_entropy_source_value, 178 kMaxLowEntropySize)); 179#else 180 return scoped_ptr<const base::FieldTrial::EntropyProvider>( 181 new PermutedEntropyProvider(low_entropy_source_value, 182 kMaxLowEntropySize)); 183#endif 184} 185 186// static 187scoped_ptr<MetricsStateManager> MetricsStateManager::Create( 188 PrefService* local_state, 189 const base::Callback<bool(void)>& is_reporting_enabled_callback, 190 const StoreClientInfoCallback& store_client_info, 191 const LoadClientInfoCallback& retrieve_client_info) { 192 scoped_ptr<MetricsStateManager> result; 193 // Note: |instance_exists_| is updated in the constructor and destructor. 194 if (!instance_exists_) { 195 result.reset(new MetricsStateManager(local_state, 196 is_reporting_enabled_callback, 197 store_client_info, 198 retrieve_client_info)); 199 } 200 return result.Pass(); 201} 202 203// static 204void MetricsStateManager::RegisterPrefs(PrefRegistrySimple* registry) { 205 registry->RegisterBooleanPref(prefs::kMetricsResetIds, false); 206 registry->RegisterStringPref(prefs::kMetricsClientID, std::string()); 207 registry->RegisterInt64Pref(prefs::kMetricsReportingEnabledTimestamp, 0); 208 registry->RegisterIntegerPref(prefs::kMetricsLowEntropySource, 209 kLowEntropySourceNotSet); 210 211 ClonedInstallDetector::RegisterPrefs(registry); 212 CachingPermutedEntropyProvider::RegisterPrefs(registry); 213 214 // TODO(asvitkine): Remove these once a couple of releases have passed. 215 // http://crbug.com/357704 216 registry->RegisterStringPref(prefs::kMetricsOldClientID, std::string()); 217 registry->RegisterIntegerPref(prefs::kMetricsOldLowEntropySource, 0); 218} 219 220void MetricsStateManager::BackUpCurrentClientInfo() { 221 ClientInfo client_info; 222 client_info.client_id = client_id_; 223 client_info.installation_date = local_state_->GetInt64(prefs::kInstallDate); 224 client_info.reporting_enabled_date = 225 local_state_->GetInt64(prefs::kMetricsReportingEnabledTimestamp); 226 store_client_info_.Run(client_info); 227} 228 229scoped_ptr<ClientInfo> MetricsStateManager::LoadClientInfoAndMaybeMigrate() { 230 scoped_ptr<metrics::ClientInfo> client_info = load_client_info_.Run(); 231 232 // Prior to 2014-07, the client ID was stripped of its dashes before being 233 // saved. Migrate back to a proper GUID if this is the case. This migration 234 // code can be removed in M41+. 235 const size_t kGUIDLengthWithoutDashes = 32U; 236 if (client_info && 237 client_info->client_id.length() == kGUIDLengthWithoutDashes) { 238 DCHECK(client_info->client_id.find('-') == std::string::npos); 239 240 std::string client_id_with_dashes; 241 client_id_with_dashes.reserve(kGUIDLengthWithoutDashes + 4U); 242 std::string::const_iterator client_id_it = client_info->client_id.begin(); 243 for (size_t i = 0; i < kGUIDLengthWithoutDashes + 4U; ++i) { 244 if (i == 8U || i == 13U || i == 18U || i == 23U) { 245 client_id_with_dashes.push_back('-'); 246 } else { 247 client_id_with_dashes.push_back(*client_id_it); 248 ++client_id_it; 249 } 250 } 251 DCHECK(client_id_it == client_info->client_id.end()); 252 client_info->client_id.assign(client_id_with_dashes); 253 } 254 255 // The GUID retrieved (and possibly fixed above) should be valid unless 256 // retrieval failed. 257 DCHECK(!client_info || base::IsValidGUID(client_info->client_id)); 258 259 return client_info.Pass(); 260} 261 262int MetricsStateManager::GetLowEntropySource() { 263 // Note that the default value for the low entropy source and the default pref 264 // value are both kLowEntropySourceNotSet, which is used to identify if the 265 // value has been set or not. 266 if (low_entropy_source_ != kLowEntropySourceNotSet) 267 return low_entropy_source_; 268 269 const CommandLine* command_line(CommandLine::ForCurrentProcess()); 270 // Only try to load the value from prefs if the user did not request a 271 // reset. 272 // Otherwise, skip to generating a new value. 273 if (!command_line->HasSwitch(switches::kResetVariationState)) { 274 int value = local_state_->GetInteger(prefs::kMetricsLowEntropySource); 275 // If the value is outside the [0, kMaxLowEntropySize) range, re-generate 276 // it below. 277 if (value >= 0 && value < kMaxLowEntropySize) { 278 low_entropy_source_ = value; 279 UMA_HISTOGRAM_BOOLEAN("UMA.GeneratedLowEntropySource", false); 280 return low_entropy_source_; 281 } 282 } 283 284 UMA_HISTOGRAM_BOOLEAN("UMA.GeneratedLowEntropySource", true); 285 low_entropy_source_ = GenerateLowEntropySource(); 286 local_state_->SetInteger(prefs::kMetricsLowEntropySource, 287 low_entropy_source_); 288 local_state_->ClearPref(prefs::kMetricsOldLowEntropySource); 289 CachingPermutedEntropyProvider::ClearCache(local_state_); 290 291 return low_entropy_source_; 292} 293 294void MetricsStateManager::ResetMetricsIDsIfNecessary() { 295 if (!local_state_->GetBoolean(prefs::kMetricsResetIds)) 296 return; 297 298 UMA_HISTOGRAM_BOOLEAN("UMA.MetricsIDsReset", true); 299 300 DCHECK(client_id_.empty()); 301 DCHECK_EQ(kLowEntropySourceNotSet, low_entropy_source_); 302 303 local_state_->ClearPref(prefs::kMetricsClientID); 304 local_state_->ClearPref(prefs::kMetricsLowEntropySource); 305 local_state_->ClearPref(prefs::kMetricsResetIds); 306 307 // Also clear the backed up client info. 308 store_client_info_.Run(ClientInfo()); 309} 310 311} // namespace metrics 312