Home | History | Annotate | Download | only in variations
      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.
     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().
     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.
     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.
     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 
     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 
    100 VariationsSeedSimulator::Result::~Result() {
    101 }
    102 
    103 VariationsSeedSimulator::VariationsSeedSimulator(
    104     const base::FieldTrial::EntropyProvider& entropy_provider)
    105     : entropy_provider_(entropy_provider) {
    106 }
    107 
    108 VariationsSeedSimulator::~VariationsSeedSimulator() {
    109 }
    110 
    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 
    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
    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
    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
    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