diff options
-rw-r--r-- | base/metrics/field_trial.cc | 10 | ||||
-rw-r--r-- | base/metrics/field_trial.h | 4 | ||||
-rw-r--r-- | chrome/browser/metrics/field_trial_synchronizer.cc | 4 | ||||
-rw-r--r-- | chrome/chrome_tests.gypi | 1 | ||||
-rw-r--r-- | chrome/common/metrics/experiments_helper.cc | 72 | ||||
-rw-r--r-- | chrome/common/metrics/experiments_helper.h | 66 | ||||
-rw-r--r-- | chrome/common/metrics/experiments_helper_unittest.cc | 144 | ||||
-rw-r--r-- | chrome/renderer/chrome_render_process_observer.cc | 4 |
8 files changed, 296 insertions, 9 deletions
diff --git a/base/metrics/field_trial.cc b/base/metrics/field_trial.cc index e04c86e..e53fdf3 100644 --- a/base/metrics/field_trial.cc +++ b/base/metrics/field_trial.cc @@ -168,6 +168,16 @@ std::string FieldTrial::MakeName(const std::string& name_prefix, } // static +FieldTrial::NameGroupId FieldTrial::MakeNameGroupId( + const std::string& trial_name, + const std::string& group_name) { + NameGroupId id; + id.name = HashName(trial_name); + id.group = HashName(group_name); + return id; +} + +// static void FieldTrial::EnableBenchmarking() { DCHECK_EQ(0u, FieldTrialList::GetFieldTrialCount()); enable_benchmarking_ = true; diff --git a/base/metrics/field_trial.h b/base/metrics/field_trial.h index 2852393..be58a93 100644 --- a/base/metrics/field_trial.h +++ b/base/metrics/field_trial.h @@ -146,6 +146,10 @@ class BASE_EXPORT FieldTrial : public RefCounted<FieldTrial> { static std::string MakeName(const std::string& name_prefix, const std::string& trial_name); + // Helper function to create a NameGroupId from |trial_name| and |group_name|. + static NameGroupId MakeNameGroupId(const std::string& trial_name, + const std::string& group_name); + // Enable benchmarking sets field trials to a common setting. static void EnableBenchmarking(); diff --git a/chrome/browser/metrics/field_trial_synchronizer.cc b/chrome/browser/metrics/field_trial_synchronizer.cc index 09f8f07..a6ad4c5 100644 --- a/chrome/browser/metrics/field_trial_synchronizer.cc +++ b/chrome/browser/metrics/field_trial_synchronizer.cc @@ -20,7 +20,7 @@ FieldTrialSynchronizer::FieldTrialSynchronizer() { field_trial_synchronizer_ = this; base::FieldTrialList::AddObserver(this); - ExperimentsHelper::SetChildProcessLoggingExperimentList(); + experiments_helper::SetChildProcessLoggingExperimentList(); } FieldTrialSynchronizer::~FieldTrialSynchronizer() { @@ -52,7 +52,7 @@ void FieldTrialSynchronizer::OnFieldTrialGroupFinalized( this, field_trial_name, group_name)); - ExperimentsHelper::SetChildProcessLoggingExperimentList(); + experiments_helper::SetChildProcessLoggingExperimentList(); } // static diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index 4f9e128..017e43a 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -2005,6 +2005,7 @@ 'common/mac/mock_launchd.h', 'common/mac/objc_method_swizzle_unittest.mm', 'common/mac/objc_zombie_unittest.mm', + 'common/metrics/experiments_helper_unittest.cc', 'common/metrics/metrics_log_base_unittest.cc', 'common/metrics/metrics_log_manager_unittest.cc', 'common/multi_process_lock_unittest.cc', diff --git a/chrome/common/metrics/experiments_helper.cc b/chrome/common/metrics/experiments_helper.cc index 09e41cc..11c7c13 100644 --- a/chrome/common/metrics/experiments_helper.cc +++ b/chrome/common/metrics/experiments_helper.cc @@ -6,13 +6,79 @@ #include <vector> -#include "base/metrics/field_trial.h" +#include "base/memory/singleton.h" #include "base/string16.h" #include "base/stringprintf.h" #include "base/utf_string_conversions.h" #include "chrome/common/child_process_logging.h" -namespace ExperimentsHelper { +namespace { + +// We need to pass a Compare class to the std::map template since NameGroupId +// is a user-defined type. +struct NameGroupIdCompare { + bool operator() (const base::FieldTrial::NameGroupId& lhs, + const base::FieldTrial::NameGroupId& rhs) const { + // The group and name fields are just SHA-1 Hashes, so we just need to treat + // them as IDs and do a less-than comparison. We test group first, since + // name is more likely to collide. + return lhs.group == rhs.group ? lhs.name < rhs.name : + lhs.group < rhs.group; + } +}; + +// The internal singleton accessor for the map, used to keep it thread-safe. +class GroupMapAccessor { + public: + // Retrieve the singleton. + static GroupMapAccessor* GetInstance() { + return Singleton<GroupMapAccessor>::get(); + } + + GroupMapAccessor() {} + ~GroupMapAccessor() {} + + void AssociateID(const base::FieldTrial::NameGroupId& group_identifier, + experiments_helper::GoogleExperimentID id) { + base::AutoLock scoped_lock(lock_); + DCHECK(group_to_id_map_.find(group_identifier) == group_to_id_map_.end()) << + "You can associate a group with a GoogleExperimentID only once."; + group_to_id_map_[group_identifier] = id; + } + + experiments_helper::GoogleExperimentID GetID( + const base::FieldTrial::NameGroupId& group_identifier) { + base::AutoLock scoped_lock(lock_); + GroupToIDMap::const_iterator it = group_to_id_map_.find(group_identifier); + if (it == group_to_id_map_.end()) + return experiments_helper::kEmptyGoogleExperimentID; + return it->second; + } + + private: + typedef std::map<base::FieldTrial::NameGroupId, + experiments_helper::GoogleExperimentID, NameGroupIdCompare> GroupToIDMap; + + base::Lock lock_; + GroupToIDMap group_to_id_map_; +}; + +} // namespace + +namespace experiments_helper { + +const GoogleExperimentID kEmptyGoogleExperimentID = 0; + +void AssociateGoogleExperimentID( + const base::FieldTrial::NameGroupId& group_identifier, + GoogleExperimentID id) { + GroupMapAccessor::GetInstance()->AssociateID(group_identifier, id); +} + +GoogleExperimentID GetGoogleExperimentID( + const base::FieldTrial::NameGroupId& group_identifier) { + return GroupMapAccessor::GetInstance()->GetID(group_identifier); +} void SetChildProcessLoggingExperimentList() { std::vector<base::FieldTrial::NameGroupId> name_group_ids; @@ -26,4 +92,4 @@ void SetChildProcessLoggingExperimentList() { child_process_logging::SetExperimentList(experiment_strings); } -} // namespace ExperimentsHelper +} // namespace experiments_helper diff --git a/chrome/common/metrics/experiments_helper.h b/chrome/common/metrics/experiments_helper.h index 630319e..102e635 100644 --- a/chrome/common/metrics/experiments_helper.h +++ b/chrome/common/metrics/experiments_helper.h @@ -6,12 +6,74 @@ #define CHROME_COMMON_METRICS_EXPERIMENTS_HELPER_H_ #pragma once -namespace ExperimentsHelper { +#include "base/metrics/field_trial.h" + +// This namespace provides various helpers that extend the functionality around +// base::FieldTrial. +// +// This includes a simple API used to handle getting and setting +// data related to Google-specific experiments in the browser. This is meant to +// be an extension to the base::FieldTrial for Google-specific functionality. +// +// These calls are meant to be made directly after appending all your groups to +// a FieldTrial (for associating IDs) and any time after the group selection has +// been done (for retrieving IDs). +// +// Typical usage looks like this: +// +// // Set up your trial and groups as usual. +// FieldTrial* trial = FieldTrialList::FactoryGetFieldTrial( +// "trial", 1000, "default", 2012, 12, 31, NULL); +// const int kHighMemGroup = trial->AppendGroup("HighMem", 20); +// const int kLowMemGroup = trial->AppendGroup("LowMem", 20); +// // All groups are now created. We want to associate GoogleExperimentIDs with +// // them, so do that now. +// AssociateGoogleExperimentID( +// FieldTrial::MakeNameGroupId("trial", "default"), 123); +// AssociateGoogleExperimentID( +// FieldTrial::MakeNameGroupId("trial", "HighMem"), 456); +// AssociateGoogleExperimentID( +// FieldTrial::MakeNameGroupId("trial", "LowMem"), 789); +// +// // Elsewhere, we are interested in retrieving the GoogleExperimentID +// // assocaited with |trial|. +// GoogleExperimentID id = GetGoogleExperimentID( +// FieldTrial::MakeNameGroupId(trial->name(), trial->group_name())); +// // Do stuff with |id|... +// +// The AssociateGoogleExperimentID and GetGoogleExperimentID API methods are +// thread safe. +namespace experiments_helper { + +// An ID used by Google servers to identify a local browser experiment. +typedef uint32 GoogleExperimentID; + +// Used to represent no associated Google experiment ID. Calls to the +// GetGoogleExperimentID API below will return this empty value for FieldTrial +// groups uninterested in associating themselves with Google experiments, or +// those that have not yet been seen yet. +extern const GoogleExperimentID kEmptyGoogleExperimentID; + +// Set the GoogleExperimentID associated with a FieldTrial group. The group is +// denoted by |group_identifier|, which can be created by passing the +// FieldTrial's trial and group names to base::FieldTrial::MakeNameGroupId. +// This does not need to be called for FieldTrials uninterested in Google +// experiments. +void AssociateGoogleExperimentID( + const base::FieldTrial::NameGroupId& group_identifier, + GoogleExperimentID id); + +// Retrieve the GoogleExperimentID associated with a FieldTrial group. The group +// is denoted by |group_identifier| (see comment above). This can be nicely +// combined with FieldTrial::GetFieldTrialNameGroupIds to enumerate the +// GoogleExperimentIDs for all active FieldTrial groups. +GoogleExperimentID GetGoogleExperimentID( + const base::FieldTrial::NameGroupId& group_identifier); // Get the current set of chosen FieldTrial groups (aka experiments) and send // them to the child process logging module so it can save it for crash dumps. void SetChildProcessLoggingExperimentList(); -} // namespace ExperimentsHelper +} // namespace experiments_helper #endif // CHROME_COMMON_METRICS_EXPERIMENTS_HELPER_H_ diff --git a/chrome/common/metrics/experiments_helper_unittest.cc b/chrome/common/metrics/experiments_helper_unittest.cc new file mode 100644 index 0000000..546cce8 --- /dev/null +++ b/chrome/common/metrics/experiments_helper_unittest.cc @@ -0,0 +1,144 @@ +// Copyright (c) 2012 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. + +// Tests for the Experiment Helpers. + +#include "base/memory/scoped_ptr.h" +#include "base/message_loop.h" +#include "base/metrics/field_trial.h" +#include "base/time.h" +#include "chrome/common/metrics/experiments_helper.h" +#include "content/test/test_browser_thread.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +// Convenience helper to retrieve the GoogleExperimentID for a FieldTrial. Note +// that this will do the group assignment in |trial| if not already done. +experiments_helper::GoogleExperimentID GetIDForTrial(base::FieldTrial* trial) { + return experiments_helper::GetGoogleExperimentID( + base::FieldTrial::MakeNameGroupId(trial->name(), + trial->group_name())); +} + +} // namespace + +class ExperimentsHelperTest : public ::testing::Test { + public: + ExperimentsHelperTest() { + // Since the API can only be called on the UI thread, we have to fake that + // we're on it. + ui_thread_.reset(new content::TestBrowserThread( + content::BrowserThread::UI, &message_loop_)); + + base::Time now = base::Time::NowFromSystemTime(); + base::TimeDelta oneYear = base::TimeDelta::FromDays(365); + base::Time::Exploded exploded; + + base::Time next_year_time = now + oneYear; + next_year_time.LocalExplode(&exploded); + next_year_ = exploded.year; + } + + protected: + int next_year_; + + private: + MessageLoop message_loop_; + scoped_ptr<content::TestBrowserThread> ui_thread_; +}; + +// Test that if the trial is immediately disabled, GetGoogleExperimentID just +// returns the empty ID. +TEST_F(ExperimentsHelperTest, DisableImmediately) { + int default_group_number = -1; + scoped_refptr<base::FieldTrial> trial( + base::FieldTrialList::FactoryGetFieldTrial("trial", 100, "default", + next_year_, 12, 12, + &default_group_number)); + trial->Disable(); + + ASSERT_EQ(default_group_number, trial->group()); + ASSERT_EQ(experiments_helper::kEmptyGoogleExperimentID, + GetIDForTrial(trial.get())); +} + +// Test that successfully associating the FieldTrial with some ID, and then +// disabling the FieldTrial actually makes GetGoogleExperimentID correctly +// return the empty ID. +TEST_F(ExperimentsHelperTest, DisableAfterInitialization) { + const std::string default_name = "default"; + const std::string non_default_name = "non_default"; + + scoped_refptr<base::FieldTrial> trial( + base::FieldTrialList::FactoryGetFieldTrial("trial", 100, default_name, + next_year_, 12, 12, NULL)); + trial->AppendGroup(non_default_name, 100); + experiments_helper::AssociateGoogleExperimentID( + base::FieldTrial::MakeNameGroupId(trial->name(), default_name), 123); + experiments_helper::AssociateGoogleExperimentID( + base::FieldTrial::MakeNameGroupId(trial->name(), non_default_name), 456); + ASSERT_EQ(non_default_name, trial->group_name()); + ASSERT_EQ(456U, GetIDForTrial(trial.get())); + trial->Disable(); + ASSERT_EQ(default_name, trial->group_name()); + ASSERT_EQ(123U, GetIDForTrial(trial.get())); +} + +// Test various successful association cases. +TEST_F(ExperimentsHelperTest, AssociateGoogleExperimentID) { + const std::string default_name1 = "default1"; + scoped_refptr<base::FieldTrial> trial_true( + base::FieldTrialList::FactoryGetFieldTrial("d1", 10, default_name1, + next_year_, 12, 31, NULL)); + const std::string winner = "TheWinner"; + int winner_group = trial_true->AppendGroup(winner, 10); + + // Set GoogleExperimentIDs so we can verify that they were chosen correctly. + experiments_helper::AssociateGoogleExperimentID( + base::FieldTrial::MakeNameGroupId(trial_true->name(), default_name1), + 123); + experiments_helper::AssociateGoogleExperimentID( + base::FieldTrial::MakeNameGroupId(trial_true->name(), winner), + 456); + + EXPECT_EQ(winner_group, trial_true->group()); + EXPECT_EQ(winner, trial_true->group_name()); + EXPECT_EQ(456U, GetIDForTrial(trial_true.get())); + + const std::string default_name2 = "default2"; + scoped_refptr<base::FieldTrial> trial_false( + base::FieldTrialList::FactoryGetFieldTrial("d2", 10, default_name2, + next_year_, 12, 31, NULL)); + const std::string loser = "ALoser"; + int loser_group = trial_false->AppendGroup(loser, 0); + + experiments_helper::AssociateGoogleExperimentID( + base::FieldTrial::MakeNameGroupId(trial_false->name(), default_name2), + 123); + experiments_helper::AssociateGoogleExperimentID( + base::FieldTrial::MakeNameGroupId(trial_false->name(), loser), + 456); + + EXPECT_NE(loser_group, trial_false->group()); + EXPECT_EQ(123U, GetIDForTrial(trial_false.get())); +} + +// Test that not associating a FieldTrial with any IDs ensure that the empty ID +// will be returned. +TEST_F(ExperimentsHelperTest, NoAssociation) { + const std::string default_name = "default"; + scoped_refptr<base::FieldTrial> no_id_trial( + base::FieldTrialList::FactoryGetFieldTrial("d3", 10, default_name, + next_year_, 12, 31, NULL)); + const std::string winner = "TheWinner"; + int winner_group = no_id_trial->AppendGroup(winner, 10); + + // Ensure that despite the fact that a normal winner is elected, it does not + // have a valid GoogleExperimentID associated with it. + EXPECT_EQ(winner_group, no_id_trial->group()); + EXPECT_EQ(winner, no_id_trial->group_name()); + EXPECT_EQ(experiments_helper::kEmptyGoogleExperimentID, + GetIDForTrial(no_id_trial.get())); +} diff --git a/chrome/renderer/chrome_render_process_observer.cc b/chrome/renderer/chrome_render_process_observer.cc index 833b84bb..70aec65 100644 --- a/chrome/renderer/chrome_render_process_observer.cc +++ b/chrome/renderer/chrome_render_process_observer.cc @@ -198,7 +198,7 @@ ChromeRenderProcessObserver::ChromeRenderProcessObserver( base::LoadNativeLibrary(FilePath(L"crypt32.dll"), &error); #endif // Setup initial set of crash dump data for Field Trials in this renderer. - ExperimentsHelper::SetChildProcessLoggingExperimentList(); + experiments_helper::SetChildProcessLoggingExperimentList(); } ChromeRenderProcessObserver::~ChromeRenderProcessObserver() { @@ -309,7 +309,7 @@ void ChromeRenderProcessObserver::OnSetFieldTrialGroup( const std::string& field_trial_name, const std::string& group_name) { base::FieldTrialList::CreateFieldTrial(field_trial_name, group_name); - ExperimentsHelper::SetChildProcessLoggingExperimentList(); + experiments_helper::SetChildProcessLoggingExperimentList(); } void ChromeRenderProcessObserver::OnGetV8HeapStats() { |