Home | History | Annotate | Download | only in omnibox
      1 // Copyright (c) 2012 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 "chrome/browser/omnibox/omnibox_field_trial.h"
      6 
      7 #include <string>
      8 
      9 #include "base/metrics/field_trial.h"
     10 #include "base/strings/string_number_conversions.h"
     11 #include "base/strings/string_split.h"
     12 #include "base/strings/string_util.h"
     13 #include "base/strings/stringprintf.h"
     14 #include "chrome/browser/autocomplete/autocomplete_input.h"
     15 #include "chrome/browser/search/search.h"
     16 #include "chrome/common/metrics/metrics_util.h"
     17 #include "chrome/common/metrics/variations/variation_ids.h"
     18 #include "chrome/common/metrics/variations/variations_util.h"
     19 
     20 namespace {
     21 
     22 // Field trial names.
     23 const char kHUPCullRedirectsFieldTrialName[] = "OmniboxHUPCullRedirects";
     24 const char kHUPCreateShorterMatchFieldTrialName[] =
     25     "OmniboxHUPCreateShorterMatch";
     26 const char kStopTimerFieldTrialName[] = "OmniboxStopTimer";
     27 
     28 // The autocomplete dynamic field trial name prefix.  Each field trial is
     29 // configured dynamically and is retrieved automatically by Chrome during
     30 // the startup.
     31 const char kAutocompleteDynamicFieldTrialPrefix[] = "AutocompleteDynamicTrial_";
     32 // The maximum number of the autocomplete dynamic field trials (aka layers).
     33 const int kMaxAutocompleteDynamicFieldTrials = 5;
     34 
     35 // Field trial experiment probabilities.
     36 
     37 // For HistoryURL provider cull redirects field trial, put 0% ( = 0/100 )
     38 // of the users in the don't-cull-redirects experiment group.
     39 // TODO(mpearson): Remove this field trial and the code it uses once I'm
     40 // sure it's no longer needed.
     41 const base::FieldTrial::Probability kHUPCullRedirectsFieldTrialDivisor = 100;
     42 const base::FieldTrial::Probability
     43     kHUPCullRedirectsFieldTrialExperimentFraction = 0;
     44 
     45 // For HistoryURL provider create shorter match field trial, put 0%
     46 // ( = 25/100 ) of the users in the don't-create-a-shorter-match
     47 // experiment group.
     48 // TODO(mpearson): Remove this field trial and the code it uses once I'm
     49 // sure it's no longer needed.
     50 const base::FieldTrial::Probability
     51     kHUPCreateShorterMatchFieldTrialDivisor = 100;
     52 const base::FieldTrial::Probability
     53     kHUPCreateShorterMatchFieldTrialExperimentFraction = 0;
     54 
     55 // Experiment group names.
     56 
     57 const char kStopTimerExperimentGroupName[] = "UseStopTimer";
     58 
     59 // Field trial IDs.
     60 // Though they are not literally "const", they are set only once, in
     61 // ActivateStaticTrials() below.
     62 
     63 // Whether the static field trials have been initialized by
     64 // ActivateStaticTrials() method.
     65 bool static_field_trials_initialized = false;
     66 
     67 // Field trial ID for the HistoryURL provider cull redirects experiment group.
     68 int hup_dont_cull_redirects_experiment_group = 0;
     69 
     70 // Field trial ID for the HistoryURL provider create shorter match
     71 // experiment group.
     72 int hup_dont_create_shorter_match_experiment_group = 0;
     73 
     74 
     75 // Concatenates the autocomplete dynamic field trial prefix with a field trial
     76 // ID to form a complete autocomplete field trial name.
     77 std::string DynamicFieldTrialName(int id) {
     78   return base::StringPrintf("%s%d", kAutocompleteDynamicFieldTrialPrefix, id);
     79 }
     80 
     81 }  // namespace
     82 
     83 
     84 void OmniboxFieldTrial::ActivateStaticTrials() {
     85   DCHECK(!static_field_trials_initialized);
     86 
     87   // Create the HistoryURL provider cull redirects field trial.
     88   // Make it expire on March 1, 2013.
     89   scoped_refptr<base::FieldTrial> trial(
     90       base::FieldTrialList::FactoryGetFieldTrial(
     91           kHUPCullRedirectsFieldTrialName, kHUPCullRedirectsFieldTrialDivisor,
     92           "Standard", 2013, 3, 1, base::FieldTrial::ONE_TIME_RANDOMIZED, NULL));
     93   hup_dont_cull_redirects_experiment_group =
     94       trial->AppendGroup("DontCullRedirects",
     95                          kHUPCullRedirectsFieldTrialExperimentFraction);
     96 
     97   // Create the HistoryURL provider create shorter match field trial.
     98   // Make it expire on March 1, 2013.
     99   trial = base::FieldTrialList::FactoryGetFieldTrial(
    100       kHUPCreateShorterMatchFieldTrialName,
    101       kHUPCreateShorterMatchFieldTrialDivisor, "Standard", 2013, 3, 1,
    102       base::FieldTrial::ONE_TIME_RANDOMIZED, NULL);
    103   hup_dont_create_shorter_match_experiment_group =
    104       trial->AppendGroup("DontCreateShorterMatch",
    105                          kHUPCreateShorterMatchFieldTrialExperimentFraction);
    106 
    107   static_field_trials_initialized = true;
    108 }
    109 
    110 void OmniboxFieldTrial::ActivateDynamicTrials() {
    111   // Initialize all autocomplete dynamic field trials.  This method may be
    112   // called multiple times.
    113   for (int i = 0; i < kMaxAutocompleteDynamicFieldTrials; ++i)
    114     base::FieldTrialList::FindValue(DynamicFieldTrialName(i));
    115 }
    116 
    117 int OmniboxFieldTrial::GetDisabledProviderTypes() {
    118   // Make sure that Autocomplete dynamic field trials are activated.  It's OK to
    119   // call this method multiple times.
    120   ActivateDynamicTrials();
    121 
    122   // Look for group names in form of "DisabledProviders_<mask>" where "mask"
    123   // is a bitmap of disabled provider types (AutocompleteProvider::Type).
    124   int provider_types = 0;
    125   for (int i = 0; i < kMaxAutocompleteDynamicFieldTrials; ++i) {
    126     std::string group_name = base::FieldTrialList::FindFullName(
    127         DynamicFieldTrialName(i));
    128     const char kDisabledProviders[] = "DisabledProviders_";
    129     if (!StartsWithASCII(group_name, kDisabledProviders, true))
    130       continue;
    131     int types = 0;
    132     if (!base::StringToInt(base::StringPiece(
    133             group_name.substr(strlen(kDisabledProviders))), &types))
    134       continue;
    135     provider_types |= types;
    136   }
    137   return provider_types;
    138 }
    139 
    140 void OmniboxFieldTrial::GetActiveSuggestFieldTrialHashes(
    141     std::vector<uint32>* field_trial_hashes) {
    142   field_trial_hashes->clear();
    143   for (int i = 0; i < kMaxAutocompleteDynamicFieldTrials; ++i) {
    144     const std::string& trial_name = DynamicFieldTrialName(i);
    145     if (base::FieldTrialList::TrialExists(trial_name))
    146       field_trial_hashes->push_back(metrics::HashName(trial_name));
    147   }
    148 }
    149 
    150 bool OmniboxFieldTrial::InHUPCullRedirectsFieldTrial() {
    151   return base::FieldTrialList::TrialExists(kHUPCullRedirectsFieldTrialName);
    152 }
    153 
    154 bool OmniboxFieldTrial::InHUPCullRedirectsFieldTrialExperimentGroup() {
    155   if (!base::FieldTrialList::TrialExists(kHUPCullRedirectsFieldTrialName))
    156     return false;
    157 
    158   // Return true if we're in the experiment group.
    159   const int group = base::FieldTrialList::FindValue(
    160       kHUPCullRedirectsFieldTrialName);
    161   return group == hup_dont_cull_redirects_experiment_group;
    162 }
    163 
    164 bool OmniboxFieldTrial::InHUPCreateShorterMatchFieldTrial() {
    165   return
    166       base::FieldTrialList::TrialExists(kHUPCreateShorterMatchFieldTrialName);
    167 }
    168 
    169 bool OmniboxFieldTrial::InHUPCreateShorterMatchFieldTrialExperimentGroup() {
    170   if (!base::FieldTrialList::TrialExists(kHUPCreateShorterMatchFieldTrialName))
    171     return false;
    172 
    173   // Return true if we're in the experiment group.
    174   const int group = base::FieldTrialList::FindValue(
    175       kHUPCreateShorterMatchFieldTrialName);
    176   return group == hup_dont_create_shorter_match_experiment_group;
    177 }
    178 
    179 bool OmniboxFieldTrial::InStopTimerFieldTrialExperimentGroup() {
    180   return (base::FieldTrialList::FindFullName(kStopTimerFieldTrialName) ==
    181           kStopTimerExperimentGroupName);
    182 }
    183 
    184 bool OmniboxFieldTrial::InZeroSuggestFieldTrial() {
    185   // Make sure that Autocomplete dynamic field trials are activated.  It's OK to
    186   // call this method multiple times.
    187   ActivateDynamicTrials();
    188 
    189   // Look for group names starting with "EnableZeroSuggest"
    190   for (int i = 0; i < kMaxAutocompleteDynamicFieldTrials; ++i) {
    191     const std::string& group_name = base::FieldTrialList::FindFullName(
    192         DynamicFieldTrialName(i));
    193     const char kEnableZeroSuggest[] = "EnableZeroSuggest";
    194     if (StartsWithASCII(group_name, kEnableZeroSuggest, true))
    195       return true;
    196   }
    197   return false;
    198 }
    199 
    200 bool OmniboxFieldTrial::ShortcutsScoringMaxRelevance(
    201     AutocompleteInput::PageClassification current_page_classification,
    202     int* max_relevance) {
    203   // The value of the rule is a string that encodes an integer containing
    204   // the max relevance.
    205   const std::string& max_relevance_str =
    206       OmniboxFieldTrial::GetValueForRuleInContext(
    207           kShortcutsScoringMaxRelevanceRule, current_page_classification);
    208   if (max_relevance_str.empty())
    209     return false;
    210   if (!base::StringToInt(max_relevance_str, max_relevance))
    211     return false;
    212   return true;
    213 }
    214 
    215 bool OmniboxFieldTrial::SearchHistoryPreventInlining(
    216     AutocompleteInput::PageClassification current_page_classification) {
    217   return OmniboxFieldTrial::GetValueForRuleInContext(
    218       kSearchHistoryRule, current_page_classification) == "PreventInlining";
    219 }
    220 
    221 bool OmniboxFieldTrial::SearchHistoryDisable(
    222     AutocompleteInput::PageClassification current_page_classification) {
    223   return OmniboxFieldTrial::GetValueForRuleInContext(
    224       kSearchHistoryRule, current_page_classification) == "Disable";
    225 }
    226 
    227 void OmniboxFieldTrial::GetDemotionsByType(
    228     AutocompleteInput::PageClassification current_page_classification,
    229     DemotionMultipliers* demotions_by_type) {
    230   demotions_by_type->clear();
    231   const std::string demotion_rule =
    232       OmniboxFieldTrial::GetValueForRuleInContext(
    233           kDemoteByTypeRule,
    234           current_page_classification);
    235   // The value of the DemoteByType rule is a comma-separated list of
    236   // {ResultType + ":" + Number} where ResultType is an AutocompleteMatchType::
    237   // Type enum represented as an integer and Number is an integer number
    238   // between 0 and 100 inclusive.   Relevance scores of matches of that result
    239   // type are multiplied by Number / 100.  100 means no change.
    240   base::StringPairs kv_pairs;
    241   if (base::SplitStringIntoKeyValuePairs(demotion_rule, ':', ',', &kv_pairs)) {
    242     for (base::StringPairs::const_iterator it = kv_pairs.begin();
    243          it != kv_pairs.end(); ++it) {
    244       // This is a best-effort conversion; we trust the hand-crafted parameters
    245       // downloaded from the server to be perfect.  There's no need for handle
    246       // errors smartly.
    247       int k, v;
    248       base::StringToInt(it->first, &k);
    249       base::StringToInt(it->second, &v);
    250       (*demotions_by_type)[static_cast<AutocompleteMatchType::Type>(k)] =
    251           static_cast<float>(v) / 100.0f;
    252     }
    253   }
    254 }
    255 
    256 bool OmniboxFieldTrial::ReorderForLegalDefaultMatch(
    257     AutocompleteInput::PageClassification current_page_classification) {
    258   return OmniboxFieldTrial::GetValueForRuleInContext(
    259       kReorderForLegalDefaultMatchRule, current_page_classification) ==
    260       kReorderForLegalDefaultMatchRuleEnabled;
    261 }
    262 
    263 const char OmniboxFieldTrial::kBundledExperimentFieldTrialName[] =
    264     "OmniboxBundledExperimentV1";
    265 const char OmniboxFieldTrial::kShortcutsScoringMaxRelevanceRule[] =
    266     "ShortcutsScoringMaxRelevance";
    267 const char OmniboxFieldTrial::kSearchHistoryRule[] = "SearchHistory";
    268 const char OmniboxFieldTrial::kDemoteByTypeRule[] = "DemoteByType";
    269 const char OmniboxFieldTrial::kReorderForLegalDefaultMatchRule[] =
    270     "ReorderForLegalDefaultMatch";
    271 const char OmniboxFieldTrial::kReorderForLegalDefaultMatchRuleEnabled[] =
    272     "ReorderForLegalDefaultMatch";
    273 
    274 // Background and implementation details:
    275 //
    276 // Each experiment group in any field trial can come with an optional set of
    277 // parameters (key-value pairs).  In the bundled omnibox experiment
    278 // (kBundledExperimentFieldTrialName), each experiment group comes with a
    279 // list of parameters in the form:
    280 //   key=<Rule>:
    281 //       <AutocompleteInput::PageClassification (as an int)>:
    282 //       <whether Instant Extended is enabled (as a 1 or 0)>
    283 //     (note that there are no linebreaks in keys; this format is for
    284 //      presentation only>
    285 //   value=<arbitrary string>
    286 // Both the AutocompleteInput::PageClassification and the Instant Extended
    287 // entries can be "*", which means this rule applies for all values of the
    288 // matching portion of the context.
    289 // One example parameter is
    290 //   key=SearchHistory:6:1
    291 //   value=PreventInlining
    292 // This means in page classification context 6 (a search result page doing
    293 // search term replacement) with Instant Extended enabled, the SearchHistory
    294 // experiment should PreventInlining.
    295 //
    296 // When an exact match to the rule in the current context is missing, we
    297 // give preference to a wildcard rule that matches the instant extended
    298 // context over a wildcard rule that matches the page classification
    299 // context.  Hopefully, though, users will write their field trial configs
    300 // so as not to rely on this fall back order.
    301 //
    302 // In short, this function tries to find the value associated with key
    303 // |rule|:|page_classification|:|instant_extended|, failing that it looks up
    304 // |rule|:*:|instant_extended|, failing that it looks up
    305 // |rule|:|page_classification|:*, failing that it looks up |rule|:*:*,
    306 // and failing that it returns the empty string.
    307 std::string OmniboxFieldTrial::GetValueForRuleInContext(
    308     const std::string& rule,
    309     AutocompleteInput::PageClassification page_classification) {
    310   std::map<std::string, std::string> params;
    311   if (!chrome_variations::GetVariationParams(kBundledExperimentFieldTrialName,
    312                                              &params)) {
    313     return std::string();
    314   }
    315   const std::string page_classification_str =
    316       base::IntToString(static_cast<int>(page_classification));
    317   const std::string instant_extended =
    318       chrome::IsInstantExtendedAPIEnabled() ? "1" : "0";
    319   // Look up rule in this exact context.
    320   std::map<std::string, std::string>::iterator it = params.find(
    321       rule + ":" + page_classification_str + ":" + instant_extended);
    322   if (it != params.end())
    323     return it->second;
    324   // Fall back to the global page classification context.
    325   it = params.find(rule + ":*:" + instant_extended);
    326   if (it != params.end())
    327     return it->second;
    328   // Fall back to the global instant extended context.
    329   it = params.find(rule + ":" + page_classification_str + ":*");
    330   if (it != params.end())
    331     return it->second;
    332   // Look up rule in the global context.
    333   it = params.find(rule + ":*:*");
    334   return (it != params.end()) ? it->second : std::string();
    335 }
    336