diff options
author | asvitkine@chromium.org <asvitkine@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-05-05 18:44:58 +0000 |
---|---|---|
committer | asvitkine@chromium.org <asvitkine@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-05-05 18:44:58 +0000 |
commit | ff1b9b212027c268b911d38cf1771c15adfefa5d (patch) | |
tree | e3a946c515c31d960d25270640abf081a17b6a05 | |
parent | 6e84e2c187647c30f3f4563aac671ba9416b4182 (diff) | |
download | chromium_src-ff1b9b212027c268b911d38cf1771c15adfefa5d.zip chromium_src-ff1b9b212027c268b911d38cf1771c15adfefa5d.tar.gz chromium_src-ff1b9b212027c268b911d38cf1771c15adfefa5d.tar.bz2 |
Refactor MetricsStateManager class out of MetricsService.
The new class is responsible for managing various MetricsService state prefs,
such as client id, low entropy source and the UMA opt-in state, as well as the
cloned install detector.
Also, moves IsMetricsReportingEnabled() from chrome_browser_main.cc to
the new class as well as changing a couple MetricsService browser tests
to instead be unit tests of the new class.
BUG=368413
Review URL: https://codereview.chromium.org/256143006
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@268232 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | chrome/browser/chrome_browser_main.cc | 51 | ||||
-rw-r--r-- | chrome/browser/chrome_browser_main.h | 3 | ||||
-rw-r--r-- | chrome/browser/metrics/cloned_install_detector_unittest.cc | 5 | ||||
-rw-r--r-- | chrome/browser/metrics/metrics_service.cc | 206 | ||||
-rw-r--r-- | chrome/browser/metrics/metrics_service.h | 92 | ||||
-rw-r--r-- | chrome/browser/metrics/metrics_service_browsertest.cc | 28 | ||||
-rw-r--r-- | chrome/browser/metrics/metrics_service_unittest.cc | 132 | ||||
-rw-r--r-- | chrome/browser/metrics/metrics_state_manager.cc | 233 | ||||
-rw-r--r-- | chrome/browser/metrics/metrics_state_manager.h | 124 | ||||
-rw-r--r-- | chrome/browser/metrics/metrics_state_manager_unittest.cc | 160 | ||||
-rw-r--r-- | chrome/browser/prefs/browser_prefs.cc | 4 | ||||
-rw-r--r-- | chrome/chrome_browser.gypi | 2 | ||||
-rw-r--r-- | chrome/chrome_tests_unit.gypi | 1 |
13 files changed, 596 insertions, 445 deletions
diff --git a/chrome/browser/chrome_browser_main.cc b/chrome/browser/chrome_browser_main.cc index 19c4cad..d84585d 100644 --- a/chrome/browser/chrome_browser_main.cc +++ b/chrome/browser/chrome_browser_main.cc @@ -562,14 +562,8 @@ void ChromeBrowserMainParts::SetupMetricsAndFieldTrials() { // Initialize FieldTrialList to support FieldTrials that use one-time // randomization. MetricsService* metrics = browser_process_->metrics_service(); - MetricsService::ReportingState reporting_state = - IsMetricsReportingEnabled() ? MetricsService::REPORTING_ENABLED : - MetricsService::REPORTING_DISABLED; - if (reporting_state == MetricsService::REPORTING_ENABLED) - metrics->ForceClientIdCreation(); // Needed below. field_trial_list_.reset( - new base::FieldTrialList( - metrics->CreateEntropyProvider(reporting_state).release())); + new base::FieldTrialList(metrics->CreateEntropyProvider().release())); const CommandLine* command_line = CommandLine::ForCurrentProcess(); if (command_line->HasSwitch(switches::kEnableBenchmarking)) @@ -617,7 +611,7 @@ void ChromeBrowserMainParts::SetupMetricsAndFieldTrials() { field_trial_synchronizer_ = new FieldTrialSynchronizer(); // Now that field trials have been created, initializes metrics recording. - metrics->InitializeMetricsRecordingState(reporting_state); + metrics->InitializeMetricsRecordingState(); } // ChromeBrowserMainParts: |SetupMetricsAndFieldTrials()| related -------------- @@ -637,33 +631,14 @@ void ChromeBrowserMainParts::StartMetricsRecording() { } metrics->CheckForClonedInstall(); - - if (IsMetricsReportingEnabled()) - metrics->Start(); -} - -bool ChromeBrowserMainParts::IsMetricsReportingEnabled() { - // If the user permits metrics reporting with the checkbox in the - // prefs, we turn on recording. We disable metrics completely for - // non-official builds. This can be forced with a flag. - const CommandLine* command_line = CommandLine::ForCurrentProcess(); - if (command_line->HasSwitch(switches::kEnableMetricsReportingForTesting)) - return true; - - bool enabled = false; - // The debug build doesn't send UMA logs when FieldTrials are forced. - if (command_line->HasSwitch(switches::kForceFieldTrials)) - return false; - -#if defined(GOOGLE_CHROME_BUILD) -#if defined(OS_CHROMEOS) - chromeos::CrosSettings::Get()->GetBoolean(chromeos::kStatsReportingPref, - &enabled); -#else - enabled = local_state_->GetBoolean(prefs::kMetricsReportingEnabled); -#endif // #if defined(OS_CHROMEOS) -#endif // defined(GOOGLE_CHROME_BUILD) - return enabled; + const bool metrics_enabled = metrics->StartIfMetricsReportingEnabled(); + if (metrics_enabled) { + // TODO(asvitkine): Since this function is not run on Android, RAPPOR is + // currently disabled there. http://crbug.com/370041 + browser_process_->rappor_service()->Start( + browser_process_->local_state(), + browser_process_->system_request_context()); + } } void ChromeBrowserMainParts::RecordBrowserStartupTime() { @@ -1094,12 +1069,6 @@ int ChromeBrowserMainParts::PreMainMessageLoopRunImpl() { StartMetricsRecording(); #endif - if (IsMetricsReportingEnabled()) { - browser_process_->rappor_service()->Start( - browser_process_->local_state(), - browser_process_->system_request_context()); - } - // Create watchdog thread after creating all other threads because it will // watch the other threads and they must be running. browser_process_->watchdog_thread(); diff --git a/chrome/browser/chrome_browser_main.h b/chrome/browser/chrome_browser_main.h index 0d1b51c..a856a2b7 100644 --- a/chrome/browser/chrome_browser_main.h +++ b/chrome/browser/chrome_browser_main.h @@ -107,9 +107,6 @@ class ChromeBrowserMainParts : public content::BrowserMainParts { // thread. void StartMetricsRecording(); - // Returns true if the user opted in to sending metric reports. - bool IsMetricsReportingEnabled(); - // Record time from process startup to present time in an UMA histogram. void RecordBrowserStartupTime(); diff --git a/chrome/browser/metrics/cloned_install_detector_unittest.cc b/chrome/browser/metrics/cloned_install_detector_unittest.cc index dcc8bf6..6114632 100644 --- a/chrome/browser/metrics/cloned_install_detector_unittest.cc +++ b/chrome/browser/metrics/cloned_install_detector_unittest.cc @@ -6,7 +6,7 @@ #include "base/prefs/testing_pref_service.h" #include "chrome/browser/metrics/machine_id_provider.h" -#include "chrome/browser/metrics/metrics_service.h" +#include "chrome/browser/metrics/metrics_state_manager.h" #include "chrome/common/pref_names.h" #include "testing/gtest/include/gtest/gtest.h" @@ -37,8 +37,7 @@ TEST(ClonedInstallDetectorTest, SaveId) { TEST(ClonedInstallDetectorTest, DetectClone) { TestingPrefServiceSimple prefs; - ClonedInstallDetector::RegisterPrefs(prefs.registry()); - MetricsService::RegisterPrefs(prefs.registry()); + MetricsStateManager::RegisterPrefs(prefs.registry()); // Save a machine id that will cause a clone to be detected. prefs.SetInteger(prefs::kMetricsMachineId, kTestHashedId + 1); diff --git a/chrome/browser/metrics/metrics_service.cc b/chrome/browser/metrics/metrics_service.cc index 55c1321..8f56fa1 100644 --- a/chrome/browser/metrics/metrics_service.cc +++ b/chrome/browser/metrics/metrics_service.cc @@ -168,14 +168,12 @@ #include "base/bind.h" #include "base/callback.h" #include "base/command_line.h" -#include "base/guid.h" #include "base/metrics/histogram.h" #include "base/metrics/sparse_histogram.h" #include "base/metrics/statistics_recorder.h" #include "base/prefs/pref_registry_simple.h" #include "base/prefs/pref_service.h" #include "base/prefs/scoped_user_pref_update.h" -#include "base/rand_util.h" #include "base/strings/string_number_conversions.h" #include "base/strings/utf_string_conversions.h" #include "base/threading/platform_thread.h" @@ -187,12 +185,11 @@ #include "chrome/browser/chrome_notification_types.h" #include "chrome/browser/io_thread.h" #include "chrome/browser/memory_details.h" -#include "chrome/browser/metrics/cloned_install_detector.h" #include "chrome/browser/metrics/compression_utils.h" -#include "chrome/browser/metrics/machine_id_provider.h" #include "chrome/browser/metrics/metrics_log.h" #include "chrome/browser/metrics/metrics_log_serializer.h" #include "chrome/browser/metrics/metrics_reporting_scheduler.h" +#include "chrome/browser/metrics/metrics_state_manager.h" #include "chrome/browser/metrics/time_ticks_experiment_win.h" #include "chrome/browser/metrics/tracking_synchronizer.h" #include "chrome/common/metrics/variations/variations_util.h" @@ -206,7 +203,6 @@ #include "chrome/common/chrome_result_codes.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/crash_keys.h" -#include "chrome/common/metrics/caching_permuted_entropy_provider.h" #include "chrome/common/net/test_server_locations.h" #include "chrome/common/pref_names.h" #include "chrome/common/render_messages.h" @@ -313,21 +309,6 @@ ResponseStatus ResponseCodeToStatus(int response_code) { } } -// The argument used to generate a non-identifying entropy source. We want no -// more than 13 bits of entropy, so use this max to return a number in the range -// [0, 7999] as the entropy source (12.97 bits of entropy). -const int kMaxLowEntropySize = 8000; - -// Default prefs value for prefs::kMetricsLowEntropySource to indicate that the -// value has not yet been set. -const int kLowEntropySourceNotSet = -1; - -// Generates a new non-identifying entropy source used to seed persistent -// activities. -int GenerateLowEntropySource() { - return base::RandInt(0, kMaxLowEntropySize - 1); -} - // Converts an exit code into something that can be inserted into our // histograms (which expect non-negative numbers less than MAX_INT). int MapCrashExitCodeForHistogram(int exit_code) { @@ -430,11 +411,8 @@ class MetricsMemoryDetails : public MemoryDetails { // static void MetricsService::RegisterPrefs(PrefRegistrySimple* registry) { DCHECK(IsSingleThreaded()); - registry->RegisterBooleanPref(prefs::kMetricsResetIds, false); - registry->RegisterStringPref(prefs::kMetricsClientID, std::string()); - registry->RegisterInt64Pref(prefs::kMetricsReportingEnabledTimestamp, 0); - registry->RegisterIntegerPref(prefs::kMetricsLowEntropySource, - kLowEntropySourceNotSet); + metrics::MetricsStateManager::RegisterPrefs(registry); + registry->RegisterInt64Pref(prefs::kStabilityLaunchTimeSec, 0); registry->RegisterInt64Pref(prefs::kStabilityLastTimestampSec, 0); registry->RegisterStringPref(prefs::kStabilityStatsVersion, std::string()); @@ -479,33 +457,28 @@ void MetricsService::RegisterPrefs(PrefRegistrySimple* registry) { registry->RegisterInt64Pref(prefs::kUninstallLastLaunchTimeSec, 0); registry->RegisterInt64Pref(prefs::kUninstallLastObservedRunTimeSec, 0); - // TODO(asvitkine): Remove these once a couple of releases have passed. - // http://crbug.com/357704 - registry->RegisterStringPref(prefs::kMetricsOldClientID, std::string()); - registry->RegisterIntegerPref(prefs::kMetricsOldLowEntropySource, 0); - #if defined(OS_ANDROID) RegisterPrefsAndroid(registry); #endif // defined(OS_ANDROID) } MetricsService::MetricsService() - : metrics_ids_reset_check_performed_(false), + : state_manager_(metrics::MetricsStateManager::Create( + g_browser_process->local_state())), recording_active_(false), reporting_active_(false), test_mode_active_(false), state_(INITIALIZED), has_initial_stability_log_(false), - low_entropy_source_(kLowEntropySourceNotSet), idle_since_last_transmission_(false), session_id_(-1), next_window_id_(0), self_ptr_factory_(this), state_saver_factory_(this), waiting_for_asynchronous_reporting_step_(false), - num_async_histogram_fetches_in_progress_(0), - entropy_source_returned_(LAST_ENTROPY_NONE) { + num_async_histogram_fetches_in_progress_(0) { DCHECK(IsSingleThreaded()); + DCHECK(state_manager_); log_manager_.set_log_serializer(new MetricsLogSerializer); log_manager_.set_max_ongoing_log_store_size(kUploadLogAvoidRetransmitSize); @@ -519,9 +492,8 @@ MetricsService::~MetricsService() { BrowserChildProcessObserver::Remove(this); } -void MetricsService::InitializeMetricsRecordingState( - ReportingState reporting_state) { - InitializeMetricsState(reporting_state); +void MetricsService::InitializeMetricsRecordingState() { + InitializeMetricsState(); base::Closure callback = base::Bind(&MetricsService::StartScheduledUpload, self_ptr_factory_.GetWeakPtr()); @@ -534,6 +506,13 @@ void MetricsService::Start() { EnableReporting(); } +bool MetricsService::StartIfMetricsReportingEnabled() { + const bool enabled = state_manager_->IsMetricsReportingEnabled(); + if (enabled) + Start(); + return enabled; +} + void MetricsService::StartRecordingForTests() { test_mode_active_ = true; EnableRecording(); @@ -558,70 +537,14 @@ void MetricsService::DisableReporting() { } std::string MetricsService::GetClientId() { - return client_id_; + return state_manager_->client_id(); } scoped_ptr<const base::FieldTrial::EntropyProvider> -MetricsService::CreateEntropyProvider(ReportingState reporting_state) { - // For metrics reporting-enabled users, we combine the client ID and low - // entropy source to get the final entropy source. Otherwise, only use the low - // entropy source. - // This has two useful properties: - // 1) It makes the entropy source less identifiable for parties that do not - // know the low entropy source. - // 2) It makes the final entropy source resettable. - const int low_entropy_source_value = GetLowEntropySource(); - UMA_HISTOGRAM_SPARSE_SLOWLY("UMA.LowEntropySourceValue", - low_entropy_source_value); - if (reporting_state == REPORTING_ENABLED) { - if (entropy_source_returned_ == LAST_ENTROPY_NONE) - entropy_source_returned_ = LAST_ENTROPY_HIGH; - DCHECK_EQ(LAST_ENTROPY_HIGH, entropy_source_returned_); - const std::string high_entropy_source = - client_id_ + base::IntToString(low_entropy_source_value); - return scoped_ptr<const base::FieldTrial::EntropyProvider>( - new metrics::SHA1EntropyProvider(high_entropy_source)); - } - - if (entropy_source_returned_ == LAST_ENTROPY_NONE) - entropy_source_returned_ = LAST_ENTROPY_LOW; - DCHECK_EQ(LAST_ENTROPY_LOW, entropy_source_returned_); - -#if defined(OS_ANDROID) || defined(OS_IOS) - return scoped_ptr<const base::FieldTrial::EntropyProvider>( - new metrics::CachingPermutedEntropyProvider( - g_browser_process->local_state(), - low_entropy_source_value, - kMaxLowEntropySize)); -#else - return scoped_ptr<const base::FieldTrial::EntropyProvider>( - new metrics::PermutedEntropyProvider(low_entropy_source_value, - kMaxLowEntropySize)); -#endif -} - -void MetricsService::ForceClientIdCreation() { - if (!client_id_.empty()) - return; - - ResetMetricsIDsIfNecessary(); - - PrefService* pref = g_browser_process->local_state(); - client_id_ = pref->GetString(prefs::kMetricsClientID); - if (!client_id_.empty()) - return; - - client_id_ = GenerateClientID(); - pref->SetString(prefs::kMetricsClientID, client_id_); - - if (pref->GetString(prefs::kMetricsOldClientID).empty()) { - // Record the timestamp of when the user opted in to UMA. - pref->SetInt64(prefs::kMetricsReportingEnabledTimestamp, - Time::Now().ToTimeT()); - } else { - UMA_HISTOGRAM_BOOLEAN("UMA.ClientIdMigrated", true); - } - pref->ClearPref(prefs::kMetricsOldClientID); +MetricsService::CreateEntropyProvider() { + // TODO(asvitkine): Refactor the code so that MetricsService does not expose + // this method. + return state_manager_->CreateEntropyProvider(); } void MetricsService::EnableRecording() { @@ -631,8 +554,8 @@ void MetricsService::EnableRecording() { return; recording_active_ = true; - ForceClientIdCreation(); - crash_keys::SetClientID(client_id_); + state_manager_->ForceClientIdCreation(); + crash_keys::SetClientID(state_manager_->client_id()); if (!log_manager_.current_log()) OpenNewLog(); @@ -918,7 +841,7 @@ void MetricsService::CountBrowserCrashDumpAttempts() { //------------------------------------------------------------------------------ // Initialization methods -void MetricsService::InitializeMetricsState(ReportingState reporting_state) { +void MetricsService::InitializeMetricsState() { #if defined(OS_POSIX) network_stats_server_ = chrome_common_net::kEchoTestServerLocation; http_pipelining_test_server_ = chrome_common_net::kPipelineTestServerBaseUrl; @@ -955,7 +878,7 @@ void MetricsService::InitializeMetricsState(ReportingState reporting_state) { // If the previous session didn't exit cleanly, then prepare an initial // stability log if UMA is enabled. - if (reporting_state == REPORTING_ENABLED) + if (state_manager_->IsMetricsReportingEnabled()) PrepareInitialStabilityLog(); } @@ -1119,7 +1042,8 @@ void MetricsService::ReceivedProfilerData( // save the profiler data. if (!initial_metrics_log_.get()) { initial_metrics_log_.reset( - new MetricsLog(client_id_, session_id_, MetricsLog::ONGOING_LOG)); + new MetricsLog(state_manager_->client_id(), session_id_, + MetricsLog::ONGOING_LOG)); } initial_metrics_log_->RecordProfilerData(process_data, process_type); @@ -1153,64 +1077,6 @@ void MetricsService::GetUptimes(PrefService* pref, } } -void MetricsService::ResetMetricsIDsIfNecessary() { - if (metrics_ids_reset_check_performed_) - return; - - metrics_ids_reset_check_performed_ = true; - - PrefService* local_state = g_browser_process->local_state(); - if (!local_state->GetBoolean(prefs::kMetricsResetIds)) - return; - - UMA_HISTOGRAM_BOOLEAN("UMA.MetricsIDsReset", true); - - DCHECK(client_id_.empty()); - DCHECK_EQ(kLowEntropySourceNotSet, low_entropy_source_); - - local_state->ClearPref(prefs::kMetricsClientID); - local_state->ClearPref(prefs::kMetricsLowEntropySource); - local_state->ClearPref(prefs::kMetricsResetIds); -} - -int MetricsService::GetLowEntropySource() { - // Note that the default value for the low entropy source and the default pref - // value are both kLowEntropySourceNotSet, which is used to identify if the - // value has been set or not. - if (low_entropy_source_ != kLowEntropySourceNotSet) - return low_entropy_source_; - - ResetMetricsIDsIfNecessary(); - - PrefService* local_state = g_browser_process->local_state(); - const CommandLine* command_line(CommandLine::ForCurrentProcess()); - // Only try to load the value from prefs if the user did not request a reset. - // Otherwise, skip to generating a new value. - if (!command_line->HasSwitch(switches::kResetVariationState)) { - int value = local_state->GetInteger(prefs::kMetricsLowEntropySource); - // If the value is outside the [0, kMaxLowEntropySize) range, re-generate - // it below. - if (value >= 0 && value < kMaxLowEntropySize) { - low_entropy_source_ = value; - UMA_HISTOGRAM_BOOLEAN("UMA.GeneratedLowEntropySource", false); - return low_entropy_source_; - } - } - - UMA_HISTOGRAM_BOOLEAN("UMA.GeneratedLowEntropySource", true); - low_entropy_source_ = GenerateLowEntropySource(); - local_state->SetInteger(prefs::kMetricsLowEntropySource, low_entropy_source_); - local_state->ClearPref(prefs::kMetricsOldLowEntropySource); - metrics::CachingPermutedEntropyProvider::ClearCache(local_state); - - return low_entropy_source_; -} - -// static -std::string MetricsService::GenerateClientID() { - return base::GenerateGUID(); -} - //------------------------------------------------------------------------------ // State save methods @@ -1244,7 +1110,8 @@ void MetricsService::OpenNewLog() { DCHECK(!log_manager_.current_log()); log_manager_.BeginLoggingWithLog( - new MetricsLog(client_id_, session_id_, MetricsLog::ONGOING_LOG)); + new MetricsLog(state_manager_->client_id(), session_id_, + MetricsLog::ONGOING_LOG)); if (state_ == INITIALIZED) { // We only need to schedule that run once. state_ = INIT_TASK_SCHEDULED; @@ -1540,7 +1407,7 @@ void MetricsService::PrepareInitialStabilityLog() { DCHECK_NE(0, pref->GetInteger(prefs::kStabilityCrashCount)); scoped_ptr<MetricsLog> initial_stability_log( - new MetricsLog(client_id_, session_id_, + new MetricsLog(state_manager_->client_id(), session_id_, MetricsLog::INITIAL_STABILITY_LOG)); if (!initial_stability_log->LoadSavedEnvironmentFromPrefs()) return; @@ -1839,18 +1706,7 @@ void MetricsService::RegisterSyntheticFieldTrial( } void MetricsService::CheckForClonedInstall() { - DCHECK(!cloned_install_detector_); - - metrics::MachineIdProvider* provider = - metrics::MachineIdProvider::CreateInstance(); - if (!provider) - return; - - cloned_install_detector_.reset( - new metrics::ClonedInstallDetector(provider)); - - PrefService* local_state = g_browser_process->local_state(); - cloned_install_detector_->CheckForClonedInstall(local_state); + state_manager_->CheckForClonedInstall(); } void MetricsService::GetCurrentSyntheticFieldTrials( diff --git a/chrome/browser/metrics/metrics_service.h b/chrome/browser/metrics/metrics_service.h index 82c3c30..1499ddf 100644 --- a/chrome/browser/metrics/metrics_service.h +++ b/chrome/browser/metrics/metrics_service.h @@ -69,7 +69,7 @@ class MetricsPrivateGetIsCrashReportingEnabledFunction; } namespace metrics { -class ClonedInstallDetector; +class MetricsStateManager; } namespace net { @@ -127,26 +127,24 @@ class MetricsService SHUTDOWN_COMPLETE = 700, }; - enum ReportingState { - REPORTING_ENABLED, - REPORTING_DISABLED, - }; - MetricsService(); virtual ~MetricsService(); // Initializes metrics recording state. Updates various bookkeeping values in // prefs and sets up the scheduler. This is a separate function rather than // being done by the constructor so that field trials could be created before - // this is run. Takes |reporting_state| parameter which specifies whether UMA - // is enabled. - void InitializeMetricsRecordingState(ReportingState reporting_state); + // this is run. + void InitializeMetricsRecordingState(); // Starts the metrics system, turning on recording and uploading of metrics. // Should be called when starting up with metrics enabled, or when metrics // are turned on. void Start(); + // If metrics reporting is enabled, starts the metrics service. Returns + // whether the metrics service was started. + bool StartIfMetricsReportingEnabled(); + // Starts the metrics system in a special test-only mode. Metrics won't ever // be uploaded or persisted in this mode, but metrics will be recorded in // memory. @@ -169,23 +167,12 @@ class MetricsService // Returns the preferred entropy provider used to seed persistent activities // based on whether or not metrics reporting will be permitted on this client. - // The caller must determine if metrics reporting will be enabled for this - // client and pass that state in as |reporting_will_be_enabled|. - // - // If |reporting_will_be_enabled| is true, this method returns an entropy - // provider that has a high source of entropy, partially based on the client - // ID. Otherwise, an entropy provider that is based on a low entropy source - // is returned. // - // Note that this reporting state can not be checked by reporting_active() - // because this method may need to be called before the MetricsService needs - // to be started. - scoped_ptr<const base::FieldTrial::EntropyProvider> CreateEntropyProvider( - ReportingState reporting_state); - - // Force the client ID to be generated. This is useful in case it's needed - // before recording. - void ForceClientIdCreation(); + // If metrics reporting is enabled, this method returns an entropy provider + // that has a high source of entropy, partially based on the client ID. + // Otherwise, it returns an entropy provider that is based on a low entropy + // source. + scoped_ptr<const base::FieldTrial::EntropyProvider> CreateEntropyProvider(); // At startup, prefs needs to be called with a list of all the pref names and // types we'll be using. @@ -312,15 +299,6 @@ class MetricsService NEED_TO_SHUTDOWN = ~CLEANLY_SHUTDOWN }; - // Designates which entropy source was returned from this MetricsService. - // This is used for testing to validate that we return the correct source - // depending on the state of the service. - enum EntropySourceReturned { - LAST_ENTROPY_NONE, - LAST_ENTROPY_LOW, - LAST_ENTROPY_HIGH, - }; - struct ChildProcessStats; typedef std::vector<SyntheticTrialGroup> SyntheticTrialGroups; @@ -368,22 +346,6 @@ class MetricsService base::TimeDelta* incremental_uptime, base::TimeDelta* uptime); - // Reset the client id and low entropy source if the kMetricsResetMetricIDs - // pref is true. - void ResetMetricsIDsIfNecessary(); - - // Returns the low entropy source for this client. This is a random value - // that is non-identifying amongst browser clients. This method will - // generate the entropy source value if it has not been called before. - int GetLowEntropySource(); - - // Returns the first entropy source that was returned by this service since - // start up, or NONE if neither was returned yet. This is exposed for testing - // only. - EntropySourceReturned entropy_source_returned() const { - return entropy_source_returned_; - } - // Turns recording on or off. // DisableRecording() also forces a persistent save of logging state (if // anything has been recorded, or transmitted). @@ -397,10 +359,7 @@ class MetricsService void HandleIdleSinceLastTransmission(bool in_idle); // Set up client ID, session ID, etc. - void InitializeMetricsState(ReportingState reporting_state); - - // Generates a new client ID to use to identify self to metrics server. - static std::string GenerateClientID(); + void InitializeMetricsState(); // Schedule the next save of LocalState information. This is called // automatically by the task that performs each save to schedule the next one. @@ -512,6 +471,10 @@ class MetricsService void GetCurrentSyntheticFieldTrials( std::vector<chrome_variations::ActiveGroupId>* synthetic_trials); + // Used to manage various metrics reporting state prefs, such as client id, + // low entropy source and whether metrics reporting is enabled. + scoped_ptr<metrics::MetricsStateManager> state_manager_; + base::ActionCallback action_callback_; content::NotificationRegistrar registrar_; @@ -563,12 +526,6 @@ class MetricsService // The HTTP pipelining test server. std::string http_pipelining_test_server_; - // The identifier that's sent to the server with the log reports. - std::string client_id_; - - // The non-identifying low entropy source value. - int low_entropy_source_; - // Whether the MetricsService object has received any notifications since // the last time a transmission was sent. bool idle_since_last_transmission_; @@ -609,9 +566,6 @@ class MetricsService scoped_refptr<chromeos::ExternalMetrics> external_metrics_; #endif - // The last entropy source returned by this service, used for testing. - EntropySourceReturned entropy_source_returned_; - // Stores the time of the first call to |GetUptimes()|. base::TimeTicks first_updated_time_; @@ -628,19 +582,10 @@ class MetricsService // Field trial groups that map to Chrome configuration states. SyntheticTrialGroups synthetic_trial_groups_; - scoped_ptr<metrics::ClonedInstallDetector> cloned_install_detector_; - - FRIEND_TEST_ALL_PREFIXES(MetricsServiceTest, ClientIdCorrectlyFormatted); FRIEND_TEST_ALL_PREFIXES(MetricsServiceTest, IsPluginProcess); - FRIEND_TEST_ALL_PREFIXES(MetricsServiceTest, LowEntropySource0NotReset); FRIEND_TEST_ALL_PREFIXES(MetricsServiceTest, PermutedEntropyCacheClearedWhenLowEntropyReset); FRIEND_TEST_ALL_PREFIXES(MetricsServiceTest, RegisterSyntheticTrial); - FRIEND_TEST_ALL_PREFIXES(MetricsServiceTest, ResetMetricsIDs); - FRIEND_TEST_ALL_PREFIXES(MetricsServiceBrowserTest, - CheckLowEntropySourceUsed); - FRIEND_TEST_ALL_PREFIXES(MetricsServiceReportingTest, - CheckHighEntropySourceUsed); DISALLOW_COPY_AND_ASSIGN(MetricsService); }; @@ -662,6 +607,9 @@ class MetricsServiceHelper { FRIEND_TEST_ALL_PREFIXES(MetricsServiceTest, CrashReportingEnabled); // Returns true if prefs::kMetricsReportingEnabled is set. + // TODO(asvitkine): Consolidate the method in MetricsStateManager. + // TODO(asvitkine): This function does not report the correct value on + // Android and ChromeOS, see http://crbug.com/362192. static bool IsMetricsReportingEnabled(); // Returns true if crash reporting is enabled. This is set at the platform diff --git a/chrome/browser/metrics/metrics_service_browsertest.cc b/chrome/browser/metrics/metrics_service_browsertest.cc index 666afae..d87f5b7 100644 --- a/chrome/browser/metrics/metrics_service_browsertest.cc +++ b/chrome/browser/metrics/metrics_service_browsertest.cc @@ -5,6 +5,8 @@ // Tests the MetricsService stat recording to make sure that the numbers are // what we expect. +#include "chrome/browser/metrics/metrics_service.h" + #include <string> #include "base/command_line.h" @@ -12,7 +14,6 @@ #include "base/path_service.h" #include "base/prefs/pref_service.h" #include "chrome/browser/browser_process.h" -#include "chrome/browser/metrics/metrics_service.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" #include "chrome/common/chrome_paths.h" @@ -58,14 +59,6 @@ class MetricsServiceBrowserTest : public InProcessBrowserTest { } }; -class MetricsServiceReportingTest : public InProcessBrowserTest { - public: - virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE { - // Enable the metrics service for testing (in the full mode). - command_line->AppendSwitch(switches::kEnableMetricsReportingForTesting); - } -}; - IN_PROC_BROWSER_TEST_F(MetricsServiceBrowserTest, CloseRenderersNormally) { OpenTabs(); @@ -115,20 +108,3 @@ IN_PROC_BROWSER_TEST_F(MetricsServiceBrowserTest, MAYBE_CrashRenderers) { // exits... it's not clear to me how to test that. } -IN_PROC_BROWSER_TEST_F(MetricsServiceBrowserTest, CheckLowEntropySourceUsed) { - // Since MetricsService is only in recording mode, and is not reporting, - // check that the low entropy source is returned at some point. - ASSERT_TRUE(g_browser_process->metrics_service()); - EXPECT_EQ(MetricsService::LAST_ENTROPY_LOW, - g_browser_process->metrics_service()->entropy_source_returned()); -} - -IN_PROC_BROWSER_TEST_F(MetricsServiceReportingTest, - CheckHighEntropySourceUsed) { - // Since the full metrics service runs in this test, we expect that - // MetricsService returns the full entropy source at some point during - // BrowserMain startup. - ASSERT_TRUE(g_browser_process->metrics_service()); - EXPECT_EQ(MetricsService::LAST_ENTROPY_HIGH, - g_browser_process->metrics_service()->entropy_source_returned()); -} diff --git a/chrome/browser/metrics/metrics_service_unittest.cc b/chrome/browser/metrics/metrics_service_unittest.cc index 48f7ec1..b42d789 100644 --- a/chrome/browser/metrics/metrics_service_unittest.cc +++ b/chrome/browser/metrics/metrics_service_unittest.cc @@ -4,7 +4,6 @@ #include "chrome/browser/metrics/metrics_service.h" -#include <ctype.h> #include <string> #include "base/command_line.h" @@ -97,6 +96,13 @@ class MetricsServiceTest : public testing::Test { return testing_local_state_.Get(); } + // Sets metrics reporting as enabled for testing. + void EnableMetricsReporting() { + // TODO(asvitkine): Refactor the code to not need this flag and delete it. + CommandLine::ForCurrentProcess()->AppendSwitch( + switches::kEnableMetricsReportingForTesting); + } + // Waits until base::TimeTicks::Now() no longer equals |value|. This should // take between 1-15ms per the documented resolution of base::TimeTicks. void WaitUntilTimeChanges(const base::TimeTicks& value) { @@ -131,20 +137,6 @@ class MetricsServiceTest : public testing::Test { } // namespace -// Ensure the ClientId is formatted as expected. -TEST_F(MetricsServiceTest, ClientIdCorrectlyFormatted) { - std::string clientid = MetricsService::GenerateClientID(); - EXPECT_EQ(36U, clientid.length()); - - for (size_t i = 0; i < clientid.length(); ++i) { - char current = clientid[i]; - if (i == 8 || i == 13 || i == 18 || i == 23) - EXPECT_EQ('-', current); - else - EXPECT_TRUE(isxdigit(current)); - } -} - TEST_F(MetricsServiceTest, IsPluginProcess) { EXPECT_TRUE( MetricsService::IsPluginProcess(content::PROCESS_TYPE_PLUGIN)); @@ -154,82 +146,19 @@ TEST_F(MetricsServiceTest, IsPluginProcess) { MetricsService::IsPluginProcess(content::PROCESS_TYPE_GPU)); } -TEST_F(MetricsServiceTest, LowEntropySource0NotReset) { - MetricsService service; - - // Get the low entropy source once, to initialize it. - service.GetLowEntropySource(); - - // Now, set it to 0 and ensure it doesn't get reset. - service.low_entropy_source_ = 0; - EXPECT_EQ(0, service.GetLowEntropySource()); - // Call it another time, just to make sure. - EXPECT_EQ(0, service.GetLowEntropySource()); -} - -TEST_F(MetricsServiceTest, PermutedEntropyCacheClearedWhenLowEntropyReset) { - const PrefService::Preference* low_entropy_pref = - GetLocalState()->FindPreference(prefs::kMetricsLowEntropySource); - const char* kCachePrefName = prefs::kMetricsPermutedEntropyCache; - int low_entropy_value = -1; - - // First, generate an initial low entropy source value. - { - EXPECT_TRUE(low_entropy_pref->IsDefaultValue()); - - MetricsService::SetExecutionPhase(MetricsService::UNINITIALIZED_PHASE); - MetricsService service; - service.GetLowEntropySource(); - - EXPECT_FALSE(low_entropy_pref->IsDefaultValue()); - EXPECT_TRUE(low_entropy_pref->GetValue()->GetAsInteger(&low_entropy_value)); - } - - // Now, set a dummy value in the permuted entropy cache pref and verify that - // another call to GetLowEntropySource() doesn't clobber it when - // --reset-variation-state wasn't specified. - { - GetLocalState()->SetString(kCachePrefName, "test"); - - MetricsService::SetExecutionPhase(MetricsService::UNINITIALIZED_PHASE); - MetricsService service; - service.GetLowEntropySource(); - - EXPECT_EQ("test", GetLocalState()->GetString(kCachePrefName)); - EXPECT_EQ(low_entropy_value, - GetLocalState()->GetInteger(prefs::kMetricsLowEntropySource)); - } - - // Verify that the cache does get reset if --reset-variations-state is passed. - { - CommandLine::ForCurrentProcess()->AppendSwitch( - switches::kResetVariationState); - - MetricsService::SetExecutionPhase(MetricsService::UNINITIALIZED_PHASE); - MetricsService service; - service.GetLowEntropySource(); - - EXPECT_TRUE(GetLocalState()->GetString(kCachePrefName).empty()); - } -} - TEST_F(MetricsServiceTest, InitialStabilityLogAfterCleanShutDown) { - base::FieldTrialList field_trial_list(NULL); - base::FieldTrialList::CreateFieldTrial("UMAStability", "SeparateLog"); - + EnableMetricsReporting(); GetLocalState()->SetBoolean(prefs::kStabilityExitedCleanly, true); TestMetricsService service; - service.InitializeMetricsRecordingState(MetricsService::REPORTING_ENABLED); + service.InitializeMetricsRecordingState(); // No initial stability log should be generated. EXPECT_FALSE(service.log_manager()->has_unsent_logs()); EXPECT_FALSE(service.log_manager()->has_staged_log()); } TEST_F(MetricsServiceTest, InitialStabilityLogAfterCrash) { - base::FieldTrialList field_trial_list(NULL); - base::FieldTrialList::CreateFieldTrial("UMAStability", "SeparateLog"); - + EnableMetricsReporting(); GetLocalState()->ClearPref(prefs::kStabilityExitedCleanly); // Set up prefs to simulate restarting after a crash. @@ -251,7 +180,7 @@ TEST_F(MetricsServiceTest, InitialStabilityLogAfterCrash) { GetLocalState()->SetBoolean(prefs::kStabilityExitedCleanly, false); TestMetricsService service; - service.InitializeMetricsRecordingState(MetricsService::REPORTING_ENABLED); + service.InitializeMetricsRecordingState(); // The initial stability log should be generated and persisted in unsent logs. MetricsLogManager* log_manager = service.log_manager(); @@ -375,42 +304,3 @@ TEST_F(MetricsServiceTest, CrashReportingEnabled) { EXPECT_FALSE(MetricsServiceHelper::IsCrashReportingEnabled()); #endif // defined(GOOGLE_CHROME_BUILD) } - -// Check that setting the kMetricsResetIds pref to true causes the client id to -// be reset. We do not check that the low entropy source is reset because we -// cannot ensure that metrics service won't generate the same id again. -TEST_F(MetricsServiceTest, ResetMetricsIDs) { - // Set an initial client id in prefs. It should not be possible for the - // metrics service to generate this id randomly. - const std::string kInitialClientId = "initial client id"; - GetLocalState()->SetString(prefs::kMetricsClientID, kInitialClientId); - - // Make sure the initial client id isn't reset by the metrics service. - { - MetricsService service; - service.ForceClientIdCreation(); - EXPECT_TRUE(service.metrics_ids_reset_check_performed_); - EXPECT_EQ(kInitialClientId, service.client_id_); - } - - - // Set the reset pref to cause the IDs to be reset. - GetLocalState()->SetBoolean(prefs::kMetricsResetIds, true); - - // Cause the actual reset to happen. - { - MetricsService service; - service.ForceClientIdCreation(); - EXPECT_TRUE(service.metrics_ids_reset_check_performed_); - EXPECT_NE(kInitialClientId, service.client_id_); - - service.GetLowEntropySource(); - - EXPECT_FALSE(GetLocalState()->GetBoolean(prefs::kMetricsResetIds)); - } - - std::string new_client_id = - GetLocalState()->GetString(prefs::kMetricsClientID); - - EXPECT_NE(kInitialClientId, new_client_id); -} diff --git a/chrome/browser/metrics/metrics_state_manager.cc b/chrome/browser/metrics/metrics_state_manager.cc new file mode 100644 index 0000000..e72e6b7 --- /dev/null +++ b/chrome/browser/metrics/metrics_state_manager.cc @@ -0,0 +1,233 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/metrics/metrics_state_manager.h" + +#include "base/command_line.h" +#include "base/guid.h" +#include "base/metrics/histogram.h" +#include "base/metrics/sparse_histogram.h" +#include "base/prefs/pref_registry_simple.h" +#include "base/prefs/pref_service.h" +#include "base/rand_util.h" +#include "base/strings/string_number_conversions.h" +#include "base/time/time.h" +#include "chrome/browser/metrics/cloned_install_detector.h" +#include "chrome/browser/metrics/machine_id_provider.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/metrics/caching_permuted_entropy_provider.h" +#include "chrome/common/pref_names.h" + +#if defined(OS_CHROMEOS) +#include "chrome/browser/chromeos/settings/cros_settings.h" +#endif + +namespace metrics { + +namespace { + +// The argument used to generate a non-identifying entropy source. We want no +// more than 13 bits of entropy, so use this max to return a number in the range +// [0, 7999] as the entropy source (12.97 bits of entropy). +const int kMaxLowEntropySize = 8000; + +// Default prefs value for prefs::kMetricsLowEntropySource to indicate that the +// value has not yet been set. +const int kLowEntropySourceNotSet = -1; + +// Generates a new non-identifying entropy source used to seed persistent +// activities. +int GenerateLowEntropySource() { + return base::RandInt(0, kMaxLowEntropySize - 1); +} + +} // namespace + +// static +bool MetricsStateManager::instance_exists_ = false; + +MetricsStateManager::MetricsStateManager(PrefService* local_state) + : local_state_(local_state), + low_entropy_source_(kLowEntropySourceNotSet), + entropy_source_returned_(ENTROPY_SOURCE_NONE) { + ResetMetricsIDsIfNecessary(); + if (IsMetricsReportingEnabled()) + ForceClientIdCreation(); + + DCHECK(!instance_exists_); + instance_exists_ = true; +} + +MetricsStateManager::~MetricsStateManager() { + DCHECK(instance_exists_); + instance_exists_ = false; +} + +bool MetricsStateManager::IsMetricsReportingEnabled() { + // If the user permits metrics reporting with the checkbox in the + // prefs, we turn on recording. We disable metrics completely for + // non-official builds. This can be forced with a flag. + const CommandLine* command_line = CommandLine::ForCurrentProcess(); + if (command_line->HasSwitch(switches::kEnableMetricsReportingForTesting)) + return true; + + // Disable metrics reporting when field trials are forced. + if (command_line->HasSwitch(switches::kForceFieldTrials)) + return false; + + bool enabled = false; +#if defined(GOOGLE_CHROME_BUILD) +#if defined(OS_CHROMEOS) + chromeos::CrosSettings::Get()->GetBoolean(chromeos::kStatsReportingPref, + &enabled); +#else + enabled = local_state->GetBoolean(prefs::kMetricsReportingEnabled); +#endif // #if defined(OS_CHROMEOS) +#endif // defined(GOOGLE_CHROME_BUILD) + return enabled; +} + +void MetricsStateManager::ForceClientIdCreation() { + if (!client_id_.empty()) + return; + + client_id_ = local_state_->GetString(prefs::kMetricsClientID); + if (!client_id_.empty()) + return; + + client_id_ = base::GenerateGUID(); + local_state_->SetString(prefs::kMetricsClientID, client_id_); + + if (local_state_->GetString(prefs::kMetricsOldClientID).empty()) { + // Record the timestamp of when the user opted in to UMA. + local_state_->SetInt64(prefs::kMetricsReportingEnabledTimestamp, + base::Time::Now().ToTimeT()); + } else { + UMA_HISTOGRAM_BOOLEAN("UMA.ClientIdMigrated", true); + } + local_state_->ClearPref(prefs::kMetricsOldClientID); +} + +void MetricsStateManager::CheckForClonedInstall() { + DCHECK(!cloned_install_detector_); + + MachineIdProvider* provider = MachineIdProvider::CreateInstance(); + if (!provider) + return; + + cloned_install_detector_.reset(new ClonedInstallDetector(provider)); + cloned_install_detector_->CheckForClonedInstall(local_state_); +} + +scoped_ptr<const base::FieldTrial::EntropyProvider> +MetricsStateManager::CreateEntropyProvider() { + // For metrics reporting-enabled users, we combine the client ID and low + // entropy source to get the final entropy source. Otherwise, only use the low + // entropy source. + // This has two useful properties: + // 1) It makes the entropy source less identifiable for parties that do not + // know the low entropy source. + // 2) It makes the final entropy source resettable. + const int low_entropy_source_value = GetLowEntropySource(); + UMA_HISTOGRAM_SPARSE_SLOWLY("UMA.LowEntropySourceValue", + low_entropy_source_value); + if (IsMetricsReportingEnabled()) { + if (entropy_source_returned_ == ENTROPY_SOURCE_NONE) + entropy_source_returned_ = ENTROPY_SOURCE_HIGH; + DCHECK_EQ(ENTROPY_SOURCE_HIGH, entropy_source_returned_); + const std::string high_entropy_source = + client_id_ + base::IntToString(low_entropy_source_value); + return scoped_ptr<const base::FieldTrial::EntropyProvider>( + new SHA1EntropyProvider(high_entropy_source)); + } + + if (entropy_source_returned_ == ENTROPY_SOURCE_NONE) + entropy_source_returned_ = ENTROPY_SOURCE_LOW; + DCHECK_EQ(ENTROPY_SOURCE_LOW, entropy_source_returned_); + +#if defined(OS_ANDROID) || defined(OS_IOS) + return scoped_ptr<const base::FieldTrial::EntropyProvider>( + new CachingPermutedEntropyProvider(local_state_, + low_entropy_source_value, + kMaxLowEntropySize)); +#else + return scoped_ptr<const base::FieldTrial::EntropyProvider>( + new PermutedEntropyProvider(low_entropy_source_value, + kMaxLowEntropySize)); +#endif +} + +// static +scoped_ptr<MetricsStateManager> MetricsStateManager::Create( + PrefService* local_state) { + scoped_ptr<MetricsStateManager> result; + // Note: |instance_exists_| is updated in the constructor and destructor. + if (!instance_exists_) + result.reset(new MetricsStateManager(local_state)); + return result.Pass(); +} + +// static +void MetricsStateManager::RegisterPrefs(PrefRegistrySimple* registry) { + registry->RegisterBooleanPref(prefs::kMetricsResetIds, false); + registry->RegisterStringPref(prefs::kMetricsClientID, std::string()); + registry->RegisterInt64Pref(prefs::kMetricsReportingEnabledTimestamp, 0); + registry->RegisterIntegerPref(prefs::kMetricsLowEntropySource, + kLowEntropySourceNotSet); + + ClonedInstallDetector::RegisterPrefs(registry); + CachingPermutedEntropyProvider::RegisterPrefs(registry); + + // TODO(asvitkine): Remove these once a couple of releases have passed. + // http://crbug.com/357704 + registry->RegisterStringPref(prefs::kMetricsOldClientID, std::string()); + registry->RegisterIntegerPref(prefs::kMetricsOldLowEntropySource, 0); +} + +int MetricsStateManager::GetLowEntropySource() { + // Note that the default value for the low entropy source and the default pref + // value are both kLowEntropySourceNotSet, which is used to identify if the + // value has been set or not. + if (low_entropy_source_ != kLowEntropySourceNotSet) + return low_entropy_source_; + + const CommandLine* command_line(CommandLine::ForCurrentProcess()); + // Only try to load the value from prefs if the user did not request a reset. + // Otherwise, skip to generating a new value. + if (!command_line->HasSwitch(switches::kResetVariationState)) { + int value = local_state_->GetInteger(prefs::kMetricsLowEntropySource); + // If the value is outside the [0, kMaxLowEntropySize) range, re-generate + // it below. + if (value >= 0 && value < kMaxLowEntropySize) { + low_entropy_source_ = value; + UMA_HISTOGRAM_BOOLEAN("UMA.GeneratedLowEntropySource", false); + return low_entropy_source_; + } + } + + UMA_HISTOGRAM_BOOLEAN("UMA.GeneratedLowEntropySource", true); + low_entropy_source_ = GenerateLowEntropySource(); + local_state_->SetInteger(prefs::kMetricsLowEntropySource, + low_entropy_source_); + local_state_->ClearPref(prefs::kMetricsOldLowEntropySource); + metrics::CachingPermutedEntropyProvider::ClearCache(local_state_); + + return low_entropy_source_; +} + +void MetricsStateManager::ResetMetricsIDsIfNecessary() { + if (!local_state_->GetBoolean(prefs::kMetricsResetIds)) + return; + + UMA_HISTOGRAM_BOOLEAN("UMA.MetricsIDsReset", true); + + DCHECK(client_id_.empty()); + DCHECK_EQ(kLowEntropySourceNotSet, low_entropy_source_); + + local_state_->ClearPref(prefs::kMetricsClientID); + local_state_->ClearPref(prefs::kMetricsLowEntropySource); + local_state_->ClearPref(prefs::kMetricsResetIds); +} + +} // namespace metrics diff --git a/chrome/browser/metrics/metrics_state_manager.h b/chrome/browser/metrics/metrics_state_manager.h new file mode 100644 index 0000000..5b2f954 --- /dev/null +++ b/chrome/browser/metrics/metrics_state_manager.h @@ -0,0 +1,124 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_METRICS_METRICS_STATE_MANAGER_H_ +#define CHROME_BROWSER_METRICS_METRICS_STATE_MANAGER_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/gtest_prod_util.h" +#include "base/memory/scoped_ptr.h" +#include "base/metrics/field_trial.h" + +class PrefService; +class PrefRegistrySimple; + +namespace metrics { + +class ClonedInstallDetector; + +// Responsible for managing MetricsService state prefs, specifically the UMA +// client id and low entropy source. Code outside the metrics directory should +// not be instantiating or using this class directly. +class MetricsStateManager { + public: + virtual ~MetricsStateManager(); + + // Returns true if the user opted in to sending metric reports. + // TODO(asvitkine): This function does not report the correct value on + // Android, see http://crbug.com/362192. + bool IsMetricsReportingEnabled(); + + // Returns the client ID for this client, or the empty string if the user is + // not opted in to metrics reporting. + const std::string& client_id() const { return client_id_; } + + // Forces the client ID to be generated. This is useful in case it's needed + // before recording. + void ForceClientIdCreation(); + + // Checks if this install was cloned or imaged from another machine. If a + // clone is detected, resets the client id and low entropy source. This + // should not be called more than once. + void CheckForClonedInstall(); + + // Returns the preferred entropy provider used to seed persistent activities + // based on whether or not metrics reporting is permitted on this client. + // + // If metrics reporting is enabled, this method returns an entropy provider + // that has a high source of entropy, partially based on the client ID. + // Otherwise, it returns an entropy provider that is based on a low entropy + // source. + scoped_ptr<const base::FieldTrial::EntropyProvider> CreateEntropyProvider(); + + // Creates the MetricsStateManager, enforcing that only a single instance + // of the class exists at a time. Returns NULL if an instance exists already. + static scoped_ptr<MetricsStateManager> Create(PrefService* local_state); + + // Registers local state prefs used by this class. + static void RegisterPrefs(PrefRegistrySimple* registry); + + private: + FRIEND_TEST_ALL_PREFIXES(MetricsStateManagerTest, EntropySourceUsed_Low); + FRIEND_TEST_ALL_PREFIXES(MetricsStateManagerTest, EntropySourceUsed_High); + FRIEND_TEST_ALL_PREFIXES(MetricsStateManagerTest, LowEntropySource0NotReset); + FRIEND_TEST_ALL_PREFIXES(MetricsStateManagerTest, + PermutedEntropyCacheClearedWhenLowEntropyReset); + FRIEND_TEST_ALL_PREFIXES(MetricsStateManagerTest, ResetMetricsIDs); + + // Designates which entropy source was returned from this class. + // This is used for testing to validate that we return the correct source + // depending on the state of the service. + enum EntropySourceType { + ENTROPY_SOURCE_NONE, + ENTROPY_SOURCE_LOW, + ENTROPY_SOURCE_HIGH, + }; + + // Creates the MetricsStateManager with the given |local_state|. Clients + // should instead use Create(), which enforces a single instance of this class + // is alive at any given time. + explicit MetricsStateManager(PrefService* local_state); + + // Returns the low entropy source for this client. This is a random value + // that is non-identifying amongst browser clients. This method will + // generate the entropy source value if it has not been called before. + int GetLowEntropySource(); + + // Returns the first entropy source that was returned by this service since + // start up, or NONE if neither was returned yet. This is exposed for testing + // only. + EntropySourceType entropy_source_returned() const { + return entropy_source_returned_; + } + + // Reset the client id and low entropy source if the kMetricsResetMetricIDs + // pref is true. + void ResetMetricsIDsIfNecessary(); + + // Whether an instance of this class exists. Used to enforce that there aren't + // multiple instances of this class at a given time. + static bool instance_exists_; + + // Weak pointer to the local state prefs store. + PrefService* local_state_; + + // The identifier that's sent to the server with the log reports. + std::string client_id_; + + // The non-identifying low entropy source value. + int low_entropy_source_; + + // The last entropy source returned by this service, used for testing. + EntropySourceType entropy_source_returned_; + + scoped_ptr<ClonedInstallDetector> cloned_install_detector_; + + DISALLOW_COPY_AND_ASSIGN(MetricsStateManager); +}; + +} // namespace metrics + +#endif // CHROME_BROWSER_METRICS_METRICS_STATE_MANAGER_H_ diff --git a/chrome/browser/metrics/metrics_state_manager_unittest.cc b/chrome/browser/metrics/metrics_state_manager_unittest.cc new file mode 100644 index 0000000..bad4b77 --- /dev/null +++ b/chrome/browser/metrics/metrics_state_manager_unittest.cc @@ -0,0 +1,160 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/metrics/metrics_state_manager.h" + +#include <ctype.h> +#include <string> + +#include "base/command_line.h" +#include "base/prefs/testing_pref_service.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/metrics/caching_permuted_entropy_provider.h" +#include "chrome/common/pref_names.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace metrics { + +class MetricsStateManagerTest : public testing::Test { + public: + MetricsStateManagerTest() { + MetricsStateManager::RegisterPrefs(prefs_.registry()); + } + + scoped_ptr<MetricsStateManager> CreateStateManager() { + return MetricsStateManager::Create(&prefs_).Pass(); + } + + protected: + TestingPrefServiceSimple prefs_; + + private: + DISALLOW_COPY_AND_ASSIGN(MetricsStateManagerTest); +}; + +// Ensure the ClientId is formatted as expected. +TEST_F(MetricsStateManagerTest, ClientIdCorrectlyFormatted) { + scoped_ptr<MetricsStateManager> state_manager(CreateStateManager()); + state_manager->ForceClientIdCreation(); + + const std::string client_id = state_manager->client_id(); + EXPECT_EQ(36U, client_id.length()); + + for (size_t i = 0; i < client_id.length(); ++i) { + char current = client_id[i]; + if (i == 8 || i == 13 || i == 18 || i == 23) + EXPECT_EQ('-', current); + else + EXPECT_TRUE(isxdigit(current)); + } +} + +TEST_F(MetricsStateManagerTest, EntropySourceUsed_Low) { + scoped_ptr<MetricsStateManager> state_manager(CreateStateManager()); + state_manager->CreateEntropyProvider(); + EXPECT_EQ(MetricsStateManager::ENTROPY_SOURCE_LOW, + state_manager->entropy_source_returned()); +} + +TEST_F(MetricsStateManagerTest, EntropySourceUsed_High) { + CommandLine::ForCurrentProcess()->AppendSwitch( + switches::kEnableMetricsReportingForTesting); + + scoped_ptr<MetricsStateManager> state_manager(CreateStateManager()); + state_manager->CreateEntropyProvider(); + EXPECT_EQ(MetricsStateManager::ENTROPY_SOURCE_HIGH, + state_manager->entropy_source_returned()); +} + +TEST_F(MetricsStateManagerTest, LowEntropySource0NotReset) { + scoped_ptr<MetricsStateManager> state_manager(CreateStateManager()); + + // Get the low entropy source once, to initialize it. + state_manager->GetLowEntropySource(); + + // Now, set it to 0 and ensure it doesn't get reset. + state_manager->low_entropy_source_ = 0; + EXPECT_EQ(0, state_manager->GetLowEntropySource()); + // Call it another time, just to make sure. + EXPECT_EQ(0, state_manager->GetLowEntropySource()); +} + +TEST_F(MetricsStateManagerTest, + PermutedEntropyCacheClearedWhenLowEntropyReset) { + const PrefService::Preference* low_entropy_pref = + prefs_.FindPreference(prefs::kMetricsLowEntropySource); + const char* kCachePrefName = prefs::kMetricsPermutedEntropyCache; + int low_entropy_value = -1; + + // First, generate an initial low entropy source value. + { + EXPECT_TRUE(low_entropy_pref->IsDefaultValue()); + + scoped_ptr<MetricsStateManager> state_manager(CreateStateManager()); + state_manager->GetLowEntropySource(); + + EXPECT_FALSE(low_entropy_pref->IsDefaultValue()); + EXPECT_TRUE(low_entropy_pref->GetValue()->GetAsInteger(&low_entropy_value)); + } + + // Now, set a dummy value in the permuted entropy cache pref and verify that + // another call to GetLowEntropySource() doesn't clobber it when + // --reset-variation-state wasn't specified. + { + prefs_.SetString(kCachePrefName, "test"); + + scoped_ptr<MetricsStateManager> state_manager(CreateStateManager()); + state_manager->GetLowEntropySource(); + + EXPECT_EQ("test", prefs_.GetString(kCachePrefName)); + EXPECT_EQ(low_entropy_value, + prefs_.GetInteger(prefs::kMetricsLowEntropySource)); + } + + // Verify that the cache does get reset if --reset-variations-state is passed. + { + CommandLine::ForCurrentProcess()->AppendSwitch( + switches::kResetVariationState); + + scoped_ptr<MetricsStateManager> state_manager(CreateStateManager()); + state_manager->GetLowEntropySource(); + + EXPECT_TRUE(prefs_.GetString(kCachePrefName).empty()); + } +} + +// Check that setting the kMetricsResetIds pref to true causes the client id to +// be reset. We do not check that the low entropy source is reset because we +// cannot ensure that metrics state manager won't generate the same id again. +TEST_F(MetricsStateManagerTest, ResetMetricsIDs) { + // Set an initial client id in prefs. It should not be possible for the + // metrics state manager to generate this id randomly. + const std::string kInitialClientId = "initial client id"; + prefs_.SetString(prefs::kMetricsClientID, kInitialClientId); + + // Make sure the initial client id isn't reset by the metrics state manager. + { + scoped_ptr<MetricsStateManager> state_manager(CreateStateManager()); + state_manager->ForceClientIdCreation(); + EXPECT_EQ(kInitialClientId, state_manager->client_id()); + } + + // Set the reset pref to cause the IDs to be reset. + prefs_.SetBoolean(prefs::kMetricsResetIds, true); + + // Cause the actual reset to happen. + { + scoped_ptr<MetricsStateManager> state_manager(CreateStateManager()); + state_manager->ForceClientIdCreation(); + EXPECT_NE(kInitialClientId, state_manager->client_id()); + + state_manager->GetLowEntropySource(); + + EXPECT_FALSE(prefs_.GetBoolean(prefs::kMetricsResetIds)); + } + + EXPECT_NE(kInitialClientId, prefs_.GetString(prefs::kMetricsClientID)); +} + +} // namespace metrics diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc index e256661..28fa463 100644 --- a/chrome/browser/prefs/browser_prefs.cc +++ b/chrome/browser/prefs/browser_prefs.cc @@ -37,7 +37,6 @@ #include "chrome/browser/media/media_capture_devices_dispatcher.h" #include "chrome/browser/media/media_device_id_salt.h" #include "chrome/browser/media/media_stream_devices_controller.h" -#include "chrome/browser/metrics/cloned_install_detector.h" #include "chrome/browser/metrics/metrics_log.h" #include "chrome/browser/metrics/metrics_service.h" #include "chrome/browser/metrics/variations/variations_service.h" @@ -87,7 +86,6 @@ #include "chrome/browser/ui/webui/print_preview/sticky_settings.h" #include "chrome/browser/upgrade_detector.h" #include "chrome/browser/web_resource/promo_resource_service.h" -#include "chrome/common/metrics/caching_permuted_entropy_provider.h" #include "chrome/common/pref_names.h" #include "components/autofill/core/browser/autofill_manager.h" #include "components/bookmarks/core/browser/bookmark_utils.h" @@ -235,8 +233,6 @@ void RegisterLocalState(PrefRegistrySimple* registry) { KeywordEditorController::RegisterPrefs(registry); MetricsLog::RegisterPrefs(registry); MetricsService::RegisterPrefs(registry); - metrics::CachingPermutedEntropyProvider::RegisterPrefs(registry); - metrics::ClonedInstallDetector::RegisterPrefs(registry); PrefProxyConfigTrackerImpl::RegisterPrefs(registry); ProfileInfoCache::RegisterPrefs(registry); profiles::RegisterPrefs(registry); diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index 3004f9f..debae30 100644 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -1215,6 +1215,8 @@ 'browser/metrics/metrics_service_android.cc', 'browser/metrics/metrics_service.cc', 'browser/metrics/metrics_service.h', + 'browser/metrics/metrics_state_manager.cc', + 'browser/metrics/metrics_state_manager.h', 'browser/metrics/perf_provider_chromeos.cc', 'browser/metrics/perf_provider_chromeos.h', 'browser/metrics/thread_watcher.cc', diff --git a/chrome/chrome_tests_unit.gypi b/chrome/chrome_tests_unit.gypi index 8b98fa2..680772a 100644 --- a/chrome/chrome_tests_unit.gypi +++ b/chrome/chrome_tests_unit.gypi @@ -1084,6 +1084,7 @@ 'browser/metrics/metrics_log_serializer_unittest.cc', 'browser/metrics/metrics_reporting_scheduler_unittest.cc', 'browser/metrics/metrics_service_unittest.cc', + 'browser/metrics/metrics_state_manager_unittest.cc', 'browser/metrics/thread_watcher_unittest.cc', 'browser/metrics/thread_watcher_android_unittest.cc', 'browser/metrics/time_ticks_experiment_unittest.cc', |