• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
4 
5 #include "components/variations/variations_seed_simulator.h"
6 
7 #include <map>
8 
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"
14 
15 namespace chrome_variations {
16 
17 namespace {
18 
19 // Fills in |current_state| with the current process' active field trials, as a
20 // map of trial names to group names.
GetCurrentTrialState(std::map<std::string,std::string> * current_state)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;
26 }
27 
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().
SimulateGroupAssignment(const base::FieldTrial::EntropyProvider & entropy_provider,const ProcessedStudy & processed_study)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());
36 
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));
44 
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());
53     }
54   }
55   if (processed_study.is_expired())
56     trial->Disable();
57   return trial->group_name();
58 }
59 
60 // Finds an experiment in |study| with name |experiment_name| and returns it,
61 // or NULL if it does not exist.
FindExperiment(const Study & study,const std::string & experiment_name)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);
67   }
68   return NULL;
69 }
70 
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.
VariationParamsAreEqual(const Study & study,const Study_Experiment & experiment)74 bool VariationParamsAreEqual(const Study& study,
75                              const Study_Experiment& experiment) {
76   std::map<std::string, std::string> params;
77   GetVariationParams(study.name(), &params);
78 
79   if (static_cast<int>(params.size()) != experiment.param_size())
80     return false;
81 
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;
87   }
88 
89   return true;
90 }
91 
92 }  // namespace
93 
Result()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) {
98 }
99 
~Result()100 VariationsSeedSimulator::Result::~Result() {
101 }
102 
VariationsSeedSimulator(const base::FieldTrial::EntropyProvider & entropy_provider)103 VariationsSeedSimulator::VariationsSeedSimulator(
104     const base::FieldTrial::EntropyProvider& entropy_provider)
105     : entropy_provider_(entropy_provider) {
106 }
107 
~VariationsSeedSimulator()108 VariationsSeedSimulator::~VariationsSeedSimulator() {
109 }
110 
SimulateSeedStudies(const VariationsSeed & seed,const std::string & locale,const base::Time & reference_date,const base::Version & version,Study_Channel channel,Study_FormFactor form_factor,const std::string & hardware_class)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);
122 
123   return ComputeDifferences(filtered_studies);
124 }
125 
ComputeDifferences(const std::vector<ProcessedStudy> & processed_studies)126 VariationsSeedSimulator::Result VariationsSeedSimulator::ComputeDifferences(
127     const std::vector<ProcessedStudy>& processed_studies) {
128   std::map<std::string, std::string> current_state;
129   GetCurrentTrialState(&current_state);
130 
131   Result result;
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());
136 
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())
146       continue;
147 
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],
155                                                selected_group);
156     } else if (study.consistency() == Study_Consistency_SESSION) {
157       change_type = SessionStudyGroupChanged(processed_studies[i],
158                                              selected_group);
159     }
160 
161     switch (change_type) {
162       case NO_CHANGE:
163         break;
164       case CHANGED:
165         ++result.normal_group_change_count;
166         break;
167       case CHANGED_KILL_BEST_EFFORT:
168         ++result.kill_best_effort_group_change_count;
169         break;
170       case CHANGED_KILL_CRITICAL:
171         ++result.kill_critical_group_change_count;
172         break;
173     }
174   }
175 
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.
179 
180   return result;
181 }
182 
183 VariationsSeedSimulator::ChangeType
ConvertExperimentTypeToChangeType(Study_Experiment_Type type)184 VariationsSeedSimulator::ConvertExperimentTypeToChangeType(
185     Study_Experiment_Type type) {
186   switch (type) {
187     case Study_Experiment_Type_NORMAL:
188       return CHANGED;
189     case Study_Experiment_Type_IGNORE_CHANGE:
190       return NO_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;
195   }
196   return CHANGED;
197 }
198 
199 VariationsSeedSimulator::ChangeType
PermanentStudyGroupChanged(const ProcessedStudy & processed_study,const std::string & selected_group)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());
205 
206   const std::string simulated_group = SimulateGroupAssignment(entropy_provider_,
207                                                               processed_study);
208   const Study_Experiment* experiment = FindExperiment(study, selected_group);
209   if (simulated_group != selected_group) {
210     if (experiment)
211       return ConvertExperimentTypeToChangeType(experiment->type());
212     return CHANGED;
213   }
214 
215   // Current group exists in the study - check whether its params changed.
216   DCHECK(experiment);
217   if (!VariationParamsAreEqual(study, *experiment))
218     return ConvertExperimentTypeToChangeType(experiment->type());
219   return NO_CHANGE;
220 }
221 
222 VariationsSeedSimulator::ChangeType
SessionStudyGroupChanged(const ProcessedStudy & processed_study,const std::string & selected_group)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());
228 
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.
234     if (experiment)
235       return ConvertExperimentTypeToChangeType(experiment->type());
236     return CHANGED;
237   }
238 
239   if (!experiment)
240     return CHANGED;
241   if (experiment->probability_weight() == 0 &&
242       !experiment->has_forcing_flag()) {
243     return ConvertExperimentTypeToChangeType(experiment->type());
244   }
245 
246   // Current group exists in the study - check whether its params changed.
247   if (!VariationParamsAreEqual(study, *experiment))
248     return ConvertExperimentTypeToChangeType(experiment->type());
249   return NO_CHANGE;
250 }
251 
252 }  // namespace chrome_variations
253