| OLD | NEW |
| (Empty) |
| 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 <cmath> | |
| 8 #include <string> | |
| 9 | |
| 10 #include "base/command_line.h" | |
| 11 #include "base/metrics/field_trial.h" | |
| 12 #include "base/strings/string_number_conversions.h" | |
| 13 #include "base/strings/string_split.h" | |
| 14 #include "base/strings/string_util.h" | |
| 15 #include "base/strings/stringprintf.h" | |
| 16 #include "base/time/time.h" | |
| 17 #include "chrome/common/chrome_switches.h" | |
| 18 #include "chrome/common/variations/variation_ids.h" | |
| 19 #include "components/metrics/proto/omnibox_event.pb.h" | |
| 20 #include "components/search/search.h" | |
| 21 #include "components/variations/active_field_trials.h" | |
| 22 #include "components/variations/metrics_util.h" | |
| 23 #include "components/variations/variations_associated_data.h" | |
| 24 | |
| 25 using metrics::OmniboxEventProto; | |
| 26 | |
| 27 namespace { | |
| 28 | |
| 29 typedef std::map<std::string, std::string> VariationParams; | |
| 30 typedef HUPScoringParams::ScoreBuckets ScoreBuckets; | |
| 31 | |
| 32 // Field trial names. | |
| 33 const char kStopTimerFieldTrialName[] = "OmniboxStopTimer"; | |
| 34 | |
| 35 // The autocomplete dynamic field trial name prefix. Each field trial is | |
| 36 // configured dynamically and is retrieved automatically by Chrome during | |
| 37 // the startup. | |
| 38 const char kAutocompleteDynamicFieldTrialPrefix[] = "AutocompleteDynamicTrial_"; | |
| 39 // The maximum number of the autocomplete dynamic field trials (aka layers). | |
| 40 const int kMaxAutocompleteDynamicFieldTrials = 5; | |
| 41 | |
| 42 | |
| 43 // Concatenates the autocomplete dynamic field trial prefix with a field trial | |
| 44 // ID to form a complete autocomplete field trial name. | |
| 45 std::string DynamicFieldTrialName(int id) { | |
| 46 return base::StringPrintf("%s%d", kAutocompleteDynamicFieldTrialPrefix, id); | |
| 47 } | |
| 48 | |
| 49 void InitializeScoreBuckets(const VariationParams& params, | |
| 50 const char* relevance_cap_param, | |
| 51 const char* half_life_param, | |
| 52 const char* score_buckets_param, | |
| 53 ScoreBuckets* score_buckets) { | |
| 54 VariationParams::const_iterator it = params.find(relevance_cap_param); | |
| 55 if (it != params.end()) { | |
| 56 int relevance_cap; | |
| 57 if (base::StringToInt(it->second, &relevance_cap)) | |
| 58 score_buckets->set_relevance_cap(relevance_cap); | |
| 59 } | |
| 60 | |
| 61 it = params.find(half_life_param); | |
| 62 if (it != params.end()) { | |
| 63 int half_life_days; | |
| 64 if (base::StringToInt(it->second, &half_life_days)) | |
| 65 score_buckets->set_half_life_days(half_life_days); | |
| 66 } | |
| 67 | |
| 68 it = params.find(score_buckets_param); | |
| 69 if (it != params.end()) { | |
| 70 // The value of the score bucket is a comma-separated list of | |
| 71 // {DecayedCount + ":" + MaxRelevance}. | |
| 72 base::StringPairs kv_pairs; | |
| 73 if (base::SplitStringIntoKeyValuePairs(it->second, ':', ',', &kv_pairs)) { | |
| 74 for (base::StringPairs::const_iterator it = kv_pairs.begin(); | |
| 75 it != kv_pairs.end(); ++it) { | |
| 76 ScoreBuckets::CountMaxRelevance bucket; | |
| 77 base::StringToDouble(it->first, &bucket.first); | |
| 78 base::StringToInt(it->second, &bucket.second); | |
| 79 score_buckets->buckets().push_back(bucket); | |
| 80 } | |
| 81 std::sort(score_buckets->buckets().begin(), | |
| 82 score_buckets->buckets().end(), | |
| 83 std::greater<ScoreBuckets::CountMaxRelevance>()); | |
| 84 } | |
| 85 } | |
| 86 } | |
| 87 | |
| 88 } // namespace | |
| 89 | |
| 90 HUPScoringParams::ScoreBuckets::ScoreBuckets() | |
| 91 : relevance_cap_(-1), | |
| 92 half_life_days_(-1) { | |
| 93 } | |
| 94 | |
| 95 HUPScoringParams::ScoreBuckets::~ScoreBuckets() { | |
| 96 } | |
| 97 | |
| 98 double HUPScoringParams::ScoreBuckets::HalfLifeTimeDecay( | |
| 99 const base::TimeDelta& elapsed_time) const { | |
| 100 double time_ms; | |
| 101 if ((half_life_days_ <= 0) || | |
| 102 ((time_ms = elapsed_time.InMillisecondsF()) <= 0)) | |
| 103 return 1.0; | |
| 104 | |
| 105 const double half_life_intervals = | |
| 106 time_ms / base::TimeDelta::FromDays(half_life_days_).InMillisecondsF(); | |
| 107 return pow(2.0, -half_life_intervals); | |
| 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 if (base::FieldTrialList::TrialExists(kBundledExperimentFieldTrialName)) { | |
| 149 field_trial_hashes->push_back( | |
| 150 metrics::HashName(kBundledExperimentFieldTrialName)); | |
| 151 } | |
| 152 } | |
| 153 | |
| 154 base::TimeDelta OmniboxFieldTrial::StopTimerFieldTrialDuration() { | |
| 155 int stop_timer_ms; | |
| 156 if (base::StringToInt( | |
| 157 base::FieldTrialList::FindFullName(kStopTimerFieldTrialName), | |
| 158 &stop_timer_ms)) | |
| 159 return base::TimeDelta::FromMilliseconds(stop_timer_ms); | |
| 160 return base::TimeDelta::FromMilliseconds(1500); | |
| 161 } | |
| 162 | |
| 163 bool OmniboxFieldTrial::InZeroSuggestFieldTrial() { | |
| 164 if (variations::GetVariationParamValue( | |
| 165 kBundledExperimentFieldTrialName, kZeroSuggestRule) == "true") | |
| 166 return true; | |
| 167 if (variations::GetVariationParamValue( | |
| 168 kBundledExperimentFieldTrialName, kZeroSuggestRule) == "false") | |
| 169 return false; | |
| 170 #if defined(OS_WIN) || defined(OS_CHROMEOS) || defined(OS_LINUX) || \ | |
| 171 (defined(OS_MACOSX) && !defined(OS_IOS)) | |
| 172 return true; | |
| 173 #else | |
| 174 return false; | |
| 175 #endif | |
| 176 } | |
| 177 | |
| 178 bool OmniboxFieldTrial::InZeroSuggestMostVisitedFieldTrial() { | |
| 179 return variations::GetVariationParamValue( | |
| 180 kBundledExperimentFieldTrialName, | |
| 181 kZeroSuggestVariantRule) == "MostVisited"; | |
| 182 } | |
| 183 | |
| 184 bool OmniboxFieldTrial::InZeroSuggestAfterTypingFieldTrial() { | |
| 185 return variations::GetVariationParamValue( | |
| 186 kBundledExperimentFieldTrialName, | |
| 187 kZeroSuggestVariantRule) == "AfterTyping"; | |
| 188 } | |
| 189 | |
| 190 bool OmniboxFieldTrial::InZeroSuggestPersonalizedFieldTrial() { | |
| 191 return variations::GetVariationParamValue( | |
| 192 kBundledExperimentFieldTrialName, | |
| 193 kZeroSuggestVariantRule) == "Personalized"; | |
| 194 } | |
| 195 | |
| 196 bool OmniboxFieldTrial::ShortcutsScoringMaxRelevance( | |
| 197 OmniboxEventProto::PageClassification current_page_classification, | |
| 198 int* max_relevance) { | |
| 199 // The value of the rule is a string that encodes an integer containing | |
| 200 // the max relevance. | |
| 201 const std::string& max_relevance_str = | |
| 202 OmniboxFieldTrial::GetValueForRuleInContext( | |
| 203 kShortcutsScoringMaxRelevanceRule, current_page_classification); | |
| 204 if (max_relevance_str.empty()) | |
| 205 return false; | |
| 206 if (!base::StringToInt(max_relevance_str, max_relevance)) | |
| 207 return false; | |
| 208 return true; | |
| 209 } | |
| 210 | |
| 211 bool OmniboxFieldTrial::SearchHistoryPreventInlining( | |
| 212 OmniboxEventProto::PageClassification current_page_classification) { | |
| 213 return OmniboxFieldTrial::GetValueForRuleInContext( | |
| 214 kSearchHistoryRule, current_page_classification) == "PreventInlining"; | |
| 215 } | |
| 216 | |
| 217 bool OmniboxFieldTrial::SearchHistoryDisable( | |
| 218 OmniboxEventProto::PageClassification current_page_classification) { | |
| 219 return OmniboxFieldTrial::GetValueForRuleInContext( | |
| 220 kSearchHistoryRule, current_page_classification) == "Disable"; | |
| 221 } | |
| 222 | |
| 223 void OmniboxFieldTrial::GetDemotionsByType( | |
| 224 OmniboxEventProto::PageClassification current_page_classification, | |
| 225 DemotionMultipliers* demotions_by_type) { | |
| 226 demotions_by_type->clear(); | |
| 227 std::string demotion_rule = OmniboxFieldTrial::GetValueForRuleInContext( | |
| 228 kDemoteByTypeRule, current_page_classification); | |
| 229 // If there is no demotion rule for this context, then use the default | |
| 230 // value for that context. At the moment the default value is non-empty | |
| 231 // only for the fakebox-focus context. | |
| 232 if (demotion_rule.empty() && | |
| 233 (current_page_classification == | |
| 234 OmniboxEventProto::INSTANT_NTP_WITH_FAKEBOX_AS_STARTING_FOCUS)) | |
| 235 demotion_rule = "1:61,2:61,3:61,4:61,16:61"; | |
| 236 | |
| 237 // The value of the DemoteByType rule is a comma-separated list of | |
| 238 // {ResultType + ":" + Number} where ResultType is an AutocompleteMatchType:: | |
| 239 // Type enum represented as an integer and Number is an integer number | |
| 240 // between 0 and 100 inclusive. Relevance scores of matches of that result | |
| 241 // type are multiplied by Number / 100. 100 means no change. | |
| 242 base::StringPairs kv_pairs; | |
| 243 if (base::SplitStringIntoKeyValuePairs(demotion_rule, ':', ',', &kv_pairs)) { | |
| 244 for (base::StringPairs::const_iterator it = kv_pairs.begin(); | |
| 245 it != kv_pairs.end(); ++it) { | |
| 246 // This is a best-effort conversion; we trust the hand-crafted parameters | |
| 247 // downloaded from the server to be perfect. There's no need to handle | |
| 248 // errors smartly. | |
| 249 int k, v; | |
| 250 base::StringToInt(it->first, &k); | |
| 251 base::StringToInt(it->second, &v); | |
| 252 (*demotions_by_type)[static_cast<AutocompleteMatchType::Type>(k)] = | |
| 253 static_cast<float>(v) / 100.0f; | |
| 254 } | |
| 255 } | |
| 256 } | |
| 257 | |
| 258 void OmniboxFieldTrial::GetExperimentalHUPScoringParams( | |
| 259 HUPScoringParams* scoring_params) { | |
| 260 scoring_params->experimental_scoring_enabled = false; | |
| 261 | |
| 262 VariationParams params; | |
| 263 if (!variations::GetVariationParams(kBundledExperimentFieldTrialName, | |
| 264 ¶ms)) | |
| 265 return; | |
| 266 | |
| 267 VariationParams::const_iterator it = params.find(kHUPNewScoringEnabledParam); | |
| 268 if (it != params.end()) { | |
| 269 int enabled = 0; | |
| 270 if (base::StringToInt(it->second, &enabled)) | |
| 271 scoring_params->experimental_scoring_enabled = (enabled != 0); | |
| 272 } | |
| 273 | |
| 274 InitializeScoreBuckets(params, kHUPNewScoringTypedCountRelevanceCapParam, | |
| 275 kHUPNewScoringTypedCountHalfLifeTimeParam, | |
| 276 kHUPNewScoringTypedCountScoreBucketsParam, | |
| 277 &scoring_params->typed_count_buckets); | |
| 278 InitializeScoreBuckets(params, kHUPNewScoringVisitedCountRelevanceCapParam, | |
| 279 kHUPNewScoringVisitedCountHalfLifeTimeParam, | |
| 280 kHUPNewScoringVisitedCountScoreBucketsParam, | |
| 281 &scoring_params->visited_count_buckets); | |
| 282 } | |
| 283 | |
| 284 int OmniboxFieldTrial::HQPBookmarkValue() { | |
| 285 std::string bookmark_value_str = | |
| 286 variations::GetVariationParamValue(kBundledExperimentFieldTrialName, | |
| 287 kHQPBookmarkValueRule); | |
| 288 if (bookmark_value_str.empty()) | |
| 289 return 10; | |
| 290 // This is a best-effort conversion; we trust the hand-crafted parameters | |
| 291 // downloaded from the server to be perfect. There's no need for handle | |
| 292 // errors smartly. | |
| 293 int bookmark_value; | |
| 294 base::StringToInt(bookmark_value_str, &bookmark_value); | |
| 295 return bookmark_value; | |
| 296 } | |
| 297 | |
| 298 bool OmniboxFieldTrial::HQPAllowMatchInTLDValue() { | |
| 299 return variations::GetVariationParamValue( | |
| 300 kBundledExperimentFieldTrialName, | |
| 301 kHQPAllowMatchInTLDRule) == "true"; | |
| 302 } | |
| 303 | |
| 304 bool OmniboxFieldTrial::HQPAllowMatchInSchemeValue() { | |
| 305 return variations::GetVariationParamValue( | |
| 306 kBundledExperimentFieldTrialName, | |
| 307 kHQPAllowMatchInSchemeRule) == "true"; | |
| 308 } | |
| 309 | |
| 310 bool OmniboxFieldTrial::BookmarksIndexURLsValue() { | |
| 311 return variations::GetVariationParamValue( | |
| 312 kBundledExperimentFieldTrialName, | |
| 313 kBookmarksIndexURLsRule) == "true"; | |
| 314 } | |
| 315 | |
| 316 bool OmniboxFieldTrial::DisableInlining() { | |
| 317 return variations::GetVariationParamValue( | |
| 318 kBundledExperimentFieldTrialName, | |
| 319 kDisableInliningRule) == "true"; | |
| 320 } | |
| 321 | |
| 322 bool OmniboxFieldTrial::EnableAnswersInSuggest() { | |
| 323 const CommandLine* cl = CommandLine::ForCurrentProcess(); | |
| 324 if (cl->HasSwitch(switches::kDisableAnswersInSuggest)) | |
| 325 return false; | |
| 326 if (cl->HasSwitch(switches::kEnableAnswersInSuggest)) | |
| 327 return true; | |
| 328 | |
| 329 return variations::GetVariationParamValue( | |
| 330 kBundledExperimentFieldTrialName, | |
| 331 kAnswersInSuggestRule) == "true"; | |
| 332 } | |
| 333 | |
| 334 bool OmniboxFieldTrial::AddUWYTMatchEvenIfPromotedURLs() { | |
| 335 return variations::GetVariationParamValue( | |
| 336 kBundledExperimentFieldTrialName, | |
| 337 kAddUWYTMatchEvenIfPromotedURLsRule) == "true"; | |
| 338 } | |
| 339 | |
| 340 bool OmniboxFieldTrial::DisplayHintTextWhenPossible() { | |
| 341 return variations::GetVariationParamValue( | |
| 342 kBundledExperimentFieldTrialName, | |
| 343 kDisplayHintTextWhenPossibleRule) == "true"; | |
| 344 } | |
| 345 | |
| 346 const char OmniboxFieldTrial::kBundledExperimentFieldTrialName[] = | |
| 347 "OmniboxBundledExperimentV1"; | |
| 348 const char OmniboxFieldTrial::kShortcutsScoringMaxRelevanceRule[] = | |
| 349 "ShortcutsScoringMaxRelevance"; | |
| 350 const char OmniboxFieldTrial::kSearchHistoryRule[] = "SearchHistory"; | |
| 351 const char OmniboxFieldTrial::kDemoteByTypeRule[] = "DemoteByType"; | |
| 352 const char OmniboxFieldTrial::kHQPBookmarkValueRule[] = | |
| 353 "HQPBookmarkValue"; | |
| 354 const char OmniboxFieldTrial::kHQPAllowMatchInTLDRule[] = "HQPAllowMatchInTLD"; | |
| 355 const char OmniboxFieldTrial::kHQPAllowMatchInSchemeRule[] = | |
| 356 "HQPAllowMatchInScheme"; | |
| 357 const char OmniboxFieldTrial::kZeroSuggestRule[] = "ZeroSuggest"; | |
| 358 const char OmniboxFieldTrial::kZeroSuggestVariantRule[] = "ZeroSuggestVariant"; | |
| 359 const char OmniboxFieldTrial::kBookmarksIndexURLsRule[] = "BookmarksIndexURLs"; | |
| 360 const char OmniboxFieldTrial::kDisableInliningRule[] = "DisableInlining"; | |
| 361 const char OmniboxFieldTrial::kAnswersInSuggestRule[] = "AnswersInSuggest"; | |
| 362 const char OmniboxFieldTrial::kAddUWYTMatchEvenIfPromotedURLsRule[] = | |
| 363 "AddUWYTMatchEvenIfPromotedURLs"; | |
| 364 const char OmniboxFieldTrial::kDisplayHintTextWhenPossibleRule[] = | |
| 365 "DisplayHintTextWhenPossible"; | |
| 366 | |
| 367 const char OmniboxFieldTrial::kHUPNewScoringEnabledParam[] = | |
| 368 "HUPExperimentalScoringEnabled"; | |
| 369 const char OmniboxFieldTrial::kHUPNewScoringTypedCountRelevanceCapParam[] = | |
| 370 "TypedCountRelevanceCap"; | |
| 371 const char OmniboxFieldTrial::kHUPNewScoringTypedCountHalfLifeTimeParam[] = | |
| 372 "TypedCountHalfLifeTime"; | |
| 373 const char OmniboxFieldTrial::kHUPNewScoringTypedCountScoreBucketsParam[] = | |
| 374 "TypedCountScoreBuckets"; | |
| 375 const char OmniboxFieldTrial::kHUPNewScoringVisitedCountRelevanceCapParam[] = | |
| 376 "VisitedCountRelevanceCap"; | |
| 377 const char OmniboxFieldTrial::kHUPNewScoringVisitedCountHalfLifeTimeParam[] = | |
| 378 "VisitedCountHalfLifeTime"; | |
| 379 const char OmniboxFieldTrial::kHUPNewScoringVisitedCountScoreBucketsParam[] = | |
| 380 "VisitedCountScoreBuckets"; | |
| 381 | |
| 382 // Background and implementation details: | |
| 383 // | |
| 384 // Each experiment group in any field trial can come with an optional set of | |
| 385 // parameters (key-value pairs). In the bundled omnibox experiment | |
| 386 // (kBundledExperimentFieldTrialName), each experiment group comes with a | |
| 387 // list of parameters in the form: | |
| 388 // key=<Rule>: | |
| 389 // <OmniboxEventProto::PageClassification (as an int)>: | |
| 390 // <whether Instant Extended is enabled (as a 1 or 0)> | |
| 391 // (note that there are no linebreaks in keys; this format is for | |
| 392 // presentation only> | |
| 393 // value=<arbitrary string> | |
| 394 // Both the OmniboxEventProto::PageClassification and the Instant Extended | |
| 395 // entries can be "*", which means this rule applies for all values of the | |
| 396 // matching portion of the context. | |
| 397 // One example parameter is | |
| 398 // key=SearchHistory:6:1 | |
| 399 // value=PreventInlining | |
| 400 // This means in page classification context 6 (a search result page doing | |
| 401 // search term replacement) with Instant Extended enabled, the SearchHistory | |
| 402 // experiment should PreventInlining. | |
| 403 // | |
| 404 // When an exact match to the rule in the current context is missing, we | |
| 405 // give preference to a wildcard rule that matches the instant extended | |
| 406 // context over a wildcard rule that matches the page classification | |
| 407 // context. Hopefully, though, users will write their field trial configs | |
| 408 // so as not to rely on this fall back order. | |
| 409 // | |
| 410 // In short, this function tries to find the value associated with key | |
| 411 // |rule|:|page_classification|:|instant_extended|, failing that it looks up | |
| 412 // |rule|:*:|instant_extended|, failing that it looks up | |
| 413 // |rule|:|page_classification|:*, failing that it looks up |rule|:*:*, | |
| 414 // and failing that it returns the empty string. | |
| 415 std::string OmniboxFieldTrial::GetValueForRuleInContext( | |
| 416 const std::string& rule, | |
| 417 OmniboxEventProto::PageClassification page_classification) { | |
| 418 VariationParams params; | |
| 419 if (!variations::GetVariationParams(kBundledExperimentFieldTrialName, | |
| 420 ¶ms)) { | |
| 421 return std::string(); | |
| 422 } | |
| 423 const std::string page_classification_str = | |
| 424 base::IntToString(static_cast<int>(page_classification)); | |
| 425 const std::string instant_extended = | |
| 426 chrome::IsInstantExtendedAPIEnabled() ? "1" : "0"; | |
| 427 // Look up rule in this exact context. | |
| 428 VariationParams::const_iterator it = params.find( | |
| 429 rule + ":" + page_classification_str + ":" + instant_extended); | |
| 430 if (it != params.end()) | |
| 431 return it->second; | |
| 432 // Fall back to the global page classification context. | |
| 433 it = params.find(rule + ":*:" + instant_extended); | |
| 434 if (it != params.end()) | |
| 435 return it->second; | |
| 436 // Fall back to the global instant extended context. | |
| 437 it = params.find(rule + ":" + page_classification_str + ":*"); | |
| 438 if (it != params.end()) | |
| 439 return it->second; | |
| 440 // Look up rule in the global context. | |
| 441 it = params.find(rule + ":*:*"); | |
| 442 return (it != params.end()) ? it->second : std::string(); | |
| 443 } | |
| OLD | NEW |