Home | History | Annotate | Download | only in metrics
      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 
     23 namespace metrics {
     24 
     25 namespace {
     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).
     30 const int kMaxLowEntropySize = 8000;
     31 
     32 // Default prefs value for prefs::kMetricsLowEntropySource to indicate that
     33 // the value has not yet been set.
     34 const int kLowEntropySourceNotSet = -1;
     35 
     36 // Generates a new non-identifying entropy source used to seed persistent
     37 // activities.
     38 int GenerateLowEntropySource() {
     39   return base::RandInt(0, kMaxLowEntropySize - 1);
     40 }
     41 
     42 }  // namespace
     43 
     44 // static
     45 bool MetricsStateManager::instance_exists_ = false;
     46 
     47 MetricsStateManager::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 
     66 MetricsStateManager::~MetricsStateManager() {
     67   DCHECK(instance_exists_);
     68   instance_exists_ = false;
     69 }
     70 
     71 bool MetricsStateManager::IsMetricsReportingEnabled() {
     72   return is_reporting_enabled_callback_.Run();
     73 }
     74 
     75 void 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 
    139 void 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 
    151 scoped_ptr<const base::FieldTrial::EntropyProvider>
    152 MetricsStateManager::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
    188 scoped_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
    205 void 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 
    221 void 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 
    233 scoped_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 
    266 int 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 
    298 void 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