summaryrefslogtreecommitdiffstats
path: root/chrome/common/metrics
diff options
context:
space:
mode:
authorstevet@chromium.org <stevet@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-04-20 17:02:47 +0000
committerstevet@chromium.org <stevet@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-04-20 17:02:47 +0000
commit7f85791b0e6ce5a7745a26be8adc4cebf0ee409d (patch)
treecd58f8e2753897abc884641dd74db42b82aa9c66 /chrome/common/metrics
parent7c80f42e202478fc3eea8747ad7d4c9bfe2997c5 (diff)
downloadchromium_src-7f85791b0e6ce5a7745a26be8adc4cebf0ee409d.zip
chromium_src-7f85791b0e6ce5a7745a26be8adc4cebf0ee409d.tar.gz
chromium_src-7f85791b0e6ce5a7745a26be8adc4cebf0ee409d.tar.bz2
Add and implement API to associate Google experiment IDs with FieldTrials.
This includes unit tests for the API, but no uses yet. This change also involves exposing a new MakeGroupNameId static helper in FieldTrial to assist with the usage of this new API. BUG=121988 TEST=Ensure that unit_tests GoogleExperimentsTest.* pass. Review URL: http://codereview.chromium.org/9965086 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@133212 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/common/metrics')
-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.cc124
3 files changed, 257 insertions, 5 deletions
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..b225c1d
--- /dev/null
+++ b/chrome/common/metrics/experiments_helper_unittest.cc
@@ -0,0 +1,124 @@
+// 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 "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_));
+ }
+
+ 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;
+ base::FieldTrial* trial = base::FieldTrialList::FactoryGetFieldTrial(
+ "trial", 100, "default", 2199, 12, 12, &default_group_number);
+ trial->Disable();
+
+ ASSERT_EQ(default_group_number, trial->group());
+ ASSERT_EQ(experiments_helper::kEmptyGoogleExperimentID, GetIDForTrial(trial));
+}
+
+// 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";
+
+ base::FieldTrial* trial = base::FieldTrialList::FactoryGetFieldTrial(
+ "trial", 100, default_name, 2199, 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));
+ trial->Disable();
+ ASSERT_EQ(default_name, trial->group_name());
+ ASSERT_EQ(123U, GetIDForTrial(trial));
+}
+
+// Test various successful association cases.
+TEST_F(ExperimentsHelperTest, AssociateGoogleExperimentID) {
+ const std::string default_name1 = "default1";
+ base::FieldTrial* trial_true = base::FieldTrialList::FactoryGetFieldTrial(
+ "d1", 10, default_name1, 2199, 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));
+
+ const std::string default_name2 = "default2";
+ base::FieldTrial* trial_false = base::FieldTrialList::FactoryGetFieldTrial(
+ "d2", 10, default_name2, 2199, 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));
+}
+
+// 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";
+ base::FieldTrial* no_id_trial = base::FieldTrialList::FactoryGetFieldTrial(
+ "d3", 10, default_name, 2199, 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));
+}