variations_seed_simulator.cc 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  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. #include "components/variations/variations_seed_simulator.h"
  5. #include <stddef.h>
  6. #include <map>
  7. #include "base/metrics/field_trial.h"
  8. #include "components/variations/client_filterable_state.h"
  9. #include "components/variations/processed_study.h"
  10. #include "components/variations/proto/study.pb.h"
  11. #include "components/variations/study_filtering.h"
  12. #include "components/variations/variations_associated_data.h"
  13. #include "components/variations/variations_layers.h"
  14. #include "components/variations/variations_seed_processor.h"
  15. namespace variations {
  16. namespace {
  17. // Fills in |current_state| with the current process' active field trials, as a
  18. // map of trial names to group names.
  19. void GetCurrentTrialState(std::map<std::string, std::string>* current_state) {
  20. base::FieldTrial::ActiveGroups trial_groups;
  21. base::FieldTrialList::GetActiveFieldTrialGroups(&trial_groups);
  22. for (auto& group : trial_groups)
  23. (*current_state)[group.trial_name] = group.group_name;
  24. }
  25. // Simulate group assignment for the specified study with PERMANENT consistency.
  26. // Returns the experiment group that will be selected. Mirrors logic in
  27. // VariationsSeedProcessor::CreateTrialFromStudy().
  28. std::string SimulateGroupAssignment(
  29. const base::FieldTrial::EntropyProvider& entropy_provider,
  30. const ProcessedStudy& processed_study) {
  31. const Study& study = *processed_study.study();
  32. DCHECK_EQ(Study_Consistency_PERMANENT, study.consistency());
  33. const double entropy_value =
  34. entropy_provider.GetEntropyForTrial(study.name(),
  35. study.randomization_seed());
  36. scoped_refptr<base::FieldTrial> trial(
  37. base::FieldTrial::CreateSimulatedFieldTrial(
  38. study.name(), processed_study.total_probability(),
  39. processed_study.GetDefaultExperimentName(), entropy_value));
  40. for (int i = 0; i < study.experiment_size(); ++i) {
  41. const Study_Experiment& experiment = study.experiment(i);
  42. // TODO(asvitkine): This needs to properly handle the case where a group was
  43. // forced via forcing_flag in the current state, so that it is not treated
  44. // as changed.
  45. if (!experiment.has_forcing_flag() &&
  46. experiment.name() != study.default_experiment_name()) {
  47. trial->AppendGroup(experiment.name(), experiment.probability_weight());
  48. }
  49. }
  50. if (processed_study.is_expired())
  51. trial->Disable();
  52. return trial->group_name();
  53. }
  54. // Finds an experiment in |study| with name |experiment_name| and returns it,
  55. // or NULL if it does not exist.
  56. const Study_Experiment* FindExperiment(const Study& study,
  57. const std::string& experiment_name) {
  58. for (int i = 0; i < study.experiment_size(); ++i) {
  59. if (study.experiment(i).name() == experiment_name)
  60. return &study.experiment(i);
  61. }
  62. return nullptr;
  63. }
  64. // Checks whether experiment params set for |experiment| on |study| are exactly
  65. // equal to the params registered for the corresponding field trial in the
  66. // current process.
  67. bool VariationParamsAreEqual(const Study& study,
  68. const Study_Experiment& experiment) {
  69. std::map<std::string, std::string> params;
  70. GetVariationParams(study.name(), &params);
  71. if (static_cast<int>(params.size()) != experiment.param_size())
  72. return false;
  73. for (int i = 0; i < experiment.param_size(); ++i) {
  74. std::map<std::string, std::string>::const_iterator it =
  75. params.find(experiment.param(i).name());
  76. if (it == params.end() || it->second != experiment.param(i).value())
  77. return false;
  78. }
  79. return true;
  80. }
  81. } // namespace
  82. VariationsSeedSimulator::Result::Result()
  83. : normal_group_change_count(0),
  84. kill_best_effort_group_change_count(0),
  85. kill_critical_group_change_count(0) {
  86. }
  87. VariationsSeedSimulator::Result::~Result() {
  88. }
  89. VariationsSeedSimulator::VariationsSeedSimulator(
  90. const base::FieldTrial::EntropyProvider& default_entropy_provider,
  91. const base::FieldTrial::EntropyProvider& low_entropy_provider)
  92. : default_entropy_provider_(default_entropy_provider),
  93. low_entropy_provider_(low_entropy_provider) {}
  94. VariationsSeedSimulator::~VariationsSeedSimulator() {
  95. }
  96. VariationsSeedSimulator::Result VariationsSeedSimulator::SimulateSeedStudies(
  97. const VariationsSeed& seed,
  98. const ClientFilterableState& client_state) {
  99. std::vector<ProcessedStudy> filtered_studies;
  100. VariationsLayers layers(seed, &low_entropy_provider_);
  101. FilterAndValidateStudies(seed, client_state, layers, &filtered_studies);
  102. return ComputeDifferences(filtered_studies);
  103. }
  104. VariationsSeedSimulator::Result VariationsSeedSimulator::ComputeDifferences(
  105. const std::vector<ProcessedStudy>& processed_studies) {
  106. std::map<std::string, std::string> current_state;
  107. GetCurrentTrialState(&current_state);
  108. Result result;
  109. for (size_t i = 0; i < processed_studies.size(); ++i) {
  110. const Study& study = *processed_studies[i].study();
  111. std::map<std::string, std::string>::const_iterator it =
  112. current_state.find(study.name());
  113. // Skip studies that aren't activated in the current state.
  114. // TODO(asvitkine): This should be handled more intelligently. There are
  115. // several cases that fall into this category:
  116. // 1) There's an existing field trial with this name but it is not active.
  117. // 2) There's an existing expired field trial with this name, which is
  118. // also not considered as active.
  119. // 3) This is a new study config that previously didn't exist.
  120. // The above cases should be differentiated and handled explicitly.
  121. if (it == current_state.end())
  122. continue;
  123. // Study exists in the current state, check whether its group will change.
  124. // Note: The logic below does the right thing if study consistency changes,
  125. // as it doesn't rely on the previous study consistency.
  126. const std::string& selected_group = it->second;
  127. ChangeType change_type = NO_CHANGE;
  128. if (study.consistency() == Study_Consistency_PERMANENT) {
  129. change_type = PermanentStudyGroupChanged(processed_studies[i],
  130. selected_group);
  131. } else if (study.consistency() == Study_Consistency_SESSION) {
  132. change_type = SessionStudyGroupChanged(processed_studies[i],
  133. selected_group);
  134. }
  135. switch (change_type) {
  136. case NO_CHANGE:
  137. break;
  138. case CHANGED:
  139. ++result.normal_group_change_count;
  140. break;
  141. case CHANGED_KILL_BEST_EFFORT:
  142. ++result.kill_best_effort_group_change_count;
  143. break;
  144. case CHANGED_KILL_CRITICAL:
  145. ++result.kill_critical_group_change_count;
  146. break;
  147. }
  148. }
  149. // TODO(asvitkine): Handle removed studies (i.e. studies that existed in the
  150. // old seed, but were removed). This will require tracking the set of studies
  151. // that were created from the original seed.
  152. return result;
  153. }
  154. VariationsSeedSimulator::ChangeType
  155. VariationsSeedSimulator::ConvertExperimentTypeToChangeType(
  156. Study_Experiment_Type type) {
  157. switch (type) {
  158. case Study_Experiment_Type_NORMAL:
  159. return CHANGED;
  160. case Study_Experiment_Type_IGNORE_CHANGE:
  161. return NO_CHANGE;
  162. case Study_Experiment_Type_KILL_BEST_EFFORT:
  163. return CHANGED_KILL_BEST_EFFORT;
  164. case Study_Experiment_Type_KILL_CRITICAL:
  165. return CHANGED_KILL_CRITICAL;
  166. }
  167. return CHANGED;
  168. }
  169. VariationsSeedSimulator::ChangeType
  170. VariationsSeedSimulator::PermanentStudyGroupChanged(
  171. const ProcessedStudy& processed_study,
  172. const std::string& selected_group) {
  173. const Study& study = *processed_study.study();
  174. DCHECK_EQ(Study_Consistency_PERMANENT, study.consistency());
  175. const base::FieldTrial::EntropyProvider& entropy_provider =
  176. VariationsSeedProcessor::ShouldStudyUseLowEntropy(study)
  177. ? low_entropy_provider_
  178. : default_entropy_provider_;
  179. const std::string simulated_group =
  180. SimulateGroupAssignment(entropy_provider, processed_study);
  181. // Note: The current (i.e. old) group is checked for the type since that group
  182. // is the one that should be annotated with the type when killing it.
  183. const Study_Experiment* experiment = FindExperiment(study, selected_group);
  184. if (simulated_group != selected_group) {
  185. if (experiment)
  186. return ConvertExperimentTypeToChangeType(experiment->type());
  187. return CHANGED;
  188. }
  189. // If the group is unchanged, check whether its params may have changed.
  190. if (experiment && !VariationParamsAreEqual(study, *experiment))
  191. return ConvertExperimentTypeToChangeType(experiment->type());
  192. // Since the group name has not changed and params are either equal or the
  193. // experiment was not found (and thus there are none), return NO_CHANGE.
  194. return NO_CHANGE;
  195. }
  196. VariationsSeedSimulator::ChangeType
  197. VariationsSeedSimulator::SessionStudyGroupChanged(
  198. const ProcessedStudy& processed_study,
  199. const std::string& selected_group) {
  200. const Study& study = *processed_study.study();
  201. DCHECK_EQ(Study_Consistency_SESSION, study.consistency());
  202. const Study_Experiment* experiment = FindExperiment(study, selected_group);
  203. if (processed_study.is_expired() &&
  204. selected_group != study.default_experiment_name()) {
  205. // An expired study will result in the default group being selected - mark
  206. // it as changed if the current group differs from the default.
  207. if (experiment)
  208. return ConvertExperimentTypeToChangeType(experiment->type());
  209. return CHANGED;
  210. }
  211. if (!experiment)
  212. return CHANGED;
  213. if (experiment->probability_weight() == 0 &&
  214. !experiment->has_forcing_flag()) {
  215. return ConvertExperimentTypeToChangeType(experiment->type());
  216. }
  217. // Current group exists in the study - check whether its params changed.
  218. if (!VariationParamsAreEqual(study, *experiment))
  219. return ConvertExperimentTypeToChangeType(experiment->type());
  220. return NO_CHANGE;
  221. }
  222. } // namespace variations