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 15namespace chrome_variations { 16 17namespace { 18 19// Fills in |current_state| with the current process' active field trials, as a 20// map of trial names to group names. 21void 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(). 31std::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. 62const 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. 74bool VariationParamsAreEqual(const Study& study, 75 const Study_Experiment& experiment) { 76 std::map<std::string, std::string> params; 77 GetVariationParams(study.name(), ¶ms); 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 94VariationsSeedSimulator::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 100VariationsSeedSimulator::Result::~Result() { 101} 102 103VariationsSeedSimulator::VariationsSeedSimulator( 104 const base::FieldTrial::EntropyProvider& entropy_provider) 105 : entropy_provider_(entropy_provider) { 106} 107 108VariationsSeedSimulator::~VariationsSeedSimulator() { 109} 110 111VariationsSeedSimulator::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 126VariationsSeedSimulator::Result VariationsSeedSimulator::ComputeDifferences( 127 const std::vector<ProcessedStudy>& processed_studies) { 128 std::map<std::string, std::string> current_state; 129 GetCurrentTrialState(¤t_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 183VariationsSeedSimulator::ChangeType 184VariationsSeedSimulator::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 199VariationsSeedSimulator::ChangeType 200VariationsSeedSimulator::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 222VariationsSeedSimulator::ChangeType 223VariationsSeedSimulator::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