1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "components/variations/variations_seed_simulator.h"
9 #include "base/metrics/field_trial.h"
10 #include "components/variations/processed_study.h"
11 #include "components/variations/proto/study.pb.h"
12 #include "components/variations/variations_associated_data.h"
14 namespace chrome_variations
{
18 // Fills in |current_state| with the current process' active field trials, as a
19 // map of trial names to group names.
20 void GetCurrentTrialState(std::map
<std::string
, std::string
>* current_state
) {
21 base::FieldTrial::ActiveGroups trial_groups
;
22 base::FieldTrialList::GetActiveFieldTrialGroups(&trial_groups
);
23 for (size_t i
= 0; i
< trial_groups
.size(); ++i
)
24 (*current_state
)[trial_groups
[i
].trial_name
] = trial_groups
[i
].group_name
;
27 // Simulate group assignment for the specified study with PERMANENT consistency.
28 // Returns the experiment group that will be selected. Mirrors logic in
29 // VariationsSeedProcessor::CreateTrialFromStudy().
30 std::string
SimulateGroupAssignment(
31 const base::FieldTrial::EntropyProvider
& entropy_provider
,
32 const ProcessedStudy
& processed_study
) {
33 const Study
& study
= *processed_study
.study();
34 DCHECK_EQ(Study_Consistency_PERMANENT
, study
.consistency());
36 const double entropy_value
=
37 entropy_provider
.GetEntropyForTrial(study
.name(),
38 study
.randomization_seed());
39 scoped_refptr
<base::FieldTrial
> trial(
40 base::FieldTrial::CreateSimulatedFieldTrial(
41 study
.name(), processed_study
.total_probability(),
42 study
.default_experiment_name(), entropy_value
));
44 for (int i
= 0; i
< study
.experiment_size(); ++i
) {
45 const Study_Experiment
& experiment
= study
.experiment(i
);
46 // TODO(asvitkine): This needs to properly handle the case where a group was
47 // forced via forcing_flag in the current state, so that it is not treated
49 if (!experiment
.has_forcing_flag() &&
50 experiment
.name() != study
.default_experiment_name()) {
51 trial
->AppendGroup(experiment
.name(), experiment
.probability_weight());
54 if (processed_study
.is_expired())
56 return trial
->group_name();
59 // Finds an experiment in |study| with name |experiment_name| and returns it,
60 // or NULL if it does not exist.
61 const Study_Experiment
* FindExperiment(const Study
& study
,
62 const std::string
& experiment_name
) {
63 for (int i
= 0; i
< study
.experiment_size(); ++i
) {
64 if (study
.experiment(i
).name() == experiment_name
)
65 return &study
.experiment(i
);
70 // Checks whether experiment params set for |experiment| on |study| are exactly
71 // equal to the params registered for the corresponding field trial in the
73 bool VariationParamsAreEqual(const Study
& study
,
74 const Study_Experiment
& experiment
) {
75 std::map
<std::string
, std::string
> params
;
76 GetVariationParams(study
.name(), ¶ms
);
78 if (static_cast<int>(params
.size()) != experiment
.param_size())
81 for (int i
= 0; i
< experiment
.param_size(); ++i
) {
82 std::map
<std::string
, std::string
>::const_iterator it
=
83 params
.find(experiment
.param(i
).name());
84 if (it
== params
.end() || it
->second
!= experiment
.param(i
).value())
93 VariationsSeedSimulator::VariationsSeedSimulator(
94 const base::FieldTrial::EntropyProvider
& entropy_provider
)
95 : entropy_provider_(entropy_provider
) {
98 VariationsSeedSimulator::~VariationsSeedSimulator() {
101 int VariationsSeedSimulator::ComputeDifferences(
102 const std::vector
<ProcessedStudy
>& processed_studies
) {
103 std::map
<std::string
, std::string
> current_state
;
104 GetCurrentTrialState(¤t_state
);
105 int group_change_count
= 0;
107 for (size_t i
= 0; i
< processed_studies
.size(); ++i
) {
108 const Study
& study
= *processed_studies
[i
].study();
109 std::map
<std::string
, std::string
>::const_iterator it
=
110 current_state
.find(study
.name());
112 // Skip studies that aren't activated in the current state.
113 // TODO(asvitkine): This should be handled more intelligently. There are
114 // several cases that fall into this category:
115 // 1) There's an existing field trial with this name but it is not active.
116 // 2) There's an existing expired field trial with this name, which is
117 // also not considered as active.
118 // 3) This is a new study config that previously didn't exist.
119 // The above cases should be differentiated and handled explicitly.
120 if (it
== current_state
.end())
123 // Study exists in the current state, check whether its group will change.
124 // Note: The logic below does the right thing if study consistency changes,
125 // as it doesn't rely on the previous study consistency.
126 const std::string
& selected_group
= it
->second
;
127 if (study
.consistency() == Study_Consistency_PERMANENT
) {
128 if (PermanentStudyGroupChanged(processed_studies
[i
], selected_group
))
129 ++group_change_count
;
130 } else if (study
.consistency() == Study_Consistency_SESSION
) {
131 if (SessionStudyGroupChanged(processed_studies
[i
], selected_group
))
132 ++group_change_count
;
136 // TODO(asvitkine): Handle removed studies (i.e. studies that existed in the
137 // old seed, but were removed). This will require tracking the set of studies
138 // that were created from the original seed.
140 return group_change_count
;
143 bool VariationsSeedSimulator::PermanentStudyGroupChanged(
144 const ProcessedStudy
& processed_study
,
145 const std::string
& selected_group
) {
146 const Study
& study
= *processed_study
.study();
147 DCHECK_EQ(Study_Consistency_PERMANENT
, study
.consistency());
149 const std::string simulated_group
= SimulateGroupAssignment(entropy_provider_
,
151 // TODO(asvitkine): Sometimes group names are changed without changing any
152 // behavior (e.g. if the behavior is controlled entirely via params). Support
153 // a mechanism to bypass this check.
154 if (simulated_group
!= selected_group
)
157 const Study_Experiment
* experiment
= FindExperiment(study
, selected_group
);
159 return !VariationParamsAreEqual(study
, *experiment
);
162 bool VariationsSeedSimulator::SessionStudyGroupChanged(
163 const ProcessedStudy
& processed_study
,
164 const std::string
& selected_group
) {
165 const Study
& study
= *processed_study
.study();
166 DCHECK_EQ(Study_Consistency_SESSION
, study
.consistency());
168 if (processed_study
.is_expired() &&
169 selected_group
!= study
.default_experiment_name()) {
170 // An expired study will result in the default group being selected - mark
171 // it as changed if the current group differs from the default.
175 const Study_Experiment
* experiment
= FindExperiment(study
, selected_group
);
178 if (experiment
->probability_weight() == 0 &&
179 !experiment
->has_forcing_flag()) {
183 // Current group exists in the study - check whether its params changed.
184 return !VariationParamsAreEqual(study
, *experiment
);
187 } // namespace chrome_variations