synthetic_trial_registry.cc 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. // Copyright 2017 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/synthetic_trial_registry.h"
  5. #include <algorithm>
  6. #include "base/containers/cxx20_erase.h"
  7. #include "base/metrics/histogram_functions.h"
  8. #include "base/observer_list.h"
  9. #include "base/strings/string_number_conversions.h"
  10. #include "components/variations/active_field_trials.h"
  11. #include "components/variations/hashing.h"
  12. #include "components/variations/variations_associated_data.h"
  13. namespace variations {
  14. namespace internal {
  15. const base::Feature kExternalExperimentAllowlist{
  16. "ExternalExperimentAllowlist", base::FEATURE_ENABLED_BY_DEFAULT};
  17. } // namespace internal
  18. SyntheticTrialRegistry::SyntheticTrialRegistry(
  19. bool enable_external_experiment_allowlist)
  20. : enable_external_experiment_allowlist_(
  21. enable_external_experiment_allowlist &&
  22. base::FeatureList::IsEnabled(
  23. internal::kExternalExperimentAllowlist)) {}
  24. SyntheticTrialRegistry::SyntheticTrialRegistry()
  25. : enable_external_experiment_allowlist_(base::FeatureList::IsEnabled(
  26. internal::kExternalExperimentAllowlist)) {}
  27. SyntheticTrialRegistry::~SyntheticTrialRegistry() = default;
  28. void SyntheticTrialRegistry::AddSyntheticTrialObserver(
  29. SyntheticTrialObserver* observer) {
  30. synthetic_trial_observer_list_.AddObserver(observer);
  31. if (!synthetic_trial_groups_.empty())
  32. observer->OnSyntheticTrialsChanged(synthetic_trial_groups_);
  33. }
  34. void SyntheticTrialRegistry::RemoveSyntheticTrialObserver(
  35. SyntheticTrialObserver* observer) {
  36. synthetic_trial_observer_list_.RemoveObserver(observer);
  37. }
  38. void SyntheticTrialRegistry::RegisterExternalExperiments(
  39. const std::string& fallback_study_name,
  40. const std::vector<int>& experiment_ids,
  41. SyntheticTrialRegistry::OverrideMode mode) {
  42. DCHECK(!fallback_study_name.empty());
  43. base::FieldTrialParams params;
  44. if (enable_external_experiment_allowlist_ &&
  45. !GetFieldTrialParamsByFeature(internal::kExternalExperimentAllowlist,
  46. &params)) {
  47. return;
  48. }
  49. // When overriding previous external experiments, remove them now.
  50. if (mode == kOverrideExistingIds) {
  51. auto is_external = [](const SyntheticTrialGroup& group) {
  52. return group.is_external();
  53. };
  54. base::EraseIf(synthetic_trial_groups_, is_external);
  55. }
  56. const base::TimeTicks start_time = base::TimeTicks::Now();
  57. int trials_added = 0;
  58. for (int experiment_id : experiment_ids) {
  59. const std::string experiment_id_str = base::NumberToString(experiment_id);
  60. const base::StringPiece study_name =
  61. GetStudyNameForExpId(fallback_study_name, params, experiment_id_str);
  62. if (study_name.empty())
  63. continue;
  64. const uint32_t trial_hash = HashName(study_name);
  65. // If existing ids shouldn't be overridden, skip entries whose study names
  66. // are already registered.
  67. if (mode == kDoNotOverrideExistingIds) {
  68. auto matches_trial = [trial_hash](const SyntheticTrialGroup& group) {
  69. return group.id().name == trial_hash;
  70. };
  71. const auto& groups = synthetic_trial_groups_;
  72. if (std::any_of(groups.begin(), groups.end(), matches_trial)) {
  73. continue;
  74. }
  75. }
  76. const uint32_t group_hash = HashName(experiment_id_str);
  77. // Since external experiments are not based on Chrome's low entropy source,
  78. // they are only sent to Google web properties for signed-in users to make
  79. // sure they couldn't be used to identify a user that's not signed-in.
  80. AssociateGoogleVariationIDForceHashes(
  81. GOOGLE_WEB_PROPERTIES_SIGNED_IN, {trial_hash, group_hash},
  82. static_cast<VariationID>(experiment_id));
  83. SyntheticTrialGroup entry(
  84. study_name, experiment_id_str,
  85. variations::SyntheticTrialAnnotationMode::kNextLog);
  86. entry.SetStartTime(start_time);
  87. entry.SetIsExternal(true);
  88. synthetic_trial_groups_.push_back(entry);
  89. trials_added++;
  90. }
  91. base::UmaHistogramCounts100("UMA.ExternalExperiment.GroupCount",
  92. trials_added);
  93. if (trials_added > 0)
  94. NotifySyntheticTrialObservers();
  95. }
  96. void SyntheticTrialRegistry::RegisterSyntheticFieldTrial(
  97. const SyntheticTrialGroup& trial) {
  98. for (auto& entry : synthetic_trial_groups_) {
  99. if (entry.id().name == trial.id().name) {
  100. // Don't necessarily need to notify observers when setting
  101. // |annotation_mode| as it is only used when producing metrics reports
  102. // and does not affect variations service.
  103. entry.SetAnnotationMode(trial.annotation_mode());
  104. if (entry.id().group != trial.id().group) {
  105. entry.SetGroupName(trial.group_name());
  106. entry.SetStartTime(base::TimeTicks::Now());
  107. NotifySyntheticTrialObservers();
  108. }
  109. return;
  110. }
  111. }
  112. SyntheticTrialGroup trial_group = trial;
  113. trial_group.SetStartTime(base::TimeTicks::Now());
  114. synthetic_trial_groups_.push_back(trial_group);
  115. NotifySyntheticTrialObservers();
  116. }
  117. base::StringPiece SyntheticTrialRegistry::GetStudyNameForExpId(
  118. const std::string& fallback_study_name,
  119. const base::FieldTrialParams& params,
  120. const std::string& experiment_id) {
  121. if (!enable_external_experiment_allowlist_)
  122. return fallback_study_name;
  123. const auto it = params.find(experiment_id);
  124. if (it == params.end())
  125. return base::StringPiece();
  126. // To support additional parameters being passed, besides the study name,
  127. // truncate the study name at the first ',' character.
  128. // For example, for an entry like {"1234": "StudyName,FOO"}, we only want the
  129. // "StudyName" part. This allows adding support for additional things like FOO
  130. // in the future without backwards compatibility problems.
  131. const size_t comma_pos = it->second.find(',');
  132. const size_t truncate_pos =
  133. (comma_pos == std::string::npos ? it->second.length() : comma_pos);
  134. return base::StringPiece(it->second.data(), truncate_pos);
  135. }
  136. void SyntheticTrialRegistry::NotifySyntheticTrialObservers() {
  137. for (SyntheticTrialObserver& observer : synthetic_trial_observer_list_) {
  138. observer.OnSyntheticTrialsChanged(synthetic_trial_groups_);
  139. }
  140. }
  141. void SyntheticTrialRegistry::GetSyntheticFieldTrialsOlderThan(
  142. base::TimeTicks time,
  143. std::vector<ActiveGroupId>* synthetic_trials,
  144. base::StringPiece suffix) const {
  145. DCHECK(synthetic_trials);
  146. synthetic_trials->clear();
  147. base::FieldTrial::ActiveGroups active_groups;
  148. for (const auto& entry : synthetic_trial_groups_) {
  149. if (entry.start_time() <= time ||
  150. entry.annotation_mode() == SyntheticTrialAnnotationMode::kCurrentLog)
  151. active_groups.push_back(entry.active_group());
  152. }
  153. GetFieldTrialActiveGroupIdsForActiveGroups(suffix, active_groups,
  154. synthetic_trials);
  155. }
  156. } // namespace variations