summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorasvitkine@chromium.org <asvitkine@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-04-11 20:59:50 +0000
committerasvitkine@chromium.org <asvitkine@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-04-11 20:59:50 +0000
commit6fded22b9a94b0b9bb1658310cc239b97c4276c8 (patch)
tree1302b01fbe1a4a1880df571c49d67bc155fec7f0
parentff402386115448de1e4cbcf6fd62251b91ababa6 (diff)
downloadchromium_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.cc8
-rw-r--r--base/metrics/field_trial.h21
-rw-r--r--chrome/browser/metrics/proto/study.proto6
-rw-r--r--chrome/browser/metrics/variations/variations_service.cc5
-rw-r--r--chrome/common/metrics/entropy_provider.cc16
-rw-r--r--chrome/common/metrics/entropy_provider.h14
-rw-r--r--chrome/common/metrics/entropy_provider_unittest.cc33
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") };