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