Roll ANGLE cc54ab3..c5b2ba5
[chromium-blink-merge.git] / components / variations / variations_seed_simulator.cc
blob36875173f0c90020441d7b4e8775682b1e751800
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"
7 #include <map>
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 {
17 namespace {
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
49 // as changed.
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())
56 trial->Disable();
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);
68 return NULL;
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
73 // current process.
74 bool VariationParamsAreEqual(const Study& study,
75 const Study_Experiment& experiment) {
76 std::map<std::string, std::string> params;
77 GetVariationParams(study.name(), &params);
79 if (static_cast<int>(params.size()) != experiment.param_size())
80 return false;
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())
86 return false;
89 return true;
92 } // namespace
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 const std::string& session_consistency_country,
120 const std::string& permanent_consistency_country) {
121 std::vector<ProcessedStudy> filtered_studies;
122 FilterAndValidateStudies(seed, locale, reference_date, version, channel,
123 form_factor, hardware_class,
124 session_consistency_country,
125 permanent_consistency_country, &filtered_studies);
127 return ComputeDifferences(filtered_studies);
130 VariationsSeedSimulator::Result VariationsSeedSimulator::ComputeDifferences(
131 const std::vector<ProcessedStudy>& processed_studies) {
132 std::map<std::string, std::string> current_state;
133 GetCurrentTrialState(&current_state);
135 Result result;
136 for (size_t i = 0; i < processed_studies.size(); ++i) {
137 const Study& study = *processed_studies[i].study();
138 std::map<std::string, std::string>::const_iterator it =
139 current_state.find(study.name());
141 // Skip studies that aren't activated in the current state.
142 // TODO(asvitkine): This should be handled more intelligently. There are
143 // several cases that fall into this category:
144 // 1) There's an existing field trial with this name but it is not active.
145 // 2) There's an existing expired field trial with this name, which is
146 // also not considered as active.
147 // 3) This is a new study config that previously didn't exist.
148 // The above cases should be differentiated and handled explicitly.
149 if (it == current_state.end())
150 continue;
152 // Study exists in the current state, check whether its group will change.
153 // Note: The logic below does the right thing if study consistency changes,
154 // as it doesn't rely on the previous study consistency.
155 const std::string& selected_group = it->second;
156 ChangeType change_type = NO_CHANGE;
157 if (study.consistency() == Study_Consistency_PERMANENT) {
158 change_type = PermanentStudyGroupChanged(processed_studies[i],
159 selected_group);
160 } else if (study.consistency() == Study_Consistency_SESSION) {
161 change_type = SessionStudyGroupChanged(processed_studies[i],
162 selected_group);
165 switch (change_type) {
166 case NO_CHANGE:
167 break;
168 case CHANGED:
169 ++result.normal_group_change_count;
170 break;
171 case CHANGED_KILL_BEST_EFFORT:
172 ++result.kill_best_effort_group_change_count;
173 break;
174 case CHANGED_KILL_CRITICAL:
175 ++result.kill_critical_group_change_count;
176 break;
180 // TODO(asvitkine): Handle removed studies (i.e. studies that existed in the
181 // old seed, but were removed). This will require tracking the set of studies
182 // that were created from the original seed.
184 return result;
187 VariationsSeedSimulator::ChangeType
188 VariationsSeedSimulator::ConvertExperimentTypeToChangeType(
189 Study_Experiment_Type type) {
190 switch (type) {
191 case Study_Experiment_Type_NORMAL:
192 return CHANGED;
193 case Study_Experiment_Type_IGNORE_CHANGE:
194 return NO_CHANGE;
195 case Study_Experiment_Type_KILL_BEST_EFFORT:
196 return CHANGED_KILL_BEST_EFFORT;
197 case Study_Experiment_Type_KILL_CRITICAL:
198 return CHANGED_KILL_CRITICAL;
200 return CHANGED;
203 VariationsSeedSimulator::ChangeType
204 VariationsSeedSimulator::PermanentStudyGroupChanged(
205 const ProcessedStudy& processed_study,
206 const std::string& selected_group) {
207 const Study& study = *processed_study.study();
208 DCHECK_EQ(Study_Consistency_PERMANENT, study.consistency());
210 const std::string simulated_group = SimulateGroupAssignment(entropy_provider_,
211 processed_study);
212 const Study_Experiment* experiment = FindExperiment(study, selected_group);
213 if (simulated_group != selected_group) {
214 if (experiment)
215 return ConvertExperimentTypeToChangeType(experiment->type());
216 return CHANGED;
219 // Current group exists in the study - check whether its params changed.
220 DCHECK(experiment);
221 if (!VariationParamsAreEqual(study, *experiment))
222 return ConvertExperimentTypeToChangeType(experiment->type());
223 return NO_CHANGE;
226 VariationsSeedSimulator::ChangeType
227 VariationsSeedSimulator::SessionStudyGroupChanged(
228 const ProcessedStudy& processed_study,
229 const std::string& selected_group) {
230 const Study& study = *processed_study.study();
231 DCHECK_EQ(Study_Consistency_SESSION, study.consistency());
233 const Study_Experiment* experiment = FindExperiment(study, selected_group);
234 if (processed_study.is_expired() &&
235 selected_group != study.default_experiment_name()) {
236 // An expired study will result in the default group being selected - mark
237 // it as changed if the current group differs from the default.
238 if (experiment)
239 return ConvertExperimentTypeToChangeType(experiment->type());
240 return CHANGED;
243 if (!experiment)
244 return CHANGED;
245 if (experiment->probability_weight() == 0 &&
246 !experiment->has_forcing_flag()) {
247 return ConvertExperimentTypeToChangeType(experiment->type());
250 // Current group exists in the study - check whether its params changed.
251 if (!VariationParamsAreEqual(study, *experiment))
252 return ConvertExperimentTypeToChangeType(experiment->type());
253 return NO_CHANGE;
256 } // namespace variations