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