1// Copyright 2013 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_processor.h" 6 7#include <map> 8#include <vector> 9 10#include "base/command_line.h" 11#include "base/metrics/field_trial.h" 12#include "base/strings/utf_string_conversions.h" 13#include "components/variations/processed_study.h" 14#include "components/variations/study_filtering.h" 15#include "components/variations/variations_associated_data.h" 16 17namespace variations { 18 19namespace { 20 21// Associates the variations params of |experiment|, if present. 22void RegisterExperimentParams(const Study& study, 23 const Study_Experiment& experiment) { 24 std::map<std::string, std::string> params; 25 for (int i = 0; i < experiment.param_size(); ++i) { 26 if (experiment.param(i).has_name() && experiment.param(i).has_value()) 27 params[experiment.param(i).name()] = experiment.param(i).value(); 28 } 29 if (!params.empty()) 30 AssociateVariationParams(study.name(), experiment.name(), params); 31} 32 33// If there are variation ids associated with |experiment|, register the 34// variation ids. 35void RegisterVariationIds(const Study_Experiment& experiment, 36 const std::string& trial_name) { 37 if (experiment.has_google_web_experiment_id()) { 38 const VariationID variation_id = 39 static_cast<VariationID>(experiment.google_web_experiment_id()); 40 AssociateGoogleVariationIDForce(GOOGLE_WEB_PROPERTIES, 41 trial_name, 42 experiment.name(), 43 variation_id); 44 } 45 if (experiment.has_google_web_trigger_experiment_id()) { 46 const VariationID variation_id = 47 static_cast<VariationID>(experiment.google_web_trigger_experiment_id()); 48 AssociateGoogleVariationIDForce(GOOGLE_WEB_PROPERTIES_TRIGGER, 49 trial_name, 50 experiment.name(), 51 variation_id); 52 } 53 if (experiment.has_google_update_experiment_id()) { 54 const VariationID variation_id = 55 static_cast<VariationID>(experiment.google_update_experiment_id()); 56 AssociateGoogleVariationIDForce(GOOGLE_UPDATE_SERVICE, 57 trial_name, 58 experiment.name(), 59 variation_id); 60 } 61} 62 63// Executes |callback| on every override defined by |experiment|. 64void ApplyUIStringOverrides( 65 const Study_Experiment& experiment, 66 const VariationsSeedProcessor::UIStringOverrideCallback& callback) { 67 for (int i = 0; i < experiment.override_ui_string_size(); ++i) { 68 const Study_Experiment_OverrideUIString& override = 69 experiment.override_ui_string(i); 70 callback.Run(override.name_hash(), base::UTF8ToUTF16(override.value())); 71 } 72} 73 74} // namespace 75 76VariationsSeedProcessor::VariationsSeedProcessor() { 77} 78 79VariationsSeedProcessor::~VariationsSeedProcessor() { 80} 81 82void VariationsSeedProcessor::CreateTrialsFromSeed( 83 const VariationsSeed& seed, 84 const std::string& locale, 85 const base::Time& reference_date, 86 const base::Version& version, 87 Study_Channel channel, 88 Study_FormFactor form_factor, 89 const std::string& hardware_class, 90 const UIStringOverrideCallback& override_callback) { 91 std::vector<ProcessedStudy> filtered_studies; 92 FilterAndValidateStudies(seed, locale, reference_date, version, channel, 93 form_factor, hardware_class, &filtered_studies); 94 95 for (size_t i = 0; i < filtered_studies.size(); ++i) 96 CreateTrialFromStudy(filtered_studies[i], override_callback); 97} 98 99void VariationsSeedProcessor::CreateTrialFromStudy( 100 const ProcessedStudy& processed_study, 101 const UIStringOverrideCallback& override_callback) { 102 const Study& study = *processed_study.study(); 103 104 // Check if any experiments need to be forced due to a command line 105 // flag. Force the first experiment with an existing flag. 106 CommandLine* command_line = CommandLine::ForCurrentProcess(); 107 for (int i = 0; i < study.experiment_size(); ++i) { 108 const Study_Experiment& experiment = study.experiment(i); 109 if (experiment.has_forcing_flag() && 110 command_line->HasSwitch(experiment.forcing_flag())) { 111 scoped_refptr<base::FieldTrial> trial( 112 base::FieldTrialList::CreateFieldTrial(study.name(), 113 experiment.name())); 114 // If |trial| is NULL, then there might already be a trial forced to a 115 // different group (e.g. via --force-fieldtrials). Break out of the loop, 116 // but don't return, so that variation ids and params for the selected 117 // group will still be picked up. 118 if (!trial.get()) 119 break; 120 121 RegisterExperimentParams(study, experiment); 122 RegisterVariationIds(experiment, study.name()); 123 if (study.activation_type() == Study_ActivationType_ACTIVATION_AUTO) { 124 trial->group(); 125 // UI Strings can only be overridden from ACTIVATION_AUTO experiments. 126 ApplyUIStringOverrides(experiment, override_callback); 127 } 128 129 DVLOG(1) << "Trial " << study.name() << " forced by flag: " 130 << experiment.forcing_flag(); 131 return; 132 } 133 } 134 135 uint32 randomization_seed = 0; 136 base::FieldTrial::RandomizationType randomization_type = 137 base::FieldTrial::SESSION_RANDOMIZED; 138 if (study.has_consistency() && 139 study.consistency() == Study_Consistency_PERMANENT) { 140 randomization_type = base::FieldTrial::ONE_TIME_RANDOMIZED; 141 if (study.has_randomization_seed()) 142 randomization_seed = study.randomization_seed(); 143 } 144 145 // The trial is created without specifying an expiration date because the 146 // expiration check in field_trial.cc is based on the build date. Instead, 147 // the expiration check using |reference_date| is done explicitly below. 148 scoped_refptr<base::FieldTrial> trial( 149 base::FieldTrialList::FactoryGetFieldTrialWithRandomizationSeed( 150 study.name(), processed_study.total_probability(), 151 study.default_experiment_name(), 152 base::FieldTrialList::kNoExpirationYear, 1, 1, randomization_type, 153 randomization_seed, NULL)); 154 155 bool has_overrides = false; 156 for (int i = 0; i < study.experiment_size(); ++i) { 157 const Study_Experiment& experiment = study.experiment(i); 158 RegisterExperimentParams(study, experiment); 159 160 // Groups with forcing flags have probability 0 and will never be selected. 161 // Therefore, there's no need to add them to the field trial. 162 if (experiment.has_forcing_flag()) 163 continue; 164 165 if (experiment.name() != study.default_experiment_name()) 166 trial->AppendGroup(experiment.name(), experiment.probability_weight()); 167 168 RegisterVariationIds(experiment, study.name()); 169 170 has_overrides = has_overrides || experiment.override_ui_string_size() > 0; 171 } 172 173 trial->SetForced(); 174 if (processed_study.is_expired()) { 175 trial->Disable(); 176 } else if (study.activation_type() == Study_ActivationType_ACTIVATION_AUTO) { 177 const std::string& group_name = trial->group_name(); 178 179 // Don't try to apply overrides if none of the experiments in this study had 180 // any. 181 if (!has_overrides) 182 return; 183 184 // UI Strings can only be overridden from ACTIVATION_AUTO experiments. 185 int experiment_index = processed_study.GetExperimentIndexByName(group_name); 186 187 // The field trial was defined from |study|, so the active experiment's name 188 // must be in the |study|. 189 DCHECK_NE(-1, experiment_index); 190 191 ApplyUIStringOverrides(study.experiment(experiment_index), 192 override_callback); 193 } 194} 195 196} // namespace variations 197