blob: f711c8d155210b4fc9ba043064374aca8af0e933 [file] [log] [blame]
[email protected]92e09a182014-01-08 18:21:001// 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
avi5dd91f82015-12-25 22:30:467#include <stddef.h>
8
[email protected]92e09a182014-01-08 18:21:009#include <map>
10
11#include "base/metrics/field_trial.h"
isherman52a81bd2017-06-07 23:30:0612#include "components/variations/client_filterable_state.h"
[email protected]92e09a182014-01-08 18:21:0013#include "components/variations/processed_study.h"
14#include "components/variations/proto/study.pb.h"
[email protected]a81120b62014-04-18 01:56:0315#include "components/variations/study_filtering.h"
[email protected]92e09a182014-01-08 18:21:0016#include "components/variations/variations_associated_data.h"
jwd67c08f752016-05-18 21:04:5917#include "components/variations/variations_seed_processor.h"
[email protected]92e09a182014-01-08 18:21:0018
[email protected]59b6f672014-07-26 18:35:4719namespace variations {
[email protected]92e09a182014-01-08 18:21:0020
21namespace {
22
23// Fills in |current_state| with the current process' active field trials, as a
24// map of trial names to group names.
25void GetCurrentTrialState(std::map<std::string, std::string>* current_state) {
26 base::FieldTrial::ActiveGroups trial_groups;
27 base::FieldTrialList::GetActiveFieldTrialGroups(&trial_groups);
28 for (size_t i = 0; i < trial_groups.size(); ++i)
29 (*current_state)[trial_groups[i].trial_name] = trial_groups[i].group_name;
30}
31
32// Simulate group assignment for the specified study with PERMANENT consistency.
33// Returns the experiment group that will be selected. Mirrors logic in
34// VariationsSeedProcessor::CreateTrialFromStudy().
35std::string SimulateGroupAssignment(
36 const base::FieldTrial::EntropyProvider& entropy_provider,
37 const ProcessedStudy& processed_study) {
38 const Study& study = *processed_study.study();
39 DCHECK_EQ(Study_Consistency_PERMANENT, study.consistency());
40
41 const double entropy_value =
42 entropy_provider.GetEntropyForTrial(study.name(),
43 study.randomization_seed());
44 scoped_refptr<base::FieldTrial> trial(
45 base::FieldTrial::CreateSimulatedFieldTrial(
46 study.name(), processed_study.total_probability(),
isherman0bd5b872017-04-29 03:11:2747 processed_study.GetDefaultExperimentName(), entropy_value));
[email protected]92e09a182014-01-08 18:21:0048
49 for (int i = 0; i < study.experiment_size(); ++i) {
50 const Study_Experiment& experiment = study.experiment(i);
51 // TODO(asvitkine): This needs to properly handle the case where a group was
52 // forced via forcing_flag in the current state, so that it is not treated
53 // as changed.
54 if (!experiment.has_forcing_flag() &&
55 experiment.name() != study.default_experiment_name()) {
56 trial->AppendGroup(experiment.name(), experiment.probability_weight());
57 }
58 }
59 if (processed_study.is_expired())
60 trial->Disable();
61 return trial->group_name();
62}
63
64// Finds an experiment in |study| with name |experiment_name| and returns it,
65// or NULL if it does not exist.
66const Study_Experiment* FindExperiment(const Study& study,
67 const std::string& experiment_name) {
68 for (int i = 0; i < study.experiment_size(); ++i) {
69 if (study.experiment(i).name() == experiment_name)
70 return &study.experiment(i);
71 }
Ivan Kotenkov75b1c3a2017-10-24 14:47:2472 return nullptr;
[email protected]92e09a182014-01-08 18:21:0073}
74
75// Checks whether experiment params set for |experiment| on |study| are exactly
76// equal to the params registered for the corresponding field trial in the
77// current process.
78bool VariationParamsAreEqual(const Study& study,
79 const Study_Experiment& experiment) {
80 std::map<std::string, std::string> params;
81 GetVariationParams(study.name(), &params);
82
83 if (static_cast<int>(params.size()) != experiment.param_size())
84 return false;
85
86 for (int i = 0; i < experiment.param_size(); ++i) {
87 std::map<std::string, std::string>::const_iterator it =
88 params.find(experiment.param(i).name());
89 if (it == params.end() || it->second != experiment.param(i).value())
90 return false;
91 }
92
93 return true;
94}
95
96} // namespace
97
[email protected]a81120b62014-04-18 01:56:0398VariationsSeedSimulator::Result::Result()
99 : normal_group_change_count(0),
100 kill_best_effort_group_change_count(0),
101 kill_critical_group_change_count(0) {
102}
103
104VariationsSeedSimulator::Result::~Result() {
105}
106
[email protected]92e09a182014-01-08 18:21:00107VariationsSeedSimulator::VariationsSeedSimulator(
jwd67c08f752016-05-18 21:04:59108 const base::FieldTrial::EntropyProvider& default_entropy_provider,
109 const base::FieldTrial::EntropyProvider& low_entropy_provider)
110 : default_entropy_provider_(default_entropy_provider),
111 low_entropy_provider_(low_entropy_provider) {}
[email protected]92e09a182014-01-08 18:21:00112
113VariationsSeedSimulator::~VariationsSeedSimulator() {
114}
115
[email protected]a81120b62014-04-18 01:56:03116VariationsSeedSimulator::Result VariationsSeedSimulator::SimulateSeedStudies(
117 const VariationsSeed& seed,
isherman52a81bd2017-06-07 23:30:06118 const ClientFilterableState& client_state) {
[email protected]a81120b62014-04-18 01:56:03119 std::vector<ProcessedStudy> filtered_studies;
isherman52a81bd2017-06-07 23:30:06120 FilterAndValidateStudies(seed, client_state, &filtered_studies);
[email protected]a81120b62014-04-18 01:56:03121
122 return ComputeDifferences(filtered_studies);
123}
124
125VariationsSeedSimulator::Result VariationsSeedSimulator::ComputeDifferences(
[email protected]92e09a182014-01-08 18:21:00126 const std::vector<ProcessedStudy>& processed_studies) {
127 std::map<std::string, std::string> current_state;
128 GetCurrentTrialState(&current_state);
[email protected]92e09a182014-01-08 18:21:00129
[email protected]a81120b62014-04-18 01:56:03130 Result result;
[email protected]92e09a182014-01-08 18:21:00131 for (size_t i = 0; i < processed_studies.size(); ++i) {
132 const Study& study = *processed_studies[i].study();
133 std::map<std::string, std::string>::const_iterator it =
134 current_state.find(study.name());
135
136 // Skip studies that aren't activated in the current state.
137 // TODO(asvitkine): This should be handled more intelligently. There are
138 // several cases that fall into this category:
139 // 1) There's an existing field trial with this name but it is not active.
140 // 2) There's an existing expired field trial with this name, which is
141 // also not considered as active.
142 // 3) This is a new study config that previously didn't exist.
143 // The above cases should be differentiated and handled explicitly.
144 if (it == current_state.end())
145 continue;
146
147 // Study exists in the current state, check whether its group will change.
148 // Note: The logic below does the right thing if study consistency changes,
149 // as it doesn't rely on the previous study consistency.
150 const std::string& selected_group = it->second;
[email protected]a81120b62014-04-18 01:56:03151 ChangeType change_type = NO_CHANGE;
[email protected]92e09a182014-01-08 18:21:00152 if (study.consistency() == Study_Consistency_PERMANENT) {
[email protected]a81120b62014-04-18 01:56:03153 change_type = PermanentStudyGroupChanged(processed_studies[i],
154 selected_group);
[email protected]92e09a182014-01-08 18:21:00155 } else if (study.consistency() == Study_Consistency_SESSION) {
[email protected]a81120b62014-04-18 01:56:03156 change_type = SessionStudyGroupChanged(processed_studies[i],
157 selected_group);
158 }
159
160 switch (change_type) {
161 case NO_CHANGE:
162 break;
163 case CHANGED:
164 ++result.normal_group_change_count;
165 break;
166 case CHANGED_KILL_BEST_EFFORT:
167 ++result.kill_best_effort_group_change_count;
168 break;
169 case CHANGED_KILL_CRITICAL:
170 ++result.kill_critical_group_change_count;
171 break;
[email protected]92e09a182014-01-08 18:21:00172 }
173 }
174
175 // TODO(asvitkine): Handle removed studies (i.e. studies that existed in the
176 // old seed, but were removed). This will require tracking the set of studies
177 // that were created from the original seed.
178
[email protected]a81120b62014-04-18 01:56:03179 return result;
[email protected]92e09a182014-01-08 18:21:00180}
181
[email protected]a81120b62014-04-18 01:56:03182VariationsSeedSimulator::ChangeType
183VariationsSeedSimulator::ConvertExperimentTypeToChangeType(
184 Study_Experiment_Type type) {
185 switch (type) {
186 case Study_Experiment_Type_NORMAL:
187 return CHANGED;
188 case Study_Experiment_Type_IGNORE_CHANGE:
189 return NO_CHANGE;
190 case Study_Experiment_Type_KILL_BEST_EFFORT:
191 return CHANGED_KILL_BEST_EFFORT;
192 case Study_Experiment_Type_KILL_CRITICAL:
193 return CHANGED_KILL_CRITICAL;
194 }
195 return CHANGED;
196}
197
198VariationsSeedSimulator::ChangeType
199VariationsSeedSimulator::PermanentStudyGroupChanged(
[email protected]92e09a182014-01-08 18:21:00200 const ProcessedStudy& processed_study,
201 const std::string& selected_group) {
202 const Study& study = *processed_study.study();
203 DCHECK_EQ(Study_Consistency_PERMANENT, study.consistency());
204
jwd67c08f752016-05-18 21:04:59205 const base::FieldTrial::EntropyProvider& entropy_provider =
206 VariationsSeedProcessor::ShouldStudyUseLowEntropy(study)
207 ? low_entropy_provider_
208 : default_entropy_provider_;
209
210 const std::string simulated_group =
211 SimulateGroupAssignment(entropy_provider, processed_study);
Alexei Svitkine59e4b212018-05-04 18:05:44212
213 // Note: The current (i.e. old) group is checked for the type since that group
214 // is the one that should be annotated with the type when killing it.
[email protected]92e09a182014-01-08 18:21:00215 const Study_Experiment* experiment = FindExperiment(study, selected_group);
[email protected]a81120b62014-04-18 01:56:03216 if (simulated_group != selected_group) {
217 if (experiment)
218 return ConvertExperimentTypeToChangeType(experiment->type());
219 return CHANGED;
220 }
221
Alexei Svitkine59e4b212018-05-04 18:05:44222 // If the group is unchanged, check whether its params may have changed.
223 if (experiment && !VariationParamsAreEqual(study, *experiment))
[email protected]a81120b62014-04-18 01:56:03224 return ConvertExperimentTypeToChangeType(experiment->type());
Alexei Svitkine59e4b212018-05-04 18:05:44225
226 // Since the group name has not changed and params are either equal or the
227 // experiment was not found (and thus there are none), return NO_CHANGE.
[email protected]a81120b62014-04-18 01:56:03228 return NO_CHANGE;
[email protected]92e09a182014-01-08 18:21:00229}
230
[email protected]a81120b62014-04-18 01:56:03231VariationsSeedSimulator::ChangeType
232VariationsSeedSimulator::SessionStudyGroupChanged(
[email protected]92e09a182014-01-08 18:21:00233 const ProcessedStudy& processed_study,
234 const std::string& selected_group) {
235 const Study& study = *processed_study.study();
236 DCHECK_EQ(Study_Consistency_SESSION, study.consistency());
237
[email protected]a81120b62014-04-18 01:56:03238 const Study_Experiment* experiment = FindExperiment(study, selected_group);
[email protected]92e09a182014-01-08 18:21:00239 if (processed_study.is_expired() &&
240 selected_group != study.default_experiment_name()) {
241 // An expired study will result in the default group being selected - mark
242 // it as changed if the current group differs from the default.
[email protected]a81120b62014-04-18 01:56:03243 if (experiment)
244 return ConvertExperimentTypeToChangeType(experiment->type());
245 return CHANGED;
[email protected]92e09a182014-01-08 18:21:00246 }
247
[email protected]92e09a182014-01-08 18:21:00248 if (!experiment)
[email protected]a81120b62014-04-18 01:56:03249 return CHANGED;
[email protected]92e09a182014-01-08 18:21:00250 if (experiment->probability_weight() == 0 &&
251 !experiment->has_forcing_flag()) {
[email protected]a81120b62014-04-18 01:56:03252 return ConvertExperimentTypeToChangeType(experiment->type());
[email protected]92e09a182014-01-08 18:21:00253 }
254
255 // Current group exists in the study - check whether its params changed.
[email protected]a81120b62014-04-18 01:56:03256 if (!VariationParamsAreEqual(study, *experiment))
257 return ConvertExperimentTypeToChangeType(experiment->type());
258 return NO_CHANGE;
[email protected]92e09a182014-01-08 18:21:00259}
260
[email protected]59b6f672014-07-26 18:35:47261} // namespace variations