diff options
author | asvitkine@chromium.org <asvitkine@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-04-11 20:59:50 +0000 |
---|---|---|
committer | asvitkine@chromium.org <asvitkine@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-04-11 20:59:50 +0000 |
commit | 6fded22b9a94b0b9bb1658310cc239b97c4276c8 (patch) | |
tree | 1302b01fbe1a4a1880df571c49d67bc155fec7f0 | |
parent | ff402386115448de1e4cbcf6fd62251b91ababa6 (diff) | |
download | chromium_src-6fded22b9a94b0b9bb1658310cc239b97c4276c8.zip chromium_src-6fded22b9a94b0b9bb1658310cc239b97c4276c8.tar.gz chromium_src-6fded22b9a94b0b9bb1658310cc239b97c4276c8.tar.bz2 |
Support custom randomization seed for VariationsService trials.
BUG=229486
TEST=New unit test.
Review URL: https://chromiumcodereview.appspot.com/13928017
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@193739 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | base/metrics/field_trial.cc | 8 | ||||
-rw-r--r-- | base/metrics/field_trial.h | 21 | ||||
-rw-r--r-- | chrome/browser/metrics/proto/study.proto | 6 | ||||
-rw-r--r-- | chrome/browser/metrics/variations/variations_service.cc | 5 | ||||
-rw-r--r-- | chrome/common/metrics/entropy_provider.cc | 16 | ||||
-rw-r--r-- | chrome/common/metrics/entropy_provider.h | 14 | ||||
-rw-r--r-- | chrome/common/metrics/entropy_provider_unittest.cc | 33 |
7 files changed, 79 insertions, 24 deletions
diff --git a/base/metrics/field_trial.cc b/base/metrics/field_trial.cc index 1f4f9ae..c87935e 100644 --- a/base/metrics/field_trial.cc +++ b/base/metrics/field_trial.cc @@ -75,6 +75,11 @@ FieldTrial::EntropyProvider::~EntropyProvider() { } void FieldTrial::UseOneTimeRandomization() { + UseOneTimeRandomizationWithCustomSeed(0); +} + +void FieldTrial::UseOneTimeRandomizationWithCustomSeed( + uint32 randomization_seed) { // No need to specify randomization when the group choice was forced. if (forced_) return; @@ -89,7 +94,8 @@ void FieldTrial::UseOneTimeRandomization() { } random_ = static_cast<Probability>( - divisor_ * entropy_provider->GetEntropyForTrial(trial_name_)); + divisor_ * entropy_provider->GetEntropyForTrial(trial_name_, + randomization_seed)); } void FieldTrial::Disable() { diff --git a/base/metrics/field_trial.h b/base/metrics/field_trial.h index a4e1f53..b9a5b66 100644 --- a/base/metrics/field_trial.h +++ b/base/metrics/field_trial.h @@ -101,10 +101,13 @@ class BASE_EXPORT FieldTrial : public RefCounted<FieldTrial> { public: virtual ~EntropyProvider(); - // Returns a double in the range of [0, 1) based on |trial_name| that will - // be used for the dice roll for the specified field trial. A given instance - // should always return the same value given the same input |trial_name|. - virtual double GetEntropyForTrial(const std::string& trial_name) const = 0; + // Returns a double in the range of [0, 1) to be used for the dice roll for + // the specified field trial. If |randomization_seed| is not 0, it will be + // used in preference to |trial_name| for generating the entropy by entropy + // providers that support it. A given instance should always return the same + // value given the same input |trial_name| and |randomization_seed| values. + virtual double GetEntropyForTrial(const std::string& trial_name, + uint32 randomization_seed) const = 0; }; // A pair representing a Field Trial and its selected group. @@ -121,9 +124,17 @@ class BASE_EXPORT FieldTrial : public RefCounted<FieldTrial> { // Changes the field trial to use one-time randomization, i.e. produce the // same result for the current trial on every run of this client. Must be - // called right after construction. + // called right after construction, before any groups are added. void UseOneTimeRandomization(); + // Changes the field trial to use one-time randomization, i.e. produce the + // same result for the current trial on every run of this client, with a + // custom randomization seed for the trial. The |randomization_seed| value + // should never be the same for two trials, else this would result in + // correlated group assignments. Must be called right after construction, + // before any groups are added. + void UseOneTimeRandomizationWithCustomSeed(uint32 randomization_seed); + // Disables this trial, meaning it always determines the default group // has been selected. May be called immediately after construction, or // at any time after initialization (should not be interleaved with diff --git a/chrome/browser/metrics/proto/study.proto b/chrome/browser/metrics/proto/study.proto index f155c3c..f4a9d43 100644 --- a/chrome/browser/metrics/proto/study.proto +++ b/chrome/browser/metrics/proto/study.proto @@ -11,7 +11,7 @@ package chrome_variations; // This defines the Protocol Buffer representation of a Chrome Variations study // as sent to clients of the Variations server. // -// Next tag: 11 +// Next tag: 12 message Study { // The name of the study. Should not contain spaces or special characters. // Ex: "my_study" @@ -130,4 +130,8 @@ message Study { // Filtering criteria for this study. A study that is filtered out for a given // client is equivalent to that study not being sent at all. optional Filter filter = 10; + + // Randomization seed to be used when |consistency| is set to PERMANENT. If + // not specified, randomization will be done using the trial name. + optional uint32 randomization_seed = 11; } diff --git a/chrome/browser/metrics/variations/variations_service.cc b/chrome/browser/metrics/variations/variations_service.cc index ee2531c..b841575 100644 --- a/chrome/browser/metrics/variations/variations_service.cc +++ b/chrome/browser/metrics/variations/variations_service.cc @@ -612,7 +612,10 @@ void VariationsService::CreateTrialFromStudy(const Study& study, if (study.has_consistency() && study.consistency() == Study_Consistency_PERMANENT) { - trial->UseOneTimeRandomization(); + if (study.has_randomization_seed()) + trial->UseOneTimeRandomizationWithCustomSeed(study.randomization_seed()); + else + trial->UseOneTimeRandomization(); } for (int i = 0; i < study.experiment_size(); ++i) { diff --git a/chrome/common/metrics/entropy_provider.cc b/chrome/common/metrics/entropy_provider.cc index 13081d8..558e0f9 100644 --- a/chrome/common/metrics/entropy_provider.cc +++ b/chrome/common/metrics/entropy_provider.cc @@ -44,12 +44,12 @@ uint32 SeededRandGenerator::operator()(uint32 range) { return value % range; } -void PermuteMappingUsingTrialName(const std::string& trial_name, - std::vector<uint16>* mapping) { +void PermuteMappingUsingRandomizationSeed(uint32 randomization_seed, + std::vector<uint16>* mapping) { for (size_t i = 0; i < mapping->size(); ++i) (*mapping)[i] = static_cast<uint16>(i); - SeededRandGenerator generator(HashName(trial_name)); + SeededRandGenerator generator(randomization_seed); std::random_shuffle(mapping->begin(), mapping->end(), generator); } @@ -63,7 +63,8 @@ SHA1EntropyProvider::~SHA1EntropyProvider() { } double SHA1EntropyProvider::GetEntropyForTrial( - const std::string& trial_name) const { + const std::string& trial_name, + uint32 randomization_seed) const { // Given enough input entropy, SHA-1 will produce a uniformly random spread // in its output space. In this case, the input entropy that is used is the // combination of the original |entropy_source_| and the |trial_name|. @@ -99,9 +100,12 @@ PermutedEntropyProvider::~PermutedEntropyProvider() { } double PermutedEntropyProvider::GetEntropyForTrial( - const std::string& trial_name) const { + const std::string& trial_name, + uint32 randomization_seed) const { std::vector<uint16> mapping(low_entropy_source_max_); - internal::PermuteMappingUsingTrialName(trial_name, &mapping); + if (randomization_seed == 0) + randomization_seed = HashName(trial_name); + internal::PermuteMappingUsingRandomizationSeed(randomization_seed, &mapping); return mapping[low_entropy_source_] / static_cast<double>(low_entropy_source_max_); diff --git a/chrome/common/metrics/entropy_provider.h b/chrome/common/metrics/entropy_provider.h index 7938a5e..56bb1fc 100644 --- a/chrome/common/metrics/entropy_provider.h +++ b/chrome/common/metrics/entropy_provider.h @@ -33,9 +33,9 @@ struct SeededRandGenerator : std::unary_function<uint32, uint32> { }; // Fills |mapping| to create a bijection of values in the range of -// [0, |mapping.size()|), permuted based on |trial_name|. -void PermuteMappingUsingTrialName(const std::string& trial_name, - std::vector<uint16>* mapping); +// [0, |mapping.size()|), permuted based on |randomization_seed|. +void PermuteMappingUsingRandomizationSeed(uint32 randomization_seed, + std::vector<uint16>* mapping); } // namespace internal @@ -52,8 +52,8 @@ class SHA1EntropyProvider : public base::FieldTrial::EntropyProvider { virtual ~SHA1EntropyProvider(); // base::FieldTrial::EntropyProvider implementation: - virtual double GetEntropyForTrial(const std::string& trial_name) const - OVERRIDE; + virtual double GetEntropyForTrial(const std::string& trial_name, + uint32 randomization_seed) const OVERRIDE; private: std::string entropy_source_; @@ -75,8 +75,8 @@ class PermutedEntropyProvider : public base::FieldTrial::EntropyProvider { virtual ~PermutedEntropyProvider(); // base::FieldTrial::EntropyProvider implementation: - virtual double GetEntropyForTrial(const std::string& trial_name) const - OVERRIDE; + virtual double GetEntropyForTrial(const std::string& trial_name, + uint32 randomization_seed) const OVERRIDE; private: uint16 low_entropy_source_; diff --git a/chrome/common/metrics/entropy_provider_unittest.cc b/chrome/common/metrics/entropy_provider_unittest.cc index c3547ab..9058af6 100644 --- a/chrome/common/metrics/entropy_provider_unittest.cc +++ b/chrome/common/metrics/entropy_provider_unittest.cc @@ -47,7 +47,7 @@ double ComputeChiSquare(const std::vector<int>& values, double GenerateSHA1Entropy(const std::string& entropy_source, const std::string& trial_name) { SHA1EntropyProvider sha1_provider(entropy_source); - return sha1_provider.GetEntropyForTrial(trial_name); + return sha1_provider.GetEntropyForTrial(trial_name, 0); } // Generates permutation-based entropy for the given |trial_name| based on @@ -56,7 +56,7 @@ double GeneratePermutedEntropy(uint16 entropy_source, size_t entropy_max, const std::string& trial_name) { PermutedEntropyProvider permuted_provider(entropy_source, entropy_max); - return permuted_provider.GetEntropyForTrial(trial_name); + return permuted_provider.GetEntropyForTrial(trial_name, 0); } // Helper interface for testing used to generate entropy values for a given @@ -107,7 +107,9 @@ class PermutedEntropyGenerator : public TrialEntropyGenerator { // Note: Given a trial name, the computed mapping will be the same. // As a performance optimization, pre-compute the mapping once per trial // name and index into it for each entropy value. - internal::PermuteMappingUsingTrialName(trial_name, &mapping_); + const uint32 randomization_seed = HashName(trial_name); + internal::PermuteMappingUsingRandomizationSeed(randomization_seed, + &mapping_); } virtual ~PermutedEntropyGenerator() { @@ -237,6 +239,31 @@ TEST_F(EntropyProviderTest, UseOneTimeRandomizationPermuted) { EXPECT_NE(trials[0]->group_name(), trials[1]->group_name()); } +TEST_F(EntropyProviderTest, UseOneTimeRandomizationWithCustomSeedPermuted) { + // Ensures that two trials with different names but the same custom seed used + // for one time randomization produce the same group assignments. + base::FieldTrialList field_trial_list( + new PermutedEntropyProvider(1234, kMaxLowEntropySize)); + scoped_refptr<base::FieldTrial> trials[] = { + base::FieldTrialList::FactoryGetFieldTrial("one", 100, "default", + base::FieldTrialList::kNoExpirationYear, 1, 1, NULL), + base::FieldTrialList::FactoryGetFieldTrial("two", 100, "default", + base::FieldTrialList::kNoExpirationYear, 1, 1, NULL) }; + const uint32 kCustomSeed = 9001; + + for (size_t i = 0; i < arraysize(trials); ++i) { + trials[i]->UseOneTimeRandomizationWithCustomSeed(kCustomSeed); + + for (int j = 0; j < 100; ++j) + trials[i]->AppendGroup(std::string(), 1); + } + + // Normally, these trials should produce different groups, but if the same + // custom seed is used, they should produce the same group assignment. + EXPECT_EQ(trials[0]->group(), trials[1]->group()); + EXPECT_EQ(trials[0]->group_name(), trials[1]->group_name()); +} + TEST_F(EntropyProviderTest, SHA1Entropy) { const double results[] = { GenerateSHA1Entropy("hi", "1"), GenerateSHA1Entropy("there", "1") }; |