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/study_filtering.h"
13 #include "components/variations/variations_associated_data.h"
15 namespace variations
{
19 // Fills in |current_state| with the current process' active field trials, as a
20 // map of trial names to group names.
21 void GetCurrentTrialState(std::map
<std::string
, std::string
>* current_state
) {
22 base::FieldTrial::ActiveGroups trial_groups
;
23 base::FieldTrialList::GetActiveFieldTrialGroups(&trial_groups
);
24 for (size_t i
= 0; i
< trial_groups
.size(); ++i
)
25 (*current_state
)[trial_groups
[i
].trial_name
] = trial_groups
[i
].group_name
;
28 // Simulate group assignment for the specified study with PERMANENT consistency.
29 // Returns the experiment group that will be selected. Mirrors logic in
30 // VariationsSeedProcessor::CreateTrialFromStudy().
31 std::string
SimulateGroupAssignment(
32 const base::FieldTrial::EntropyProvider
& entropy_provider
,
33 const ProcessedStudy
& processed_study
) {
34 const Study
& study
= *processed_study
.study();
35 DCHECK_EQ(Study_Consistency_PERMANENT
, study
.consistency());
37 const double entropy_value
=
38 entropy_provider
.GetEntropyForTrial(study
.name(),
39 study
.randomization_seed());
40 scoped_refptr
<base::FieldTrial
> trial(
41 base::FieldTrial::CreateSimulatedFieldTrial(
42 study
.name(), processed_study
.total_probability(),
43 study
.default_experiment_name(), entropy_value
));
45 for (int i
= 0; i
< study
.experiment_size(); ++i
) {
46 const Study_Experiment
& experiment
= study
.experiment(i
);
47 // TODO(asvitkine): This needs to properly handle the case where a group was
48 // forced via forcing_flag in the current state, so that it is not treated
50 if (!experiment
.has_forcing_flag() &&
51 experiment
.name() != study
.default_experiment_name()) {
52 trial
->AppendGroup(experiment
.name(), experiment
.probability_weight());
55 if (processed_study
.is_expired())
57 return trial
->group_name();
60 // Finds an experiment in |study| with name |experiment_name| and returns it,
61 // or NULL if it does not exist.
62 const Study_Experiment
* FindExperiment(const Study
& study
,
63 const std::string
& experiment_name
) {
64 for (int i
= 0; i
< study
.experiment_size(); ++i
) {
65 if (study
.experiment(i
).name() == experiment_name
)
66 return &study
.experiment(i
);
71 // Checks whether experiment params set for |experiment| on |study| are exactly
72 // equal to the params registered for the corresponding field trial in the
74 bool VariationParamsAreEqual(const Study
& study
,
75 const Study_Experiment
& experiment
) {
76 std::map
<std::string
, std::string
> params
;
77 GetVariationParams(study
.name(), ¶ms
);
79 if (static_cast<int>(params
.size()) != experiment
.param_size())
82 for (int i
= 0; i
< experiment
.param_size(); ++i
) {
83 std::map
<std::string
, std::string
>::const_iterator it
=
84 params
.find(experiment
.param(i
).name());
85 if (it
== params
.end() || it
->second
!= experiment
.param(i
).value())
94 VariationsSeedSimulator::Result::Result()
95 : normal_group_change_count(0),
96 kill_best_effort_group_change_count(0),
97 kill_critical_group_change_count(0) {
100 VariationsSeedSimulator::Result::~Result() {
103 VariationsSeedSimulator::VariationsSeedSimulator(
104 const base::FieldTrial::EntropyProvider
& entropy_provider
)
105 : entropy_provider_(entropy_provider
) {
108 VariationsSeedSimulator::~VariationsSeedSimulator() {
111 VariationsSeedSimulator::Result
VariationsSeedSimulator::SimulateSeedStudies(
112 const VariationsSeed
& seed
,
113 const std::string
& locale
,
114 const base::Time
& reference_date
,
115 const base::Version
& version
,
116 Study_Channel channel
,
117 Study_FormFactor form_factor
,
118 const std::string
& hardware_class
) {
119 std::vector
<ProcessedStudy
> filtered_studies
;
120 FilterAndValidateStudies(seed
, locale
, reference_date
, version
, channel
,
121 form_factor
, hardware_class
, &filtered_studies
);
123 return ComputeDifferences(filtered_studies
);
126 VariationsSeedSimulator::Result
VariationsSeedSimulator::ComputeDifferences(
127 const std::vector
<ProcessedStudy
>& processed_studies
) {
128 std::map
<std::string
, std::string
> current_state
;
129 GetCurrentTrialState(¤t_state
);
132 for (size_t i
= 0; i
< processed_studies
.size(); ++i
) {
133 const Study
& study
= *processed_studies
[i
].study();
134 std::map
<std::string
, std::string
>::const_iterator it
=
135 current_state
.find(study
.name());
137 // Skip studies that aren't activated in the current state.
138 // TODO(asvitkine): This should be handled more intelligently. There are
139 // several cases that fall into this category:
140 // 1) There's an existing field trial with this name but it is not active.
141 // 2) There's an existing expired field trial with this name, which is
142 // also not considered as active.
143 // 3) This is a new study config that previously didn't exist.
144 // The above cases should be differentiated and handled explicitly.
145 if (it
== current_state
.end())
148 // Study exists in the current state, check whether its group will change.
149 // Note: The logic below does the right thing if study consistency changes,
150 // as it doesn't rely on the previous study consistency.
151 const std::string
& selected_group
= it
->second
;
152 ChangeType change_type
= NO_CHANGE
;
153 if (study
.consistency() == Study_Consistency_PERMANENT
) {
154 change_type
= PermanentStudyGroupChanged(processed_studies
[i
],
156 } else if (study
.consistency() == Study_Consistency_SESSION
) {
157 change_type
= SessionStudyGroupChanged(processed_studies
[i
],
161 switch (change_type
) {
165 ++result
.normal_group_change_count
;
167 case CHANGED_KILL_BEST_EFFORT
:
168 ++result
.kill_best_effort_group_change_count
;
170 case CHANGED_KILL_CRITICAL
:
171 ++result
.kill_critical_group_change_count
;
176 // TODO(asvitkine): Handle removed studies (i.e. studies that existed in the
177 // old seed, but were removed). This will require tracking the set of studies
178 // that were created from the original seed.
183 VariationsSeedSimulator::ChangeType
184 VariationsSeedSimulator::ConvertExperimentTypeToChangeType(
185 Study_Experiment_Type type
) {
187 case Study_Experiment_Type_NORMAL
:
189 case Study_Experiment_Type_IGNORE_CHANGE
:
191 case Study_Experiment_Type_KILL_BEST_EFFORT
:
192 return CHANGED_KILL_BEST_EFFORT
;
193 case Study_Experiment_Type_KILL_CRITICAL
:
194 return CHANGED_KILL_CRITICAL
;
199 VariationsSeedSimulator::ChangeType
200 VariationsSeedSimulator::PermanentStudyGroupChanged(
201 const ProcessedStudy
& processed_study
,
202 const std::string
& selected_group
) {
203 const Study
& study
= *processed_study
.study();
204 DCHECK_EQ(Study_Consistency_PERMANENT
, study
.consistency());
206 const std::string simulated_group
= SimulateGroupAssignment(entropy_provider_
,
208 const Study_Experiment
* experiment
= FindExperiment(study
, selected_group
);
209 if (simulated_group
!= selected_group
) {
211 return ConvertExperimentTypeToChangeType(experiment
->type());
215 // Current group exists in the study - check whether its params changed.
217 if (!VariationParamsAreEqual(study
, *experiment
))
218 return ConvertExperimentTypeToChangeType(experiment
->type());
222 VariationsSeedSimulator::ChangeType
223 VariationsSeedSimulator::SessionStudyGroupChanged(
224 const ProcessedStudy
& processed_study
,
225 const std::string
& selected_group
) {
226 const Study
& study
= *processed_study
.study();
227 DCHECK_EQ(Study_Consistency_SESSION
, study
.consistency());
229 const Study_Experiment
* experiment
= FindExperiment(study
, selected_group
);
230 if (processed_study
.is_expired() &&
231 selected_group
!= study
.default_experiment_name()) {
232 // An expired study will result in the default group being selected - mark
233 // it as changed if the current group differs from the default.
235 return ConvertExperimentTypeToChangeType(experiment
->type());
241 if (experiment
->probability_weight() == 0 &&
242 !experiment
->has_forcing_flag()) {
243 return ConvertExperimentTypeToChangeType(experiment
->type());
246 // Current group exists in the study - check whether its params changed.
247 if (!VariationParamsAreEqual(study
, *experiment
))
248 return ConvertExperimentTypeToChangeType(experiment
->type());
252 } // namespace variations