diff options
author | stevet@chromium.org <stevet@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-04-20 17:02:47 +0000 |
---|---|---|
committer | stevet@chromium.org <stevet@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-04-20 17:02:47 +0000 |
commit | 7f85791b0e6ce5a7745a26be8adc4cebf0ee409d (patch) | |
tree | cd58f8e2753897abc884641dd74db42b82aa9c66 /chrome/common/metrics | |
parent | 7c80f42e202478fc3eea8747ad7d4c9bfe2997c5 (diff) | |
download | chromium_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.cc | 72 | ||||
-rw-r--r-- | chrome/common/metrics/experiments_helper.h | 66 | ||||
-rw-r--r-- | chrome/common/metrics/experiments_helper_unittest.cc | 124 |
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)); +} |