| // Copyright 2014 The Chromium Authors. All rights reserved. | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | #include "components/variations/variations_seed_simulator.h" | 
 |  | 
 | #include <map> | 
 |  | 
 | #include "base/metrics/field_trial.h" | 
 | #include "components/variations/processed_study.h" | 
 | #include "components/variations/proto/study.pb.h" | 
 | #include "components/variations/study_filtering.h" | 
 | #include "components/variations/variations_associated_data.h" | 
 |  | 
 | namespace variations { | 
 |  | 
 | namespace { | 
 |  | 
 | // Fills in |current_state| with the current process' active field trials, as a | 
 | // map of trial names to group names. | 
 | void GetCurrentTrialState(std::map<std::string, std::string>* current_state) { | 
 |   base::FieldTrial::ActiveGroups trial_groups; | 
 |   base::FieldTrialList::GetActiveFieldTrialGroups(&trial_groups); | 
 |   for (size_t i = 0; i < trial_groups.size(); ++i) | 
 |     (*current_state)[trial_groups[i].trial_name] = trial_groups[i].group_name; | 
 | } | 
 |  | 
 | // Simulate group assignment for the specified study with PERMANENT consistency. | 
 | // Returns the experiment group that will be selected. Mirrors logic in | 
 | // VariationsSeedProcessor::CreateTrialFromStudy(). | 
 | std::string SimulateGroupAssignment( | 
 |     const base::FieldTrial::EntropyProvider& entropy_provider, | 
 |     const ProcessedStudy& processed_study) { | 
 |   const Study& study = *processed_study.study(); | 
 |   DCHECK_EQ(Study_Consistency_PERMANENT, study.consistency()); | 
 |  | 
 |   const double entropy_value = | 
 |       entropy_provider.GetEntropyForTrial(study.name(), | 
 |                                           study.randomization_seed()); | 
 |   scoped_refptr<base::FieldTrial> trial( | 
 |       base::FieldTrial::CreateSimulatedFieldTrial( | 
 |           study.name(), processed_study.total_probability(), | 
 |           study.default_experiment_name(), entropy_value)); | 
 |  | 
 |   for (int i = 0; i < study.experiment_size(); ++i) { | 
 |     const Study_Experiment& experiment = study.experiment(i); | 
 |     // TODO(asvitkine): This needs to properly handle the case where a group was | 
 |     // forced via forcing_flag in the current state, so that it is not treated | 
 |     // as changed. | 
 |     if (!experiment.has_forcing_flag() && | 
 |         experiment.name() != study.default_experiment_name()) { | 
 |       trial->AppendGroup(experiment.name(), experiment.probability_weight()); | 
 |     } | 
 |   } | 
 |   if (processed_study.is_expired()) | 
 |     trial->Disable(); | 
 |   return trial->group_name(); | 
 | } | 
 |  | 
 | // Finds an experiment in |study| with name |experiment_name| and returns it, | 
 | // or NULL if it does not exist. | 
 | const Study_Experiment* FindExperiment(const Study& study, | 
 |                                        const std::string& experiment_name) { | 
 |   for (int i = 0; i < study.experiment_size(); ++i) { | 
 |     if (study.experiment(i).name() == experiment_name) | 
 |       return &study.experiment(i); | 
 |   } | 
 |   return NULL; | 
 | } | 
 |  | 
 | // Checks whether experiment params set for |experiment| on |study| are exactly | 
 | // equal to the params registered for the corresponding field trial in the | 
 | // current process. | 
 | bool VariationParamsAreEqual(const Study& study, | 
 |                              const Study_Experiment& experiment) { | 
 |   std::map<std::string, std::string> params; | 
 |   GetVariationParams(study.name(), ¶ms); | 
 |  | 
 |   if (static_cast<int>(params.size()) != experiment.param_size()) | 
 |     return false; | 
 |  | 
 |   for (int i = 0; i < experiment.param_size(); ++i) { | 
 |     std::map<std::string, std::string>::const_iterator it = | 
 |         params.find(experiment.param(i).name()); | 
 |     if (it == params.end() || it->second != experiment.param(i).value()) | 
 |       return false; | 
 |   } | 
 |  | 
 |   return true; | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | VariationsSeedSimulator::Result::Result() | 
 |     : normal_group_change_count(0), | 
 |       kill_best_effort_group_change_count(0), | 
 |       kill_critical_group_change_count(0) { | 
 | } | 
 |  | 
 | VariationsSeedSimulator::Result::~Result() { | 
 | } | 
 |  | 
 | VariationsSeedSimulator::VariationsSeedSimulator( | 
 |     const base::FieldTrial::EntropyProvider& entropy_provider) | 
 |     : entropy_provider_(entropy_provider) { | 
 | } | 
 |  | 
 | VariationsSeedSimulator::~VariationsSeedSimulator() { | 
 | } | 
 |  | 
 | VariationsSeedSimulator::Result VariationsSeedSimulator::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, | 
 |     const std::string& session_consistency_country, | 
 |     const std::string& permanent_consistency_country) { | 
 |   std::vector<ProcessedStudy> filtered_studies; | 
 |   FilterAndValidateStudies(seed, locale, reference_date, version, channel, | 
 |                            form_factor, hardware_class, | 
 |                            session_consistency_country, | 
 |                            permanent_consistency_country, &filtered_studies); | 
 |  | 
 |   return ComputeDifferences(filtered_studies); | 
 | } | 
 |  | 
 | VariationsSeedSimulator::Result VariationsSeedSimulator::ComputeDifferences( | 
 |     const std::vector<ProcessedStudy>& processed_studies) { | 
 |   std::map<std::string, std::string> current_state; | 
 |   GetCurrentTrialState(¤t_state); | 
 |  | 
 |   Result result; | 
 |   for (size_t i = 0; i < processed_studies.size(); ++i) { | 
 |     const Study& study = *processed_studies[i].study(); | 
 |     std::map<std::string, std::string>::const_iterator it = | 
 |         current_state.find(study.name()); | 
 |  | 
 |     // Skip studies that aren't activated in the current state. | 
 |     // TODO(asvitkine): This should be handled more intelligently. There are | 
 |     // several cases that fall into this category: | 
 |     //   1) There's an existing field trial with this name but it is not active. | 
 |     //   2) There's an existing expired field trial with this name, which is | 
 |     //      also not considered as active. | 
 |     //   3) This is a new study config that previously didn't exist. | 
 |     // The above cases should be differentiated and handled explicitly. | 
 |     if (it == current_state.end()) | 
 |       continue; | 
 |  | 
 |     // Study exists in the current state, check whether its group will change. | 
 |     // Note: The logic below does the right thing if study consistency changes, | 
 |     // as it doesn't rely on the previous study consistency. | 
 |     const std::string& selected_group = it->second; | 
 |     ChangeType change_type = NO_CHANGE; | 
 |     if (study.consistency() == Study_Consistency_PERMANENT) { | 
 |       change_type = PermanentStudyGroupChanged(processed_studies[i], | 
 |                                                selected_group); | 
 |     } else if (study.consistency() == Study_Consistency_SESSION) { | 
 |       change_type = SessionStudyGroupChanged(processed_studies[i], | 
 |                                              selected_group); | 
 |     } | 
 |  | 
 |     switch (change_type) { | 
 |       case NO_CHANGE: | 
 |         break; | 
 |       case CHANGED: | 
 |         ++result.normal_group_change_count; | 
 |         break; | 
 |       case CHANGED_KILL_BEST_EFFORT: | 
 |         ++result.kill_best_effort_group_change_count; | 
 |         break; | 
 |       case CHANGED_KILL_CRITICAL: | 
 |         ++result.kill_critical_group_change_count; | 
 |         break; | 
 |     } | 
 |   } | 
 |  | 
 |   // TODO(asvitkine): Handle removed studies (i.e. studies that existed in the | 
 |   // old seed, but were removed). This will require tracking the set of studies | 
 |   // that were created from the original seed. | 
 |  | 
 |   return result; | 
 | } | 
 |  | 
 | VariationsSeedSimulator::ChangeType | 
 | VariationsSeedSimulator::ConvertExperimentTypeToChangeType( | 
 |     Study_Experiment_Type type) { | 
 |   switch (type) { | 
 |     case Study_Experiment_Type_NORMAL: | 
 |       return CHANGED; | 
 |     case Study_Experiment_Type_IGNORE_CHANGE: | 
 |       return NO_CHANGE; | 
 |     case Study_Experiment_Type_KILL_BEST_EFFORT: | 
 |       return CHANGED_KILL_BEST_EFFORT; | 
 |     case Study_Experiment_Type_KILL_CRITICAL: | 
 |       return CHANGED_KILL_CRITICAL; | 
 |   } | 
 |   return CHANGED; | 
 | } | 
 |  | 
 | VariationsSeedSimulator::ChangeType | 
 | VariationsSeedSimulator::PermanentStudyGroupChanged( | 
 |     const ProcessedStudy& processed_study, | 
 |     const std::string& selected_group) { | 
 |   const Study& study = *processed_study.study(); | 
 |   DCHECK_EQ(Study_Consistency_PERMANENT, study.consistency()); | 
 |  | 
 |   const std::string simulated_group = SimulateGroupAssignment(entropy_provider_, | 
 |                                                               processed_study); | 
 |   const Study_Experiment* experiment = FindExperiment(study, selected_group); | 
 |   if (simulated_group != selected_group) { | 
 |     if (experiment) | 
 |       return ConvertExperimentTypeToChangeType(experiment->type()); | 
 |     return CHANGED; | 
 |   } | 
 |  | 
 |   // Current group exists in the study - check whether its params changed. | 
 |   DCHECK(experiment); | 
 |   if (!VariationParamsAreEqual(study, *experiment)) | 
 |     return ConvertExperimentTypeToChangeType(experiment->type()); | 
 |   return NO_CHANGE; | 
 | } | 
 |  | 
 | VariationsSeedSimulator::ChangeType | 
 | VariationsSeedSimulator::SessionStudyGroupChanged( | 
 |     const ProcessedStudy& processed_study, | 
 |     const std::string& selected_group) { | 
 |   const Study& study = *processed_study.study(); | 
 |   DCHECK_EQ(Study_Consistency_SESSION, study.consistency()); | 
 |  | 
 |   const Study_Experiment* experiment = FindExperiment(study, selected_group); | 
 |   if (processed_study.is_expired() && | 
 |       selected_group != study.default_experiment_name()) { | 
 |     // An expired study will result in the default group being selected - mark | 
 |     // it as changed if the current group differs from the default. | 
 |     if (experiment) | 
 |       return ConvertExperimentTypeToChangeType(experiment->type()); | 
 |     return CHANGED; | 
 |   } | 
 |  | 
 |   if (!experiment) | 
 |     return CHANGED; | 
 |   if (experiment->probability_weight() == 0 && | 
 |       !experiment->has_forcing_flag()) { | 
 |     return ConvertExperimentTypeToChangeType(experiment->type()); | 
 |   } | 
 |  | 
 |   // Current group exists in the study - check whether its params changed. | 
 |   if (!VariationParamsAreEqual(study, *experiment)) | 
 |     return ConvertExperimentTypeToChangeType(experiment->type()); | 
 |   return NO_CHANGE; | 
 | } | 
 |  | 
 | }  // namespace variations |