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