summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--base/metrics/field_trial.cc10
-rw-r--r--base/metrics/field_trial.h4
-rw-r--r--chrome/browser/metrics/field_trial_synchronizer.cc4
-rw-r--r--chrome/chrome_tests.gypi1
-rw-r--r--chrome/common/metrics/experiments_helper.cc72
-rw-r--r--chrome/common/metrics/experiments_helper.h66
-rw-r--r--chrome/common/metrics/experiments_helper_unittest.cc144
-rw-r--r--chrome/renderer/chrome_render_process_observer.cc4
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() {