123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263 |
- // 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 <stddef.h>
- #include <map>
- #include "base/metrics/field_trial.h"
- #include "components/variations/client_filterable_state.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"
- #include "components/variations/variations_layers.h"
- #include "components/variations/variations_seed_processor.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 (auto& group : trial_groups)
- (*current_state)[group.trial_name] = group.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(),
- processed_study.GetDefaultExperimentName(), 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 nullptr;
- }
- // 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& default_entropy_provider,
- const base::FieldTrial::EntropyProvider& low_entropy_provider)
- : default_entropy_provider_(default_entropy_provider),
- low_entropy_provider_(low_entropy_provider) {}
- VariationsSeedSimulator::~VariationsSeedSimulator() {
- }
- VariationsSeedSimulator::Result VariationsSeedSimulator::SimulateSeedStudies(
- const VariationsSeed& seed,
- const ClientFilterableState& client_state) {
- std::vector<ProcessedStudy> filtered_studies;
- VariationsLayers layers(seed, &low_entropy_provider_);
- FilterAndValidateStudies(seed, client_state, layers, &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 base::FieldTrial::EntropyProvider& entropy_provider =
- VariationsSeedProcessor::ShouldStudyUseLowEntropy(study)
- ? low_entropy_provider_
- : default_entropy_provider_;
- const std::string simulated_group =
- SimulateGroupAssignment(entropy_provider, processed_study);
- // Note: The current (i.e. old) group is checked for the type since that group
- // is the one that should be annotated with the type when killing it.
- const Study_Experiment* experiment = FindExperiment(study, selected_group);
- if (simulated_group != selected_group) {
- if (experiment)
- return ConvertExperimentTypeToChangeType(experiment->type());
- return CHANGED;
- }
- // If the group is unchanged, check whether its params may have changed.
- if (experiment && !VariationParamsAreEqual(study, *experiment))
- return ConvertExperimentTypeToChangeType(experiment->type());
- // Since the group name has not changed and params are either equal or the
- // experiment was not found (and thus there are none), return NO_CHANGE.
- 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
|